Files
gastown/internal/witness/protocol_test.go
Steve Yegge fa2997c5ca Implement Polecat-Witness protocol handlers (gt-m5w4g.3)
Add protocol message parsing and handlers for polecat→witness communication:
- POLECAT_DONE: Parse completion messages, create cleanup wisps
- HELP: Parse help requests, assess if Witness can help or escalate to Mayor
- MERGED: Parse refinery merge confirmations
- LIFECYCLE:Shutdown: Handle daemon-triggered shutdowns
- SWARM_START: Parse batch work initialization

Files added:
- internal/witness/protocol.go: Message classification and parsing
- internal/witness/handlers.go: Handler implementations with wisp/mail integration
- internal/witness/protocol_test.go: Unit tests for all parsing functions

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 10:44:34 -08:00

251 lines
6.5 KiB
Go

package witness
import (
"testing"
)
func TestClassifyMessage(t *testing.T) {
tests := []struct {
subject string
expected ProtocolType
}{
{"POLECAT_DONE nux", ProtoPolecatDone},
{"POLECAT_DONE ace", ProtoPolecatDone},
{"LIFECYCLE:Shutdown nux", ProtoLifecycleShutdown},
{"HELP: Tests failing", ProtoHelp},
{"HELP: Git conflict", ProtoHelp},
{"MERGED nux", ProtoMerged},
{"MERGED valkyrie", ProtoMerged},
{"🤝 HANDOFF: Patrol context", ProtoHandoff},
{"🤝HANDOFF: No space", ProtoHandoff},
{"SWARM_START", ProtoSwarmStart},
{"Unknown message", ProtoUnknown},
{"", ProtoUnknown},
}
for _, tc := range tests {
t.Run(tc.subject, func(t *testing.T) {
result := ClassifyMessage(tc.subject)
if result != tc.expected {
t.Errorf("ClassifyMessage(%q) = %v, want %v", tc.subject, result, tc.expected)
}
})
}
}
func TestParsePolecatDone(t *testing.T) {
subject := "POLECAT_DONE nux"
body := `Exit: MERGED
Issue: gt-abc123
MR: gt-mr-xyz
Branch: feature-branch`
payload, err := ParsePolecatDone(subject, body)
if err != nil {
t.Fatalf("ParsePolecatDone() error = %v", err)
}
if payload.PolecatName != "nux" {
t.Errorf("PolecatName = %q, want %q", payload.PolecatName, "nux")
}
if payload.Exit != "MERGED" {
t.Errorf("Exit = %q, want %q", payload.Exit, "MERGED")
}
if payload.IssueID != "gt-abc123" {
t.Errorf("IssueID = %q, want %q", payload.IssueID, "gt-abc123")
}
if payload.MRID != "gt-mr-xyz" {
t.Errorf("MRID = %q, want %q", payload.MRID, "gt-mr-xyz")
}
if payload.Branch != "feature-branch" {
t.Errorf("Branch = %q, want %q", payload.Branch, "feature-branch")
}
}
func TestParsePolecatDone_MinimalBody(t *testing.T) {
subject := "POLECAT_DONE ace"
body := "Exit: DEFERRED"
payload, err := ParsePolecatDone(subject, body)
if err != nil {
t.Fatalf("ParsePolecatDone() error = %v", err)
}
if payload.PolecatName != "ace" {
t.Errorf("PolecatName = %q, want %q", payload.PolecatName, "ace")
}
if payload.Exit != "DEFERRED" {
t.Errorf("Exit = %q, want %q", payload.Exit, "DEFERRED")
}
if payload.IssueID != "" {
t.Errorf("IssueID = %q, want empty", payload.IssueID)
}
}
func TestParsePolecatDone_InvalidSubject(t *testing.T) {
_, err := ParsePolecatDone("Invalid subject", "body")
if err == nil {
t.Error("ParsePolecatDone() expected error for invalid subject")
}
}
func TestParseHelp(t *testing.T) {
subject := "HELP: Tests failing on CI"
body := `Agent: gastown/polecats/nux
Issue: gt-abc123
Problem: Unit tests timeout after 30 seconds
Tried: Increased timeout, checked for deadlocks`
payload, err := ParseHelp(subject, body)
if err != nil {
t.Fatalf("ParseHelp() error = %v", err)
}
if payload.Topic != "Tests failing on CI" {
t.Errorf("Topic = %q, want %q", payload.Topic, "Tests failing on CI")
}
if payload.Agent != "gastown/polecats/nux" {
t.Errorf("Agent = %q, want %q", payload.Agent, "gastown/polecats/nux")
}
if payload.IssueID != "gt-abc123" {
t.Errorf("IssueID = %q, want %q", payload.IssueID, "gt-abc123")
}
if payload.Problem != "Unit tests timeout after 30 seconds" {
t.Errorf("Problem = %q, want %q", payload.Problem, "Unit tests timeout after 30 seconds")
}
if payload.Tried != "Increased timeout, checked for deadlocks" {
t.Errorf("Tried = %q, want %q", payload.Tried, "Increased timeout, checked for deadlocks")
}
}
func TestParseHelp_InvalidSubject(t *testing.T) {
_, err := ParseHelp("Not a help message", "body")
if err == nil {
t.Error("ParseHelp() expected error for invalid subject")
}
}
func TestParseMerged(t *testing.T) {
subject := "MERGED nux"
body := `Branch: feature-nux
Issue: gt-abc123
Merged-At: 2025-12-30T10:30:00Z`
payload, err := ParseMerged(subject, body)
if err != nil {
t.Fatalf("ParseMerged() error = %v", err)
}
if payload.PolecatName != "nux" {
t.Errorf("PolecatName = %q, want %q", payload.PolecatName, "nux")
}
if payload.Branch != "feature-nux" {
t.Errorf("Branch = %q, want %q", payload.Branch, "feature-nux")
}
if payload.IssueID != "gt-abc123" {
t.Errorf("IssueID = %q, want %q", payload.IssueID, "gt-abc123")
}
if payload.MergedAt.IsZero() {
t.Error("MergedAt should not be zero")
}
}
func TestParseMerged_InvalidSubject(t *testing.T) {
_, err := ParseMerged("Not merged", "body")
if err == nil {
t.Error("ParseMerged() expected error for invalid subject")
}
}
func TestCleanupWispLabels(t *testing.T) {
labels := CleanupWispLabels("nux", "pending")
expected := []string{"cleanup", "polecat:nux", "state:pending"}
if len(labels) != len(expected) {
t.Fatalf("CleanupWispLabels() returned %d labels, want %d", len(labels), len(expected))
}
for i, label := range labels {
if label != expected[i] {
t.Errorf("labels[%d] = %q, want %q", i, label, expected[i])
}
}
}
func TestAssessHelpRequest_GitConflict(t *testing.T) {
payload := &HelpPayload{
Topic: "Git issue",
Problem: "Merge conflict in main.go",
}
assessment := AssessHelpRequest(payload)
if assessment.CanHelp {
t.Error("Should not be able to help with git conflicts")
}
if !assessment.NeedsEscalation {
t.Error("Git conflicts should need escalation")
}
}
func TestAssessHelpRequest_GitPush(t *testing.T) {
payload := &HelpPayload{
Topic: "Git push failing",
Problem: "Cannot push to remote",
}
assessment := AssessHelpRequest(payload)
if !assessment.CanHelp {
t.Error("Should be able to help with git push issues")
}
if assessment.HelpAction == "" {
t.Error("HelpAction should not be empty")
}
}
func TestAssessHelpRequest_TestFailures(t *testing.T) {
payload := &HelpPayload{
Topic: "Test failures",
Problem: "Tests fail on CI",
}
assessment := AssessHelpRequest(payload)
if assessment.CanHelp {
t.Error("Should not be able to help with test failures")
}
if !assessment.NeedsEscalation {
t.Error("Test failures should need escalation")
}
}
func TestAssessHelpRequest_RequirementsUnclear(t *testing.T) {
payload := &HelpPayload{
Topic: "Requirements unclear",
Problem: "Don't understand the requirements for this task",
}
assessment := AssessHelpRequest(payload)
if assessment.CanHelp {
t.Error("Should not be able to help with unclear requirements")
}
if !assessment.NeedsEscalation {
t.Error("Unclear requirements should need escalation")
}
}
func TestAssessHelpRequest_BuildIssues(t *testing.T) {
payload := &HelpPayload{
Topic: "Build failing",
Problem: "Cannot compile the project",
}
assessment := AssessHelpRequest(payload)
if !assessment.CanHelp {
t.Error("Should be able to help with build issues")
}
}