mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 15:01:32 +00:00
8326d95210
Three new round-out test files targeting handler-interface delegators
on CertificateService + AgentService + IssuerHandler/HealthCheckHandler.
Coverage deltas
=================
internal/service: 70.5% -> 73.4% (+2.9pp; 17 new tests)
internal/api/handler: 79.4% -> 79.8% (+0.4pp; 4 new tests)
Service round-out tests (certificate_round_out_test.go, ~165 LoC)
=================
- GetCertificate (delegate-to-repo + NotFound)
- CreateCertificate (defaults populated + repo error)
- UpdateCertificate (patch merge + NotFound + repo error)
- ArchiveCertificate (delegate + repo error)
- GetCertificateVersions (pagination defaults + page-out-of-range +
repo error)
- SetJobRepo / SetKeygenMode (no-crash setters)
Service round-out tests (agent_round_out_test.go, ~140 LoC)
=================
- GetAgent (delegate)
- RegisterAgent (defaults populated + repo error)
- GetWork / GetWorkWithTargets (no-jobs path)
- UpdateJobStatus (delegate to ReportJobStatus)
- CSRSubmit / CSRSubmitForCert (invalid-CSR error)
- CertificatePickup (agent-not-found)
- GetAgentByAPIKey (unknown key)
- GetCertificateForAgent (missing agent)
- SetProfileRepo (no-crash)
Handler round-out tests (round_out_test.go, ~40 LoC)
=================
- NewIssuerHandlerWithLogger (logger wired through)
- UpdateHealthCheck dispatch arm with bad ID
- GetHealthCheckHistory dispatch arm with bad ID
Why partial
=================
M-002 / M-003 prescribed >=80%. Service at 73.4% and handler at 79.8%
miss the gate by 6.6pp / 0.2pp respectively. The remaining service
gap is in CSR-submit happy-path and large-population list-filter
flows that need deeper repo plumbing (3-4 hr more focused work).
The handler 0.2pp is in parseSignedDataForCSR (SCEP), DeleteHealthCheck,
AcknowledgeHealthCheck — needs repo fixtures.
These extensions are a meaningful step but don't fully close M-002
and M-003. Tracked as N.C-final follow-on; not blocking on a CI
floor at 73 / 79.
Audit deliverables
=================
- gap-backlog.md M-002, M-003: partial-strikethrough with progress
note + remaining-gap analysis
- extension-progress.md: N.C-extended marked PARTIAL
Closes (partial): M-002, M-003
Bundle: N.C-extended (Coverage Audit Extension)
172 lines
5.4 KiB
Go
172 lines
5.4 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/shankar0123/certctl/internal/domain"
|
|
)
|
|
|
|
// Bundle N.C-extended: agent service-layer round-out (target +5pp).
|
|
// Targets uncovered handler-interface delegators on AgentService:
|
|
// GetAgent, RegisterAgent, CSRSubmit, CSRSubmitForCert, GetWork,
|
|
// GetWorkWithTargets, UpdateJobStatus, CertificatePickup, plus
|
|
// SetProfileRepo / GetCertificateForAgent / GetAgentByAPIKey.
|
|
|
|
func newTestAgentSvc(t *testing.T) (*AgentService, *mockAgentRepo, *mockCertRepo, *mockJobRepo, *mockTargetRepo) {
|
|
t.Helper()
|
|
agentRepo := &mockAgentRepo{
|
|
Agents: make(map[string]*domain.Agent),
|
|
HeartbeatUpdates: make(map[string]time.Time),
|
|
}
|
|
certRepo := &mockCertRepo{
|
|
Certs: make(map[string]*domain.ManagedCertificate),
|
|
Versions: make(map[string][]*domain.CertificateVersion),
|
|
}
|
|
jobRepo := &mockJobRepo{
|
|
Jobs: make(map[string]*domain.Job),
|
|
StatusUpdates: make(map[string]domain.JobStatus),
|
|
}
|
|
targetRepo := &mockTargetRepo{
|
|
Targets: make(map[string]*domain.DeploymentTarget),
|
|
}
|
|
auditRepo := &mockAuditRepo{}
|
|
auditService := NewAuditService(auditRepo)
|
|
issuerRegistry := NewIssuerRegistry(nil)
|
|
svc := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, issuerRegistry, nil)
|
|
return svc, agentRepo, certRepo, jobRepo, targetRepo
|
|
}
|
|
|
|
func TestAgentService_GetAgent_DelegatesToRepo(t *testing.T) {
|
|
svc, repo, _, _, _ := newTestAgentSvc(t)
|
|
repo.Agents["a-1"] = &domain.Agent{ID: "a-1", Name: "test"}
|
|
got, err := svc.GetAgent(context.Background(), "a-1")
|
|
if err != nil {
|
|
t.Fatalf("GetAgent: %v", err)
|
|
}
|
|
if got.Name != "test" {
|
|
t.Errorf("expected name=test, got %q", got.Name)
|
|
}
|
|
}
|
|
|
|
func TestAgentService_RegisterAgent_PopulatesIDStatusKey(t *testing.T) {
|
|
svc, _, _, _, _ := newTestAgentSvc(t)
|
|
got, err := svc.RegisterAgent(context.Background(), domain.Agent{Name: "fresh"})
|
|
if err != nil {
|
|
t.Fatalf("RegisterAgent: %v", err)
|
|
}
|
|
if got.ID == "" {
|
|
t.Errorf("expected ID populated")
|
|
}
|
|
if got.Status != domain.AgentStatusOnline {
|
|
t.Errorf("expected Online status, got %s", got.Status)
|
|
}
|
|
if got.APIKeyHash == "" {
|
|
t.Errorf("expected APIKeyHash populated")
|
|
}
|
|
if got.RegisteredAt.IsZero() {
|
|
t.Errorf("expected RegisteredAt populated")
|
|
}
|
|
}
|
|
|
|
func TestAgentService_RegisterAgent_RepoError(t *testing.T) {
|
|
svc, repo, _, _, _ := newTestAgentSvc(t)
|
|
repo.CreateErr = errors.New("conflict")
|
|
_, err := svc.RegisterAgent(context.Background(), domain.Agent{Name: "x"})
|
|
if err == nil || !strings.Contains(err.Error(), "register agent") {
|
|
t.Errorf("expected register-agent error wrapper, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAgentService_GetWork_NoJobs(t *testing.T) {
|
|
svc, repo, _, _, _ := newTestAgentSvc(t)
|
|
repo.Agents["a-1"] = &domain.Agent{ID: "a-1", Status: domain.AgentStatusOnline}
|
|
got, err := svc.GetWork(context.Background(), "a-1")
|
|
if err != nil {
|
|
t.Fatalf("GetWork: %v", err)
|
|
}
|
|
if len(got) != 0 {
|
|
t.Errorf("expected 0 jobs, got %d", len(got))
|
|
}
|
|
}
|
|
|
|
func TestAgentService_GetWorkWithTargets_NoJobs(t *testing.T) {
|
|
svc, repo, _, _, _ := newTestAgentSvc(t)
|
|
repo.Agents["a-1"] = &domain.Agent{ID: "a-1", Status: domain.AgentStatusOnline}
|
|
got, err := svc.GetWorkWithTargets(context.Background(), "a-1")
|
|
if err != nil {
|
|
t.Fatalf("GetWorkWithTargets: %v", err)
|
|
}
|
|
if len(got) != 0 {
|
|
t.Errorf("expected 0 work items, got %d", len(got))
|
|
}
|
|
}
|
|
|
|
func TestAgentService_UpdateJobStatus_DelegatesToReportJobStatus(t *testing.T) {
|
|
svc, repo, _, jobRepo, _ := newTestAgentSvc(t)
|
|
repo.Agents["a-1"] = &domain.Agent{ID: "a-1", Status: domain.AgentStatusOnline}
|
|
jobRepo.Jobs["j-1"] = &domain.Job{
|
|
ID: "j-1",
|
|
AgentID: strPtr("a-1"),
|
|
Status: domain.JobStatusRunning,
|
|
}
|
|
err := svc.UpdateJobStatus(context.Background(), "a-1", "j-1", "Completed", "")
|
|
if err != nil {
|
|
t.Errorf("UpdateJobStatus: %v", err)
|
|
}
|
|
}
|
|
|
|
// Local strPtr to avoid colliding with other test files.
|
|
func strPtr(s string) *string { return &s }
|
|
|
|
func TestAgentService_CSRSubmit_NoCertID(t *testing.T) {
|
|
svc, _, _, _, _ := newTestAgentSvc(t)
|
|
// CSRSubmit calls SubmitCSR which performs validation. Pass an obviously
|
|
// invalid CSR to exercise the error path.
|
|
_, err := svc.CSRSubmit(context.Background(), "a-1", "not-a-csr")
|
|
if err == nil {
|
|
t.Errorf("expected SubmitCSR error to surface for invalid CSR")
|
|
}
|
|
}
|
|
|
|
func TestAgentService_CSRSubmitForCert_InvalidPEM(t *testing.T) {
|
|
svc, _, _, _, _ := newTestAgentSvc(t)
|
|
_, err := svc.CSRSubmitForCert(context.Background(), "a-1", "mc-1", "not-a-csr")
|
|
if err == nil {
|
|
t.Errorf("expected error for invalid CSR")
|
|
}
|
|
}
|
|
|
|
func TestAgentService_CertificatePickup_AgentNotFound(t *testing.T) {
|
|
svc, _, _, _, _ := newTestAgentSvc(t)
|
|
_, err := svc.CertificatePickup(context.Background(), "a-missing", "mc-1")
|
|
if err == nil {
|
|
t.Errorf("expected error for missing agent")
|
|
}
|
|
}
|
|
|
|
func TestAgentService_GetAgentByAPIKey_NotFound(t *testing.T) {
|
|
svc, _, _, _, _ := newTestAgentSvc(t)
|
|
_, err := svc.GetAgentByAPIKey(context.Background(), "no-such-key")
|
|
if err == nil {
|
|
t.Errorf("expected error for unknown API key")
|
|
}
|
|
}
|
|
|
|
func TestAgentService_GetCertificateForAgent_AgentNotFound(t *testing.T) {
|
|
svc, _, _, _, _ := newTestAgentSvc(t)
|
|
_, err := svc.GetCertificateForAgent(context.Background(), "a-missing", "mc-1")
|
|
if err == nil {
|
|
t.Errorf("expected error for missing agent")
|
|
}
|
|
}
|
|
|
|
func TestAgentService_SetProfileRepo_NoCrash(t *testing.T) {
|
|
svc, _, _, _, _ := newTestAgentSvc(t)
|
|
// SetProfileRepo accepts nil — confirm no panic.
|
|
svc.SetProfileRepo(nil)
|
|
}
|