// SPDX-License-Identifier: EUPL-1.2 package agentic import ( "os/exec" "path/filepath" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // --- countRunningByModel --- func TestCountRunningByModel_Good_Empty(t *testing.T) { root := t.TempDir() t.Setenv("CORE_WORKSPACE", root) s := &PrepSubsystem{} assert.Equal(t, 0, s.countRunningByModel("claude:opus")) } func TestCountRunningByModel_Good_SkipsNonRunning(t *testing.T) { root := t.TempDir() t.Setenv("CORE_WORKSPACE", root) // Completed workspace — must not be counted ws := filepath.Join(root, "workspace", "test-ws") require.True(t, fs.EnsureDir(ws).OK) require.NoError(t, writeStatus(ws, &WorkspaceStatus{ Status: "completed", Agent: "codex:gpt-5.4", PID: 0, })) s := &PrepSubsystem{} assert.Equal(t, 0, s.countRunningByModel("codex:gpt-5.4")) } func TestCountRunningByModel_Good_SkipsMismatchedModel(t *testing.T) { root := t.TempDir() t.Setenv("CORE_WORKSPACE", root) ws := filepath.Join(root, "workspace", "model-ws") require.True(t, fs.EnsureDir(ws).OK) require.NoError(t, writeStatus(ws, &WorkspaceStatus{ Status: "running", Agent: "gemini:flash", PID: 0, })) s := &PrepSubsystem{} // Asking for gemini:pro — must not count gemini:flash assert.Equal(t, 0, s.countRunningByModel("gemini:pro")) } func TestCountRunningByModel_Good_DeepLayout(t *testing.T) { root := t.TempDir() t.Setenv("CORE_WORKSPACE", root) // Deep layout: workspace/org/repo/task-N/status.json ws := filepath.Join(root, "workspace", "core", "go-io", "task-1") require.True(t, fs.EnsureDir(ws).OK) require.NoError(t, writeStatus(ws, &WorkspaceStatus{ Status: "completed", Agent: "codex:gpt-5.4", })) s := &PrepSubsystem{} // Completed, so count is still 0 assert.Equal(t, 0, s.countRunningByModel("codex:gpt-5.4")) } // --- drainQueue --- func TestDrainQueue_Good_FrozenReturnsImmediately(t *testing.T) { root := t.TempDir() t.Setenv("CORE_WORKSPACE", root) s := &PrepSubsystem{frozen: true, backoff: make(map[string]time.Time), failCount: make(map[string]int)} // Must not panic and must not block assert.NotPanics(t, func() { s.drainQueue() }) } func TestDrainQueue_Good_EmptyWorkspace(t *testing.T) { root := t.TempDir() t.Setenv("CORE_WORKSPACE", root) s := &PrepSubsystem{frozen: false, backoff: make(map[string]time.Time), failCount: make(map[string]int)} // No workspaces — must return without error/panic assert.NotPanics(t, func() { s.drainQueue() }) } // --- Poke --- func TestPoke_Good_NilChannel(t *testing.T) { s := &PrepSubsystem{pokeCh: nil} // Must not panic when pokeCh is nil assert.NotPanics(t, func() { s.Poke() }) } func TestPoke_Good_ChannelReceivesSignal(t *testing.T) { s := &PrepSubsystem{} s.pokeCh = make(chan struct{}, 1) s.Poke() assert.Len(t, s.pokeCh, 1, "poke should enqueue one signal") } func TestPoke_Good_NonBlockingWhenFull(t *testing.T) { s := &PrepSubsystem{} s.pokeCh = make(chan struct{}, 1) // Pre-fill the channel s.pokeCh <- struct{}{} // Second poke must not block or panic assert.NotPanics(t, func() { s.Poke() }) assert.Len(t, s.pokeCh, 1, "channel length should remain 1") } // --- StartRunner --- func TestStartRunner_Good_CreatesPokeCh(t *testing.T) { root := t.TempDir() t.Setenv("CORE_WORKSPACE", root) t.Setenv("CORE_AGENT_DISPATCH", "") s := NewPrep() assert.Nil(t, s.pokeCh) s.StartRunner() assert.NotNil(t, s.pokeCh, "StartRunner should initialise pokeCh") } func TestStartRunner_Good_FrozenByDefault(t *testing.T) { root := t.TempDir() t.Setenv("CORE_WORKSPACE", root) t.Setenv("CORE_AGENT_DISPATCH", "") s := NewPrep() s.StartRunner() assert.True(t, s.frozen, "queue should be frozen by default") } func TestStartRunner_Good_AutoStartEnvVar(t *testing.T) { root := t.TempDir() t.Setenv("CORE_WORKSPACE", root) t.Setenv("CORE_AGENT_DISPATCH", "1") s := NewPrep() s.StartRunner() assert.False(t, s.frozen, "CORE_AGENT_DISPATCH=1 should unfreeze the queue") } // --- DefaultBranch --- func TestDefaultBranch_Good_DefaultsToMain(t *testing.T) { // Non-git temp dir — git commands fail, fallback is "main" dir := t.TempDir() branch := DefaultBranch(dir) assert.Equal(t, "main", branch) } func TestDefaultBranch_Good_RealGitRepo(t *testing.T) { dir := t.TempDir() // Init a real git repo with a main branch require.NoError(t, runGitInit(dir)) branch := DefaultBranch(dir) // Any valid branch name — just must not panic or be empty assert.NotEmpty(t, branch) } // --- LocalFs --- func TestLocalFs_Good_NonNil(t *testing.T) { f := LocalFs() assert.NotNil(t, f, "LocalFs should return a non-nil *core.Fs") } func TestLocalFs_Good_CanRead(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "hello.txt") require.True(t, fs.Write(path, "hello").OK) f := LocalFs() r := f.Read(path) assert.True(t, r.OK) assert.Equal(t, "hello", r.Value.(string)) } // --- helpers --- // runGitInit initialises a bare git repo with one commit so branch detection works. func runGitInit(dir string) error { cmds := [][]string{ {"git", "init", "-b", "main"}, {"git", "config", "user.email", "test@test.com"}, {"git", "config", "user.name", "Test"}, {"git", "commit", "--allow-empty", "-m", "init"}, } for _, args := range cmds { cmd := exec.Command(args[0], args[1:]...) cmd.Dir = dir if err := cmd.Run(); err != nil { return err } } return nil }