返回 DeepSeek-Reasonix
types_test.go
根目录 / desktop / internal / hostrpc / types_test.go
1 package hostrpc
2
3 import (
4 "encoding/json"
5 "reflect"
6 "testing"
7 "time"
8 )
9
10 type shapeBase struct {
11 ID string `json:"id"`
12 Shadow string `json:"shadow"`
13 _ int
14 }
15
16 type shapeLevel int
17
18 func (l shapeLevel) MarshalText() ([]byte, error) { return []byte("level"), nil }
19
20 type shapeNode struct {
21 Children []shapeNode `json:"children"`
22 }
23
24 type shapeDTO struct {
25 shapeBase
26 Shadow int `json:"shadow"`
27 When time.Time `json:"when"`
28 Raw json.RawMessage `json:"raw"`
29 Blob []byte `json:"blob"`
30 Digits [4]byte `json:"digits"`
31 Any any `json:"any"`
32 Ptr *shapeBase `json:"ptr"`
33 Opt string `json:"opt,omitempty"`
34 Zero int `json:"zero,omitzero"`
35 Quoted int64 `json:"quoted,string"`
36 ByName map[string]int `json:"byName"`
37 ByID map[int]string `json:"byId"`
38 Level shapeLevel `json:"level"`
39 Levels map[shapeLevel]bool `json:"levels"`
40 Inline struct{ X float64 } `json:"inline"`
41 Tree shapeNode `json:"tree"`
42 Untagged bool
43 Skipped string `json:"-"`
44 _ string
45 }
46
47 type shapeAmbiguousA struct{ Name string }
48 type shapeAmbiguousB struct{ Name string }
49 type shapeAmbiguous struct {
50 shapeAmbiguousA
51 shapeAmbiguousB
52 Keep string `json:"keep"`
53 }
54
55 type shapeBadKey struct {
56 M map[float64]string
57 }
58
59 type shapeBadField struct {
60 F chan int
61 }
62
63 func describe(t *testing.T, typ reflect.Type) (TypeRef, map[string]ObjectType) {
64 t.Helper()
65 c := newTypeCollector()
66 ref, err := c.ref(typ, "hint")
67 if err != nil {
68 t.Fatal(err)
69 }
70 return ref, c.types
71 }
72
73 func fieldByName(t *testing.T, obj ObjectType, name string) Field {
74 t.Helper()
75 for _, f := range obj.Fields {
76 if f.Name == name {
77 return f
78 }
79 }
80 t.Fatalf("field %q missing from %+v", name, obj.Fields)
81 return Field{}
82 }
83
84 func TestTypeRefFollowsEncodingJSONRules(t *testing.T) {
85 ref, types := describe(t, reflect.TypeFor[shapeDTO]())
86 if ref.Kind != KindObject || ref.Ref != "hostrpc.shapeDTO" {
87 t.Fatalf("ref = %+v", ref)
88 }
89 dto := types["hostrpc.shapeDTO"]
90 want := map[string]TypeRef{
91 "id": {Kind: KindString},
92 "shadow": {Kind: KindInteger},
93 "when": {Kind: KindString},
94 "raw": {Kind: KindAny},
95 "blob": {Kind: KindString},
96 "digits": {Kind: KindArray, Elem: &TypeRef{Kind: KindInteger}},
97 "any": {Kind: KindAny},
98 "ptr": {Kind: KindNullable, Elem: &TypeRef{Kind: KindObject, Ref: "hostrpc.shapeBase"}},
99 "opt": {Kind: KindString},
100 "zero": {Kind: KindInteger},
101 "quoted": {Kind: KindString},
102 "byName": {Kind: KindMap, Key: &TypeRef{Kind: KindString}, Elem: &TypeRef{Kind: KindInteger}},
103 "byId": {Kind: KindMap, Key: &TypeRef{Kind: KindInteger}, Elem: &TypeRef{Kind: KindString}},
104 "level": {Kind: KindString},
105 "levels": {Kind: KindMap, Key: &TypeRef{Kind: KindString}, Elem: &TypeRef{Kind: KindBoolean}},
106 "inline": {Kind: KindObject, Ref: "hostrpc.shapeDTO.Inline"},
107 "tree": {Kind: KindObject, Ref: "hostrpc.shapeNode"},
108 "Untagged": {Kind: KindBoolean},
109 }
110 for name, wantRef := range want {
111 if got := fieldByName(t, dto, name).Type; !reflect.DeepEqual(got, wantRef) {
112 t.Errorf("%s = %+v, want %+v", name, got, wantRef)
113 }
114 }
115 if len(dto.Fields) != len(want) {
116 t.Fatalf("got %d fields, want %d: %+v", len(dto.Fields), len(want), dto.Fields)
117 }
118 for _, name := range []string{"opt", "zero", "ptr"} {
119 if !fieldByName(t, dto, name).Optional {
120 t.Errorf("%s must be optional", name)
121 }
122 }
123 if fieldByName(t, dto, "id").Optional || fieldByName(t, dto, "when").Optional {
124 t.Error("required members must not be optional")
125 }
126 if dto.Fields[0].Name != "id" || dto.Fields[1].Name != "shadow" {
127 t.Fatalf("embedded members must keep declaration order: %+v", dto.Fields[:3])
128 }
129 if _, ok := types["hostrpc.shapeBase"]; !ok {
130 t.Fatal("embedded struct reached through a pointer must be collected")
131 }
132 node := types["hostrpc.shapeNode"]
133 if got := fieldByName(t, node, "children").Type; got.Kind != KindArray || got.Elem.Ref != "hostrpc.shapeNode" {
134 t.Fatalf("recursive type = %+v", got)
135 }
136 if inline := types["hostrpc.shapeDTO.Inline"]; len(inline.Fields) != 1 || inline.Fields[0].Name != "X" {
137 t.Fatalf("anonymous struct = %+v", inline)
138 }
139 }
140
141 func TestTypeRefDropsAmbiguousEmbeddedMembers(t *testing.T) {
142 _, types := describe(t, reflect.TypeFor[shapeAmbiguous]())
143 obj := types["hostrpc.shapeAmbiguous"]
144 if len(obj.Fields) != 1 || obj.Fields[0].Name != "keep" {
145 t.Fatalf("fields = %+v", obj.Fields)
146 }
147 }
148
149 func TestTypeRefRejectsUnserialisableShapes(t *testing.T) {
150 for _, typ := range []reflect.Type{
151 reflect.TypeFor[shapeBadKey](),
152 reflect.TypeFor[shapeBadField](),
153 reflect.TypeFor[func()](),
154 reflect.TypeFor[complex128](),
155 } {
156 if _, err := newTypeCollector().ref(typ, "hint"); err == nil {
157 t.Errorf("%s must be rejected", typ)
158 }
159 }
160 }
161
162 func TestTypeRefNamesAnonymousStructsFromHint(t *testing.T) {
163 ref, types := describe(t, reflect.TypeFor[struct{ A string }]())
164 if ref.Ref != "hint" {
165 t.Fatalf("ref = %+v", ref)
166 }
167 if _, ok := types["hint"]; !ok {
168 t.Fatalf("types = %v", types)
169 }
170 }
171
171 lines GO