feat(mail): Add gt mail release command for releasing claimed queue messages

Implements the release subcommand to allow workers to release claimed
messages back to their queues. The command:
- Verifies the caller owns the claimed message
- Validates the message has a queue label
- Returns the message to open status with queue assignee

Includes TestMailReleaseValidation unit tests.

(gt-guyt5)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gastown/polecats/nux
2026-01-01 18:16:08 -08:00
committed by Steve Yegge
parent 261defa3b4
commit 7552be25e5
2 changed files with 280 additions and 1 deletions

View File

@@ -1,6 +1,10 @@
package cmd
import "testing"
import (
"fmt"
"strings"
"testing"
)
func TestMatchWorkerPattern(t *testing.T) {
tests := []struct {
@@ -158,3 +162,108 @@ func TestIsEligibleWorker(t *testing.T) {
})
}
}
// TestMailReleaseValidation tests the validation logic for the release command.
// This tests that release correctly identifies:
// - Messages not claimed (still in queue)
// - Messages claimed by a different worker
// - Messages without queue labels (non-queue messages)
func TestMailReleaseValidation(t *testing.T) {
tests := []struct {
name string
msgInfo *messageInfo
caller string
wantErr bool
errContains string
}{
{
name: "caller matches assignee - valid release",
msgInfo: &messageInfo{
ID: "hq-test1",
Title: "Test Message",
Assignee: "gastown/polecats/nux",
QueueName: "work/gastown",
Status: "in_progress",
},
caller: "gastown/polecats/nux",
wantErr: false,
},
{
name: "message still in queue - not claimed",
msgInfo: &messageInfo{
ID: "hq-test2",
Title: "Test Message",
Assignee: "queue:work/gastown",
QueueName: "work/gastown",
Status: "open",
},
caller: "gastown/polecats/nux",
wantErr: true,
errContains: "not claimed",
},
{
name: "claimed by different worker",
msgInfo: &messageInfo{
ID: "hq-test3",
Title: "Test Message",
Assignee: "gastown/polecats/other",
QueueName: "work/gastown",
Status: "in_progress",
},
caller: "gastown/polecats/nux",
wantErr: true,
errContains: "was claimed by",
},
{
name: "not a queue message",
msgInfo: &messageInfo{
ID: "hq-test4",
Title: "Test Message",
Assignee: "gastown/polecats/nux",
QueueName: "", // No queue label
Status: "open",
},
caller: "gastown/polecats/nux",
wantErr: true,
errContains: "not a queue message",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateRelease(tt.msgInfo, tt.caller)
if tt.wantErr {
if err == nil {
t.Error("expected error, got nil")
return
}
if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) {
t.Errorf("error %q should contain %q", err.Error(), tt.errContains)
}
} else {
if err != nil {
t.Errorf("unexpected error: %v", err)
}
}
})
}
}
// validateRelease checks if a message can be released by the caller.
// This is extracted for testing; the actual release command uses this logic inline.
func validateRelease(msgInfo *messageInfo, caller string) error {
// Verify message is a queue message
if msgInfo.QueueName == "" {
return fmt.Errorf("message %s is not a queue message (no queue label)", msgInfo.ID)
}
// Verify caller is the one who claimed it
if msgInfo.Assignee != caller {
if strings.HasPrefix(msgInfo.Assignee, "queue:") {
return fmt.Errorf("message %s is not claimed (still in queue)", msgInfo.ID)
}
return fmt.Errorf("message %s was claimed by %s, not %s", msgInfo.ID, msgInfo.Assignee, caller)
}
return nil
}