Files
certctl/internal/service/health_check_test.go
T
shankar0123 7cb453a336 chore(fmt): repo-wide gofmt -w sweep — close drift surfaced by ci-pipeline-cleanup Phase 4
Mechanical reformat. The new 'gofmt drift' CI step (added in
ci-pipeline-cleanup Phase 4, commit 0f205a8) surfaced 111 files
with accumulated gofmt drift across cmd/, internal/, and deploy/test/.

Each file's diff is gofmt-standard: whitespace adjustments, intra-
group import sorting (alphabetical by import path within blank-line-
separated groups), and struct-tag column alignment. No semantic
changes — verified via 'git diff --ignore-all-space' which shows only
the line-position deltas from import reordering.

The gate stays in place after this commit. Going forward it catches
gofmt drift at PR time.
2026-04-30 22:33:57 +00:00

351 lines
9.4 KiB
Go

package service
import (
"context"
"errors"
"log/slog"
"os"
"testing"
"time"
"github.com/shankar0123/certctl/internal/domain"
"github.com/shankar0123/certctl/internal/repository"
)
// mockHealthCheckRepo implements the HealthCheckRepository interface for testing.
type mockHealthCheckRepo struct {
checks map[string]*domain.EndpointHealthCheck
history []*domain.HealthHistoryEntry
createErr error
getErr error
updateErr error
deleteErr error
listErr error
listDueErr error
getHistoryErr error
recordHistoryErr error
purgeHistoryErr error
getSummaryErr error
getSummaryResult *domain.HealthCheckSummary
}
func newMockHealthCheckRepo() *mockHealthCheckRepo {
return &mockHealthCheckRepo{
checks: make(map[string]*domain.EndpointHealthCheck),
history: []*domain.HealthHistoryEntry{},
getSummaryResult: &domain.HealthCheckSummary{
Healthy: 0,
Degraded: 0,
Down: 0,
CertMismatch: 0,
Unknown: 0,
},
}
}
func (m *mockHealthCheckRepo) Create(ctx context.Context, check *domain.EndpointHealthCheck) error {
if m.createErr != nil {
return m.createErr
}
m.checks[check.ID] = check
return nil
}
func (m *mockHealthCheckRepo) Get(ctx context.Context, id string) (*domain.EndpointHealthCheck, error) {
if m.getErr != nil {
return nil, m.getErr
}
if check, ok := m.checks[id]; ok {
return check, nil
}
return nil, errors.New("not found")
}
func (m *mockHealthCheckRepo) GetByEndpoint(ctx context.Context, endpoint string) (*domain.EndpointHealthCheck, error) {
for _, check := range m.checks {
if check.Endpoint == endpoint {
return check, nil
}
}
return nil, errors.New("not found")
}
func (m *mockHealthCheckRepo) Update(ctx context.Context, check *domain.EndpointHealthCheck) error {
if m.updateErr != nil {
return m.updateErr
}
m.checks[check.ID] = check
return nil
}
func (m *mockHealthCheckRepo) Delete(ctx context.Context, id string) error {
if m.deleteErr != nil {
return m.deleteErr
}
delete(m.checks, id)
return nil
}
func (m *mockHealthCheckRepo) List(ctx context.Context, filter *repository.HealthCheckFilter) ([]*domain.EndpointHealthCheck, int, error) {
if m.listErr != nil {
return nil, 0, m.listErr
}
checks := make([]*domain.EndpointHealthCheck, 0, len(m.checks))
for _, check := range m.checks {
checks = append(checks, check)
}
return checks, len(checks), nil
}
func (m *mockHealthCheckRepo) ListDueForCheck(ctx context.Context) ([]*domain.EndpointHealthCheck, error) {
if m.listDueErr != nil {
return nil, m.listDueErr
}
checks := make([]*domain.EndpointHealthCheck, 0, len(m.checks))
for _, check := range m.checks {
if check.Enabled {
checks = append(checks, check)
}
}
return checks, nil
}
func (m *mockHealthCheckRepo) GetHistory(ctx context.Context, healthCheckID string, limit int) ([]*domain.HealthHistoryEntry, error) {
if m.getHistoryErr != nil {
return nil, m.getHistoryErr
}
return m.history, nil
}
func (m *mockHealthCheckRepo) RecordHistory(ctx context.Context, entry *domain.HealthHistoryEntry) error {
if m.recordHistoryErr != nil {
return m.recordHistoryErr
}
m.history = append(m.history, entry)
return nil
}
func (m *mockHealthCheckRepo) PurgeHistory(ctx context.Context, before time.Time) (int64, error) {
if m.purgeHistoryErr != nil {
return 0, m.purgeHistoryErr
}
return 0, nil
}
func (m *mockHealthCheckRepo) GetSummary(ctx context.Context) (*domain.HealthCheckSummary, error) {
if m.getSummaryErr != nil {
return nil, m.getSummaryErr
}
return m.getSummaryResult, nil
}
// Tests
func newTestLogger() *slog.Logger {
return slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))
}
func TestHealthCheckService_Create_Success(t *testing.T) {
repo := newMockHealthCheckRepo()
logger := newTestLogger()
svc := NewHealthCheckService(repo, nil, logger, 10, 5*time.Second, 30*24*time.Hour, false)
check := &domain.EndpointHealthCheck{
Endpoint: "example.com:443",
Status: domain.HealthStatusUnknown,
Enabled: true,
CheckIntervalSecs: 300,
}
err := svc.Create(context.Background(), check)
if err != nil {
t.Fatalf("Create failed: %v", err)
}
if check.ID == "" {
t.Fatal("Expected ID to be set")
}
retrieved, _ := repo.Get(context.Background(), check.ID)
if retrieved == nil {
t.Fatal("Expected check to be in repo")
}
if retrieved.Endpoint != "example.com:443" {
t.Errorf("Expected endpoint example.com:443, got %s", retrieved.Endpoint)
}
}
func TestHealthCheckService_Create_RepoError(t *testing.T) {
repo := newMockHealthCheckRepo()
repo.createErr = errors.New("db error")
logger := newTestLogger()
svc := NewHealthCheckService(repo, nil, logger, 10, 5*time.Second, 30*24*time.Hour, false)
check := &domain.EndpointHealthCheck{
Endpoint: "example.com:443",
Enabled: true,
}
err := svc.Create(context.Background(), check)
if err == nil {
t.Fatal("Expected error, got nil")
}
}
func TestHealthCheckService_Get_Success(t *testing.T) {
repo := newMockHealthCheckRepo()
logger := newTestLogger()
svc := NewHealthCheckService(repo, nil, logger, 10, 5*time.Second, 30*24*time.Hour, false)
check := &domain.EndpointHealthCheck{
ID: "hc-test-1",
Endpoint: "example.com:443",
Status: domain.HealthStatusHealthy,
}
repo.checks["hc-test-1"] = check
retrieved, err := svc.Get(context.Background(), "hc-test-1")
if err != nil {
t.Fatalf("Get failed: %v", err)
}
if retrieved.Endpoint != "example.com:443" {
t.Errorf("Expected endpoint example.com:443, got %s", retrieved.Endpoint)
}
}
func TestHealthCheckService_Get_NotFound(t *testing.T) {
repo := newMockHealthCheckRepo()
logger := newTestLogger()
svc := NewHealthCheckService(repo, nil, logger, 10, 5*time.Second, 30*24*time.Hour, false)
_, err := svc.Get(context.Background(), "nonexistent")
if err == nil {
t.Fatal("Expected error for nonexistent check")
}
}
func TestHealthCheckService_List_Success(t *testing.T) {
repo := newMockHealthCheckRepo()
logger := newTestLogger()
svc := NewHealthCheckService(repo, nil, logger, 10, 5*time.Second, 30*24*time.Hour, false)
check1 := &domain.EndpointHealthCheck{
ID: "hc-1",
Endpoint: "api.example.com:443",
Status: domain.HealthStatusHealthy,
}
check2 := &domain.EndpointHealthCheck{
ID: "hc-2",
Endpoint: "web.example.com:443",
Status: domain.HealthStatusDegraded,
}
repo.checks["hc-1"] = check1
repo.checks["hc-2"] = check2
checks, total, err := svc.List(context.Background(), nil)
if err != nil {
t.Fatalf("List failed: %v", err)
}
if len(checks) != 2 {
t.Errorf("Expected 2 checks, got %d", len(checks))
}
if total != 2 {
t.Errorf("Expected total 2, got %d", total)
}
}
func TestHealthCheckService_Delete_Success(t *testing.T) {
repo := newMockHealthCheckRepo()
logger := newTestLogger()
svc := NewHealthCheckService(repo, nil, logger, 10, 5*time.Second, 30*24*time.Hour, false)
check := &domain.EndpointHealthCheck{
ID: "hc-test-1",
Endpoint: "example.com:443",
}
repo.checks["hc-test-1"] = check
err := svc.Delete(context.Background(), "hc-test-1")
if err != nil {
t.Fatalf("Delete failed: %v", err)
}
if _, ok := repo.checks["hc-test-1"]; ok {
t.Fatal("Expected check to be deleted")
}
}
func TestHealthCheckService_AcknowledgeIncident_Success(t *testing.T) {
repo := newMockHealthCheckRepo()
logger := newTestLogger()
svc := NewHealthCheckService(repo, nil, logger, 10, 5*time.Second, 30*24*time.Hour, false)
check := &domain.EndpointHealthCheck{
ID: "hc-test-1",
Endpoint: "example.com:443",
Status: domain.HealthStatusDown,
Acknowledged: false,
}
repo.checks["hc-test-1"] = check
err := svc.AcknowledgeIncident(context.Background(), "hc-test-1", "user@example.com")
if err != nil {
t.Fatalf("AcknowledgeIncident failed: %v", err)
}
retrieved := repo.checks["hc-test-1"]
if !retrieved.Acknowledged {
t.Fatal("Expected Acknowledged to be true")
}
if retrieved.AcknowledgedBy != "user@example.com" {
t.Errorf("Expected AcknowledgedBy to be user@example.com, got %s", retrieved.AcknowledgedBy)
}
if retrieved.AcknowledgedAt == nil {
t.Fatal("Expected AcknowledgedAt to be set")
}
}
func TestHealthCheckService_GetSummary_Success(t *testing.T) {
repo := newMockHealthCheckRepo()
logger := newTestLogger()
svc := NewHealthCheckService(repo, nil, logger, 10, 5*time.Second, 30*24*time.Hour, false)
repo.getSummaryResult = &domain.HealthCheckSummary{
Healthy: 5,
Degraded: 2,
Down: 1,
CertMismatch: 1,
Unknown: 0,
}
summary, err := svc.GetSummary(context.Background())
if err != nil {
t.Fatalf("GetSummary failed: %v", err)
}
if summary.Healthy != 5 {
t.Errorf("Expected 5 healthy, got %d", summary.Healthy)
}
}
func TestHealthCheckService_RunHealthChecks_NoEndpoints(t *testing.T) {
repo := newMockHealthCheckRepo()
logger := newTestLogger()
svc := NewHealthCheckService(repo, nil, logger, 10, 5*time.Second, 30*24*time.Hour, false)
err := svc.RunHealthChecks(context.Background())
if err != nil {
t.Fatalf("RunHealthChecks failed: %v", err)
}
}
func TestHealthCheckService_PurgeOldHistory_Success(t *testing.T) {
repo := newMockHealthCheckRepo()
logger := newTestLogger()
svc := NewHealthCheckService(repo, nil, logger, 10, 5*time.Second, 30*24*time.Hour, false)
err := svc.PurgeOldHistory(context.Background())
if err != nil {
t.Fatalf("PurgeOldHistory failed: %v", err)
}
}