-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathschema_introspect_test.go
More file actions
172 lines (138 loc) · 5.84 KB
/
Copy pathschema_introspect_test.go
File metadata and controls
172 lines (138 loc) · 5.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package fisk
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
// TestIntrospectModelSchemaRoundTrip pins the fix for the bug where Schema /
// RestrictedSchema degraded to all-string after the introspect JSON round-trip:
// the Value-derived type information must survive because IntrospectModel
// precomputes the schemas while the Values are still live.
func TestIntrospectModelSchemaRoundTrip(t *testing.T) {
app := New("app", "an app")
set := app.Command("set", "set a thing")
set.Flag("level", "log level").Enum("debug", "info", "warn")
set.Flag("count", "how many").Int()
set.Arg("ttl", "time to live").Duration()
// round-trip exactly as --fisk-introspect does: marshal the introspect model, ship, unmarshal.
data, err := json.Marshal(app.introspectModel())
require.NoError(t, err)
var m ApplicationModel
require.NoError(t, json.Unmarshal(data, &m))
var cmd *CmdModel
for _, c := range m.Commands {
if c.Name == "set" {
cmd = c
}
}
require.NotNil(t, cmd)
require.NotNil(t, cmd.RestrictedSchema, "RestrictedSchema must be populated and survive the round-trip")
props, ok := cmd.RestrictedSchema["properties"].(map[string]any)
require.True(t, ok)
level, ok := props["level"].(map[string]any)
require.True(t, ok)
require.Equal(t, "string", level["type"])
require.ElementsMatch(t, []any{"debug", "info", "warn"}, level["enum"], "enum options must survive")
count, ok := props["count"].(map[string]any)
require.True(t, ok)
require.Equal(t, "integer", count["type"], "integer type must survive (not collapse to string)")
ttl, ok := props["ttl"].(map[string]any)
require.True(t, ok)
require.Equal(t, "string", ttl["type"])
require.Contains(t, ttl, "pattern", "duration pattern must survive")
// the full Schema is also populated
require.NotNil(t, cmd.Schema)
}
// TestIntrospectModelPerCommandCheats pins that a cheat set on a specific command
// is emitted on that command's model (and survives the JSON round-trip) only when
// the cheat label matches the command name; a differently-labeled cheat stays in
// the application-level Cheats, which remains the merged set across all commands.
func TestIntrospectModelPerCommandCheats(t *testing.T) {
app := New("app", "an app")
app.Cheat("", "# top cheat")
// label matches the command name -> attributed to the command
app.Command("matching", "a command").Cheat("matching", "# matching cheat")
// empty label defaults to the command name -> attributed to the command
app.Command("named", "a command").Cheat("", "# named cheat")
// label matches one of the command's aliases -> attributed to the command
aliased := app.Command("aliased", "a command")
aliased.Alias("al")
aliased.Cheat("al", "# aliased cheat")
// nested command whose label matches its name -> attributed
sub := app.Command("sub", "a sub command")
sub.Command("leaf", "a leaf").Cheat("leaf", "# leaf cheat")
// label differs from the command name -> global only, not on the command
app.Command("mismatch", "a command").Cheat("other", "# other cheat")
app.Command("plain", "no cheat here")
data, err := json.Marshal(app.introspectModel())
require.NoError(t, err)
var m ApplicationModel
require.NoError(t, json.Unmarshal(data, &m))
byName := map[string]*CmdModel{}
for _, c := range m.Commands {
byName[c.Name] = c
}
// a matching label is attributed to the command
require.NotNil(t, byName["matching"])
require.Equal(t, "# matching cheat", byName["matching"].Cheat)
// an empty label defaults to the command name and is attributed
require.NotNil(t, byName["named"])
require.Equal(t, "# named cheat", byName["named"].Cheat)
// a label matching an alias is attributed to the command
require.NotNil(t, byName["aliased"])
require.Equal(t, "# aliased cheat", byName["aliased"].Cheat)
// nested commands too
var leaf *CmdModel
for _, c := range byName["sub"].Commands {
if c.Name == "leaf" {
leaf = c
}
}
require.NotNil(t, leaf)
require.Equal(t, "# leaf cheat", leaf.Cheat)
// a mismatched label is NOT attributed to the command
require.NotNil(t, byName["mismatch"])
require.Empty(t, byName["mismatch"].Cheat)
// a command with no cheat omits the field entirely
require.NotNil(t, byName["plain"])
require.Empty(t, byName["plain"].Cheat)
// the application-level set still contains every cheat (merged), including
// the mismatched one under its own label
require.Equal(t, "# top cheat", m.Cheats["app"])
require.Equal(t, "# matching cheat", m.Cheats["matching"])
require.Equal(t, "# named cheat", m.Cheats["named"])
require.Equal(t, "# aliased cheat", m.Cheats["al"])
require.Equal(t, "# leaf cheat", m.Cheats["leaf"])
require.Equal(t, "# other cheat", m.Cheats["other"])
}
// TestRestrictedSchemaOmitsOneOf pins that the Anthropic-restricted schema does
// not emit oneOf for IP-typed values: Anthropic's strict schema subset rejects
// oneOf, so the ipv4/ipv6 alternative belongs only in the unrestricted Schema.
func TestRestrictedSchemaOmitsOneOf(t *testing.T) {
app := New("app", "an app")
set := app.Command("set", "set a thing")
set.Flag("addr", "an address").IP()
data, err := json.Marshal(app.introspectModel())
require.NoError(t, err)
var m ApplicationModel
require.NoError(t, json.Unmarshal(data, &m))
var cmd *CmdModel
for _, c := range m.Commands {
if c.Name == "set" {
cmd = c
}
}
require.NotNil(t, cmd)
restricted, ok := cmd.RestrictedSchema["properties"].(map[string]any)
require.True(t, ok)
addr, ok := restricted["addr"].(map[string]any)
require.True(t, ok)
require.Equal(t, "string", addr["type"])
require.NotContains(t, addr, "oneOf", "restricted schema must not contain oneOf")
// the unrestricted schema keeps the ipv4/ipv6 alternative
full, ok := cmd.Schema["properties"].(map[string]any)
require.True(t, ok)
fullAddr, ok := full["addr"].(map[string]any)
require.True(t, ok)
require.Contains(t, fullAddr, "oneOf", "unrestricted schema must keep oneOf")
}