From c0bc7675a13e20f8e5ce7132ea122c2afeeec465 Mon Sep 17 00:00:00 2001 From: Snider Date: Wed, 25 Mar 2026 09:31:38 +0000 Subject: [PATCH] =?UTF-8?q?test:=20batch=204=20=E2=80=94=20fill=2036=20tes?= =?UTF-8?q?table=20gaps,=20802=20tests,=20AX-7=2089%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - commands.go: factory wrapper Good/Bad/Ugly - dispatch.go: containerCommand Bad - queue.go: UnmarshalYAML/loadAgentsConfig Good/Bad/Ugly - remote.go: resolveHost/remoteToken Bad/Ugly - remote_client.go: setHeaders Bad - prep.go: TestPrepWorkspace/TestBuildPrompt public API GBU - prep.go: sanitise Good tests (collapseRepeatedRune, sanitisePlanSlug, trimRuneEdges) - ingest.go: ingestFindings/createIssueViaAPI Ugly - scan.go: scan Good - runner.go: Poke Ugly, StartRunner Bad/Ugly - process_register.go: ProcessRegister Good/Bad/Ugly AX-7: 462/516 filled (89%), 152/172 functions complete Coverage: 77.2%, 802 tests Co-Authored-By: Virgil --- pkg/agentic/commands_test.go | 62 +++++++++++++++ pkg/agentic/dispatch_test.go | 14 ++++ pkg/agentic/ingest_test.go | 56 +++++++++++++ pkg/agentic/prep_extra_test.go | 29 +++++++ pkg/agentic/prep_test.go | 128 ++++++++++++++++++++++++++++++ pkg/agentic/queue_extra_test.go | 100 +++++++++++++++++++++++ pkg/agentic/queue_logic_test.go | 51 ++++++++++++ pkg/agentic/register_test.go | 30 +++++++ pkg/agentic/remote_client_test.go | 13 +++ pkg/agentic/remote_test.go | 35 ++++++++ pkg/agentic/scan_test.go | 23 ++++++ 11 files changed, 541 insertions(+) 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{