From 8c7ea8a991091aff34836a388ec827ef73be5731 Mon Sep 17 00:00:00 2001 From: immortan Date: Fri, 2 Jan 2026 00:09:47 -0800 Subject: [PATCH] feat(mail): Add isAnnounceAddress() and parseAnnounceName() helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add announce address detection to internal/mail/router.go following the same pattern as isListAddress/parseListName and isQueueAddress/ parseQueueName. Added: - isAnnounceAddress(address string) bool - returns true for 'announce:' prefix - parseAnnounceName(address string) string - extracts channel name - ErrUnknownAnnounce error variable (gt-pn2fq) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- internal/mail/router.go | 13 ++++++++++ internal/mail/router_test.go | 49 ++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) 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))