agent/pkg/agentic/plan_logic_test.go
Snider 537226bd4d feat: AX v0.8.0 upgrade — Core features + quality gates
AX Quality Gates (RFC-025):
- Eliminate os/exec from all test + production code (12+ files)
- Eliminate encoding/json from all test files (15 files, 66 occurrences)
- Eliminate os from all test files except TestMain (Go runtime contract)
- Eliminate path/filepath, net/url from all files
- String concat: 39 violations replaced with core.Concat()
- Test naming AX-7: 264 test functions renamed across all 6 packages
- Example test 1:1 coverage complete

Core Features Adopted:
- Task Composition: agent.completion pipeline (QA → PR → Verify → Ingest → Poke)
- PerformAsync: completion pipeline runs with WaitGroup + progress tracking
- Config: agents.yaml loaded once, feature flags (auto-qa/pr/merge/ingest)
- Named Locks: c.Lock("drain") for queue serialisation
- Registry: workspace state with cross-package QUERY access
- QUERY: c.QUERY(WorkspaceQuery{Status: "running"}) for cross-service queries
- Action descriptions: 25+ Actions self-documenting
- Data mounts: prompts/tasks/flows/personas/workspaces via c.Data()
- Content Actions: agentic.prompt/task/flow/persona callable via IPC
- Drive endpoints: forge + brain registered with tokens
- Drive REST helpers: DriveGet/DrivePost/DriveDo for Drive-aware HTTP
- HandleIPCEvents: auto-discovered by WithService (no manual wiring)
- Entitlement: frozen-queue gate on write Actions
- CLI dispatch: workspace dispatch wired to real dispatch method
- CLI: --quiet/-q and --debug/-d global flags
- CLI: banner, version, check (with service/action/command counts), env
- main.go: minimal — 5 services + c.Run(), no os import
- cmd tests: 84.2% coverage (was 0%)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-26 06:38:02 +00:00

175 lines
4.7 KiB
Go

