rdev/internal/worker/mock_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

389 lines
10 KiB
Go

package worker
import (
"context"
"fmt"
"sync"
"time"
"github.com/orchard9/rdev/internal/domain"
"github.com/orchard9/rdev/internal/port"
"github.com/orchard9/rdev/internal/service"
)
// =============================================================================
// Mock implementations for worker package tests
// =============================================================================
type mockWorkQueue struct {
mu sync.Mutex
tasks map[string]*domain.WorkTask
err error
}
func newMockWorkQueue() *mockWorkQueue {
return &mockWorkQueue{tasks: make(map[string]*domain.WorkTask)}
}
func (m *mockWorkQueue) Enqueue(_ context.Context, task *domain.WorkTask) (string, error) {
m.mu.Lock()
defer m.mu.Unlock()
if m.err != nil {
return "", m.err
}
id := fmt.Sprintf("task-%d", len(m.tasks)+1)
task.ID = id
task.Status = domain.WorkTaskStatusPending
task.CreatedAt = time.Now()
m.tasks[id] = task
return id, nil
}
func (m *mockWorkQueue) Dequeue(_ context.Context, workerID string) (*domain.WorkTask, error) {
m.mu.Lock()
defer m.mu.Unlock()
if m.err != nil {
return nil, m.err
}
for _, task := range m.tasks {
if task.Status == domain.WorkTaskStatusPending {
task.Status = domain.WorkTaskStatusRunning
task.WorkerID = workerID
now := time.Now()
task.StartedAt = &now
return task, nil
}
}
return nil, nil
}
func (m *mockWorkQueue) Complete(_ context.Context, taskID string, result *domain.WorkResult) error {
m.mu.Lock()
defer m.mu.Unlock()
task, ok := m.tasks[taskID]
if !ok {
return domain.ErrWorkTaskNotFound
}
task.Status = domain.WorkTaskStatusCompleted
task.Result = result
now := time.Now()
task.CompletedAt = &now
return nil
}
func (m *mockWorkQueue) Fail(_ context.Context, taskID string, errMsg string) error {
return m.FailWithCode(context.Background(), taskID, errMsg, domain.WorkErrorCodeNone)
}
func (m *mockWorkQueue) FailWithCode(_ context.Context, taskID string, errMsg string, code domain.WorkErrorCode) error {
m.mu.Lock()
defer m.mu.Unlock()
task, ok := m.tasks[taskID]
if !ok {
return domain.ErrWorkTaskNotFound
}
task.RetryCount++
if task.RetryCount >= task.MaxRetries {
task.Status = domain.WorkTaskStatusFailed
task.Error = errMsg
task.ErrorCode = code
} else {
task.Status = domain.WorkTaskStatusPending
task.WorkerID = ""
}
return nil
}
func (m *mockWorkQueue) Cancel(_ context.Context, taskID string) error { return nil }
func (m *mockWorkQueue) GetTask(_ context.Context, taskID string) (*domain.WorkTask, error) {
m.mu.Lock()
defer m.mu.Unlock()
task, ok := m.tasks[taskID]
if !ok {
return nil, domain.ErrWorkTaskNotFound
}
return task, nil
}
func (m *mockWorkQueue) ListByProject(_ context.Context, _ string, _ *domain.WorkTaskStatus, _ domain.WorkListOptions) (*domain.WorkListResult, error) {
return &domain.WorkListResult{}, nil
}
func (m *mockWorkQueue) GetStats(_ context.Context) (*domain.WorkQueueStats, error) {
return &domain.WorkQueueStats{}, nil
}
func (m *mockWorkQueue) CleanupOld(_ context.Context, _ time.Duration) (int64, error) {
return 0, nil
}
func (m *mockWorkQueue) RequeueStale(_ context.Context, _ time.Duration) (int64, error) {
return 0, nil
}
func (m *mockWorkQueue) RequeueStaleWithIDs(_ context.Context, _ time.Duration) ([]string, error) {
return nil, nil
}
type mockWorkerRegistry struct {
mu sync.Mutex
workers map[string]*domain.Worker
err error
}
func newMockWorkerRegistry() *mockWorkerRegistry {
return &mockWorkerRegistry{workers: make(map[string]*domain.Worker)}
}
func (m *mockWorkerRegistry) Register(_ context.Context, worker *domain.Worker) error {
m.mu.Lock()
defer m.mu.Unlock()
if m.err != nil {
return m.err
}
m.workers[worker.ID] = worker
return nil
}
func (m *mockWorkerRegistry) Heartbeat(_ context.Context, workerID string) error {
m.mu.Lock()
defer m.mu.Unlock()
w, ok := m.workers[workerID]
if !ok {
return domain.ErrWorkerNotFound
}
w.LastHeartbeat = time.Now()
return nil
}
func (m *mockWorkerRegistry) UpdateStatus(_ context.Context, workerID string, status domain.WorkerStatus, taskID string) error {
m.mu.Lock()
defer m.mu.Unlock()
w, ok := m.workers[workerID]
if !ok {
return domain.ErrWorkerNotFound
}
w.Status = status
w.CurrentTask = taskID
return nil
}
func (m *mockWorkerRegistry) Deregister(_ context.Context, workerID string) error {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.workers, workerID)
return nil
}
func (m *mockWorkerRegistry) Get(_ context.Context, workerID string) (*domain.Worker, error) {
m.mu.Lock()
defer m.mu.Unlock()
w, ok := m.workers[workerID]
if !ok {
return nil, domain.ErrWorkerNotFound
}
return w, nil
}
func (m *mockWorkerRegistry) List(_ context.Context, filter port.WorkerFilter) ([]*domain.Worker, error) {
m.mu.Lock()
defer m.mu.Unlock()
var result []*domain.Worker
for _, w := range m.workers {
if filter.Status != nil && w.Status != *filter.Status {
continue
}
result = append(result, w)
}
return result, nil
}
func (m *mockWorkerRegistry) MarkStaleOffline(_ context.Context, _ time.Duration) (int, error) {
return 0, nil
}
type mockBuildAudit struct {
mu sync.Mutex
entries map[string]*domain.BuildAuditEntry
}
func newMockBuildAudit() *mockBuildAudit {
return &mockBuildAudit{entries: make(map[string]*domain.BuildAuditEntry)}
}
func (m *mockBuildAudit) Record(_ context.Context, entry *domain.BuildAuditEntry) error {
m.mu.Lock()
defer m.mu.Unlock()
m.entries[entry.TaskID] = entry
return nil
}
func (m *mockBuildAudit) Update(_ context.Context, taskID string, result *domain.BuildResult) error {
m.mu.Lock()
defer m.mu.Unlock()
entry, ok := m.entries[taskID]
if !ok {
return domain.ErrBuildNotFound
}
entry.Result = result
if result.Success {
entry.Status = domain.BuildStatusCompleted
} else {
entry.Status = domain.BuildStatusFailed
}
return nil
}
func (m *mockBuildAudit) UpdateStatus(_ context.Context, taskID string, status domain.BuildStatus, workerID string) error {
m.mu.Lock()
defer m.mu.Unlock()
entry, ok := m.entries[taskID]
if !ok {
return domain.ErrBuildNotFound
}
entry.Status = status
entry.WorkerID = workerID
return nil
}
func (m *mockBuildAudit) Get(_ context.Context, taskID string) (*domain.BuildAuditEntry, error) {
m.mu.Lock()
defer m.mu.Unlock()
entry, ok := m.entries[taskID]
if !ok {
return nil, domain.ErrBuildNotFound
}
return entry, nil
}
func (m *mockBuildAudit) List(_ context.Context, _ port.BuildAuditFilter) ([]*domain.BuildAuditEntry, error) {
return nil, nil
}
type mockCodeAgent struct {
result *domain.AgentResult
err error
}
func (m *mockCodeAgent) Name() string { return "mock-agent" }
func (m *mockCodeAgent) Provider() domain.AgentProvider { return "mock" }
func (m *mockCodeAgent) Cancel(_ context.Context, _ string) error { return nil }
func (m *mockCodeAgent) Capabilities() domain.AgentCapabilities {
return domain.AgentCapabilities{Provider: "mock"}
}
func (m *mockCodeAgent) Available(_ context.Context) bool { return true }
func (m *mockCodeAgent) Execute(_ context.Context, req *domain.AgentRequest, handler domain.AgentEventHandler) (*domain.AgentResult, error) {
if handler != nil {
handler(domain.AgentEvent{
Type: domain.AgentEventOutput,
Content: "mock output for: " + req.Prompt,
})
}
if m.err != nil {
return nil, m.err
}
return m.result, nil
}
type mockCodeAgentRegistry struct {
agent port.CodeAgent
}
func (m *mockCodeAgentRegistry) Register(agent port.CodeAgent) { m.agent = agent }
func (m *mockCodeAgentRegistry) Get(_ domain.AgentProvider) port.CodeAgent { return m.agent }
func (m *mockCodeAgentRegistry) Default() port.CodeAgent { return m.agent }
func (m *mockCodeAgentRegistry) DefaultProvider() domain.AgentProvider { return "mock" }
func (m *mockCodeAgentRegistry) SetDefault(_ domain.AgentProvider) error { return nil }
func (m *mockCodeAgentRegistry) Available() []domain.AgentProvider {
return []domain.AgentProvider{"mock"}
}
func (m *mockCodeAgentRegistry) AvailableAgents(_ context.Context) []port.CodeAgent {
return []port.CodeAgent{m.agent}
}
func (m *mockCodeAgentRegistry) Count() int { return 1 }
// =============================================================================
// Mock CommandExecutor for verify tests
// =============================================================================
type mockCommandExecutor struct {
result *domain.CommandResult
err error
output []domain.OutputLine
podExists bool
podExistErr error
}
func newMockCommandExecutor() *mockCommandExecutor {
return &mockCommandExecutor{
result: &domain.CommandResult{
ExitCode: 0,
DurationMs: 100,
},
podExists: true,
}
}
func (m *mockCommandExecutor) Execute(_ context.Context, _ *domain.Command, _ string, handler domain.OutputHandler) (*domain.CommandResult, error) {
if m.err != nil {
return nil, m.err
}
// Deliver output lines to handler
for _, line := range m.output {
handler(line)
}
return m.result, nil
}
func (m *mockCommandExecutor) Cancel(_ context.Context, _ domain.CommandID) error {
return nil
}
func (m *mockCommandExecutor) PodExists(_ context.Context, _ string) (bool, error) {
return m.podExists, m.podExistErr
}
func (m *mockCommandExecutor) CheckConnection(_ context.Context) error {
return nil
}
// =============================================================================
// Helper to build test dependencies
// =============================================================================
type testDeps struct {
queue *mockWorkQueue
registry *mockWorkerRegistry
audit *mockBuildAudit
agent *mockCodeAgent
workerSvc *service.WorkerService
workSvc *service.WorkService
buildExec *BuildExecutor
}
func newTestDeps() *testDeps {
queue := newMockWorkQueue()
registry := newMockWorkerRegistry()
audit := newMockBuildAudit()
agent := &mockCodeAgent{
result: &domain.AgentResult{
ExitCode: 0,
DurationMs: 1000,
},
}
agentRegistry := &mockCodeAgentRegistry{agent: agent}
workerSvc := service.NewWorkerService(registry, queue).
WithBuildAudit(audit)
workSvc := service.NewWorkService(queue)
buildExec := NewBuildExecutor(agentRegistry, nil, nil, nil, nil)
return &testDeps{
queue: queue,
registry: registry,
audit: audit,
agent: agent,
workerSvc: workerSvc,
workSvc: workSvc,
buildExec: buildExec,
}
}