ScuttleBot

scuttlebot / internal / topology / policy_test.go
Blame History Raw 188 lines
1
package topology
2
3
import (
4
"testing"
5
"time"
6
7
"github.com/conflicthq/scuttlebot/internal/config"
8
)
9
10
func testPolicy() *Policy {
11
return NewPolicy(config.TopologyConfig{
12
Channels: []config.StaticChannelConfig{
13
{
14
Name: "#general",
15
Topic: "Fleet coordination",
16
Autojoin: []string{"bridge", "oracle", "scribe"},
17
},
18
{
19
Name: "#alerts",
20
Autojoin: []string{"bridge", "sentinel", "steward"},
21
},
22
},
23
Types: []config.ChannelTypeConfig{
24
{
25
Name: "task",
26
Prefix: "task.",
27
Autojoin: []string{"bridge", "scribe"},
28
Supervision: "#general",
29
Ephemeral: true,
30
TTL: config.Duration{Duration: 72 * time.Hour},
31
},
32
{
33
Name: "sprint",
34
Prefix: "sprint.",
35
Autojoin: []string{"bridge", "oracle", "herald"},
36
Supervision: "",
37
},
38
{
39
Name: "incident",
40
Prefix: "incident.",
41
Autojoin: []string{"bridge", "sentinel", "steward", "oracle"},
42
Supervision: "#alerts",
43
Ephemeral: true,
44
TTL: config.Duration{Duration: 168 * time.Hour},
45
},
46
{
47
Name: "experiment",
48
Prefix: "experiment.",
49
},
50
},
51
})
52
}
53
54
func TestPolicyMatch(t *testing.T) {
55
p := testPolicy()
56
57
cases := []struct {
58
channel string
59
wantType string
60
}{
61
{"#task.gh-42", "task"},
62
{"#task.JIRA-99", "task"},
63
{"#sprint.2026-q2", "sprint"},
64
{"#incident.p1", "incident"},
65
{"#experiment.llm-v3", "experiment"},
66
{"#general", ""}, // static, no type
67
{"#unknown", ""}, // no match
68
{"#taskforce", ""}, // prefix must match exactly (task. not task)
69
}
70
71
for _, tc := range cases {
72
t.Run(tc.channel, func(t *testing.T) {
73
got := p.TypeName(tc.channel)
74
if got != tc.wantType {
75
t.Errorf("TypeName(%q) = %q, want %q", tc.channel, got, tc.wantType)
76
}
77
})
78
}
79
}
80
81
func TestPolicyAutojoinFor(t *testing.T) {
82
p := testPolicy()
83
84
cases := []struct {
85
channel string
86
wantBots []string
87
}{
88
{"#task.gh-42", []string{"bridge", "scribe"}},
89
{"#sprint.2026-q2", []string{"bridge", "oracle", "herald"}},
90
{"#incident.p1", []string{"bridge", "sentinel", "steward", "oracle"}},
91
{"#general", []string{"bridge", "oracle", "scribe"}}, // static channel
92
{"#alerts", []string{"bridge", "sentinel", "steward"}}, // static channel
93
{"#unknown", nil},
94
{"#experiment.llm-v3", nil}, // type exists but no autojoin configured
95
}
96
97
for _, tc := range cases {
98
t.Run(tc.channel, func(t *testing.T) {
99
got := p.AutojoinFor(tc.channel)
100
if len(got) != len(tc.wantBots) {
101
t.Fatalf("AutojoinFor(%q) = %v, want %v", tc.channel, got, tc.wantBots)
102
}
103
for i, nick := range tc.wantBots {
104
if got[i] != nick {
105
t.Errorf("AutojoinFor(%q)[%d] = %q, want %q", tc.channel, i, got[i], nick)
106
}
107
}
108
})
109
}
110
}
111
112
func TestPolicySupervisionFor(t *testing.T) {
113
p := testPolicy()
114
115
cases := []struct {
116
channel string
117
want string
118
}{
119
{"#task.gh-42", "#general"},
120
{"#incident.p1", "#alerts"},
121
{"#sprint.2026-q2", ""},
122
{"#general", ""},
123
{"#unknown", ""},
124
}
125
126
for _, tc := range cases {
127
t.Run(tc.channel, func(t *testing.T) {
128
got := p.SupervisionFor(tc.channel)
129
if got != tc.want {
130
t.Errorf("SupervisionFor(%q) = %q, want %q", tc.channel, got, tc.want)
131
}
132
})
133
}
134
}
135
136
func TestPolicyEphemeral(t *testing.T) {
137
p := testPolicy()
138
139
if !p.IsEphemeral("#task.gh-42") {
140
t.Error("#task.gh-42 should be ephemeral")
141
}
142
if p.IsEphemeral("#sprint.2026-q2") {
143
t.Error("#sprint.2026-q2 should not be ephemeral")
144
}
145
if p.IsEphemeral("#general") {
146
t.Error("#general should not be ephemeral")
147
}
148
149
if got := p.TTLFor("#task.gh-42"); got != 72*time.Hour {
150
t.Errorf("TTLFor #task.gh-42 = %v, want 72h", got)
151
}
152
if got := p.TTLFor("#incident.p1"); got != 168*time.Hour {
153
t.Errorf("TTLFor #incident.p1 = %v, want 168h", got)
154
}
155
if got := p.TTLFor("#sprint.2026-q2"); got != 0 {
156
t.Errorf("TTLFor #sprint.2026-q2 = %v, want 0", got)
157
}
158
}
159
160
func TestPolicyStaticChannels(t *testing.T) {
161
p := testPolicy()
162
statics := p.StaticChannels()
163
if len(statics) != 2 {
164
t.Fatalf("want 2 static channels, got %d", len(statics))
165
}
166
if statics[0].Name != "#general" {
167
t.Errorf("statics[0].Name = %q, want #general", statics[0].Name)
168
}
169
}
170
171
func TestPolicyTypes(t *testing.T) {
172
p := testPolicy()
173
types := p.Types()
174
if len(types) != 4 {
175
t.Fatalf("want 4 types, got %d", len(types))
176
}
177
}
178
179
func TestNewPolicyEmpty(t *testing.T) {
180
p := NewPolicy(config.TopologyConfig{})
181
if p.Match("#anything") != nil {
182
t.Error("empty policy should not match")
183
}
184
if p.AutojoinFor("#general") != nil {
185
t.Error("empty policy should return nil autojoin")
186
}
187
}
188

Keyboard Shortcuts

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