package worker import ( "context" "fmt" "testing" "time" "github.com/orchard9/rdev/internal/domain" ) func TestVerifyExecutor_Execute_Success(t *testing.T) { cmdExec := newMockCommandExecutor() // Simulate capture.js JSON output cmdExec.output = []domain.OutputLine{ {Stream: "stdout", Line: `{"screenshots":{"1920x1080":"/captures/task-1/1920_1080.png","375x667":"/captures/task-1/375_667.png"},"video":"/captures/task-1/recording.webm"}`, Timestamp: time.Now()}, } exec := NewVerifyExecutor(cmdExec, nil, nil) task := &domain.WorkTask{ ID: "task-1", ProjectID: "project-1", Type: domain.WorkTaskTypeVerify, Spec: map[string]any{ "url": "https://example.com", "viewports": []any{"1920x1080", "375x667"}, "video": true, }, } result := exec.Execute(context.Background(), task) if !result.Success { t.Errorf("expected success, got error: %s", result.Error) } if result.DurationMs < 0 { t.Errorf("expected non-negative duration, got %d", result.DurationMs) } // Check artifacts were populated if result.Artifacts == nil { t.Fatal("expected artifacts to be populated") } if result.Artifacts["screenshot_1920x1080"] != "/captures/task-1/1920_1080.png" { t.Errorf("screenshot_1920x1080 = %q", result.Artifacts["screenshot_1920x1080"]) } if result.Artifacts["video"] != "/captures/task-1/recording.webm" { t.Errorf("video = %q", result.Artifacts["video"]) } } func TestVerifyExecutor_Execute_URLRequired(t *testing.T) { cmdExec := newMockCommandExecutor() exec := NewVerifyExecutor(cmdExec, nil, nil) task := &domain.WorkTask{ ID: "task-1", ProjectID: "project-1", Type: domain.WorkTaskTypeVerify, Spec: map[string]any{}, // Missing URL } result := exec.Execute(context.Background(), task) if result.Success { t.Error("expected failure for missing URL") } if result.Error == "" { t.Error("expected error message") } } func TestVerifyExecutor_Execute_InvalidURL(t *testing.T) { cmdExec := newMockCommandExecutor() exec := NewVerifyExecutor(cmdExec, nil, nil) task := &domain.WorkTask{ ID: "task-1", ProjectID: "project-1", Type: domain.WorkTaskTypeVerify, Spec: map[string]any{ "url": "ftp://invalid-scheme.com", }, } result := exec.Execute(context.Background(), task) if result.Success { t.Error("expected failure for invalid URL scheme") } } func TestVerifyExecutor_Execute_CaptureFailure(t *testing.T) { cmdExec := newMockCommandExecutor() cmdExec.err = fmt.Errorf("kubectl exec failed: connection refused") exec := NewVerifyExecutor(cmdExec, nil, nil) task := &domain.WorkTask{ ID: "task-1", ProjectID: "project-1", Type: domain.WorkTaskTypeVerify, Spec: map[string]any{ "url": "https://example.com", }, } result := exec.Execute(context.Background(), task) if result.Success { t.Error("expected failure on capture execution error") } if result.Error == "" { t.Error("expected error message") } } func TestVerifyExecutor_Execute_NonZeroExitCode(t *testing.T) { cmdExec := newMockCommandExecutor() cmdExec.result = &domain.CommandResult{ ExitCode: 1, DurationMs: 100, } exec := NewVerifyExecutor(cmdExec, nil, nil) task := &domain.WorkTask{ ID: "task-1", ProjectID: "project-1", Type: domain.WorkTaskTypeVerify, Spec: map[string]any{ "url": "https://example.com", }, } result := exec.Execute(context.Background(), task) if result.Success { t.Error("expected failure on non-zero exit code") } } func TestVerifyExecutor_Execute_InvalidManifestJSON(t *testing.T) { cmdExec := newMockCommandExecutor() cmdExec.output = []domain.OutputLine{ {Stream: "stdout", Line: "not valid json", Timestamp: time.Now()}, } exec := NewVerifyExecutor(cmdExec, nil, nil) task := &domain.WorkTask{ ID: "task-1", ProjectID: "project-1", Type: domain.WorkTaskTypeVerify, Spec: map[string]any{ "url": "https://example.com", }, } result := exec.Execute(context.Background(), task) if result.Success { t.Error("expected failure on invalid JSON manifest") } } func TestVerifyExecutor_ParseSpec(t *testing.T) { exec := NewVerifyExecutor(nil, nil, nil) t.Run("valid spec with all fields", func(t *testing.T) { spec, err := exec.parseSpec(map[string]any{ "url": "https://example.com", "viewports": []any{"1920x1080", "800x600"}, "wait_for": "#main", "wait_timeout": float64(5000), "full_page": true, "video": true, "evaluate": true, "prompt": "Check for hero section", "callback_url": "https://webhook.example.com/notify", }) if err != nil { t.Fatalf("parseSpec() error = %v", err) } if spec.URL != "https://example.com" { t.Errorf("URL = %q", spec.URL) } if len(spec.Viewports) != 2 { t.Errorf("Viewports count = %d", len(spec.Viewports)) } if spec.WaitFor != "#main" { t.Errorf("WaitFor = %q", spec.WaitFor) } if spec.WaitTimeout != 5000 { t.Errorf("WaitTimeout = %d", spec.WaitTimeout) } if !spec.FullPage { t.Error("expected FullPage = true") } if !spec.Video { t.Error("expected Video = true") } if !spec.Evaluate { t.Error("expected Evaluate = true") } if spec.Prompt != "Check for hero section" { t.Errorf("Prompt = %q", spec.Prompt) } if spec.CallbackURL != "https://webhook.example.com/notify" { t.Errorf("CallbackURL = %q", spec.CallbackURL) } }) t.Run("minimal spec", func(t *testing.T) { spec, err := exec.parseSpec(map[string]any{ "url": "https://example.com", }) if err != nil { t.Fatalf("parseSpec() error = %v", err) } if spec.URL != "https://example.com" { t.Errorf("URL = %q", spec.URL) } // Other fields should be zero/empty if len(spec.Viewports) != 0 { t.Errorf("expected empty viewports, got %v", spec.Viewports) } }) t.Run("missing URL", func(t *testing.T) { _, err := exec.parseSpec(map[string]any{ "viewports": []any{"1920x1080"}, }) if err == nil { t.Error("expected error for missing URL") } }) } func TestVerifyExecutor_BuildCaptureCommand(t *testing.T) { exec := NewVerifyExecutor(nil, nil, nil) spec := &domain.VerifySpec{ URL: "https://example.com/page", Viewports: []string{"1920x1080", "375x667"}, WaitFor: "#app", FullPage: true, Video: true, } args := exec.buildCaptureCommand(spec, "/captures/task-123") // Check command structure if args[0] != "node" { t.Errorf("expected 'node', got %q", args[0]) } if args[1] != "/scripts/capture.js" { t.Errorf("expected '/scripts/capture.js', got %q", args[1]) } // Check URL is included found := false for _, arg := range args { if arg == "--url=https://example.com/page" { found = true break } } if !found { t.Errorf("URL argument not found in %v", args) } // Check viewports found = false for _, arg := range args { if arg == "--viewports=1920x1080,375x667" { found = true break } } if !found { t.Errorf("viewports argument not found in %v", args) } // Check full-page flag found = false for _, arg := range args { if arg == "--full-page=true" { found = true break } } if !found { t.Errorf("full-page argument not found in %v", args) } // Check video flag found = false for _, arg := range args { if arg == "--video=true" { found = true break } } if !found { t.Errorf("video argument not found in %v", args) } } func TestVerifyExecutor_Config(t *testing.T) { t.Run("default config", func(t *testing.T) { exec := NewVerifyExecutor(nil, nil, nil) if exec.namespace != "rdev" { t.Errorf("namespace = %q, want 'rdev'", exec.namespace) } if exec.podName != "playwright-0" { t.Errorf("podName = %q, want 'playwright-0'", exec.podName) } }) t.Run("custom config", func(t *testing.T) { exec := NewVerifyExecutor(nil, nil, &VerifyExecutorConfig{ Namespace: "custom-ns", PodName: "custom-pod-0", }) if exec.namespace != "custom-ns" { t.Errorf("namespace = %q, want 'custom-ns'", exec.namespace) } if exec.podName != "custom-pod-0" { t.Errorf("podName = %q, want 'custom-pod-0'", exec.podName) } }) }