ScuttleBot
| f0853f5… | lmata | 1 | package topology |
| f0853f5… | lmata | 2 | |
| f0853f5… | lmata | 3 | import ( |
| f0853f5… | lmata | 4 | "io" |
| f0853f5… | lmata | 5 | "log/slog" |
| f0853f5… | lmata | 6 | "testing" |
| f0853f5… | lmata | 7 | "time" |
| f0853f5… | lmata | 8 | |
| f0853f5… | lmata | 9 | "github.com/conflicthq/scuttlebot/internal/config" |
| f0853f5… | lmata | 10 | ) |
| f0853f5… | lmata | 11 | |
| f0853f5… | lmata | 12 | // reapDry runs the reaper's expiry check without calling ChanServ. |
| f0853f5… | lmata | 13 | // It returns the names of channels that would be reaped. |
| f0853f5… | lmata | 14 | func reapDry(m *Manager, now time.Time) []string { |
| f0853f5… | lmata | 15 | if m.policy == nil { |
| f0853f5… | lmata | 16 | return nil |
| f0853f5… | lmata | 17 | } |
| f0853f5… | lmata | 18 | m.mu.Lock() |
| f0853f5… | lmata | 19 | defer m.mu.Unlock() |
| f0853f5… | lmata | 20 | var out []string |
| f0853f5… | lmata | 21 | for _, rec := range m.channels { |
| f0853f5… | lmata | 22 | ttl := m.policy.TTLFor(rec.name) |
| f0853f5… | lmata | 23 | if ttl > 0 && m.policy.IsEphemeral(rec.name) && now.Sub(rec.provisionedAt) > ttl { |
| f0853f5… | lmata | 24 | out = append(out, rec.name) |
| f0853f5… | lmata | 25 | } |
| f0853f5… | lmata | 26 | } |
| f0853f5… | lmata | 27 | return out |
| f0853f5… | lmata | 28 | } |
| f0853f5… | lmata | 29 | |
| f0853f5… | lmata | 30 | func TestReaperExpiry(t *testing.T) { |
| f0853f5… | lmata | 31 | pol := NewPolicy(config.TopologyConfig{ |
| f0853f5… | lmata | 32 | Types: []config.ChannelTypeConfig{ |
| f0853f5… | lmata | 33 | { |
| f0853f5… | lmata | 34 | Name: "task", |
| f0853f5… | lmata | 35 | Prefix: "task.", |
| f0853f5… | lmata | 36 | Ephemeral: true, |
| f0853f5… | lmata | 37 | TTL: config.Duration{Duration: 72 * time.Hour}, |
| f0853f5… | lmata | 38 | }, |
| f0853f5… | lmata | 39 | { |
| f0853f5… | lmata | 40 | Name: "sprint", |
| f0853f5… | lmata | 41 | Prefix: "sprint.", |
| f0853f5… | lmata | 42 | }, |
| f0853f5… | lmata | 43 | }, |
| f0853f5… | lmata | 44 | }) |
| f0853f5… | lmata | 45 | log := slog.New(slog.NewTextHandler(io.Discard, nil)) |
| a408eee… | lmata | 46 | m := NewManager("localhost:6667", "topology", "pass", "", pol, log) |
| f0853f5… | lmata | 47 | |
| f0853f5… | lmata | 48 | // Simulate that channels were provisioned at different times. |
| f0853f5… | lmata | 49 | m.mu.Lock() |
| f0853f5… | lmata | 50 | m.channels["#task.old"] = channelRecord{name: "#task.old", provisionedAt: time.Now().Add(-80 * time.Hour)} |
| f0853f5… | lmata | 51 | m.channels["#task.fresh"] = channelRecord{name: "#task.fresh", provisionedAt: time.Now().Add(-10 * time.Hour)} |
| f0853f5… | lmata | 52 | m.channels["#sprint.2026-q2"] = channelRecord{name: "#sprint.2026-q2", provisionedAt: time.Now().Add(-200 * time.Hour)} |
| f0853f5… | lmata | 53 | m.mu.Unlock() |
| f0853f5… | lmata | 54 | |
| f0853f5… | lmata | 55 | expired := reapDry(m, time.Now()) |
| f0853f5… | lmata | 56 | if len(expired) != 1 || expired[0] != "#task.old" { |
| f0853f5… | lmata | 57 | t.Errorf("expected [#task.old] to be expired, got %v", expired) |
| f0853f5… | lmata | 58 | } |
| f0853f5… | lmata | 59 | } |