feat(mail): Add isAnnounceAddress() and parseAnnounceName() helpers

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 <noreply@anthropic.com>
This commit is contained in:
immortan
2026-01-02 00:09:47 -08:00
committed by Steve Yegge
parent 302e14cf27
commit 8c7ea8a991
2 changed files with 62 additions and 0 deletions

View File

@@ -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) {

View File

@@ -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))