package monstersirenfetch_test import ( "bytes" "encoding/json" "io" "os" "reflect" "strconv" "testing" "unsafe" . "git.gensokyo.uk/yonah/monstersirenfetch" ) func TestNullableString(t *testing.T) { checkJSONRoundTripM(t, []jsonRoundTripTestCase[NullableString]{ {"zero", nil, `null`, ""}, {"string", nil, `"null"`, "null"}, {"invalid json", newSyntaxError("unexpected end of JSON input", 5), `"null`, "\x00"}, }) } func TestStringInt(t *testing.T) { checkJSONRoundTripM(t, []jsonRoundTripTestCase[StringInt]{ {"valid long", nil, `"3735928559"`, 0xdeadbeef}, {"valid pad 4", nil, `"0000"`, 0}, {"valid 4", nil, `"1000"`, 1e3}, {"valid pad 6", nil, `"010000"`, 1e4}, {"valid 6", nil, `"100000"`, 1e5}, {"invalid json", newSyntaxError("unexpected end of JSON input", 11), `"3735928559`, -1}, {"invalid number", &strconv.NumError{Func: "Atoi", Num: ":3735928559", Err: strconv.ErrSyntax}, `":3735928559"`, -1}, }) } func newSyntaxError(msg string, offset int64) *json.SyntaxError { e := &json.SyntaxError{Offset: offset} msgV := reflect.ValueOf(e).Elem().FieldByName("msg") reflect.NewAt(msgV.Type(), unsafe.Pointer(msgV.UnsafeAddr())).Elem().SetString(msg) return e } type jsonRoundTripTestCase[T comparable] struct { name string wantErr error data string val T } func checkJSONRoundTripM[T comparable](t *testing.T, testCases []jsonRoundTripTestCase[T]) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { if tc.wantErr == nil { t.Run("marshal", func(t *testing.T) { if got, err := json.Marshal(&tc.val); err != nil { t.Fatalf("Marshal: error = %v", err) } else if string(got) != tc.data { t.Errorf("Marshal: %s, want %s", string(got), tc.data) } }) } t.Run("unmarshal", func(t *testing.T) { var got T err := json.Unmarshal([]byte(tc.data), &got) if !reflect.DeepEqual(err, tc.wantErr) { t.Errorf("Unmarshal: error = %v, want %v", err, tc.wantErr) } if tc.wantErr == nil { if got != tc.val { t.Errorf("Unmarshal: %v, want %v", got, tc.val) } } }) }) } } func checkJSONRoundTrip[T any](t *testing.T, v T, data []byte) { t.Run("marshal", func(t *testing.T) { buf := new(bytes.Buffer) buf.Grow(len(data)) err := NewEncoder(buf).Encode(&v) if err != nil { t.Fatalf("Marshal: error = %v", err) } got := buf.Bytes()[:buf.Len()-1] if string(got) != string(data) { t.Errorf("Marshal:\n%s\nwant\n%s", string(got), string(data)) } }) t.Run("unmarshal", func(t *testing.T) { var got T if err := json.Unmarshal(data, &got); err != nil { t.Fatalf("Unmarshal: error = %v", err) } if !reflect.DeepEqual(&got, &v) { t.Errorf("Unmarshal:\n%#v\nwant\n%#v", got, v) } }) } type errorCloser struct{ io.Reader } func (errorCloser) Close() error { return os.ErrInvalid } type nopCloser struct{ io.Reader } func (nopCloser) Close() error { return nil }