package response_test import ( "bytes" "encoding/json" "math" "os" "path/filepath" "strconv" "testing" "git.yuntuo.hk/k2safe/go-omni/config" "git.yuntuo.hk/k2safe/go-omni/response" ) func TestCodeTypeWidthsAndWireValues(t *testing.T) { for _, tc := range []struct { kind string min, max int64 }{ {"int32", math.MinInt32, math.MaxInt32}, {"int64", math.MinInt64, math.MaxInt64}, {"integer", -9007199254740991, 9007199254740991}, } { t.Run(tc.kind, func(t *testing.T) { cfg := response.Config{CodeType: tc.kind, SuccessCode: tc.max, ErrorCode: tc.min, Codes: map[string]any{"ZERO": 0}} formatter, err := response.New(cfg) if err != nil { t.Fatal(err) } for _, sample := range []struct { result response.Result want int64 }{ {response.Result{Status: 200, Code: "OK"}, tc.max}, {response.Result{Status: 500, Code: "INTERNAL"}, tc.min}, {response.Result{Status: 409, Code: "ZERO"}, 0}, } { payload, err := formatter.Encode(sample.result) if err != nil { t.Fatal(err) } decoder := json.NewDecoder(bytes.NewReader(payload.Body)) decoder.UseNumber() var body map[string]any if err = decoder.Decode(&body); err != nil { t.Fatal(err) } code, ok := body["code"].(json.Number) if !ok || code.String() != strconv.FormatInt(sample.want, 10) { t.Fatalf("precision/type lost: %s", payload.Body) } } }) } for _, tc := range []struct { kind string value any }{ {"int32", int64(math.MaxInt32) + 1}, {"int32", int64(math.MinInt32) - 1}, {"int64", json.Number("9223372036854775808")}, {"int64", json.Number("-9223372036854775809")}, {"int64", uint64(math.MaxUint64)}, {"int64", float64(math.MaxInt64)}, {"int32", 1.5}, {"int64", json.Number("1.5")}, {"int64", "200"}, {"string", 200}, } { for _, field := range []string{"successCode", "errorCode", "codes"} { cfg := response.Config{CodeType: tc.kind, SuccessCode: 0, ErrorCode: 1} if tc.kind == "string" { cfg.SuccessCode = "OK" cfg.ErrorCode = "ERROR" } switch field { case "successCode": cfg.SuccessCode = tc.value case "errorCode": cfg.ErrorCode = tc.value case "codes": cfg.Codes = map[string]any{"MAPPED": tc.value} } if _, err := response.New(cfg); err == nil { t.Fatalf("%s %s accepted %#v", tc.kind, field, tc.value) } } } for _, kind := range []string{"int32", "int64"} { if _, err := response.New(response.Config{CodeType: kind}); err == nil { t.Fatalf("%s accepted missing numeric fallbacks", kind) } } f := response.MustNew(response.Config{CodeType: "string", SuccessCode: "SUCCESS", ErrorCode: "ERROR", Codes: map[string]any{"DENIED": "403"}}) if got := f.Value(response.Result{Status: 403, Code: "DENIED"})["code"]; got != "403" { t.Fatal(got) } } func TestInt64CodesFromConfigurationKeepPrecision(t *testing.T) { for ext, body := range map[string]string{ "json": `{"response":{"codeType":"int64","successCode":9223372036854775807,"errorCode":-9223372036854775808,"codes":{"EXACT":9007199254740993}}}`, "yaml": "response:\n codeType: int64\n successCode: 9223372036854775807\n errorCode: -9223372036854775808\n codes:\n EXACT: 9007199254740993\n", } { path := filepath.Join(t.TempDir(), "config."+ext) if err := os.WriteFile(path, []byte(body), 0600); err != nil { t.Fatal(err) } cfg, err := config.LoadFile(path) if err != nil { t.Fatalf("%s: %v", ext, err) } f := response.MustNew(cfg.Response) if f.Config().SuccessCode != int64(math.MaxInt64) || f.Config().ErrorCode != int64(math.MinInt64) || f.Config().Codes["EXACT"] != int64(9007199254740993) { t.Fatalf("%s: precision lost: %#v", ext, f.Config()) } } var cfg response.Config if err := json.Unmarshal([]byte(`{"codeType":"int64","successCode":9223372036854775807,"errorCode":0}`), &cfg); err != nil { t.Fatal(err) } if response.MustNew(cfg).Config().SuccessCode != int64(math.MaxInt64) { t.Fatal("json.Unmarshal lost precision") } if err := json.Unmarshal([]byte(`{"unknownOption":1}`), &cfg); err == nil { t.Fatal("custom decoder bypassed unknown-field validation") } }