package templates import ( "os" "strings" "testing" ) func TestNew(t *testing.T) { tmpl, err := New() if err != nil { t.Fatalf("New() error = %v", err) } if tmpl == nil { t.Fatal("New() returned nil") } } func TestRenderRole_Mayor(t *testing.T) { tmpl, err := New() if err != nil { t.Fatalf("New() error = %v", err) } data := RoleData{ Role: "mayor", TownRoot: "/test/town", TownName: "town", WorkDir: "/test/town", DefaultBranch: "main", MayorSession: "gt-town-mayor", DeaconSession: "gt-town-deacon", } output, err := tmpl.RenderRole("mayor", data) if err != nil { t.Fatalf("RenderRole() error = %v", err) } // Check for key content if !strings.Contains(output, "Mayor Context") { t.Error("output missing 'Mayor Context'") } if !strings.Contains(output, "/test/town") { t.Error("output missing town root") } if !strings.Contains(output, "global coordinator") { t.Error("output missing role description") } } func TestRenderRole_Polecat(t *testing.T) { tmpl, err := New() if err != nil { t.Fatalf("New() error = %v", err) } data := RoleData{ Role: "polecat", RigName: "myrig", TownRoot: "/test/town", TownName: "town", WorkDir: "/test/town/myrig/polecats/TestCat", DefaultBranch: "main", Polecat: "TestCat", MayorSession: "gt-town-mayor", DeaconSession: "gt-town-deacon", } output, err := tmpl.RenderRole("polecat", data) if err != nil { t.Fatalf("RenderRole() error = %v", err) } // Check for key content if !strings.Contains(output, "Polecat Context") { t.Error("output missing 'Polecat Context'") } if !strings.Contains(output, "TestCat") { t.Error("output missing polecat name") } if !strings.Contains(output, "myrig") { t.Error("output missing rig name") } } func TestRenderRole_Deacon(t *testing.T) { tmpl, err := New() if err != nil { t.Fatalf("New() error = %v", err) } data := RoleData{ Role: "deacon", TownRoot: "/test/town", TownName: "town", WorkDir: "/test/town", DefaultBranch: "main", MayorSession: "gt-town-mayor", DeaconSession: "gt-town-deacon", } output, err := tmpl.RenderRole("deacon", data) if err != nil { t.Fatalf("RenderRole() error = %v", err) } // Check for key content if !strings.Contains(output, "Deacon Context") { t.Error("output missing 'Deacon Context'") } if !strings.Contains(output, "/test/town") { t.Error("output missing town root") } if !strings.Contains(output, "Patrol Executor") { t.Error("output missing role description") } if !strings.Contains(output, "Startup Protocol: Propulsion") { t.Error("output missing startup protocol section") } if !strings.Contains(output, "mol-deacon-patrol") { t.Error("output missing patrol molecule reference") } } func TestRenderRole_Refinery_DefaultBranch(t *testing.T) { tmpl, err := New() if err != nil { t.Fatalf("New() error = %v", err) } // Test with custom default branch (e.g., "develop") data := RoleData{ Role: "refinery", RigName: "myrig", TownRoot: "/test/town", TownName: "town", WorkDir: "/test/town/myrig/refinery/rig", DefaultBranch: "develop", MayorSession: "gt-town-mayor", DeaconSession: "gt-town-deacon", } output, err := tmpl.RenderRole("refinery", data) if err != nil { t.Fatalf("RenderRole() error = %v", err) } // Check that the custom default branch is used in git commands if !strings.Contains(output, "origin/develop") { t.Error("output missing 'origin/develop' - DefaultBranch not being used for rebase") } if !strings.Contains(output, "git checkout develop") { t.Error("output missing 'git checkout develop' - DefaultBranch not being used for checkout") } if !strings.Contains(output, "git push origin develop") { t.Error("output missing 'git push origin develop' - DefaultBranch not being used for push") } // Verify it does NOT contain hardcoded "main" in git commands // (main may appear in other contexts like "main branch" descriptions, so we check specific patterns) if strings.Contains(output, "git rebase origin/main") { t.Error("output still contains hardcoded 'git rebase origin/main' - should use DefaultBranch") } if strings.Contains(output, "git checkout main") { t.Error("output still contains hardcoded 'git checkout main' - should use DefaultBranch") } if strings.Contains(output, "git push origin main") { t.Error("output still contains hardcoded 'git push origin main' - should use DefaultBranch") } } func TestRenderMessage_Spawn(t *testing.T) { tmpl, err := New() if err != nil { t.Fatalf("New() error = %v", err) } data := SpawnData{ Issue: "gt-123", Title: "Test Issue", Priority: 1, Description: "Test description", Branch: "feature/test", RigName: "myrig", Polecat: "TestCat", } output, err := tmpl.RenderMessage("spawn", data) if err != nil { t.Fatalf("RenderMessage() error = %v", err) } // Check for key content if !strings.Contains(output, "gt-123") { t.Error("output missing issue ID") } if !strings.Contains(output, "Test Issue") { t.Error("output missing issue title") } } func TestRenderMessage_Nudge(t *testing.T) { tmpl, err := New() if err != nil { t.Fatalf("New() error = %v", err) } data := NudgeData{ Polecat: "TestCat", Reason: "No progress for 30 minutes", NudgeCount: 2, MaxNudges: 3, Issue: "gt-123", Status: "in_progress", } output, err := tmpl.RenderMessage("nudge", data) if err != nil { t.Fatalf("RenderMessage() error = %v", err) } // Check for key content if !strings.Contains(output, "TestCat") { t.Error("output missing polecat name") } if !strings.Contains(output, "2/3") { t.Error("output missing nudge count") } } func TestRoleNames(t *testing.T) { tmpl, err := New() if err != nil { t.Fatalf("New() error = %v", err) } names := tmpl.RoleNames() expected := []string{"mayor", "witness", "refinery", "polecat", "crew", "deacon"} if len(names) != len(expected) { t.Errorf("RoleNames() = %v, want %v", names, expected) } for i, name := range names { if name != expected[i] { t.Errorf("RoleNames()[%d] = %q, want %q", i, name, expected[i]) } } } func TestGetAllRoleTemplates(t *testing.T) { templates, err := GetAllRoleTemplates() if err != nil { t.Fatalf("GetAllRoleTemplates() error = %v", err) } if len(templates) == 0 { t.Fatal("GetAllRoleTemplates() returned empty map") } expectedFiles := []string{ "deacon.md.tmpl", "witness.md.tmpl", "refinery.md.tmpl", "mayor.md.tmpl", "polecat.md.tmpl", "crew.md.tmpl", } for _, file := range expectedFiles { content, ok := templates[file] if !ok { t.Errorf("GetAllRoleTemplates() missing %s", file) continue } if len(content) == 0 { t.Errorf("GetAllRoleTemplates()[%s] has empty content", file) } } } func TestGetAllRoleTemplates_ContentValidity(t *testing.T) { templates, err := GetAllRoleTemplates() if err != nil { t.Fatalf("GetAllRoleTemplates() error = %v", err) } for name, content := range templates { if !strings.HasSuffix(name, ".md.tmpl") { t.Errorf("unexpected file %s (should end with .md.tmpl)", name) } contentStr := string(content) if !strings.Contains(contentStr, "Context") { t.Errorf("%s doesn't contain 'Context' - may not be a valid role template", name) } } } func TestNewWithOverrides_NoOverrides(t *testing.T) { // When no override paths exist, should work like New() tmpl, err := NewWithOverrides("/nonexistent/town", "/nonexistent/rig") if err != nil { t.Fatalf("NewWithOverrides() error = %v", err) } if tmpl == nil { t.Fatal("NewWithOverrides() returned nil") } // Should have no overrides if tmpl.RoleOverrideCount() != 0 { t.Errorf("RoleOverrideCount() = %d, want 0", tmpl.RoleOverrideCount()) } // Should still render embedded templates data := RoleData{ Role: "polecat", RigName: "myrig", Polecat: "TestCat", } output, err := tmpl.RenderRole("polecat", data) if err != nil { t.Fatalf("RenderRole() error = %v", err) } if !strings.Contains(output, "Polecat Context") { t.Error("embedded template not rendered correctly") } } func TestNewWithOverrides_WithOverride(t *testing.T) { // Create a temp directory with an override template tempDir := t.TempDir() overrideDir := tempDir + "/templates/roles" if err := os.MkdirAll(overrideDir, 0755); err != nil { t.Fatalf("failed to create override dir: %v", err) } // Write a simple override template overrideContent := `# Custom Polecat Context You are polecat {{ .Polecat }} with custom instructions. Rig: {{ .RigName }} ` if err := os.WriteFile(overrideDir+"/polecat.md.tmpl", []byte(overrideContent), 0644); err != nil { t.Fatalf("failed to write override template: %v", err) } // Create Templates with the override tmpl, err := NewWithOverrides("", tempDir) if err != nil { t.Fatalf("NewWithOverrides() error = %v", err) } // Should have one override if tmpl.RoleOverrideCount() != 1 { t.Errorf("RoleOverrideCount() = %d, want 1", tmpl.RoleOverrideCount()) } // Should report polecat as having override if !tmpl.HasRoleOverride("polecat") { t.Error("HasRoleOverride('polecat') = false, want true") } if tmpl.HasRoleOverride("mayor") { t.Error("HasRoleOverride('mayor') = true, want false") } // Render should use override data := RoleData{ Role: "polecat", RigName: "myrig", Polecat: "TestCat", } output, err := tmpl.RenderRole("polecat", data) if err != nil { t.Fatalf("RenderRole() error = %v", err) } // Should contain custom content, not embedded if !strings.Contains(output, "Custom Polecat Context") { t.Error("override template not used - missing 'Custom Polecat Context'") } if strings.Contains(output, "Idle Polecat Heresy") { t.Error("embedded template used instead of override") } if !strings.Contains(output, "TestCat") { t.Error("template variable not expanded") } } func TestNewWithOverrides_RigOverridesTown(t *testing.T) { // Create temp dirs for both town and rig overrides townDir := t.TempDir() rigDir := t.TempDir() townOverrideDir := townDir + "/templates/roles" rigOverrideDir := rigDir + "/templates/roles" if err := os.MkdirAll(townOverrideDir, 0755); err != nil { t.Fatalf("failed to create town override dir: %v", err) } if err := os.MkdirAll(rigOverrideDir, 0755); err != nil { t.Fatalf("failed to create rig override dir: %v", err) } // Write town override townContent := "# Town Polecat\nTown override: {{ .Polecat }}" if err := os.WriteFile(townOverrideDir+"/polecat.md.tmpl", []byte(townContent), 0644); err != nil { t.Fatalf("failed to write town override: %v", err) } // Write rig override (should win) rigContent := "# Rig Polecat\nRig override: {{ .Polecat }}" if err := os.WriteFile(rigOverrideDir+"/polecat.md.tmpl", []byte(rigContent), 0644); err != nil { t.Fatalf("failed to write rig override: %v", err) } // Create Templates with both overrides tmpl, err := NewWithOverrides(townDir, rigDir) if err != nil { t.Fatalf("NewWithOverrides() error = %v", err) } // Render should use rig override (higher precedence) data := RoleData{Polecat: "TestCat"} output, err := tmpl.RenderRole("polecat", data) if err != nil { t.Fatalf("RenderRole() error = %v", err) } if !strings.Contains(output, "Rig Polecat") { t.Error("rig override not used") } if strings.Contains(output, "Town Polecat") { t.Error("town override used instead of rig override") } } func TestNewWithOverrides_InvalidTemplate(t *testing.T) { // Create a temp directory with an invalid template tempDir := t.TempDir() overrideDir := tempDir + "/templates/roles" if err := os.MkdirAll(overrideDir, 0755); err != nil { t.Fatalf("failed to create override dir: %v", err) } // Write an invalid template (unclosed action) invalidContent := "# Invalid Template\n{{ .Polecat" if err := os.WriteFile(overrideDir+"/polecat.md.tmpl", []byte(invalidContent), 0644); err != nil { t.Fatalf("failed to write invalid template: %v", err) } // Should return error for invalid template _, err := NewWithOverrides("", tempDir) if err == nil { t.Error("NewWithOverrides() should fail with invalid template") } if !strings.Contains(err.Error(), "parsing override template") { t.Errorf("error should mention parsing override template: %v", err) } }