Files
gastown/internal/beads/beads_queue_test.go
gastown/crew/jack 2ffc8e8712 feat(mail): implement beads-native gt mail claim command
Implement claiming for queue messages using beads-native approach:

- Add claim_pattern field to QueueFields for eligibility checking
- Add MatchClaimPattern function for pattern matching (wildcards supported)
- Add FindEligibleQueues to find all queues an agent can claim from
- Rewrite runMailClaim to use beads-native queue lookup
- Support optional queue argument (claim from any eligible if not specified)
- Use claimed-by/claimed-at labels instead of changing assignee
- Update runMailRelease to work with new claiming approach
- Add comprehensive tests for pattern matching and validation

Queue messages are now claimed via labels:
  - claimed-by: <agent-identity>
  - claimed-at: <RFC3339 timestamp>

Messages with queue:<name> label but no claimed-by are unclaimed.

Closes gt-xfqh1e.11

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 21:25:11 -08:00

302 lines
6.3 KiB
Go

package beads
import (
"strings"
"testing"
)
func TestMatchClaimPattern(t *testing.T) {
tests := []struct {
name string
pattern string
identity string
want bool
}{
// Wildcard matches anyone
{
name: "wildcard matches anyone",
pattern: "*",
identity: "gastown/crew/max",
want: true,
},
{
name: "wildcard matches town-level agent",
pattern: "*",
identity: "mayor/",
want: true,
},
// Exact match
{
name: "exact match",
pattern: "gastown/crew/max",
identity: "gastown/crew/max",
want: true,
},
{
name: "exact match fails on different identity",
pattern: "gastown/crew/max",
identity: "gastown/crew/nux",
want: false,
},
// Suffix wildcard
{
name: "suffix wildcard matches",
pattern: "gastown/polecats/*",
identity: "gastown/polecats/capable",
want: true,
},
{
name: "suffix wildcard matches different name",
pattern: "gastown/polecats/*",
identity: "gastown/polecats/nux",
want: true,
},
{
name: "suffix wildcard doesn't match nested path",
pattern: "gastown/polecats/*",
identity: "gastown/polecats/sub/capable",
want: false,
},
{
name: "suffix wildcard doesn't match different rig",
pattern: "gastown/polecats/*",
identity: "bartertown/polecats/capable",
want: false,
},
// Prefix wildcard
{
name: "prefix wildcard matches",
pattern: "*/witness",
identity: "gastown/witness",
want: true,
},
{
name: "prefix wildcard matches different rig",
pattern: "*/witness",
identity: "bartertown/witness",
want: true,
},
{
name: "prefix wildcard doesn't match different role",
pattern: "*/witness",
identity: "gastown/refinery",
want: false,
},
// Crew patterns
{
name: "crew wildcard",
pattern: "gastown/crew/*",
identity: "gastown/crew/max",
want: true,
},
{
name: "crew wildcard matches any crew member",
pattern: "gastown/crew/*",
identity: "gastown/crew/jack",
want: true,
},
// Edge cases
{
name: "empty identity doesn't match",
pattern: "*",
identity: "",
want: true, // * matches anything
},
{
name: "empty pattern doesn't match",
pattern: "",
identity: "gastown/crew/max",
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := MatchClaimPattern(tt.pattern, tt.identity)
if got != tt.want {
t.Errorf("MatchClaimPattern(%q, %q) = %v, want %v",
tt.pattern, tt.identity, got, tt.want)
}
})
}
}
func TestFormatQueueDescription(t *testing.T) {
tests := []struct {
name string
title string
fields *QueueFields
want []string // Lines that should be present
}{
{
name: "basic queue",
title: "Queue: work-requests",
fields: &QueueFields{
Name: "work-requests",
ClaimPattern: "gastown/crew/*",
Status: QueueStatusActive,
},
want: []string{
"Queue: work-requests",
"name: work-requests",
"claim_pattern: gastown/crew/*",
"status: active",
},
},
{
name: "queue with default claim pattern",
title: "Queue: public",
fields: &QueueFields{
Name: "public",
Status: QueueStatusActive,
},
want: []string{
"name: public",
"claim_pattern: *", // Default
"status: active",
},
},
{
name: "queue with counts",
title: "Queue: processing",
fields: &QueueFields{
Name: "processing",
ClaimPattern: "*/refinery",
Status: QueueStatusActive,
AvailableCount: 5,
ProcessingCount: 2,
CompletedCount: 10,
FailedCount: 1,
},
want: []string{
"name: processing",
"claim_pattern: */refinery",
"available_count: 5",
"processing_count: 2",
"completed_count: 10",
"failed_count: 1",
},
},
{
name: "nil fields",
title: "Just Title",
fields: nil,
want: []string{"Just Title"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := FormatQueueDescription(tt.title, tt.fields)
for _, line := range tt.want {
if !strings.Contains(got, line) {
t.Errorf("FormatQueueDescription() missing line %q in:\n%s", line, got)
}
}
})
}
}
func TestParseQueueFields(t *testing.T) {
tests := []struct {
name string
description string
wantName string
wantPattern string
wantStatus string
}{
{
name: "basic queue",
description: `Queue: work-requests
name: work-requests
claim_pattern: gastown/crew/*
status: active`,
wantName: "work-requests",
wantPattern: "gastown/crew/*",
wantStatus: QueueStatusActive,
},
{
name: "queue with defaults",
description: `Queue: minimal
name: minimal`,
wantName: "minimal",
wantPattern: "*", // Default
wantStatus: QueueStatusActive,
},
{
name: "empty description",
description: "",
wantName: "",
wantPattern: "*", // Default
wantStatus: QueueStatusActive,
},
{
name: "queue with counts",
description: `Queue: processing
name: processing
claim_pattern: */refinery
status: paused
available_count: 5
processing_count: 2`,
wantName: "processing",
wantPattern: "*/refinery",
wantStatus: QueueStatusPaused,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ParseQueueFields(tt.description)
if got.Name != tt.wantName {
t.Errorf("Name = %q, want %q", got.Name, tt.wantName)
}
if got.ClaimPattern != tt.wantPattern {
t.Errorf("ClaimPattern = %q, want %q", got.ClaimPattern, tt.wantPattern)
}
if got.Status != tt.wantStatus {
t.Errorf("Status = %q, want %q", got.Status, tt.wantStatus)
}
})
}
}
func TestQueueBeadID(t *testing.T) {
tests := []struct {
name string
queueName string
isTownLevel bool
want string
}{
{
name: "town-level queue",
queueName: "dispatch",
isTownLevel: true,
want: "hq-q-dispatch",
},
{
name: "rig-level queue",
queueName: "merge",
isTownLevel: false,
want: "gt-q-merge",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := QueueBeadID(tt.queueName, tt.isTownLevel)
if got != tt.want {
t.Errorf("QueueBeadID(%q, %v) = %q, want %q",
tt.queueName, tt.isTownLevel, got, tt.want)
}
})
}
}