package worker import ( "context" "testing" "time" "github.com/orchard9/rdev/internal/domain" ) // mockRegistryChecker is a mock implementation of port.RegistryChecker. type mockRegistryChecker struct { healthy bool err string latency time.Duration } func (m *mockRegistryChecker) Check(_ context.Context) domain.RegistryStatus { status := domain.RegistryStatus{ Healthy: m.healthy, URL: "https://registry.test", Latency: m.latency.String(), LastChecked: time.Now().UTC(), } if !m.healthy { status.Error = m.err } return status } // mockExternalHealthChecker is a mock implementation of port.ExternalHealthChecker. type mockExternalHealthChecker struct { system domain.ExternalSystem healthy bool err string latency time.Duration } func (m *mockExternalHealthChecker) Check(_ context.Context) domain.ExternalSystemStatus { status := domain.ExternalSystemStatus{ System: m.system, Healthy: m.healthy, URL: "https://test.system", Latency: m.latency, LastChecked: time.Now().UTC(), } if m.healthy { status.LastHealthy = status.LastChecked } else { status.Error = m.err } return status } func TestExternalHealthChecker_GetStatus(t *testing.T) { registry := &mockRegistryChecker{healthy: true, latency: 50 * time.Millisecond} ci := &mockExternalHealthChecker{system: domain.ExternalSystemCI, healthy: true, latency: 100 * time.Millisecond} git := &mockExternalHealthChecker{system: domain.ExternalSystemGit, healthy: true, latency: 75 * time.Millisecond} checker := NewExternalHealthChecker(registry, ci, git, ExternalHealthConfig{ CheckInterval: 100 * time.Millisecond, }) // Run checks synchronously checker.runChecks() // Verify registry status regStatus, ok := checker.GetStatus(domain.ExternalSystemRegistry) if !ok { t.Fatal("expected registry status to exist") } if !regStatus.Healthy { t.Error("expected registry to be healthy") } // Verify CI status ciStatus, ok := checker.GetStatus(domain.ExternalSystemCI) if !ok { t.Fatal("expected CI status to exist") } if !ciStatus.Healthy { t.Error("expected CI to be healthy") } // Verify Git status gitStatus, ok := checker.GetStatus(domain.ExternalSystemGit) if !ok { t.Fatal("expected Git status to exist") } if !gitStatus.Healthy { t.Error("expected Git to be healthy") } } func TestExternalHealthChecker_GetAllStatuses(t *testing.T) { registry := &mockRegistryChecker{healthy: true} ci := &mockExternalHealthChecker{system: domain.ExternalSystemCI, healthy: false, err: "connection refused"} git := &mockExternalHealthChecker{system: domain.ExternalSystemGit, healthy: true} checker := NewExternalHealthChecker(registry, ci, git, ExternalHealthConfig{ CheckInterval: 100 * time.Millisecond, }) checker.runChecks() statuses := checker.GetAllStatuses() if len(statuses) != 3 { t.Fatalf("expected 3 statuses, got %d", len(statuses)) } if statuses[domain.ExternalSystemRegistry].Healthy != true { t.Error("expected registry to be healthy") } if statuses[domain.ExternalSystemCI].Healthy != false { t.Error("expected CI to be unhealthy") } if statuses[domain.ExternalSystemCI].Error != "connection refused" { t.Errorf("expected CI error 'connection refused', got %q", statuses[domain.ExternalSystemCI].Error) } if statuses[domain.ExternalSystemGit].Healthy != true { t.Error("expected Git to be healthy") } } func TestExternalHealthChecker_NilCheckers(t *testing.T) { // All nil checkers should result in empty statuses checker := NewExternalHealthChecker(nil, nil, nil, ExternalHealthConfig{ CheckInterval: 100 * time.Millisecond, }) checker.runChecks() statuses := checker.GetAllStatuses() if len(statuses) != 0 { t.Fatalf("expected 0 statuses with nil checkers, got %d", len(statuses)) } } func TestExternalHealthChecker_StartStop(t *testing.T) { registry := &mockRegistryChecker{healthy: true} checker := NewExternalHealthChecker(registry, nil, nil, ExternalHealthConfig{ CheckInterval: 50 * time.Millisecond, }) checker.Start() // Wait for a couple of check cycles time.Sleep(120 * time.Millisecond) // Verify status was populated status, ok := checker.GetStatus(domain.ExternalSystemRegistry) if !ok { t.Fatal("expected registry status after start") } if !status.Healthy { t.Error("expected registry to be healthy") } checker.Stop() // After stop, statuses should still be available (cached) status, ok = checker.GetStatus(domain.ExternalSystemRegistry) if !ok { t.Fatal("expected registry status after stop") } } func TestExternalHealthChecker_StateTransition(t *testing.T) { registry := &mockRegistryChecker{healthy: true} checker := NewExternalHealthChecker(registry, nil, nil, ExternalHealthConfig{ CheckInterval: 100 * time.Millisecond, }) // Initial check - healthy checker.runChecks() status, _ := checker.GetStatus(domain.ExternalSystemRegistry) if !status.Healthy { t.Error("expected initial status to be healthy") } firstHealthy := status.LastHealthy // Change to unhealthy registry.healthy = false registry.err = "connection refused" checker.runChecks() status, _ = checker.GetStatus(domain.ExternalSystemRegistry) if status.Healthy { t.Error("expected status to be unhealthy after state change") } // LastHealthy should be preserved from when it was healthy if status.LastHealthy.IsZero() { t.Error("expected LastHealthy to be preserved") } if !status.LastHealthy.Equal(firstHealthy) { t.Error("expected LastHealthy to remain from healthy period") } // Recover to healthy registry.healthy = true registry.err = "" checker.runChecks() status, _ = checker.GetStatus(domain.ExternalSystemRegistry) if !status.Healthy { t.Error("expected status to be healthy after recovery") } // LastHealthy should be updated if status.LastHealthy.Before(firstHealthy) { t.Error("expected LastHealthy to be updated on recovery") } } func TestExternalHealthChecker_PartialFailure(t *testing.T) { // Registry healthy, CI unhealthy, Git healthy registry := &mockRegistryChecker{healthy: true} ci := &mockExternalHealthChecker{system: domain.ExternalSystemCI, healthy: false, err: "timeout"} git := &mockExternalHealthChecker{system: domain.ExternalSystemGit, healthy: true} checker := NewExternalHealthChecker(registry, ci, git, ExternalHealthConfig{ CheckInterval: 100 * time.Millisecond, }) checker.runChecks() statuses := checker.GetAllStatuses() // Partial failure should not affect other systems if !statuses[domain.ExternalSystemRegistry].Healthy { t.Error("registry should be healthy despite CI failure") } if statuses[domain.ExternalSystemCI].Healthy { t.Error("CI should be unhealthy") } if !statuses[domain.ExternalSystemGit].Healthy { t.Error("git should be healthy despite CI failure") } }