test(agentic): add unit tests for paths, status, queue, plans
Some checks failed
CI / test (push) Failing after 3s
Some checks failed
CI / test (push) Failing after 3s
Coverage: 4.2% → 9.2%. Tests for extractPRNumber, workspace status scanning, queue management, and plan file operations. Remaining coverage requires integration tests (git/forge/process). Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
267550b288
commit
726a384873
4 changed files with 675 additions and 0 deletions
212
pkg/agentic/plan_test.go
Normal file
212
pkg/agentic/plan_test.go
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package agentic
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
coreio "forge.lthn.ai/core/go-io"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPlanPath_Good(t *testing.T) {
|
||||
assert.Equal(t, "/tmp/plans/my-plan-abc123.json", planPath("/tmp/plans", "my-plan-abc123"))
|
||||
assert.Equal(t, "/data/test.json", planPath("/data", "test"))
|
||||
}
|
||||
|
||||
func TestWritePlan_Good(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
plan := &Plan{
|
||||
ID: "test-plan-abc123",
|
||||
Title: "Test Plan",
|
||||
Status: "draft",
|
||||
Objective: "Test the plan system",
|
||||
}
|
||||
|
||||
path, err := writePlan(dir, plan)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, filepath.Join(dir, "test-plan-abc123.json"), path)
|
||||
|
||||
// Verify file exists
|
||||
assert.True(t, coreio.Local.IsFile(path))
|
||||
}
|
||||
|
||||
func TestWritePlan_Good_CreatesDirectory(t *testing.T) {
|
||||
base := t.TempDir()
|
||||
dir := filepath.Join(base, "nested", "plans")
|
||||
|
||||
plan := &Plan{
|
||||
ID: "nested-plan-abc123",
|
||||
Title: "Nested",
|
||||
Status: "draft",
|
||||
Objective: "Test nested directory creation",
|
||||
}
|
||||
|
||||
path, err := writePlan(dir, plan)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, path, "nested-plan-abc123.json")
|
||||
}
|
||||
|
||||
func TestReadPlan_Good(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
original := &Plan{
|
||||
ID: "read-test-abc123",
|
||||
Title: "Read Test",
|
||||
Status: "ready",
|
||||
Repo: "go-io",
|
||||
Org: "core",
|
||||
Objective: "Verify plan reading works",
|
||||
Phases: []Phase{
|
||||
{Number: 1, Name: "Setup", Status: "done"},
|
||||
{Number: 2, Name: "Implement", Status: "pending"},
|
||||
},
|
||||
Notes: "Some notes",
|
||||
Agent: "claude:opus",
|
||||
}
|
||||
|
||||
_, err := writePlan(dir, original)
|
||||
require.NoError(t, err)
|
||||
|
||||
read, err := readPlan(dir, "read-test-abc123")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, original.ID, read.ID)
|
||||
assert.Equal(t, original.Title, read.Title)
|
||||
assert.Equal(t, original.Status, read.Status)
|
||||
assert.Equal(t, original.Repo, read.Repo)
|
||||
assert.Equal(t, original.Org, read.Org)
|
||||
assert.Equal(t, original.Objective, read.Objective)
|
||||
assert.Len(t, read.Phases, 2)
|
||||
assert.Equal(t, "Setup", read.Phases[0].Name)
|
||||
assert.Equal(t, "done", read.Phases[0].Status)
|
||||
assert.Equal(t, "Implement", read.Phases[1].Name)
|
||||
assert.Equal(t, "pending", read.Phases[1].Status)
|
||||
assert.Equal(t, "Some notes", read.Notes)
|
||||
assert.Equal(t, "claude:opus", read.Agent)
|
||||
}
|
||||
|
||||
func TestReadPlan_Bad_NotFound(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
_, err := readPlan(dir, "nonexistent-plan")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestReadPlan_Bad_InvalidJSON(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, coreio.Local.Write(filepath.Join(dir, "bad-json.json"), "{broken"))
|
||||
|
||||
_, err := readPlan(dir, "bad-json")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestWriteReadPlan_Good_Roundtrip(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
plan := &Plan{
|
||||
ID: "roundtrip-abc123",
|
||||
Title: "Roundtrip Test",
|
||||
Status: "in_progress",
|
||||
Repo: "agent",
|
||||
Org: "core",
|
||||
Objective: "Ensure write-read roundtrip works",
|
||||
Phases: []Phase{
|
||||
{Number: 1, Name: "Phase One", Status: "done", Criteria: []string{"tests pass", "coverage > 80%"}, Tests: 5},
|
||||
{Number: 2, Name: "Phase Two", Status: "in_progress", Notes: "Working on it"},
|
||||
{Number: 3, Name: "Phase Three", Status: "pending"},
|
||||
},
|
||||
Notes: "Important plan",
|
||||
Agent: "gemini",
|
||||
}
|
||||
|
||||
_, err := writePlan(dir, plan)
|
||||
require.NoError(t, err)
|
||||
|
||||
read, err := readPlan(dir, "roundtrip-abc123")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, plan.Title, read.Title)
|
||||
assert.Equal(t, plan.Status, read.Status)
|
||||
assert.Len(t, read.Phases, 3)
|
||||
assert.Equal(t, []string{"tests pass", "coverage > 80%"}, read.Phases[0].Criteria)
|
||||
assert.Equal(t, 5, read.Phases[0].Tests)
|
||||
assert.Equal(t, "Working on it", read.Phases[1].Notes)
|
||||
}
|
||||
|
||||
func TestGeneratePlanID_Good_Slugifies(t *testing.T) {
|
||||
id := generatePlanID("Add Unit Tests for Agentic")
|
||||
assert.True(t, strings.HasPrefix(id, "add-unit-tests-for-agentic"), "got: %s", id)
|
||||
// Should have random suffix
|
||||
parts := strings.Split(id, "-")
|
||||
assert.True(t, len(parts) >= 5, "expected slug with random suffix, got: %s", id)
|
||||
}
|
||||
|
||||
func TestGeneratePlanID_Good_TruncatesLong(t *testing.T) {
|
||||
id := generatePlanID("This is a very long title that should be truncated to a reasonable length for file naming purposes")
|
||||
// Slug part (before random suffix) should be <= 30 chars
|
||||
lastDash := strings.LastIndex(id, "-")
|
||||
slug := id[:lastDash]
|
||||
assert.True(t, len(slug) <= 36, "slug too long: %s (%d chars)", slug, len(slug))
|
||||
}
|
||||
|
||||
func TestGeneratePlanID_Good_HandlesSpecialChars(t *testing.T) {
|
||||
id := generatePlanID("Fix bug #123: auth & session!")
|
||||
assert.True(t, strings.Contains(id, "fix-bug"), "got: %s", id)
|
||||
assert.NotContains(t, id, "#")
|
||||
assert.NotContains(t, id, "!")
|
||||
assert.NotContains(t, id, "&")
|
||||
}
|
||||
|
||||
func TestGeneratePlanID_Good_Unique(t *testing.T) {
|
||||
id1 := generatePlanID("Same Title")
|
||||
id2 := generatePlanID("Same Title")
|
||||
assert.NotEqual(t, id1, id2, "IDs should differ due to random suffix")
|
||||
}
|
||||
|
||||
func TestValidPlanStatus_Good_AllValid(t *testing.T) {
|
||||
validStatuses := []string{"draft", "ready", "in_progress", "needs_verification", "verified", "approved"}
|
||||
for _, s := range validStatuses {
|
||||
assert.True(t, validPlanStatus(s), "expected %q to be valid", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidPlanStatus_Bad_Invalid(t *testing.T) {
|
||||
invalidStatuses := []string{"", "running", "completed", "cancelled", "archived", "DRAFT", "Draft"}
|
||||
for _, s := range invalidStatuses {
|
||||
assert.False(t, validPlanStatus(s), "expected %q to be invalid", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWritePlan_Good_OverwriteExisting(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
plan := &Plan{
|
||||
ID: "overwrite-abc123",
|
||||
Title: "Original",
|
||||
Status: "draft",
|
||||
Objective: "Original objective",
|
||||
}
|
||||
|
||||
_, err := writePlan(dir, plan)
|
||||
require.NoError(t, err)
|
||||
|
||||
plan.Title = "Updated"
|
||||
plan.Status = "ready"
|
||||
_, err = writePlan(dir, plan)
|
||||
require.NoError(t, err)
|
||||
|
||||
read, err := readPlan(dir, "overwrite-abc123")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Updated", read.Title)
|
||||
assert.Equal(t, "ready", read.Status)
|
||||
}
|
||||
|
||||
func TestReadPlan_Ugly_EmptyFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, coreio.Local.Write(filepath.Join(dir, "empty.json"), ""))
|
||||
|
||||
_, err := readPlan(dir, "empty")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
192
pkg/agentic/prep_test.go
Normal file
192
pkg/agentic/prep_test.go
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package agentic
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
coreio "forge.lthn.ai/core/go-io"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEnvOr_Good_EnvSet(t *testing.T) {
|
||||
t.Setenv("TEST_ENVVAR_CUSTOM", "custom-value")
|
||||
assert.Equal(t, "custom-value", envOr("TEST_ENVVAR_CUSTOM", "default"))
|
||||
}
|
||||
|
||||
func TestEnvOr_Good_Fallback(t *testing.T) {
|
||||
t.Setenv("TEST_ENVVAR_MISSING", "")
|
||||
assert.Equal(t, "default-value", envOr("TEST_ENVVAR_MISSING", "default-value"))
|
||||
}
|
||||
|
||||
func TestEnvOr_Good_UnsetUsesFallback(t *testing.T) {
|
||||
t.Setenv("TEST_ENVVAR_TOTALLY_MISSING", "")
|
||||
assert.Equal(t, "fallback", envOr("TEST_ENVVAR_TOTALLY_MISSING", "fallback"))
|
||||
}
|
||||
|
||||
func TestDetectLanguage_Good_Go(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, coreio.Local.Write(filepath.Join(dir, "go.mod"), "module test"))
|
||||
assert.Equal(t, "go", detectLanguage(dir))
|
||||
}
|
||||
|
||||
func TestDetectLanguage_Good_PHP(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, coreio.Local.Write(filepath.Join(dir, "composer.json"), "{}"))
|
||||
assert.Equal(t, "php", detectLanguage(dir))
|
||||
}
|
||||
|
||||
func TestDetectLanguage_Good_TypeScript(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, coreio.Local.Write(filepath.Join(dir, "package.json"), "{}"))
|
||||
assert.Equal(t, "ts", detectLanguage(dir))
|
||||
}
|
||||
|
||||
func TestDetectLanguage_Good_Rust(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, coreio.Local.Write(filepath.Join(dir, "Cargo.toml"), "[package]"))
|
||||
assert.Equal(t, "rust", detectLanguage(dir))
|
||||
}
|
||||
|
||||
func TestDetectLanguage_Good_Python(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, coreio.Local.Write(filepath.Join(dir, "requirements.txt"), "flask"))
|
||||
assert.Equal(t, "py", detectLanguage(dir))
|
||||
}
|
||||
|
||||
func TestDetectLanguage_Good_Cpp(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, coreio.Local.Write(filepath.Join(dir, "CMakeLists.txt"), "cmake_minimum_required"))
|
||||
assert.Equal(t, "cpp", detectLanguage(dir))
|
||||
}
|
||||
|
||||
func TestDetectLanguage_Good_Docker(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, coreio.Local.Write(filepath.Join(dir, "Dockerfile"), "FROM alpine"))
|
||||
assert.Equal(t, "docker", detectLanguage(dir))
|
||||
}
|
||||
|
||||
func TestDetectLanguage_Good_DefaultsToGo(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
assert.Equal(t, "go", detectLanguage(dir))
|
||||
}
|
||||
|
||||
func TestDetectBuildCmd_Good(t *testing.T) {
|
||||
tests := []struct {
|
||||
file string
|
||||
content string
|
||||
expected string
|
||||
}{
|
||||
{"go.mod", "module test", "go build ./..."},
|
||||
{"composer.json", "{}", "composer install"},
|
||||
{"package.json", "{}", "npm run build"},
|
||||
{"requirements.txt", "flask", "pip install -e ."},
|
||||
{"Cargo.toml", "[package]", "cargo build"},
|
||||
{"CMakeLists.txt", "cmake", "cmake --build ."},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.file, func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, coreio.Local.Write(filepath.Join(dir, tt.file), tt.content))
|
||||
assert.Equal(t, tt.expected, detectBuildCmd(dir))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectBuildCmd_Good_DefaultsToGo(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
assert.Equal(t, "go build ./...", detectBuildCmd(dir))
|
||||
}
|
||||
|
||||
func TestDetectTestCmd_Good(t *testing.T) {
|
||||
tests := []struct {
|
||||
file string
|
||||
content string
|
||||
expected string
|
||||
}{
|
||||
{"go.mod", "module test", "go test ./..."},
|
||||
{"composer.json", "{}", "composer test"},
|
||||
{"package.json", "{}", "npm test"},
|
||||
{"requirements.txt", "flask", "pytest"},
|
||||
{"Cargo.toml", "[package]", "cargo test"},
|
||||
{"CMakeLists.txt", "cmake", "ctest"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.file, func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, coreio.Local.Write(filepath.Join(dir, tt.file), tt.content))
|
||||
assert.Equal(t, tt.expected, detectTestCmd(dir))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectTestCmd_Good_DefaultsToGo(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
assert.Equal(t, "go test ./...", detectTestCmd(dir))
|
||||
}
|
||||
|
||||
func TestNewPrep_Good_Defaults(t *testing.T) {
|
||||
t.Setenv("FORGE_TOKEN", "")
|
||||
t.Setenv("GITEA_TOKEN", "")
|
||||
t.Setenv("CORE_BRAIN_KEY", "")
|
||||
t.Setenv("FORGE_URL", "")
|
||||
t.Setenv("CORE_BRAIN_URL", "")
|
||||
t.Setenv("SPECS_PATH", "")
|
||||
t.Setenv("CODE_PATH", "")
|
||||
|
||||
s := NewPrep()
|
||||
assert.Equal(t, "https://forge.lthn.ai", s.forgeURL)
|
||||
assert.Equal(t, "https://api.lthn.sh", s.brainURL)
|
||||
assert.NotNil(t, s.client)
|
||||
}
|
||||
|
||||
func TestNewPrep_Good_EnvOverrides(t *testing.T) {
|
||||
t.Setenv("FORGE_URL", "https://custom-forge.example.com")
|
||||
t.Setenv("FORGE_TOKEN", "test-token")
|
||||
t.Setenv("CORE_BRAIN_URL", "https://custom-brain.example.com")
|
||||
t.Setenv("CORE_BRAIN_KEY", "brain-key-123")
|
||||
t.Setenv("SPECS_PATH", "/custom/specs")
|
||||
t.Setenv("CODE_PATH", "/custom/code")
|
||||
|
||||
s := NewPrep()
|
||||
assert.Equal(t, "https://custom-forge.example.com", s.forgeURL)
|
||||
assert.Equal(t, "test-token", s.forgeToken)
|
||||
assert.Equal(t, "https://custom-brain.example.com", s.brainURL)
|
||||
assert.Equal(t, "brain-key-123", s.brainKey)
|
||||
assert.Equal(t, "/custom/specs", s.specsPath)
|
||||
assert.Equal(t, "/custom/code", s.codePath)
|
||||
}
|
||||
|
||||
func TestNewPrep_Good_GiteaTokenFallback(t *testing.T) {
|
||||
t.Setenv("FORGE_TOKEN", "")
|
||||
t.Setenv("GITEA_TOKEN", "gitea-fallback-token")
|
||||
|
||||
s := NewPrep()
|
||||
assert.Equal(t, "gitea-fallback-token", s.forgeToken)
|
||||
}
|
||||
|
||||
func TestPrepSubsystem_Good_Name(t *testing.T) {
|
||||
s := &PrepSubsystem{}
|
||||
assert.Equal(t, "agentic", s.Name())
|
||||
}
|
||||
|
||||
func TestSetCompletionNotifier_Good(t *testing.T) {
|
||||
s := &PrepSubsystem{}
|
||||
assert.Nil(t, s.onComplete)
|
||||
|
||||
notifier := &mockNotifier{}
|
||||
s.SetCompletionNotifier(notifier)
|
||||
assert.NotNil(t, s.onComplete)
|
||||
}
|
||||
|
||||
type mockNotifier struct {
|
||||
poked bool
|
||||
}
|
||||
|
||||
func (m *mockNotifier) Poke() {
|
||||
m.poked = true
|
||||
}
|
||||
87
pkg/agentic/queue_test.go
Normal file
87
pkg/agentic/queue_test.go
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package agentic
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
coreio "forge.lthn.ai/core/go-io"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBaseAgent_Ugly_Empty(t *testing.T) {
|
||||
assert.Equal(t, "", baseAgent(""))
|
||||
}
|
||||
|
||||
func TestBaseAgent_Ugly_MultipleColons(t *testing.T) {
|
||||
// SplitN with N=2 should only split on first colon
|
||||
assert.Equal(t, "claude", baseAgent("claude:opus:extra"))
|
||||
}
|
||||
|
||||
func TestDispatchConfig_Good_Defaults(t *testing.T) {
|
||||
// loadAgentsConfig falls back to defaults when no config file exists
|
||||
s := &PrepSubsystem{codePath: t.TempDir()}
|
||||
t.Setenv("CORE_WORKSPACE", t.TempDir())
|
||||
|
||||
cfg := s.loadAgentsConfig()
|
||||
assert.Equal(t, "claude", cfg.Dispatch.DefaultAgent)
|
||||
assert.Equal(t, "coding", cfg.Dispatch.DefaultTemplate)
|
||||
assert.Equal(t, 1, cfg.Concurrency["claude"])
|
||||
assert.Equal(t, 3, cfg.Concurrency["gemini"])
|
||||
}
|
||||
|
||||
func TestCanDispatchAgent_Good_NoConfig(t *testing.T) {
|
||||
// With no running workspaces and default config, should be able to dispatch
|
||||
root := t.TempDir()
|
||||
t.Setenv("CORE_WORKSPACE", root)
|
||||
require.NoError(t, coreio.Local.EnsureDir(filepath.Join(root, "workspace")))
|
||||
|
||||
s := &PrepSubsystem{codePath: t.TempDir()}
|
||||
assert.True(t, s.canDispatchAgent("gemini"))
|
||||
}
|
||||
|
||||
func TestCanDispatchAgent_Good_UnknownAgent(t *testing.T) {
|
||||
// Unknown agent has no limit, so always allowed
|
||||
root := t.TempDir()
|
||||
t.Setenv("CORE_WORKSPACE", root)
|
||||
require.NoError(t, coreio.Local.EnsureDir(filepath.Join(root, "workspace")))
|
||||
|
||||
s := &PrepSubsystem{codePath: t.TempDir()}
|
||||
assert.True(t, s.canDispatchAgent("unknown-agent"))
|
||||
}
|
||||
|
||||
func TestCountRunningByAgent_Good_EmptyWorkspace(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
t.Setenv("CORE_WORKSPACE", root)
|
||||
require.NoError(t, coreio.Local.EnsureDir(filepath.Join(root, "workspace")))
|
||||
|
||||
s := &PrepSubsystem{}
|
||||
assert.Equal(t, 0, s.countRunningByAgent("gemini"))
|
||||
assert.Equal(t, 0, s.countRunningByAgent("claude"))
|
||||
}
|
||||
|
||||
func TestCountRunningByAgent_Good_NoRunning(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
t.Setenv("CORE_WORKSPACE", root)
|
||||
|
||||
// Create a workspace with completed status under workspace/
|
||||
ws := filepath.Join(root, "workspace", "test-ws")
|
||||
require.NoError(t, coreio.Local.EnsureDir(ws))
|
||||
require.NoError(t, writeStatus(ws, &WorkspaceStatus{
|
||||
Status: "completed",
|
||||
Agent: "gemini",
|
||||
PID: 0,
|
||||
}))
|
||||
|
||||
s := &PrepSubsystem{}
|
||||
assert.Equal(t, 0, s.countRunningByAgent("gemini"))
|
||||
}
|
||||
|
||||
func TestDelayForAgent_Good_NoConfig(t *testing.T) {
|
||||
// With no config, delay should be 0
|
||||
t.Setenv("CORE_WORKSPACE", t.TempDir())
|
||||
s := &PrepSubsystem{codePath: t.TempDir()}
|
||||
assert.Equal(t, 0, int(s.delayForAgent("gemini").Seconds()))
|
||||
}
|
||||
184
pkg/agentic/status_test.go
Normal file
184
pkg/agentic/status_test.go
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package agentic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
coreio "forge.lthn.ai/core/go-io"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWriteStatus_Good(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
status := &WorkspaceStatus{
|
||||
Status: "running",
|
||||
Agent: "gemini",
|
||||
Repo: "go-io",
|
||||
Task: "fix tests",
|
||||
PID: 12345,
|
||||
StartedAt: time.Now(),
|
||||
Runs: 1,
|
||||
}
|
||||
|
||||
err := writeStatus(dir, status)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify file was written via coreio
|
||||
data, err := coreio.Local.Read(filepath.Join(dir, "status.json"))
|
||||
require.NoError(t, err)
|
||||
|
||||
var read WorkspaceStatus
|
||||
err = json.Unmarshal([]byte(data), &read)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "running", read.Status)
|
||||
assert.Equal(t, "gemini", read.Agent)
|
||||
assert.Equal(t, "go-io", read.Repo)
|
||||
assert.Equal(t, "fix tests", read.Task)
|
||||
assert.Equal(t, 12345, read.PID)
|
||||
assert.Equal(t, 1, read.Runs)
|
||||
assert.False(t, read.UpdatedAt.IsZero(), "UpdatedAt should be set by writeStatus")
|
||||
}
|
||||
|
||||
func TestWriteStatus_Good_UpdatesTimestamp(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
before := time.Now().Add(-time.Second)
|
||||
|
||||
status := &WorkspaceStatus{
|
||||
Status: "running",
|
||||
Agent: "claude",
|
||||
}
|
||||
|
||||
err := writeStatus(dir, status)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, status.UpdatedAt.After(before), "UpdatedAt should be after the start time")
|
||||
}
|
||||
|
||||
func TestReadStatus_Good(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
status := &WorkspaceStatus{
|
||||
Status: "completed",
|
||||
Agent: "codex",
|
||||
Repo: "go-log",
|
||||
Task: "add logging",
|
||||
Branch: "agent/add-logging",
|
||||
StartedAt: time.Now().Truncate(time.Second),
|
||||
UpdatedAt: time.Now().Truncate(time.Second),
|
||||
Runs: 2,
|
||||
PRURL: "https://forge.lthn.ai/core/go-log/pulls/5",
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(status, "", " ")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, coreio.Local.Write(filepath.Join(dir, "status.json"), string(data)))
|
||||
|
||||
read, err := readStatus(dir)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "completed", read.Status)
|
||||
assert.Equal(t, "codex", read.Agent)
|
||||
assert.Equal(t, "go-log", read.Repo)
|
||||
assert.Equal(t, "add logging", read.Task)
|
||||
assert.Equal(t, "agent/add-logging", read.Branch)
|
||||
assert.Equal(t, 2, read.Runs)
|
||||
assert.Equal(t, "https://forge.lthn.ai/core/go-log/pulls/5", read.PRURL)
|
||||
}
|
||||
|
||||
func TestReadStatus_Bad_NoFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
_, err := readStatus(dir)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestReadStatus_Bad_InvalidJSON(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, coreio.Local.Write(filepath.Join(dir, "status.json"), "not json{"))
|
||||
|
||||
_, err := readStatus(dir)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestReadStatus_Good_BlockedWithQuestion(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
status := &WorkspaceStatus{
|
||||
Status: "blocked",
|
||||
Agent: "gemini",
|
||||
Repo: "go-io",
|
||||
Question: "Which interface should I implement?",
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(status, "", " ")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, coreio.Local.Write(filepath.Join(dir, "status.json"), string(data)))
|
||||
|
||||
read, err := readStatus(dir)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "blocked", read.Status)
|
||||
assert.Equal(t, "Which interface should I implement?", read.Question)
|
||||
}
|
||||
|
||||
func TestWriteReadStatus_Good_Roundtrip(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
original := &WorkspaceStatus{
|
||||
Status: "running",
|
||||
Agent: "claude:opus",
|
||||
Repo: "agent",
|
||||
Org: "core",
|
||||
Task: "write tests for agentic package",
|
||||
Branch: "agent/write-tests",
|
||||
Issue: 42,
|
||||
PID: 99999,
|
||||
StartedAt: time.Now().Truncate(time.Second),
|
||||
Runs: 3,
|
||||
}
|
||||
|
||||
err := writeStatus(dir, original)
|
||||
require.NoError(t, err)
|
||||
|
||||
read, err := readStatus(dir)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, original.Status, read.Status)
|
||||
assert.Equal(t, original.Agent, read.Agent)
|
||||
assert.Equal(t, original.Repo, read.Repo)
|
||||
assert.Equal(t, original.Org, read.Org)
|
||||
assert.Equal(t, original.Task, read.Task)
|
||||
assert.Equal(t, original.Branch, read.Branch)
|
||||
assert.Equal(t, original.Issue, read.Issue)
|
||||
assert.Equal(t, original.PID, read.PID)
|
||||
assert.Equal(t, original.Runs, read.Runs)
|
||||
}
|
||||
|
||||
func TestWriteStatus_Good_OverwriteExisting(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
first := &WorkspaceStatus{Status: "running", Agent: "gemini"}
|
||||
err := writeStatus(dir, first)
|
||||
require.NoError(t, err)
|
||||
|
||||
second := &WorkspaceStatus{Status: "completed", Agent: "gemini"}
|
||||
err = writeStatus(dir, second)
|
||||
require.NoError(t, err)
|
||||
|
||||
read, err := readStatus(dir)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "completed", read.Status)
|
||||
}
|
||||
|
||||
func TestReadStatus_Ugly_EmptyFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, coreio.Local.Write(filepath.Join(dir, "status.json"), ""))
|
||||
|
||||
_, err := readStatus(dir)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue