DevOps plugin (5 skills): - install-core-agent, repair-core-agent, merge-workspace, update-deps, clean-workspaces CLI commands: version, check, extract for diagnostics. Codex dispatch: --skip-git-repo-check, removed broken --model-reasoning-effort, --sandbox workspace-write via --full-auto. Workspace template extracts to wsDir not srcDir. AX sweep (Codex-generated): sanitise.go extracted from prep/plan, mirror.go JSON parsing via encoding/json, setup/config.go URL parsing via net/url, strings/fmt imports eliminated from setup. CODEX.md template updated with Env/Path patterns. Review workspace template with audit-only PROMPT.md. Marketplace updated with devops plugin. Co-Authored-By: Virgil <virgil@lethean.io>
273 lines
5.8 KiB
Go
273 lines
5.8 KiB
Go
package lib
|
|
|
|
import (
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
// --- Prompt ---
|
|
|
|
func TestPrompt_Good(t *testing.T) {
|
|
r := Prompt("coding")
|
|
if !r.OK {
|
|
t.Fatal("Prompt('coding') returned !OK")
|
|
}
|
|
if r.Value.(string) == "" {
|
|
t.Error("Prompt('coding') returned empty string")
|
|
}
|
|
}
|
|
|
|
func TestPrompt_Bad(t *testing.T) {
|
|
r := Prompt("nonexistent-slug")
|
|
if r.OK {
|
|
t.Error("Prompt('nonexistent-slug') should return !OK")
|
|
}
|
|
}
|
|
|
|
// --- Task ---
|
|
|
|
func TestTask_Good_Yaml(t *testing.T) {
|
|
r := Task("bug-fix")
|
|
if !r.OK {
|
|
t.Fatal("Task('bug-fix') returned !OK")
|
|
}
|
|
if r.Value.(string) == "" {
|
|
t.Error("Task('bug-fix') returned empty string")
|
|
}
|
|
}
|
|
|
|
func TestTask_Good_Md(t *testing.T) {
|
|
r := Task("code/review")
|
|
if !r.OK {
|
|
t.Fatal("Task('code/review') returned !OK")
|
|
}
|
|
if r.Value.(string) == "" {
|
|
t.Error("Task('code/review') returned empty string")
|
|
}
|
|
}
|
|
|
|
func TestTask_Bad(t *testing.T) {
|
|
r := Task("nonexistent-slug")
|
|
if r.OK {
|
|
t.Error("Task('nonexistent-slug') should return !OK")
|
|
}
|
|
if r.Value != fs.ErrNotExist {
|
|
t.Error("Task('nonexistent-slug') should return fs.ErrNotExist")
|
|
}
|
|
}
|
|
|
|
// --- TaskBundle ---
|
|
|
|
func TestTaskBundle_Good(t *testing.T) {
|
|
r := TaskBundle("code/review")
|
|
if !r.OK {
|
|
t.Fatal("TaskBundle('code/review') returned !OK")
|
|
}
|
|
b := r.Value.(Bundle)
|
|
if b.Main == "" {
|
|
t.Error("Bundle.Main is empty")
|
|
}
|
|
if len(b.Files) == 0 {
|
|
t.Error("Bundle.Files is empty — expected companion files")
|
|
}
|
|
}
|
|
|
|
func TestTaskBundle_Bad(t *testing.T) {
|
|
r := TaskBundle("nonexistent")
|
|
if r.OK {
|
|
t.Error("TaskBundle('nonexistent') should return !OK")
|
|
}
|
|
}
|
|
|
|
// --- Flow ---
|
|
|
|
func TestFlow_Good(t *testing.T) {
|
|
r := Flow("go")
|
|
if !r.OK {
|
|
t.Fatal("Flow('go') returned !OK")
|
|
}
|
|
if r.Value.(string) == "" {
|
|
t.Error("Flow('go') returned empty string")
|
|
}
|
|
}
|
|
|
|
// --- Persona ---
|
|
|
|
func TestPersona_Good(t *testing.T) {
|
|
// Use first persona from list to avoid hardcoding
|
|
personas := ListPersonas()
|
|
if len(personas) == 0 {
|
|
t.Skip("no personas found")
|
|
}
|
|
r := Persona(personas[0])
|
|
if !r.OK {
|
|
t.Fatalf("Persona(%q) returned !OK", personas[0])
|
|
}
|
|
if r.Value.(string) == "" {
|
|
t.Errorf("Persona(%q) returned empty string", personas[0])
|
|
}
|
|
}
|
|
|
|
// --- Template ---
|
|
|
|
func TestTemplate_Good_Prompt(t *testing.T) {
|
|
r := Template("coding")
|
|
if !r.OK {
|
|
t.Fatal("Template('coding') returned !OK")
|
|
}
|
|
if r.Value.(string) == "" {
|
|
t.Error("Template('coding') returned empty string")
|
|
}
|
|
}
|
|
|
|
func TestTemplate_Good_TaskFallback(t *testing.T) {
|
|
r := Template("bug-fix")
|
|
if !r.OK {
|
|
t.Fatal("Template('bug-fix') returned !OK — should fall through to Task")
|
|
}
|
|
}
|
|
|
|
func TestTemplate_Bad(t *testing.T) {
|
|
r := Template("nonexistent-slug")
|
|
if r.OK {
|
|
t.Error("Template('nonexistent-slug') should return !OK")
|
|
}
|
|
}
|
|
|
|
// --- List Functions ---
|
|
|
|
func TestListPrompts(t *testing.T) {
|
|
prompts := ListPrompts()
|
|
if len(prompts) == 0 {
|
|
t.Error("ListPrompts() returned empty")
|
|
}
|
|
}
|
|
|
|
func TestListTasks(t *testing.T) {
|
|
tasks := ListTasks()
|
|
if len(tasks) == 0 {
|
|
t.Fatal("ListTasks() returned empty")
|
|
}
|
|
// Verify nested paths are included (e.g., "code/review")
|
|
found := false
|
|
for _, s := range tasks {
|
|
if s == "code/review" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("ListTasks() missing nested path 'code/review'")
|
|
}
|
|
}
|
|
|
|
func TestListPersonas(t *testing.T) {
|
|
personas := ListPersonas()
|
|
if len(personas) == 0 {
|
|
t.Error("ListPersonas() returned empty")
|
|
}
|
|
// Should have nested paths like "code/go"
|
|
hasNested := false
|
|
for _, p := range personas {
|
|
if len(p) > 0 && filepath.Dir(p) != "." {
|
|
hasNested = true
|
|
break
|
|
}
|
|
}
|
|
if !hasNested {
|
|
t.Error("ListPersonas() has no nested paths")
|
|
}
|
|
}
|
|
|
|
func TestListFlows(t *testing.T) {
|
|
flows := ListFlows()
|
|
if len(flows) == 0 {
|
|
t.Error("ListFlows() returned empty")
|
|
}
|
|
}
|
|
|
|
func TestListWorkspaces(t *testing.T) {
|
|
workspaces := ListWorkspaces()
|
|
if len(workspaces) == 0 {
|
|
t.Error("ListWorkspaces() returned empty")
|
|
}
|
|
}
|
|
|
|
// --- ExtractWorkspace ---
|
|
|
|
func TestExtractWorkspace_CreatesFiles(t *testing.T) {
|
|
dir := t.TempDir()
|
|
data := &WorkspaceData{Repo: "test-repo", Task: "test task"}
|
|
|
|
err := ExtractWorkspace("default", dir, data)
|
|
if err != nil {
|
|
t.Fatalf("ExtractWorkspace failed: %v", err)
|
|
}
|
|
|
|
for _, name := range []string{"CODEX.md", "CLAUDE.md", "PROMPT.md", "TODO.md", "CONTEXT.md", "go.work"} {
|
|
path := filepath.Join(dir, name)
|
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
t.Errorf("expected %s to exist", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExtractWorkspace_CreatesSubdirectories(t *testing.T) {
|
|
dir := t.TempDir()
|
|
data := &WorkspaceData{Repo: "test-repo", Task: "test task"}
|
|
|
|
err := ExtractWorkspace("default", dir, data)
|
|
if err != nil {
|
|
t.Fatalf("ExtractWorkspace failed: %v", err)
|
|
}
|
|
|
|
refDir := filepath.Join(dir, ".core", "reference")
|
|
if _, err := os.Stat(refDir); os.IsNotExist(err) {
|
|
t.Fatalf(".core/reference/ directory not created")
|
|
}
|
|
|
|
axSpec := filepath.Join(refDir, "RFC-025-AGENT-EXPERIENCE.md")
|
|
if _, err := os.Stat(axSpec); os.IsNotExist(err) {
|
|
t.Errorf("AX spec not extracted: %s", axSpec)
|
|
}
|
|
|
|
entries, err := os.ReadDir(refDir)
|
|
if err != nil {
|
|
t.Fatalf("failed to read reference dir: %v", err)
|
|
}
|
|
|
|
goFiles := 0
|
|
for _, e := range entries {
|
|
if filepath.Ext(e.Name()) == ".go" {
|
|
goFiles++
|
|
}
|
|
}
|
|
if goFiles == 0 {
|
|
t.Error("no .go files in .core/reference/")
|
|
}
|
|
|
|
docsDir := filepath.Join(refDir, "docs")
|
|
if _, err := os.Stat(docsDir); os.IsNotExist(err) {
|
|
t.Errorf(".core/reference/docs/ not created")
|
|
}
|
|
}
|
|
|
|
func TestExtractWorkspace_TemplateSubstitution(t *testing.T) {
|
|
dir := t.TempDir()
|
|
data := &WorkspaceData{Repo: "my-repo", Task: "fix the bug"}
|
|
|
|
err := ExtractWorkspace("default", dir, data)
|
|
if err != nil {
|
|
t.Fatalf("ExtractWorkspace failed: %v", err)
|
|
}
|
|
|
|
content, err := os.ReadFile(filepath.Join(dir, "TODO.md"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read TODO.md: %v", err)
|
|
}
|
|
if len(content) == 0 {
|
|
t.Error("TODO.md is empty")
|
|
}
|
|
}
|