agent/pkg/agentic/status_extra_test.go
Virgil 454f7a8e96 feat(agentic): add status filters
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-31 18:55:26 +00:00

757 lines
22 KiB
Go

// SPDX-License-Identifier: EUPL-1.2
package agentic
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"time"
core "dappco.re/go/core"
"dappco.re/go/core/forge"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// coreWithRunnerActions builds a Core with stub runner.start/stop/kill Actions
// so shutdown tool tests can verify delegation without a real runner service.
func coreWithRunnerActions() *core.Core {
c := core.New()
c.Action("runner.start", func(_ context.Context, _ core.Options) core.Result { return core.Result{OK: true} })
c.Action("runner.stop", func(_ context.Context, _ core.Options) core.Result { return core.Result{OK: true} })
c.Action("runner.kill", func(_ context.Context, _ core.Options) core.Result { return core.Result{OK: true} })
return c
}
// --- status tool ---
func TestStatus_EmptyWorkspace_Good(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
require.True(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK)
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
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, 0, out.Total)
assert.Equal(t, 0, out.Running)
assert.Equal(t, 0, out.Completed)
}
func TestStatus_MixedWorkspaces_Good(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsRoot := core.JoinPath(root, "workspace")
// Create completed workspace (old layout)
ws1 := core.JoinPath(wsRoot, "task-1")
require.True(t, fs.EnsureDir(ws1).OK)
require.NoError(t, writeStatus(ws1, &WorkspaceStatus{
Status: "completed",
Repo: "go-io",
Agent: "codex",
}))
// Create failed workspace (old layout)
ws2 := core.JoinPath(wsRoot, "task-2")
require.True(t, fs.EnsureDir(ws2).OK)
require.NoError(t, writeStatus(ws2, &WorkspaceStatus{
Status: "failed",
Repo: "go-log",
Agent: "claude",
}))
// Create blocked workspace (old layout)
ws3 := core.JoinPath(wsRoot, "task-3")
require.True(t, fs.EnsureDir(ws3).OK)
require.NoError(t, writeStatus(ws3, &WorkspaceStatus{
Status: "blocked",
Repo: "agent",
Agent: "gemini",
Question: "Which API version?",
}))
// Create queued workspace (old layout)
ws4 := core.JoinPath(wsRoot, "task-4")
require.True(t, fs.EnsureDir(ws4).OK)
require.NoError(t, writeStatus(ws4, &WorkspaceStatus{
Status: "queued",
Repo: "go-mcp",
Agent: "codex",
}))
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
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, 4, out.Total)
assert.Equal(t, 1, out.Completed)
assert.Equal(t, 1, out.Failed)
assert.Equal(t, 1, out.Queued)
assert.Len(t, out.Blocked, 1)
assert.Equal(t, "Which API version?", out.Blocked[0].Question)
assert.Equal(t, "agent", out.Blocked[0].Repo)
}
func TestStatus_FilteredWorkspaces_Good(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsRoot := core.JoinPath(root, "workspace")
ws1 := core.JoinPath(wsRoot, "task-1")
require.True(t, fs.EnsureDir(ws1).OK)
require.NoError(t, writeStatus(ws1, &WorkspaceStatus{
Status: "completed",
Repo: "go-io",
Agent: "codex",
}))
ws2 := core.JoinPath(wsRoot, "task-2")
require.True(t, fs.EnsureDir(ws2).OK)
require.NoError(t, writeStatus(ws2, &WorkspaceStatus{
Status: "blocked",
Repo: "go-log",
Agent: "claude",
Question: "Which log format?",
}))
ws3 := core.JoinPath(wsRoot, "task-3")
require.True(t, fs.EnsureDir(ws3).OK)
require.NoError(t, writeStatus(ws3, &WorkspaceStatus{
Status: "running",
Repo: "agent",
Agent: "gemini",
}))
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
_, out, err := s.status(context.Background(), nil, StatusInput{
Workspace: "task-2",
Status: "blocked",
Limit: 1,
})
require.NoError(t, err)
assert.Equal(t, 1, out.Total)
assert.Equal(t, 0, out.Failed)
assert.Equal(t, 0, 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_DeepLayout_Good(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsRoot := core.JoinPath(root, "workspace")
// Create workspace in deep layout (org/repo/task)
ws := core.JoinPath(wsRoot, "core", "go-io", "task-15")
require.True(t, fs.EnsureDir(ws).OK)
require.NoError(t, writeStatus(ws, &WorkspaceStatus{
Status: "completed",
Repo: "go-io",
Agent: "codex",
}))
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
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.Equal(t, 1, out.Completed)
}
func TestStatus_CorruptStatus_Good(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsRoot := core.JoinPath(root, "workspace")
ws := core.JoinPath(wsRoot, "corrupt-ws")
require.True(t, fs.EnsureDir(ws).OK)
require.True(t, fs.Write(core.JoinPath(ws, "status.json"), "invalid-json{{{").OK)
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
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.Equal(t, 1, out.Failed) // corrupt status counts as failed
}
// --- shutdown tools ---
func TestShutdown_DispatchStart_Good(t *testing.T) {
// dispatchStart delegates to runner.start Action — verify it calls the Action and returns success.
c := coreWithRunnerActions()
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}),
frozen: true,
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.Contains(t, out.Message, "started")
}
func TestShutdown_ShutdownGraceful_Good(t *testing.T) {
// shutdownGraceful delegates to runner.stop Action — verify it returns success and frozen message.
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
c := coreWithRunnerActions()
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}),
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.Contains(t, out.Message, "frozen")
}
func TestShutdown_ShutdownNow_Good_EmptyWorkspace(t *testing.T) {
// shutdownNow delegates to runner.kill Action — verify it returns success.
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
require.True(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK)
c := coreWithRunnerActions()
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}),
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.Contains(t, out.Message, "killed all agents, cleared queue")
}
func TestShutdown_ShutdownNow_Good_ClearsQueued(t *testing.T) {
// shutdownNow delegates to runner.kill Action — queue clearing is now
// handled by the runner service. Verify the delegation returns success.
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsRoot := core.JoinPath(root, "workspace")
// Create queued workspaces (runner.kill would clear these in production)
for i := 1; i <= 3; i++ {
ws := core.JoinPath(wsRoot, "task-"+itoa(i))
require.True(t, fs.EnsureDir(ws).OK)
require.NoError(t, writeStatus(ws, &WorkspaceStatus{
Status: "queued",
Repo: "go-io",
Agent: "codex",
}))
}
c := coreWithRunnerActions()
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}),
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.Contains(t, out.Message, "killed all agents, cleared queue")
}
// --- brainRecall ---
func TestPrep_BrainRecall_Good_Success(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "POST", r.Method)
assert.Contains(t, r.URL.Path, "/v1/brain/recall")
w.Write([]byte(core.JSONMarshalString(map[string]any{
"memories": []map[string]any{
{"type": "architecture", "content": "Core uses DI pattern", "project": "go-core"},
{"type": "convention", "content": "Use E() for errors", "project": "go-core"},
},
})))
}))
t.Cleanup(srv.Close)
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
brainURL: srv.URL,
brainKey: "test-brain-key",
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
result, count := s.brainRecall(context.Background(), "go-core")
assert.Equal(t, 2, count)
assert.Contains(t, result, "Core uses DI pattern")
assert.Contains(t, result, "Use E() for errors")
}
func TestPrep_BrainRecall_Good_NoMemories(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(core.JSONMarshalString(map[string]any{
"memories": []map[string]any{},
})))
}))
t.Cleanup(srv.Close)
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
brainURL: srv.URL,
brainKey: "test-brain-key",
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
result, count := s.brainRecall(context.Background(), "go-core")
assert.Equal(t, 0, count)
assert.Empty(t, result)
}
func TestPrep_BrainRecall_Bad_NoBrainKey(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
brainKey: "",
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
result, count := s.brainRecall(context.Background(), "go-core")
assert.Equal(t, 0, count)
assert.Empty(t, result)
}
func TestPrep_BrainRecall_Bad_ServerError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500)
}))
t.Cleanup(srv.Close)
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
brainURL: srv.URL,
brainKey: "test-brain-key",
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
result, count := s.brainRecall(context.Background(), "go-core")
assert.Equal(t, 0, count)
assert.Empty(t, result)
}
// --- prepWorkspace ---
func TestPrep_PrepWorkspace_Bad_NoRepo(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
_, _, err := s.prepWorkspace(context.Background(), nil, PrepInput{})
assert.Error(t, err)
assert.Contains(t, err.Error(), "repo is required")
}
func TestPrep_PrepWorkspace_Bad_NoIdentifier(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
codePath: t.TempDir(),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
_, _, err := s.prepWorkspace(context.Background(), nil, PrepInput{
Repo: "go-io",
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "one of issue, pr, branch, or tag is required")
}
func TestPrep_PrepWorkspace_Bad_InvalidRepoName(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
codePath: t.TempDir(),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
_, _, err := s.prepWorkspace(context.Background(), nil, PrepInput{
Repo: "..",
Issue: 1,
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid repo name")
}
// --- listPRs ---
func TestPr_ListPRs_Good_SpecificRepo(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Return mock PRs
w.Write([]byte(core.JSONMarshalString([]map[string]any{
{
"number": 1,
"title": "Fix tests",
"state": "open",
"html_url": "https://forge.test/core/go-io/pulls/1",
"mergeable": true,
"user": map[string]any{"login": "virgil"},
"head": map[string]any{"ref": "agent/fix-tests"},
"base": map[string]any{"ref": "dev"},
"labels": []map[string]any{{"name": "agentic"}},
},
})))
}))
t.Cleanup(srv.Close)
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
forge: forge.NewForge(srv.URL, "test-token"),
forgeURL: srv.URL,
forgeToken: "test-token",
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
_, out, err := s.listPRs(context.Background(), nil, ListPRsInput{
Repo: "go-io",
})
require.NoError(t, err)
assert.True(t, out.Success)
assert.Equal(t, 1, out.Count)
assert.Equal(t, "Fix tests", out.PRs[0].Title)
assert.Equal(t, "virgil", out.PRs[0].Author)
assert.Equal(t, "agent/fix-tests", out.PRs[0].Branch)
assert.Contains(t, out.PRs[0].Labels, "agentic")
}
// --- Poke ---
func TestRunner_Poke_Good_SendsSignal(t *testing.T) {
// Poke is now a no-op — queue poke is owned by pkg/runner.Service.
// Verify it does not panic and does not send to the channel.
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
pokeCh: make(chan struct{}, 1),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
assert.NotPanics(t, func() { s.Poke() })
assert.Len(t, s.pokeCh, 0, "no-op Poke should not send to channel")
}
func TestRunner_Poke_Good_NonBlocking(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
pokeCh: make(chan struct{}, 1),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
// Fill the channel
s.pokeCh <- struct{}{}
// Second poke should not block
assert.NotPanics(t, func() {
s.Poke()
})
}
func TestRunner_Poke_Bad_NilChannel(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
pokeCh: nil,
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
// Should not panic with nil channel
assert.NotPanics(t, func() {
s.Poke()
})
}
// --- ReadStatusResult / writeStatus (extended) ---
func TestStatus_WriteRead_Good_WithPID(t *testing.T) {
dir := t.TempDir()
st := &WorkspaceStatus{
Status: "running",
Agent: "codex",
Repo: "go-io",
Task: "Fix it",
PID: 12345,
}
err := writeStatus(dir, st)
require.NoError(t, err)
// Read it back
got := mustReadStatus(t, dir)
assert.Equal(t, "running", got.Status)
assert.Equal(t, "codex", got.Agent)
assert.Equal(t, "go-io", got.Repo)
assert.Equal(t, 12345, got.PID)
assert.False(t, got.UpdatedAt.IsZero())
}
func TestStatus_WriteRead_Good_AllFields(t *testing.T) {
dir := t.TempDir()
now := time.Now()
st := &WorkspaceStatus{
Status: "blocked",
Agent: "claude",
Repo: "go-log",
Org: "core",
Task: "Add structured logging",
Branch: "agent/add-logging",
Issue: 42,
PID: 99999,
StartedAt: now,
Question: "Which log format?",
Runs: 3,
PRURL: "https://forge.test/core/go-log/pulls/5",
}
err := writeStatus(dir, st)
require.NoError(t, err)
got := mustReadStatus(t, dir)
assert.Equal(t, "blocked", got.Status)
assert.Equal(t, "claude", got.Agent)
assert.Equal(t, "core", got.Org)
assert.Equal(t, 42, got.Issue)
assert.Equal(t, "Which log format?", got.Question)
assert.Equal(t, 3, got.Runs)
assert.Equal(t, "https://forge.test/core/go-log/pulls/5", got.PRURL)
}
// --- OnStartup / OnShutdown ---
func TestPrep_OnShutdown_Good(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
frozen: false,
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
r := s.OnShutdown(context.Background())
assert.True(t, r.OK)
assert.True(t, s.frozen)
}
// --- drainQueue ---
func TestQueue_DrainQueue_Good_FrozenDoesNothing(t *testing.T) {
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
frozen: true,
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
// Should return immediately when frozen
assert.NotPanics(t, func() {
s.drainQueue()
})
}
// --- shutdownNow (Ugly — deep layout with queued status) ---
func TestShutdown_ShutdownNow_Ugly_DeepLayout(t *testing.T) {
// shutdownNow delegates to runner.kill Action — queue clearing is now
// handled by the runner service. Verify delegation with deep-layout workspaces.
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsRoot := core.JoinPath(root, "workspace")
// Create workspace in deep layout (org/repo/task)
ws := core.JoinPath(wsRoot, "core", "go-io", "task-5")
require.True(t, fs.EnsureDir(ws).OK)
require.NoError(t, writeStatus(ws, &WorkspaceStatus{
Status: "queued",
Repo: "go-io",
Agent: "codex",
Task: "Add tests",
}))
c := coreWithRunnerActions()
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}),
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.Contains(t, out.Message, "killed all agents, cleared queue")
}
// --- dispatchStart Bad/Ugly ---
func TestShutdown_DispatchStart_Bad_NilPokeCh(t *testing.T) {
// dispatchStart delegates to runner.start Action — verify it succeeds with nil pokeCh.
c := coreWithRunnerActions()
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}),
frozen: true,
pokeCh: nil,
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.Contains(t, out.Message, "started")
}
func TestShutdown_DispatchStart_Ugly_AlreadyUnfrozen(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
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{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
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) {
// shutdownGraceful delegates to runner.stop Action — verify it returns success
// even when workspaces exist.
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsRoot := core.JoinPath(root, "workspace")
// Create workspaces with various statuses
for _, name := range []string{"ws-completed", "ws-failed", "ws-blocked"} {
ws := core.JoinPath(wsRoot, name)
require.True(t, fs.EnsureDir(ws).OK)
require.NoError(t, writeStatus(ws, &WorkspaceStatus{
Status: "completed",
Repo: "go-io",
Agent: "codex",
}))
}
c := coreWithRunnerActions()
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}),
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.Contains(t, out.Message, "frozen")
}
// --- shutdownNow Bad ---
func TestShutdown_ShutdownNow_Bad_NoRunningPIDs(t *testing.T) {
// shutdownNow delegates to runner.kill Action — verify it returns success
// even when there are no running PIDs. Kill counting is now in pkg/runner.
root := t.TempDir()
t.Setenv("CORE_WORKSPACE", root)
wsRoot := core.JoinPath(root, "workspace")
// Create completed workspaces only (no running PIDs to kill)
for i := 1; i <= 2; i++ {
ws := core.JoinPath(wsRoot, "task-"+itoa(i))
require.True(t, fs.EnsureDir(ws).OK)
require.NoError(t, writeStatus(ws, &WorkspaceStatus{
Status: "completed",
Repo: "go-io",
Agent: "codex",
}))
}
c := coreWithRunnerActions()
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}),
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.Contains(t, out.Message, "killed all agents, cleared queue")
}