1package subagents
2
3import "testing"
4
5func TestSupervisorResolvesWebSearchPolicy(t *testing.T) {
6 cases := []struct {
7 name string
8 config Config
9 req SpawnRequest
10 want WebSearchPolicy
11 }{
12 {
13 name: "generic child inherits allowed parent",
14 config: Config{WebSearchPolicy: WebSearchAllow},
15 want: WebSearchAllow,
16 },
17 {
18 name: "missing parent policy denies",
19 config: Config{},
20 want: WebSearchDeny,
21 },
22 {
23 name: "unknown parent policy denies",
24 config: Config{WebSearchPolicy: WebSearchPolicy(99)},
25 want: WebSearchDeny,
26 },
27 {
28 name: "parent denial wins",
29 config: Config{WebSearchPolicy: WebSearchDeny},
30 want: WebSearchDeny,
31 },
32 {
33 name: "allowed tools ceiling denies generic child",
34 config: Config{
35 WebSearchPolicy: WebSearchAllow,
36 Policy: SubagentPolicy{AllowedTools: []string{"read"}},
37 },
38 want: WebSearchDeny,
39 },
40 {
41 name: "named profile without tools denies",
42 config: Config{WebSearchPolicy: WebSearchAllow},
43 req: SpawnRequest{Subagent: "reviewer"},
44 want: WebSearchDeny,
45 },
46 {
47 name: "named profile explicit tool allows",
48 config: Config{WebSearchPolicy: WebSearchAllow},
49 req: SpawnRequest{Subagent: "researcher", Tools: []string{"read", "web_search"}},
50 want: WebSearchAllow,
51 },
52 {
53 name: "named profile cannot exceed denied parent",
54 config: Config{WebSearchPolicy: WebSearchDeny},
55 req: SpawnRequest{Subagent: "researcher", Tools: []string{"web_search"}},
56 want: WebSearchDeny,
57 },
58 {
59 name: "invalid request policy denies generic child",
60 config: Config{WebSearchPolicy: WebSearchAllow},
61 req: SpawnRequest{WebSearchPolicy: WebSearchPolicy(99)},
62 want: WebSearchDeny,
63 },
64 {
65 name: "invalid request policy denies named child",
66 config: Config{WebSearchPolicy: WebSearchAllow},
67 req: SpawnRequest{WebSearchPolicy: WebSearchPolicy(99), Subagent: "researcher", Tools: []string{"web_search"}},
68 want: WebSearchDeny,
69 },
70 }
71 for _, tc := range cases {
72 t.Run(tc.name, func(t *testing.T) {
73 f := New(tc.config)
74 if got := f.resolveWebSearchPolicy(tc.req); got != tc.want {
75 t.Fatalf("policy = %v, want %v", got, tc.want)
76 }
77 })
78 }
79}
80
81func TestWorkerWebSearchPolicyIsExplicitAndFailClosed(t *testing.T) {
82 cases := []struct {
83 name string
84 policy WebSearchPolicy
85 subagent string
86 tools []string
87 want string
88 }{
89 {name: "missing generic policy", policy: WebSearchInherit, want: "deny"},
90 {name: "unknown generic policy", policy: WebSearchPolicy(99), want: "deny"},
91 {name: "named policy without opt in", policy: WebSearchAllow, subagent: "researcher", want: "deny"},
92 {name: "named policy with explicit opt in", policy: WebSearchAllow, subagent: "researcher", tools: []string{"read", "web_search"}, want: "allow"},
93 {name: "invalid named policy cannot use profile opt in", policy: WebSearchPolicy(99), subagent: "researcher", tools: []string{"web_search"}, want: "deny"},
94 }
95 for _, tc := range cases {
96 t.Run(tc.name, func(t *testing.T) {
97 args := subagentWorkerArgs(subagentWorkerArgsOpts{
98 Exe: "/zut",
99 Dir: "/work",
100 SessionPath: "/state/session.json",
101 InboxPath: "/state/inbox.sock",
102 Subagent: tc.subagent,
103 Tools: tc.tools,
104 WebSearchPolicy: tc.policy,
105 })
106 idx := indexOf(args, "--web-search-policy")
107 if idx < 0 || safeAt(args, idx+1) != tc.want {
108 t.Fatalf("argv = %v, want policy %q", args, tc.want)
109 }
110 })
111 }
112
113 if got := WebSearchPolicy(99).String(); got != "deny" {
114 t.Fatalf("unknown policy string = %q, want deny", got)
115 }
116}