From 2e2b2187b957e9af78797a67ec5c6874615fae02 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Sun, 8 Feb 2026 21:35:45 -1000 Subject: Initial project: task model, executor, API server, CLI, storage, reporter Claudomator automation toolkit for Claude Code with: - Task model with YAML parsing, validation, state machine (49 tests, 0 races) - SQLite storage for tasks and executions - Executor pool with bounded concurrency, timeout, cancellation - REST API + WebSocket for mobile PWA integration - Webhook/multi-notifier system - CLI: init, run, serve, list, status commands - Console, JSON, HTML reporters with cost tracking Co-Authored-By: Claude Opus 4.6 --- internal/task/parser_test.go | 152 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 internal/task/parser_test.go (limited to 'internal/task/parser_test.go') diff --git a/internal/task/parser_test.go b/internal/task/parser_test.go new file mode 100644 index 0000000..cb68e86 --- /dev/null +++ b/internal/task/parser_test.go @@ -0,0 +1,152 @@ +package task + +import ( + "os" + "path/filepath" + "testing" + "time" +) + +func TestParse_SingleTask(t *testing.T) { + yaml := ` +name: "Test Task" +description: "A simple test" +claude: + model: "sonnet" + instructions: "Do something" + working_dir: "/tmp" +timeout: "10m" +tags: + - "test" +` + tasks, err := Parse([]byte(yaml)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(tasks) != 1 { + t.Fatalf("expected 1 task, got %d", len(tasks)) + } + task := tasks[0] + if task.Name != "Test Task" { + t.Errorf("expected name 'Test Task', got %q", task.Name) + } + if task.Claude.Model != "sonnet" { + t.Errorf("expected model 'sonnet', got %q", task.Claude.Model) + } + if task.Timeout.Duration != 10*time.Minute { + t.Errorf("expected timeout 10m, got %v", task.Timeout.Duration) + } + if task.State != StatePending { + t.Errorf("expected state PENDING, got %v", task.State) + } + if task.ID == "" { + t.Error("expected auto-generated ID") + } + if task.Priority != PriorityNormal { + t.Errorf("expected default priority 'normal', got %q", task.Priority) + } +} + +func TestParse_BatchTasks(t *testing.T) { + yaml := ` +tasks: + - name: "Task A" + claude: + instructions: "Do A" + working_dir: "/tmp" + tags: ["alpha"] + - name: "Task B" + claude: + instructions: "Do B" + working_dir: "/tmp" + tags: ["beta"] +` + tasks, err := Parse([]byte(yaml)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(tasks) != 2 { + t.Fatalf("expected 2 tasks, got %d", len(tasks)) + } + if tasks[0].Name != "Task A" { + t.Errorf("expected 'Task A', got %q", tasks[0].Name) + } + if tasks[1].Name != "Task B" { + t.Errorf("expected 'Task B', got %q", tasks[1].Name) + } +} + +func TestParse_MissingName_ReturnsError(t *testing.T) { + yaml := ` +description: "no name" +claude: + instructions: "something" +` + _, err := Parse([]byte(yaml)) + if err == nil { + t.Fatal("expected error for missing name") + } +} + +func TestParse_DefaultRetryConfig(t *testing.T) { + yaml := ` +name: "Defaults" +claude: + instructions: "test" +` + tasks, err := Parse([]byte(yaml)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if tasks[0].Retry.MaxAttempts != 1 { + t.Errorf("expected default max_attempts=1, got %d", tasks[0].Retry.MaxAttempts) + } + if tasks[0].Retry.Backoff != "exponential" { + t.Errorf("expected default backoff 'exponential', got %q", tasks[0].Retry.Backoff) + } +} + +func TestParse_WithPriority(t *testing.T) { + yaml := ` +name: "High Priority" +priority: "high" +claude: + instructions: "urgent" +` + tasks, err := Parse([]byte(yaml)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if tasks[0].Priority != PriorityHigh { + t.Errorf("expected priority 'high', got %q", tasks[0].Priority) + } +} + +func TestParseFile(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "task.yaml") + content := ` +name: "File Task" +claude: + instructions: "from file" + working_dir: "/tmp" +` + if err := os.WriteFile(path, []byte(content), 0644); err != nil { + t.Fatal(err) + } + + tasks, err := ParseFile(path) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(tasks) != 1 || tasks[0].Name != "File Task" { + t.Errorf("unexpected tasks: %+v", tasks) + } +} + +func TestParseFile_NotFound(t *testing.T) { + _, err := ParseFile("/nonexistent/task.yaml") + if err == nil { + t.Fatal("expected error for nonexistent file") + } +} -- cgit v1.2.3