T06: CLI — komandni interfejs
- 5 komandi: run, status, next, verify, history - Config proširen sa KAOS_TASKS_DIR - Tabelarni status sa emoji ikonama - run: scan → find → move → run → verify → report → review - 8 CLI testova, 59 ukupno — svi prolaze - T05 premešten u done/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
028872be43
commit
38e1e1029c
47
TASKS/reports/T06-report.md
Normal file
47
TASKS/reports/T06-report.md
Normal file
@ -0,0 +1,47 @@
|
||||
# T06 Izveštaj: CLI — komandni interfejs
|
||||
|
||||
**Agent:** coder
|
||||
**Model:** Opus
|
||||
**Datum:** 2026-02-20
|
||||
|
||||
---
|
||||
|
||||
## Šta je urađeno
|
||||
|
||||
Implementiran CLI u `code/cmd/kaos-supervisor/main.go`:
|
||||
|
||||
### Izmenjeni/kreirani fajlovi
|
||||
|
||||
| Fajl | Opis |
|
||||
|------|------|
|
||||
| `cmd/kaos-supervisor/main.go` | 5 komandi: run, status, next, verify, history |
|
||||
| `cmd/kaos-supervisor/main_test.go` | 8 testova za CLI komande |
|
||||
| `internal/config/config.go` | Dodat KAOS_TASKS_DIR |
|
||||
| `internal/config/config_test.go` | Dodat test za KAOS_TASKS_DIR |
|
||||
| `.env.example` | Dodat KAOS_TASKS_DIR |
|
||||
|
||||
### Komande
|
||||
|
||||
- **run [TASK_ID]** — pokreni task (ili sledeći), verify, report, premesti u review/
|
||||
- **status** — tabelarni prikaz svih taskova sa emoji statusima
|
||||
- **next** — sledeći task za rad
|
||||
- **verify [PATH]** — verifikacija Go projekta
|
||||
- **history** — lista izveštaja
|
||||
|
||||
### Testovi — 8/8 PASS (CLI)
|
||||
|
||||
```
|
||||
TestCmdStatus_ShowsTasks PASS
|
||||
TestCmdNext_FindsReadyTask PASS
|
||||
TestCmdVerify_OnTestProject PASS (0.46s)
|
||||
TestCmdHistory_ShowsReports PASS
|
||||
TestUnknownCommand PASS
|
||||
TestCmdRun_NoReadyTasks PASS
|
||||
TestStatusIcon PASS
|
||||
TestNextTask_Integration PASS
|
||||
```
|
||||
|
||||
### Ukupno projekat: 59 testova, svi prolaze
|
||||
|
||||
- `go vet ./...` — čist
|
||||
- `go build ./...` — prolazi
|
||||
90
TASKS/review/T06.md
Normal file
90
TASKS/review/T06.md
Normal file
@ -0,0 +1,90 @@
|
||||
# T06: CLI — komandni interfejs
|
||||
|
||||
**Kreirao:** planer
|
||||
**Datum:** 2026-02-20
|
||||
**Agent:** coder
|
||||
**Model:** Sonnet
|
||||
**Zavisi od:** T02 ✅, T03 ✅, T04 ✅, T05
|
||||
|
||||
---
|
||||
|
||||
## Opis
|
||||
|
||||
Entry point za supervisor. Parsira komande, poziva supervisor funkcije.
|
||||
Koristi sve module iz T02-T05.
|
||||
|
||||
## Fajlovi za izmenu
|
||||
|
||||
```
|
||||
code/cmd/kaos-supervisor/main.go ← komande
|
||||
```
|
||||
|
||||
## Komande
|
||||
|
||||
```bash
|
||||
kaos-supervisor run [TASK_ID] # pokreni task (ili sledeći iz ready/)
|
||||
kaos-supervisor status # prikaži status svih taskova
|
||||
kaos-supervisor next # prikaži šta je sledeće za rad
|
||||
kaos-supervisor verify [PATH] # pokreni verifikaciju na projektu
|
||||
kaos-supervisor history # prikaži izvršene taskove iz reports/
|
||||
```
|
||||
|
||||
## Detalji komandi
|
||||
|
||||
### run [TASK_ID]
|
||||
1. ScanTasks() — učitaj sve taskove
|
||||
2. Ako je TASK_ID dat → FindTask, proveri da je u ready/
|
||||
3. Ako nije dat → NextTask (prvi iz ready/ sa ispunjenim zavisnostima)
|
||||
4. MoveTask → active/
|
||||
5. RunTask (iz runner.go)
|
||||
6. Verify (iz checker.go)
|
||||
7. WriteReport (iz reporter.go)
|
||||
8. MoveTask → review/
|
||||
9. Prikaži rezime
|
||||
|
||||
### status
|
||||
- ScanTasks → tabelarni prikaz svih taskova
|
||||
- Format: ID | Naslov | Folder | Zavisi od
|
||||
|
||||
### next
|
||||
- NextTask → prikaži koji task je sledeći za rad
|
||||
|
||||
### verify [PATH]
|
||||
- Verify(path) → prikaži rezultat
|
||||
- Default path: code/
|
||||
|
||||
### history
|
||||
- Čitaj fajlove iz TASKS/reports/ → prikaži listu
|
||||
|
||||
## Pravila
|
||||
|
||||
- Bez eksternih CLI biblioteka (flag package ili os.Args)
|
||||
- Formatiran terminal output sa emoji za status
|
||||
- Poruke na srpskom
|
||||
- Config čita iz .env (putanja do TASKS/, timeout)
|
||||
- Exit code: 0 uspeh, 1 greška
|
||||
|
||||
## Testovi
|
||||
|
||||
- status sa primer taskovima → ispravan output
|
||||
- next → vraća pravi task
|
||||
- verify na test projektu → prikaže rezultat
|
||||
- Nepoznata komanda → help poruka
|
||||
- run bez taskova u ready/ → poruka "nema taskova"
|
||||
|
||||
## Očekivani izlaz
|
||||
|
||||
`make build` → `bin/kaos-supervisor` radi sve komande.
|
||||
`go test ./... -v` — svi testovi zeleni.
|
||||
|
||||
---
|
||||
|
||||
## Pitanja
|
||||
|
||||
*(agent piše pitanja ovde, planer odgovara)*
|
||||
|
||||
---
|
||||
|
||||
## Odgovori
|
||||
|
||||
*(planer piše odgovore ovde)*
|
||||
@ -1,2 +1,3 @@
|
||||
KAOS_TIMEOUT=30m
|
||||
KAOS_PROJECT_PATH=.
|
||||
KAOS_TASKS_DIR=../TASKS
|
||||
|
||||
@ -1,22 +1,242 @@
|
||||
// Package main is the entry point for the KAOS supervisor process.
|
||||
// It loads configuration and starts the supervisor.
|
||||
// It parses CLI commands and delegates to supervisor functions.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/dal/kaos/internal/config"
|
||||
_ "github.com/dal/kaos/internal/supervisor"
|
||||
"github.com/dal/kaos/internal/supervisor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load config: %v", err)
|
||||
fmt.Fprintf(os.Stderr, "Greška pri učitavanju konfiguracije: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "KAOS Supervisor started (timeout=%s, project_path=%s)\n",
|
||||
cfg.Timeout, cfg.ProjectPath)
|
||||
if len(os.Args) < 2 {
|
||||
printHelp()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
command := os.Args[1]
|
||||
|
||||
switch command {
|
||||
case "run":
|
||||
os.Exit(cmdRun(cfg))
|
||||
case "status":
|
||||
os.Exit(cmdStatus(cfg))
|
||||
case "next":
|
||||
os.Exit(cmdNext(cfg))
|
||||
case "verify":
|
||||
os.Exit(cmdVerify(cfg))
|
||||
case "history":
|
||||
os.Exit(cmdHistory(cfg))
|
||||
case "help", "--help", "-h":
|
||||
printHelp()
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Nepoznata komanda: %s\n\n", command)
|
||||
printHelp()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func printHelp() {
|
||||
fmt.Println("KAOS Supervisor")
|
||||
fmt.Println()
|
||||
fmt.Println("Komande:")
|
||||
fmt.Println(" run [TASK_ID] Pokreni task (ili sledeći iz ready/)")
|
||||
fmt.Println(" status Prikaži status svih taskova")
|
||||
fmt.Println(" next Prikaži sledeći task za rad")
|
||||
fmt.Println(" verify [PATH] Pokreni verifikaciju na projektu")
|
||||
fmt.Println(" history Prikaži izvršene taskove iz reports/")
|
||||
fmt.Println(" help Prikaži ovu pomoć")
|
||||
}
|
||||
|
||||
func cmdRun(cfg *config.Config) int {
|
||||
tasks, err := supervisor.ScanTasks(cfg.TasksDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Greška pri skeniranju taskova: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
var task *supervisor.Task
|
||||
|
||||
if len(os.Args) > 2 {
|
||||
taskID := os.Args[2]
|
||||
task = supervisor.FindTask(tasks, taskID)
|
||||
if task == nil {
|
||||
fmt.Fprintf(os.Stderr, "Task %s nije pronađen\n", taskID)
|
||||
return 1
|
||||
}
|
||||
if task.Status != "ready" {
|
||||
fmt.Fprintf(os.Stderr, "Task %s nije u ready/ (trenutno: %s)\n", taskID, task.Status)
|
||||
return 1
|
||||
}
|
||||
} else {
|
||||
task = supervisor.NextTask(tasks)
|
||||
if task == nil {
|
||||
fmt.Println("Nema taskova spremnih za rad")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("▶ Pokrećem: %s — %s\n", task.ID, task.Title)
|
||||
|
||||
// Move to active
|
||||
if err := supervisor.MoveTask(cfg.TasksDir, task.ID, "ready", "active"); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Greška pri premještanju u active/: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
// Run the task
|
||||
result := supervisor.RunTask(*task, cfg.ProjectPath, cfg.Timeout, nil)
|
||||
|
||||
fmt.Printf("⏱ Trajanje: %s\n", result.Duration.Round(1000000000)) // round to seconds
|
||||
fmt.Printf("📤 Exit code: %d\n", result.ExitCode)
|
||||
|
||||
// Verify
|
||||
fmt.Println("\n🔍 Verifikacija...")
|
||||
verify := supervisor.Verify(cfg.ProjectPath)
|
||||
fmt.Print(supervisor.FormatResult(verify))
|
||||
|
||||
// Write report
|
||||
reportsDir := filepath.Join(cfg.TasksDir, "reports")
|
||||
reportPath, err := supervisor.WriteReport(*task, result, verify, reportsDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Greška pri pisanju izveštaja: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("\n📝 Izveštaj: %s\n", reportPath)
|
||||
}
|
||||
|
||||
// Move to review
|
||||
if err := supervisor.MoveTask(cfg.TasksDir, task.ID, "active", "review"); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Greška pri premještanju u review/: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
fmt.Printf("\n✅ %s premešten u review/\n", task.ID)
|
||||
|
||||
if !verify.AllPassed {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func cmdStatus(cfg *config.Config) int {
|
||||
tasks, err := supervisor.ScanTasks(cfg.TasksDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Greška pri skeniranju taskova: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
if len(tasks) == 0 {
|
||||
fmt.Println("Nema taskova")
|
||||
return 0
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
fmt.Fprintln(w, "ID\tNaslov\tFolder\tZavisi od")
|
||||
fmt.Fprintln(w, "──\t──────\t──────\t─────────")
|
||||
|
||||
for _, t := range tasks {
|
||||
deps := "—"
|
||||
if len(t.DependsOn) > 0 {
|
||||
deps = strings.Join(t.DependsOn, ", ")
|
||||
}
|
||||
|
||||
icon := statusIcon(t.Status)
|
||||
fmt.Fprintf(w, "%s\t%s\t%s %s\t%s\n", t.ID, t.Title, icon, t.Status, deps)
|
||||
}
|
||||
w.Flush()
|
||||
return 0
|
||||
}
|
||||
|
||||
func cmdNext(cfg *config.Config) int {
|
||||
tasks, err := supervisor.ScanTasks(cfg.TasksDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Greška pri skeniranju taskova: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
task := supervisor.NextTask(tasks)
|
||||
if task == nil {
|
||||
fmt.Println("Nema taskova spremnih za rad")
|
||||
return 0
|
||||
}
|
||||
|
||||
fmt.Printf("Sledeći: %s — %s\n", task.ID, task.Title)
|
||||
fmt.Printf("Agent: %s | Model: %s\n", task.Agent, task.Model)
|
||||
if len(task.DependsOn) > 0 {
|
||||
fmt.Printf("Zavisi od: %s\n", strings.Join(task.DependsOn, ", "))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func cmdVerify(cfg *config.Config) int {
|
||||
path := cfg.ProjectPath
|
||||
if len(os.Args) > 2 {
|
||||
path = os.Args[2]
|
||||
}
|
||||
|
||||
fmt.Printf("🔍 Verifikacija: %s\n\n", path)
|
||||
result := supervisor.Verify(path)
|
||||
fmt.Print(supervisor.FormatResult(result))
|
||||
|
||||
if !result.AllPassed {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func cmdHistory(cfg *config.Config) int {
|
||||
reportsDir := filepath.Join(cfg.TasksDir, "reports")
|
||||
|
||||
entries, err := os.ReadDir(reportsDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
fmt.Println("Nema izveštaja")
|
||||
return 0
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Greška pri čitanju reports/: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
if len(entries) == 0 {
|
||||
fmt.Println("Nema izveštaja")
|
||||
return 0
|
||||
}
|
||||
|
||||
fmt.Println("Izveštaji:")
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() || !strings.HasSuffix(entry.Name(), "-report.md") {
|
||||
continue
|
||||
}
|
||||
name := strings.TrimSuffix(entry.Name(), "-report.md")
|
||||
fmt.Printf(" 📝 %s (%s)\n", name, filepath.Join(reportsDir, entry.Name()))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func statusIcon(status string) string {
|
||||
switch status {
|
||||
case "done":
|
||||
return "✅"
|
||||
case "active":
|
||||
return "🔄"
|
||||
case "ready":
|
||||
return "📋"
|
||||
case "review":
|
||||
return "👀"
|
||||
case "backlog":
|
||||
return "📦"
|
||||
default:
|
||||
return "❓"
|
||||
}
|
||||
}
|
||||
|
||||
268
code/cmd/kaos-supervisor/main_test.go
Normal file
268
code/cmd/kaos-supervisor/main_test.go
Normal file
@ -0,0 +1,268 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/dal/kaos/internal/config"
|
||||
"github.com/dal/kaos/internal/supervisor"
|
||||
)
|
||||
|
||||
func setupTestTasks(t *testing.T) (string, *config.Config) {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
|
||||
tasksDir := filepath.Join(dir, "TASKS")
|
||||
for _, folder := range []string{"backlog", "ready", "active", "review", "done", "reports"} {
|
||||
os.MkdirAll(filepath.Join(tasksDir, folder), 0755)
|
||||
}
|
||||
|
||||
// Create test tasks
|
||||
t1 := `# T01: Prvi task
|
||||
|
||||
**Agent:** coder
|
||||
**Model:** Sonnet
|
||||
**Zavisi od:** —
|
||||
|
||||
---
|
||||
|
||||
## Opis
|
||||
|
||||
Opis prvog taska.
|
||||
|
||||
---
|
||||
`
|
||||
t2 := `# T02: Drugi task
|
||||
|
||||
**Agent:** coder
|
||||
**Model:** Sonnet
|
||||
**Zavisi od:** T01
|
||||
|
||||
---
|
||||
|
||||
## Opis
|
||||
|
||||
Opis drugog taska.
|
||||
|
||||
---
|
||||
`
|
||||
os.WriteFile(filepath.Join(tasksDir, "done", "T01.md"), []byte(t1), 0644)
|
||||
os.WriteFile(filepath.Join(tasksDir, "ready", "T02.md"), []byte(t2), 0644)
|
||||
|
||||
cfg := &config.Config{
|
||||
TasksDir: tasksDir,
|
||||
ProjectPath: dir,
|
||||
}
|
||||
|
||||
return dir, cfg
|
||||
}
|
||||
|
||||
func TestCmdStatus_ShowsTasks(t *testing.T) {
|
||||
_, cfg := setupTestTasks(t)
|
||||
|
||||
// Capture output by redirecting stdout
|
||||
old := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
exitCode := cmdStatus(cfg)
|
||||
|
||||
w.Close()
|
||||
os.Stdout = old
|
||||
|
||||
var buf [4096]byte
|
||||
n, _ := r.Read(buf[:])
|
||||
output := string(buf[:n])
|
||||
|
||||
if exitCode != 0 {
|
||||
t.Errorf("expected exit code 0, got %d", exitCode)
|
||||
}
|
||||
if !strings.Contains(output, "T01") {
|
||||
t.Errorf("expected T01 in output, got:\n%s", output)
|
||||
}
|
||||
if !strings.Contains(output, "T02") {
|
||||
t.Errorf("expected T02 in output, got:\n%s", output)
|
||||
}
|
||||
if !strings.Contains(output, "done") {
|
||||
t.Errorf("expected 'done' status in output, got:\n%s", output)
|
||||
}
|
||||
if !strings.Contains(output, "ready") {
|
||||
t.Errorf("expected 'ready' status in output, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCmdNext_FindsReadyTask(t *testing.T) {
|
||||
_, cfg := setupTestTasks(t)
|
||||
|
||||
old := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
exitCode := cmdNext(cfg)
|
||||
|
||||
w.Close()
|
||||
os.Stdout = old
|
||||
|
||||
var buf [4096]byte
|
||||
n, _ := r.Read(buf[:])
|
||||
output := string(buf[:n])
|
||||
|
||||
if exitCode != 0 {
|
||||
t.Errorf("expected exit code 0, got %d", exitCode)
|
||||
}
|
||||
if !strings.Contains(output, "T02") {
|
||||
t.Errorf("expected T02 as next task, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCmdVerify_OnTestProject(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
// Create a minimal passing Go project
|
||||
os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module testproj\n\ngo 1.22\n"), 0644)
|
||||
os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main\n\nfunc main() {}\n"), 0644)
|
||||
|
||||
cfg := &config.Config{ProjectPath: dir}
|
||||
|
||||
// Set os.Args so cmdVerify doesn't pick up test flags
|
||||
oldArgs := os.Args
|
||||
os.Args = []string{"kaos-supervisor", "verify"}
|
||||
defer func() { os.Args = oldArgs }()
|
||||
|
||||
old := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
exitCode := cmdVerify(cfg)
|
||||
|
||||
w.Close()
|
||||
os.Stdout = old
|
||||
|
||||
var buf [4096]byte
|
||||
n, _ := r.Read(buf[:])
|
||||
output := string(buf[:n])
|
||||
|
||||
if exitCode != 0 {
|
||||
t.Errorf("expected exit code 0, got %d\noutput: %s", exitCode, output)
|
||||
}
|
||||
if !strings.Contains(output, "All checks passed") {
|
||||
t.Errorf("expected 'All checks passed', got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCmdHistory_ShowsReports(t *testing.T) {
|
||||
_, cfg := setupTestTasks(t)
|
||||
|
||||
// Create a fake report
|
||||
reportsDir := filepath.Join(cfg.TasksDir, "reports")
|
||||
os.WriteFile(filepath.Join(reportsDir, "T01-report.md"), []byte("# T01 report\n"), 0644)
|
||||
|
||||
old := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
exitCode := cmdHistory(cfg)
|
||||
|
||||
w.Close()
|
||||
os.Stdout = old
|
||||
|
||||
var buf [4096]byte
|
||||
n, _ := r.Read(buf[:])
|
||||
output := string(buf[:n])
|
||||
|
||||
if exitCode != 0 {
|
||||
t.Errorf("expected exit code 0, got %d", exitCode)
|
||||
}
|
||||
if !strings.Contains(output, "T01") {
|
||||
t.Errorf("expected T01 in history, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnknownCommand(t *testing.T) {
|
||||
// Test printHelp doesn't panic
|
||||
old := os.Stdout
|
||||
_, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
printHelp()
|
||||
w.Close()
|
||||
os.Stdout = old
|
||||
}
|
||||
|
||||
func TestCmdRun_NoReadyTasks(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
tasksDir := filepath.Join(dir, "TASKS")
|
||||
for _, folder := range []string{"backlog", "ready", "active", "review", "done", "reports"} {
|
||||
os.MkdirAll(filepath.Join(tasksDir, folder), 0755)
|
||||
}
|
||||
|
||||
cfg := &config.Config{
|
||||
TasksDir: tasksDir,
|
||||
ProjectPath: dir,
|
||||
}
|
||||
|
||||
old := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
// Save and restore os.Args
|
||||
oldArgs := os.Args
|
||||
os.Args = []string{"kaos-supervisor", "run"}
|
||||
defer func() { os.Args = oldArgs }()
|
||||
|
||||
exitCode := cmdRun(cfg)
|
||||
|
||||
w.Close()
|
||||
os.Stdout = old
|
||||
|
||||
var buf [4096]byte
|
||||
n, _ := r.Read(buf[:])
|
||||
output := string(buf[:n])
|
||||
|
||||
if exitCode != 0 {
|
||||
t.Errorf("expected exit code 0, got %d", exitCode)
|
||||
}
|
||||
if !strings.Contains(output, "Nema taskova") {
|
||||
t.Errorf("expected 'Nema taskova', got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusIcon(t *testing.T) {
|
||||
tests := []struct {
|
||||
status string
|
||||
want string
|
||||
}{
|
||||
{"done", "✅"},
|
||||
{"active", "🔄"},
|
||||
{"ready", "📋"},
|
||||
{"review", "👀"},
|
||||
{"backlog", "📦"},
|
||||
{"unknown", "❓"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := statusIcon(tt.status)
|
||||
if got != tt.want {
|
||||
t.Errorf("statusIcon(%q) = %q, want %q", tt.status, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that ScanTasks and NextTask integration works
|
||||
func TestNextTask_Integration(t *testing.T) {
|
||||
_, cfg := setupTestTasks(t)
|
||||
|
||||
tasks, err := supervisor.ScanTasks(cfg.TasksDir)
|
||||
if err != nil {
|
||||
t.Fatalf("ScanTasks error: %v", err)
|
||||
}
|
||||
|
||||
next := supervisor.NextTask(tasks)
|
||||
if next == nil {
|
||||
t.Fatal("expected a next task")
|
||||
}
|
||||
if next.ID != "T02" {
|
||||
t.Errorf("expected T02, got %s", next.ID)
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,8 @@ type Config struct {
|
||||
Timeout time.Duration
|
||||
// ProjectPath is the root path of the project being supervised.
|
||||
ProjectPath string
|
||||
// TasksDir is the path to the TASKS directory.
|
||||
TasksDir string
|
||||
}
|
||||
|
||||
// Load reads configuration from environment variables.
|
||||
@ -39,9 +41,15 @@ func Load() (*Config, error) {
|
||||
return nil, fmt.Errorf("KAOS_PROJECT_PATH environment variable is required")
|
||||
}
|
||||
|
||||
tasksDir := os.Getenv("KAOS_TASKS_DIR")
|
||||
if tasksDir == "" {
|
||||
return nil, fmt.Errorf("KAOS_TASKS_DIR environment variable is required")
|
||||
}
|
||||
|
||||
return &Config{
|
||||
Timeout: timeout,
|
||||
ProjectPath: projectPath,
|
||||
TasksDir: tasksDir,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
func TestLoad_WithValidEnv(t *testing.T) {
|
||||
t.Setenv("KAOS_TIMEOUT", "30m")
|
||||
t.Setenv("KAOS_PROJECT_PATH", "/tmp/test")
|
||||
t.Setenv("KAOS_TASKS_DIR", "/tmp/tasks")
|
||||
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
@ -25,11 +26,16 @@ func TestLoad_WithValidEnv(t *testing.T) {
|
||||
if cfg.ProjectPath != "/tmp/test" {
|
||||
t.Errorf("expected project path /tmp/test, got %s", cfg.ProjectPath)
|
||||
}
|
||||
|
||||
if cfg.TasksDir != "/tmp/tasks" {
|
||||
t.Errorf("expected tasks dir /tmp/tasks, got %s", cfg.TasksDir)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_MissingTimeout(t *testing.T) {
|
||||
t.Setenv("KAOS_TIMEOUT", "")
|
||||
t.Setenv("KAOS_PROJECT_PATH", "/tmp/test")
|
||||
t.Setenv("KAOS_TASKS_DIR", "/tmp/tasks")
|
||||
|
||||
_, err := Load()
|
||||
if err == nil {
|
||||
@ -40,6 +46,7 @@ func TestLoad_MissingTimeout(t *testing.T) {
|
||||
func TestLoad_InvalidTimeout(t *testing.T) {
|
||||
t.Setenv("KAOS_TIMEOUT", "not-a-duration")
|
||||
t.Setenv("KAOS_PROJECT_PATH", "/tmp/test")
|
||||
t.Setenv("KAOS_TASKS_DIR", "/tmp/tasks")
|
||||
|
||||
_, err := Load()
|
||||
if err == nil {
|
||||
@ -50,6 +57,7 @@ func TestLoad_InvalidTimeout(t *testing.T) {
|
||||
func TestLoad_MissingProjectPath(t *testing.T) {
|
||||
t.Setenv("KAOS_TIMEOUT", "30m")
|
||||
t.Setenv("KAOS_PROJECT_PATH", "")
|
||||
t.Setenv("KAOS_TASKS_DIR", "/tmp/tasks")
|
||||
|
||||
_, err := Load()
|
||||
if err == nil {
|
||||
@ -57,6 +65,17 @@ func TestLoad_MissingProjectPath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_MissingTasksDir(t *testing.T) {
|
||||
t.Setenv("KAOS_TIMEOUT", "30m")
|
||||
t.Setenv("KAOS_PROJECT_PATH", "/tmp/test")
|
||||
t.Setenv("KAOS_TASKS_DIR", "")
|
||||
|
||||
_, err := Load()
|
||||
if err == nil {
|
||||
t.Fatal("expected error for missing KAOS_TASKS_DIR")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadEnvFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
envPath := filepath.Join(dir, ".env")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user