|
1efddcb…
|
lmata
|
1 |
package registry_test |
|
1efddcb…
|
lmata
|
2 |
|
|
1efddcb…
|
lmata
|
3 |
import ( |
|
1efddcb…
|
lmata
|
4 |
"fmt" |
|
1efddcb…
|
lmata
|
5 |
"sync" |
|
1efddcb…
|
lmata
|
6 |
"testing" |
|
1efddcb…
|
lmata
|
7 |
|
|
1efddcb…
|
lmata
|
8 |
"github.com/conflicthq/scuttlebot/internal/registry" |
|
1efddcb…
|
lmata
|
9 |
) |
|
1efddcb…
|
lmata
|
10 |
|
|
1efddcb…
|
lmata
|
11 |
// mockProvisioner records calls for test assertions. |
|
1efddcb…
|
lmata
|
12 |
type mockProvisioner struct { |
|
1efddcb…
|
lmata
|
13 |
mu sync.Mutex |
|
1efddcb…
|
lmata
|
14 |
accounts map[string]string // nick → passphrase |
|
1efddcb…
|
lmata
|
15 |
} |
|
1efddcb…
|
lmata
|
16 |
|
|
1efddcb…
|
lmata
|
17 |
func newMockProvisioner() *mockProvisioner { |
|
1efddcb…
|
lmata
|
18 |
return &mockProvisioner{accounts: make(map[string]string)} |
|
1efddcb…
|
lmata
|
19 |
} |
|
1efddcb…
|
lmata
|
20 |
|
|
1efddcb…
|
lmata
|
21 |
func (m *mockProvisioner) RegisterAccount(name, passphrase string) error { |
|
1efddcb…
|
lmata
|
22 |
m.mu.Lock() |
|
1efddcb…
|
lmata
|
23 |
defer m.mu.Unlock() |
|
1efddcb…
|
lmata
|
24 |
if _, exists := m.accounts[name]; exists { |
|
1efddcb…
|
lmata
|
25 |
return fmt.Errorf("ACCOUNT_EXISTS") |
|
1efddcb…
|
lmata
|
26 |
} |
|
1efddcb…
|
lmata
|
27 |
m.accounts[name] = passphrase |
|
1efddcb…
|
lmata
|
28 |
return nil |
|
1efddcb…
|
lmata
|
29 |
} |
|
1efddcb…
|
lmata
|
30 |
|
|
1efddcb…
|
lmata
|
31 |
func (m *mockProvisioner) ChangePassword(name, passphrase string) error { |
|
1efddcb…
|
lmata
|
32 |
m.mu.Lock() |
|
1efddcb…
|
lmata
|
33 |
defer m.mu.Unlock() |
|
1efddcb…
|
lmata
|
34 |
if _, exists := m.accounts[name]; !exists { |
|
1efddcb…
|
lmata
|
35 |
return fmt.Errorf("ACCOUNT_DOES_NOT_EXIST") |
|
1efddcb…
|
lmata
|
36 |
} |
|
1efddcb…
|
lmata
|
37 |
m.accounts[name] = passphrase |
|
1efddcb…
|
lmata
|
38 |
return nil |
|
1efddcb…
|
lmata
|
39 |
} |
|
1efddcb…
|
lmata
|
40 |
|
|
1efddcb…
|
lmata
|
41 |
func (m *mockProvisioner) passphrase(nick string) string { |
|
1efddcb…
|
lmata
|
42 |
m.mu.Lock() |
|
1efddcb…
|
lmata
|
43 |
defer m.mu.Unlock() |
|
1efddcb…
|
lmata
|
44 |
return m.accounts[nick] |
|
1efddcb…
|
lmata
|
45 |
} |
|
1efddcb…
|
lmata
|
46 |
|
|
1efddcb…
|
lmata
|
47 |
var testKey = []byte("test-signing-key-do-not-use-in-production") |
|
1efddcb…
|
lmata
|
48 |
|
|
7830697…
|
lmata
|
49 |
func cfg(channels, permissions []string) registry.EngagementConfig { |
|
7830697…
|
lmata
|
50 |
return registry.EngagementConfig{Channels: channels, Permissions: permissions} |
|
7830697…
|
lmata
|
51 |
} |
|
7830697…
|
lmata
|
52 |
|
|
1efddcb…
|
lmata
|
53 |
func TestRegister(t *testing.T) { |
|
1efddcb…
|
lmata
|
54 |
p := newMockProvisioner() |
|
1efddcb…
|
lmata
|
55 |
r := registry.New(p, testKey) |
|
1efddcb…
|
lmata
|
56 |
|
|
1efddcb…
|
lmata
|
57 |
creds, payload, err := r.Register("claude-01", registry.AgentTypeWorker, |
|
7830697…
|
lmata
|
58 |
cfg([]string{"#fleet", "#project.test"}, []string{"task.create"})) |
|
1efddcb…
|
lmata
|
59 |
if err != nil { |
|
1efddcb…
|
lmata
|
60 |
t.Fatalf("Register: %v", err) |
|
1efddcb…
|
lmata
|
61 |
} |
|
1efddcb…
|
lmata
|
62 |
|
|
1efddcb…
|
lmata
|
63 |
if creds.Nick != "claude-01" { |
|
1efddcb…
|
lmata
|
64 |
t.Errorf("Nick: got %q, want %q", creds.Nick, "claude-01") |
|
1efddcb…
|
lmata
|
65 |
} |
|
1efddcb…
|
lmata
|
66 |
if creds.Passphrase == "" { |
|
1efddcb…
|
lmata
|
67 |
t.Error("Passphrase is empty") |
|
1efddcb…
|
lmata
|
68 |
} |
|
1efddcb…
|
lmata
|
69 |
if p.passphrase("claude-01") == "" { |
|
1efddcb…
|
lmata
|
70 |
t.Error("account not created in provisioner") |
|
1efddcb…
|
lmata
|
71 |
} |
|
1efddcb…
|
lmata
|
72 |
if payload.Payload.Nick != "claude-01" { |
|
1efddcb…
|
lmata
|
73 |
t.Errorf("payload Nick: got %q", payload.Payload.Nick) |
|
1efddcb…
|
lmata
|
74 |
} |
|
1efddcb…
|
lmata
|
75 |
if payload.Signature == "" { |
|
1efddcb…
|
lmata
|
76 |
t.Error("payload signature is empty") |
|
1efddcb…
|
lmata
|
77 |
} |
|
7830697…
|
lmata
|
78 |
if len(payload.Payload.Config.Channels) != 2 { |
|
7830697…
|
lmata
|
79 |
t.Errorf("payload channels: got %d, want 2", len(payload.Payload.Config.Channels)) |
|
7830697…
|
lmata
|
80 |
} |
|
1efddcb…
|
lmata
|
81 |
} |
|
1efddcb…
|
lmata
|
82 |
|
|
1efddcb…
|
lmata
|
83 |
func TestRegisterDuplicate(t *testing.T) { |
|
1efddcb…
|
lmata
|
84 |
p := newMockProvisioner() |
|
1efddcb…
|
lmata
|
85 |
r := registry.New(p, testKey) |
|
1efddcb…
|
lmata
|
86 |
|
|
7830697…
|
lmata
|
87 |
if _, _, err := r.Register("agent-01", registry.AgentTypeWorker, registry.EngagementConfig{}); err != nil { |
|
1efddcb…
|
lmata
|
88 |
t.Fatalf("first Register: %v", err) |
|
1efddcb…
|
lmata
|
89 |
} |
|
7830697…
|
lmata
|
90 |
if _, _, err := r.Register("agent-01", registry.AgentTypeWorker, registry.EngagementConfig{}); err == nil { |
|
1efddcb…
|
lmata
|
91 |
t.Error("expected error on duplicate registration, got nil") |
|
1efddcb…
|
lmata
|
92 |
} |
|
1efddcb…
|
lmata
|
93 |
} |
|
1efddcb…
|
lmata
|
94 |
|
|
1efddcb…
|
lmata
|
95 |
func TestRotate(t *testing.T) { |
|
1efddcb…
|
lmata
|
96 |
p := newMockProvisioner() |
|
1efddcb…
|
lmata
|
97 |
r := registry.New(p, testKey) |
|
1efddcb…
|
lmata
|
98 |
|
|
7830697…
|
lmata
|
99 |
creds, _, err := r.Register("agent-02", registry.AgentTypeWorker, registry.EngagementConfig{}) |
|
1efddcb…
|
lmata
|
100 |
if err != nil { |
|
1efddcb…
|
lmata
|
101 |
t.Fatalf("Register: %v", err) |
|
1efddcb…
|
lmata
|
102 |
} |
|
1efddcb…
|
lmata
|
103 |
original := creds.Passphrase |
|
1efddcb…
|
lmata
|
104 |
|
|
1efddcb…
|
lmata
|
105 |
newCreds, err := r.Rotate("agent-02") |
|
1efddcb…
|
lmata
|
106 |
if err != nil { |
|
1efddcb…
|
lmata
|
107 |
t.Fatalf("Rotate: %v", err) |
|
1efddcb…
|
lmata
|
108 |
} |
|
1efddcb…
|
lmata
|
109 |
if newCreds.Passphrase == original { |
|
1efddcb…
|
lmata
|
110 |
t.Error("passphrase should change after rotation") |
|
1efddcb…
|
lmata
|
111 |
} |
|
1efddcb…
|
lmata
|
112 |
if p.passphrase("agent-02") != newCreds.Passphrase { |
|
1efddcb…
|
lmata
|
113 |
t.Error("provisioner passphrase should match rotated credentials") |
|
1efddcb…
|
lmata
|
114 |
} |
|
1efddcb…
|
lmata
|
115 |
} |
|
1efddcb…
|
lmata
|
116 |
|
|
1efddcb…
|
lmata
|
117 |
func TestRevoke(t *testing.T) { |
|
1efddcb…
|
lmata
|
118 |
p := newMockProvisioner() |
|
1efddcb…
|
lmata
|
119 |
r := registry.New(p, testKey) |
|
1efddcb…
|
lmata
|
120 |
|
|
7830697…
|
lmata
|
121 |
creds, _, err := r.Register("agent-03", registry.AgentTypeWorker, registry.EngagementConfig{}) |
|
1efddcb…
|
lmata
|
122 |
if err != nil { |
|
1efddcb…
|
lmata
|
123 |
t.Fatalf("Register: %v", err) |
|
1efddcb…
|
lmata
|
124 |
} |
|
1efddcb…
|
lmata
|
125 |
|
|
1efddcb…
|
lmata
|
126 |
if err := r.Revoke("agent-03"); err != nil { |
|
1efddcb…
|
lmata
|
127 |
t.Fatalf("Revoke: %v", err) |
|
1efddcb…
|
lmata
|
128 |
} |
|
1efddcb…
|
lmata
|
129 |
|
|
1efddcb…
|
lmata
|
130 |
if p.passphrase("agent-03") == creds.Passphrase { |
|
1efddcb…
|
lmata
|
131 |
t.Error("passphrase should change after revocation") |
|
1efddcb…
|
lmata
|
132 |
} |
|
1efddcb…
|
lmata
|
133 |
if _, err := r.Get("agent-03"); err == nil { |
|
1efddcb…
|
lmata
|
134 |
t.Error("Get should fail for revoked agent") |
|
1efddcb…
|
lmata
|
135 |
} |
|
1efddcb…
|
lmata
|
136 |
} |
|
1efddcb…
|
lmata
|
137 |
|
|
1efddcb…
|
lmata
|
138 |
func TestVerifyPayload(t *testing.T) { |
|
1efddcb…
|
lmata
|
139 |
p := newMockProvisioner() |
|
1efddcb…
|
lmata
|
140 |
r := registry.New(p, testKey) |
|
1efddcb…
|
lmata
|
141 |
|
|
1efddcb…
|
lmata
|
142 |
_, payload, err := r.Register("agent-04", registry.AgentTypeOrchestrator, |
|
7830697…
|
lmata
|
143 |
cfg([]string{"#fleet"}, []string{"task.create", "task.assign"})) |
|
1efddcb…
|
lmata
|
144 |
if err != nil { |
|
1efddcb…
|
lmata
|
145 |
t.Fatalf("Register: %v", err) |
|
1efddcb…
|
lmata
|
146 |
} |
|
1efddcb…
|
lmata
|
147 |
|
|
1efddcb…
|
lmata
|
148 |
if err := registry.VerifyPayload(payload, testKey); err != nil { |
|
1efddcb…
|
lmata
|
149 |
t.Errorf("VerifyPayload: %v", err) |
|
1efddcb…
|
lmata
|
150 |
} |
|
1efddcb…
|
lmata
|
151 |
|
|
1efddcb…
|
lmata
|
152 |
// Tamper with the payload. |
|
1efddcb…
|
lmata
|
153 |
payload.Payload.Nick = "evil-agent" |
|
1efddcb…
|
lmata
|
154 |
if err := registry.VerifyPayload(payload, testKey); err == nil { |
|
1efddcb…
|
lmata
|
155 |
t.Error("VerifyPayload should fail after tampering") |
|
1efddcb…
|
lmata
|
156 |
} |
|
1efddcb…
|
lmata
|
157 |
} |
|
1efddcb…
|
lmata
|
158 |
|
|
1efddcb…
|
lmata
|
159 |
func TestList(t *testing.T) { |
|
1efddcb…
|
lmata
|
160 |
p := newMockProvisioner() |
|
1efddcb…
|
lmata
|
161 |
r := registry.New(p, testKey) |
|
1efddcb…
|
lmata
|
162 |
|
|
1efddcb…
|
lmata
|
163 |
for _, nick := range []string{"a", "b", "c"} { |
|
7830697…
|
lmata
|
164 |
if _, _, err := r.Register(nick, registry.AgentTypeWorker, registry.EngagementConfig{}); err != nil { |
|
1efddcb…
|
lmata
|
165 |
t.Fatalf("Register %q: %v", nick, err) |
|
1efddcb…
|
lmata
|
166 |
} |
|
1efddcb…
|
lmata
|
167 |
} |
|
1efddcb…
|
lmata
|
168 |
if err := r.Revoke("b"); err != nil { |
|
1efddcb…
|
lmata
|
169 |
t.Fatalf("Revoke: %v", err) |
|
1efddcb…
|
lmata
|
170 |
} |
|
1efddcb…
|
lmata
|
171 |
|
|
1efddcb…
|
lmata
|
172 |
agents := r.List() |
|
66d18d7…
|
lmata
|
173 |
// 3 registered (a, b, c), b revoked — List returns all including revoked. |
|
66d18d7…
|
lmata
|
174 |
registered := []string{"a", "b", "c"} |
|
66d18d7…
|
lmata
|
175 |
if len(agents) != len(registered) { |
|
66d18d7…
|
lmata
|
176 |
t.Errorf("List: got %d agents, want %d", len(agents), len(registered)) |
|
66d18d7…
|
lmata
|
177 |
} |
|
66d18d7…
|
lmata
|
178 |
var revokedCount int |
|
66d18d7…
|
lmata
|
179 |
for _, a := range agents { |
|
66d18d7…
|
lmata
|
180 |
if a.Revoked { |
|
66d18d7…
|
lmata
|
181 |
revokedCount++ |
|
66d18d7…
|
lmata
|
182 |
} |
|
66d18d7…
|
lmata
|
183 |
} |
|
66d18d7…
|
lmata
|
184 |
if revokedCount != 1 { |
|
66d18d7…
|
lmata
|
185 |
t.Errorf("List: got %d revoked, want 1", revokedCount) |
|
7830697…
|
lmata
|
186 |
} |
|
7830697…
|
lmata
|
187 |
} |
|
7830697…
|
lmata
|
188 |
|
|
7830697…
|
lmata
|
189 |
func TestEngagementConfigValidation(t *testing.T) { |
|
7830697…
|
lmata
|
190 |
tests := []struct { |
|
7830697…
|
lmata
|
191 |
name string |
|
7830697…
|
lmata
|
192 |
cfg registry.EngagementConfig |
|
7830697…
|
lmata
|
193 |
wantErr bool |
|
7830697…
|
lmata
|
194 |
}{ |
|
7830697…
|
lmata
|
195 |
{ |
|
7830697…
|
lmata
|
196 |
name: "valid full config", |
|
7830697…
|
lmata
|
197 |
cfg: registry.EngagementConfig{ |
|
7830697…
|
lmata
|
198 |
Channels: []string{"#fleet", "#project.test"}, |
|
7830697…
|
lmata
|
199 |
OpsChannels: []string{"#fleet"}, |
|
7830697…
|
lmata
|
200 |
Permissions: []string{"task.create"}, |
|
7830697…
|
lmata
|
201 |
RateLimit: registry.RateLimitConfig{MessagesPerSecond: 10, Burst: 20}, |
|
7830697…
|
lmata
|
202 |
Rules: registry.EngagementRules{ |
|
7830697…
|
lmata
|
203 |
RespondToTypes: []string{"task.create"}, |
|
7830697…
|
lmata
|
204 |
IgnoreNicks: []string{"scribe"}, |
|
7830697…
|
lmata
|
205 |
}, |
|
7830697…
|
lmata
|
206 |
}, |
|
7830697…
|
lmata
|
207 |
wantErr: false, |
|
7830697…
|
lmata
|
208 |
}, |
|
7830697…
|
lmata
|
209 |
{ |
|
7830697…
|
lmata
|
210 |
name: "empty config is valid", |
|
7830697…
|
lmata
|
211 |
cfg: registry.EngagementConfig{}, |
|
7830697…
|
lmata
|
212 |
wantErr: false, |
|
7830697…
|
lmata
|
213 |
}, |
|
7830697…
|
lmata
|
214 |
{ |
|
7830697…
|
lmata
|
215 |
name: "channel missing hash", |
|
7830697…
|
lmata
|
216 |
cfg: registry.EngagementConfig{Channels: []string{"fleet"}}, |
|
7830697…
|
lmata
|
217 |
wantErr: true, |
|
7830697…
|
lmata
|
218 |
}, |
|
7830697…
|
lmata
|
219 |
{ |
|
7830697…
|
lmata
|
220 |
name: "channel with space", |
|
7830697…
|
lmata
|
221 |
cfg: registry.EngagementConfig{Channels: []string{"#fleet channel"}}, |
|
7830697…
|
lmata
|
222 |
wantErr: true, |
|
7830697…
|
lmata
|
223 |
}, |
|
7830697…
|
lmata
|
224 |
{ |
|
7830697…
|
lmata
|
225 |
name: "ops_channel not in channels", |
|
7830697…
|
lmata
|
226 |
cfg: registry.EngagementConfig{Channels: []string{"#fleet"}, OpsChannels: []string{"#other"}}, |
|
7830697…
|
lmata
|
227 |
wantErr: true, |
|
7830697…
|
lmata
|
228 |
}, |
|
7830697…
|
lmata
|
229 |
{ |
|
7830697…
|
lmata
|
230 |
name: "negative rate limit", |
|
7830697…
|
lmata
|
231 |
cfg: registry.EngagementConfig{RateLimit: registry.RateLimitConfig{MessagesPerSecond: -1}}, |
|
7830697…
|
lmata
|
232 |
wantErr: true, |
|
7830697…
|
lmata
|
233 |
}, |
|
7830697…
|
lmata
|
234 |
{ |
|
7830697…
|
lmata
|
235 |
name: "negative burst", |
|
7830697…
|
lmata
|
236 |
cfg: registry.EngagementConfig{RateLimit: registry.RateLimitConfig{Burst: -5}}, |
|
7830697…
|
lmata
|
237 |
wantErr: true, |
|
7830697…
|
lmata
|
238 |
}, |
|
7830697…
|
lmata
|
239 |
{ |
|
7830697…
|
lmata
|
240 |
name: "empty respond_to_type", |
|
7830697…
|
lmata
|
241 |
cfg: registry.EngagementConfig{Rules: registry.EngagementRules{RespondToTypes: []string{""}}}, |
|
7830697…
|
lmata
|
242 |
wantErr: true, |
|
7830697…
|
lmata
|
243 |
}, |
|
7830697…
|
lmata
|
244 |
} |
|
7830697…
|
lmata
|
245 |
|
|
7830697…
|
lmata
|
246 |
for _, tt := range tests { |
|
7830697…
|
lmata
|
247 |
t.Run(tt.name, func(t *testing.T) { |
|
7830697…
|
lmata
|
248 |
err := tt.cfg.Validate() |
|
7830697…
|
lmata
|
249 |
if (err != nil) != tt.wantErr { |
|
7830697…
|
lmata
|
250 |
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) |
|
7830697…
|
lmata
|
251 |
} |
|
7830697…
|
lmata
|
252 |
}) |
|
7830697…
|
lmata
|
253 |
} |
|
7830697…
|
lmata
|
254 |
} |
|
7830697…
|
lmata
|
255 |
|
|
7830697…
|
lmata
|
256 |
func TestRegisterInvalidConfig(t *testing.T) { |
|
7830697…
|
lmata
|
257 |
p := newMockProvisioner() |
|
7830697…
|
lmata
|
258 |
r := registry.New(p, testKey) |
|
7830697…
|
lmata
|
259 |
|
|
7830697…
|
lmata
|
260 |
_, _, err := r.Register("bad-agent", registry.AgentTypeWorker, registry.EngagementConfig{ |
|
7830697…
|
lmata
|
261 |
Channels: []string{"no-hash-here"}, |
|
7830697…
|
lmata
|
262 |
}) |
|
7830697…
|
lmata
|
263 |
if err == nil { |
|
7830697…
|
lmata
|
264 |
t.Error("expected error for invalid channel name, got nil") |
|
7830697…
|
lmata
|
265 |
} |
|
7830697…
|
lmata
|
266 |
// Account should not have been created. |
|
7830697…
|
lmata
|
267 |
if p.passphrase("bad-agent") != "" { |
|
7830697…
|
lmata
|
268 |
t.Error("account should not be created when config is invalid") |
|
763c873…
|
lmata
|
269 |
} |
|
763c873…
|
lmata
|
270 |
} |
|
763c873…
|
lmata
|
271 |
|
|
763c873…
|
lmata
|
272 |
func TestAdopt(t *testing.T) { |
|
763c873…
|
lmata
|
273 |
p := newMockProvisioner() |
|
763c873…
|
lmata
|
274 |
r := registry.New(p, testKey) |
|
763c873…
|
lmata
|
275 |
|
|
763c873…
|
lmata
|
276 |
payload, err := r.Adopt("preexisting-bot", registry.AgentTypeWorker, |
|
763c873…
|
lmata
|
277 |
cfg([]string{"#fleet"}, []string{"read"})) |
|
763c873…
|
lmata
|
278 |
if err != nil { |
|
763c873…
|
lmata
|
279 |
t.Fatalf("Adopt: %v", err) |
|
763c873…
|
lmata
|
280 |
} |
|
763c873…
|
lmata
|
281 |
if payload.Payload.Nick != "preexisting-bot" { |
|
763c873…
|
lmata
|
282 |
t.Errorf("payload Nick = %q, want preexisting-bot", payload.Payload.Nick) |
|
763c873…
|
lmata
|
283 |
} |
|
763c873…
|
lmata
|
284 |
// Adopt must NOT create a NickServ account (password should be empty in mock). |
|
763c873…
|
lmata
|
285 |
if p.passphrase("preexisting-bot") != "" { |
|
763c873…
|
lmata
|
286 |
t.Error("Adopt should not create a NickServ account") |
|
763c873…
|
lmata
|
287 |
} |
|
763c873…
|
lmata
|
288 |
// Agent should be visible in the registry. |
|
763c873…
|
lmata
|
289 |
agent, err := r.Get("preexisting-bot") |
|
763c873…
|
lmata
|
290 |
if err != nil { |
|
763c873…
|
lmata
|
291 |
t.Fatalf("Get after Adopt: %v", err) |
|
763c873…
|
lmata
|
292 |
} |
|
763c873…
|
lmata
|
293 |
if agent.Nick != "preexisting-bot" { |
|
763c873…
|
lmata
|
294 |
t.Errorf("Get Nick = %q", agent.Nick) |
|
763c873…
|
lmata
|
295 |
} |
|
763c873…
|
lmata
|
296 |
} |
|
763c873…
|
lmata
|
297 |
|
|
763c873…
|
lmata
|
298 |
func TestAdoptDuplicate(t *testing.T) { |
|
763c873…
|
lmata
|
299 |
p := newMockProvisioner() |
|
763c873…
|
lmata
|
300 |
r := registry.New(p, testKey) |
|
763c873…
|
lmata
|
301 |
|
|
763c873…
|
lmata
|
302 |
if _, err := r.Adopt("bot-dup", registry.AgentTypeWorker, registry.EngagementConfig{}); err != nil { |
|
763c873…
|
lmata
|
303 |
t.Fatalf("first Adopt: %v", err) |
|
763c873…
|
lmata
|
304 |
} |
|
763c873…
|
lmata
|
305 |
if _, err := r.Adopt("bot-dup", registry.AgentTypeWorker, registry.EngagementConfig{}); err == nil { |
|
763c873…
|
lmata
|
306 |
t.Error("expected error on duplicate Adopt, got nil") |
|
763c873…
|
lmata
|
307 |
} |
|
763c873…
|
lmata
|
308 |
} |
|
763c873…
|
lmata
|
309 |
|
|
763c873…
|
lmata
|
310 |
func TestDelete(t *testing.T) { |
|
763c873…
|
lmata
|
311 |
p := newMockProvisioner() |
|
763c873…
|
lmata
|
312 |
r := registry.New(p, testKey) |
|
763c873…
|
lmata
|
313 |
|
|
763c873…
|
lmata
|
314 |
if _, _, err := r.Register("del-agent", registry.AgentTypeWorker, registry.EngagementConfig{}); err != nil { |
|
763c873…
|
lmata
|
315 |
t.Fatalf("Register: %v", err) |
|
763c873…
|
lmata
|
316 |
} |
|
763c873…
|
lmata
|
317 |
|
|
763c873…
|
lmata
|
318 |
if err := r.Delete("del-agent"); err != nil { |
|
763c873…
|
lmata
|
319 |
t.Fatalf("Delete: %v", err) |
|
763c873…
|
lmata
|
320 |
} |
|
763c873…
|
lmata
|
321 |
|
|
763c873…
|
lmata
|
322 |
// Agent must no longer appear in List. |
|
763c873…
|
lmata
|
323 |
for _, a := range r.List() { |
|
763c873…
|
lmata
|
324 |
if a.Nick == "del-agent" { |
|
763c873…
|
lmata
|
325 |
t.Error("deleted agent should not appear in List()") |
|
763c873…
|
lmata
|
326 |
} |
|
763c873…
|
lmata
|
327 |
} |
|
763c873…
|
lmata
|
328 |
|
|
763c873…
|
lmata
|
329 |
// Get must fail. |
|
763c873…
|
lmata
|
330 |
if _, err := r.Get("del-agent"); err == nil { |
|
763c873…
|
lmata
|
331 |
t.Error("Get should fail for deleted agent") |
|
763c873…
|
lmata
|
332 |
} |
|
763c873…
|
lmata
|
333 |
} |
|
763c873…
|
lmata
|
334 |
|
|
763c873…
|
lmata
|
335 |
func TestDeleteRevoked(t *testing.T) { |
|
763c873…
|
lmata
|
336 |
// Deleting a revoked agent should succeed (lockout step skipped). |
|
763c873…
|
lmata
|
337 |
p := newMockProvisioner() |
|
763c873…
|
lmata
|
338 |
r := registry.New(p, testKey) |
|
763c873…
|
lmata
|
339 |
|
|
763c873…
|
lmata
|
340 |
if _, _, err := r.Register("rev-del", registry.AgentTypeWorker, registry.EngagementConfig{}); err != nil { |
|
763c873…
|
lmata
|
341 |
t.Fatalf("Register: %v", err) |
|
763c873…
|
lmata
|
342 |
} |
|
763c873…
|
lmata
|
343 |
if err := r.Revoke("rev-del"); err != nil { |
|
763c873…
|
lmata
|
344 |
t.Fatalf("Revoke: %v", err) |
|
763c873…
|
lmata
|
345 |
} |
|
763c873…
|
lmata
|
346 |
if err := r.Delete("rev-del"); err != nil { |
|
763c873…
|
lmata
|
347 |
t.Fatalf("Delete of revoked agent: %v", err) |
|
763c873…
|
lmata
|
348 |
} |
|
763c873…
|
lmata
|
349 |
} |
|
763c873…
|
lmata
|
350 |
|
|
763c873…
|
lmata
|
351 |
func TestDeleteNotFound(t *testing.T) { |
|
763c873…
|
lmata
|
352 |
p := newMockProvisioner() |
|
763c873…
|
lmata
|
353 |
r := registry.New(p, testKey) |
|
763c873…
|
lmata
|
354 |
if err := r.Delete("nobody"); err == nil { |
|
763c873…
|
lmata
|
355 |
t.Error("expected error deleting non-existent agent, got nil") |
|
763c873…
|
lmata
|
356 |
} |
|
763c873…
|
lmata
|
357 |
} |
|
763c873…
|
lmata
|
358 |
|
|
763c873…
|
lmata
|
359 |
func TestUpdateChannels(t *testing.T) { |
|
763c873…
|
lmata
|
360 |
p := newMockProvisioner() |
|
763c873…
|
lmata
|
361 |
r := registry.New(p, testKey) |
|
763c873…
|
lmata
|
362 |
|
|
763c873…
|
lmata
|
363 |
if _, _, err := r.Register("chan-agent", registry.AgentTypeWorker, |
|
763c873…
|
lmata
|
364 |
cfg([]string{"#fleet"}, nil)); err != nil { |
|
763c873…
|
lmata
|
365 |
t.Fatalf("Register: %v", err) |
|
763c873…
|
lmata
|
366 |
} |
|
763c873…
|
lmata
|
367 |
|
|
763c873…
|
lmata
|
368 |
newChans := []string{"#fleet", "#project.foo"} |
|
763c873…
|
lmata
|
369 |
if err := r.UpdateChannels("chan-agent", newChans); err != nil { |
|
763c873…
|
lmata
|
370 |
t.Fatalf("UpdateChannels: %v", err) |
|
763c873…
|
lmata
|
371 |
} |
|
763c873…
|
lmata
|
372 |
|
|
763c873…
|
lmata
|
373 |
agent, err := r.Get("chan-agent") |
|
763c873…
|
lmata
|
374 |
if err != nil { |
|
763c873…
|
lmata
|
375 |
t.Fatalf("Get: %v", err) |
|
763c873…
|
lmata
|
376 |
} |
|
763c873…
|
lmata
|
377 |
if len(agent.Channels) != 2 { |
|
763c873…
|
lmata
|
378 |
t.Errorf("Channels len = %d, want 2", len(agent.Channels)) |
|
763c873…
|
lmata
|
379 |
} |
|
763c873…
|
lmata
|
380 |
if agent.Channels[1] != "#project.foo" { |
|
763c873…
|
lmata
|
381 |
t.Errorf("Channels[1] = %q, want #project.foo", agent.Channels[1]) |
|
763c873…
|
lmata
|
382 |
} |
|
763c873…
|
lmata
|
383 |
} |
|
763c873…
|
lmata
|
384 |
|
|
763c873…
|
lmata
|
385 |
func TestUpdateChannelsNotFound(t *testing.T) { |
|
763c873…
|
lmata
|
386 |
p := newMockProvisioner() |
|
763c873…
|
lmata
|
387 |
r := registry.New(p, testKey) |
|
763c873…
|
lmata
|
388 |
if err := r.UpdateChannels("ghost", []string{"#fleet"}); err == nil { |
|
763c873…
|
lmata
|
389 |
t.Error("expected error for unknown agent, got nil") |
|
763c873…
|
lmata
|
390 |
} |
|
763c873…
|
lmata
|
391 |
} |
|
763c873…
|
lmata
|
392 |
|
|
763c873…
|
lmata
|
393 |
func TestSetDataPathPersistence(t *testing.T) { |
|
763c873…
|
lmata
|
394 |
dataPath := t.TempDir() + "/agents.json" |
|
763c873…
|
lmata
|
395 |
p := newMockProvisioner() |
|
763c873…
|
lmata
|
396 |
r := registry.New(p, testKey) |
|
763c873…
|
lmata
|
397 |
|
|
763c873…
|
lmata
|
398 |
if err := r.SetDataPath(dataPath); err != nil { |
|
763c873…
|
lmata
|
399 |
t.Fatalf("SetDataPath: %v", err) |
|
763c873…
|
lmata
|
400 |
} |
|
763c873…
|
lmata
|
401 |
|
|
763c873…
|
lmata
|
402 |
if _, _, err := r.Register("persist-me", registry.AgentTypeWorker, |
|
763c873…
|
lmata
|
403 |
cfg([]string{"#fleet"}, nil)); err != nil { |
|
763c873…
|
lmata
|
404 |
t.Fatalf("Register: %v", err) |
|
763c873…
|
lmata
|
405 |
} |
|
763c873…
|
lmata
|
406 |
|
|
763c873…
|
lmata
|
407 |
// New registry loaded from the same path — must contain the persisted agent. |
|
763c873…
|
lmata
|
408 |
r2 := registry.New(newMockProvisioner(), testKey) |
|
763c873…
|
lmata
|
409 |
if err := r2.SetDataPath(dataPath); err != nil { |
|
763c873…
|
lmata
|
410 |
t.Fatalf("SetDataPath (r2): %v", err) |
|
763c873…
|
lmata
|
411 |
} |
|
763c873…
|
lmata
|
412 |
|
|
763c873…
|
lmata
|
413 |
agent, err := r2.Get("persist-me") |
|
763c873…
|
lmata
|
414 |
if err != nil { |
|
763c873…
|
lmata
|
415 |
t.Fatalf("Get after reload: %v", err) |
|
763c873…
|
lmata
|
416 |
} |
|
763c873…
|
lmata
|
417 |
if agent.Nick != "persist-me" { |
|
763c873…
|
lmata
|
418 |
t.Errorf("reloaded Nick = %q, want persist-me", agent.Nick) |
|
763c873…
|
lmata
|
419 |
} |
|
763c873…
|
lmata
|
420 |
} |
|
763c873…
|
lmata
|
421 |
|
|
763c873…
|
lmata
|
422 |
func TestSetDataPathMissingFileOK(t *testing.T) { |
|
763c873…
|
lmata
|
423 |
r := registry.New(newMockProvisioner(), testKey) |
|
763c873…
|
lmata
|
424 |
// Path doesn't exist yet — should not error. |
|
763c873…
|
lmata
|
425 |
if err := r.SetDataPath(t.TempDir() + "/agents.json"); err != nil { |
|
763c873…
|
lmata
|
426 |
t.Errorf("SetDataPath on missing file: %v", err) |
|
1efddcb…
|
lmata
|
427 |
} |
|
1efddcb…
|
lmata
|
428 |
} |