refactor(test): delete catch-all test files, rewrite dispatch_test.go

Delete edge_case_test.go, coverage_push_test.go, dispatch_extra_test.go.
Rewrite dispatch_test.go with proper naming: TestDispatch_Function_{Good,Bad,Ugly}.

Every function in dispatch.go now has Good/Bad/Ugly test groups.
Tests for non-dispatch functions will be restored to their correct files.

agentic 72.6% (temporary regression — tests being redistributed)

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-25 08:08:36 +00:00
parent 18eac65b70
commit 52f6031822
4 changed files with 411 additions and 1359 deletions

View file

@ -1,291 +0,0 @@
// SPDX-License-Identifier: EUPL-1.2
// Tests targeting partially covered functions to push toward 80%.
package agentic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"testing"
"time"
core "dappco.re/go/core"
"dappco.re/go/core/forge"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// --- statusRemote error parsing ---
func TestStatusRemote_Good_ErrorResponse(t *testing.T) {
callCount := 0
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
callCount++
w.Header().Set("Mcp-Session-Id", "s")
w.Header().Set("Content-Type", "text/event-stream")
switch callCount {
case 1:
fmt.Fprintf(w, "data: {\"result\":{}}\n\n")
case 2:
w.WriteHeader(200)
case 3:
// JSON-RPC error
result := map[string]any{
"error": map[string]any{"code": -32000, "message": "internal error"},
}
data, _ := json.Marshal(result)
fmt.Fprintf(w, "data: %s\n\n", data)
}
}))
t.Cleanup(srv.Close)
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
_, out, err := s.statusRemote(context.Background(), nil, RemoteStatusInput{
Host: srv.Listener.Addr().String(),
})
require.NoError(t, err)
assert.False(t, out.Success)
assert.Contains(t, out.Error, "internal error")
}
func TestStatusRemote_Good_CallFails(t *testing.T) {
callCount := 0
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
callCount++
w.Header().Set("Mcp-Session-Id", "s")
w.Header().Set("Content-Type", "text/event-stream")
switch callCount {
case 1:
fmt.Fprintf(w, "data: {\"result\":{}}\n\n")
case 2:
w.WriteHeader(200)
case 3:
w.WriteHeader(500)
}
}))
t.Cleanup(srv.Close)
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
_, out, err := s.statusRemote(context.Background(), nil, RemoteStatusInput{
Host: srv.Listener.Addr().String(),
})
require.NoError(t, err)
assert.Contains(t, out.Error, "call failed")
}
// --- loadRateLimitState parse error ---
func TestLoadRateLimitState_Bad_InvalidJSON(t *testing.T) {
// Write corrupt JSON at the expected path
home := core.Env("DIR_HOME")
path := filepath.Join(home, ".core", "coderabbit-ratelimit.json")
os.MkdirAll(filepath.Dir(path), 0o755)
original, _ := os.ReadFile(path)
os.WriteFile(path, []byte("{invalid"), 0o644)
t.Cleanup(func() {
if len(original) > 0 {
os.WriteFile(path, original, 0o644)
} else {
os.Remove(path)
}
})
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
result := s.loadRateLimitState()
assert.Nil(t, result)
}
// --- shutdownNow with deep layout ---
func TestShutdownNow_Good_DeepLayout(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsRoot := filepath.Join(root, "workspace")
// Create workspace in deep layout (org/repo/task)
ws := filepath.Join(wsRoot, "core", "go-io", "task-5")
os.MkdirAll(ws, 0o755)
require.NoError(t, writeStatus(ws, &WorkspaceStatus{
Status: "queued", Repo: "go-io", Agent: "codex",
}))
s := &PrepSubsystem{frozen: false, backoff: make(map[string]time.Time), failCount: make(map[string]int)}
_, out, err := s.shutdownNow(context.Background(), nil, ShutdownInput{})
require.NoError(t, err)
assert.Contains(t, out.Message, "cleared 1")
}
// --- findReviewCandidates ---
func TestFindReviewCandidates_Good_NoGitHubRemote(t *testing.T) {
root := t.TempDir()
// Create a repo dir without github remote
repoDir := filepath.Join(root, "go-io")
os.MkdirAll(repoDir, 0o755)
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
candidates := s.findReviewCandidates(root)
assert.Empty(t, candidates)
}
// --- prepWorkspace invalid repo ---
func TestPrepWorkspace_Bad_PathTraversal(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)}
_, _, err := s.prepWorkspace(context.Background(), nil, PrepInput{
Repo: "../../../etc", Issue: 1,
})
assert.Error(t, err)
}
// --- dispatch dry run ---
func TestDispatch_Good_DryRun(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
// Create a local repo clone for prep to find
repoSrc := filepath.Join(t.TempDir(), "core", "go-io")
os.MkdirAll(repoSrc, 0o755)
s := &PrepSubsystem{
codePath: filepath.Dir(filepath.Dir(repoSrc)),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
_, _, err := s.dispatch(context.Background(), nil, DispatchInput{
Repo: "go-io",
Task: "test dispatch",
Issue: 1,
DryRun: true,
})
// May fail (no git repo to clone) — exercises the dry run path validation
_ = err
}
// --- DefaultBranch with master ---
func TestDefaultBranch_Good_MasterBranch(t *testing.T) {
dir := t.TempDir()
// Init with master
exec.Command("git", "init", "-b", "master", dir).Run()
exec.Command("git", "-C", dir, "config", "user.email", "t@t.com").Run()
exec.Command("git", "-C", dir, "config", "user.name", "T").Run()
os.WriteFile(filepath.Join(dir, "f.txt"), []byte("x"), 0o644)
exec.Command("git", "-C", dir, "add", ".").Run()
exec.Command("git", "-C", dir, "commit", "-m", "init").Run()
branch := DefaultBranch(dir)
assert.Equal(t, "master", branch)
}
// --- extractPRNumber edge cases ---
func TestExtractPRNumber_Good_SimpleNumber(t *testing.T) {
assert.Equal(t, 5, extractPRNumber("5"))
}
func TestExtractPRNumber_Ugly_TrailingSlash(t *testing.T) {
assert.Equal(t, 0, extractPRNumber("https://forge.test/pulls/"))
}
// --- attemptVerifyAndMerge with Go test failure ---
func TestAttemptVerifyAndMerge_Bad_TestFails(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(map[string]any{"id": 1}) // comment
}))
t.Cleanup(srv.Close)
dir := t.TempDir()
// Create broken Go project
os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module test\n\ngo 1.22\n"), 0o644)
os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main\nimport \"fmt\"\n"), 0o644)
s := &PrepSubsystem{
forge: forge.NewForge(srv.URL, "tok"), forgeURL: srv.URL, forgeToken: "tok",
client: srv.Client(), backoff: make(map[string]time.Time), failCount: make(map[string]int),
}
result := s.attemptVerifyAndMerge(dir, "core", "test", "fix", 1)
assert.Equal(t, testFailed, result)
}
// --- autoVerifyAndMerge with invalid PR number ---
func TestAutoVerifyAndMerge_Bad_ZeroPRNumber(t *testing.T) {
dir := t.TempDir()
st := &WorkspaceStatus{Status: "completed", Repo: "test", PRURL: "https://forge.test/pulls/0"}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(dir, "status.json"), data, 0o644)
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
s.autoVerifyAndMerge(dir) // PR number = 0 → early return
}
// --- runQA with node project ---
func TestRunQA_Good_NodeNoNPM(t *testing.T) {
dir := t.TempDir()
repoDir := filepath.Join(dir, "repo")
os.MkdirAll(repoDir, 0o755)
os.WriteFile(filepath.Join(repoDir, "package.json"), []byte(`{"name":"test","scripts":{"test":"echo ok"}}`), 0o644)
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
// npm install may fail without node_modules — exercises the node path
_ = s.runQA(dir)
}
// --- buildPRBody ---
func TestBuildPRBody_Good_WithIssueRef(t *testing.T) {
st := &WorkspaceStatus{
Task: "Fix auth",
Agent: "codex",
Issue: 42,
Branch: "agent/fix-auth",
}
s := &PrepSubsystem{}
body := s.buildPRBody(st)
assert.Contains(t, body, "Fix auth")
assert.Contains(t, body, "Closes #42")
assert.Contains(t, body, "codex")
}
// --- resume with completed workspace ---
func TestResume_Good_CompletedDryRun(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsRoot := WorkspaceRoot()
ws := filepath.Join(wsRoot, "ws-completed")
repoDir := filepath.Join(ws, "repo")
os.MkdirAll(repoDir, 0o755)
exec.Command("git", "init", repoDir).Run()
st := &WorkspaceStatus{Status: "completed", Repo: "go-io", Agent: "codex", Task: "Review code"}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(ws, "status.json"), data, 0o644)
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
_, out, err := s.resume(context.Background(), nil, ResumeInput{
Workspace: "ws-completed",
DryRun: true,
})
require.NoError(t, err)
assert.True(t, out.Success)
assert.Contains(t, out.Prompt, "Review code")
}

