diff --git a/pkg/agentic/commands_test.go b/pkg/agentic/commands_test.go
index 283ed35..6e1b73d 100644
--- a/pkg/agentic/commands_test.go
+++ b/pkg/agentic/commands_test.go
@@ -757,6 +757,68 @@ func TestCommands_CmdRunTask_Ugly_MixedIssueString(t *testing.T) {
assert.False(t, r.OK)
}
+// --- CmdRunTaskFactory Good/Bad/Ugly ---
+
+func TestCommands_CmdRunTaskFactory_Good(t *testing.T) {
+ s, _ := testPrepWithCore(t, nil)
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ fn := s.cmdRunTaskFactory(ctx)
+ assert.NotNil(t, fn, "factory should return a non-nil func")
+}
+
+func TestCommands_CmdRunTaskFactory_Bad(t *testing.T) {
+ s, _ := testPrepWithCore(t, nil)
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel() // cancelled ctx
+
+ fn := s.cmdRunTaskFactory(ctx)
+ assert.NotNil(t, fn, "factory should return a func even with cancelled ctx")
+}
+
+func TestCommands_CmdRunTaskFactory_Ugly(t *testing.T) {
+ s, _ := testPrepWithCore(t, nil)
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ fn := s.cmdRunTaskFactory(ctx)
+ // Call with empty options — should fail gracefully (missing repo+task)
+ r := fn(core.NewOptions())
+ assert.False(t, r.OK)
+}
+
+// --- CmdOrchestratorFactory Good/Bad/Ugly ---
+
+func TestCommands_CmdOrchestratorFactory_Good(t *testing.T) {
+ s, _ := testPrepWithCore(t, nil)
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ fn := s.cmdOrchestratorFactory(ctx)
+ assert.NotNil(t, fn, "factory should return a non-nil func")
+}
+
+func TestCommands_CmdOrchestratorFactory_Bad(t *testing.T) {
+ s, _ := testPrepWithCore(t, nil)
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel() // cancelled ctx
+
+ fn := s.cmdOrchestratorFactory(ctx)
+ assert.NotNil(t, fn, "factory should return a func even with cancelled ctx")
+}
+
+func TestCommands_CmdOrchestratorFactory_Ugly(t *testing.T) {
+ s, _ := testPrepWithCore(t, nil)
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel() // pre-cancelled
+
+ fn := s.cmdOrchestratorFactory(ctx)
+ // Calling the factory result with a cancelled ctx should return OK (exits immediately)
+ r := fn(core.NewOptions())
+ assert.True(t, r.OK)
+}
+
// --- CmdStatus Bad/Ugly ---
func TestCommands_CmdStatus_Bad_NoWorkspaceDir(t *testing.T) {
diff --git a/pkg/agentic/dispatch_test.go b/pkg/agentic/dispatch_test.go
index ab453dc..1920df6 100644
--- a/pkg/agentic/dispatch_test.go
+++ b/pkg/agentic/dispatch_test.go
@@ -485,6 +485,20 @@ func TestDispatch_WorkspaceDir_Ugly(t *testing.T) {
assert.Contains(t, dir, "pr-3")
}
+// --- containerCommand ---
+
+func TestDispatch_ContainerCommand_Bad(t *testing.T) {
+ t.Setenv("AGENT_DOCKER_IMAGE", "")
+ t.Setenv("DIR_HOME", "/home/dev")
+
+ // Empty command string — docker still runs, just with no command after image
+ cmd, args := containerCommand("codex", "", []string{}, "/ws/repo", "/ws/.meta")
+ assert.Equal(t, "docker", cmd)
+ assert.Contains(t, args, "run")
+ // The image should still be present in args
+ assert.Contains(t, args, defaultDockerImage)
+}
+
// --- canDispatchAgent ---
// Good: tested in queue_test.go
// Bad: tested in queue_test.go
diff --git a/pkg/agentic/ingest_test.go b/pkg/agentic/ingest_test.go
index 927f64b..90dc60d 100644
--- a/pkg/agentic/ingest_test.go
+++ b/pkg/agentic/ingest_test.go
@@ -276,6 +276,62 @@ func TestIngest_CountFileRefs_Good_SecurityFindings(t *testing.T) {
assert.Equal(t, 2, countFileRefs(body))
}
+// --- IngestFindings Ugly ---
+
+func TestIngest_IngestFindings_Ugly(t *testing.T) {
+ // Workspace with no findings file (completed but empty meta dir)
+ wsDir := t.TempDir()
+ require.NoError(t, writeStatus(wsDir, &WorkspaceStatus{
+ Status: "completed",
+ Repo: "go-io",
+ Agent: "codex",
+ }))
+ // No agent-*.log files at all
+
+ s := &PrepSubsystem{
+ backoff: make(map[string]time.Time),
+ failCount: make(map[string]int),
+ }
+
+ // Should return early without panic — no log files
+ assert.NotPanics(t, func() {
+ s.ingestFindings(wsDir)
+ })
+}
+
+// --- CreateIssueViaAPI Ugly ---
+
+func TestIngest_CreateIssueViaAPI_Ugly(t *testing.T) {
+ // Issue body with HTML injection chars — should be passed as-is without panic
+ called := false
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ called = true
+ var body map[string]string
+ json.NewDecoder(r.Body).Decode(&body)
+ // Verify the body preserved HTML chars
+ assert.Contains(t, body["description"], "bold&", "bug", "high", "scan")
+ assert.True(t, called)
+}
+
func TestIngest_CountFileRefs_Good_PHPSecurityFindings(t *testing.T) {
body := "PHP audit:\n" +
"- `src/Controller/Api.php:42` SQL injection risk\n" +
diff --git a/pkg/agentic/prep_extra_test.go b/pkg/agentic/prep_extra_test.go
index 2396e45..334acbe 100644
--- a/pkg/agentic/prep_extra_test.go
+++ b/pkg/agentic/prep_extra_test.go
@@ -472,6 +472,35 @@ func TestPrep_BrainRecall_Ugly(t *testing.T) {
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) {
diff --git a/pkg/agentic/prep_test.go b/pkg/agentic/prep_test.go
index 4828cdf..3318cd5 100644
--- a/pkg/agentic/prep_test.go
+++ b/pkg/agentic/prep_test.go
@@ -513,6 +513,134 @@ func TestPrep_DetectBuildCmd_Ugly(t *testing.T) {
})
}
+// --- TestPrepWorkspace (public API wrapper) ---
+
+func TestPrep_TestPrepWorkspace_Good(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),
+ }
+
+ // Valid input but repo won't exist — still exercises the public wrapper delegation
+ _, _, err := s.TestPrepWorkspace(context.Background(), PrepInput{
+ Repo: "go-io",
+ Issue: 1,
+ })
+ // Error expected (no local clone) but we verified it delegates to prepWorkspace
+ assert.Error(t, err)
+}
+
+func TestPrep_TestPrepWorkspace_Bad(t *testing.T) {
+ s := &PrepSubsystem{
+ codePath: t.TempDir(),
+ backoff: make(map[string]time.Time),
+ failCount: make(map[string]int),
+ }
+
+ // Missing repo — should return error
+ _, _, err := s.TestPrepWorkspace(context.Background(), PrepInput{})
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "repo is required")
+}
+
+func TestPrep_TestPrepWorkspace_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),
+ }
+
+ // Bare ".." is caught as invalid repo name by PathBase check
+ _, _, err := s.TestPrepWorkspace(context.Background(), PrepInput{
+ Repo: "..",
+ Issue: 1,
+ })
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "invalid repo name")
+}
+
+// --- TestBuildPrompt (public API wrapper) ---
+
+func TestPrep_TestBuildPrompt_Good(t *testing.T) {
+ dir := t.TempDir()
+ require.True(t, fs.Write(filepath.Join(dir, "go.mod"), "module test").OK)
+
+ s := &PrepSubsystem{
+ codePath: t.TempDir(),
+ backoff: make(map[string]time.Time),
+ failCount: make(map[string]int),
+ }
+
+ prompt, memories, consumers := s.TestBuildPrompt(context.Background(), PrepInput{
+ Task: "Review code",
+ Org: "core",
+ Repo: "go-io",
+ }, "dev", dir)
+
+ assert.NotEmpty(t, prompt)
+ assert.Contains(t, prompt, "TASK: Review code")
+ assert.Contains(t, prompt, "REPO: core/go-io on branch dev")
+ assert.Equal(t, 0, memories)
+ assert.Equal(t, 0, consumers)
+}
+
+func TestPrep_TestBuildPrompt_Bad(t *testing.T) {
+ s := &PrepSubsystem{
+ codePath: t.TempDir(),
+ backoff: make(map[string]time.Time),
+ failCount: make(map[string]int),
+ }
+
+ // Empty inputs — should still return a prompt string without panicking
+ prompt, memories, consumers := s.TestBuildPrompt(context.Background(), PrepInput{}, "", "")
+ assert.NotEmpty(t, prompt)
+ assert.Contains(t, prompt, "TASK:")
+ assert.Contains(t, prompt, "CONSTRAINTS:")
+ assert.Equal(t, 0, memories)
+ assert.Equal(t, 0, consumers)
+}
+
+func TestPrep_TestBuildPrompt_Ugly(t *testing.T) {
+ dir := t.TempDir()
+
+ s := &PrepSubsystem{
+ codePath: t.TempDir(),
+ backoff: make(map[string]time.Time),
+ failCount: make(map[string]int),
+ }
+
+ // Unicode in all fields — should not panic
+ prompt, _, _ := s.TestBuildPrompt(context.Background(), PrepInput{
+ Task: "\u00e9nchantr\u00efx \u2603 \U0001f600",
+ Org: "c\u00f6re",
+ Repo: "g\u00f6-i\u00f6",
+ }, "\u00e9-branch", dir)
+
+ assert.NotEmpty(t, prompt)
+ assert.Contains(t, prompt, "\u00e9nchantr\u00efx")
+}
+
+// --- collapseRepeatedRune / sanitisePlanSlug / trimRuneEdges Good ---
+
+func TestPrep_CollapseRepeatedRune_Good(t *testing.T) {
+ assert.Equal(t, "hello-world", collapseRepeatedRune("hello---world", '-'))
+}
+
+func TestPrep_SanitisePlanSlug_Good(t *testing.T) {
+ assert.Equal(t, "my-cool-plan", sanitisePlanSlug("My Cool Plan"))
+}
+
+func TestPrep_TrimRuneEdges_Good(t *testing.T) {
+ assert.Equal(t, "hello", trimRuneEdges("--hello--", '-'))
+}
+
// --- DetectTestCmd Bad/Ugly ---
func TestPrep_DetectTestCmd_Bad(t *testing.T) {
diff --git a/pkg/agentic/queue_extra_test.go b/pkg/agentic/queue_extra_test.go
index 0fa0aba..de4b86c 100644
--- a/pkg/agentic/queue_extra_test.go
+++ b/pkg/agentic/queue_extra_test.go
@@ -491,6 +491,106 @@ func TestQueue_DrainOne_Ugly_QueuedButInBackoffWindow(t *testing.T) {
// --- DrainQueue Bad ---
+// --- UnmarshalYAML (renamed convention) ---
+
+func TestQueue_UnmarshalYAML_Good(t *testing.T) {
+ var cfg struct {
+ Limit ConcurrencyLimit `yaml:"limit"`
+ }
+ err := yaml.Unmarshal([]byte("limit: 5"), &cfg)
+ require.NoError(t, err)
+ assert.Equal(t, 5, cfg.Limit.Total)
+ assert.Nil(t, cfg.Limit.Models)
+}
+
+func TestQueue_UnmarshalYAML_Bad(t *testing.T) {
+ var cfg struct {
+ Limit ConcurrencyLimit `yaml:"limit"`
+ }
+ // Invalid YAML — nested map with bad types
+ err := yaml.Unmarshal([]byte("limit: [1, 2, 3]"), &cfg)
+ assert.Error(t, err)
+}
+
+func TestQueue_UnmarshalYAML_Ugly(t *testing.T) {
+ var cfg struct {
+ Limit ConcurrencyLimit `yaml:"limit"`
+ }
+ // Float value — YAML truncates to int, so 3.5 becomes 3
+ err := yaml.Unmarshal([]byte("limit: 3.5"), &cfg)
+ require.NoError(t, err)
+ assert.Equal(t, 3, cfg.Limit.Total)
+ assert.Nil(t, cfg.Limit.Models)
+}
+
+// --- loadAgentsConfig ---
+
+func TestQueue_LoadAgentsConfig_Good(t *testing.T) {
+ root := t.TempDir()
+ t.Setenv("CORE_WORKSPACE", root)
+
+ cfg := `version: 1
+concurrency:
+ claude: 1
+ codex: 2
+rates:
+ codex:
+ sustained_delay: 60`
+ require.True(t, fs.Write(core.JoinPath(root, "agents.yaml"), cfg).OK)
+
+ s := &PrepSubsystem{
+ codePath: t.TempDir(),
+ backoff: make(map[string]time.Time),
+ failCount: make(map[string]int),
+ }
+
+ loaded := s.loadAgentsConfig()
+ assert.NotNil(t, loaded)
+ assert.Equal(t, 1, loaded.Version)
+ assert.Equal(t, 1, loaded.Concurrency["claude"].Total)
+ assert.Equal(t, 2, loaded.Concurrency["codex"].Total)
+ assert.Equal(t, 60, loaded.Rates["codex"].SustainedDelay)
+}
+
+func TestQueue_LoadAgentsConfig_Bad(t *testing.T) {
+ root := t.TempDir()
+ t.Setenv("CORE_WORKSPACE", root)
+
+ // Corrupt YAML file
+ require.True(t, fs.Write(core.JoinPath(root, "agents.yaml"), "{{{not yaml!!!").OK)
+
+ s := &PrepSubsystem{
+ codePath: t.TempDir(),
+ backoff: make(map[string]time.Time),
+ failCount: make(map[string]int),
+ }
+
+ // Should return defaults when YAML is corrupt
+ loaded := s.loadAgentsConfig()
+ assert.NotNil(t, loaded)
+ assert.Equal(t, "claude", loaded.Dispatch.DefaultAgent)
+}
+
+func TestQueue_LoadAgentsConfig_Ugly(t *testing.T) {
+ root := t.TempDir()
+ t.Setenv("CORE_WORKSPACE", root)
+ // No agents.yaml file at all — should return defaults
+
+ s := &PrepSubsystem{
+ codePath: t.TempDir(),
+ backoff: make(map[string]time.Time),
+ failCount: make(map[string]int),
+ }
+
+ loaded := s.loadAgentsConfig()
+ assert.NotNil(t, loaded)
+ assert.Equal(t, "claude", loaded.Dispatch.DefaultAgent)
+ assert.Equal(t, "coding", loaded.Dispatch.DefaultTemplate)
+ assert.NotNil(t, loaded.Concurrency)
+}
+
+// --- DrainQueue Bad ---
+
func TestQueue_DrainQueue_Bad_FrozenQueueDoesNothing(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
diff --git a/pkg/agentic/queue_logic_test.go b/pkg/agentic/queue_logic_test.go
index edf13b7..e42b9cd 100644
--- a/pkg/agentic/queue_logic_test.go
+++ b/pkg/agentic/queue_logic_test.go
@@ -162,6 +162,57 @@ func TestRunner_StartRunner_Good_AutoStartEnvVar(t *testing.T) {
assert.False(t, s.frozen, "CORE_AGENT_DISPATCH=1 should unfreeze the queue")
}
+// --- Poke Ugly ---
+
+func TestRunner_Poke_Ugly(t *testing.T) {
+ // Poke on a closed channel — the select with default protects against panic
+ // but closing + sending would panic. However, Poke uses non-blocking send,
+ // so we test that pokeCh=nil is safe (already tested), and that
+ // double-filling is safe (already tested). Here we test rapid multi-poke.
+ s := &PrepSubsystem{}
+ s.pokeCh = make(chan struct{}, 1)
+
+ // Rapid-fire pokes — should all be safe
+ for i := 0; i < 100; i++ {
+ assert.NotPanics(t, func() { s.Poke() })
+ }
+ // Channel should have at most 1 signal
+ assert.LessOrEqual(t, len(s.pokeCh), 1)
+}
+
+// --- StartRunner Bad/Ugly ---
+
+func TestRunner_StartRunner_Bad(t *testing.T) {
+ root := t.TempDir()
+ t.Setenv("CORE_WORKSPACE", root)
+ t.Setenv("CORE_AGENT_DISPATCH", "")
+
+ s := NewPrep()
+ s.StartRunner()
+ // Without CORE_AGENT_DISPATCH=1, queue should be frozen
+ assert.True(t, s.frozen, "queue must be frozen when CORE_AGENT_DISPATCH is not set")
+ assert.NotNil(t, s.pokeCh)
+}
+
+func TestRunner_StartRunner_Ugly(t *testing.T) {
+ root := t.TempDir()
+ t.Setenv("CORE_WORKSPACE", root)
+ t.Setenv("CORE_AGENT_DISPATCH", "1")
+
+ s := NewPrep()
+
+ // Start twice — second call overwrites pokeCh
+ s.StartRunner()
+ firstCh := s.pokeCh
+ assert.NotNil(t, firstCh)
+
+ s.StartRunner()
+ secondCh := s.pokeCh
+ assert.NotNil(t, secondCh)
+ // The channels should be different objects (new make each time)
+ assert.NotSame(t, &firstCh, &secondCh)
+}
+
// --- DefaultBranch ---
func TestPaths_DefaultBranch_Good_DefaultsToMain(t *testing.T) {
diff --git a/pkg/agentic/register_test.go b/pkg/agentic/register_test.go
index 919d162..e769ca6 100644
--- a/pkg/agentic/register_test.go
+++ b/pkg/agentic/register_test.go
@@ -55,6 +55,36 @@ func TestRegister_Good_AgentsConfigLoaded(t *testing.T) {
assert.NotNil(t, concurrency, "Register must store agents.concurrency in Core Config")
}
+// --- ProcessRegister ---
+
+func TestProcessRegister_ProcessRegister_Good(t *testing.T) {
+ t.Setenv("CORE_WORKSPACE", t.TempDir())
+
+ c := core.New()
+ result := ProcessRegister(c)
+ assert.True(t, result.OK, "ProcessRegister should succeed with a real Core instance")
+ assert.NotNil(t, result.Value)
+}
+
+func TestProcessRegister_ProcessRegister_Bad(t *testing.T) {
+ // nil Core — the process.NewService factory tolerates nil Core, returns a result
+ result := ProcessRegister(nil)
+ // Either OK (service created without Core) or not OK (error) — must not panic
+ _ = result
+}
+
+func TestProcessRegister_ProcessRegister_Ugly(t *testing.T) {
+ // Call twice with same Core — second call should still succeed
+ t.Setenv("CORE_WORKSPACE", t.TempDir())
+
+ c := core.New()
+ r1 := ProcessRegister(c)
+ assert.True(t, r1.OK)
+
+ r2 := ProcessRegister(c)
+ assert.True(t, r2.OK, "second ProcessRegister call should not fail")
+}
+
// --- OnStartup ---
func TestPrep_OnStartup_Good_CreatesPokeCh(t *testing.T) {
diff --git a/pkg/agentic/remote_client_test.go b/pkg/agentic/remote_client_test.go
index 5052366..ebac33f 100644
--- a/pkg/agentic/remote_client_test.go
+++ b/pkg/agentic/remote_client_test.go
@@ -125,6 +125,19 @@ func TestRemoteClient_SetHeaders_Good_NoToken(t *testing.T) {
assert.Empty(t, req.Header.Get("Mcp-Session-Id"))
}
+// --- setHeaders Bad ---
+
+func TestRemoteClient_SetHeaders_Bad(t *testing.T) {
+ // Both token and session empty — only Content-Type and Accept are set
+ req, _ := http.NewRequest("POST", "http://example.com", nil)
+ setHeaders(req, "", "")
+
+ assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
+ assert.Equal(t, "application/json, text/event-stream", req.Header.Get("Accept"))
+ assert.Empty(t, req.Header.Get("Authorization"), "no auth header when token is empty")
+ assert.Empty(t, req.Header.Get("Mcp-Session-Id"), "no session header when session is empty")
+}
+
// --- readSSEData ---
func TestRemoteClient_ReadSSEData_Good(t *testing.T) {
diff --git a/pkg/agentic/remote_test.go b/pkg/agentic/remote_test.go
index c3c4205..0d835b9 100644
--- a/pkg/agentic/remote_test.go
+++ b/pkg/agentic/remote_test.go
@@ -43,3 +43,38 @@ func TestRemote_RemoteToken_Good_EnvPrecedence(t *testing.T) {
token := remoteToken("PRIO")
assert.Equal(t, "specific-token", token, "host-specific env should take precedence")
}
+
+// --- resolveHost Bad/Ugly ---
+
+func TestRemote_ResolveHost_Bad(t *testing.T) {
+ // Empty string — returns empty host with default port appended
+ result := resolveHost("")
+ assert.Equal(t, ":9101", result)
+}
+
+func TestRemote_ResolveHost_Ugly(t *testing.T) {
+ // Unicode host name — not an alias, no colon, so default port appended
+ result := resolveHost("\u00e9nchantr\u00efx")
+ assert.Equal(t, "\u00e9nchantr\u00efx:9101", result)
+}
+
+// --- remoteToken Bad/Ugly ---
+
+func TestRemote_RemoteToken_Bad(t *testing.T) {
+ // Host with no matching env var and no file — returns empty
+ t.Setenv("AGENT_TOKEN_NOHOST", "")
+ t.Setenv("MCP_AUTH_TOKEN", "")
+ t.Setenv("DIR_HOME", t.TempDir()) // no token files
+ token := remoteToken("nohost")
+ assert.Equal(t, "", token)
+}
+
+func TestRemote_RemoteToken_Ugly(t *testing.T) {
+ // Host name with dashes and dots — creates odd env key like AGENT_TOKEN_MY-HOST.LOCAL
+ // Env lookup will use the exact uppercased key
+ t.Setenv("AGENT_TOKEN_MY-HOST.LOCAL", "")
+ t.Setenv("MCP_AUTH_TOKEN", "")
+ t.Setenv("DIR_HOME", t.TempDir())
+ token := remoteToken("my-host.local")
+ assert.Equal(t, "", token)
+}
diff --git a/pkg/agentic/scan_test.go b/pkg/agentic/scan_test.go
index 1701e61..d9cb467 100644
--- a/pkg/agentic/scan_test.go
+++ b/pkg/agentic/scan_test.go
@@ -74,6 +74,29 @@ func mockScanServer(t *testing.T) *httptest.Server {
// --- scan ---
+func TestScan_Scan_Good(t *testing.T) {
+ srv := mockScanServer(t)
+ s := &PrepSubsystem{
+ forge: forge.NewForge(srv.URL, "test-token"),
+ forgeURL: srv.URL,
+ forgeToken: "test-token",
+ client: srv.Client(),
+ backoff: make(map[string]time.Time),
+ failCount: make(map[string]int),
+ }
+
+ _, out, err := s.scan(context.Background(), nil, ScanInput{Org: "core"})
+ require.NoError(t, err)
+ assert.True(t, out.Success)
+ assert.Greater(t, out.Count, 0)
+ // Verify issues contain repos from mock server
+ repos := make(map[string]bool)
+ for _, iss := range out.Issues {
+ repos[iss.Repo] = true
+ }
+ assert.True(t, repos["go-io"] || repos["go-log"], "should contain issues from mock repos")
+}
+
func TestScan_Good_AllRepos(t *testing.T) {
srv := mockScanServer(t)
s := &PrepSubsystem{