test: batch 1 — add 80 Bad/Ugly tests for paths, plan, status, shutdown, forge cmds

Fill missing Good/Bad/Ugly categories for:
- paths.go: LocalFs, WorkspaceRoot, CoreRoot, PlansRoot, AgentName, GitHubOrg, parseInt, DefaultBranch
- plan.go: planCreate/Read/Update/Delete/List Ugly, planPath Ugly, validPlanStatus Ugly
- status.go: writeStatus Bad, status Good/Bad
- shutdown.go: dispatchStart/shutdownGraceful Bad/Ugly, shutdownNow Ugly
- commands_forge.go: all 9 cmd* functions Ugly (with httptest mocks)
- sanitise.go: Bad/Ugly for all 5 functions
- prep.go: various lifecycle Bad/Ugly

Gap: 260 → 208 missing categories
Tests: 566 → 646 (+80)
Coverage: 74.4%

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-25 08:43:35 +00:00
parent 97d06c1e90
commit a5afad870c
6 changed files with 749 additions and 2 deletions

View file

@ -3,6 +3,8 @@
package agentic
import (
"net/http"
"net/http/httptest"
"testing"
core "dappco.re/go/core"
@ -58,3 +60,136 @@ func TestCommandsForge_FmtIndex_Good(t *testing.T) {
assert.Equal(t, "0", fmtIndex(0))
assert.Equal(t, "999999", fmtIndex(999999))
}
// --- parseForgeArgs Ugly ---
func TestCommandsForge_ParseForgeArgs_Ugly_OrgSetButNoRepo(t *testing.T) {
opts := core.NewOptions(
core.Option{Key: "org", Value: "custom-org"},
)
org, repo, num := parseForgeArgs(opts)
assert.Equal(t, "custom-org", org)
assert.Empty(t, repo, "repo should be empty when only org is set")
assert.Equal(t, int64(0), num)
}
func TestCommandsForge_ParseForgeArgs_Ugly_NegativeNumber(t *testing.T) {
opts := core.NewOptions(
core.Option{Key: "_arg", Value: "go-io"},
core.Option{Key: "number", Value: "-5"},
)
_, _, num := parseForgeArgs(opts)
assert.Equal(t, int64(-5), num, "negative numbers parse but are semantically invalid")
}
// --- fmtIndex Bad/Ugly ---
func TestCommandsForge_FmtIndex_Bad_Negative(t *testing.T) {
result := fmtIndex(-1)
assert.Equal(t, "-1", result, "negative should format as negative string")
}
func TestCommandsForge_FmtIndex_Ugly_VeryLarge(t *testing.T) {
result := fmtIndex(9999999999)
assert.Equal(t, "9999999999", result)
}
func TestCommandsForge_FmtIndex_Ugly_MaxInt64(t *testing.T) {
result := fmtIndex(9223372036854775807) // math.MaxInt64
assert.NotEmpty(t, result)
assert.Equal(t, "9223372036854775807", result)
}
// --- Forge commands Ugly (special chars → API returns 404/error) ---
func TestCommandsForge_CmdIssueGet_Ugly(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(404) }))
t.Cleanup(srv.Close)
s, _ := testPrepWithCore(t, srv)
r := s.cmdIssueGet(core.NewOptions(
core.Option{Key: "_arg", Value: "go-io/<script>"},
core.Option{Key: "number", Value: "1"},
))
assert.False(t, r.OK)
}
func TestCommandsForge_CmdIssueList_Ugly(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) }))
t.Cleanup(srv.Close)
s, _ := testPrepWithCore(t, srv)
r := s.cmdIssueList(core.NewOptions(core.Option{Key: "_arg", Value: "repo&evil=true"}))
assert.False(t, r.OK)
}
func TestCommandsForge_CmdIssueComment_Ugly(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) }))
t.Cleanup(srv.Close)
s, _ := testPrepWithCore(t, srv)
r := s.cmdIssueComment(core.NewOptions(
core.Option{Key: "_arg", Value: "go-io"},
core.Option{Key: "number", Value: "1"},
core.Option{Key: "body", Value: "Hello <b>world</b> & \"quotes\""},
))
assert.False(t, r.OK)
}
func TestCommandsForge_CmdIssueCreate_Ugly(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) }))
t.Cleanup(srv.Close)
s, _ := testPrepWithCore(t, srv)
r := s.cmdIssueCreate(core.NewOptions(
core.Option{Key: "_arg", Value: "go-io"},
core.Option{Key: "title", Value: "Fix <b>bug</b> #123"},
))
assert.False(t, r.OK)
}
func TestCommandsForge_CmdPRGet_Ugly(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(404) }))
t.Cleanup(srv.Close)
s, _ := testPrepWithCore(t, srv)
r := s.cmdPRGet(core.NewOptions(
core.Option{Key: "_arg", Value: "../../../etc/passwd"},
core.Option{Key: "number", Value: "1"},
))
assert.False(t, r.OK)
}
func TestCommandsForge_CmdPRList_Ugly(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) }))
t.Cleanup(srv.Close)
s, _ := testPrepWithCore(t, srv)
r := s.cmdPRList(core.NewOptions(core.Option{Key: "_arg", Value: "repo%00null"}))
assert.False(t, r.OK)
}
func TestCommandsForge_CmdPRMerge_Ugly(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(422) }))
t.Cleanup(srv.Close)
s, _ := testPrepWithCore(t, srv)
r := s.cmdPRMerge(core.NewOptions(
core.Option{Key: "_arg", Value: "go-io"},
core.Option{Key: "number", Value: "1"},
core.Option{Key: "method", Value: "invalid-method"},
))
assert.False(t, r.OK)
}
func TestCommandsForge_CmdRepoGet_Ugly(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(404) }))
t.Cleanup(srv.Close)
s, _ := testPrepWithCore(t, srv)
r := s.cmdRepoGet(core.NewOptions(
core.Option{Key: "_arg", Value: "go-io"},
core.Option{Key: "org", Value: "org/with/slashes"},
))
assert.False(t, r.OK)
}
func TestCommandsForge_CmdRepoList_Ugly(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) }))
t.Cleanup(srv.Close)
s, _ := testPrepWithCore(t, srv)
r := s.cmdRepoList(core.NewOptions(core.Option{Key: "org", Value: "<script>alert(1)</script>"}))
assert.False(t, r.OK)
}

