package jsontypes_test import ( "testing" "github.com/tendermint/tendermint/internal/jsontypes" ) type testPtrType struct { Field string `json:"field"` } func (*testPtrType) TypeTag() string { return "test/PointerType" } func (t *testPtrType) Value() string { return t.Field } type testBareType struct { Field string `json:"field"` } func (testBareType) TypeTag() string { return "test/BareType" } func (t testBareType) Value() string { return t.Field } type fielder interface{ Value() string } func TestRoundTrip(t *testing.T) { t.Run("MustRegister_ok", func(t *testing.T) { defer func() { if x := recover(); x != nil { t.Fatalf("Registration panicked: %v", x) } }() jsontypes.MustRegister((*testPtrType)(nil)) jsontypes.MustRegister(testBareType{}) }) t.Run("MustRegister_fail", func(t *testing.T) { defer func() { if x := recover(); x != nil { t.Logf("Got expected panic: %v", x) } }() jsontypes.MustRegister((*testPtrType)(nil)) t.Fatal("Registration should not have succeeded") }) t.Run("Marshal_nilTagged", func(t *testing.T) { bits, err := jsontypes.Marshal(nil) if err != nil { t.Fatalf("Marshal failed: %v", err) } if got := string(bits); got != "null" { t.Errorf("Marshal nil: got %#q, want null", got) } }) t.Run("RoundTrip_pointerType", func(t *testing.T) { const wantEncoded = `{"type":"test/PointerType","value":{"field":"hello"}}` obj := testPtrType{Field: "hello"} bits, err := jsontypes.Marshal(&obj) if err != nil { t.Fatalf("Marshal %T failed: %v", obj, err) } if got := string(bits); got != wantEncoded { t.Errorf("Marshal %T: got %#q, want %#q", obj, got, wantEncoded) } var cmp testPtrType if err := jsontypes.Unmarshal(bits, &cmp); err != nil { t.Errorf("Unmarshal %#q failed: %v", string(bits), err) } if obj != cmp { t.Errorf("Unmarshal %#q: got %+v, want %+v", string(bits), cmp, obj) } }) t.Run("RoundTrip_bareType", func(t *testing.T) { const wantEncoded = `{"type":"test/BareType","value":{"field":"hello"}}` obj := testBareType{Field: "hello"} bits, err := jsontypes.Marshal(&obj) if err != nil { t.Fatalf("Marshal %T failed: %v", obj, err) } if got := string(bits); got != wantEncoded { t.Errorf("Marshal %T: got %#q, want %#q", obj, got, wantEncoded) } var cmp testBareType if err := jsontypes.Unmarshal(bits, &cmp); err != nil { t.Errorf("Unmarshal %#q failed: %v", string(bits), err) } if obj != cmp { t.Errorf("Unmarshal %#q: got %+v, want %+v", string(bits), cmp, obj) } }) t.Run("Unmarshal_nilPointer", func(t *testing.T) { var obj *testBareType // Unmarshaling to a nil pointer target should report an error. if err := jsontypes.Unmarshal([]byte(`null`), obj); err == nil { t.Errorf("Unmarshal nil: got %+v, wanted error", obj) } else { t.Logf("Unmarshal correctly failed: %v", err) } }) t.Run("Unmarshal_bareType", func(t *testing.T) { const want = "foobar" const input = `{"type":"test/BareType","value":{"field":"` + want + `"}}` var obj testBareType if err := jsontypes.Unmarshal([]byte(input), &obj); err != nil { t.Fatalf("Unmarshal failed: %v", err) } if obj.Field != want { t.Errorf("Unmarshal result: got %q, want %q", obj.Field, want) } }) t.Run("Unmarshal_bareType_interface", func(t *testing.T) { const want = "foobar" const input = `{"type":"test/BareType","value":{"field":"` + want + `"}}` var obj fielder if err := jsontypes.Unmarshal([]byte(input), &obj); err != nil { t.Fatalf("Unmarshal failed: %v", err) } if got := obj.Value(); got != want { t.Errorf("Unmarshal result: got %q, want %q", got, want) } }) t.Run("Unmarshal_pointerType", func(t *testing.T) { const want = "bazquux" const input = `{"type":"test/PointerType","value":{"field":"` + want + `"}}` var obj testPtrType if err := jsontypes.Unmarshal([]byte(input), &obj); err != nil { t.Fatalf("Unmarshal failed: %v", err) } if obj.Field != want { t.Errorf("Unmarshal result: got %q, want %q", obj.Field, want) } }) t.Run("Unmarshal_pointerType_interface", func(t *testing.T) { const want = "foobar" const input = `{"type":"test/PointerType","value":{"field":"` + want + `"}}` var obj fielder if err := jsontypes.Unmarshal([]byte(input), &obj); err != nil { t.Fatalf("Unmarshal failed: %v", err) } if got := obj.Value(); got != want { t.Errorf("Unmarshal result: got %q, want %q", got, want) } }) t.Run("Unmarshal_unknownTypeTag", func(t *testing.T) { const input = `{"type":"test/Nonesuch","value":null}` // An unregistered type tag in a valid envelope should report an error. var obj interface{} if err := jsontypes.Unmarshal([]byte(input), &obj); err == nil { t.Errorf("Unmarshal: got %+v, wanted error", obj) } else { t.Logf("Unmarshal correctly failed: %v", err) } }) t.Run("Unmarshal_similarTarget", func(t *testing.T) { const want = "zootie-zoot-zoot" const input = `{"type":"test/PointerType","value":{"field":"` + want + `"}}` // The target has a compatible (i.e., assignable) shape to the registered // type. This should work even though it's not the original named type. var cmp struct { Field string `json:"field"` } if err := jsontypes.Unmarshal([]byte(input), &cmp); err != nil { t.Errorf("Unmarshal %#q failed: %v", input, err) } else if cmp.Field != want { t.Errorf("Unmarshal result: got %q, want %q", cmp.Field, want) } }) }