|
dd3a887…
|
lmata
|
1 |
package api |
|
dd3a887…
|
lmata
|
2 |
|
|
dd3a887…
|
lmata
|
3 |
import ( |
|
dd3a887…
|
lmata
|
4 |
"bytes" |
|
dd3a887…
|
lmata
|
5 |
"encoding/json" |
|
0902a34…
|
lmata
|
6 |
"fmt" |
|
dd3a887…
|
lmata
|
7 |
"io" |
|
dd3a887…
|
lmata
|
8 |
"log/slog" |
|
dd3a887…
|
lmata
|
9 |
"net/http" |
|
dd3a887…
|
lmata
|
10 |
"net/http/httptest" |
|
dd3a887…
|
lmata
|
11 |
"testing" |
|
dd3a887…
|
lmata
|
12 |
"time" |
|
dd3a887…
|
lmata
|
13 |
|
|
68677f9…
|
noreply
|
14 |
"github.com/conflicthq/scuttlebot/internal/auth" |
|
dd3a887…
|
lmata
|
15 |
"github.com/conflicthq/scuttlebot/internal/config" |
|
dd3a887…
|
lmata
|
16 |
"github.com/conflicthq/scuttlebot/internal/registry" |
|
dd3a887…
|
lmata
|
17 |
"github.com/conflicthq/scuttlebot/internal/topology" |
|
dd3a887…
|
lmata
|
18 |
) |
|
0902a34…
|
lmata
|
19 |
|
|
0902a34…
|
lmata
|
20 |
// accessCall records a single GrantAccess or RevokeAccess invocation. |
|
0902a34…
|
lmata
|
21 |
type accessCall struct { |
|
0902a34…
|
lmata
|
22 |
Nick string |
|
0902a34…
|
lmata
|
23 |
Channel string |
|
0902a34…
|
lmata
|
24 |
Level string // "OP", "VOICE", or "" for revoke |
|
0902a34…
|
lmata
|
25 |
} |
|
dd3a887…
|
lmata
|
26 |
|
|
dd3a887…
|
lmata
|
27 |
// stubTopologyManager implements topologyManager for tests. |
|
dd3a887…
|
lmata
|
28 |
// It records the last ProvisionChannel call and returns a canned Policy. |
|
dd3a887…
|
lmata
|
29 |
type stubTopologyManager struct { |
|
dd3a887…
|
lmata
|
30 |
last topology.ChannelConfig |
|
dd3a887…
|
lmata
|
31 |
policy *topology.Policy |
|
dd3a887…
|
lmata
|
32 |
provErr error |
|
0902a34…
|
lmata
|
33 |
grants []accessCall |
|
0902a34…
|
lmata
|
34 |
revokes []accessCall |
|
dd3a887…
|
lmata
|
35 |
} |
|
dd3a887…
|
lmata
|
36 |
|
|
dd3a887…
|
lmata
|
37 |
func (s *stubTopologyManager) ProvisionChannel(ch topology.ChannelConfig) error { |
|
dd3a887…
|
lmata
|
38 |
s.last = ch |
|
dd3a887…
|
lmata
|
39 |
return s.provErr |
|
dd3a887…
|
lmata
|
40 |
} |
|
dd3a887…
|
lmata
|
41 |
|
|
f0853f5…
|
lmata
|
42 |
func (s *stubTopologyManager) DropChannel(_ string) {} |
|
f0853f5…
|
lmata
|
43 |
|
|
dd3a887…
|
lmata
|
44 |
func (s *stubTopologyManager) Policy() *topology.Policy { return s.policy } |
|
f0853f5…
|
lmata
|
45 |
|
|
0902a34…
|
lmata
|
46 |
func (s *stubTopologyManager) GrantAccess(nick, channel, level string) { |
|
0902a34…
|
lmata
|
47 |
s.grants = append(s.grants, accessCall{Nick: nick, Channel: channel, Level: level}) |
|
0902a34…
|
lmata
|
48 |
} |
|
0902a34…
|
lmata
|
49 |
|
|
0902a34…
|
lmata
|
50 |
func (s *stubTopologyManager) RevokeAccess(nick, channel string) { |
|
0902a34…
|
lmata
|
51 |
s.revokes = append(s.revokes, accessCall{Nick: nick, Channel: channel}) |
|
0902a34…
|
lmata
|
52 |
} |
|
900677e…
|
noreply
|
53 |
|
|
900677e…
|
noreply
|
54 |
func (s *stubTopologyManager) ListChannels() []topology.ChannelInfo { return nil } |
|
68677f9…
|
noreply
|
55 |
|
|
0902a34…
|
lmata
|
56 |
// stubProvisioner is a minimal AccountProvisioner for agent registration tests. |
|
0902a34…
|
lmata
|
57 |
type stubProvisioner struct { |
|
0902a34…
|
lmata
|
58 |
accounts map[string]string |
|
0902a34…
|
lmata
|
59 |
} |
|
0902a34…
|
lmata
|
60 |
|
|
0902a34…
|
lmata
|
61 |
func newStubProvisioner() *stubProvisioner { |
|
0902a34…
|
lmata
|
62 |
return &stubProvisioner{accounts: make(map[string]string)} |
|
0902a34…
|
lmata
|
63 |
} |
|
0902a34…
|
lmata
|
64 |
|
|
0902a34…
|
lmata
|
65 |
func (p *stubProvisioner) RegisterAccount(name, pass string) error { |
|
0902a34…
|
lmata
|
66 |
if _, ok := p.accounts[name]; ok { |
|
0902a34…
|
lmata
|
67 |
return fmt.Errorf("ACCOUNT_EXISTS") |
|
0902a34…
|
lmata
|
68 |
} |
|
0902a34…
|
lmata
|
69 |
p.accounts[name] = pass |
|
0902a34…
|
lmata
|
70 |
return nil |
|
0902a34…
|
lmata
|
71 |
} |
|
0902a34…
|
lmata
|
72 |
|
|
0902a34…
|
lmata
|
73 |
func (p *stubProvisioner) ChangePassword(name, pass string) error { |
|
0902a34…
|
lmata
|
74 |
p.accounts[name] = pass |
|
0902a34…
|
lmata
|
75 |
return nil |
|
0902a34…
|
lmata
|
76 |
} |
|
0902a34…
|
lmata
|
77 |
|
|
dd3a887…
|
lmata
|
78 |
func newTopoTestServer(t *testing.T, topo *stubTopologyManager) (*httptest.Server, string) { |
|
dd3a887…
|
lmata
|
79 |
t.Helper() |
|
dd3a887…
|
lmata
|
80 |
reg := registry.New(nil, []byte("key")) |
|
dd3a887…
|
lmata
|
81 |
log := slog.New(slog.NewTextHandler(io.Discard, nil)) |
|
68677f9…
|
noreply
|
82 |
srv := httptest.NewServer(New(reg, auth.TestStore("tok"), nil, nil, nil, nil, topo, nil, "", log).Handler()) |
|
0902a34…
|
lmata
|
83 |
t.Cleanup(srv.Close) |
|
0902a34…
|
lmata
|
84 |
return srv, "tok" |
|
0902a34…
|
lmata
|
85 |
} |
|
0902a34…
|
lmata
|
86 |
|
|
0902a34…
|
lmata
|
87 |
// newTopoTestServerWithRegistry creates a test server with both topology and a |
|
0902a34…
|
lmata
|
88 |
// real registry backed by stubProvisioner, so agent registration works. |
|
0902a34…
|
lmata
|
89 |
func newTopoTestServerWithRegistry(t *testing.T, topo *stubTopologyManager) (*httptest.Server, string) { |
|
0902a34…
|
lmata
|
90 |
t.Helper() |
|
0902a34…
|
lmata
|
91 |
reg := registry.New(newStubProvisioner(), []byte("key")) |
|
0902a34…
|
lmata
|
92 |
log := slog.New(slog.NewTextHandler(io.Discard, nil)) |
|
68677f9…
|
noreply
|
93 |
srv := httptest.NewServer(New(reg, auth.TestStore("tok"), nil, nil, nil, nil, topo, nil, "", log).Handler()) |
|
dd3a887…
|
lmata
|
94 |
t.Cleanup(srv.Close) |
|
dd3a887…
|
lmata
|
95 |
return srv, "tok" |
|
dd3a887…
|
lmata
|
96 |
} |
|
dd3a887…
|
lmata
|
97 |
|
|
dd3a887…
|
lmata
|
98 |
func TestHandleProvisionChannel(t *testing.T) { |
|
dd3a887…
|
lmata
|
99 |
pol := topology.NewPolicy(config.TopologyConfig{ |
|
dd3a887…
|
lmata
|
100 |
Types: []config.ChannelTypeConfig{ |
|
dd3a887…
|
lmata
|
101 |
{ |
|
dd3a887…
|
lmata
|
102 |
Name: "task", |
|
dd3a887…
|
lmata
|
103 |
Prefix: "task.", |
|
dd3a887…
|
lmata
|
104 |
Autojoin: []string{"bridge", "scribe"}, |
|
dd3a887…
|
lmata
|
105 |
TTL: config.Duration{Duration: 72 * time.Hour}, |
|
dd3a887…
|
lmata
|
106 |
}, |
|
dd3a887…
|
lmata
|
107 |
}, |
|
dd3a887…
|
lmata
|
108 |
}) |
|
dd3a887…
|
lmata
|
109 |
stub := &stubTopologyManager{policy: pol} |
|
dd3a887…
|
lmata
|
110 |
srv, tok := newTopoTestServer(t, stub) |
|
dd3a887…
|
lmata
|
111 |
|
|
dd3a887…
|
lmata
|
112 |
body, _ := json.Marshal(map[string]string{"name": "#task.gh-1"}) |
|
dd3a887…
|
lmata
|
113 |
req, _ := http.NewRequest(http.MethodPost, srv.URL+"/v1/channels", bytes.NewReader(body)) |
|
dd3a887…
|
lmata
|
114 |
req.Header.Set("Authorization", "Bearer "+tok) |
|
dd3a887…
|
lmata
|
115 |
req.Header.Set("Content-Type", "application/json") |
|
dd3a887…
|
lmata
|
116 |
resp, err := http.DefaultClient.Do(req) |
|
dd3a887…
|
lmata
|
117 |
if err != nil { |
|
dd3a887…
|
lmata
|
118 |
t.Fatal(err) |
|
dd3a887…
|
lmata
|
119 |
} |
|
dd3a887…
|
lmata
|
120 |
defer resp.Body.Close() |
|
dd3a887…
|
lmata
|
121 |
|
|
dd3a887…
|
lmata
|
122 |
if resp.StatusCode != http.StatusCreated { |
|
dd3a887…
|
lmata
|
123 |
t.Fatalf("want 201, got %d", resp.StatusCode) |
|
dd3a887…
|
lmata
|
124 |
} |
|
dd3a887…
|
lmata
|
125 |
var got struct { |
|
dd3a887…
|
lmata
|
126 |
Channel string `json:"channel"` |
|
dd3a887…
|
lmata
|
127 |
Type string `json:"type"` |
|
dd3a887…
|
lmata
|
128 |
Autojoin []string `json:"autojoin"` |
|
dd3a887…
|
lmata
|
129 |
} |
|
dd3a887…
|
lmata
|
130 |
if err := json.NewDecoder(resp.Body).Decode(&got); err != nil { |
|
dd3a887…
|
lmata
|
131 |
t.Fatal(err) |
|
dd3a887…
|
lmata
|
132 |
} |
|
dd3a887…
|
lmata
|
133 |
if got.Channel != "#task.gh-1" { |
|
dd3a887…
|
lmata
|
134 |
t.Errorf("channel = %q, want #task.gh-1", got.Channel) |
|
dd3a887…
|
lmata
|
135 |
} |
|
dd3a887…
|
lmata
|
136 |
if got.Type != "task" { |
|
dd3a887…
|
lmata
|
137 |
t.Errorf("type = %q, want task", got.Type) |
|
dd3a887…
|
lmata
|
138 |
} |
|
dd3a887…
|
lmata
|
139 |
if len(got.Autojoin) != 2 || got.Autojoin[0] != "bridge" { |
|
dd3a887…
|
lmata
|
140 |
t.Errorf("autojoin = %v, want [bridge scribe]", got.Autojoin) |
|
dd3a887…
|
lmata
|
141 |
} |
|
dd3a887…
|
lmata
|
142 |
// Verify autojoin was forwarded to ProvisionChannel. |
|
dd3a887…
|
lmata
|
143 |
if len(stub.last.Autojoin) != 2 { |
|
dd3a887…
|
lmata
|
144 |
t.Errorf("stub.last.Autojoin = %v, want [bridge scribe]", stub.last.Autojoin) |
|
dd3a887…
|
lmata
|
145 |
} |
|
dd3a887…
|
lmata
|
146 |
} |
|
dd3a887…
|
lmata
|
147 |
|
|
dd3a887…
|
lmata
|
148 |
func TestHandleProvisionChannelInvalidName(t *testing.T) { |
|
dd3a887…
|
lmata
|
149 |
stub := &stubTopologyManager{} |
|
dd3a887…
|
lmata
|
150 |
srv, tok := newTopoTestServer(t, stub) |
|
dd3a887…
|
lmata
|
151 |
|
|
dd3a887…
|
lmata
|
152 |
body, _ := json.Marshal(map[string]string{"name": "no-hash"}) |
|
dd3a887…
|
lmata
|
153 |
req, _ := http.NewRequest(http.MethodPost, srv.URL+"/v1/channels", bytes.NewReader(body)) |
|
dd3a887…
|
lmata
|
154 |
req.Header.Set("Authorization", "Bearer "+tok) |
|
dd3a887…
|
lmata
|
155 |
req.Header.Set("Content-Type", "application/json") |
|
336984b…
|
lmata
|
156 |
resp, err := http.DefaultClient.Do(req) |
|
336984b…
|
lmata
|
157 |
if err != nil { |
|
336984b…
|
lmata
|
158 |
t.Fatalf("do: %v", err) |
|
336984b…
|
lmata
|
159 |
} |
|
dd3a887…
|
lmata
|
160 |
defer resp.Body.Close() |
|
dd3a887…
|
lmata
|
161 |
if resp.StatusCode != http.StatusBadRequest { |
|
dd3a887…
|
lmata
|
162 |
t.Errorf("want 400, got %d", resp.StatusCode) |
|
dd3a887…
|
lmata
|
163 |
} |
|
dd3a887…
|
lmata
|
164 |
} |
|
dd3a887…
|
lmata
|
165 |
|
|
dd3a887…
|
lmata
|
166 |
func TestHandleGetTopology(t *testing.T) { |
|
dd3a887…
|
lmata
|
167 |
pol := topology.NewPolicy(config.TopologyConfig{ |
|
dd3a887…
|
lmata
|
168 |
Channels: []config.StaticChannelConfig{{Name: "#general"}}, |
|
dd3a887…
|
lmata
|
169 |
Types: []config.ChannelTypeConfig{ |
|
dd3a887…
|
lmata
|
170 |
{Name: "task", Prefix: "task.", Autojoin: []string{"bridge"}}, |
|
dd3a887…
|
lmata
|
171 |
}, |
|
dd3a887…
|
lmata
|
172 |
}) |
|
dd3a887…
|
lmata
|
173 |
stub := &stubTopologyManager{policy: pol} |
|
dd3a887…
|
lmata
|
174 |
srv, tok := newTopoTestServer(t, stub) |
|
dd3a887…
|
lmata
|
175 |
|
|
dd3a887…
|
lmata
|
176 |
req, _ := http.NewRequest(http.MethodGet, srv.URL+"/v1/topology", nil) |
|
dd3a887…
|
lmata
|
177 |
req.Header.Set("Authorization", "Bearer "+tok) |
|
dd3a887…
|
lmata
|
178 |
resp, err := http.DefaultClient.Do(req) |
|
dd3a887…
|
lmata
|
179 |
if err != nil { |
|
dd3a887…
|
lmata
|
180 |
t.Fatal(err) |
|
dd3a887…
|
lmata
|
181 |
} |
|
dd3a887…
|
lmata
|
182 |
defer resp.Body.Close() |
|
dd3a887…
|
lmata
|
183 |
|
|
dd3a887…
|
lmata
|
184 |
if resp.StatusCode != http.StatusOK { |
|
dd3a887…
|
lmata
|
185 |
t.Fatalf("want 200, got %d", resp.StatusCode) |
|
dd3a887…
|
lmata
|
186 |
} |
|
dd3a887…
|
lmata
|
187 |
var got struct { |
|
dd3a887…
|
lmata
|
188 |
StaticChannels []string `json:"static_channels"` |
|
dd3a887…
|
lmata
|
189 |
Types []struct { |
|
dd3a887…
|
lmata
|
190 |
Name string `json:"name"` |
|
dd3a887…
|
lmata
|
191 |
Prefix string `json:"prefix"` |
|
dd3a887…
|
lmata
|
192 |
} `json:"types"` |
|
dd3a887…
|
lmata
|
193 |
} |
|
dd3a887…
|
lmata
|
194 |
if err := json.NewDecoder(resp.Body).Decode(&got); err != nil { |
|
dd3a887…
|
lmata
|
195 |
t.Fatal(err) |
|
dd3a887…
|
lmata
|
196 |
} |
|
dd3a887…
|
lmata
|
197 |
if len(got.StaticChannels) != 1 || got.StaticChannels[0] != "#general" { |
|
dd3a887…
|
lmata
|
198 |
t.Errorf("static_channels = %v, want [#general]", got.StaticChannels) |
|
dd3a887…
|
lmata
|
199 |
} |
|
dd3a887…
|
lmata
|
200 |
if len(got.Types) != 1 || got.Types[0].Name != "task" { |
|
dd3a887…
|
lmata
|
201 |
t.Errorf("types = %v", got.Types) |
|
0902a34…
|
lmata
|
202 |
} |
|
0902a34…
|
lmata
|
203 |
} |
|
0902a34…
|
lmata
|
204 |
|
|
0902a34…
|
lmata
|
205 |
// --- Agent mode assignment tests --- |
|
0902a34…
|
lmata
|
206 |
|
|
0902a34…
|
lmata
|
207 |
// topoDoJSON is a helper for issuing authenticated JSON requests against a test server. |
|
0902a34…
|
lmata
|
208 |
func topoDoJSON(t *testing.T, srv *httptest.Server, tok, method, path string, body any) *http.Response { |
|
0902a34…
|
lmata
|
209 |
t.Helper() |
|
0902a34…
|
lmata
|
210 |
var buf bytes.Buffer |
|
0902a34…
|
lmata
|
211 |
if body != nil { |
|
0902a34…
|
lmata
|
212 |
if err := json.NewEncoder(&buf).Encode(body); err != nil { |
|
0902a34…
|
lmata
|
213 |
t.Fatalf("encode: %v", err) |
|
0902a34…
|
lmata
|
214 |
} |
|
0902a34…
|
lmata
|
215 |
} |
|
0902a34…
|
lmata
|
216 |
req, _ := http.NewRequest(method, srv.URL+path, &buf) |
|
0902a34…
|
lmata
|
217 |
req.Header.Set("Authorization", "Bearer "+tok) |
|
0902a34…
|
lmata
|
218 |
req.Header.Set("Content-Type", "application/json") |
|
0902a34…
|
lmata
|
219 |
resp, err := http.DefaultClient.Do(req) |
|
0902a34…
|
lmata
|
220 |
if err != nil { |
|
0902a34…
|
lmata
|
221 |
t.Fatalf("do: %v", err) |
|
0902a34…
|
lmata
|
222 |
} |
|
0902a34…
|
lmata
|
223 |
return resp |
|
0902a34…
|
lmata
|
224 |
} |
|
0902a34…
|
lmata
|
225 |
|
|
0902a34…
|
lmata
|
226 |
func TestRegisterGrantsOPForOrchestrator(t *testing.T) { |
|
0902a34…
|
lmata
|
227 |
stub := &stubTopologyManager{} |
|
0902a34…
|
lmata
|
228 |
srv, tok := newTopoTestServerWithRegistry(t, stub) |
|
0902a34…
|
lmata
|
229 |
|
|
0902a34…
|
lmata
|
230 |
resp := topoDoJSON(t, srv, tok, "POST", "/v1/agents/register", map[string]any{ |
|
0902a34…
|
lmata
|
231 |
"nick": "orch-1", |
|
0902a34…
|
lmata
|
232 |
"type": "orchestrator", |
|
0902a34…
|
lmata
|
233 |
"channels": []string{"#fleet", "#project.foo"}, |
|
0902a34…
|
lmata
|
234 |
}) |
|
0902a34…
|
lmata
|
235 |
defer resp.Body.Close() |
|
0902a34…
|
lmata
|
236 |
if resp.StatusCode != http.StatusCreated { |
|
0902a34…
|
lmata
|
237 |
t.Fatalf("register: want 201, got %d", resp.StatusCode) |
|
0902a34…
|
lmata
|
238 |
} |
|
0902a34…
|
lmata
|
239 |
|
|
0902a34…
|
lmata
|
240 |
if len(stub.grants) != 2 { |
|
0902a34…
|
lmata
|
241 |
t.Fatalf("grants: want 2, got %d", len(stub.grants)) |
|
0902a34…
|
lmata
|
242 |
} |
|
0902a34…
|
lmata
|
243 |
for i, want := range []accessCall{ |
|
0902a34…
|
lmata
|
244 |
{Nick: "orch-1", Channel: "#fleet", Level: "OP"}, |
|
0902a34…
|
lmata
|
245 |
{Nick: "orch-1", Channel: "#project.foo", Level: "OP"}, |
|
0902a34…
|
lmata
|
246 |
} { |
|
0902a34…
|
lmata
|
247 |
if stub.grants[i] != want { |
|
0902a34…
|
lmata
|
248 |
t.Errorf("grant[%d] = %+v, want %+v", i, stub.grants[i], want) |
|
0902a34…
|
lmata
|
249 |
} |
|
0902a34…
|
lmata
|
250 |
} |
|
0902a34…
|
lmata
|
251 |
} |
|
0902a34…
|
lmata
|
252 |
|
|
0902a34…
|
lmata
|
253 |
func TestRegisterGrantsVOICEForWorker(t *testing.T) { |
|
0902a34…
|
lmata
|
254 |
stub := &stubTopologyManager{} |
|
0902a34…
|
lmata
|
255 |
srv, tok := newTopoTestServerWithRegistry(t, stub) |
|
0902a34…
|
lmata
|
256 |
|
|
0902a34…
|
lmata
|
257 |
resp := topoDoJSON(t, srv, tok, "POST", "/v1/agents/register", map[string]any{ |
|
0902a34…
|
lmata
|
258 |
"nick": "worker-1", |
|
0902a34…
|
lmata
|
259 |
"type": "worker", |
|
0902a34…
|
lmata
|
260 |
"channels": []string{"#fleet"}, |
|
0902a34…
|
lmata
|
261 |
}) |
|
0902a34…
|
lmata
|
262 |
defer resp.Body.Close() |
|
0902a34…
|
lmata
|
263 |
if resp.StatusCode != http.StatusCreated { |
|
0902a34…
|
lmata
|
264 |
t.Fatalf("register: want 201, got %d", resp.StatusCode) |
|
0902a34…
|
lmata
|
265 |
} |
|
0902a34…
|
lmata
|
266 |
|
|
0902a34…
|
lmata
|
267 |
if len(stub.grants) != 1 { |
|
0902a34…
|
lmata
|
268 |
t.Fatalf("grants: want 1, got %d", len(stub.grants)) |
|
0902a34…
|
lmata
|
269 |
} |
|
0902a34…
|
lmata
|
270 |
if stub.grants[0].Level != "VOICE" { |
|
0902a34…
|
lmata
|
271 |
t.Errorf("level = %q, want VOICE", stub.grants[0].Level) |
|
0902a34…
|
lmata
|
272 |
} |
|
0902a34…
|
lmata
|
273 |
} |
|
0902a34…
|
lmata
|
274 |
|
|
0902a34…
|
lmata
|
275 |
func TestRegisterNoModeForObserver(t *testing.T) { |
|
0902a34…
|
lmata
|
276 |
stub := &stubTopologyManager{} |
|
0902a34…
|
lmata
|
277 |
srv, tok := newTopoTestServerWithRegistry(t, stub) |
|
0902a34…
|
lmata
|
278 |
|
|
0902a34…
|
lmata
|
279 |
resp := topoDoJSON(t, srv, tok, "POST", "/v1/agents/register", map[string]any{ |
|
0902a34…
|
lmata
|
280 |
"nick": "obs-1", |
|
0902a34…
|
lmata
|
281 |
"type": "observer", |
|
0902a34…
|
lmata
|
282 |
"channels": []string{"#fleet"}, |
|
0902a34…
|
lmata
|
283 |
}) |
|
0902a34…
|
lmata
|
284 |
defer resp.Body.Close() |
|
0902a34…
|
lmata
|
285 |
if resp.StatusCode != http.StatusCreated { |
|
0902a34…
|
lmata
|
286 |
t.Fatalf("register: want 201, got %d", resp.StatusCode) |
|
0902a34…
|
lmata
|
287 |
} |
|
0902a34…
|
lmata
|
288 |
|
|
0902a34…
|
lmata
|
289 |
if len(stub.grants) != 0 { |
|
0902a34…
|
lmata
|
290 |
t.Errorf("grants: want 0, got %d — observer should get no mode", len(stub.grants)) |
|
0902a34…
|
lmata
|
291 |
} |
|
0902a34…
|
lmata
|
292 |
} |
|
0902a34…
|
lmata
|
293 |
|
|
0902a34…
|
lmata
|
294 |
func TestRegisterGrantsOPForOperator(t *testing.T) { |
|
0902a34…
|
lmata
|
295 |
stub := &stubTopologyManager{} |
|
0902a34…
|
lmata
|
296 |
srv, tok := newTopoTestServerWithRegistry(t, stub) |
|
0902a34…
|
lmata
|
297 |
|
|
0902a34…
|
lmata
|
298 |
resp := topoDoJSON(t, srv, tok, "POST", "/v1/agents/register", map[string]any{ |
|
0902a34…
|
lmata
|
299 |
"nick": "human-op", |
|
0902a34…
|
lmata
|
300 |
"type": "operator", |
|
0902a34…
|
lmata
|
301 |
"channels": []string{"#fleet"}, |
|
0902a34…
|
lmata
|
302 |
}) |
|
0902a34…
|
lmata
|
303 |
defer resp.Body.Close() |
|
0902a34…
|
lmata
|
304 |
if resp.StatusCode != http.StatusCreated { |
|
0902a34…
|
lmata
|
305 |
t.Fatalf("register: want 201, got %d", resp.StatusCode) |
|
0902a34…
|
lmata
|
306 |
} |
|
0902a34…
|
lmata
|
307 |
|
|
0902a34…
|
lmata
|
308 |
if len(stub.grants) != 1 { |
|
0902a34…
|
lmata
|
309 |
t.Fatalf("grants: want 1, got %d", len(stub.grants)) |
|
0902a34…
|
lmata
|
310 |
} |
|
0902a34…
|
lmata
|
311 |
if stub.grants[0].Level != "OP" { |
|
0902a34…
|
lmata
|
312 |
t.Errorf("level = %q, want OP", stub.grants[0].Level) |
|
3e3b163…
|
lmata
|
313 |
} |
|
3e3b163…
|
lmata
|
314 |
} |
|
3e3b163…
|
lmata
|
315 |
|
|
3e3b163…
|
lmata
|
316 |
func TestRegisterOrchestratorWithOpsChannels(t *testing.T) { |
|
3e3b163…
|
lmata
|
317 |
stub := &stubTopologyManager{} |
|
3e3b163…
|
lmata
|
318 |
srv, tok := newTopoTestServerWithRegistry(t, stub) |
|
3e3b163…
|
lmata
|
319 |
|
|
3e3b163…
|
lmata
|
320 |
resp := topoDoJSON(t, srv, tok, "POST", "/v1/agents/register", map[string]any{ |
|
3e3b163…
|
lmata
|
321 |
"nick": "orch-ops", |
|
3e3b163…
|
lmata
|
322 |
"type": "orchestrator", |
|
3e3b163…
|
lmata
|
323 |
"channels": []string{"#fleet", "#project.foo", "#project.bar"}, |
|
3e3b163…
|
lmata
|
324 |
"ops_channels": []string{"#fleet"}, |
|
3e3b163…
|
lmata
|
325 |
}) |
|
3e3b163…
|
lmata
|
326 |
defer resp.Body.Close() |
|
3e3b163…
|
lmata
|
327 |
if resp.StatusCode != http.StatusCreated { |
|
3e3b163…
|
lmata
|
328 |
t.Fatalf("register: want 201, got %d", resp.StatusCode) |
|
3e3b163…
|
lmata
|
329 |
} |
|
3e3b163…
|
lmata
|
330 |
|
|
3e3b163…
|
lmata
|
331 |
if len(stub.grants) != 3 { |
|
3e3b163…
|
lmata
|
332 |
t.Fatalf("grants: want 3, got %d", len(stub.grants)) |
|
3e3b163…
|
lmata
|
333 |
} |
|
3e3b163…
|
lmata
|
334 |
for i, want := range []accessCall{ |
|
3e3b163…
|
lmata
|
335 |
{Nick: "orch-ops", Channel: "#fleet", Level: "OP"}, |
|
3e3b163…
|
lmata
|
336 |
{Nick: "orch-ops", Channel: "#project.foo", Level: "VOICE"}, |
|
3e3b163…
|
lmata
|
337 |
{Nick: "orch-ops", Channel: "#project.bar", Level: "VOICE"}, |
|
3e3b163…
|
lmata
|
338 |
} { |
|
3e3b163…
|
lmata
|
339 |
if stub.grants[i] != want { |
|
3e3b163…
|
lmata
|
340 |
t.Errorf("grant[%d] = %+v, want %+v", i, stub.grants[i], want) |
|
3e3b163…
|
lmata
|
341 |
} |
|
0902a34…
|
lmata
|
342 |
} |
|
0902a34…
|
lmata
|
343 |
} |
|
0902a34…
|
lmata
|
344 |
|
|
0902a34…
|
lmata
|
345 |
func TestRevokeRemovesAccess(t *testing.T) { |
|
0902a34…
|
lmata
|
346 |
stub := &stubTopologyManager{} |
|
0902a34…
|
lmata
|
347 |
srv, tok := newTopoTestServerWithRegistry(t, stub) |
|
0902a34…
|
lmata
|
348 |
|
|
0902a34…
|
lmata
|
349 |
resp := topoDoJSON(t, srv, tok, "POST", "/v1/agents/register", map[string]any{ |
|
0902a34…
|
lmata
|
350 |
"nick": "orch-rev", |
|
0902a34…
|
lmata
|
351 |
"type": "orchestrator", |
|
0902a34…
|
lmata
|
352 |
"channels": []string{"#fleet", "#project.x"}, |
|
0902a34…
|
lmata
|
353 |
}) |
|
0902a34…
|
lmata
|
354 |
resp.Body.Close() |
|
0902a34…
|
lmata
|
355 |
|
|
0902a34…
|
lmata
|
356 |
resp = topoDoJSON(t, srv, tok, "POST", "/v1/agents/orch-rev/revoke", nil) |
|
0902a34…
|
lmata
|
357 |
defer resp.Body.Close() |
|
0902a34…
|
lmata
|
358 |
if resp.StatusCode != http.StatusNoContent { |
|
0902a34…
|
lmata
|
359 |
t.Fatalf("revoke: want 204, got %d", resp.StatusCode) |
|
0902a34…
|
lmata
|
360 |
} |
|
0902a34…
|
lmata
|
361 |
|
|
0902a34…
|
lmata
|
362 |
if len(stub.revokes) != 2 { |
|
0902a34…
|
lmata
|
363 |
t.Fatalf("revokes: want 2, got %d", len(stub.revokes)) |
|
0902a34…
|
lmata
|
364 |
} |
|
0902a34…
|
lmata
|
365 |
for i, want := range []string{"#fleet", "#project.x"} { |
|
0902a34…
|
lmata
|
366 |
if stub.revokes[i].Channel != want { |
|
0902a34…
|
lmata
|
367 |
t.Errorf("revoke[%d].Channel = %q, want %q", i, stub.revokes[i].Channel, want) |
|
0902a34…
|
lmata
|
368 |
} |
|
0902a34…
|
lmata
|
369 |
} |
|
0902a34…
|
lmata
|
370 |
} |
|
0902a34…
|
lmata
|
371 |
|
|
0902a34…
|
lmata
|
372 |
func TestDeleteRemovesAccess(t *testing.T) { |
|
0902a34…
|
lmata
|
373 |
stub := &stubTopologyManager{} |
|
0902a34…
|
lmata
|
374 |
srv, tok := newTopoTestServerWithRegistry(t, stub) |
|
0902a34…
|
lmata
|
375 |
|
|
0902a34…
|
lmata
|
376 |
resp := topoDoJSON(t, srv, tok, "POST", "/v1/agents/register", map[string]any{ |
|
0902a34…
|
lmata
|
377 |
"nick": "del-agent", |
|
0902a34…
|
lmata
|
378 |
"type": "worker", |
|
0902a34…
|
lmata
|
379 |
"channels": []string{"#fleet"}, |
|
0902a34…
|
lmata
|
380 |
}) |
|
0902a34…
|
lmata
|
381 |
resp.Body.Close() |
|
0902a34…
|
lmata
|
382 |
|
|
0902a34…
|
lmata
|
383 |
resp = topoDoJSON(t, srv, tok, "DELETE", "/v1/agents/del-agent", nil) |
|
0902a34…
|
lmata
|
384 |
defer resp.Body.Close() |
|
0902a34…
|
lmata
|
385 |
if resp.StatusCode != http.StatusNoContent { |
|
0902a34…
|
lmata
|
386 |
t.Fatalf("delete: want 204, got %d", resp.StatusCode) |
|
0902a34…
|
lmata
|
387 |
} |
|
0902a34…
|
lmata
|
388 |
|
|
0902a34…
|
lmata
|
389 |
if len(stub.revokes) != 1 { |
|
0902a34…
|
lmata
|
390 |
t.Fatalf("revokes: want 1, got %d", len(stub.revokes)) |
|
0902a34…
|
lmata
|
391 |
} |
|
0902a34…
|
lmata
|
392 |
if stub.revokes[0].Nick != "del-agent" || stub.revokes[0].Channel != "#fleet" { |
|
0902a34…
|
lmata
|
393 |
t.Errorf("revoke = %+v, want del-agent on #fleet", stub.revokes[0]) |
|
dd3a887…
|
lmata
|
394 |
} |
|
dd3a887…
|
lmata
|
395 |
} |