// SPDX-License-Identifier: EUPL-1.2
package agentic
import (
"testing"
"time"
core "dappco.re/go/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// --- planPath ---
func TestPlan_PlanPath_Good_BasicFormat(t *testing.T) {
result := planPath("/tmp/plans", "my-plan-abc123")
assert.Equal(t, "/tmp/plans/my-plan-abc123.json", result)
}
func TestPlan_PlanPath_Good_NestedIDStripped(t *testing.T) {
// PathBase strips directory component — prevents path traversal
result := planPath("/plans", "../../../etc/passwd")
assert.Equal(t, "/plans/passwd.json", result)
}
func TestPlan_PlanPath_Good_SimpleID(t *testing.T) {
assert.Equal(t, "/data/test.json", planPath("/data", "test"))
}
func TestPlan_PlanPath_Good_SlugWithDashes(t *testing.T) {
assert.Equal(t, "/root/migrate-core-abc123.json", planPath("/root", "migrate-core-abc123"))
}
func TestPlan_PlanPath_Bad_DotID(t *testing.T) {
// "." is sanitised to "invalid" to prevent exploiting the root directory
result := planPath("/plans", ".")
assert.Equal(t, "/plans/invalid.json", result)
}
func TestPlan_PlanPath_Bad_DoubleDotID(t *testing.T) {
result := planPath("/plans", "..")
assert.Equal(t, "/plans/invalid.json", result)
}
func TestPlan_PlanPath_Bad_EmptyID(t *testing.T) {
result := planPath("/plans", "")
assert.Equal(t, "/plans/invalid.json", result)
}
// --- readPlan / writePlan ---
func TestPlan_ReadWrite_Good_BasicRoundtrip(t *testing.T) {
dir := t.TempDir()
now := time.Now().Truncate(time.Second)
plan := &Plan{
ID: "basic-plan-abc",
Title: "Basic Plan",
Status: "draft",
Repo: "go-io",
Org: "core",
Objective: "Verify round-trip works",
Agent: "claude:opus",
CreatedAt: now,
UpdatedAt: now,
}
path, err := writePlan(dir, plan)
require.NoError(t, err)
assert.Equal(t, core.JoinPath(dir, "basic-plan-abc.json"), path)
read, err := readPlan(dir, "basic-plan-abc")
require.NoError(t, err)
assert.Equal(t, plan.ID, read.ID)
assert.Equal(t, plan.Title, read.Title)
assert.Equal(t, plan.Status, read.Status)
assert.Equal(t, plan.Repo, read.Repo)
assert.Equal(t, plan.Org, read.Org)
assert.Equal(t, plan.Objective, read.Objective)
assert.Equal(t, plan.Agent, read.Agent)
}
func TestPlan_ReadWrite_Good_WithPhases(t *testing.T) {
dir := t.TempDir()
plan := &Plan{
ID: "phase-plan-abc",
Title: "Phased Work",
Status: "in_progress",
Objective: "Multi-phase plan",
Phases: []Phase{
{Number: 1, Name: "Setup", Status: "done", Criteria: []string{"repo cloned", "deps installed"}, Tests: 3},
{Number: 2, Name: "Implement", Status: "in_progress", Notes: "WIP"},
{Number: 3, Name: "Verify", Status: "pending"},
},
}
_, err := writePlan(dir, plan)
require.NoError(t, err)
read, err := readPlan(dir, "phase-plan-abc")
require.NoError(t, err)
require.Len(t, read.Phases, 3)
assert.Equal(t, "Setup", read.Phases[0].Name)
assert.Equal(t, "done", read.Phases[0].Status)
assert.Equal(t, []string{"repo cloned", "deps installed"}, read.Phases[0].Criteria)
assert.Equal(t, 3, read.Phases[0].Tests)
assert.Equal(t, "WIP", read.Phases[1].Notes)
assert.Equal(t, "pending", read.Phases[2].Status)
}
func TestPlan_ReadPlan_Bad_MissingFile(t *testing.T) {
dir := t.TempDir()
_, err := readPlan(dir, "nonexistent-plan")
assert.Error(t, err)
}
func TestPlan_ReadPlan_Bad_CorruptJSON(t *testing.T) {
dir := t.TempDir()
require.True(t, fs.Write(core.JoinPath(dir, "bad.json"), `{broken`).OK)
_, err := readPlan(dir, "bad")
assert.Error(t, err)
}
func TestPlan_WritePlan_Good_CreatesNestedDir(t *testing.T) {
base := t.TempDir()
nested := core.JoinPath(base, "deep", "nested", "plans")
plan := &Plan{
ID: "deep-plan-xyz",
Title: "Deep",
Status: "draft",
Objective: "Test nested dir creation",
}
path, err := writePlan(nested, plan)
require.NoError(t, err)
assert.Equal(t, core.JoinPath(nested, "deep-plan-xyz.json"), path)
assert.True(t, fs.IsFile(path))
}
func TestPlan_WritePlan_Good_OverwriteExistingLogic(t *testing.T) {
dir := t.TempDir()
plan := &Plan{
ID: "overwrite-plan-abc",
Title: "First Title",
Status: "draft",
Objective: "Initial",
}
_, err := writePlan(dir, plan)
require.NoError(t, err)
plan.Title = "Second Title"
plan.Status = "approved"
_, err = writePlan(dir, plan)
require.NoError(t, err)
read, err := readPlan(dir, "overwrite-plan-abc")
require.NoError(t, err)
assert.Equal(t, "Second Title", read.Title)
assert.Equal(t, "approved", read.Status)
}
func TestPlan_ReadPlan_Ugly_EmptyFileLogic(t *testing.T) {
dir := t.TempDir()
require.True(t, fs.Write(core.JoinPath(dir, "empty.json"), "").OK)
_, err := readPlan(dir, "empty")
assert.Error(t, err)
}