ScuttleBot

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

Keyboard Shortcuts

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