diff --git a/internal/mail/router.go b/internal/mail/router.go index fb9b78be..8ba19a20 100644 --- a/internal/mail/router.go +++ b/internal/mail/router.go @@ -20,6 +20,9 @@ var ErrUnknownList = errors.New("unknown mailing list") // ErrUnknownQueue indicates a queue name was not found in configuration. var ErrUnknownQueue = errors.New("unknown queue") +// ErrUnknownAnnounce indicates an announce channel name was not found in configuration. +var ErrUnknownAnnounce = errors.New("unknown announce channel") + // Router handles message delivery via beads. // It routes messages to the correct beads database based on address: // - Town-level (mayor/, deacon/) -> {townRoot}/.beads @@ -73,6 +76,16 @@ func parseQueueName(address string) string { return strings.TrimPrefix(address, "queue:") } +// isAnnounceAddress returns true if the address uses announce:name syntax. +func isAnnounceAddress(address string) bool { + return strings.HasPrefix(address, "announce:") +} + +// parseAnnounceName extracts the announce channel name from an announce:name address. +func parseAnnounceName(address string) string { + return strings.TrimPrefix(address, "announce:") +} + // expandList returns the recipients for a mailing list. // Returns ErrUnknownList if the list is not found. func (r *Router) expandList(listName string) ([]string, error) { diff --git a/internal/mail/router_test.go b/internal/mail/router_test.go index 7ced3617..494ec257 100644 --- a/internal/mail/router_test.go +++ b/internal/mail/router_test.go @@ -493,6 +493,55 @@ func TestExpandQueueNoTownRoot(t *testing.T) { } } +// ============ Announce Address Tests ============ + +func TestIsAnnounceAddress(t *testing.T) { + tests := []struct { + address string + want bool + }{ + {"announce:bulletin", true}, + {"announce:gastown/updates", true}, + {"announce:", true}, // Edge case: empty announce name (will fail on expand) + {"mayor/", false}, + {"gastown/witness", false}, + {"announcebulletin", false}, // Missing colon + {"list:oncall", false}, + {"queue:work", false}, + {"", false}, + } + + for _, tt := range tests { + t.Run(tt.address, func(t *testing.T) { + got := isAnnounceAddress(tt.address) + if got != tt.want { + t.Errorf("isAnnounceAddress(%q) = %v, want %v", tt.address, got, tt.want) + } + }) + } +} + +func TestParseAnnounceName(t *testing.T) { + tests := []struct { + address string + want string + }{ + {"announce:bulletin", "bulletin"}, + {"announce:gastown/updates", "gastown/updates"}, + {"announce:", ""}, + {"announce:priority-alerts", "priority-alerts"}, + } + + for _, tt := range tests { + t.Run(tt.address, func(t *testing.T) { + got := parseAnnounceName(tt.address) + if got != tt.want { + t.Errorf("parseAnnounceName(%q) = %q, want %q", tt.address, got, tt.want) + } + }) + } +} + // contains checks if s contains substr (helper for error checking) func contains(s, substr string) bool { return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsHelper(s, substr))