// SPDX-License-Identifier: EUPL-1.2 package agentic import ( "context" "encoding/json" "net/http" "net/http/httptest" "os" "os/exec" "path/filepath" "testing" "time" "dappco.re/go/core/forge" "github.com/stretchr/testify/assert" ) // --- Shutdown --- func TestPrep_Shutdown_Good(t *testing.T) { s := &PrepSubsystem{ backoff: make(map[string]time.Time), failCount: make(map[string]int), } err := s.Shutdown(context.Background()) assert.NoError(t, err) } // --- Name --- func TestPrep_Name_Good(t *testing.T) { s := &PrepSubsystem{} assert.Equal(t, "agentic", s.Name()) } // --- findConsumersList --- func TestPrep_FindConsumersList_Good_HasConsumers(t *testing.T) { dir := t.TempDir() // Create go.work goWork := `go 1.22 use ( ./core/go ./core/agent ./core/mcp )` os.WriteFile(filepath.Join(dir, "go.work"), []byte(goWork), 0o644) // Create module dirs with go.mod for _, mod := range []struct { path string content string }{ {"core/go", "module forge.lthn.ai/core/go\n\ngo 1.22\n"}, {"core/agent", "module forge.lthn.ai/core/agent\n\nrequire forge.lthn.ai/core/go v0.7.0\n"}, {"core/mcp", "module forge.lthn.ai/core/mcp\n\nrequire forge.lthn.ai/core/go v0.7.0\n"}, } { modDir := filepath.Join(dir, mod.path) os.MkdirAll(modDir, 0o755) os.WriteFile(filepath.Join(modDir, "go.mod"), []byte(mod.content), 0o644) } s := &PrepSubsystem{ codePath: dir, backoff: make(map[string]time.Time), failCount: make(map[string]int), } list, count := s.findConsumersList("go") assert.Equal(t, 2, count) assert.Contains(t, list, "agent") assert.Contains(t, list, "mcp") assert.Contains(t, list, "Breaking change risk") } func TestPrep_FindConsumersList_Good_NoConsumers(t *testing.T) { dir := t.TempDir() goWork := `go 1.22 use ( ./core/go )` os.WriteFile(filepath.Join(dir, "go.work"), []byte(goWork), 0o644) 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) s := &PrepSubsystem{ codePath: dir, backoff: make(map[string]time.Time), failCount: make(map[string]int), } list, count := s.findConsumersList("go") assert.Equal(t, 0, count) assert.Empty(t, list) } func TestPrep_FindConsumersList_Bad_NoGoWork(t *testing.T) { s := &PrepSubsystem{ codePath: t.TempDir(), backoff: make(map[string]time.Time), failCount: make(map[string]int), } list, count := s.findConsumersList("go") assert.Equal(t, 0, count) assert.Empty(t, list) } // --- pullWikiContent --- func TestPrep_PullWikiContent_Good_WithPages(t *testing.T) { 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": "Home", "sub_url": "Home"}, {"title": "Architecture", "sub_url": "Architecture"}, }) case r.URL.Path == "/api/v1/repos/core/go-io/wiki/page/Home": // "Hello World" base64 json.NewEncoder(w).Encode(map[string]any{ "title": "Home", "content_base64": "SGVsbG8gV29ybGQ=", }) case r.URL.Path == "/api/v1/repos/core/go-io/wiki/page/Architecture": json.NewEncoder(w).Encode(map[string]any{ "title": "Architecture", "content_base64": "TGF5ZXJlZA==", }) 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") assert.Contains(t, content, "Hello World") assert.Contains(t, content, "Layered") assert.Contains(t, content, "### Home") assert.Contains(t, content, "### Architecture") } func TestPrep_PullWikiContent_Good_NoPages(t *testing.T) { 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"), 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) } // --- getIssueBody --- func TestPrep_GetIssueBody_Good(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]any{ "number": 15, "title": "Fix tests", "body": "The tests are broken in pkg/core", }) })) 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", 15) assert.Contains(t, body, "tests are broken") } func TestPrep_GetIssueBody_Bad_NotFound(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 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), } body := s.getIssueBody(context.Background(), "core", "go-io", 999) assert.Empty(t, body) } // --- buildPrompt --- func TestPrep_BuildPrompt_Good_BasicFields(t *testing.T) { dir := t.TempDir() // Create go.mod to detect language os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module test\n\ngo 1.22\n"), 0o644) s := &PrepSubsystem{ codePath: t.TempDir(), backoff: make(map[string]time.Time), failCount: make(map[string]int), } prompt, memories, consumers := s.buildPrompt(context.Background(), PrepInput{ Task: "Fix the tests", Org: "core", Repo: "go-io", }, "dev", dir) assert.Contains(t, prompt, "TASK: Fix the tests") assert.Contains(t, prompt, "REPO: core/go-io on branch dev") assert.Contains(t, prompt, "LANGUAGE: go") assert.Contains(t, prompt, "CONSTRAINTS:") assert.Contains(t, prompt, "CODEX.md") assert.Equal(t, 0, memories) assert.Equal(t, 0, consumers) } func TestPrep_BuildPrompt_Good_WithIssue(t *testing.T) { dir := t.TempDir() srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]any{ "number": 42, "title": "Bug report", "body": "Steps to reproduce the bug", }) })) t.Cleanup(srv.Close) s := &PrepSubsystem{ forge: forge.NewForge(srv.URL, "test-token"), codePath: t.TempDir(), client: srv.Client(), backoff: make(map[string]time.Time), failCount: make(map[string]int), } prompt, _, _ := s.buildPrompt(context.Background(), PrepInput{ Task: "Fix the bug", Org: "core", Repo: "go-io", Issue: 42, }, "dev", dir) assert.Contains(t, prompt, "ISSUE:") assert.Contains(t, prompt, "Steps to reproduce") } // --- buildPrompt (naming convention tests) --- func TestPrep_BuildPrompt_Good(t *testing.T) { dir := t.TempDir() // Create go.mod to detect language as "go" os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module test\n\ngo 1.22\n"), 0o644) s := &PrepSubsystem{ codePath: t.TempDir(), backoff: make(map[string]time.Time), failCount: make(map[string]int), } prompt, memories, consumers := s.buildPrompt(context.Background(), PrepInput{ Task: "Add unit tests", Org: "core", Repo: "go-io", }, "dev", dir) assert.Contains(t, prompt, "TASK: Add unit tests") assert.Contains(t, prompt, "REPO: core/go-io on branch dev") assert.Contains(t, prompt, "LANGUAGE: go") assert.Contains(t, prompt, "BUILD: go build ./...") assert.Contains(t, prompt, "TEST: go test ./...") assert.Contains(t, prompt, "CONSTRAINTS:") assert.Equal(t, 0, memories) assert.Equal(t, 0, consumers) } func TestPrep_BuildPrompt_Bad(t *testing.T) { // Empty repo path — still produces a prompt (no crash) s := &PrepSubsystem{ codePath: t.TempDir(), backoff: make(map[string]time.Time), failCount: make(map[string]int), } prompt, memories, consumers := s.buildPrompt(context.Background(), PrepInput{ Task: "Do something", Org: "core", Repo: "go-io", }, "main", "") assert.Contains(t, prompt, "TASK: Do something") assert.Contains(t, prompt, "CONSTRAINTS:") assert.Equal(t, 0, memories) assert.Equal(t, 0, consumers) } func TestPrep_BuildPrompt_Ugly(t *testing.T) { dir := t.TempDir() os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module test\n\ngo 1.22\n"), 0o644) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]any{ "number": 99, "title": "Critical bug", "body": "Server crashes on startup", }) })) t.Cleanup(srv.Close) s := &PrepSubsystem{ forge: forge.NewForge(srv.URL, "test-token"), codePath: t.TempDir(), client: srv.Client(), backoff: make(map[string]time.Time), failCount: make(map[string]int), } prompt, _, _ := s.buildPrompt(context.Background(), PrepInput{ Task: "Fix critical bug", Org: "core", Repo: "go-io", Persona: "reviewer", PlanTemplate: "nonexistent-plan", Issue: 99, }, "agent/fix-bug", dir) // Persona may or may not resolve, but prompt must still contain core fields assert.Contains(t, prompt, "TASK: Fix critical bug") assert.Contains(t, prompt, "REPO: core/go-io on branch agent/fix-bug") assert.Contains(t, prompt, "ISSUE:") assert.Contains(t, prompt, "Server crashes on startup") assert.Contains(t, prompt, "CONSTRAINTS:") } func TestPrep_BuildPrompt_Ugly_WithGitLog(t *testing.T) { dir := t.TempDir() os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module test\n\ngo 1.22\n"), 0o644) // Init a real git repo with commits so git log path is covered exec.Command("git", "init", "-b", "main", dir).Run() exec.Command("git", "-C", dir, "config", "user.email", "t@t.com").Run() exec.Command("git", "-C", dir, "config", "user.name", "T").Run() exec.Command("git", "-C", dir, "add", ".").Run() exec.Command("git", "-C", dir, "commit", "-m", "init").Run() s := &PrepSubsystem{ codePath: t.TempDir(), backoff: make(map[string]time.Time), failCount: make(map[string]int), } prompt, _, _ := s.buildPrompt(context.Background(), PrepInput{ Task: "Fix tests", Org: "core", Repo: "go-io", }, "dev", dir) assert.Contains(t, prompt, "RECENT CHANGES:") assert.Contains(t, prompt, "init") } // --- runQA --- func TestDispatch_RunQA_Good_PHPNoComposer(t *testing.T) { dir := t.TempDir() repoDir := filepath.Join(dir, "repo") os.MkdirAll(repoDir, 0o755) // composer.json present but no composer binary os.WriteFile(filepath.Join(repoDir, "composer.json"), []byte(`{"name":"test"}`), 0o644) s := &PrepSubsystem{ backoff: make(map[string]time.Time), failCount: make(map[string]int), } // Will fail (composer not found) — that's the expected path 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) } // --- PrepWorkspace Ugly --- func TestPrep_PrepWorkspace_Ugly(t *testing.T) { root := t.TempDir() t.Setenv("CORE_WORKSPACE", root) s := &PrepSubsystem{ codePath: t.TempDir(), backoff: make(map[string]time.Time), failCount: make(map[string]int), } // Repo name "." — should be rejected as invalid _, _, err := s.prepWorkspace(context.Background(), nil, PrepInput{ Repo: ".", Issue: 1, }) assert.Error(t, err) assert.Contains(t, err.Error(), "invalid repo name") // Repo name ".." — should be rejected as invalid _, _, err2 := s.prepWorkspace(context.Background(), nil, PrepInput{ Repo: "..", Issue: 1, }) assert.Error(t, err2) assert.Contains(t, err2.Error(), "invalid repo name") } // --- 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, "