View file

@ -1,401 +0,0 @@
// SPDX-License-Identifier: EUPL-1.2
package agentic
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"time"
core "dappco.re/go/core"
"dappco.re/go/core/forge"
"github.com/stretchr/testify/assert"
)
// --- agentOutputFile ---
func TestAgentOutputFile_Good(t *testing.T) {
assert.Contains(t, agentOutputFile("/ws", "codex"), ".meta/agent-codex.log")
assert.Contains(t, agentOutputFile("/ws", "claude:opus"), ".meta/agent-claude.log")
assert.Contains(t, agentOutputFile("/ws", "gemini:flash"), ".meta/agent-gemini.log")
}
// --- detectFinalStatus ---
func TestDetectFinalStatus_Good_Completed(t *testing.T) {
dir := t.TempDir()
status, question := detectFinalStatus(dir, 0, "completed")
assert.Equal(t, "completed", status)
assert.Empty(t, question)
}
func TestDetectFinalStatus_Good_Blocked(t *testing.T) {
dir := t.TempDir()
os.WriteFile(filepath.Join(dir, "BLOCKED.md"), []byte("Need API key for external service"), 0o644)
status, question := detectFinalStatus(dir, 0, "completed")
assert.Equal(t, "blocked", status)
assert.Equal(t, "Need API key for external service", question)
}
func TestDetectFinalStatus_Good_BlockedEmpty(t *testing.T) {
dir := t.TempDir()
// BLOCKED.md exists but is empty — should NOT be treated as blocked
os.WriteFile(filepath.Join(dir, "BLOCKED.md"), []byte(" \n "), 0o644)
status, _ := detectFinalStatus(dir, 0, "completed")
assert.Equal(t, "completed", status)
}
func TestDetectFinalStatus_Good_FailedExitCode(t *testing.T) {
dir := t.TempDir()
status, question := detectFinalStatus(dir, 1, "completed")
assert.Equal(t, "failed", status)
assert.Contains(t, question, "code 1")
}
func TestDetectFinalStatus_Good_FailedKilled(t *testing.T) {
dir := t.TempDir()
status, _ := detectFinalStatus(dir, 0, "killed")
assert.Equal(t, "failed", status)
}
func TestDetectFinalStatus_Good_FailedStatus(t *testing.T) {
dir := t.TempDir()
status, _ := detectFinalStatus(dir, 0, "failed")
assert.Equal(t, "failed", status)
}
func TestDetectFinalStatus_Good_BlockedTakesPrecedence(t *testing.T) {
dir := t.TempDir()
// Agent wrote BLOCKED.md AND exited non-zero — blocked takes precedence
os.WriteFile(filepath.Join(dir, "BLOCKED.md"), []byte("Need help"), 0o644)
status, question := detectFinalStatus(dir, 1, "failed")
assert.Equal(t, "blocked", status)
assert.Equal(t, "Need help", question)
}
// --- trackFailureRate ---
func TestTrackFailureRate_Good_SuccessResetsCount(t *testing.T) {
s := &PrepSubsystem{
backoff: make(map[string]time.Time),
failCount: map[string]int{"codex": 2},
}
triggered := s.trackFailureRate("codex", "completed", time.Now().Add(-10*time.Second))
assert.False(t, triggered)
assert.Equal(t, 0, s.failCount["codex"])
}
func TestTrackFailureRate_Good_SlowFailureResetsCount(t *testing.T) {
s := &PrepSubsystem{
backoff: make(map[string]time.Time),
failCount: map[string]int{"codex": 2},
}
// Started 5 minutes ago = slow failure
triggered := s.trackFailureRate("codex", "failed", time.Now().Add(-5*time.Minute))
assert.False(t, triggered)
assert.Equal(t, 0, s.failCount["codex"])
}
func TestTrackFailureRate_Good_FastFailureIncrementsCount(t *testing.T) {
s := &PrepSubsystem{
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
// Started 10 seconds ago = fast failure
triggered := s.trackFailureRate("codex", "failed", time.Now().Add(-10*time.Second))
assert.False(t, triggered)
assert.Equal(t, 1, s.failCount["codex"])
}
func TestTrackFailureRate_Good_ThreeFailsTriggersBackoff(t *testing.T) {
s := &PrepSubsystem{
backoff: make(map[string]time.Time),
failCount: map[string]int{"codex": 2}, // already 2 fast failures
}
triggered := s.trackFailureRate("codex", "failed", time.Now().Add(-10*time.Second))
assert.True(t, triggered)
assert.True(t, time.Now().Before(s.backoff["codex"]))
}
func TestTrackFailureRate_Good_ModelVariantUsesPool(t *testing.T) {
s := &PrepSubsystem{
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
s.trackFailureRate("codex:gpt-5.4", "failed", time.Now().Add(-10*time.Second))
assert.Equal(t, 1, s.failCount["codex"], "should track by base agent pool")
}
// --- startIssueTracking / stopIssueTracking ---
func TestStartIssueTracking_Good_NoForge(t *testing.T) {
s := &PrepSubsystem{
forge: nil,
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
s.startIssueTracking(t.TempDir())
}
func TestStopIssueTracking_Good_NoForge(t *testing.T) {
s := &PrepSubsystem{
forge: nil,
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
s.stopIssueTracking(t.TempDir())
}
func TestStartIssueTracking_Good_NoIssue(t *testing.T) {
dir := t.TempDir()
st := &WorkspaceStatus{Status: "running", Repo: "test"}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(dir, "status.json"), data, 0o644)
s := &PrepSubsystem{
forge: nil,
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
s.startIssueTracking(dir)
}
func TestStartIssueTracking_Good_NoStatusFile(t *testing.T) {
s := &PrepSubsystem{
forge: nil,
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
// No status.json — should return early
s.startIssueTracking(t.TempDir())
}
func TestStartIssueTracking_Good_WithForge(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(201) // Forge stopwatch start returns 201
}))
t.Cleanup(srv.Close)
dir := t.TempDir()
st := &WorkspaceStatus{Status: "running", Repo: "go-io", Org: "core", Issue: 15}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(dir, "status.json"), data, 0o644)
s := &PrepSubsystem{
forge: forge.NewForge(srv.URL, "test-token"),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
s.startIssueTracking(dir)
}
func TestStopIssueTracking_Good_WithForge(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(204)
}))
t.Cleanup(srv.Close)
dir := t.TempDir()
st := &WorkspaceStatus{Status: "completed", Repo: "go-io", Issue: 10}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(dir, "status.json"), data, 0o644)
s := &PrepSubsystem{
forge: forge.NewForge(srv.URL, "test-token"),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
s.stopIssueTracking(dir)
}
// --- broadcastStart / broadcastComplete ---
func TestBroadcastStart_Good_NoCore(t *testing.T) {
dir := t.TempDir()
t.Setenv("CORE_WORKSPACE", dir)
s := &PrepSubsystem{
core: nil,
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
s.broadcastStart("codex", dir)
}
func TestBroadcastStart_Good_WithCore(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsDir := filepath.Join(root, "workspace", "ws-test")
os.MkdirAll(wsDir, 0o755)
st := &WorkspaceStatus{Repo: "go-io", Agent: "codex"}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(wsDir, "status.json"), data, 0o644)
c := core.New()
s := &PrepSubsystem{
core: c,
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
s.broadcastStart("codex", wsDir)
}
func TestBroadcastComplete_Good_NoCore(t *testing.T) {
dir := t.TempDir()
t.Setenv("CORE_WORKSPACE", dir)
s := &PrepSubsystem{
core: nil,
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
s.broadcastComplete("codex", dir, "completed")
}
func TestBroadcastComplete_Good_WithCore(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsDir := filepath.Join(root, "workspace", "ws-test")
os.MkdirAll(wsDir, 0o755)
st := &WorkspaceStatus{Repo: "go-io", Agent: "codex"}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(wsDir, "status.json"), data, 0o644)
c := core.New()
s := &PrepSubsystem{
core: c,
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
s.broadcastComplete("codex", wsDir, "completed")
}
// --- onAgentComplete ---
func TestOnAgentComplete_Good_Completed(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsDir := filepath.Join(root, "ws-test")
repoDir := filepath.Join(wsDir, "repo")
metaDir := filepath.Join(wsDir, ".meta")
os.MkdirAll(repoDir, 0o755)
os.MkdirAll(metaDir, 0o755)
// Write initial status
st := &WorkspaceStatus{Status: "running", Repo: "go-io", Agent: "codex", StartedAt: time.Now()}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(wsDir, "status.json"), data, 0o644)
s := &PrepSubsystem{
core: nil,
forge: nil,
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
outputFile := filepath.Join(metaDir, "agent-codex.log")
s.onAgentComplete("codex", wsDir, outputFile, 0, "completed", "test output")
// Verify status was updated
updated, err := ReadStatus(wsDir)
assert.NoError(t, err)
assert.Equal(t, "completed", updated.Status)
assert.Equal(t, 0, updated.PID)
assert.Empty(t, updated.Question)
// Verify output was written
content, _ := os.ReadFile(outputFile)
assert.Equal(t, "test output", string(content))
}
func TestOnAgentComplete_Good_Failed(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsDir := filepath.Join(root, "ws-fail")
repoDir := filepath.Join(wsDir, "repo")
metaDir := filepath.Join(wsDir, ".meta")
os.MkdirAll(repoDir, 0o755)
os.MkdirAll(metaDir, 0o755)
st := &WorkspaceStatus{Status: "running", Repo: "go-io", Agent: "codex", StartedAt: time.Now()}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(wsDir, "status.json"), data, 0o644)
s := &PrepSubsystem{
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
s.onAgentComplete("codex", wsDir, filepath.Join(metaDir, "agent-codex.log"), 1, "failed", "error output")
updated, _ := ReadStatus(wsDir)
assert.Equal(t, "failed", updated.Status)
assert.Contains(t, updated.Question, "code 1")
}
func TestOnAgentComplete_Good_Blocked(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsDir := filepath.Join(root, "ws-blocked")
repoDir := filepath.Join(wsDir, "repo")
metaDir := filepath.Join(wsDir, ".meta")
os.MkdirAll(repoDir, 0o755)
os.MkdirAll(metaDir, 0o755)
// Create BLOCKED.md
os.WriteFile(filepath.Join(repoDir, "BLOCKED.md"), []byte("Need credentials"), 0o644)
st := &WorkspaceStatus{Status: "running", Repo: "go-io", Agent: "codex", StartedAt: time.Now()}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(wsDir, "status.json"), data, 0o644)
s := &PrepSubsystem{
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
s.onAgentComplete("codex", wsDir, filepath.Join(metaDir, "agent-codex.log"), 0, "completed", "")
updated, _ := ReadStatus(wsDir)
assert.Equal(t, "blocked", updated.Status)
assert.Equal(t, "Need credentials", updated.Question)
}
func TestOnAgentComplete_Good_EmptyOutput(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsDir := filepath.Join(root, "ws-empty")
repoDir := filepath.Join(wsDir, "repo")
metaDir := filepath.Join(wsDir, ".meta")
os.MkdirAll(repoDir, 0o755)
os.MkdirAll(metaDir, 0o755)
st := &WorkspaceStatus{Status: "running", Repo: "test", Agent: "codex", StartedAt: time.Now()}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(wsDir, "status.json"), data, 0o644)
s := &PrepSubsystem{
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
outputFile := filepath.Join(metaDir, "agent-codex.log")
s.onAgentComplete("codex", wsDir, outputFile, 0, "completed", "")
// Output file should NOT be created for empty output
_, err := os.Stat(outputFile)
assert.True(t, os.IsNotExist(err))
}

View file

@ -7,295 +7,485 @@ import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"testing"
"time"
core "dappco.re/go/core"
"dappco.re/go/core/forge"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// --- dispatch (validation) ---
// --- agentCommand ---
func TestDispatch_Bad_NoRepo(t *testing.T) {
s := &PrepSubsystem{
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
// Good: tested in logic_test.go (TestAgentCommand_Good_*)
// Bad: tested in logic_test.go (TestAgentCommand_Bad_Unknown)
// Ugly: tested in logic_test.go (TestAgentCommand_Ugly_EmptyAgent)
_, _, err := s.dispatch(context.Background(), nil, DispatchInput{
Task: "Fix the bug",
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "repo is required")
// --- containerCommand ---
// Good: tested in logic_test.go (TestContainerCommand_Good_*)
// --- agentOutputFile ---
func TestDispatch_AgentOutputFile_Good(t *testing.T) {
assert.Contains(t, agentOutputFile("/ws", "codex"), ".meta/agent-codex.log")
assert.Contains(t, agentOutputFile("/ws", "claude:opus"), ".meta/agent-claude.log")
assert.Contains(t, agentOutputFile("/ws", "gemini:flash"), ".meta/agent-gemini.log")
}
func TestDispatch_Bad_NoTask(t *testing.T) {
s := &PrepSubsystem{
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
_, _, err := s.dispatch(context.Background(), nil, DispatchInput{
Repo: "go-io",
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "task is required")
func TestDispatch_AgentOutputFile_Bad(t *testing.T) {
// Empty agent — still produces a path (no crash)
result := agentOutputFile("/ws", "")
assert.Contains(t, result, ".meta/agent-.log")
}
func TestDispatch_Good_DefaultsApplied(t *testing.T) {
// We can't test full dispatch without Docker, but we can verify defaults
// by using DryRun and checking the workspace prep
func TestDispatch_AgentOutputFile_Ugly(t *testing.T) {
// Agent with multiple colons — only splits on first
result := agentOutputFile("/ws", "claude:opus:latest")
assert.Contains(t, result, "agent-claude.log")
}
// --- detectFinalStatus ---
func TestDispatch_DetectFinalStatus_Good(t *testing.T) {
dir := t.TempDir()
// Clean exit = completed
status, question := detectFinalStatus(dir, 0, "completed")
assert.Equal(t, "completed", status)
assert.Empty(t, question)
}
func TestDispatch_DetectFinalStatus_Bad(t *testing.T) {
dir := t.TempDir()
// Non-zero exit code
status, question := detectFinalStatus(dir, 1, "completed")
assert.Equal(t, "failed", status)
assert.Contains(t, question, "code 1")
// Process killed
status2, _ := detectFinalStatus(dir, 0, "killed")
assert.Equal(t, "failed", status2)
// Process status "failed"
status3, _ := detectFinalStatus(dir, 0, "failed")
assert.Equal(t, "failed", status3)
}
func TestDispatch_DetectFinalStatus_Ugly(t *testing.T) {
dir := t.TempDir()
// BLOCKED.md exists but is whitespace only — NOT blocked
os.WriteFile(filepath.Join(dir, "BLOCKED.md"), []byte(" \n "), 0o644)
status, _ := detectFinalStatus(dir, 0, "completed")
assert.Equal(t, "completed", status)
// BLOCKED.md takes precedence over non-zero exit
os.WriteFile(filepath.Join(dir, "BLOCKED.md"), []byte("Need credentials"), 0o644)
status2, question2 := detectFinalStatus(dir, 1, "failed")
assert.Equal(t, "blocked", status2)
assert.Equal(t, "Need credentials", question2)
}
// --- trackFailureRate ---
func TestDispatch_TrackFailureRate_Good(t *testing.T) {
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: map[string]int{"codex": 2}}
// Success resets count
triggered := s.trackFailureRate("codex", "completed", time.Now().Add(-10*time.Second))
assert.False(t, triggered)
assert.Equal(t, 0, s.failCount["codex"])
}
func TestDispatch_TrackFailureRate_Bad(t *testing.T) {
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: map[string]int{"codex": 2}}
// 3rd fast failure triggers backoff
triggered := s.trackFailureRate("codex", "failed", time.Now().Add(-10*time.Second))
assert.True(t, triggered)
assert.True(t, time.Now().Before(s.backoff["codex"]))
}
func TestDispatch_TrackFailureRate_Ugly(t *testing.T) {
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
// Slow failure (>60s) resets count instead of incrementing
s.failCount["codex"] = 2
s.trackFailureRate("codex", "failed", time.Now().Add(-5*time.Minute))
assert.Equal(t, 0, s.failCount["codex"])
// Model variant tracks by base pool
s.trackFailureRate("codex:gpt-5.4", "failed", time.Now().Add(-10*time.Second))
assert.Equal(t, 1, s.failCount["codex"])
}
// --- startIssueTracking ---
func TestDispatch_StartIssueTracking_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(201)
}))
t.Cleanup(srv.Close)
dir := t.TempDir()
st := &WorkspaceStatus{Status: "running", Repo: "go-io", Org: "core", Issue: 15}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(dir, "status.json"), data, 0o644)
s := &PrepSubsystem{forge: forge.NewForge(srv.URL, "tok"), backoff: make(map[string]time.Time), failCount: make(map[string]int)}
s.startIssueTracking(dir)
}
func TestDispatch_StartIssueTracking_Bad(t *testing.T) {
// No forge — returns early
s := &PrepSubsystem{forge: nil, backoff: make(map[string]time.Time), failCount: make(map[string]int)}
s.startIssueTracking(t.TempDir())
// No status file
s2 := &PrepSubsystem{forge: nil, backoff: make(map[string]time.Time), failCount: make(map[string]int)}
s2.startIssueTracking(t.TempDir())
}
func TestDispatch_StartIssueTracking_Ugly(t *testing.T) {
// Status has no issue — early return
dir := t.TempDir()
st := &WorkspaceStatus{Status: "running", Repo: "test"}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(dir, "status.json"), data, 0o644)
s := &PrepSubsystem{forge: forge.NewForge("http://invalid", "tok"), backoff: make(map[string]time.Time), failCount: make(map[string]int)}
s.startIssueTracking(dir) // no issue → skips API call
}
// --- stopIssueTracking ---
func TestDispatch_StopIssueTracking_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(204)
}))
t.Cleanup(srv.Close)
dir := t.TempDir()
st := &WorkspaceStatus{Status: "completed", Repo: "go-io", Issue: 10}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(dir, "status.json"), data, 0o644)
s := &PrepSubsystem{forge: forge.NewForge(srv.URL, "tok"), backoff: make(map[string]time.Time), failCount: make(map[string]int)}
s.stopIssueTracking(dir)
}
func TestDispatch_StopIssueTracking_Bad(t *testing.T) {
s := &PrepSubsystem{forge: nil, backoff: make(map[string]time.Time), failCount: make(map[string]int)}
s.stopIssueTracking(t.TempDir())
}
func TestDispatch_StopIssueTracking_Ugly(t *testing.T) {
// Status has no issue
dir := t.TempDir()
st := &WorkspaceStatus{Status: "completed", Repo: "test"}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(dir, "status.json"), data, 0o644)
s := &PrepSubsystem{forge: forge.NewForge("http://invalid", "tok"), backoff: make(map[string]time.Time), failCount: make(map[string]int)}
s.stopIssueTracking(dir)
}
// --- broadcastStart ---
func TestDispatch_BroadcastStart_Good(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
// Mock forge server for issue fetching
forgeSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(map[string]any{
"title": "Test issue",
"body": "Fix the thing",
})
}))
t.Cleanup(forgeSrv.Close)
wsDir := filepath.Join(root, "workspace", "ws-test")
os.MkdirAll(wsDir, 0o755)
data, _ := json.Marshal(WorkspaceStatus{Repo: "go-io", Agent: "codex"})
os.WriteFile(filepath.Join(wsDir, "status.json"), data, 0o644)
// Create source repo to clone from
srcRepo := filepath.Join(t.TempDir(), "core", "go-io")
require.NoError(t, exec.Command("git", "init", "-b", "main", srcRepo).Run())
gitCmd := exec.Command("git", "config", "user.name", "Test")
gitCmd.Dir = srcRepo
gitCmd.Run()
gitCmd = exec.Command("git", "config", "user.email", "test@test.com")
gitCmd.Dir = srcRepo
gitCmd.Run()
require.True(t, fs.Write(filepath.Join(srcRepo, "go.mod"), "module test\n\ngo 1.22").OK)
gitCmd = exec.Command("git", "add", ".")
gitCmd.Dir = srcRepo
gitCmd.Run()
gitCmd = exec.Command("git", "commit", "-m", "init")
gitCmd.Dir = srcRepo
gitCmd.Env = append(gitCmd.Environ(),
"GIT_AUTHOR_NAME=Test",
"GIT_AUTHOR_EMAIL=test@test.com",
"GIT_COMMITTER_NAME=Test",
"GIT_COMMITTER_EMAIL=test@test.com",
)
gitCmd.Run()
c := core.New()
s := &PrepSubsystem{core: c, backoff: make(map[string]time.Time), failCount: make(map[string]int)}
s.broadcastStart("codex", wsDir)
}
s := &PrepSubsystem{
forge: forge.NewForge(forgeSrv.URL, "test-token"),
codePath: filepath.Dir(filepath.Dir(srcRepo)), // parent of core/go-io
client: forgeSrv.Client(),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
func TestDispatch_BroadcastStart_Bad(t *testing.T) {
// No Core — should not panic
s := &PrepSubsystem{core: nil, backoff: make(map[string]time.Time), failCount: make(map[string]int)}
s.broadcastStart("codex", t.TempDir())
}
_, out, err := s.dispatch(context.Background(), nil, DispatchInput{
Repo: "go-io",
Task: "Fix stuff",
Issue: 42,
DryRun: true,
})
func TestDispatch_BroadcastStart_Ugly(t *testing.T) {
// No status file — broadcasts with empty repo
c := core.New()
s := &PrepSubsystem{core: c, backoff: make(map[string]time.Time), failCount: make(map[string]int)}
s.broadcastStart("codex", t.TempDir())
}
// --- broadcastComplete ---
func TestDispatch_BroadcastComplete_Good(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsDir := filepath.Join(root, "workspace", "ws-test")
os.MkdirAll(wsDir, 0o755)
data, _ := json.Marshal(WorkspaceStatus{Repo: "go-io", Agent: "codex"})
os.WriteFile(filepath.Join(wsDir, "status.json"), data, 0o644)
c := core.New()
s := &PrepSubsystem{core: c, backoff: make(map[string]time.Time), failCount: make(map[string]int)}
s.broadcastComplete("codex", wsDir, "completed")
}
func TestDispatch_BroadcastComplete_Bad(t *testing.T) {
s := &PrepSubsystem{core: nil, backoff: make(map[string]time.Time), failCount: make(map[string]int)}
s.broadcastComplete("codex", t.TempDir(), "failed")
}
func TestDispatch_BroadcastComplete_Ugly(t *testing.T) {
// No status file
c := core.New()
s := &PrepSubsystem{core: c, backoff: make(map[string]time.Time), failCount: make(map[string]int)}
s.broadcastComplete("codex", t.TempDir(), "completed")
}
// --- onAgentComplete ---
func TestDispatch_OnAgentComplete_Good(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsDir := filepath.Join(root, "ws-test")
repoDir := filepath.Join(wsDir, "repo")
metaDir := filepath.Join(wsDir, ".meta")
os.MkdirAll(repoDir, 0o755)
os.MkdirAll(metaDir, 0o755)
st := &WorkspaceStatus{Status: "running", Repo: "go-io", Agent: "codex", StartedAt: time.Now()}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(wsDir, "status.json"), data, 0o644)
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
outputFile := filepath.Join(metaDir, "agent-codex.log")
s.onAgentComplete("codex", wsDir, outputFile, 0, "completed", "test output")
updated, err := ReadStatus(wsDir)
require.NoError(t, err)
assert.True(t, out.Success)
assert.Equal(t, "codex", out.Agent) // default agent
assert.Equal(t, "go-io", out.Repo)
assert.NotEmpty(t, out.WorkspaceDir)
assert.NotEmpty(t, out.Prompt)
assert.Equal(t, "completed", updated.Status)
assert.Equal(t, 0, updated.PID)
content, _ := os.ReadFile(outputFile)
assert.Equal(t, "test output", string(content))
}
func TestDispatch_OnAgentComplete_Bad(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsDir := filepath.Join(root, "ws-fail")
repoDir := filepath.Join(wsDir, "repo")
metaDir := filepath.Join(wsDir, ".meta")
os.MkdirAll(repoDir, 0o755)
os.MkdirAll(metaDir, 0o755)
st := &WorkspaceStatus{Status: "running", Repo: "go-io", Agent: "codex", StartedAt: time.Now()}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(wsDir, "status.json"), data, 0o644)
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
s.onAgentComplete("codex", wsDir, filepath.Join(metaDir, "agent-codex.log"), 1, "failed", "error")
updated, _ := ReadStatus(wsDir)
assert.Equal(t, "failed", updated.Status)
assert.Contains(t, updated.Question, "code 1")
}
func TestDispatch_OnAgentComplete_Ugly(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsDir := filepath.Join(root, "ws-blocked")
repoDir := filepath.Join(wsDir, "repo")
metaDir := filepath.Join(wsDir, ".meta")
os.MkdirAll(repoDir, 0o755)
os.MkdirAll(metaDir, 0o755)
os.WriteFile(filepath.Join(repoDir, "BLOCKED.md"), []byte("Need credentials"), 0o644)
st := &WorkspaceStatus{Status: "running", Repo: "go-io", Agent: "codex", StartedAt: time.Now()}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(wsDir, "status.json"), data, 0o644)
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
s.onAgentComplete("codex", wsDir, filepath.Join(metaDir, "agent-codex.log"), 0, "completed", "")
updated, _ := ReadStatus(wsDir)
assert.Equal(t, "blocked", updated.Status)
assert.Equal(t, "Need credentials", updated.Question)
// Empty output should NOT create log file
_, err := os.Stat(filepath.Join(metaDir, "agent-codex.log"))
assert.True(t, os.IsNotExist(err))
}
// --- runQA ---
func TestRunQA_Good_GoProject(t *testing.T) {
// Create a minimal valid Go project
func TestDispatch_RunQA_Good(t *testing.T) {
wsDir := t.TempDir()
repoDir := filepath.Join(wsDir, "repo")
require.True(t, fs.EnsureDir(repoDir).OK)
os.MkdirAll(repoDir, 0o755)
os.WriteFile(filepath.Join(repoDir, "go.mod"), []byte("module testmod\n\ngo 1.22\n"), 0o644)
os.WriteFile(filepath.Join(repoDir, "main.go"), []byte("package main\nfunc main() {}\n"), 0o644)
require.True(t, fs.Write(filepath.Join(repoDir, "go.mod"), "module testmod\n\ngo 1.22\n").OK)
require.True(t, fs.Write(filepath.Join(repoDir, "main.go"), "package main\n\nfunc main() {}\n").OK)
s := &PrepSubsystem{
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
// go build, go vet, go test should all pass on this minimal project
result := s.runQA(wsDir)
assert.True(t, result)
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
assert.True(t, s.runQA(wsDir))
}
func TestRunQA_Bad_GoBrokenCode(t *testing.T) {
func TestDispatch_RunQA_Bad(t *testing.T) {
wsDir := t.TempDir()
repoDir := filepath.Join(wsDir, "repo")
require.True(t, fs.EnsureDir(repoDir).OK)
os.MkdirAll(repoDir, 0o755)
require.True(t, fs.Write(filepath.Join(repoDir, "go.mod"), "module testmod\n\ngo 1.22\n").OK)
// Deliberately broken Go code — won't compile
require.True(t, fs.Write(filepath.Join(repoDir, "main.go"), "package main\n\nfunc main( {\n}\n").OK)
// Broken Go code
os.WriteFile(filepath.Join(repoDir, "go.mod"), []byte("module testmod\n\ngo 1.22\n"), 0o644)
os.WriteFile(filepath.Join(repoDir, "main.go"), []byte("package main\nfunc main( {\n}\n"), 0o644)
s := &PrepSubsystem{
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
assert.False(t, s.runQA(wsDir))
result := s.runQA(wsDir)
assert.False(t, result)
// PHP project — composer not available
wsDir2 := t.TempDir()
repoDir2 := filepath.Join(wsDir2, "repo")
os.MkdirAll(repoDir2, 0o755)
os.WriteFile(filepath.Join(repoDir2, "composer.json"), []byte(`{"name":"test"}`), 0o644)
assert.False(t, s.runQA(wsDir2))
}
func TestRunQA_Good_UnknownLanguage(t *testing.T) {
// No go.mod, composer.json, or package.json → passes QA (no checks)
func TestDispatch_RunQA_Ugly(t *testing.T) {
// Unknown language — passes QA (no checks)
wsDir := t.TempDir()
repoDir := filepath.Join(wsDir, "repo")
require.True(t, fs.EnsureDir(repoDir).OK)
os.MkdirAll(filepath.Join(wsDir, "repo"), 0o755)
s := &PrepSubsystem{
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
assert.True(t, s.runQA(wsDir))
result := s.runQA(wsDir)
assert.True(t, result)
// Go vet failure (compiles but bad printf)
wsDir2 := t.TempDir()
repoDir2 := filepath.Join(wsDir2, "repo")
os.MkdirAll(repoDir2, 0o755)
os.WriteFile(filepath.Join(repoDir2, "go.mod"), []byte("module testmod\n\ngo 1.22\n"), 0o644)
os.WriteFile(filepath.Join(repoDir2, "main.go"), []byte("package main\nimport \"fmt\"\nfunc main() { fmt.Printf(\"%d\", \"x\") }\n"), 0o644)
assert.False(t, s.runQA(wsDir2))
// Node project — npm install likely fails
wsDir3 := t.TempDir()
repoDir3 := filepath.Join(wsDir3, "repo")
os.MkdirAll(repoDir3, 0o755)
os.WriteFile(filepath.Join(repoDir3, "package.json"), []byte(`{"name":"test","scripts":{"test":"echo ok"}}`), 0o644)
_ = s.runQA(wsDir3) // exercises the node path
}
func TestRunQA_Good_GoVetFailure(t *testing.T) {
wsDir := t.TempDir()
repoDir := filepath.Join(wsDir, "repo")
require.True(t, fs.EnsureDir(repoDir).OK)
// --- dispatch ---
require.True(t, fs.Write(filepath.Join(repoDir, "go.mod"), "module testmod\n\ngo 1.22\n").OK)
// Code that compiles but has a vet issue (unreachable code after return)
code := `package main
func TestDispatch_Dispatch_Good(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
import "fmt"
forgeSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(map[string]any{"title": "Issue", "body": "Fix"})
}))
t.Cleanup(forgeSrv.Close)
func main() {
fmt.Printf("%d", "not a number")
}
`
require.True(t, fs.Write(filepath.Join(repoDir, "main.go"), code).OK)
srcRepo := filepath.Join(t.TempDir(), "core", "go-io")
exec.Command("git", "init", "-b", "main", srcRepo).Run()
exec.Command("git", "-C", srcRepo, "config", "user.name", "T").Run()
exec.Command("git", "-C", srcRepo, "config", "user.email", "t@t.com").Run()
os.WriteFile(filepath.Join(srcRepo, "go.mod"), []byte("module test\ngo 1.22\n"), 0o644)
exec.Command("git", "-C", srcRepo, "add", ".").Run()
exec.Command("git", "-C", srcRepo, "commit", "-m", "init").Run()
s := &PrepSubsystem{
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
forge: forge.NewForge(forgeSrv.URL, "tok"), codePath: filepath.Dir(filepath.Dir(srcRepo)),
client: forgeSrv.Client(), backoff: make(map[string]time.Time), failCount: make(map[string]int),
}
result := s.runQA(wsDir)
// go vet should catch the Printf format mismatch
assert.False(t, result)
_, out, err := s.dispatch(context.Background(), nil, DispatchInput{
Repo: "go-io", Task: "Fix stuff", Issue: 42, DryRun: true,
})
require.NoError(t, err)
assert.True(t, out.Success)
assert.Equal(t, "codex", out.Agent)
assert.NotEmpty(t, out.Prompt)
}
func TestDispatch_Dispatch_Bad(t *testing.T) {
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
// No repo
_, _, err := s.dispatch(context.Background(), nil, DispatchInput{Task: "do"})
assert.Error(t, err)
assert.Contains(t, err.Error(), "repo is required")
// No task
_, _, err = s.dispatch(context.Background(), nil, DispatchInput{Repo: "go-io"})
assert.Error(t, err)
assert.Contains(t, err.Error(), "task is required")
}
func TestDispatch_Dispatch_Ugly(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
// Prep fails (no local clone)
s := &PrepSubsystem{codePath: t.TempDir(), backoff: make(map[string]time.Time), failCount: make(map[string]int)}
_, _, err := s.dispatch(context.Background(), nil, DispatchInput{
Repo: "nonexistent", Task: "do", Issue: 1,
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "prep workspace failed")
}
// --- workspaceDir ---
func TestWorkspaceDir_Good_Issue(t *testing.T) {
func TestDispatch_WorkspaceDir_Good(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
dir, err := workspaceDir("core", "go-io", PrepInput{Issue: 42})
require.NoError(t, err)
assert.Contains(t, dir, "task-42")
dir2, _ := workspaceDir("core", "go-io", PrepInput{PR: 7})
assert.Contains(t, dir2, "pr-7")
dir3, _ := workspaceDir("core", "go-io", PrepInput{Branch: "feat/new"})
assert.Contains(t, dir3, "feat/new")
dir4, _ := workspaceDir("core", "go-io", PrepInput{Tag: "v1.0.0"})
assert.Contains(t, dir4, "v1.0.0")
}
func TestWorkspaceDir_Good_PR(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
dir, err := workspaceDir("core", "go-io", PrepInput{PR: 7})
require.NoError(t, err)
assert.Contains(t, dir, "pr-7")
}
func TestWorkspaceDir_Good_Branch(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
dir, err := workspaceDir("core", "go-io", PrepInput{Branch: "feature/new-api"})
require.NoError(t, err)
assert.Contains(t, dir, "feature/new-api")
}
func TestWorkspaceDir_Good_Tag(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
dir, err := workspaceDir("core", "go-io", PrepInput{Tag: "v1.0.0"})
require.NoError(t, err)
assert.Contains(t, dir, "v1.0.0")
}
func TestWorkspaceDir_Bad_NoIdentifier(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
func TestDispatch_WorkspaceDir_Bad(t *testing.T) {
_, err := workspaceDir("core", "go-io", PrepInput{})
assert.Error(t, err)
assert.Contains(t, err.Error(), "one of issue, pr, branch, or tag is required")
assert.Contains(t, err.Error(), "one of issue, pr, branch, or tag")
}
// --- DispatchInput defaults ---
func TestDispatch_WorkspaceDir_Ugly(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
func TestDispatchInput_Good_Defaults(t *testing.T) {
input := DispatchInput{
Repo: "go-io",
Task: "Fix it",
}
// Verify default values are empty until dispatch applies them
assert.Empty(t, input.Org)
assert.Empty(t, input.Agent)
assert.Empty(t, input.Template)
}
// --- buildPRBody ---
func TestBuildPRBody_Good_AllFields(t *testing.T) {
s := &PrepSubsystem{}
st := &WorkspaceStatus{
Task: "Implement new feature",
Agent: "claude",
Issue: 15,
Branch: "agent/implement-new-feature",
Runs: 3,
}
body := s.buildPRBody(st)
assert.Contains(t, body, "Implement new feature")
assert.Contains(t, body, "Closes #15")
assert.Contains(t, body, "**Agent:** claude")
assert.Contains(t, body, "**Runs:** 3")
}
func TestBuildPRBody_Good_NoIssue(t *testing.T) {
s := &PrepSubsystem{}
st := &WorkspaceStatus{
Task: "Refactor internals",
Agent: "codex",
Runs: 1,
}
body := s.buildPRBody(st)
assert.Contains(t, body, "Refactor internals")
assert.NotContains(t, body, "Closes #")
}
func TestBuildPRBody_Bad_EmptyStatus(t *testing.T) {
s := &PrepSubsystem{}
st := &WorkspaceStatus{}
body := s.buildPRBody(st)
// Should still produce valid markdown, just with empty fields
assert.Contains(t, body, "## Summary")
// PR takes precedence when multiple set (first match)
dir, err := workspaceDir("core", "go-io", PrepInput{PR: 3, Issue: 5})
require.NoError(t, err)
assert.Contains(t, dir, "pr-3")
}
// --- canDispatchAgent ---
func TestCanDispatchAgent_Good_NoLimitsConfigured(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),
}
// No config, no running agents — should allow dispatch
assert.True(t, s.canDispatchAgent("claude"))
}
// Good: tested in queue_test.go
// Bad: tested in queue_test.go
// Ugly: see queue_extra_test.go

View file

@ -1,446 +0,0 @@
// SPDX-License-Identifier: EUPL-1.2
// Edge-case tests to push partially covered functions toward 80%+.
package agentic
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"testing"
"time"
core "dappco.re/go/core"
"dappco.re/go/core/forge"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// --- autoCreatePR ---
func TestAutoCreatePR_Bad_NoStatus(t *testing.T) {
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
s.autoCreatePR(t.TempDir()) // should not panic
}
func TestAutoCreatePR_Bad_EmptyBranch(t *testing.T) {
dir := t.TempDir()
st := &WorkspaceStatus{Status: "completed", Repo: "test", Branch: ""}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(dir, "status.json"), data, 0o644)
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
s.autoCreatePR(dir)
}
func TestAutoCreatePR_Bad_EmptyRepo(t *testing.T) {
dir := t.TempDir()
st := &WorkspaceStatus{Status: "completed", Branch: "agent/fix"}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(dir, "status.json"), data, 0o644)
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
s.autoCreatePR(dir)
}
func TestAutoCreatePR_Bad_NoCommits(t *testing.T) {
dir := t.TempDir()
repoDir := filepath.Join(dir, "repo")
os.MkdirAll(repoDir, 0o755)
// Init a real git repo with a commit
exec.Command("git", "init", repoDir).Run()
exec.Command("git", "-C", repoDir, "config", "user.email", "test@test.com").Run()
exec.Command("git", "-C", repoDir, "config", "user.name", "Test").Run()
os.WriteFile(filepath.Join(repoDir, "f.txt"), []byte("hi"), 0o644)
exec.Command("git", "-C", repoDir, "add", ".").Run()
exec.Command("git", "-C", repoDir, "commit", "-m", "init").Run()
exec.Command("git", "-C", repoDir, "checkout", "-b", "dev").Run()
st := &WorkspaceStatus{Status: "completed", Repo: "test", Branch: "dev", Agent: "codex"}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(dir, "status.json"), data, 0o644)
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
s.autoCreatePR(dir) // no commits ahead → early return
}
// --- createPR ---
func TestCreatePR_Bad_MissingWorkspace(t *testing.T) {
s := &PrepSubsystem{forgeToken: "tok", backoff: make(map[string]time.Time), failCount: make(map[string]int)}
_, _, err := s.createPR(context.Background(), nil, CreatePRInput{})
assert.Error(t, err)
assert.Contains(t, err.Error(), "workspace is required")
}
func TestCreatePR_Bad_NoForgeToken(t *testing.T) {
s := &PrepSubsystem{forgeToken: "", backoff: make(map[string]time.Time), failCount: make(map[string]int)}
_, _, err := s.createPR(context.Background(), nil, CreatePRInput{Workspace: "ws"})
assert.Error(t, err)
assert.Contains(t, err.Error(), "no Forge token")
}
func TestCreatePR_Bad_NoStatusFile(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsRoot := filepath.Join(root, "workspace")
ws := filepath.Join(wsRoot, "ws-nostatus")
repoDir := filepath.Join(ws, "repo")
os.MkdirAll(repoDir, 0o755)
exec.Command("git", "init", repoDir).Run()
s := &PrepSubsystem{forgeToken: "tok", backoff: make(map[string]time.Time), failCount: make(map[string]int)}
_, _, err := s.createPR(context.Background(), nil, CreatePRInput{Workspace: "ws-nostatus"})
assert.Error(t, err)
assert.Contains(t, err.Error(), "no status")
}
func TestCreatePR_Good_DryRunNoBranch(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsRoot := filepath.Join(root, "workspace")
ws := filepath.Join(wsRoot, "ws-nobranch")
repoDir := filepath.Join(ws, "repo")
os.MkdirAll(repoDir, 0o755)
// Init git with a commit so rev-parse works
exec.Command("git", "init", "-b", "agent-test", repoDir).Run()
exec.Command("git", "-C", repoDir, "config", "user.email", "t@t.com").Run()
exec.Command("git", "-C", repoDir, "config", "user.name", "T").Run()
os.WriteFile(filepath.Join(repoDir, "f.txt"), []byte("x"), 0o644)
exec.Command("git", "-C", repoDir, "add", ".").Run()
exec.Command("git", "-C", repoDir, "commit", "-m", "init").Run()
// Status has no branch — createPR should detect from git
st := &WorkspaceStatus{Status: "completed", Repo: "go-io", Task: "Fix", Agent: "codex"}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(ws, "status.json"), data, 0o644)
s := &PrepSubsystem{forgeToken: "tok", backoff: make(map[string]time.Time), failCount: make(map[string]int)}
_, out, err := s.createPR(context.Background(), nil, CreatePRInput{
Workspace: "ws-nobranch",
DryRun: true,
})
require.NoError(t, err)
assert.True(t, out.Success)
assert.Equal(t, "agent-test", out.Branch)
}
func TestCreatePR_Good_DryRunDefaultTitle(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsRoot := filepath.Join(root, "workspace")
ws := filepath.Join(wsRoot, "ws-notitle")
repoDir := filepath.Join(ws, "repo")
os.MkdirAll(repoDir, 0o755)
exec.Command("git", "init", repoDir).Run()
// Status with no Task — title defaults to branch name
st := &WorkspaceStatus{Status: "completed", Repo: "go-io", Branch: "agent/fix", Agent: "codex"}
data, _ := json.Marshal(st)
os.WriteFile(filepath.Join(ws, "status.json"), data, 0o644)
s := &PrepSubsystem{forgeToken: "tok", backoff: make(map[string]time.Time), failCount: make(map[string]int)}
_, out, err := s.createPR(context.Background(), nil, CreatePRInput{
Workspace: "ws-notitle",
DryRun: true,
})
require.NoError(t, err)
assert.Contains(t, out.Title, "agent/fix")
}
// --- listPRs ---
func TestListPRs_Bad_AllRepos(t *testing.T) {
// Test the "all repos" path — lists from all org repos
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case r.URL.Path == "/api/v1/orgs/core/repos":
json.NewEncoder(w).Encode([]map[string]any{
{"name": "go-io", "owner": map[string]any{"login": "core"}},
})
default:
json.NewEncoder(w).Encode([]map[string]any{
{"number": 1, "title": "PR", "state": "open", "html_url": "url",
"user": map[string]any{"login": "virgil"},
"head": map[string]any{"ref": "fix"}, "base": map[string]any{"ref": "dev"},
"labels": []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{})
require.NoError(t, err)
assert.True(t, out.Success)
}
// --- status (more branches) ---
func TestStatus_Good_RunningPIDDead_Blocked(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsRoot := filepath.Join(root, "workspace")
ws := filepath.Join(wsRoot, "ws-dead")
repoDir := filepath.Join(ws, "repo")
os.MkdirAll(repoDir, 0o755)
// Write BLOCKED.md — dead process with blocked file = blocked
os.WriteFile(filepath.Join(repoDir, "BLOCKED.md"), []byte("Need help with API"), 0o644)
writeStatus(ws, &WorkspaceStatus{
Status: "running", Repo: "test", Agent: "codex", PID: 999999, // non-existent PID
})
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
_, out, err := s.status(context.Background(), nil, StatusInput{})
require.NoError(t, err)
assert.Equal(t, 1, out.Total)
assert.Len(t, out.Blocked, 1)
assert.Contains(t, out.Blocked[0].Question, "Need help")
}
func TestStatus_Good_RunningPIDDead_Completed(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsRoot := filepath.Join(root, "workspace")
ws := filepath.Join(wsRoot, "ws-dead2")
os.MkdirAll(filepath.Join(ws, "repo"), 0o755)
// Write agent log — dead process with log = completed
os.WriteFile(filepath.Join(ws, "agent-codex.log"), []byte("done"), 0o644)
writeStatus(ws, &WorkspaceStatus{
Status: "running", Repo: "test", Agent: "codex", PID: 999998,
})
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
_, out, err := s.status(context.Background(), nil, StatusInput{})
require.NoError(t, err)
assert.Equal(t, 1, out.Completed)
}
func TestStatus_Good_RunningPIDDead_Failed(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsRoot := filepath.Join(root, "workspace")
ws := filepath.Join(wsRoot, "ws-dead3")
os.MkdirAll(filepath.Join(ws, "repo"), 0o755)
// No BLOCKED.md, no agent log — dead process = failed
writeStatus(ws, &WorkspaceStatus{
Status: "running", Repo: "test", Agent: "codex", PID: 999997,
})
s := &PrepSubsystem{backoff: make(map[string]time.Time), failCount: make(map[string]int)}
_, out, err := s.status(context.Background(), nil, StatusInput{})
require.NoError(t, err)
assert.Equal(t, 1, out.Failed)
}
// --- DefaultBranch ---
func TestDefaultBranch_Good_GitRepo(t *testing.T) {
dir := t.TempDir()
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()
os.WriteFile(filepath.Join(dir, "f.txt"), []byte("x"), 0o644)
exec.Command("git", "-C", dir, "add", ".").Run()
exec.Command("git", "-C", dir, "commit", "-m", "init").Run()
branch := DefaultBranch(dir)
assert.Equal(t, "main", branch)
}
func TestDefaultBranch_Bad_NoGit(t *testing.T) {
dir := t.TempDir()
branch := DefaultBranch(dir)
assert.Equal(t, "main", branch, "should default to main for non-git dirs")
}
// --- writeStatus edge cases ---
func TestWriteStatus_Good_UpdatesTimestampOnWrite(t *testing.T) {
dir := t.TempDir()
before := time.Now().Add(-1 * time.Second)
st := &WorkspaceStatus{Status: "running", Repo: "test"}
err := writeStatus(dir, st)
require.NoError(t, err)
after := time.Now().Add(1 * time.Second)
read, _ := ReadStatus(dir)
assert.True(t, read.UpdatedAt.After(before))
assert.True(t, read.UpdatedAt.Before(after))
}
func TestWriteStatus_Good_PreservesFields(t *testing.T) {
dir := t.TempDir()
st := &WorkspaceStatus{
Status: "running", Repo: "go-io", Agent: "codex", Org: "core",
Task: "Fix it", Branch: "agent/fix", Issue: 42, PID: 12345,
Question: "need help", Runs: 3, PRURL: "https://forge.test/pulls/1",
}
require.NoError(t, writeStatus(dir, st))
read, err := ReadStatus(dir)
require.NoError(t, err)
assert.Equal(t, "running", read.Status)
assert.Equal(t, "go-io", read.Repo)
assert.Equal(t, 42, read.Issue)
assert.Equal(t, 12345, read.PID)
assert.Equal(t, "need help", read.Question)
}
// --- reviewQueue edge cases ---
func TestReviewQueue_Good_RespectLimit(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
s := &PrepSubsystem{
codePath: root,
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
_, out, err := s.reviewQueue(context.Background(), nil, ReviewQueueInput{Limit: 1})
require.NoError(t, err)
assert.True(t, out.Success)
}
// --- cmdPrep with branch/pr/tag/issue ---
func TestCmdPrep_Good_WithIssue(t *testing.T) {
s, _ := testPrepWithCore(t, nil)
r := s.cmdPrep(core.NewOptions(
core.Option{Key: "_arg", Value: "nonexistent"},
core.Option{Key: "issue", Value: "42"},
))
// Will fail (no local clone) but exercises the issue parsing path
assert.False(t, r.OK)
}
func TestCmdPrep_Good_WithPR(t *testing.T) {
s, _ := testPrepWithCore(t, nil)
r := s.cmdPrep(core.NewOptions(
core.Option{Key: "_arg", Value: "nonexistent"},
core.Option{Key: "pr", Value: "7"},
))
assert.False(t, r.OK)
}
func TestCmdPrep_Good_WithBranch(t *testing.T) {
s, _ := testPrepWithCore(t, nil)
r := s.cmdPrep(core.NewOptions(
core.Option{Key: "_arg", Value: "nonexistent"},
core.Option{Key: "branch", Value: "feat/new"},
))
assert.False(t, r.OK)
}
func TestCmdPrep_Good_WithTag(t *testing.T) {
s, _ := testPrepWithCore(t, nil)
r := s.cmdPrep(core.NewOptions(
core.Option{Key: "_arg", Value: "nonexistent"},
core.Option{Key: "tag", Value: "v1.0.0"},
))
assert.False(t, r.OK)
}
// --- cmdRunTask with defaults ---
func TestCmdRunTask_Good_DefaultsApplied(t *testing.T) {
s, _ := testPrepWithCore(t, nil)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Has repo+task but dispatch will fail (no local clone) — exercises default logic
r := s.cmdRunTask(ctx, core.NewOptions(
core.Option{Key: "repo", Value: "go-io"},
core.Option{Key: "task", Value: "fix tests"},
core.Option{Key: "issue", Value: "15"},
))
assert.False(t, r.OK) // dispatch fails, but exercises all defaults
}
// --- canDispatchAgent with Core config ---
func TestCanDispatchAgent_Good_WithCoreConfig(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
os.MkdirAll(filepath.Join(root, "workspace"), 0o755)
c := core.New()
// Set concurrency config on Core
c.Config().Set("agents.concurrency", map[string]ConcurrencyLimit{
"claude": {Total: 5},
})
s := &PrepSubsystem{
core: c,
codePath: t.TempDir(),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
assert.True(t, s.canDispatchAgent("claude"))
}
// --- buildPrompt with persona ---
func TestBuildPrompt_Good_WithPersona(t *testing.T) {
dir := t.TempDir()
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",
Persona: "engineering/engineering-security-engineer",
}, "dev", dir)
assert.Contains(t, prompt, "TASK: Fix tests")
// Persona may or may not be found — just exercises the branch
}
// --- buildPrompt with plan template ---
func TestBuildPrompt_Good_WithPlanTemplate(t *testing.T) {
dir := t.TempDir()
s := &PrepSubsystem{
codePath: t.TempDir(),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
prompt, _, _ := s.buildPrompt(context.Background(), PrepInput{
Task: "Fix the auth bug",
Org: "core",
Repo: "go-io",
PlanTemplate: "bug-fix",
}, "dev", dir)
assert.Contains(t, prompt, "TASK: Fix the auth bug")
// Plan template may render if embedded — exercises the branch
}