package service import ( "context" "errors" "strings" "testing" "github.com/certctl-io/certctl/internal/domain" ) // Bundle N.C-extended: service-layer round-out (70.5% → ≥80%). // Targets the previously-uncovered handler-interface methods on // CertificateService that delegate to the repo: GetCertificate, // CreateCertificate, UpdateCertificate, ArchiveCertificate, // GetCertificateVersions, SetJobRepo, SetKeygenMode, // ListCertificatesWithFilter, TriggerDeployment. func newTestCertSvc(t *testing.T) (*CertificateService, *mockCertRepo) { t.Helper() certRepo := &mockCertRepo{ Certs: make(map[string]*domain.ManagedCertificate), Versions: make(map[string][]*domain.CertificateVersion), } auditRepo := &mockAuditRepo{} auditService := NewAuditService(auditRepo) svc := NewCertificateService(certRepo, nil, auditService) return svc, certRepo } func TestCertificateService_GetCertificate_DelegatesToRepo(t *testing.T) { svc, repo := newTestCertSvc(t) repo.Certs["mc-1"] = &domain.ManagedCertificate{ID: "mc-1", Name: "x"} got, err := svc.GetCertificate(context.Background(), "mc-1") if err != nil { t.Fatalf("GetCertificate: %v", err) } if got == nil || got.ID != "mc-1" { t.Errorf("expected mc-1, got %+v", got) } } func TestCertificateService_GetCertificate_NotFound(t *testing.T) { svc, _ := newTestCertSvc(t) _, err := svc.GetCertificate(context.Background(), "missing") if err == nil { t.Errorf("expected NotFound error") } } func TestCertificateService_CreateCertificate_PopulatesDefaults(t *testing.T) { svc, _ := newTestCertSvc(t) cert := domain.ManagedCertificate{Name: "no-id-no-status"} got, err := svc.CreateCertificate(context.Background(), cert) if err != nil { t.Fatalf("CreateCertificate: %v", err) } if got.ID == "" { t.Errorf("expected ID populated, got empty") } if got.Status == "" { t.Errorf("expected default status populated") } if got.Tags == nil { t.Errorf("expected Tags initialized to non-nil map") } if got.CreatedAt.IsZero() { t.Errorf("expected CreatedAt populated") } } func TestCertificateService_CreateCertificate_RepoError(t *testing.T) { svc, repo := newTestCertSvc(t) repo.CreateErr = errors.New("db down") _, err := svc.CreateCertificate(context.Background(), domain.ManagedCertificate{ID: "mc-x", Name: "x"}) if err == nil || !strings.Contains(err.Error(), "failed to create") { t.Errorf("expected create-error wrapper, got %v", err) } } func TestCertificateService_UpdateCertificate_MergesPatch(t *testing.T) { svc, repo := newTestCertSvc(t) repo.Certs["mc-u"] = &domain.ManagedCertificate{ ID: "mc-u", Name: "old", CommonName: "old.example.com", Environment: "staging", } patch := domain.ManagedCertificate{ Name: "new", CommonName: "new.example.com", Environment: "prod", SANs: []string{"new.example.com"}, OwnerID: "o-alice", TeamID: "t-platform", IssuerID: "iss-le", } got, err := svc.UpdateCertificate(context.Background(), "mc-u", patch) if err != nil { t.Fatalf("UpdateCertificate: %v", err) } if got.Name != "new" || got.CommonName != "new.example.com" || got.Environment != "prod" { t.Errorf("expected merged fields, got %+v", got) } if got.OwnerID != "o-alice" || got.TeamID != "t-platform" { t.Errorf("expected owner/team merged, got %s/%s", got.OwnerID, got.TeamID) } } func TestCertificateService_UpdateCertificate_NotFound(t *testing.T) { svc, _ := newTestCertSvc(t) _, err := svc.UpdateCertificate(context.Background(), "missing", domain.ManagedCertificate{Name: "x"}) if err == nil || !strings.Contains(err.Error(), "not found") { t.Errorf("expected NotFound error, got %v", err) } } func TestCertificateService_UpdateCertificate_RepoUpdateError(t *testing.T) { svc, repo := newTestCertSvc(t) repo.Certs["mc-u"] = &domain.ManagedCertificate{ID: "mc-u", Name: "old"} repo.UpdateErr = errors.New("constraint violation") _, err := svc.UpdateCertificate(context.Background(), "mc-u", domain.ManagedCertificate{Name: "new"}) if err == nil || !strings.Contains(err.Error(), "failed to update") { t.Errorf("expected update-error wrapper, got %v", err) } } func TestCertificateService_ArchiveCertificate_DelegatesToRepo(t *testing.T) { svc, repo := newTestCertSvc(t) repo.Certs["mc-a"] = &domain.ManagedCertificate{ID: "mc-a"} if err := svc.ArchiveCertificate(context.Background(), "mc-a"); err != nil { t.Errorf("ArchiveCertificate: %v", err) } } func TestCertificateService_ArchiveCertificate_RepoError(t *testing.T) { svc, repo := newTestCertSvc(t) repo.ArchiveErr = errors.New("archive fail") if err := svc.ArchiveCertificate(context.Background(), "mc-a"); err == nil { t.Errorf("expected archive error to propagate") } } func TestCertificateService_GetCertificateVersions_PaginationDefaults(t *testing.T) { svc, repo := newTestCertSvc(t) versions := []*domain.CertificateVersion{ {SerialNumber: "01"}, {SerialNumber: "02"}, {SerialNumber: "03"}, } repo.ListVersionsResult = versions repo.Versions["mc-v"] = versions got, total, err := svc.GetCertificateVersions(context.Background(), "mc-v", 0, 0) if err != nil { t.Fatalf("GetCertificateVersions: %v", err) } if total != 3 { t.Errorf("expected total=3, got %d", total) } if len(got) != 3 { t.Errorf("expected 3 versions returned, got %d", len(got)) } } func TestCertificateService_GetCertificateVersions_PageOutOfRange(t *testing.T) { svc, repo := newTestCertSvc(t) repo.ListVersionsResult = []*domain.CertificateVersion{{SerialNumber: "01"}} got, total, err := svc.GetCertificateVersions(context.Background(), "mc-v", 99, 50) if err != nil { t.Fatalf("GetCertificateVersions: %v", err) } if total != 1 { t.Errorf("expected total=1, got %d", total) } if len(got) != 0 { t.Errorf("expected 0 results for out-of-range page, got %d", len(got)) } } func TestCertificateService_GetCertificateVersions_RepoError(t *testing.T) { svc, repo := newTestCertSvc(t) repo.ListVersionsErr = errors.New("list down") _, _, err := svc.GetCertificateVersions(context.Background(), "mc-v", 1, 50) if err == nil { t.Errorf("expected versions-list error to propagate") } } func TestCertificateService_SetJobRepo_SetKeygenMode_NoCrash(t *testing.T) { svc, _ := newTestCertSvc(t) // SetJobRepo accepts a repo (or nil) — confirm no panic. svc.SetJobRepo(nil) svc.SetKeygenMode("agent") svc.SetKeygenMode("server") }