rdev/internal/service/work_service_test.go
jordan d69da6d627 feat: add structured logging infrastructure and SDLC extensions
Major changes:
- Add internal/logging package with field constants, context propagation,
  sensitive data auto-redaction, and per-component log levels
- Add worker timeout constants (TimeoutQuickOp, TimeoutHealthCheck, etc.)
- Extend SDLC with callback handlers, generate endpoints, and executor
- Add new cookbook trees for aeries and slackpath progression
- Add skeleton templates for queue, realtime, and microservices
- Add worker component template with async job processing
- Refactor services and handlers to use new logging infrastructure
- Split component.go into component_infra.go and component_listing.go

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

216 lines
5.0 KiB
Go

package service
import (
"context"
"testing"
"github.com/orchard9/rdev/internal/domain"
)
func newTestWorkService() (*WorkService, *mockWorkQueue) {
q := newMockWorkQueue()
svc := NewWorkService(q)
return svc, q
}
func TestWorkService_EnqueueTask(t *testing.T) {
t.Run("success", func(t *testing.T) {
svc, q := newTestWorkService()
result, err := svc.EnqueueTask(context.Background(), EnqueueTaskRequest{
ProjectID: "myapp",
Type: domain.WorkTaskTypeBuild,
Priority: 1,
Spec: map[string]any{"branch": "main"},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result.TaskID == "" {
t.Error("task ID should not be empty")
}
if result.StatusURL == "" {
t.Error("status URL should not be empty")
}
if len(q.tasks) != 1 {
t.Errorf("tasks in queue = %d, want 1", len(q.tasks))
}
})
t.Run("default max retries", func(t *testing.T) {
svc, q := newTestWorkService()
_, err := svc.EnqueueTask(context.Background(), EnqueueTaskRequest{
ProjectID: "myapp",
Type: domain.WorkTaskTypeBuild,
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
for _, task := range q.tasks {
if task.MaxRetries != 3 {
t.Errorf("max retries = %d, want 3 (default)", task.MaxRetries)
}
}
})
t.Run("missing project id", func(t *testing.T) {
svc, _ := newTestWorkService()
_, err := svc.EnqueueTask(context.Background(), EnqueueTaskRequest{
Type: domain.WorkTaskTypeBuild,
})
if err == nil {
t.Error("expected error for missing project_id")
}
})
t.Run("missing type", func(t *testing.T) {
svc, _ := newTestWorkService()
_, err := svc.EnqueueTask(context.Background(), EnqueueTaskRequest{
ProjectID: "myapp",
})
if err == nil {
t.Error("expected error for missing type")
}
})
}
func TestWorkService_DequeueTask(t *testing.T) {
t.Run("success", func(t *testing.T) {
svc, q := newTestWorkService()
// Enqueue a task first
q.tasks["task-1"] = &domain.WorkTask{
ID: "task-1",
ProjectID: "myapp",
Type: domain.WorkTaskTypeBuild,
Status: domain.WorkTaskStatusPending,
}
task, err := svc.DequeueTask(context.Background(), "worker-1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if task == nil {
t.Fatal("expected a task, got nil")
}
if task.ID != "task-1" {
t.Errorf("task ID = %q, want %q", task.ID, "task-1")
}
if task.Status != domain.WorkTaskStatusRunning {
t.Errorf("task status = %q, want %q", task.Status, domain.WorkTaskStatusRunning)
}
})
t.Run("empty queue", func(t *testing.T) {
svc, _ := newTestWorkService()
task, err := svc.DequeueTask(context.Background(), "worker-1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if task != nil {
t.Error("expected nil task for empty queue")
}
})
t.Run("missing worker id", func(t *testing.T) {
svc, _ := newTestWorkService()
_, err := svc.DequeueTask(context.Background(), "")
if err == nil {
t.Error("expected error for missing worker_id")
}
})
}
func TestWorkService_CompleteTask(t *testing.T) {
t.Run("success", func(t *testing.T) {
svc, q := newTestWorkService()
q.tasks["task-1"] = &domain.WorkTask{
ID: "task-1",
ProjectID: "myapp",
Type: domain.WorkTaskTypeBuild,
Status: domain.WorkTaskStatusRunning,
}
result := &domain.WorkResult{Output: "ok"}
err := svc.CompleteTask(context.Background(), "task-1", result)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
task := q.tasks["task-1"]
if task.Status != domain.WorkTaskStatusCompleted {
t.Errorf("status = %q, want %q", task.Status, domain.WorkTaskStatusCompleted)
}
})
t.Run("task not found", func(t *testing.T) {
svc, _ := newTestWorkService()
err := svc.CompleteTask(context.Background(), "nonexistent", nil)
if err == nil {
t.Error("expected error for nonexistent task")
}
})
}
func TestWorkService_GetTask(t *testing.T) {
t.Run("found", func(t *testing.T) {
svc, q := newTestWorkService()
q.tasks["task-1"] = &domain.WorkTask{
ID: "task-1",
ProjectID: "myapp",
}
task, err := svc.GetTask(context.Background(), "task-1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if task.ID != "task-1" {
t.Errorf("task ID = %q, want %q", task.ID, "task-1")
}
})
t.Run("not found", func(t *testing.T) {
svc, _ := newTestWorkService()
_, err := svc.GetTask(context.Background(), "missing")
if err == nil {
t.Error("expected error for missing task")
}
})
}
func TestWorkService_GetStats(t *testing.T) {
svc, _ := newTestWorkService()
stats, err := svc.GetStats(context.Background())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if stats == nil {
t.Error("stats should not be nil")
}
}
func TestWorkService_CancelTask(t *testing.T) {
svc, q := newTestWorkService()
q.tasks["task-1"] = &domain.WorkTask{
ID: "task-1",
ProjectID: "myapp",
Status: domain.WorkTaskStatusPending,
}
err := svc.CancelTask(context.Background(), "task-1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}