mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 14:11:31 +00:00
cdc9d03d5b
Collapses CertificateService, RevocationSvc, and CAOperationsSvc to ctx-accepting method signatures. Removes context.Background() synthesis at 24 internal call sites across certificate.go, revocation_svc.go, and ca_operations.go. - Primary repo calls inherit request cancellation via the passed ctx. - Audit and notification dispatches use context.WithoutCancel(ctx) so they survive client disconnect. - Collapses TriggerRenewal/TriggerRenewalWithActor, TriggerDeployment/TriggerDeploymentWithActor, and RevokeCertificate/RevokeCertificateWithActor sibling pairs into single canonical ctx-accepting methods (decisions D-1, D-2). Handlers pass r.Context(). Mocks and tests updated to match new signatures. No HTTP surface change, no OpenAPI change. PR 1 of 6 in the M-2 remediation chain. Master green at this commit. Refs: certctl-audit-report.md M-2 (L143, L224)
365 lines
13 KiB
Go
365 lines
13 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/shankar0123/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())
|
|
}
|
|
}
|