package service import ( "context" "log/slog" "testing" "time" "github.com/certctl-io/certctl/internal/domain" ) // TestCertificateService_RevokeCertificate_RevocationSvcNil tests RevokeCertificateWithActor // when RevocationSvc is not configured (nil). func TestCertificateService_RevokeCertificate_RevocationSvcNil(t *testing.T) { // Setup: Create CertificateService WITHOUT calling SetRevocationSvc certRepo := newMockCertificateRepository() auditRepo := newMockAuditRepository() policyRepo := newMockPolicyRepository() auditService := NewAuditService(auditRepo) policyService := NewPolicyService(policyRepo, auditService) // Create service WITHOUT RevocationSvc certService := NewCertificateService(certRepo, policyService, auditService) // Note: NOT calling certService.SetRevocationSvc(...) // Add a test certificate cert := &domain.ManagedCertificate{ ID: "cert-1", CommonName: "example.com", IssuerID: "iss-local", Status: domain.CertificateStatusActive, } certRepo.AddCert(cert) // Call RevokeCertificateWithActor with nil RevocationSvc err := certService.RevokeCertificate(context.Background(), "cert-1", "keyCompromise", "admin") // Assert: Should return error, NOT panic if err == nil { t.Fatal("expected error, got nil") } // Verify error message indicates service not configured errMsg := err.Error() if errMsg != "revocation service not configured" { t.Errorf("expected error message 'revocation service not configured', got: %s", errMsg) } } // TestCertificateService_GenerateDERCRL_CAOpsSvcNil tests GenerateDERCRL // when CAOperationsSvc is not configured (nil). func TestCertificateService_GenerateDERCRL_CAOpsSvcNil(t *testing.T) { // Setup: Create CertificateService WITHOUT calling SetCAOperationsSvc certRepo := newMockCertificateRepository() auditRepo := newMockAuditRepository() policyRepo := newMockPolicyRepository() auditService := NewAuditService(auditRepo) policyService := NewPolicyService(policyRepo, auditService) // Create service WITHOUT CAOperationsSvc certService := NewCertificateService(certRepo, policyService, auditService) // Note: NOT calling certService.SetCAOperationsSvc(...) // Call GenerateDERCRL with nil CAOperationsSvc _, err := certService.GenerateDERCRL(context.Background(), "iss-local") // Assert: Should return error, NOT panic if err == nil { t.Fatal("expected error, got nil") } // Verify error message indicates service not configured errMsg := err.Error() if errMsg != "CA operations service not configured" { t.Errorf("expected error message 'CA operations service not configured', got: %s", errMsg) } } // TestCertificateService_GetOCSPResponse_CAOpsSvcNil tests GetOCSPResponse // when CAOperationsSvc is not configured (nil). func TestCertificateService_GetOCSPResponse_CAOpsSvcNil(t *testing.T) { // Setup: Create CertificateService WITHOUT calling SetCAOperationsSvc certRepo := newMockCertificateRepository() auditRepo := newMockAuditRepository() policyRepo := newMockPolicyRepository() auditService := NewAuditService(auditRepo) policyService := NewPolicyService(policyRepo, auditService) // Create service WITHOUT CAOperationsSvc certService := NewCertificateService(certRepo, policyService, auditService) // Note: NOT calling certService.SetCAOperationsSvc(...) // Call GetOCSPResponse with nil CAOperationsSvc _, err := certService.GetOCSPResponse(context.Background(), "iss-local", "serial123") // Assert: Should return error, NOT panic if err == nil { t.Fatal("expected error, got nil") } // Verify error message indicates service not configured errMsg := err.Error() if errMsg != "CA operations service not configured" { t.Errorf("expected error message 'CA operations service not configured', got: %s", errMsg) } } // TestCertificateService_GetRevokedCertificates_RevocationSvcNil tests GetRevokedCertificates // when RevocationSvc is not configured (nil). func TestCertificateService_GetRevokedCertificates_RevocationSvcNil(t *testing.T) { // Setup: Create CertificateService WITHOUT calling SetRevocationSvc certRepo := newMockCertificateRepository() auditRepo := newMockAuditRepository() policyRepo := newMockPolicyRepository() auditService := NewAuditService(auditRepo) policyService := NewPolicyService(policyRepo, auditService) // Create service WITHOUT RevocationSvc certService := NewCertificateService(certRepo, policyService, auditService) // Note: NOT calling certService.SetRevocationSvc(...) // Call GetRevokedCertificates with nil RevocationSvc _, err := certService.GetRevokedCertificates(context.Background()) // Assert: Should return error, NOT panic if err == nil { t.Fatal("expected error, got nil") } // Verify error message indicates service not configured errMsg := err.Error() if errMsg != "revocation service not configured" { t.Errorf("expected error message 'revocation service not configured', got: %s", errMsg) } } // TestCertificateService_GetCertificateDeployments_Success tests GetCertificateDeployments // when TargetRepo is properly configured. func TestCertificateService_GetCertificateDeployments_Success(t *testing.T) { // Setup: Create CertificateService with properly configured TargetRepo certRepo := newMockCertificateRepository() auditRepo := newMockAuditRepository() policyRepo := newMockPolicyRepository() targetRepo := &mockTargetRepo{Targets: make(map[string]*domain.DeploymentTarget)} auditService := NewAuditService(auditRepo) policyService := NewPolicyService(policyRepo, auditService) certService := NewCertificateService(certRepo, policyService, auditService) certService.SetTargetRepo(targetRepo) // Add a test certificate cert := &domain.ManagedCertificate{ ID: "cert-1", CommonName: "example.com", IssuerID: "iss-local", Status: domain.CertificateStatusActive, } certRepo.AddCert(cert) // Add deployment targets target1 := &domain.DeploymentTarget{ ID: "t-1", Name: "nginx-prod", Type: domain.TargetTypeNGINX, } target2 := &domain.DeploymentTarget{ ID: "t-2", Name: "apache-prod", Type: domain.TargetTypeApache, } targetRepo.AddTarget(target1) targetRepo.AddTarget(target2) // Call GetCertificateDeployments deployments, err := certService.GetCertificateDeployments(context.Background(), "cert-1") // Assert: Should return deployment list successfully if err != nil { t.Fatalf("expected no error, got: %v", err) } // Verify deployments are returned (note: mock ListByCertificate returns all targets) if len(deployments) == 0 { t.Error("expected deployment list to be non-empty") } } // TestCertificateService_GetCertificateDeployments_RepositoryError tests GetCertificateDeployments // when TargetRepo returns an error. func TestCertificateService_GetCertificateDeployments_RepositoryError(t *testing.T) { // Setup: Create CertificateService with TargetRepo configured to return error certRepo := newMockCertificateRepository() auditRepo := newMockAuditRepository() policyRepo := newMockPolicyRepository() targetRepo := &mockTargetRepo{ Targets: make(map[string]*domain.DeploymentTarget), ListByCertErr: errNotFound, } auditService := NewAuditService(auditRepo) policyService := NewPolicyService(policyRepo, auditService) certService := NewCertificateService(certRepo, policyService, auditService) certService.SetTargetRepo(targetRepo) // Add a test certificate cert := &domain.ManagedCertificate{ ID: "cert-1", CommonName: "example.com", IssuerID: "iss-local", Status: domain.CertificateStatusActive, } certRepo.AddCert(cert) // Call GetCertificateDeployments with repo error _, err := certService.GetCertificateDeployments(context.Background(), "cert-1") // Assert: Should return error, NOT panic if err == nil { t.Fatal("expected error, got nil") } // Verify error indicates repo failure if err.Error() != "failed to list deployment targets: not found" { t.Errorf("expected repo error message, got: %s", err.Error()) } } // TestCertificateService_GetCertificateDeployments_CertNotFound tests GetCertificateDeployments // when the certificate doesn't exist. func TestCertificateService_GetCertificateDeployments_CertNotFound(t *testing.T) { // Setup: Create CertificateService with empty cert repo certRepo := newMockCertificateRepository() auditRepo := newMockAuditRepository() policyRepo := newMockPolicyRepository() targetRepo := &mockTargetRepo{Targets: make(map[string]*domain.DeploymentTarget)} auditService := NewAuditService(auditRepo) policyService := NewPolicyService(policyRepo, auditService) certService := NewCertificateService(certRepo, policyService, auditService) certService.SetTargetRepo(targetRepo) // Call GetCertificateDeployments with nonexistent certificate _, err := certService.GetCertificateDeployments(context.Background(), "nonexistent-cert") // Assert: Should return error if err == nil { t.Fatal("expected error for nonexistent certificate, got nil") } if err.Error() != "certificate not found: not found" { t.Errorf("expected certificate not found error, got: %s", err.Error()) } } // TestCertificateService_GetCertificateDeployments_NilTargetRepo tests GetCertificateDeployments // when TargetRepo is nil (empty graceful handling). func TestCertificateService_GetCertificateDeployments_NilTargetRepo(t *testing.T) { // Setup: Create CertificateService WITHOUT TargetRepo certRepo := newMockCertificateRepository() auditRepo := newMockAuditRepository() policyRepo := newMockPolicyRepository() auditService := NewAuditService(auditRepo) policyService := NewPolicyService(policyRepo, auditService) certService := NewCertificateService(certRepo, policyService, auditService) // Note: NOT calling certService.SetTargetRepo(...) // Add a test certificate cert := &domain.ManagedCertificate{ ID: "cert-1", CommonName: "example.com", IssuerID: "iss-local", Status: domain.CertificateStatusActive, } certRepo.AddCert(cert) // Call GetCertificateDeployments with nil TargetRepo deployments, err := certService.GetCertificateDeployments(context.Background(), "cert-1") // Assert: Should return empty list gracefully (not panic) if err != nil { t.Fatalf("expected no error, got: %v", err) } if len(deployments) != 0 { t.Errorf("expected empty deployment list, got %d deployments", len(deployments)) } } // TestCertificateService_Multiple_NilSafetyChecks tests multiple nil-safety operations in sequence. func TestCertificateService_Multiple_NilSafetyChecks(t *testing.T) { // Setup: Create CertificateService with partial configuration certRepo := newMockCertificateRepository() auditRepo := newMockAuditRepository() policyRepo := newMockPolicyRepository() auditService := NewAuditService(auditRepo) policyService := NewPolicyService(policyRepo, auditService) certService := NewCertificateService(certRepo, policyService, auditService) // Only set RevocationSvc, leave CAOperationsSvc nil revSvc := NewRevocationSvc(certRepo, newMockRevocationRepository(), auditService) certService.SetRevocationSvc(revSvc) // Add a test certificate cert := &domain.ManagedCertificate{ ID: "cert-1", CommonName: "example.com", IssuerID: "iss-local", Status: domain.CertificateStatusActive, ExpiresAt: time.Now().AddDate(0, 6, 0), } certRepo.AddCert(cert) // Add a certificate version version := &domain.CertificateVersion{ ID: "ver-1", CertificateID: "cert-1", SerialNumber: "ABC123", NotBefore: time.Now(), NotAfter: time.Now().AddDate(1, 0, 0), CreatedAt: time.Now(), } certRepo.Versions["cert-1"] = []*domain.CertificateVersion{version} // Set up issuer registry for revocation registry := NewIssuerRegistry(slog.Default()) registry.Set("iss-local", &mockIssuerConnector{}) revSvc.SetIssuerRegistry(registry) // Test 1: RevokeCertificateWithActor should succeed (RevocationSvc is set) errRevoke := certService.RevokeCertificate(context.Background(), "cert-1", "keyCompromise", "admin") if errRevoke != nil { t.Fatalf("RevokeCertificateWithActor failed unexpectedly: %v", errRevoke) } // Test 2: GenerateDERCRL should fail gracefully (CAOperationsSvc is nil) _, errCRL := certService.GenerateDERCRL(context.Background(), "iss-local") if errCRL == nil { t.Fatal("GenerateDERCRL expected error, got nil") } // Test 3: GetOCSPResponse should fail gracefully (CAOperationsSvc is nil) _, errOCSP := certService.GetOCSPResponse(context.Background(), "iss-local", "ABC123") if errOCSP == nil { t.Fatal("GetOCSPResponse expected error, got nil") } // Assert that errors are for correct reasons if errCRL.Error() != "CA operations service not configured" { t.Errorf("CRL error should be about CA ops service, got: %s", errCRL.Error()) } if errOCSP.Error() != "CA operations service not configured" { t.Errorf("OCSP error should be about CA ops service, got: %s", errOCSP.Error()) } }