package protocol import ( "bytes" "strings" "testing" "time" "github.com/steveyegge/gastown/internal/mail" ) func TestParseMessageType(t *testing.T) { tests := []struct { subject string expected MessageType }{ {"MERGE_READY nux", TypeMergeReady}, {"MERGED Toast", TypeMerged}, {"MERGE_FAILED ace", TypeMergeFailed}, {"REWORK_REQUEST valkyrie", TypeReworkRequest}, {"MERGE_READY", TypeMergeReady}, // no polecat name {"Unknown subject", ""}, {"", ""}, {" MERGE_READY nux ", TypeMergeReady}, // with whitespace } for _, tt := range tests { t.Run(tt.subject, func(t *testing.T) { result := ParseMessageType(tt.subject) if result != tt.expected { t.Errorf("ParseMessageType(%q) = %q, want %q", tt.subject, result, tt.expected) } }) } } func TestExtractPolecat(t *testing.T) { tests := []struct { subject string expected string }{ {"MERGE_READY nux", "nux"}, {"MERGED Toast", "Toast"}, {"MERGE_FAILED ace", "ace"}, {"REWORK_REQUEST valkyrie", "valkyrie"}, {"MERGE_READY", ""}, {"", ""}, {" MERGE_READY nux ", "nux"}, } for _, tt := range tests { t.Run(tt.subject, func(t *testing.T) { result := ExtractPolecat(tt.subject) if result != tt.expected { t.Errorf("ExtractPolecat(%q) = %q, want %q", tt.subject, result, tt.expected) } }) } } func TestIsProtocolMessage(t *testing.T) { tests := []struct { subject string expected bool }{ {"MERGE_READY nux", true}, {"MERGED Toast", true}, {"MERGE_FAILED ace", true}, {"REWORK_REQUEST valkyrie", true}, {"Unknown subject", false}, {"", false}, {"Hello world", false}, } for _, tt := range tests { t.Run(tt.subject, func(t *testing.T) { result := IsProtocolMessage(tt.subject) if result != tt.expected { t.Errorf("IsProtocolMessage(%q) = %v, want %v", tt.subject, result, tt.expected) } }) } } func TestNewMergeReadyMessage(t *testing.T) { msg := NewMergeReadyMessage("gastown", "nux", "polecat/nux/gt-abc", "gt-abc") if msg.Subject != "MERGE_READY nux" { t.Errorf("Subject = %q, want %q", msg.Subject, "MERGE_READY nux") } if msg.From != "gastown/witness" { t.Errorf("From = %q, want %q", msg.From, "gastown/witness") } if msg.To != "gastown/refinery" { t.Errorf("To = %q, want %q", msg.To, "gastown/refinery") } if msg.Priority != mail.PriorityHigh { t.Errorf("Priority = %q, want %q", msg.Priority, mail.PriorityHigh) } if !strings.Contains(msg.Body, "Branch: polecat/nux/gt-abc") { t.Errorf("Body missing branch: %s", msg.Body) } if !strings.Contains(msg.Body, "Issue: gt-abc") { t.Errorf("Body missing issue: %s", msg.Body) } } func TestNewMergedMessage(t *testing.T) { msg := NewMergedMessage("gastown", "nux", "polecat/nux/gt-abc", "gt-abc", "main", "abc123") if msg.Subject != "MERGED nux" { t.Errorf("Subject = %q, want %q", msg.Subject, "MERGED nux") } if msg.From != "gastown/refinery" { t.Errorf("From = %q, want %q", msg.From, "gastown/refinery") } if msg.To != "gastown/witness" { t.Errorf("To = %q, want %q", msg.To, "gastown/witness") } if !strings.Contains(msg.Body, "Merge-Commit: abc123") { t.Errorf("Body missing merge commit: %s", msg.Body) } } func TestNewMergeFailedMessage(t *testing.T) { msg := NewMergeFailedMessage("gastown", "nux", "polecat/nux/gt-abc", "gt-abc", "main", "tests", "Test failed") if msg.Subject != "MERGE_FAILED nux" { t.Errorf("Subject = %q, want %q", msg.Subject, "MERGE_FAILED nux") } if !strings.Contains(msg.Body, "Failure-Type: tests") { t.Errorf("Body missing failure type: %s", msg.Body) } if !strings.Contains(msg.Body, "Error: Test failed") { t.Errorf("Body missing error: %s", msg.Body) } } func TestNewReworkRequestMessage(t *testing.T) { conflicts := []string{"file1.go", "file2.go"} msg := NewReworkRequestMessage("gastown", "nux", "polecat/nux/gt-abc", "gt-abc", "main", conflicts) if msg.Subject != "REWORK_REQUEST nux" { t.Errorf("Subject = %q, want %q", msg.Subject, "REWORK_REQUEST nux") } if !strings.Contains(msg.Body, "Conflict-Files: file1.go, file2.go") { t.Errorf("Body missing conflict files: %s", msg.Body) } if !strings.Contains(msg.Body, "git rebase origin/main") { t.Errorf("Body missing rebase instructions: %s", msg.Body) } } func TestParseMergeReadyPayload(t *testing.T) { body := `Branch: polecat/nux/gt-abc Issue: gt-abc Polecat: nux Rig: gastown Verified: clean git state` payload := ParseMergeReadyPayload(body) if payload.Branch != "polecat/nux/gt-abc" { t.Errorf("Branch = %q, want %q", payload.Branch, "polecat/nux/gt-abc") } if payload.Issue != "gt-abc" { t.Errorf("Issue = %q, want %q", payload.Issue, "gt-abc") } if payload.Polecat != "nux" { t.Errorf("Polecat = %q, want %q", payload.Polecat, "nux") } if payload.Rig != "gastown" { t.Errorf("Rig = %q, want %q", payload.Rig, "gastown") } } func TestParseMergedPayload(t *testing.T) { ts := time.Now().Format(time.RFC3339) body := `Branch: polecat/nux/gt-abc Issue: gt-abc Polecat: nux Rig: gastown Target: main Merged-At: ` + ts + ` Merge-Commit: abc123` payload := ParseMergedPayload(body) if payload.Branch != "polecat/nux/gt-abc" { t.Errorf("Branch = %q, want %q", payload.Branch, "polecat/nux/gt-abc") } if payload.MergeCommit != "abc123" { t.Errorf("MergeCommit = %q, want %q", payload.MergeCommit, "abc123") } if payload.TargetBranch != "main" { t.Errorf("TargetBranch = %q, want %q", payload.TargetBranch, "main") } } func TestHandlerRegistry(t *testing.T) { registry := NewHandlerRegistry() handled := false registry.Register(TypeMergeReady, func(msg *mail.Message) error { handled = true return nil }) msg := &mail.Message{Subject: "MERGE_READY nux"} if !registry.CanHandle(msg) { t.Error("Registry should be able to handle MERGE_READY message") } if err := registry.Handle(msg); err != nil { t.Errorf("Handle returned error: %v", err) } if !handled { t.Error("Handler was not called") } // Test unregistered message type unknownMsg := &mail.Message{Subject: "UNKNOWN message"} if registry.CanHandle(unknownMsg) { t.Error("Registry should not handle unknown message type") } } func TestWrapWitnessHandlers(t *testing.T) { handler := &mockWitnessHandler{} registry := WrapWitnessHandlers(handler) // Test MERGED mergedMsg := &mail.Message{ Subject: "MERGED nux", Body: "Branch: polecat/nux\nIssue: gt-abc\nPolecat: nux\nRig: gastown\nTarget: main", } if err := registry.Handle(mergedMsg); err != nil { t.Errorf("HandleMerged error: %v", err) } if !handler.mergedCalled { t.Error("HandleMerged was not called") } // Test MERGE_FAILED failedMsg := &mail.Message{ Subject: "MERGE_FAILED nux", Body: "Branch: polecat/nux\nIssue: gt-abc\nPolecat: nux\nRig: gastown\nTarget: main\nFailure-Type: tests\nError: failed", } if err := registry.Handle(failedMsg); err != nil { t.Errorf("HandleMergeFailed error: %v", err) } if !handler.failedCalled { t.Error("HandleMergeFailed was not called") } // Test REWORK_REQUEST reworkMsg := &mail.Message{ Subject: "REWORK_REQUEST nux", Body: "Branch: polecat/nux\nIssue: gt-abc\nPolecat: nux\nRig: gastown\nTarget: main", } if err := registry.Handle(reworkMsg); err != nil { t.Errorf("HandleReworkRequest error: %v", err) } if !handler.reworkCalled { t.Error("HandleReworkRequest was not called") } } func TestWrapRefineryHandlers(t *testing.T) { handler := &mockRefineryHandler{} registry := WrapRefineryHandlers(handler) msg := &mail.Message{ Subject: "MERGE_READY nux", Body: "Branch: polecat/nux\nIssue: gt-abc\nPolecat: nux\nRig: gastown", } if err := registry.Handle(msg); err != nil { t.Errorf("HandleMergeReady error: %v", err) } if !handler.readyCalled { t.Error("HandleMergeReady was not called") } } func TestDefaultWitnessHandler(t *testing.T) { tmpDir := t.TempDir() handler := NewWitnessHandler("gastown", tmpDir) // Capture output var buf bytes.Buffer handler.SetOutput(&buf) // Test HandleMerged mergedPayload := &MergedPayload{ Branch: "polecat/nux/gt-abc", Issue: "gt-abc", Polecat: "nux", Rig: "gastown", TargetBranch: "main", MergeCommit: "abc123", } if err := handler.HandleMerged(mergedPayload); err != nil { t.Errorf("HandleMerged error: %v", err) } if !strings.Contains(buf.String(), "MERGED received") { t.Errorf("Output missing expected text: %s", buf.String()) } // Test HandleMergeFailed buf.Reset() failedPayload := &MergeFailedPayload{ Branch: "polecat/nux/gt-abc", Issue: "gt-abc", Polecat: "nux", Rig: "gastown", TargetBranch: "main", FailureType: "tests", Error: "Test failed", } if err := handler.HandleMergeFailed(failedPayload); err != nil { t.Errorf("HandleMergeFailed error: %v", err) } if !strings.Contains(buf.String(), "MERGE_FAILED received") { t.Errorf("Output missing expected text: %s", buf.String()) } // Test HandleReworkRequest buf.Reset() reworkPayload := &ReworkRequestPayload{ Branch: "polecat/nux/gt-abc", Issue: "gt-abc", Polecat: "nux", Rig: "gastown", TargetBranch: "main", ConflictFiles: []string{"file1.go"}, } if err := handler.HandleReworkRequest(reworkPayload); err != nil { t.Errorf("HandleReworkRequest error: %v", err) } if !strings.Contains(buf.String(), "REWORK_REQUEST received") { t.Errorf("Output missing expected text: %s", buf.String()) } } // Mock handlers for testing type mockWitnessHandler struct { mergedCalled bool failedCalled bool reworkCalled bool } func (m *mockWitnessHandler) HandleMerged(payload *MergedPayload) error { m.mergedCalled = true return nil } func (m *mockWitnessHandler) HandleMergeFailed(payload *MergeFailedPayload) error { m.failedCalled = true return nil } func (m *mockWitnessHandler) HandleReworkRequest(payload *ReworkRequestPayload) error { m.reworkCalled = true return nil } type mockRefineryHandler struct { readyCalled bool } func (m *mockRefineryHandler) HandleMergeReady(payload *MergeReadyPayload) error { m.readyCalled = true return nil }