ScuttleBot

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

Keyboard Shortcuts

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