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>
216 lines
5.0 KiB
Go
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)
|
|
}
|
|
}
|