rdev/internal/worker/sdlc_executor_test.go
jordan 853ec4cf81 fix: go.work race condition with batch components and idempotent provisioning
Three coordinated fixes for CI pipeline race conditions:

1. Woodpecker step dependencies: Added depends_on: [deps] to all 6 component
   templates (service, worker, cli, app-astro, app-react, app-nextjs) so build
   steps wait for go work sync to complete.

2. Idempotent resource provisioning: Modified provisionResources() to check
   for existing database/cache before creating, preventing "already exists"
   errors on component re-adds.

3. Batch component endpoint: POST /projects/{id}/components/batch enables
   atomic multi-component additions in a single git commit. Validates all
   components upfront, provisions infra sequentially, commits code components
   atomically.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 12:31:40 -07:00

184 lines
4.6 KiB
Go

package worker
import (
"context"
"testing"
"time"
"github.com/orchard9/rdev/internal/domain"
)
func TestSDLCTaskExecutor_ParseSpec(t *testing.T) {
exec := NewSDLCTaskExecutor(SDLCTaskExecutorConfig{
Namespace: "rdev",
})
t.Run("valid spec", func(t *testing.T) {
spec, err := exec.parseSpec(map[string]any{
"command": "feature-create",
"args": []any{"auth-flow", "--title", "Authentication Flow"},
"git_clone_url": "https://git.example.com/owner/repo.git",
"auto_commit": true,
"auto_push": true,
})
if err != nil {
t.Fatalf("parseSpec() error = %v", err)
}
if spec.Command != "feature-create" {
t.Errorf("got command %q, want %q", spec.Command, "feature-create")
}
if len(spec.Args) != 3 {
t.Errorf("got %d args, want 3", len(spec.Args))
}
if spec.GitCloneURL != "https://git.example.com/owner/repo.git" {
t.Errorf("got git_clone_url %q", spec.GitCloneURL)
}
if !spec.AutoCommit {
t.Error("expected auto_commit = true")
}
if !spec.AutoPush {
t.Error("expected auto_push = true")
}
})
t.Run("missing command", func(t *testing.T) {
_, err := exec.parseSpec(map[string]any{
"git_clone_url": "https://git.example.com/owner/repo.git",
})
if err == nil {
t.Error("expected error for missing command")
}
})
t.Run("missing git_clone_url", func(t *testing.T) {
_, err := exec.parseSpec(map[string]any{
"command": "feature-create",
})
if err == nil {
t.Error("expected error for missing git_clone_url")
}
})
t.Run("args as string slice", func(t *testing.T) {
spec, err := exec.parseSpec(map[string]any{
"command": "feature-create",
"args": []string{"arg1", "arg2"},
"git_clone_url": "https://git.example.com/owner/repo.git",
})
if err != nil {
t.Fatalf("parseSpec() error = %v", err)
}
if len(spec.Args) != 2 {
t.Errorf("got %d args, want 2", len(spec.Args))
}
})
t.Run("empty args", func(t *testing.T) {
spec, err := exec.parseSpec(map[string]any{
"command": "state",
"git_clone_url": "https://git.example.com/owner/repo.git",
})
if err != nil {
t.Fatalf("parseSpec() error = %v", err)
}
if len(spec.Args) != 0 {
t.Errorf("got %d args, want 0", len(spec.Args))
}
})
}
func TestSDLCTaskExecutor_Execute_NoPodGitOps(t *testing.T) {
exec := NewSDLCTaskExecutor(SDLCTaskExecutorConfig{
Namespace: "rdev",
PodGitOps: nil, // No git operations configured
})
task := &domain.WorkTask{
ID: "task-1",
ProjectID: "project-1",
Type: domain.WorkTaskTypeSDLC,
Spec: map[string]any{
"command": "feature-create",
"args": []any{"auth-flow"},
"git_clone_url": "https://git.example.com/owner/repo.git",
},
CreatedAt: time.Now(),
}
result := exec.Execute(context.Background(), task)
if result.Success {
t.Error("expected failure when pod git operations not configured")
}
if result.Error == "" {
t.Error("expected error message")
}
}
func TestSDLCTaskExecutor_Execute_InvalidSpec(t *testing.T) {
exec := NewSDLCTaskExecutor(SDLCTaskExecutorConfig{
Namespace: "rdev",
})
task := &domain.WorkTask{
ID: "task-1",
ProjectID: "project-1",
Type: domain.WorkTaskTypeSDLC,
Spec: map[string]any{
// Missing required fields
},
CreatedAt: time.Now(),
}
result := exec.Execute(context.Background(), task)
if result.Success {
t.Error("expected failure for invalid spec")
}
if result.Error == "" {
t.Error("expected error message for invalid spec")
}
}
func TestSDLCTaskSpec_Valid(t *testing.T) {
// Verify the domain type is valid
spec := domain.SDLCTaskSpec{
Command: "feature-create",
Args: []string{"auth-flow", "--title", "Auth Flow"},
GitCloneURL: "https://git.example.com/owner/repo.git",
AutoCommit: true,
AutoPush: true,
}
if spec.Command == "" {
t.Error("command should not be empty")
}
if len(spec.Args) != 3 {
t.Errorf("got %d args, want 3", len(spec.Args))
}
}
func TestShellQuote(t *testing.T) {
tests := []struct {
name string
input string
want string
}{
{"simple", "auth-flow", "auth-flow"},
{"with space", "Authentication System", "'Authentication System'"},
{"with single quote", "it's working", "'it'\"'\"'s working'"},
{"flag", "--title", "--title"},
{"empty", "", ""},
{"with dollar", "$HOME", "'$HOME'"},
{"with backtick", "`cmd`", "'`cmd`'"},
{"with semicolon", "a;b", "'a;b'"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := shellQuote(tt.input)
if got != tt.want {
t.Errorf("shellQuote(%q) = %q, want %q", tt.input, got, tt.want)
}
})
}
}