diff --git a/pkg/agentic/epic_test.go b/pkg/agentic/epic_test.go index d27cbcd..b0bcc50 100644 --- a/pkg/agentic/epic_test.go +++ b/pkg/agentic/epic_test.go @@ -7,6 +7,7 @@ import ( "encoding/json" "net/http" "net/http/httptest" + "strings" "sync/atomic" "testing" "time" @@ -391,3 +392,55 @@ func TestEpic_CreateEpic_Good_AgenticLabelNotDuplicated(t *testing.T) { require.NoError(t, err) assert.True(t, out.Success) } + +// --- Ugly tests --- + +func TestEpic_CreateEpic_Ugly(t *testing.T) { + // Very long title/description + srv, _ := mockForgeServer(t) + s := newTestSubsystem(t, srv) + + longTitle := strings.Repeat("Very Long Epic Title ", 50) + longBody := strings.Repeat("Detailed description of the epic work to be done. ", 100) + + _, out, err := s.createEpic(context.Background(), nil, EpicInput{ + Repo: "test-repo", + Title: longTitle, + Body: longBody, + Tasks: []string{"Task 1"}, + }) + require.NoError(t, err) + assert.True(t, out.Success) + assert.NotZero(t, out.EpicNumber) +} + +func TestEpic_CreateIssue_Ugly(t *testing.T) { + // Issue with HTML in body + srv, _ := mockForgeServer(t) + s := newTestSubsystem(t, srv) + + htmlBody := "

Issue

This has bold and

" + child, err := s.createIssue(context.Background(), "core", "test-repo", "HTML Issue", htmlBody, []int64{1}) + require.NoError(t, err) + assert.Equal(t, "HTML Issue", child.Title) + assert.NotZero(t, child.Number) +} + +func TestEpic_ResolveLabelIDs_Ugly(t *testing.T) { + // Label names with special chars + srv, _ := mockForgeServer(t) + s := newTestSubsystem(t, srv) + + ids := s.resolveLabelIDs(context.Background(), "core", "test-repo", []string{"bug/fix", "feature:new", "label with spaces"}) + // These will all be created as new labels since they don't match existing ones + assert.NotNil(t, ids) +} + +func TestEpic_CreateLabel_Ugly(t *testing.T) { + // Label with unicode name + srv, _ := mockForgeServer(t) + s := newTestSubsystem(t, srv) + + id := s.createLabel(context.Background(), "core", "test-repo", "\u00e9nhancement-\u00fc\u00f1ic\u00f6de") + assert.NotZero(t, id) +} diff --git a/pkg/agentic/logic_test.go b/pkg/agentic/logic_test.go index e6f05ba..d4ea32e 100644 --- a/pkg/agentic/logic_test.go +++ b/pkg/agentic/logic_test.go @@ -377,6 +377,106 @@ func TestEvents_EmitEvent_Ugly_EmptyFields(t *testing.T) { }) } +// --- emitStartEvent/emitCompletionEvent (Good/Bad/Ugly) --- + +func TestEvents_EmitStartEvent_Good(t *testing.T) { + root := t.TempDir() + t.Setenv("CORE_WORKSPACE", root) + require.True(t, fs.EnsureDir(filepath.Join(root, "workspace")).OK) + + emitStartEvent("codex", "core/go-io/task-10") + + eventsFile := filepath.Join(root, "workspace", "events.jsonl") + r := fs.Read(eventsFile) + require.True(t, r.OK) + content := r.Value.(string) + assert.Contains(t, content, "agent_started") + assert.Contains(t, content, "codex") + assert.Contains(t, content, "core/go-io/task-10") +} + +func TestEvents_EmitStartEvent_Bad(t *testing.T) { + // Empty agent name + root := t.TempDir() + t.Setenv("CORE_WORKSPACE", root) + require.True(t, fs.EnsureDir(filepath.Join(root, "workspace")).OK) + + assert.NotPanics(t, func() { + emitStartEvent("", "core/go-io/task-10") + }) + + eventsFile := filepath.Join(root, "workspace", "events.jsonl") + r := fs.Read(eventsFile) + require.True(t, r.OK) + content := r.Value.(string) + assert.Contains(t, content, "agent_started") +} + +func TestEvents_EmitStartEvent_Ugly(t *testing.T) { + // Very long workspace name + root := t.TempDir() + t.Setenv("CORE_WORKSPACE", root) + require.True(t, fs.EnsureDir(filepath.Join(root, "workspace")).OK) + + longName := strings.Repeat("very-long-workspace-name-", 50) + assert.NotPanics(t, func() { + emitStartEvent("claude", longName) + }) + + eventsFile := filepath.Join(root, "workspace", "events.jsonl") + r := fs.Read(eventsFile) + require.True(t, r.OK) + assert.Contains(t, r.Value.(string), "agent_started") +} + +func TestEvents_EmitCompletionEvent_Good(t *testing.T) { + root := t.TempDir() + t.Setenv("CORE_WORKSPACE", root) + require.True(t, fs.EnsureDir(filepath.Join(root, "workspace")).OK) + + emitCompletionEvent("gemini", "core/go-log/task-5", "completed") + + eventsFile := filepath.Join(root, "workspace", "events.jsonl") + r := fs.Read(eventsFile) + require.True(t, r.OK) + content := r.Value.(string) + assert.Contains(t, content, "agent_completed") + assert.Contains(t, content, "gemini") + assert.Contains(t, content, "completed") +} + +func TestEvents_EmitCompletionEvent_Bad(t *testing.T) { + // Empty status + root := t.TempDir() + t.Setenv("CORE_WORKSPACE", root) + require.True(t, fs.EnsureDir(filepath.Join(root, "workspace")).OK) + + assert.NotPanics(t, func() { + emitCompletionEvent("claude", "core/agent/task-1", "") + }) + + eventsFile := filepath.Join(root, "workspace", "events.jsonl") + r := fs.Read(eventsFile) + require.True(t, r.OK) + assert.Contains(t, r.Value.(string), "agent_completed") +} + +func TestEvents_EmitCompletionEvent_Ugly(t *testing.T) { + // Unicode in agent name + root := t.TempDir() + t.Setenv("CORE_WORKSPACE", root) + require.True(t, fs.EnsureDir(filepath.Join(root, "workspace")).OK) + + assert.NotPanics(t, func() { + emitCompletionEvent("\u00e9nchantr\u00efx-\u2603", "core/agent/task-1", "completed") + }) + + eventsFile := filepath.Join(root, "workspace", "events.jsonl") + r := fs.Read(eventsFile) + require.True(t, r.OK) + assert.Contains(t, r.Value.(string), "\u00e9nchantr\u00efx") +} + // --- countFileRefs --- func TestIngest_CountFileRefs_Good_GoRefs(t *testing.T) { diff --git a/pkg/agentic/paths_test.go b/pkg/agentic/paths_test.go index 2522789..370650a 100644 --- a/pkg/agentic/paths_test.go +++ b/pkg/agentic/paths_test.go @@ -321,7 +321,20 @@ func TestPaths_ParseInt_Ugly_LeadingTrailingWhitespace(t *testing.T) { assert.Equal(t, 42, parseInt(" 42 ")) } -// --- newFs Bad/Ugly --- +// --- newFs Good/Bad/Ugly --- + +func TestPaths_NewFs_Good(t *testing.T) { + f := newFs("/tmp") + assert.NotNil(t, f, "newFs should return a non-nil Fs") + assert.IsType(t, &core.Fs{}, f) +} + +// --- parseInt Good --- + +func TestPaths_ParseInt_Good(t *testing.T) { + assert.Equal(t, 42, parseInt("42")) + assert.Equal(t, 0, parseInt("0")) +} func TestPaths_NewFs_Bad_EmptyRoot(t *testing.T) { f := newFs("") diff --git a/pkg/agentic/plan_crud_test.go b/pkg/agentic/plan_crud_test.go index a717302..a24bf04 100644 --- a/pkg/agentic/plan_crud_test.go +++ b/pkg/agentic/plan_crud_test.go @@ -506,3 +506,92 @@ func TestPlan_ValidPlanStatus_Ugly_NearMissStatus(t *testing.T) { assert.False(t, validPlanStatus(" draft")) // leading space assert.False(t, validPlanStatus("draft ")) // trailing space } + +// --- generatePlanID Bad/Ugly --- + +func TestPlan_GeneratePlanID_Bad(t *testing.T) { + // Empty title — slug will be empty, but random suffix is still appended + id := generatePlanID("") + assert.NotEmpty(t, id, "should still generate an ID with random suffix") + assert.Contains(t, id, "-", "should have random suffix separated by dash") +} + +func TestPlan_GeneratePlanID_Ugly(t *testing.T) { + // Title with only special chars — slug will be empty + id := generatePlanID("!@#$%^&*()") + assert.NotEmpty(t, id, "should still generate an ID with random suffix") +} + +// --- planList Bad/Ugly --- + +func TestPlan_PlanList_Bad(t *testing.T) { + // Plans dir doesn't exist yet — should create it + dir := t.TempDir() + t.Setenv("CORE_WORKSPACE", dir) + + s := newTestPrep(t) + _, out, err := s.planList(context.Background(), nil, PlanListInput{}) + require.NoError(t, err) + assert.True(t, out.Success) + assert.Equal(t, 0, out.Count) +} + +func TestPlan_PlanList_Ugly(t *testing.T) { + // Plans dir has corrupt JSON files + dir := t.TempDir() + t.Setenv("CORE_WORKSPACE", dir) + + s := newTestPrep(t) + // Create a real plan + s.planCreate(context.Background(), nil, PlanCreateInput{Title: "Real Plan", Objective: "Test"}) + + // Write corrupt JSON file in plans dir + plansDir := PlansRoot() + os.WriteFile(plansDir+"/corrupt-plan.json", []byte("not valid json {{{"), 0o644) + + _, out, err := s.planList(context.Background(), nil, PlanListInput{}) + require.NoError(t, err) + assert.Equal(t, 1, out.Count, "corrupt JSON file should be skipped") +} + +// --- writePlan Bad/Ugly --- + +func TestPlan_WritePlan_Bad(t *testing.T) { + // Plan with empty ID + dir := t.TempDir() + plan := &Plan{ + ID: "", + Title: "No ID Plan", + Status: "draft", + Objective: "Test empty ID", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + // Should write with planPath sanitising empty ID to "invalid" + path, err := writePlan(dir, plan) + require.NoError(t, err) + assert.Contains(t, path, "invalid.json") +} + +func TestPlan_WritePlan_Ugly(t *testing.T) { + // Plan with moderately long ID (within filesystem limits) + dir := t.TempDir() + longID := strings.Repeat("a", 100) + plan := &Plan{ + ID: longID, + Title: "Long ID Plan", + Status: "draft", + Objective: "Test long ID", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + path, err := writePlan(dir, plan) + require.NoError(t, err) + assert.NotEmpty(t, path) + assert.Contains(t, path, ".json") + + // Verify we can read it back + readBack, err := readPlan(dir, longID) + require.NoError(t, err) + assert.Equal(t, "Long ID Plan", readBack.Title) +} diff --git a/pkg/agentic/pr_test.go b/pkg/agentic/pr_test.go index 318b8e6..0c1b882 100644 --- a/pkg/agentic/pr_test.go +++ b/pkg/agentic/pr_test.go @@ -7,8 +7,10 @@ import ( "encoding/json" "net/http" "net/http/httptest" + "os" "os/exec" "path/filepath" + "strings" "testing" "time" @@ -264,3 +266,275 @@ func TestPr_CommentOnIssue_Good_PostsComment(t *testing.T) { s.commentOnIssue(context.Background(), "core", "go-io", 42, "Test comment") assert.True(t, commentPosted) } + +// --- buildPRBody --- + +func TestPr_BuildPRBody_Good(t *testing.T) { + s := &PrepSubsystem{} + st := &WorkspaceStatus{ + Status: "completed", + Repo: "go-io", + Task: "Fix the login bug", + Agent: "codex", + Branch: "agent/fix-login", + Issue: 42, + Runs: 3, + } + body := s.buildPRBody(st) + assert.Contains(t, body, "## Summary") + assert.Contains(t, body, "Fix the login bug") + assert.Contains(t, body, "Closes #42") + assert.Contains(t, body, "**Agent:** codex") + assert.Contains(t, body, "**Runs:** 3") +} + +func TestPr_BuildPRBody_Bad(t *testing.T) { + // Empty status struct + s := &PrepSubsystem{} + st := &WorkspaceStatus{} + body := s.buildPRBody(st) + assert.Contains(t, body, "## Summary") + assert.Contains(t, body, "**Agent:**") + assert.NotContains(t, body, "Closes #") +} + +func TestPr_BuildPRBody_Ugly(t *testing.T) { + // Very long task string + s := &PrepSubsystem{} + longTask := strings.Repeat("This is a very long task description. ", 100) + st := &WorkspaceStatus{ + Task: longTask, + Agent: "claude", + Runs: 1, + } + body := s.buildPRBody(st) + assert.Contains(t, body, "## Summary") + assert.Contains(t, body, "very long task") +} + +// --- commentOnIssue Bad/Ugly --- + +func TestPr_CommentOnIssue_Bad(t *testing.T) { + // Forge returns error (500) + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(500) + })) + t.Cleanup(srv.Close) + + s := &PrepSubsystem{ + forge: forge.NewForge(srv.URL, "test-token"), + forgeURL: srv.URL, + forgeToken: "test-token", + client: srv.Client(), + backoff: make(map[string]time.Time), + failCount: make(map[string]int), + } + + // Should not panic even on server error + assert.NotPanics(t, func() { + s.commentOnIssue(context.Background(), "core", "go-io", 42, "Test comment") + }) +} + +func TestPr_CommentOnIssue_Ugly(t *testing.T) { + // Very long comment body + commentPosted := false + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + commentPosted = true + w.WriteHeader(201) + } + })) + t.Cleanup(srv.Close) + + s := &PrepSubsystem{ + forge: forge.NewForge(srv.URL, "test-token"), + forgeURL: srv.URL, + forgeToken: "test-token", + client: srv.Client(), + backoff: make(map[string]time.Time), + failCount: make(map[string]int), + } + + longComment := strings.Repeat("This is a very long comment with details. ", 1000) + s.commentOnIssue(context.Background(), "core", "go-io", 42, longComment) + assert.True(t, commentPosted) +} + +// --- createPR Ugly --- + +func TestPr_CreatePR_Ugly(t *testing.T) { + // Workspace with no branch in status (auto-detect from git) + root := t.TempDir() + t.Setenv("CORE_WORKSPACE", root) + + wsDir := filepath.Join(root, "workspace", "test-ws-ugly") + repoDir := filepath.Join(wsDir, "repo") + require.NoError(t, exec.Command("git", "init", "-b", "main", repoDir).Run()) + gitCmd := exec.Command("git", "config", "user.name", "Test") + gitCmd.Dir = repoDir + gitCmd.Run() + gitCmd = exec.Command("git", "config", "user.email", "test@test.com") + gitCmd.Dir = repoDir + gitCmd.Run() + + // Need an initial commit so HEAD exists for branch detection + require.NoError(t, os.WriteFile(filepath.Join(repoDir, "README.md"), []byte("# Test"), 0o644)) + addCmd := exec.Command("git", "add", ".") + addCmd.Dir = repoDir + require.NoError(t, addCmd.Run()) + commitCmd := exec.Command("git", "commit", "-m", "init") + commitCmd.Dir = repoDir + require.NoError(t, commitCmd.Run()) + + // Write status with empty branch + require.NoError(t, writeStatus(wsDir, &WorkspaceStatus{ + Status: "completed", + Repo: "go-io", + Branch: "", // empty branch — should auto-detect + Task: "Fix something", + })) + + s := &PrepSubsystem{ + forgeToken: "test-token", + backoff: make(map[string]time.Time), + failCount: make(map[string]int), + } + + _, out, err := s.createPR(context.Background(), nil, CreatePRInput{ + Workspace: "test-ws-ugly", + DryRun: true, + }) + require.NoError(t, err) + assert.True(t, out.Success) + assert.NotEmpty(t, out.Branch, "branch should be auto-detected from git") +} + +// --- forgeCreatePR Ugly --- + +func TestPr_ForgeCreatePR_Ugly(t *testing.T) { + // Server returns 201 with unexpected JSON + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + w.WriteHeader(201) + json.NewEncoder(w).Encode(map[string]any{ + "unexpected": "fields", + "number": 0, + }) + return + } + w.WriteHeader(200) + })) + t.Cleanup(srv.Close) + + s := &PrepSubsystem{ + forge: forge.NewForge(srv.URL, "test-token"), + forgeURL: srv.URL, + forgeToken: "test-token", + client: srv.Client(), + backoff: make(map[string]time.Time), + failCount: make(map[string]int), + } + + // Should not panic — may return zero values for missing fields + assert.NotPanics(t, func() { + _, _, _ = s.forgeCreatePR( + context.Background(), + "core", "test-repo", + "agent/fix", "dev", + "Title", "Body", + ) + }) +} + +// --- listPRs Ugly --- + +func TestPr_ListPRs_Ugly(t *testing.T) { + // State filter "all" + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if containsStr(r.URL.Path, "/repos") && !containsStr(r.URL.Path, "/pulls") { + json.NewEncoder(w).Encode([]map[string]any{ + {"name": "go-io", "full_name": "core/go-io"}, + }) + return + } + json.NewEncoder(w).Encode([]map[string]any{}) + })) + t.Cleanup(srv.Close) + + s := &PrepSubsystem{ + forge: forge.NewForge(srv.URL, "test-token"), + forgeURL: srv.URL, + forgeToken: "test-token", + client: srv.Client(), + backoff: make(map[string]time.Time), + failCount: make(map[string]int), + } + + _, out, err := s.listPRs(context.Background(), nil, ListPRsInput{ + State: "all", + }) + require.NoError(t, err) + assert.True(t, out.Success) +} + +// --- listRepoPRs Good/Bad/Ugly --- + +func TestPr_ListRepoPRs_Good(t *testing.T) { + // Specific repo with PRs + srv := mockPRForgeServer(t) + s := &PrepSubsystem{ + forge: forge.NewForge(srv.URL, "test-token"), + forgeURL: srv.URL, + forgeToken: "test-token", + client: srv.Client(), + backoff: make(map[string]time.Time), + failCount: make(map[string]int), + } + + prs, err := s.listRepoPRs(context.Background(), "core", "test-repo", "open") + require.NoError(t, err) + // May be empty depending on mock, but should not error + _ = prs +} + +func TestPr_ListRepoPRs_Bad(t *testing.T) { + // Forge returns error + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(500) + })) + t.Cleanup(srv.Close) + + s := &PrepSubsystem{ + forge: forge.NewForge(srv.URL, "test-token"), + forgeURL: srv.URL, + forgeToken: "test-token", + client: srv.Client(), + backoff: make(map[string]time.Time), + failCount: make(map[string]int), + } + + _, err := s.listRepoPRs(context.Background(), "core", "go-io", "open") + assert.Error(t, err) +} + +func TestPr_ListRepoPRs_Ugly(t *testing.T) { + // Repo with no PRs + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + json.NewEncoder(w).Encode([]map[string]any{}) + })) + t.Cleanup(srv.Close) + + s := &PrepSubsystem{ + forge: forge.NewForge(srv.URL, "test-token"), + forgeURL: srv.URL, + forgeToken: "test-token", + client: srv.Client(), + backoff: make(map[string]time.Time), + failCount: make(map[string]int), + } + + prs, err := s.listRepoPRs(context.Background(), "core", "empty-repo", "open") + require.NoError(t, err) + assert.Empty(t, prs) +} diff --git a/pkg/agentic/prep_extra_test.go b/pkg/agentic/prep_extra_test.go index 6edaaad..2396e45 100644 --- a/pkg/agentic/prep_extra_test.go +++ b/pkg/agentic/prep_extra_test.go @@ -374,3 +374,155 @@ func TestDispatch_RunQA_Good_PHPNoComposer(t *testing.T) { result := s.runQA(dir) assert.False(t, result) } + +// --- pullWikiContent Bad/Ugly --- + +func TestPrep_PullWikiContent_Bad(t *testing.T) { + // Forge returns error on wiki pages + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(500) + })) + t.Cleanup(srv.Close) + + s := &PrepSubsystem{ + forge: forge.NewForge(srv.URL, "test-token"), + client: srv.Client(), + backoff: make(map[string]time.Time), + failCount: make(map[string]int), + } + + content := s.pullWikiContent(context.Background(), "core", "go-io") + assert.Empty(t, content) +} + +func TestPrep_PullWikiContent_Ugly(t *testing.T) { + // Forge returns pages with empty content + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.URL.Path == "/api/v1/repos/core/go-io/wiki/pages": + json.NewEncoder(w).Encode([]map[string]any{ + {"title": "EmptyPage", "sub_url": "EmptyPage"}, + }) + case r.URL.Path == "/api/v1/repos/core/go-io/wiki/page/EmptyPage": + json.NewEncoder(w).Encode(map[string]any{ + "title": "EmptyPage", + "content_base64": "", + }) + default: + w.WriteHeader(404) + } + })) + t.Cleanup(srv.Close) + + s := &PrepSubsystem{ + forge: forge.NewForge(srv.URL, "test-token"), + client: srv.Client(), + backoff: make(map[string]time.Time), + failCount: make(map[string]int), + } + + content := s.pullWikiContent(context.Background(), "core", "go-io") + // Empty content_base64 means the page is skipped + assert.Empty(t, content) +} + +// --- renderPlan Ugly --- + +func TestPrep_RenderPlan_Ugly(t *testing.T) { + // Template with variables that don't exist in template — variables just won't match + s := &PrepSubsystem{ + backoff: make(map[string]time.Time), + failCount: make(map[string]int), + } + + // Passing variables that won't match any {{placeholder}} in the template + result := s.renderPlan("coding", map[string]string{ + "nonexistent_var": "value1", + "another_missing": "value2", + }, "Test task") + // Should return the template rendered without substitution (if template exists) + // or empty if template doesn't exist. Either way, no panic. + _ = result +} + +// --- brainRecall Ugly --- + +func TestPrep_BrainRecall_Ugly(t *testing.T) { + // Server returns unexpected JSON structure + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + // Return JSON that doesn't have "memories" key + json.NewEncoder(w).Encode(map[string]any{ + "unexpected_key": "unexpected_value", + "data": []string{"not", "the", "right", "shape"}, + }) + })) + t.Cleanup(srv.Close) + + s := &PrepSubsystem{ + brainURL: srv.URL, + brainKey: "test-key", + client: srv.Client(), + backoff: make(map[string]time.Time), + failCount: make(map[string]int), + } + + recall, count := s.brainRecall(context.Background(), "go-io") + assert.Empty(t, recall, "unexpected JSON structure should yield no memories") + assert.Equal(t, 0, count) +} + +// --- findConsumersList Ugly --- + +func TestPrep_FindConsumersList_Ugly(t *testing.T) { + // go.work with modules that don't have go.mod + dir := t.TempDir() + + goWork := "go 1.22\n\nuse (\n\t./core/go\n\t./core/missing\n)" + os.WriteFile(filepath.Join(dir, "go.work"), []byte(goWork), 0o644) + + // Create only the first module dir with go.mod + modDir := filepath.Join(dir, "core", "go") + os.MkdirAll(modDir, 0o755) + os.WriteFile(filepath.Join(modDir, "go.mod"), []byte("module forge.lthn.ai/core/go\n"), 0o644) + + // core/missing has no go.mod + os.MkdirAll(filepath.Join(dir, "core", "missing"), 0o755) + + s := &PrepSubsystem{ + codePath: dir, + backoff: make(map[string]time.Time), + failCount: make(map[string]int), + } + + // Should not panic, just skip missing go.mod entries + list, count := s.findConsumersList("go") + assert.Equal(t, 0, count) + assert.Empty(t, list) +} + +// --- getIssueBody Ugly --- + +func TestPrep_GetIssueBody_Ugly(t *testing.T) { + // Issue body with HTML/special chars + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + json.NewEncoder(w).Encode(map[string]any{ + "number": 99, + "title": "Issue with ", + "body": "Body has & HTML <tags> and \"quotes\" and 'apostrophes' bold", + }) + })) + t.Cleanup(srv.Close) + + s := &PrepSubsystem{ + forge: forge.NewForge(srv.URL, "test-token"), + client: srv.Client(), + backoff: make(map[string]time.Time), + failCount: make(map[string]int), + } + + body := s.getIssueBody(context.Background(), "core", "go-io", 99) + assert.NotEmpty(t, body) + assert.Contains(t, body, "HTML") + assert.Contains(t, body, "