package api
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
"craftdesign.group/quarry/internal/oltp"
"craftdesign.group/quarry/internal/ops"
)
// A JSON field of the wrong type is refused in the spec's words, never
// the decoder's.
//
// forum.moderation took `enabled` as a boolean and a caller sending the
// old "on" was told `cannot unmarshal string into Go struct field
// moderationSetIn.enabled of type bool` (jva, #1085): a struct and a
// Go type the caller has never seen, where every other refusal on the
// body says which field and what it takes. One rewording at the decode,
// so every op's bodies read the same.
func TestAJSONFieldOfTheWrongTypeIsRefusedInTheSpecsWords(t *testing.T) {
type in struct {
Flag bool `json:"flag"`
Count int `json:"count"`
Names []string `json:"names"`
}
r := ops.NewRegistry()
if err := ops.Register(r, ops.Op[in, struct{}]{
Name: "typed.post", Kind: ops.Command, Auth: ops.Anonymous,
Route: "POST /api/v1/typed", Doc: "test double with a boolean, a number and a list",
Run: func(context.Context, in) (struct{}, error) { return struct{}{}, nil },
}); err != nil {
t.Fatal(err)
}
h, err := RouterWithCaps(r, "test", func(_, _ string) CapSet { return oltp.CapSet("s") }, nil)
if err != nil {
t.Fatal(err)
}
for _, c := range []struct{ body, want string }{
{`{"flag": "on"}`, `field "flag" must be a boolean, not a string`},
{`{"count": "3"}`, `field "count" must be a number, not a string`},
{`{"names": "x"}`, `field "names" must be a list, not a string`},
{`{"flag": 1}`, `field "flag" must be a boolean, not a number`},
} {
req := httptest.NewRequest("POST", "/api/v1/typed", strings.NewReader(c.body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
h.ServeHTTP(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("%s: %d %s", c.body, w.Code, w.Body.String())
continue
}
got := errorOf(t, w.Body.Bytes())
if !strings.Contains(got, c.want) || strings.Contains(got, "Go struct") || strings.Contains(got, "unmarshal") {
t.Errorf("%s: %q, want it to say %q and nothing about Go", c.body, got, c.want)
}
}
// The right types still land.
req := httptest.NewRequest("POST", "/api/v1/typed", strings.NewReader(`{"flag": true, "count": 3, "names": ["a"]}`))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
h.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("well-typed body: %d %s", w.Code, w.Body.String())
}
}