mirror of
https://github.com/warmbly/warmbly.git
synced 2026-10-03 16:02:02 +00:00
99 lines
4.2 KiB
Go
99 lines
4.2 KiB
Go
package errx
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type bindSend struct {
|
|
To []string `json:"to" binding:"required"`
|
|
Subject string `json:"subject" binding:"required"`
|
|
Mode string `json:"send_mode" binding:"omitempty,oneof=instant smart"`
|
|
Limit int `json:"limit" binding:"omitempty,max=10"`
|
|
Inner struct {
|
|
Name string `json:"name" binding:"required"`
|
|
} `json:"inner"`
|
|
}
|
|
|
|
type bindContact struct {
|
|
Email string `json:"email" binding:"required"`
|
|
Due *time.Time `json:"due"`
|
|
Campaign uuid.UUID `json:"campaign"`
|
|
Tags []uuid.UUID `json:"tags"`
|
|
}
|
|
|
|
func bindBody(t *testing.T, body string, dst any) error {
|
|
t.Helper()
|
|
gin.SetMode(gin.TestMode)
|
|
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
return c.ShouldBindJSON(dst)
|
|
}
|
|
|
|
func TestInvalidBody(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
body string
|
|
dst func() any
|
|
want []string
|
|
}{
|
|
{"empty", "", func() any { return &bindSend{} }, []string{"request body is empty"}},
|
|
{"syntax", `{"to": [}`, func() any { return &bindSend{} }, []string{"not valid JSON", "at byte 9"}},
|
|
{"truncated", `{"to": ["a"`, func() any { return &bindSend{} }, []string{"ends before the value is complete"}},
|
|
{"required fields on an empty object", `{}`, func() any { return &bindSend{} }, []string{`"to" is required`, `"subject" is required`, `"inner.name" is required`}},
|
|
{"oneof", `{"to":["a"],"subject":"s","send_mode":"later","inner":{"name":"n"}}`, func() any { return &bindSend{} }, []string{`"send_mode" must be one of: instant, smart`}},
|
|
{"max", `{"to":["a"],"subject":"s","limit":11,"inner":{"name":"n"}}`, func() any { return &bindSend{} }, []string{`"limit" must be at most 10`}},
|
|
{"field of the wrong type", `{"to":"a@b.co","subject":"s"}`, func() any { return &bindSend{} }, []string{`Field "to" must be a JSON array, not a JSON string`}},
|
|
{"nested field of the wrong type", `{"to":["a"],"subject":"s","inner":{"name":5}}`, func() any { return &bindSend{} }, []string{`Field "inner.name" must be a JSON string, not a JSON number`}},
|
|
{"object where an array is expected", `{"email":"a@b.co"}`, func() any { return &[]bindContact{} }, []string{"must be a JSON array, not a JSON object"}},
|
|
{"array where an object is expected", `[]`, func() any { return &bindSend{} }, []string{"must be a JSON object, not a JSON array"}},
|
|
{"slice element validation", `[{"email":"a@b.co"},{}]`, func() any { return &[]bindContact{} }, []string{`"email" is required`}},
|
|
{"uuid", `{"email":"a@b.co","campaign":"nope"}`, func() any { return &bindContact{} }, []string{"should be a UUID"}},
|
|
{"time", `{"email":"a@b.co","due":"tomorrow"}`, func() any { return &bindContact{} }, []string{"not RFC 3339", `"tomorrow"`}},
|
|
{"uuid of the wrong JSON type", `{"email":"a@b.co","campaign":5}`, func() any { return &bindContact{} }, []string{`Field "campaign" must be a JSON string, not a JSON number`}},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := bindBody(t, tc.body, tc.dst())
|
|
if err == nil {
|
|
t.Fatalf("bind of %q succeeded, want an error", tc.body)
|
|
}
|
|
got := InvalidBody(err)
|
|
if got.Code != BadRequest {
|
|
t.Fatalf("code = %d, want %d", got.Code, BadRequest)
|
|
}
|
|
if strings.Contains(got.Message, "malformed JSON") {
|
|
t.Fatalf("message still says malformed JSON: %q", got.Message)
|
|
}
|
|
for _, w := range tc.want {
|
|
if !strings.Contains(got.Message, w) {
|
|
t.Errorf("message %q does not contain %q", got.Message, w)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestInvalidBodyCapsTheList(t *testing.T) {
|
|
type many struct {
|
|
A string `json:"a" binding:"required"`
|
|
B string `json:"b" binding:"required"`
|
|
C string `json:"c" binding:"required"`
|
|
D string `json:"d" binding:"required"`
|
|
E string `json:"e" binding:"required"`
|
|
F string `json:"f" binding:"required"`
|
|
G string `json:"g" binding:"required"`
|
|
}
|
|
got := InvalidBody(bindBody(t, `{}`, &many{}))
|
|
if !strings.Contains(got.Message, "and 2 more") || strings.Contains(got.Message, `"f"`) {
|
|
t.Fatalf("message = %q, want five fields and a count of the rest", got.Message)
|
|
}
|
|
}
|