View file

@ -10,6 +10,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
core "dappco.re/go/core"
)
func TestPaths_CoreRoot_Good_EnvVar(t *testing.T) {
@ -197,3 +199,143 @@ func TestPaths_DefaultBranch_Ugly(t *testing.T) {
branch := DefaultBranch(dir)
assert.Equal(t, "master", branch)
}
// --- LocalFs Bad/Ugly ---
func TestPaths_LocalFs_Bad_ReadNonExistent(t *testing.T) {
f := LocalFs()
r := f.Read("/tmp/nonexistent-path-" + strings.Repeat("x", 20) + "/file.txt")
assert.False(t, r.OK, "reading a non-existent file should fail")
}
func TestPaths_LocalFs_Ugly_EmptyPath(t *testing.T) {
f := LocalFs()
assert.NotPanics(t, func() {
f.Read("")
})
}
// --- WorkspaceRoot Bad/Ugly ---
func TestPaths_WorkspaceRoot_Bad_EmptyEnv(t *testing.T) {
t.Setenv("CORE_WORKSPACE", "")
home, _ := os.UserHomeDir()
// Should fall back to ~/Code/.core/workspace
assert.Equal(t, home+"/Code/.core/workspace", WorkspaceRoot())
}
func TestPaths_WorkspaceRoot_Ugly_TrailingSlash(t *testing.T) {
t.Setenv("CORE_WORKSPACE", "/tmp/test-core/")
// Verify it still constructs a valid path (JoinPath handles trailing slash)
ws := WorkspaceRoot()
assert.NotEmpty(t, ws)
assert.Contains(t, ws, "workspace")
}
// --- CoreRoot Bad/Ugly ---
func TestPaths_CoreRoot_Bad_WhitespaceEnv(t *testing.T) {
t.Setenv("CORE_WORKSPACE", " ")
// Non-empty string (whitespace) will be used as-is
root := CoreRoot()
assert.Equal(t, " ", root)
}
func TestPaths_CoreRoot_Ugly_UnicodeEnv(t *testing.T) {
t.Setenv("CORE_WORKSPACE", "/tmp/\u00e9\u00e0\u00fc")
assert.NotPanics(t, func() {
root := CoreRoot()
assert.Equal(t, "/tmp/\u00e9\u00e0\u00fc", root)
})
}
// --- PlansRoot Bad/Ugly ---
func TestPaths_PlansRoot_Bad_EmptyEnv(t *testing.T) {
t.Setenv("CORE_WORKSPACE", "")
home, _ := os.UserHomeDir()
assert.Equal(t, home+"/Code/.core/plans", PlansRoot())
}
func TestPaths_PlansRoot_Ugly_NestedPath(t *testing.T) {
t.Setenv("CORE_WORKSPACE", "/a/b/c/d/e/f")
assert.Equal(t, "/a/b/c/d/e/f/plans", PlansRoot())
}
// --- AgentName Bad/Ugly ---
func TestPaths_AgentName_Bad_WhitespaceEnv(t *testing.T) {
t.Setenv("AGENT_NAME", " ")
// Whitespace is non-empty, so returned as-is
assert.Equal(t, " ", AgentName())
}
func TestPaths_AgentName_Ugly_UnicodeEnv(t *testing.T) {
t.Setenv("AGENT_NAME", "\u00e9nchantr\u00efx")
assert.NotPanics(t, func() {
name := AgentName()
assert.Equal(t, "\u00e9nchantr\u00efx", name)
})
}
// --- GitHubOrg Bad/Ugly ---
func TestPaths_GitHubOrg_Bad_WhitespaceEnv(t *testing.T) {
t.Setenv("GITHUB_ORG", " ")
assert.Equal(t, " ", GitHubOrg())
}
func TestPaths_GitHubOrg_Ugly_SpecialChars(t *testing.T) {
t.Setenv("GITHUB_ORG", "org/with/slashes")
assert.NotPanics(t, func() {
org := GitHubOrg()
assert.Equal(t, "org/with/slashes", org)
})
}
// --- parseInt Bad/Ugly ---
func TestPaths_ParseInt_Bad_EmptyString(t *testing.T) {
assert.Equal(t, 0, parseInt(""))
}
func TestPaths_ParseInt_Bad_NonNumeric(t *testing.T) {
assert.Equal(t, 0, parseInt("abc"))
assert.Equal(t, 0, parseInt("12.5"))
assert.Equal(t, 0, parseInt("0xff"))
}
func TestPaths_ParseInt_Bad_WhitespaceOnly(t *testing.T) {
assert.Equal(t, 0, parseInt(" "))
}
func TestPaths_ParseInt_Ugly_NegativeNumber(t *testing.T) {
assert.Equal(t, -42, parseInt("-42"))
}
func TestPaths_ParseInt_Ugly_VeryLargeNumber(t *testing.T) {
assert.Equal(t, 0, parseInt("99999999999999999999999"))
}
func TestPaths_ParseInt_Ugly_LeadingTrailingWhitespace(t *testing.T) {
assert.Equal(t, 42, parseInt(" 42 "))
}
// --- newFs Bad/Ugly ---
func TestPaths_NewFs_Bad_EmptyRoot(t *testing.T) {
f := newFs("")
assert.NotNil(t, f, "newFs with empty root should not return nil")
}
func TestPaths_NewFs_Ugly_UnicodeRoot(t *testing.T) {
assert.NotPanics(t, func() {
f := newFs("/tmp/\u00e9\u00e0\u00fc/\u00f1o\u00f0\u00e9s")
assert.NotNil(t, f)
})
}
func TestPaths_NewFs_Ugly_VerifyIsFs(t *testing.T) {
f := newFs("/tmp")
assert.IsType(t, &core.Fs{}, f)
}

View file

@ -5,6 +5,7 @@ package agentic
import (
"context"
"os"
"strings"
"testing"
"time"
@ -351,3 +352,157 @@ func TestPlan_PlanPath_Bad_Dot(t *testing.T) {
assert.Contains(t, planPath("/tmp", ".."), "invalid")
assert.Contains(t, planPath("/tmp", ""), "invalid")
}
// --- planCreate Ugly ---
func TestPlan_PlanCreate_Ugly_VeryLongTitle(t *testing.T) {
dir := t.TempDir()
t.Setenv("CORE_WORKSPACE", dir)
s := newTestPrep(t)
longTitle := strings.Repeat("Long Title With Many Words ", 20)
_, out, err := s.planCreate(context.Background(), nil, PlanCreateInput{
Title: longTitle,
Objective: "Test very long title handling",
})
require.NoError(t, err)
assert.True(t, out.Success)
assert.NotEmpty(t, out.ID)
// The slug portion should be truncated
assert.LessOrEqual(t, len(out.ID), 50, "ID should be reasonably short")
}
func TestPlan_PlanCreate_Ugly_UnicodeTitle(t *testing.T) {
dir := t.TempDir()
t.Setenv("CORE_WORKSPACE", dir)
s := newTestPrep(t)
_, out, err := s.planCreate(context.Background(), nil, PlanCreateInput{
Title: "\u00e9\u00e0\u00fc\u00f1\u00f0 Plan \u2603\u2764\u270c",
Objective: "Handle unicode gracefully",
})
require.NoError(t, err)
assert.True(t, out.Success)
assert.NotEmpty(t, out.ID)
// Should be readable from disk
_, statErr := os.Stat(out.Path)
assert.NoError(t, statErr)
}
// --- planRead Ugly ---
func TestPlan_PlanRead_Ugly_SpecialCharsInID(t *testing.T) {
dir := t.TempDir()
t.Setenv("CORE_WORKSPACE", dir)
s := newTestPrep(t)
// Try to read with special chars — should safely not find it
_, _, err := s.planRead(context.Background(), nil, PlanReadInput{ID: "plan-with-<script>-chars"})
assert.Error(t, err)
assert.Contains(t, err.Error(), "not found")
}
func TestPlan_PlanRead_Ugly_UnicodeID(t *testing.T) {
dir := t.TempDir()
t.Setenv("CORE_WORKSPACE", dir)
s := newTestPrep(t)
_, _, err := s.planRead(context.Background(), nil, PlanReadInput{ID: "\u00e9\u00e0\u00fc-plan"})
assert.Error(t, err, "unicode ID should not find a file")
}
// --- planUpdate Ugly ---
func TestPlan_PlanUpdate_Ugly_EmptyPhasesArray(t *testing.T) {
dir := t.TempDir()
t.Setenv("CORE_WORKSPACE", dir)
s := newTestPrep(t)
_, createOut, _ := s.planCreate(context.Background(), nil, PlanCreateInput{
Title: "Phase Test",
Objective: "Test empty phases",
Phases: []Phase{{Name: "Phase 1", Status: "pending"}},
})
// Update with empty phases array — should replace with no phases
_, updateOut, err := s.planUpdate(context.Background(), nil, PlanUpdateInput{
ID: createOut.ID,
Phases: []Phase{},
})
require.NoError(t, err)
// Empty slice is still non-nil, so it replaces
assert.Empty(t, updateOut.Plan.Phases)
}
func TestPlan_PlanUpdate_Ugly_UnicodeNotes(t *testing.T) {
dir := t.TempDir()
t.Setenv("CORE_WORKSPACE", dir)
s := newTestPrep(t)
_, createOut, _ := s.planCreate(context.Background(), nil, PlanCreateInput{
Title: "Unicode Notes",
Objective: "Test unicode in notes",
})
_, updateOut, err := s.planUpdate(context.Background(), nil, PlanUpdateInput{
ID: createOut.ID,
Notes: "\u00e9\u00e0\u00fc\u00f1 notes with \u2603 snowman and \u00a3 pound sign",
})
require.NoError(t, err)
assert.Contains(t, updateOut.Plan.Notes, "\u2603")
}
// --- planDelete Ugly ---
func TestPlan_PlanDelete_Ugly_PathTraversalAttempt(t *testing.T) {
dir := t.TempDir()
t.Setenv("CORE_WORKSPACE", dir)
s := newTestPrep(t)
// Path traversal attempt should be sanitised and not find anything
_, _, err := s.planDelete(context.Background(), nil, PlanDeleteInput{ID: "../../etc/passwd"})
assert.Error(t, err)
assert.Contains(t, err.Error(), "not found")
}
func TestPlan_PlanDelete_Ugly_UnicodeID(t *testing.T) {
dir := t.TempDir()
t.Setenv("CORE_WORKSPACE", dir)
s := newTestPrep(t)
_, _, err := s.planDelete(context.Background(), nil, PlanDeleteInput{ID: "\u00e9\u00e0\u00fc-to-delete"})
assert.Error(t, err, "unicode ID should not match a real plan")
}
// --- planPath Ugly ---
func TestPlan_PlanPath_Ugly_UnicodeID(t *testing.T) {
result := planPath("/tmp/plans", "\u00e9\u00e0\u00fc-plan-\u2603")
assert.NotPanics(t, func() {
_ = planPath("/tmp", "\u00e9\u00e0\u00fc")
})
assert.Contains(t, result, ".json")
}
func TestPlan_PlanPath_Ugly_VeryLongID(t *testing.T) {
longID := strings.Repeat("a", 500)
result := planPath("/tmp/plans", longID)
assert.Contains(t, result, ".json")
assert.NotEmpty(t, result)
}
// --- validPlanStatus Ugly ---
func TestPlan_ValidPlanStatus_Ugly_UnicodeStatus(t *testing.T) {
assert.False(t, validPlanStatus("\u00e9\u00e0\u00fc"))
assert.False(t, validPlanStatus("\u2603"))
assert.False(t, validPlanStatus("\u0000"))
}
func TestPlan_ValidPlanStatus_Ugly_NearMissStatus(t *testing.T) {
assert.False(t, validPlanStatus("Draft")) // capital D
assert.False(t, validPlanStatus("DRAFT")) // all caps
assert.False(t, validPlanStatus("in-progress")) // hyphen instead of underscore
assert.False(t, validPlanStatus(" draft")) // leading space
assert.False(t, validPlanStatus("draft ")) // trailing space
}

View file

@ -4,6 +4,7 @@ package agentic
import (
"path/filepath"
"strings"
"testing"
core "dappco.re/go/core"
@ -194,3 +195,135 @@ func TestPrep_SetCore_Good(t *testing.T) {
s.SetCore(c)
assert.NotNil(t, s.core)
}
// --- sanitiseBranchSlug Bad/Ugly ---
func TestSanitise_SanitiseBranchSlug_Bad_EmptyString(t *testing.T) {
assert.Equal(t, "", sanitiseBranchSlug("", 40))
}
func TestSanitise_SanitiseBranchSlug_Bad_OnlySpecialChars(t *testing.T) {
assert.Equal(t, "", sanitiseBranchSlug("!@#$%^&*()", 40))
}
func TestSanitise_SanitiseBranchSlug_Bad_OnlyDashes(t *testing.T) {
assert.Equal(t, "", sanitiseBranchSlug("------", 40))
}
func TestSanitise_SanitiseBranchSlug_Ugly_VeryLongString(t *testing.T) {
long := strings.Repeat("abcdefghij", 100)
result := sanitiseBranchSlug(long, 50)
assert.LessOrEqual(t, len(result), 50)
}
func TestSanitise_SanitiseBranchSlug_Ugly_Unicode(t *testing.T) {
// Unicode chars should be replaced with dashes, then edges trimmed
result := sanitiseBranchSlug("\u00e9\u00e0\u00fc\u00f1\u00f0", 40)
assert.NotContains(t, result, "\u00e9")
// All replaced with dashes, then trimmed = empty
assert.Equal(t, "", result)
}
func TestSanitise_SanitiseBranchSlug_Ugly_ZeroMax(t *testing.T) {
// max=0 means no limit
result := sanitiseBranchSlug("hello-world", 0)
assert.Equal(t, "hello-world", result)
}
// --- sanitisePlanSlug Bad/Ugly ---
func TestSanitise_SanitisePlanSlug_Bad_EmptyString(t *testing.T) {
assert.Equal(t, "", sanitisePlanSlug(""))
}
func TestSanitise_SanitisePlanSlug_Bad_OnlySpecialChars(t *testing.T) {
assert.Equal(t, "", sanitisePlanSlug("!@#$%^&*()"))
}
func TestSanitise_SanitisePlanSlug_Bad_OnlySpaces(t *testing.T) {
// Spaces become dashes, then collapsed, then trimmed
assert.Equal(t, "", sanitisePlanSlug(" "))
}
func TestSanitise_SanitisePlanSlug_Ugly_VeryLongString(t *testing.T) {
long := strings.Repeat("abcdefghij ", 20)
result := sanitisePlanSlug(long)
assert.LessOrEqual(t, len(result), 30)
}
func TestSanitise_SanitisePlanSlug_Ugly_Unicode(t *testing.T) {
result := sanitisePlanSlug("\u00e9\u00e0\u00fc\u00f1\u00f0")
assert.Equal(t, "", result, "unicode chars should be stripped, leaving empty string")
}
func TestSanitise_SanitisePlanSlug_Ugly_AllDashInput(t *testing.T) {
assert.Equal(t, "", sanitisePlanSlug("---"))
}
// --- sanitiseFilename Bad/Ugly ---
func TestSanitise_SanitiseFilename_Bad_EmptyString(t *testing.T) {
assert.Equal(t, "", sanitiseFilename(""))
}
func TestSanitise_SanitiseFilename_Bad_OnlySpecialChars(t *testing.T) {
result := sanitiseFilename("!@#$%^&*()")
// All replaced with dashes
assert.Equal(t, "----------", result)
}
func TestSanitise_SanitiseFilename_Ugly_VeryLongString(t *testing.T) {
long := strings.Repeat("a", 1000)
result := sanitiseFilename(long)
assert.Equal(t, 1000, len(result))
}
func TestSanitise_SanitiseFilename_Ugly_Unicode(t *testing.T) {
result := sanitiseFilename("\u00e9\u00e0\u00fc\u00f1\u00f0")
// All replaced with dashes
for _, r := range result {
assert.Equal(t, '-', r)
}
}
func TestSanitise_SanitiseFilename_Ugly_PreservesDotsUnderscores(t *testing.T) {
assert.Equal(t, "my_file.test.txt", sanitiseFilename("my_file.test.txt"))
}
// --- collapseRepeatedRune Bad/Ugly ---
func TestSanitise_CollapseRepeatedRune_Bad_EmptyString(t *testing.T) {
assert.Equal(t, "", collapseRepeatedRune("", '-'))
}
func TestSanitise_CollapseRepeatedRune_Bad_AllTarget(t *testing.T) {
assert.Equal(t, "-", collapseRepeatedRune("-----", '-'))
}
func TestSanitise_CollapseRepeatedRune_Ugly_Unicode(t *testing.T) {
assert.Equal(t, "h\u00e9llo", collapseRepeatedRune("h\u00e9\u00e9\u00e9llo", '\u00e9'))
}
func TestSanitise_CollapseRepeatedRune_Ugly_VeryLong(t *testing.T) {
long := strings.Repeat("--a", 500)
result := collapseRepeatedRune(long, '-')
assert.NotContains(t, result, "--")
}
// --- trimRuneEdges Bad/Ugly ---
func TestSanitise_TrimRuneEdges_Bad_EmptyString(t *testing.T) {
assert.Equal(t, "", trimRuneEdges("", '-'))
}
func TestSanitise_TrimRuneEdges_Bad_AllTarget(t *testing.T) {
assert.Equal(t, "", trimRuneEdges("-----", '-'))
}
func TestSanitise_TrimRuneEdges_Ugly_Unicode(t *testing.T) {
assert.Equal(t, "hello", trimRuneEdges("\u00e9hello\u00e9\u00e9", '\u00e9'))
}
func TestSanitise_TrimRuneEdges_Ugly_NoMatch(t *testing.T) {
assert.Equal(t, "hello", trimRuneEdges("hello", '-'))
}

View file

@ -569,3 +569,116 @@ func TestPrep_Shutdown_ShutdownNow_Ugly(t *testing.T) {
assert.Equal(t, "failed", st.Status)
assert.Contains(t, st.Question, "cleared by shutdown_now")
}
// --- dispatchStart Bad/Ugly ---
func TestShutdown_DispatchStart_Bad_NilPokeCh(t *testing.T) {
s := &PrepSubsystem{
frozen: true,
pokeCh: nil,
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
// Should not panic even with nil pokeCh (Poke is nil-safe)
_, out, err := s.dispatchStart(context.Background(), nil, ShutdownInput{})
require.NoError(t, err)
assert.True(t, out.Success)
assert.False(t, s.frozen, "frozen should be cleared even with nil pokeCh")
}
func TestShutdown_DispatchStart_Ugly_AlreadyUnfrozen(t *testing.T) {
s := &PrepSubsystem{
frozen: false, // already unfrozen
pokeCh: make(chan struct{}, 1),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
_, out, err := s.dispatchStart(context.Background(), nil, ShutdownInput{})
require.NoError(t, err)
assert.True(t, out.Success)
assert.False(t, s.frozen, "should remain unfrozen")
assert.Contains(t, out.Message, "started")
}
// --- shutdownGraceful Bad/Ugly ---
func TestShutdown_ShutdownGraceful_Bad_AlreadyFrozen(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
s := &PrepSubsystem{
frozen: true, // already frozen
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
_, out, err := s.shutdownGraceful(context.Background(), nil, ShutdownInput{})
require.NoError(t, err)
assert.True(t, out.Success)
assert.True(t, s.frozen, "should remain frozen")
assert.Contains(t, out.Message, "frozen")
}
func TestShutdown_ShutdownGraceful_Ugly_WithWorkspaces(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsRoot := filepath.Join(root, "workspace")
// Create workspaces with various statuses
for _, name := range []string{"ws-completed", "ws-failed", "ws-blocked"} {
ws := filepath.Join(wsRoot, name)
require.True(t, fs.EnsureDir(ws).OK)
require.NoError(t, writeStatus(ws, &WorkspaceStatus{
Status: "completed",
Repo: "go-io",
Agent: "codex",
}))
}
s := &PrepSubsystem{
frozen: false,
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
_, out, err := s.shutdownGraceful(context.Background(), nil, ShutdownInput{})
require.NoError(t, err)
assert.True(t, out.Success)
assert.True(t, s.frozen)
// Running count should be 0 (no live PIDs)
assert.Equal(t, 0, out.Running)
}
// --- shutdownNow Bad ---
func TestShutdown_ShutdownNow_Bad_NoRunningPIDs(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsRoot := filepath.Join(root, "workspace")
// Create completed workspaces only (no running PIDs to kill)
for i := 1; i <= 2; i++ {
ws := filepath.Join(wsRoot, "task-"+itoa(i))
require.True(t, fs.EnsureDir(ws).OK)
require.NoError(t, writeStatus(ws, &WorkspaceStatus{
Status: "completed",
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.True(t, out.Success)
assert.True(t, s.frozen)
assert.Contains(t, out.Message, "killed 0")
assert.Contains(t, out.Message, "cleared 0")
}

View file

@ -3,12 +3,14 @@
package agentic
import (
"context"
"encoding/json"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestStatus_WriteStatus_Good(t *testing.T) {
@ -299,3 +301,70 @@ func TestStatus_WriteStatus_Ugly(t *testing.T) {
assert.Equal(t, "https://forge.lthn.ai/core/go-mcp/pulls/12", read.PRURL)
assert.False(t, read.UpdatedAt.IsZero(), "UpdatedAt must survive roundtrip")
}
// --- writeStatus Bad ---
func TestStatus_WriteStatus_Bad_ReadOnlyPath(t *testing.T) {
// go-io fs.Write auto-creates dirs, so test with /dev/null parent
st := &WorkspaceStatus{Status: "running", Agent: "codex"}
err := writeStatus("/dev/null/impossible", st)
assert.Error(t, err, "writeStatus to an impossible path should fail")
}
// --- status() MCP handler Good/Bad ---
func TestStatus_Status_Good_PopulatedWorkspaces(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsRoot := filepath.Join(root, "workspace")
// Create a running workspace with a live PID (our own PID)
ws1 := filepath.Join(wsRoot, "task-running")
require.True(t, fs.EnsureDir(filepath.Join(ws1, "repo")).OK)
require.NoError(t, writeStatus(ws1, &WorkspaceStatus{
Status: "completed",
Repo: "go-io",
Agent: "codex",
Task: "fix tests",
}))
// Create a blocked workspace
ws2 := filepath.Join(wsRoot, "task-blocked")
require.True(t, fs.EnsureDir(filepath.Join(ws2, "repo")).OK)
require.NoError(t, writeStatus(ws2, &WorkspaceStatus{
Status: "blocked",
Repo: "go-log",
Agent: "gemini",
Question: "Which log format?",
}))
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, 2, out.Total)
assert.Equal(t, 1, out.Completed)
assert.Len(t, out.Blocked, 1)
assert.Equal(t, "go-log", out.Blocked[0].Repo)
assert.Equal(t, "Which log format?", out.Blocked[0].Question)
}
func TestStatus_Status_Bad_EmptyWorkspaceRoot(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
// Do NOT create the workspace/ subdirectory
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, "status on missing workspace dir should not error")
assert.Equal(t, 0, out.Total)
assert.Equal(t, 0, out.Running)
assert.Equal(t, 0, out.Completed)
}