ScuttleBot

scuttlebot / internal / bots / manager / manager_test.go
Blame History Raw 205 lines
1
package manager_test
2
3
import (
4
"context"
5
"fmt"
6
"os"
7
"path/filepath"
8
"testing"
9
10
"github.com/conflicthq/scuttlebot/internal/bots/manager"
11
"log/slog"
12
)
13
14
var testLog = slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError}))
15
16
// stubProvisioner records RegisterAccount/ChangePassword calls.
17
type stubProvisioner struct {
18
accounts map[string]string
19
failOn string // if set, RegisterAccount returns an error for this nick
20
}
21
22
func newStub() *stubProvisioner {
23
return &stubProvisioner{accounts: make(map[string]string)}
24
}
25
26
func (p *stubProvisioner) RegisterAccount(name, pass string) error {
27
if p.failOn == name {
28
return fmt.Errorf("ACCOUNT_EXISTS")
29
}
30
if _, ok := p.accounts[name]; ok {
31
return fmt.Errorf("ACCOUNT_EXISTS")
32
}
33
p.accounts[name] = pass
34
return nil
35
}
36
37
func (p *stubProvisioner) ChangePassword(name, pass string) error {
38
if _, ok := p.accounts[name]; !ok {
39
return fmt.Errorf("ACCOUNT_DOES_NOT_EXIST")
40
}
41
p.accounts[name] = pass
42
return nil
43
}
44
45
// stubChannels returns a fixed list of channels.
46
type stubChannels struct {
47
channels []string
48
err error
49
}
50
51
func (c *stubChannels) ListChannels() ([]string, error) {
52
return c.channels, c.err
53
}
54
55
func newManager(t *testing.T) *manager.Manager {
56
t.Helper()
57
return manager.New(
58
"127.0.0.1:6667",
59
t.TempDir(),
60
newStub(),
61
&stubChannels{channels: []string{"#fleet", "#ops"}},
62
testLog,
63
)
64
}
65
66
// scribeSpec returns a minimal enabled scribe BotSpec.
67
func scribeSpec() manager.BotSpec {
68
return manager.BotSpec{
69
ID: "scribe",
70
Nick: "scribe",
71
Enabled: true,
72
Config: map[string]any{"dir": "/tmp/scribe-test-logs"},
73
}
74
}
75
76
func TestSyncStartsEnabledBot(t *testing.T) {
77
m := newManager(t)
78
ctx, cancel := context.WithCancel(context.Background())
79
defer cancel()
80
81
m.Sync(ctx, []manager.BotSpec{scribeSpec()})
82
83
running := m.Running()
84
if len(running) != 1 || running[0] != "scribe" {
85
t.Errorf("expected [scribe] running, got %v", running)
86
}
87
}
88
89
func TestSyncDisabledBotNotStarted(t *testing.T) {
90
m := newManager(t)
91
ctx, cancel := context.WithCancel(context.Background())
92
defer cancel()
93
94
spec := scribeSpec()
95
spec.Enabled = false
96
m.Sync(ctx, []manager.BotSpec{spec})
97
98
if len(m.Running()) != 0 {
99
t.Errorf("expected no bots running, got %v", m.Running())
100
}
101
}
102
103
func TestSyncStopsDisabledBot(t *testing.T) {
104
m := newManager(t)
105
ctx, cancel := context.WithCancel(context.Background())
106
defer cancel()
107
108
// Start it.
109
m.Sync(ctx, []manager.BotSpec{scribeSpec()})
110
if len(m.Running()) != 1 {
111
t.Fatalf("bot should be running before disable")
112
}
113
114
// Disable it.
115
spec := scribeSpec()
116
spec.Enabled = false
117
m.Sync(ctx, []manager.BotSpec{spec})
118
119
if len(m.Running()) != 0 {
120
t.Errorf("expected bot stopped after disable, got %v", m.Running())
121
}
122
}
123
124
func TestSyncIdempotent(t *testing.T) {
125
m := newManager(t)
126
ctx, cancel := context.WithCancel(context.Background())
127
defer cancel()
128
129
spec := scribeSpec()
130
m.Sync(ctx, []manager.BotSpec{spec})
131
m.Sync(ctx, []manager.BotSpec{spec}) // second call — should not start a second copy
132
133
if len(m.Running()) != 1 {
134
t.Errorf("expected exactly 1 running bot, got %v", m.Running())
135
}
136
}
137
138
func TestPasswordPersistence(t *testing.T) {
139
dir := t.TempDir()
140
prov := newStub()
141
m1 := manager.New("127.0.0.1:6667", dir, prov, &stubChannels{}, testLog)
142
143
ctx, cancel := context.WithCancel(context.Background())
144
m1.Sync(ctx, []manager.BotSpec{scribeSpec()})
145
cancel()
146
147
// Passwords file should exist.
148
pwPath := filepath.Join(dir, "bot_passwords.json")
149
if _, err := os.Stat(pwPath); err != nil {
150
t.Fatalf("passwords file not created: %v", err)
151
}
152
153
// Load a second manager from the same dir — it should reuse the same password
154
// (ensureAccount will call ChangePassword, not RegisterAccount, because the stub
155
// already has the account from the first run).
156
m2 := manager.New("127.0.0.1:6667", dir, prov, &stubChannels{}, testLog)
157
ctx2, cancel2 := context.WithCancel(context.Background())
158
defer cancel2()
159
160
// Should not panic and should be able to start the bot.
161
m2.Sync(ctx2, []manager.BotSpec{scribeSpec()})
162
if len(m2.Running()) != 1 {
163
t.Errorf("second manager: expected 1 running bot, got %v", m2.Running())
164
}
165
}
166
167
func TestSyncOracleStarts(t *testing.T) {
168
// Oracle now starts with default config (no API key — it won't respond to
169
// summaries but the bot itself connects to IRC and runs).
170
m := newManager(t)
171
ctx, cancel := context.WithCancel(context.Background())
172
defer cancel()
173
174
spec := manager.BotSpec{ID: "oracle", Nick: "oracle", Enabled: true}
175
m.Sync(ctx, []manager.BotSpec{spec})
176
177
running := m.Running()
178
found := false
179
for _, nick := range running {
180
if nick == "oracle" {
181
found = true
182
}
183
}
184
if !found {
185
t.Errorf("expected oracle to be in Running, got %v", running)
186
}
187
}
188
189
func TestSyncMultipleBots(t *testing.T) {
190
m := newManager(t)
191
ctx, cancel := context.WithCancel(context.Background())
192
defer cancel()
193
194
specs := []manager.BotSpec{
195
scribeSpec(),
196
{ID: "snitch", Nick: "snitch", Enabled: true},
197
}
198
m.Sync(ctx, []manager.BotSpec{specs[0], specs[1]})
199
200
running := m.Running()
201
if len(running) != 2 {
202
t.Errorf("expected 2 running bots, got %v", running)
203
}
204
}
205

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button