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>
169 lines
4.8 KiB
Go
169 lines
4.8 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"
|
|
)
|
|
|
|
// --- ReadStatus ---
|
|
|
|
func TestStatus_ReadStatus_Good_AllFields(t *testing.T) {
|
|
dir := t.TempDir()
|
|
now := time.Now().Truncate(time.Second)
|
|
|
|
original := WorkspaceStatus{
|
|
Status: "running",
|
|
Agent: "claude:opus",
|
|
Repo: "go-io",
|
|
Org: "core",
|
|
Task: "add observability",
|
|
Branch: "agent/add-observability",
|
|
Issue: 7,
|
|
PID: 42100,
|
|
StartedAt: now,
|
|
UpdatedAt: now,
|
|
Question: "",
|
|
Runs: 2,
|
|
PRURL: "",
|
|
}
|
|
require.True(t, fs.Write(core.JoinPath(dir, "status.json"), core.JSONMarshalString(original)).OK)
|
|
|
|
st, err := ReadStatus(dir)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, original.Status, st.Status)
|
|
assert.Equal(t, original.Agent, st.Agent)
|
|
assert.Equal(t, original.Repo, st.Repo)
|
|
assert.Equal(t, original.Org, st.Org)
|
|
assert.Equal(t, original.Task, st.Task)
|
|
assert.Equal(t, original.Branch, st.Branch)
|
|
assert.Equal(t, original.Issue, st.Issue)
|
|
assert.Equal(t, original.PID, st.PID)
|
|
assert.Equal(t, original.Runs, st.Runs)
|
|
}
|
|
|
|
func TestStatus_ReadStatus_Bad_MissingFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
_, err := ReadStatus(dir)
|
|
assert.Error(t, err, "missing status.json must return an error")
|
|
}
|
|
|
|
func TestStatus_ReadStatus_Bad_CorruptJSON(t *testing.T) {
|
|
dir := t.TempDir()
|
|
require.True(t, fs.Write(core.JoinPath(dir, "status.json"), `{"status": "running", broken`).OK)
|
|
|
|
_, err := ReadStatus(dir)
|
|
assert.Error(t, err, "corrupt JSON must return an error")
|
|
}
|
|
|
|
func TestStatus_ReadStatus_Bad_NullJSON(t *testing.T) {
|
|
dir := t.TempDir()
|
|
require.True(t, fs.Write(core.JoinPath(dir, "status.json"), "null").OK)
|
|
|
|
// null is valid JSON — ReadStatus returns a zero-value struct, not an error
|
|
st, err := ReadStatus(dir)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "", st.Status)
|
|
}
|
|
|
|
// --- writeStatus ---
|
|
|
|
func TestStatus_WriteStatus_Good_WritesAndReadsBack(t *testing.T) {
|
|
dir := t.TempDir()
|
|
st := &WorkspaceStatus{
|
|
Status: "queued",
|
|
Agent: "gemini:pro",
|
|
Repo: "go-log",
|
|
Task: "improve logging",
|
|
Runs: 0,
|
|
}
|
|
|
|
err := writeStatus(dir, st)
|
|
require.NoError(t, err)
|
|
|
|
read, err := ReadStatus(dir)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "queued", read.Status)
|
|
assert.Equal(t, "gemini:pro", read.Agent)
|
|
assert.Equal(t, "go-log", read.Repo)
|
|
assert.Equal(t, "improve logging", read.Task)
|
|
}
|
|
|
|
func TestStatus_WriteStatus_Good_SetsUpdatedAt(t *testing.T) {
|
|
dir := t.TempDir()
|
|
before := time.Now().Add(-time.Millisecond)
|
|
|
|
st := &WorkspaceStatus{Status: "failed", Agent: "codex"}
|
|
err := writeStatus(dir, st)
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, st.UpdatedAt.After(before), "writeStatus must set UpdatedAt to a recent time")
|
|
}
|
|
|
|
func TestStatus_WriteStatus_Good_Overwrites(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
require.NoError(t, writeStatus(dir, &WorkspaceStatus{Status: "running", Agent: "gemini"}))
|
|
require.NoError(t, writeStatus(dir, &WorkspaceStatus{Status: "completed", Agent: "gemini"}))
|
|
|
|
st, err := ReadStatus(dir)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "completed", st.Status)
|
|
}
|
|
|
|
// --- WorkspaceStatus JSON round-trip ---
|
|
|
|
func TestStatus_WorkspaceStatus_Good_JSONRoundTrip(t *testing.T) {
|
|
now := time.Now().Truncate(time.Second)
|
|
original := WorkspaceStatus{
|
|
Status: "blocked",
|
|
Agent: "codex:gpt-5.4",
|
|
Repo: "agent",
|
|
Org: "core",
|
|
Task: "write more tests",
|
|
Branch: "agent/write-more-tests",
|
|
Issue: 15,
|
|
PID: 99001,
|
|
StartedAt: now,
|
|
UpdatedAt: now,
|
|
Question: "Which pattern should I use?",
|
|
Runs: 3,
|
|
PRURL: "https://forge.lthn.ai/core/agent/pulls/10",
|
|
}
|
|
|
|
jsonStr := core.JSONMarshalString(original)
|
|
|
|
var decoded WorkspaceStatus
|
|
require.True(t, core.JSONUnmarshalString(jsonStr, &decoded).OK)
|
|
|
|
assert.Equal(t, original.Status, decoded.Status)
|
|
assert.Equal(t, original.Agent, decoded.Agent)
|
|
assert.Equal(t, original.Repo, decoded.Repo)
|
|
assert.Equal(t, original.Org, decoded.Org)
|
|
assert.Equal(t, original.Task, decoded.Task)
|
|
assert.Equal(t, original.Branch, decoded.Branch)
|
|
assert.Equal(t, original.Issue, decoded.Issue)
|
|
assert.Equal(t, original.PID, decoded.PID)
|
|
assert.Equal(t, original.Question, decoded.Question)
|
|
assert.Equal(t, original.Runs, decoded.Runs)
|
|
assert.Equal(t, original.PRURL, decoded.PRURL)
|
|
}
|
|
|
|
func TestStatus_WorkspaceStatus_Good_OmitemptyFields(t *testing.T) {
|
|
st := WorkspaceStatus{Status: "queued", Agent: "claude"}
|
|
|
|
// Optional fields with omitempty must be absent when zero
|
|
jsonStr := core.JSONMarshalString(st)
|
|
assert.NotContains(t, jsonStr, `"org"`)
|
|
assert.NotContains(t, jsonStr, `"branch"`)
|
|
assert.NotContains(t, jsonStr, `"question"`)
|
|
assert.NotContains(t, jsonStr, `"pr_url"`)
|
|
assert.NotContains(t, jsonStr, `"pid"`)
|
|
assert.NotContains(t, jsonStr, `"issue"`)
|
|
}
|