rdev/internal/worker/verify_executor_test.go
jordan 53862c773b fix: resolve systemic debt in worker and skeleton templates
Worker template fixes:
- Replace panic() with logger.Error() + os.Exit(1) for config errors
- Remove double-timeout application (context + middleware)
- Add error message truncation to prevent log bloat
- Use named constants for shutdown grace period and stale check interval

Skeleton pkg/auth fixes:
- Fix error wrapping to use %w consistently in jwt.go
- Add GetUserOrError() as safe alternative to MustGetUser() panic

Skeleton pkg/queue fixes:
- Check RowsAffected() errors instead of ignoring them
- Add input validation to EnqueueWithOptions (require job type, cap retries)
- Add log truncation for error messages
- Fix inaccurate doc comment claiming exponential backoff

Worker timeout consolidation:
- Add internal/worker/timeouts.go with named constants
- Migrate all workers to use timeout constants

Cleanup:
- Remove obsolete slack-preparation-thoughts.md files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 23:44:55 -07:00

334 lines
8.0 KiB
Go

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)
}
})
}