recursive-llm-ts 5.0.1 → 5.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -3
- package/bin/rlm-go +0 -0
- package/go/README.md +0 -426
- package/go/integration_test.sh +0 -169
- package/go/rlm/benchmark_test.go +0 -168
- package/go/rlm/context_overflow_test.go +0 -1271
- package/go/rlm/context_savings_test.go +0 -387
- package/go/rlm/lcm_episodes_test.go +0 -384
- package/go/rlm/lcm_test.go +0 -1407
- package/go/rlm/meta_agent_test.go +0 -270
- package/go/rlm/observability_test.go +0 -252
- package/go/rlm/parser_test.go +0 -202
- package/go/rlm/repl_test.go +0 -291
- package/go/rlm/schema_test.go +0 -343
- package/go/rlm/store_backend_test.go +0 -428
- package/go/rlm/structured_test.go +0 -895
- package/go/rlm/textrank_test.go +0 -335
- package/go/rlm/tfidf_test.go +0 -272
- package/go/rlm/token_tracking_test.go +0 -859
- package/go/rlm/tokenizer_test.go +0 -305
- package/go/rlm.test +0 -0
- package/go/test_mock.sh +0 -90
- package/go/test_rlm.sh +0 -41
- package/go/test_simple.sh +0 -78
package/go/rlm/repl_test.go
DELETED
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
package rlm
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"strings"
|
|
5
|
-
"testing"
|
|
6
|
-
)
|
|
7
|
-
|
|
8
|
-
func TestREPLExecutor_BasicExecution(t *testing.T) {
|
|
9
|
-
repl := NewREPLExecutor()
|
|
10
|
-
|
|
11
|
-
tests := []struct {
|
|
12
|
-
name string
|
|
13
|
-
code string
|
|
14
|
-
env map[string]interface{}
|
|
15
|
-
want string
|
|
16
|
-
wantErr bool
|
|
17
|
-
}{
|
|
18
|
-
{
|
|
19
|
-
name: "Simple print",
|
|
20
|
-
code: `console.log("Hello World")`,
|
|
21
|
-
env: map[string]interface{}{},
|
|
22
|
-
want: "Hello World",
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
name: "Variable access",
|
|
26
|
-
code: `console.log(context)`,
|
|
27
|
-
env: map[string]interface{}{"context": "Test Context"},
|
|
28
|
-
want: "Test Context",
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
name: "String slicing",
|
|
32
|
-
code: `console.log(context.slice(0, 5))`,
|
|
33
|
-
env: map[string]interface{}{"context": "Hello World"},
|
|
34
|
-
want: "Hello",
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
name: "len function",
|
|
38
|
-
code: `console.log(len(context))`,
|
|
39
|
-
env: map[string]interface{}{"context": "Hello"},
|
|
40
|
-
want: "5",
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
name: "regex findall",
|
|
44
|
-
code: `console.log(re.findall("ERROR", context))`,
|
|
45
|
-
env: map[string]interface{}{"context": "ERROR 1 ERROR 2", "re": NewRegexHelper()},
|
|
46
|
-
want: "ERROR,ERROR",
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
name: "Last expression evaluation",
|
|
50
|
-
code: `const x = 42; x`,
|
|
51
|
-
env: map[string]interface{}{},
|
|
52
|
-
want: "42",
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
name: "Array operations",
|
|
56
|
-
code: `const arr = [1, 2, 3]; console.log(arr.length)`,
|
|
57
|
-
env: map[string]interface{}{},
|
|
58
|
-
want: "3",
|
|
59
|
-
},
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
for _, tt := range tests {
|
|
63
|
-
t.Run(tt.name, func(t *testing.T) {
|
|
64
|
-
got, err := repl.Execute(tt.code, tt.env)
|
|
65
|
-
if (err != nil) != tt.wantErr {
|
|
66
|
-
t.Errorf("Execute() error = %v, wantErr %v", err, tt.wantErr)
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
if !tt.wantErr && got != tt.want {
|
|
70
|
-
t.Errorf("Execute() = %q, want %q", got, tt.want)
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
func TestREPLExecutor_CodeExtraction(t *testing.T) {
|
|
77
|
-
repl := NewREPLExecutor()
|
|
78
|
-
|
|
79
|
-
tests := []struct {
|
|
80
|
-
name string
|
|
81
|
-
code string
|
|
82
|
-
env map[string]interface{}
|
|
83
|
-
want string
|
|
84
|
-
}{
|
|
85
|
-
{
|
|
86
|
-
name: "JavaScript code block",
|
|
87
|
-
code: "```javascript\nconsole.log('test')\n```",
|
|
88
|
-
env: map[string]interface{}{},
|
|
89
|
-
want: "test",
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
name: "Python code block (should still extract)",
|
|
93
|
-
code: "```python\nconsole.log('test')\n```",
|
|
94
|
-
env: map[string]interface{}{},
|
|
95
|
-
want: "test",
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
name: "Generic code block",
|
|
99
|
-
code: "```\nconsole.log('test')\n```",
|
|
100
|
-
env: map[string]interface{}{},
|
|
101
|
-
want: "test",
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
name: "No code block",
|
|
105
|
-
code: "console.log('test')",
|
|
106
|
-
env: map[string]interface{}{},
|
|
107
|
-
want: "test",
|
|
108
|
-
},
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
for _, tt := range tests {
|
|
112
|
-
t.Run(tt.name, func(t *testing.T) {
|
|
113
|
-
got, err := repl.Execute(tt.code, tt.env)
|
|
114
|
-
if err != nil {
|
|
115
|
-
t.Errorf("Execute() error = %v", err)
|
|
116
|
-
return
|
|
117
|
-
}
|
|
118
|
-
if got != tt.want {
|
|
119
|
-
t.Errorf("Execute() = %q, want %q", got, tt.want)
|
|
120
|
-
}
|
|
121
|
-
})
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
func TestREPLExecutor_JSBootstrap(t *testing.T) {
|
|
126
|
-
repl := NewREPLExecutor()
|
|
127
|
-
|
|
128
|
-
tests := []struct {
|
|
129
|
-
name string
|
|
130
|
-
code string
|
|
131
|
-
want string
|
|
132
|
-
}{
|
|
133
|
-
{
|
|
134
|
-
name: "json.loads",
|
|
135
|
-
code: `const obj = json.loads('{"key":"value"}'); console.log(obj.key)`,
|
|
136
|
-
want: "value",
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
name: "json.dumps",
|
|
140
|
-
code: `console.log(json.dumps({key: "value"}))`,
|
|
141
|
-
want: `{"key":"value"}`,
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
name: "range",
|
|
145
|
-
code: `console.log(range(5).length)`,
|
|
146
|
-
want: "5",
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
name: "sum",
|
|
150
|
-
code: `console.log(sum([1, 2, 3]))`,
|
|
151
|
-
want: "6",
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
name: "Counter",
|
|
155
|
-
code: `const c = Counter("hello"); console.log(c.l)`,
|
|
156
|
-
want: "2",
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
name: "sorted",
|
|
160
|
-
code: `console.log(sorted([3, 1, 2]).join(','))`,
|
|
161
|
-
want: "1,2,3",
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
name: "enumerate",
|
|
165
|
-
code: `console.log(enumerate(['a', 'b']).length)`,
|
|
166
|
-
want: "2",
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
name: "zip",
|
|
170
|
-
code: `console.log(zip([1, 2], ['a', 'b']).length)`,
|
|
171
|
-
want: "2",
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
name: "any",
|
|
175
|
-
code: `console.log(any([false, true, false]))`,
|
|
176
|
-
want: "true",
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
name: "all",
|
|
180
|
-
code: `console.log(all([true, true, true]))`,
|
|
181
|
-
want: "true",
|
|
182
|
-
},
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
for _, tt := range tests {
|
|
186
|
-
t.Run(tt.name, func(t *testing.T) {
|
|
187
|
-
got, err := repl.Execute(tt.code, map[string]interface{}{})
|
|
188
|
-
if err != nil {
|
|
189
|
-
t.Errorf("Execute() error = %v", err)
|
|
190
|
-
return
|
|
191
|
-
}
|
|
192
|
-
if got != tt.want {
|
|
193
|
-
t.Errorf("Execute() = %q, want %q", got, tt.want)
|
|
194
|
-
}
|
|
195
|
-
})
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
func TestREPLExecutor_OutputTruncation(t *testing.T) {
|
|
200
|
-
repl := NewREPLExecutor()
|
|
201
|
-
repl.maxOutputChars = 50
|
|
202
|
-
|
|
203
|
-
longOutput := strings.Repeat("x", 100)
|
|
204
|
-
code := `console.log("` + longOutput + `")`
|
|
205
|
-
|
|
206
|
-
got, err := repl.Execute(code, map[string]interface{}{})
|
|
207
|
-
if err != nil {
|
|
208
|
-
t.Errorf("Execute() error = %v", err)
|
|
209
|
-
return
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if len(got) <= repl.maxOutputChars {
|
|
213
|
-
t.Errorf("Expected truncation, but got length %d", len(got))
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if !strings.Contains(got, "[Output truncated") {
|
|
217
|
-
t.Errorf("Expected truncation message in output")
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
func TestREPLExecutor_ErrorHandling(t *testing.T) {
|
|
222
|
-
repl := NewREPLExecutor()
|
|
223
|
-
|
|
224
|
-
tests := []struct {
|
|
225
|
-
name string
|
|
226
|
-
code string
|
|
227
|
-
wantErr bool
|
|
228
|
-
}{
|
|
229
|
-
{
|
|
230
|
-
name: "Syntax error",
|
|
231
|
-
code: `const x = ;`,
|
|
232
|
-
wantErr: true,
|
|
233
|
-
},
|
|
234
|
-
{
|
|
235
|
-
name: "Reference error",
|
|
236
|
-
code: `console.log(undefinedVariable)`,
|
|
237
|
-
wantErr: true,
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
name: "Valid code",
|
|
241
|
-
code: `console.log("ok")`,
|
|
242
|
-
wantErr: false,
|
|
243
|
-
},
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
for _, tt := range tests {
|
|
247
|
-
t.Run(tt.name, func(t *testing.T) {
|
|
248
|
-
_, err := repl.Execute(tt.code, map[string]interface{}{})
|
|
249
|
-
if (err != nil) != tt.wantErr {
|
|
250
|
-
t.Errorf("Execute() error = %v, wantErr %v", err, tt.wantErr)
|
|
251
|
-
}
|
|
252
|
-
})
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
func TestRegexHelper(t *testing.T) {
|
|
257
|
-
re := NewRegexHelper()
|
|
258
|
-
|
|
259
|
-
t.Run("findall", func(t *testing.T) {
|
|
260
|
-
result := re["findall"]("ERROR", "ERROR 1 ERROR 2 WARNING")
|
|
261
|
-
matches, ok := result.([]string)
|
|
262
|
-
if !ok {
|
|
263
|
-
t.Fatal("Expected []string from findall")
|
|
264
|
-
}
|
|
265
|
-
if len(matches) != 2 {
|
|
266
|
-
t.Errorf("Expected 2 matches, got %d", len(matches))
|
|
267
|
-
}
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
t.Run("search", func(t *testing.T) {
|
|
271
|
-
result := re["search"]("ERROR", "INFO ERROR WARNING")
|
|
272
|
-
match, ok := result.(string)
|
|
273
|
-
if !ok {
|
|
274
|
-
t.Fatal("Expected string from search")
|
|
275
|
-
}
|
|
276
|
-
if match != "ERROR" {
|
|
277
|
-
t.Errorf("Expected 'ERROR', got %q", match)
|
|
278
|
-
}
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
t.Run("no match", func(t *testing.T) {
|
|
282
|
-
result := re["search"]("ERROR", "INFO WARNING")
|
|
283
|
-
match, ok := result.(string)
|
|
284
|
-
if !ok {
|
|
285
|
-
t.Fatal("Expected string from search")
|
|
286
|
-
}
|
|
287
|
-
if match != "" {
|
|
288
|
-
t.Errorf("Expected empty string, got %q", match)
|
|
289
|
-
}
|
|
290
|
-
})
|
|
291
|
-
}
|
package/go/rlm/schema_test.go
DELETED
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
package rlm
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"encoding/json"
|
|
5
|
-
"testing"
|
|
6
|
-
)
|
|
7
|
-
|
|
8
|
-
func TestNewSchemaValidator(t *testing.T) {
|
|
9
|
-
tests := []struct {
|
|
10
|
-
name string
|
|
11
|
-
schema *JSONSchema
|
|
12
|
-
wantErr bool
|
|
13
|
-
}{
|
|
14
|
-
{
|
|
15
|
-
name: "nil schema",
|
|
16
|
-
schema: nil,
|
|
17
|
-
wantErr: true,
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
name: "simple string schema",
|
|
21
|
-
schema: &JSONSchema{
|
|
22
|
-
Type: "string",
|
|
23
|
-
},
|
|
24
|
-
wantErr: false,
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
name: "object schema with properties",
|
|
28
|
-
schema: &JSONSchema{
|
|
29
|
-
Type: "object",
|
|
30
|
-
Properties: map[string]*JSONSchema{
|
|
31
|
-
"name": {Type: "string"},
|
|
32
|
-
"age": {Type: "number"},
|
|
33
|
-
},
|
|
34
|
-
Required: []string{"name"},
|
|
35
|
-
},
|
|
36
|
-
wantErr: false,
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
name: "array schema",
|
|
40
|
-
schema: &JSONSchema{
|
|
41
|
-
Type: "array",
|
|
42
|
-
Items: &JSONSchema{
|
|
43
|
-
Type: "string",
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
wantErr: false,
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
name: "nested object schema",
|
|
50
|
-
schema: &JSONSchema{
|
|
51
|
-
Type: "object",
|
|
52
|
-
Properties: map[string]*JSONSchema{
|
|
53
|
-
"address": {
|
|
54
|
-
Type: "object",
|
|
55
|
-
Properties: map[string]*JSONSchema{
|
|
56
|
-
"street": {Type: "string"},
|
|
57
|
-
"city": {Type: "string"},
|
|
58
|
-
},
|
|
59
|
-
Required: []string{"street", "city"},
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
Required: []string{"address"},
|
|
63
|
-
},
|
|
64
|
-
wantErr: false,
|
|
65
|
-
},
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
for _, tt := range tests {
|
|
69
|
-
t.Run(tt.name, func(t *testing.T) {
|
|
70
|
-
validator, err := NewSchemaValidator(tt.schema)
|
|
71
|
-
if tt.wantErr {
|
|
72
|
-
if err == nil {
|
|
73
|
-
t.Error("expected error but got nil")
|
|
74
|
-
}
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
if err != nil {
|
|
78
|
-
t.Errorf("unexpected error: %v", err)
|
|
79
|
-
return
|
|
80
|
-
}
|
|
81
|
-
if validator == nil {
|
|
82
|
-
t.Error("expected non-nil validator")
|
|
83
|
-
}
|
|
84
|
-
})
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
func TestSchemaValidatorValidate(t *testing.T) {
|
|
89
|
-
schema := &JSONSchema{
|
|
90
|
-
Type: "object",
|
|
91
|
-
Properties: map[string]*JSONSchema{
|
|
92
|
-
"name": {Type: "string"},
|
|
93
|
-
"age": {Type: "number"},
|
|
94
|
-
},
|
|
95
|
-
Required: []string{"name"},
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
validator, err := NewSchemaValidator(schema)
|
|
99
|
-
if err != nil {
|
|
100
|
-
t.Fatalf("failed to create validator: %v", err)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
tests := []struct {
|
|
104
|
-
name string
|
|
105
|
-
data interface{}
|
|
106
|
-
wantErr bool
|
|
107
|
-
}{
|
|
108
|
-
{
|
|
109
|
-
name: "valid data with all fields",
|
|
110
|
-
data: map[string]interface{}{
|
|
111
|
-
"name": "Alice",
|
|
112
|
-
"age": float64(30),
|
|
113
|
-
},
|
|
114
|
-
wantErr: false,
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
name: "valid data with only required fields",
|
|
118
|
-
data: map[string]interface{}{
|
|
119
|
-
"name": "Bob",
|
|
120
|
-
},
|
|
121
|
-
wantErr: false,
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
name: "invalid - missing required field",
|
|
125
|
-
data: map[string]interface{}{
|
|
126
|
-
"age": float64(25),
|
|
127
|
-
},
|
|
128
|
-
wantErr: true,
|
|
129
|
-
},
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
for _, tt := range tests {
|
|
133
|
-
t.Run(tt.name, func(t *testing.T) {
|
|
134
|
-
err := validator.Validate(tt.data)
|
|
135
|
-
if tt.wantErr && err == nil {
|
|
136
|
-
t.Error("expected validation error but got nil")
|
|
137
|
-
}
|
|
138
|
-
if !tt.wantErr && err != nil {
|
|
139
|
-
t.Errorf("unexpected validation error: %v", err)
|
|
140
|
-
}
|
|
141
|
-
})
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
func TestValidateJSON(t *testing.T) {
|
|
146
|
-
schema := &JSONSchema{
|
|
147
|
-
Type: "object",
|
|
148
|
-
Properties: map[string]*JSONSchema{
|
|
149
|
-
"status": {Type: "string"},
|
|
150
|
-
},
|
|
151
|
-
Required: []string{"status"},
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
validator, err := NewSchemaValidator(schema)
|
|
155
|
-
if err != nil {
|
|
156
|
-
t.Fatalf("failed to create validator: %v", err)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
validJSON := `{"status": "ok"}`
|
|
160
|
-
if err := validator.ValidateJSON([]byte(validJSON)); err != nil {
|
|
161
|
-
t.Errorf("expected valid JSON to pass: %v", err)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
invalidJSON := `{"missing": "field"}`
|
|
165
|
-
if err := validator.ValidateJSON([]byte(invalidJSON)); err == nil {
|
|
166
|
-
t.Error("expected invalid JSON to fail validation")
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
malformedJSON := `{not valid json`
|
|
170
|
-
if err := validator.ValidateJSON([]byte(malformedJSON)); err == nil {
|
|
171
|
-
t.Error("expected malformed JSON to fail")
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
func TestJSONSchemaConversion(t *testing.T) {
|
|
176
|
-
original := &JSONSchema{
|
|
177
|
-
Type: "object",
|
|
178
|
-
Properties: map[string]*JSONSchema{
|
|
179
|
-
"name": {Type: "string"},
|
|
180
|
-
"score": {Type: "number"},
|
|
181
|
-
},
|
|
182
|
-
Required: []string{"name", "score"},
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Convert to Google schema and back
|
|
186
|
-
googleSchema, err := JSONSchemaToGoogleSchema(original)
|
|
187
|
-
if err != nil {
|
|
188
|
-
t.Fatalf("failed to convert to Google schema: %v", err)
|
|
189
|
-
}
|
|
190
|
-
if googleSchema == nil {
|
|
191
|
-
t.Fatal("expected non-nil Google schema")
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
roundTrip, err := GoogleSchemaToJSONSchema(googleSchema)
|
|
195
|
-
if err != nil {
|
|
196
|
-
t.Fatalf("failed to convert back: %v", err)
|
|
197
|
-
}
|
|
198
|
-
if roundTrip.Type != original.Type {
|
|
199
|
-
t.Errorf("type mismatch: got %s, want %s", roundTrip.Type, original.Type)
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
func TestInferSchemaFromJSON(t *testing.T) {
|
|
204
|
-
tests := []struct {
|
|
205
|
-
name string
|
|
206
|
-
json string
|
|
207
|
-
expectedType string
|
|
208
|
-
}{
|
|
209
|
-
{
|
|
210
|
-
name: "simple object",
|
|
211
|
-
json: `{"name": "Alice", "age": 30}`,
|
|
212
|
-
expectedType: "object",
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
name: "array",
|
|
216
|
-
json: `[1, 2, 3]`,
|
|
217
|
-
expectedType: "array",
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
name: "string",
|
|
221
|
-
json: `"hello"`,
|
|
222
|
-
expectedType: "string",
|
|
223
|
-
},
|
|
224
|
-
{
|
|
225
|
-
name: "number",
|
|
226
|
-
json: `3.14`,
|
|
227
|
-
expectedType: "number",
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
name: "integer",
|
|
231
|
-
json: `42`,
|
|
232
|
-
expectedType: "integer",
|
|
233
|
-
},
|
|
234
|
-
{
|
|
235
|
-
name: "boolean",
|
|
236
|
-
json: `true`,
|
|
237
|
-
expectedType: "boolean",
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
name: "null",
|
|
241
|
-
json: `null`,
|
|
242
|
-
expectedType: "null",
|
|
243
|
-
},
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
for _, tt := range tests {
|
|
247
|
-
t.Run(tt.name, func(t *testing.T) {
|
|
248
|
-
schema, err := InferSchemaFromJSON([]byte(tt.json))
|
|
249
|
-
if err != nil {
|
|
250
|
-
t.Fatalf("unexpected error: %v", err)
|
|
251
|
-
}
|
|
252
|
-
if schema.Type != tt.expectedType {
|
|
253
|
-
t.Errorf("got type %s, want %s", schema.Type, tt.expectedType)
|
|
254
|
-
}
|
|
255
|
-
})
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
func TestInferSchemaFromJSON_NestedObject(t *testing.T) {
|
|
260
|
-
jsonData := `{
|
|
261
|
-
"user": {
|
|
262
|
-
"name": "Alice",
|
|
263
|
-
"scores": [95, 87, 92]
|
|
264
|
-
}
|
|
265
|
-
}`
|
|
266
|
-
|
|
267
|
-
schema, err := InferSchemaFromJSON([]byte(jsonData))
|
|
268
|
-
if err != nil {
|
|
269
|
-
t.Fatalf("unexpected error: %v", err)
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if schema.Type != "object" {
|
|
273
|
-
t.Fatalf("expected object, got %s", schema.Type)
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
userProp, ok := schema.Properties["user"]
|
|
277
|
-
if !ok {
|
|
278
|
-
t.Fatal("missing 'user' property")
|
|
279
|
-
}
|
|
280
|
-
if userProp.Type != "object" {
|
|
281
|
-
t.Errorf("expected user to be object, got %s", userProp.Type)
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
scoresProp, ok := userProp.Properties["scores"]
|
|
285
|
-
if !ok {
|
|
286
|
-
t.Fatal("missing 'scores' property")
|
|
287
|
-
}
|
|
288
|
-
if scoresProp.Type != "array" {
|
|
289
|
-
t.Errorf("expected scores to be array, got %s", scoresProp.Type)
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
func TestSchemaJSONRoundTrip(t *testing.T) {
|
|
294
|
-
// Test that our JSONSchema type serializes/deserializes correctly
|
|
295
|
-
minLen := 5
|
|
296
|
-
maxLen := 100
|
|
297
|
-
min := 1.0
|
|
298
|
-
max := 10.0
|
|
299
|
-
|
|
300
|
-
original := &JSONSchema{
|
|
301
|
-
Type: "object",
|
|
302
|
-
Properties: map[string]*JSONSchema{
|
|
303
|
-
"name": {
|
|
304
|
-
Type: "string",
|
|
305
|
-
MinLength: &minLen,
|
|
306
|
-
MaxLength: &maxLen,
|
|
307
|
-
},
|
|
308
|
-
"score": {
|
|
309
|
-
Type: "number",
|
|
310
|
-
Minimum: &min,
|
|
311
|
-
Maximum: &max,
|
|
312
|
-
},
|
|
313
|
-
"tags": {
|
|
314
|
-
Type: "array",
|
|
315
|
-
Items: &JSONSchema{
|
|
316
|
-
Type: "string",
|
|
317
|
-
Enum: []string{"a", "b", "c"},
|
|
318
|
-
},
|
|
319
|
-
},
|
|
320
|
-
},
|
|
321
|
-
Required: []string{"name", "score"},
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
data, err := json.Marshal(original)
|
|
325
|
-
if err != nil {
|
|
326
|
-
t.Fatalf("failed to marshal: %v", err)
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
var parsed JSONSchema
|
|
330
|
-
if err := json.Unmarshal(data, &parsed); err != nil {
|
|
331
|
-
t.Fatalf("failed to unmarshal: %v", err)
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if parsed.Type != "object" {
|
|
335
|
-
t.Errorf("type mismatch: got %s", parsed.Type)
|
|
336
|
-
}
|
|
337
|
-
if len(parsed.Properties) != 3 {
|
|
338
|
-
t.Errorf("expected 3 properties, got %d", len(parsed.Properties))
|
|
339
|
-
}
|
|
340
|
-
if len(parsed.Required) != 2 {
|
|
341
|
-
t.Errorf("expected 2 required, got %d", len(parsed.Required))
|
|
342
|
-
}
|
|
343
|
-
}
|