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.
This commit is contained in:
shankar0123
2026-04-30 22:33:57 +00:00
parent e2298c8222
commit 7cb453a336
111 changed files with 761 additions and 770 deletions
@@ -56,8 +56,8 @@ func adversarialPathInputs() []struct {
{"null_byte_trailing", "mc-001\x00"},
{"null_byte_embedded", "mc-\x00-001"},
{"long_id_10k", strings.Repeat("A", 10000)},
{"unicode_homoglyph_hyphen", "mc\u2010001"}, // U+2010 HYPHEN
{"unicode_homoglyph_fullwidth", "mc\uFF0D001"}, // U+FF0D FULLWIDTH HYPHEN-MINUS
{"unicode_homoglyph_hyphen", "mc\u2010001"}, // U+2010 HYPHEN
{"unicode_homoglyph_fullwidth", "mc\uFF0D001"}, // U+FF0D FULLWIDTH HYPHEN-MINUS
{"control_char_newline", "mc-001\n"},
{"control_char_tab", "mc\t001"},
{"control_char_bell", "mc\x07001"},
+2 -2
View File
@@ -1,10 +1,10 @@
package handler
import (
"github.com/shankar0123/certctl/internal/repository"
"errors"
"context"
"encoding/json"
"errors"
"github.com/shankar0123/certctl/internal/repository"
"net/http"
"strconv"
"strings"
+2 -2
View File
@@ -28,8 +28,8 @@ type MockAgentService struct {
// I-004: soft-retirement hooks. Tests that don't set these receive nil
// results and nil errors, which mirrors the safest default (no-op) for
// unrelated suites that mock only the legacy surface.
RetireAgentFn func(agentID, actor string, force bool, reason string) (*service.AgentRetirementResult, error)
ListRetiredAgentsFn func(page, perPage int) ([]domain.Agent, int64, error)
RetireAgentFn func(agentID, actor string, force bool, reason string) (*service.AgentRetirementResult, error)
ListRetiredAgentsFn func(page, perPage int) ([]domain.Agent, int64, error)
}
func (m *MockAgentService) ListAgents(_ context.Context, page, perPage int) ([]domain.Agent, int64, error) {
@@ -56,10 +56,10 @@ func TestRetireAgentHandler_Success_200(t *testing.T) {
}
var body struct {
RetiredAt time.Time `json:"retired_at"`
AlreadyRetired bool `json:"already_retired"`
Cascade bool `json:"cascade"`
Counts domain.AgentDependencyCounts `json:"counts"`
RetiredAt time.Time `json:"retired_at"`
AlreadyRetired bool `json:"already_retired"`
Cascade bool `json:"cascade"`
Counts domain.AgentDependencyCounts `json:"counts"`
}
if err := json.NewDecoder(w.Body).Decode(&body); err != nil {
t.Fatalf("decode 200 body: %v", err)
@@ -273,10 +273,10 @@ func TestRetireAgentHandler_ForceCascade_200(t *testing.T) {
}
var body struct {
RetiredAt time.Time `json:"retired_at"`
AlreadyRetired bool `json:"already_retired"`
Cascade bool `json:"cascade"`
Counts domain.AgentDependencyCounts `json:"counts"`
RetiredAt time.Time `json:"retired_at"`
AlreadyRetired bool `json:"already_retired"`
Cascade bool `json:"cascade"`
Counts domain.AgentDependencyCounts `json:"counts"`
}
if err := json.NewDecoder(w.Body).Decode(&body); err != nil {
t.Fatalf("decode force-cascade 200 body: %v", err)
+1 -1
View File
@@ -1,10 +1,10 @@
package handler
import (
"github.com/shankar0123/certctl/internal/repository"
"context"
"encoding/json"
"errors"
"github.com/shankar0123/certctl/internal/repository"
"log/slog"
"net/http"
"strconv"
+3 -3
View File
@@ -9,14 +9,14 @@ import (
"testing"
"time"
"github.com/shankar0123/certctl/internal/domain"
"github.com/shankar0123/certctl/internal/api/middleware"
"github.com/shankar0123/certctl/internal/domain"
)
// mockAuditService implements AuditService for testing.
type mockAuditService struct {
listFunc func(page, perPage int) ([]domain.AuditEvent, int64, error)
getFunc func(id string) (*domain.AuditEvent, error)
listFunc func(page, perPage int) ([]domain.AuditEvent, int64, error)
getFunc func(id string) (*domain.AuditEvent, error)
}
func (m *mockAuditService) ListAuditEvents(_ context.Context, page, perPage int) ([]domain.AuditEvent, int64, error) {
@@ -86,10 +86,10 @@ func TestBulkRenew_PartialFailure_ReportsBoth(t *testing.T) {
svc := &mockBulkRenewalService{
BulkRenewFn: func(ctx context.Context, criteria domain.BulkRenewalCriteria, actor string) (*domain.BulkRenewalResult, error) {
return &domain.BulkRenewalResult{
TotalMatched: 3,
TotalMatched: 3,
TotalEnqueued: 2,
TotalSkipped: 0,
TotalFailed: 1,
TotalSkipped: 0,
TotalFailed: 1,
Errors: []domain.BulkOperationError{
{CertificateID: "mc-failed", Error: "renewal job enqueue failed: db timeout"},
},
+6 -6
View File
@@ -98,12 +98,12 @@ func generateTestCertPEM(t *testing.T) string {
t.Fatalf("failed to generate key: %v", err)
}
template := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "Test CA"},
NotBefore: time.Now().Add(-1 * time.Hour),
NotAfter: time.Now().Add(24 * time.Hour),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
IsCA: true,
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "Test CA"},
NotBefore: time.Now().Add(-1 * time.Hour),
NotAfter: time.Now().Add(24 * time.Hour),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
IsCA: true,
BasicConstraintsValid: true,
}
certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
+2 -2
View File
@@ -1,11 +1,11 @@
package handler
import (
"github.com/shankar0123/certctl/internal/repository"
"errors"
"context"
"encoding/json"
"errors"
"fmt"
"github.com/shankar0123/certctl/internal/repository"
"log/slog"
"net/http"
"strings"
+6 -6
View File
@@ -59,12 +59,12 @@ func (h *HealthCheckHandler) ListHealthChecks(w http.ResponseWriter, r *http.Req
}
filter := &repository.HealthCheckFilter{
Status: status,
CertificateID: certificateID,
NetworkScanTargetID: networkScanTargetID,
Enabled: enabledFilter,
Page: page,
PerPage: perPage,
Status: status,
CertificateID: certificateID,
NetworkScanTargetID: networkScanTargetID,
Enabled: enabledFilter,
Page: page,
PerPage: perPage,
}
checks, total, err := h.service.List(r.Context(), filter)
+2 -2
View File
@@ -1,10 +1,10 @@
package handler
import (
"github.com/shankar0123/certctl/internal/repository"
"errors"
"context"
"encoding/json"
"errors"
"github.com/shankar0123/certctl/internal/repository"
"log/slog"
"net/http"
"strconv"
+1 -1
View File
@@ -1,10 +1,10 @@
package handler
import (
"github.com/shankar0123/certctl/internal/repository"
"context"
"encoding/json"
"errors"
"github.com/shankar0123/certctl/internal/repository"
"io"
"net/http"
"strconv"
+2 -2
View File
@@ -1,9 +1,9 @@
package handler
import (
"github.com/shankar0123/certctl/internal/repository"
"errors"
"context"
"errors"
"github.com/shankar0123/certctl/internal/repository"
"net/http"
"strconv"
"strings"
+2 -2
View File
@@ -1,10 +1,10 @@
package handler
import (
"github.com/shankar0123/certctl/internal/repository"
"errors"
"context"
"encoding/json"
"errors"
"github.com/shankar0123/certctl/internal/repository"
"net/http"
"strconv"
"strings"
+3 -3
View File
@@ -237,9 +237,9 @@ func TestCreateProfile_Success(t *testing.T) {
}
body := map[string]interface{}{
"name": "New Profile",
"name": "New Profile",
"max_ttl_seconds": 86400,
"allowed_ekus": []string{"serverAuth"},
"allowed_ekus": []string{"serverAuth"},
}
bodyBytes, _ := json.Marshal(body)
@@ -331,7 +331,7 @@ func TestUpdateProfile_Success(t *testing.T) {
}
body := map[string]interface{}{
"name": "Updated Profile",
"name": "Updated Profile",
"max_ttl_seconds": 172800,
}
bodyBytes, _ := json.Marshal(body)
+2 -2
View File
@@ -1,10 +1,10 @@
package handler
import (
"github.com/shankar0123/certctl/internal/repository"
"errors"
"context"
"encoding/json"
"errors"
"github.com/shankar0123/certctl/internal/repository"
"net/http"
"strconv"
"strings"
+1 -1
View File
@@ -1,10 +1,10 @@
package handler
import (
"github.com/shankar0123/certctl/internal/repository"
"context"
"encoding/json"
"errors"
"github.com/shankar0123/certctl/internal/repository"
"net/http"
"strconv"
"strings"
@@ -26,11 +26,11 @@ import (
// MockRenewalPolicyService is a mock implementation of RenewalPolicyService.
type MockRenewalPolicyService struct {
ListRenewalPoliciesFn func(page, perPage int) ([]domain.RenewalPolicy, int64, error)
GetRenewalPolicyFn func(id string) (*domain.RenewalPolicy, error)
CreateRenewalPolicyFn func(rp domain.RenewalPolicy) (*domain.RenewalPolicy, error)
UpdateRenewalPolicyFn func(id string, rp domain.RenewalPolicy) (*domain.RenewalPolicy, error)
DeleteRenewalPolicyFn func(id string) error
ListRenewalPoliciesFn func(page, perPage int) ([]domain.RenewalPolicy, int64, error)
GetRenewalPolicyFn func(id string) (*domain.RenewalPolicy, error)
CreateRenewalPolicyFn func(rp domain.RenewalPolicy) (*domain.RenewalPolicy, error)
UpdateRenewalPolicyFn func(id string, rp domain.RenewalPolicy) (*domain.RenewalPolicy, error)
DeleteRenewalPolicyFn func(id string) error
}
func (m *MockRenewalPolicyService) ListRenewalPolicies(_ context.Context, page, perPage int) ([]domain.RenewalPolicy, int64, error) {
@@ -199,11 +199,11 @@ func TestCreateRenewalPolicy_Success(t *testing.T) {
}
body := map[string]interface{}{
"name": "New Policy",
"renewal_window_days": 30,
"max_retries": 3,
"name": "New Policy",
"renewal_window_days": 30,
"max_retries": 3,
"retry_interval_seconds": 3600,
"auto_renew": true,
"auto_renew": true,
}
bodyBytes, _ := json.Marshal(body)
@@ -221,8 +221,8 @@ func TestCreateRenewalPolicy_Success(t *testing.T) {
func TestCreateRenewalPolicy_MissingName(t *testing.T) {
body := map[string]interface{}{
"renewal_window_days": 30,
"max_retries": 3,
"renewal_window_days": 30,
"max_retries": 3,
"retry_interval_seconds": 3600,
}
bodyBytes, _ := json.Marshal(body)
@@ -261,9 +261,9 @@ func TestCreateRenewalPolicy_DuplicateName(t *testing.T) {
}
body := map[string]interface{}{
"name": "Duplicate",
"renewal_window_days": 30,
"max_retries": 3,
"name": "Duplicate",
"renewal_window_days": 30,
"max_retries": 3,
"retry_interval_seconds": 3600,
}
bodyBytes, _ := json.Marshal(body)
@@ -308,11 +308,11 @@ func TestUpdateRenewalPolicy_Success(t *testing.T) {
}
body := map[string]interface{}{
"name": "Updated Policy",
"renewal_window_days": 45,
"max_retries": 5,
"name": "Updated Policy",
"renewal_window_days": 45,
"max_retries": 5,
"retry_interval_seconds": 1800,
"auto_renew": true,
"auto_renew": true,
}
bodyBytes, _ := json.Marshal(body)
@@ -336,9 +336,9 @@ func TestUpdateRenewalPolicy_NotFound(t *testing.T) {
}
body := map[string]interface{}{
"name": "Updated",
"renewal_window_days": 30,
"max_retries": 3,
"name": "Updated",
"renewal_window_days": 30,
"max_retries": 3,
"retry_interval_seconds": 3600,
}
bodyBytes, _ := json.Marshal(body)
+6 -6
View File
@@ -346,8 +346,8 @@ func TestCursorPagedResponse_EmptyNextCursor(t *testing.T) {
func TestFilterFields_SingleObject(t *testing.T) {
data := map[string]interface{}{
"id": "cert-123",
"name": "My Cert",
"id": "cert-123",
"name": "My Cert",
"expiry": "2025-01-01",
"status": "active",
}
@@ -379,8 +379,8 @@ func TestFilterFields_SingleObject(t *testing.T) {
func TestFilterFields_EmptyFields(t *testing.T) {
// Empty fields list should return data unchanged
data := map[string]interface{}{
"id": "cert-123",
"name": "My Cert",
"id": "cert-123",
"name": "My Cert",
}
result := filterFields(data, []string{})
@@ -398,8 +398,8 @@ func TestFilterFields_EmptyFields(t *testing.T) {
func TestFilterFields_NoMatchingFields(t *testing.T) {
data := map[string]interface{}{
"id": "cert-123",
"name": "My Cert",
"id": "cert-123",
"name": "My Cert",
}
result := filterFields(data, []string{"nonexistent", "also-not-there"})
+1 -1
View File
@@ -333,7 +333,7 @@ func TestValidateCSRPEM_InvalidInputs(t *testing.T) {
// TestValidatePolicyType_ValidTypes tests valid policy types.
func TestValidatePolicyType_ValidTypes(t *testing.T) {
validTypes := []struct {
name string
name string
ptype interface{}
}{
{
+2 -2
View File
@@ -97,8 +97,8 @@ func (h VerificationHandler) VerifyDeployment(w http.ResponseWriter, r *http.Req
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"job_id": jobID,
"verified": req.Verified,
"job_id": jobID,
"verified": req.Verified,
"verified_at": result.VerifiedAt,
})
}
+5 -5
View File
@@ -221,11 +221,11 @@ func NewAuditServiceAdapter(recordFn func(ctx context.Context, actor string, act
// RecordAPICall implements AuditRecorder by translating API call data into an audit event.
func (a *AuditServiceAdapter) RecordAPICall(ctx context.Context, method, path, actor string, bodyHash string, status int, latencyMs int64) error {
details := map[string]interface{}{
"method": method,
"path": path,
"body_hash": bodyHash,
"status": status,
"latency_ms": latencyMs,
"method": method,
"path": path,
"body_hash": bodyHash,
"status": status,
"latency_ms": latencyMs,
}
action := fmt.Sprintf("api_%s", strings.ToLower(method))
+4 -4
View File
@@ -15,10 +15,10 @@ import (
// mockAuditRecorder captures RecordAPICall invocations for testing.
type mockAuditRecorder struct {
mu sync.Mutex
calls []auditCall
err error // if non-nil, RecordAPICall returns this
block chan struct{} // if non-nil, RecordAPICall blocks on receive before returning
mu sync.Mutex
calls []auditCall
err error // if non-nil, RecordAPICall returns this
block chan struct{} // if non-nil, RecordAPICall blocks on receive before returning
}
type auditCall struct {
+6 -6
View File
@@ -40,9 +40,9 @@ func TestNewCORS_NilOriginsDeniesAll(t *testing.T) {
// TestNewCORS_M013_ContractDocumentedInOrder pins the documented dispatch
// order so a refactor cannot silently invert the cases:
//
// 1. len(AllowedOrigins) == 0 → deny (no CORS headers)
// 2. AllowedOrigins == ["*"] → allow all (Access-Control-Allow-Origin: *)
// 3. else → exact-match allowlist with Vary: Origin
// 1. len(AllowedOrigins) == 0 → deny (no CORS headers)
// 2. AllowedOrigins == ["*"] → allow all (Access-Control-Allow-Origin: *)
// 3. else → exact-match allowlist with Vary: Origin
//
// If a refactor accidentally falls through to the allow-all branch when
// AllowedOrigins is empty, this test fails on case 1.
@@ -293,9 +293,9 @@ func TestNewCORS_MultipleOrigins(t *testing.T) {
}))
tests := []struct {
origin string
shouldAllow bool
description string
origin string
shouldAllow bool
description string
}{
{"https://app.example.com", true, "first origin in list"},
{"https://admin.example.com", true, "second origin in list"},
+5 -5
View File
@@ -290,11 +290,11 @@ func NewRateLimiter(cfg RateLimitConfig) func(http.Handler) http.Handler {
}
limiter := &keyedRateLimiter{
ipRate: cfg.RPS,
ipBurst: float64(cfg.BurstSize),
userRate: perUserRPS,
userBurst: perUserBurst,
buckets: make(map[string]*tokenBucket),
ipRate: cfg.RPS,
ipBurst: float64(cfg.BurstSize),
userRate: perUserRPS,
userBurst: perUserBurst,
buckets: make(map[string]*tokenBucket),
}
return func(next http.Handler) http.Handler {
+4 -3
View File
@@ -990,9 +990,9 @@ func TestGetLogLevel_AllLevels(t *testing.T) {
{"info", slog.LevelInfo},
{"warn", slog.LevelWarn},
{"error", slog.LevelError},
{"unknown", slog.LevelInfo}, // default fallback
{"", slog.LevelInfo}, // empty string
{"DEBUG", slog.LevelInfo}, // case-sensitive, no match → default
{"unknown", slog.LevelInfo}, // default fallback
{"", slog.LevelInfo}, // empty string
{"DEBUG", slog.LevelInfo}, // case-sensitive, no match → default
}
for _, tt := range tests {
t.Run(tt.level, func(t *testing.T) {
@@ -1091,6 +1091,7 @@ func TestGetEnvBool(t *testing.T) {
})
}
}
// I-003: Job timeout reaper configuration tests
func TestConfig_Scheduler_JobTimeoutDefaults(t *testing.T) {
clearCertctlEnv(t)
@@ -20,10 +20,10 @@ import (
// mockSMClient is a mock implementation of SMClient for testing.
type mockSMClient struct {
secrets map[string]string // secret name -> secret value
secretMetadata map[string]SecretMetadata // secret name -> metadata
listError error
getErrors map[string]error // secret name -> error
secrets map[string]string // secret name -> secret value
secretMetadata map[string]SecretMetadata // secret name -> metadata
listError error
getErrors map[string]error // secret name -> error
}
func newMockSMClient() *mockSMClient {
@@ -369,4 +369,3 @@ func TestSource_Discover_AgentIDAndSourcePath(t *testing.T) {
t.Errorf("expected source path 'aws-sm://eu-west-1/my-secret', got %s", report.Certificates[0].SourcePath)
}
}
+11 -11
View File
@@ -69,10 +69,10 @@ type certificateListResponse struct {
Value []struct {
ID string `json:"id"`
Attributes struct {
Enabled int64 `json:"enabled"`
Created int64 `json:"created"`
Updated int64 `json:"updated"`
Exp int64 `json:"exp"`
Enabled int64 `json:"enabled"`
Created int64 `json:"created"`
Updated int64 `json:"updated"`
Exp int64 `json:"exp"`
} `json:"attributes,omitempty"`
Tags map[string]string `json:"tags,omitempty"`
} `json:"value"`
@@ -84,10 +84,10 @@ type certificateBundle struct {
ID string `json:"id"`
CER string `json:"cer"`
Attributes struct {
Enabled int64 `json:"enabled"`
Created int64 `json:"created"`
Updated int64 `json:"updated"`
Exp int64 `json:"exp"`
Enabled int64 `json:"enabled"`
Created int64 `json:"created"`
Updated int64 `json:"updated"`
Exp int64 `json:"exp"`
} `json:"attributes,omitempty"`
}
@@ -170,10 +170,10 @@ func (s *Source) Discover(ctx context.Context) (*domain.DiscoveryReport, error)
s.logger.Info("starting Azure Key Vault discovery", "vault_url", s.config.VaultURL)
report := &domain.DiscoveryReport{
AgentID: "cloud-azure-kv",
Directories: []string{fmt.Sprintf("azure-kv://%s/", s.config.VaultURL)},
AgentID: "cloud-azure-kv",
Directories: []string{fmt.Sprintf("azure-kv://%s/", s.config.VaultURL)},
Certificates: []domain.DiscoveredCertEntry{},
Errors: []string{},
Errors: []string{},
}
startTime := time.Now()
@@ -82,7 +82,7 @@ func generateTestCertificate(cn string, expire time.Duration) (*x509.Certificate
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
},
DNSNames: []string{"example.com", "*.example.com"},
DNSNames: []string{"example.com", "*.example.com"},
EmailAddresses: []string{"test@example.com"},
}
+10 -10
View File
@@ -798,9 +798,9 @@ func TestValidateConfig_DNSPersistIssuerDomainRequired(t *testing.T) {
c := New(nil, testLogger())
cfg, _ := json.Marshal(map[string]string{
"directory_url": srv.URL,
"email": "test@example.com",
"challenge_type": "dns-persist-01",
"directory_url": srv.URL,
"email": "test@example.com",
"challenge_type": "dns-persist-01",
"dns_present_script": "/tmp/script.sh",
// Missing dns_persist_issuer_domain
})
@@ -870,9 +870,9 @@ func TestValidateConfig_DNS01WithPresentScript(t *testing.T) {
c := New(nil, testLogger())
cfg, _ := json.Marshal(map[string]string{
"directory_url": srv.URL,
"email": "test@example.com",
"challenge_type": "dns-01",
"directory_url": srv.URL,
"email": "test@example.com",
"challenge_type": "dns-01",
"dns_present_script": "/bin/sh",
"dns_cleanup_script": "/bin/sh",
})
@@ -897,10 +897,10 @@ func TestValidateConfig_DNSPersist01WithAllFields(t *testing.T) {
c := New(nil, testLogger())
cfg, _ := json.Marshal(map[string]string{
"directory_url": srv.URL,
"email": "test@example.com",
"challenge_type": "dns-persist-01",
"dns_present_script": "/bin/sh",
"directory_url": srv.URL,
"email": "test@example.com",
"challenge_type": "dns-persist-01",
"dns_present_script": "/bin/sh",
"dns_persist_issuer_domain": "letsencrypt.org",
})
+4 -4
View File
@@ -74,8 +74,8 @@ func TestGetRenewalInfo_NotFound(t *testing.T) {
if r.URL.Path == "/directory" && r.Method == http.MethodGet {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"newOrder": "/acme/new-order",
"newAccount": "/acme/new-account",
"newOrder": "/acme/new-order",
"newAccount": "/acme/new-account",
})
return
}
@@ -115,8 +115,8 @@ func TestGetRenewalInfo_ServerError(t *testing.T) {
if r.URL.Path == "/directory" && r.Method == http.MethodGet {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"newOrder": "/acme/new-order",
"newAccount": "/acme/new-account",
"newOrder": "/acme/new-order",
"newAccount": "/acme/new-account",
})
return
}
@@ -106,7 +106,7 @@ type pebbleMockServer struct {
idSeq int64
// Behavior toggles for failure-mode tests.
failNewAccount bool
rateLimitedOrder int32 // atomic counter; non-zero ⇒ first N orders return 429
rateLimitedOrder int32 // atomic counter; non-zero ⇒ first N orders return 429
finalizeReturns string // "" (default), "processing-stuck", "invalid"
authzPending bool // when true, new authzs start as "pending" and only flip to "valid" after the challenge endpoint is POSTed
challengeType string // when set, the per-authz challenge type emitted (default "http-01")
@@ -990,12 +990,12 @@ func TestPebbleMock_ContextCancel_DuringIssuance(t *testing.T) {
// ─────────────────────────────────────────────────────────────────────────────
type mockDNSSolver struct {
mu sync.Mutex
presented map[string]string // domain → keyAuth (or recordValue)
cleanedUp map[string]bool
presentErr error
cleanErr error
presentDelay time.Duration
mu sync.Mutex
presented map[string]string // domain → keyAuth (or recordValue)
cleanedUp map[string]bool
presentErr error
cleanErr error
presentDelay time.Duration
}
func newMockDNSSolver() *mockDNSSolver {
@@ -249,4 +249,3 @@ func signJWS(key *ecdsa.PrivateKey, kid, nonce, targetURL string, payload []byte
return json.Marshal(jws)
}
@@ -105,9 +105,9 @@ type GetCertificateOutput struct {
// RevokeCertificateInput represents the request to revoke a certificate.
type RevokeCertificateInput struct {
CAArn string
CertificateSerial string
RevocationReason string
CAArn string
CertificateSerial string
RevocationReason string
}
// GetCACertificateInput represents the request to retrieve the CA certificate.
@@ -395,14 +395,14 @@ func mapRevocationReason(reason *string) string {
}
reasonMap := map[string]string{
"unspecified": "UNSPECIFIED",
"keyCompromise": "KEY_COMPROMISE",
"caCompromise": "CERTIFICATE_AUTHORITY_COMPROMISE",
"affiliationChanged": "AFFILIATION_CHANGED",
"superseded": "SUPERSEDED",
"cessationOfOperation": "CESSATION_OF_OPERATION",
"certificateHold": "CERTIFICATE_HOLD",
"privilegeWithdrawn": "PRIVILEGE_WITHDRAWN",
"unspecified": "UNSPECIFIED",
"keyCompromise": "KEY_COMPROMISE",
"caCompromise": "CERTIFICATE_AUTHORITY_COMPROMISE",
"affiliationChanged": "AFFILIATION_CHANGED",
"superseded": "SUPERSEDED",
"cessationOfOperation": "CESSATION_OF_OPERATION",
"certificateHold": "CERTIFICATE_HOLD",
"privilegeWithdrawn": "PRIVILEGE_WITHDRAWN",
}
if mapped, ok := reasonMap[*reason]; ok {
@@ -22,15 +22,15 @@ import (
// mockACMPCAClient implements the ACMPCAClient interface for testing.
type mockACMPCAClient struct {
issueCertificateErr error
getCertificateErr error
revokeCertificateErr error
getCACertificateErr error
issuedCertPEM string
issuedChainPEM string
caCertPEM string
caCertChainPEM string
lastIssueCertificateInput *awsacmpca.IssueCertificateInput
issueCertificateErr error
getCertificateErr error
revokeCertificateErr error
getCACertificateErr error
issuedCertPEM string
issuedChainPEM string
caCertPEM string
caCertChainPEM string
lastIssueCertificateInput *awsacmpca.IssueCertificateInput
lastRevokeCertificateInput *awsacmpca.RevokeCertificateInput
}
@@ -90,9 +90,9 @@ func New(config *Config, logger *slog.Logger) *Connector {
// orderRequest is the JSON body for DigiCert certificate order submission.
type orderRequest struct {
Certificate orderCert `json:"certificate"`
Organization orderOrg `json:"organization"`
ValidityYears int `json:"validity_years"`
Certificate orderCert `json:"certificate"`
Organization orderOrg `json:"organization"`
ValidityYears int `json:"validity_years"`
}
type orderCert struct {
+1 -1
View File
@@ -162,7 +162,7 @@ func (c *Connector) IssueCertificate(ctx context.Context, request issuer.Issuanc
csrBase64 := base64.StdEncoding.EncodeToString(csrBlock.Bytes)
enrollReq := map[string]interface{}{
"certificate_request": csrBase64,
"certificate_request": csrBase64,
"certificate_authority_name": c.config.CAName,
}
@@ -175,7 +175,7 @@ func TestEJBCAConnector(t *testing.T) {
w.Header().Set("Content-Type", "application/json")
respData := map[string]interface{}{
"certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes),
"certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes),
"certificate_chain": []string{base64.StdEncoding.EncodeToString(chainBlock.Bytes)},
"serial_number": "123456",
}
@@ -242,7 +242,7 @@ func TestEJBCAConnector(t *testing.T) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
respData := map[string]interface{}{
"certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes),
"certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes),
"certificate_chain": []string{},
"serial_number": "789012",
}
@@ -314,7 +314,7 @@ func TestEJBCAConnector(t *testing.T) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
respData := map[string]interface{}{
"certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes),
"certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes),
"certificate_chain": []string{},
"serial_number": "123456",
}
@@ -356,7 +356,7 @@ func TestEJBCAConnector(t *testing.T) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
respData := map[string]interface{}{
"certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes),
"certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes),
"certificate_chain": []string{},
"serial_number": "654321",
}
+3 -3
View File
@@ -89,9 +89,9 @@ func NewWithHTTPClient(config *Config, logger *slog.Logger, client *http.Client)
// enrollmentRequest is the JSON body for Entrust enrollment submission.
type enrollmentRequest struct {
CSR string `json:"csr"`
ProfileId string `json:"profileId,omitempty"`
SubjectAltNames []san `json:"subjectAltNames,omitempty"`
CSR string `json:"csr"`
ProfileId string `json:"profileId,omitempty"`
SubjectAltNames []san `json:"subjectAltNames,omitempty"`
CertificateAuthority string `json:"certificateAuthority,omitempty"`
}
@@ -107,9 +107,9 @@ func NewWithHTTPClient(config *Config, logger *slog.Logger, client *http.Client)
// certificateRequest is the JSON body for GlobalSign certificate order submission.
type certificateRequest struct {
CSR string `json:"csr"`
CSR string `json:"csr"`
SubjectDN subjectDNRequest `json:"subject_dn"`
SAN sanRequest `json:"san,omitempty"`
SAN sanRequest `json:"san,omitempty"`
}
type subjectDNRequest struct {
@@ -93,10 +93,10 @@ type Connector struct {
httpClient *http.Client
// OAuth2 token caching
mu sync.Mutex
tokenCache *cachedToken
saKey *serviceAccountKey
rsaKey *rsa.PrivateKey
mu sync.Mutex
tokenCache *cachedToken
saKey *serviceAccountKey
rsaKey *rsa.PrivateKey
}
// New creates a new Google CAS connector with the given configuration and logger.
+3 -3
View File
@@ -479,9 +479,9 @@ func (c *Connector) parseCertificate(certPEM []byte) (*x509.Certificate, string,
// Format: [{"serial": "...", "revoked_at": "...", "reason_code": ...}, ...]
func (c *Connector) marshalRevokedSerials(revokedCerts []issuer.RevokedCertEntry) ([]byte, error) {
type RevokedEntry struct {
Serial string `json:"serial"`
RevokedAt string `json:"revoked_at"`
ReasonCode int `json:"reason_code"`
Serial string `json:"serial"`
RevokedAt string `json:"revoked_at"`
ReasonCode int `json:"reason_code"`
}
entries := make([]RevokedEntry, len(revokedCerts))
@@ -643,7 +643,7 @@ func generateTestCSR(cn string) (*x509.CertificateRequest, string, error) {
}
csrTemplate := x509.CertificateRequest{
Subject: subject,
Subject: subject,
DNSNames: []string{cn, "www." + cn},
}
+11 -11
View File
@@ -168,9 +168,9 @@ type signRequest struct {
// signResponse is the JSON response from the step-ca /sign endpoint.
type signResponse struct {
ServerPEM certificateChain `json:"serverPEM,omitempty"`
CaPEM certificateChain `json:"caPEM,omitempty"`
CertChainPEM []certBlock `json:"certChainPEM,omitempty"`
ServerPEM certificateChain `json:"serverPEM,omitempty"`
CaPEM certificateChain `json:"caPEM,omitempty"`
CertChainPEM []certBlock `json:"certChainPEM,omitempty"`
}
type certificateChain struct {
@@ -380,14 +380,14 @@ func (c *Connector) generateProvisionerToken(subject string, sans []string) (str
// step-ca expects: aud = <ca-url>/1.0/sign (the sign endpoint audience)
claims := map[string]interface{}{
"sub": subject,
"iss": c.config.ProvisionerName,
"aud": c.config.CAURL + "/1.0/sign",
"nbf": now.Unix(),
"iat": now.Unix(),
"exp": now.Add(5 * time.Minute).Unix(),
"jti": generateJTI(),
"sha": kid, // step-ca uses this to look up the provisioner by key fingerprint
"sub": subject,
"iss": c.config.ProvisionerName,
"aud": c.config.CAURL + "/1.0/sign",
"nbf": now.Unix(),
"iat": now.Unix(),
"exp": now.Add(5 * time.Minute).Unix(),
"jti": generateJTI(),
"sha": kid, // step-ca uses this to look up the provisioner by key fingerprint
}
if len(sans) > 0 {
@@ -1574,9 +1574,9 @@ func TestLoadProvisionerKey_FileNotReadable(t *testing.T) {
// Test with a provisioner key path that can't be read
config := stepca.Config{
CAURL: srv.URL,
ProvisionerName: "test-provisioner",
ProvisionerKeyPath: "/root/.ssh/no_such_key", // Permission denied or doesn't exist
CAURL: srv.URL,
ProvisionerName: "test-provisioner",
ProvisionerKeyPath: "/root/.ssh/no_such_key", // Permission denied or doesn't exist
ProvisionerPassword: "password",
}
@@ -1770,4 +1770,3 @@ func TestIntegration_FullLifecycle(t *testing.T) {
t.Errorf("Expected status 'completed', got '%s'", status.Status)
}
}
+3 -3
View File
@@ -86,9 +86,9 @@ func New(config *Config, logger *slog.Logger) *Connector {
// vaultResponse is the standard Vault API response wrapper.
type vaultResponse struct {
Data json.RawMessage `json:"data"`
Errors []string `json:"errors,omitempty"`
Warnings []string `json:"warnings,omitempty"`
Data json.RawMessage `json:"data"`
Errors []string `json:"errors,omitempty"`
Warnings []string `json:"warnings,omitempty"`
}
// signData holds the data returned from the /sign endpoint.
@@ -88,11 +88,11 @@ func TestEmail_ValidateConfig_MissingPort(t *testing.T) {
func TestEmail_ValidateConfig_MissingFromAddress(t *testing.T) {
cfg := &Config{
SMTPHost: "smtp.example.com",
SMTPPort: 587,
Username: "user",
Password: "pass",
UseTLS: true,
SMTPHost: "smtp.example.com",
SMTPPort: 587,
Username: "user",
Password: "pass",
UseTLS: true,
}
rawConfig, _ := json.Marshal(cfg)
+8 -8
View File
@@ -19,11 +19,11 @@ import (
// to a directory that Envoy watches via its SDS (Secret Discovery Service)
// file-based configuration or static filename references in the bootstrap config.
type Config struct {
CertDir string `json:"cert_dir"` // Directory where Envoy watches for cert files (required)
CertFilename string `json:"cert_filename"` // Filename for certificate (default: cert.pem)
KeyFilename string `json:"key_filename"` // Filename for private key (default: key.pem)
ChainFilename string `json:"chain_filename"` // Optional filename for chain (if set, chain written separately)
SDSConfig bool `json:"sds_config"` // If true, write an SDS discovery JSON file for file-based SDS
CertDir string `json:"cert_dir"` // Directory where Envoy watches for cert files (required)
CertFilename string `json:"cert_filename"` // Filename for certificate (default: cert.pem)
KeyFilename string `json:"key_filename"` // Filename for private key (default: key.pem)
ChainFilename string `json:"chain_filename"` // Optional filename for chain (if set, chain written separately)
SDSConfig bool `json:"sds_config"` // If true, write an SDS discovery JSON file for file-based SDS
}
// SDSResource represents an Envoy SDS tls_certificate resource for file-based SDS.
@@ -34,9 +34,9 @@ type SDSResource struct {
// SDSTLSCertificate represents a single SDS tls_certificate entry.
type SDSTLSCertificate struct {
Type string `json:"@type"`
Name string `json:"name"`
TLSCertificate TLSCertificate `json:"tls_certificate"`
Type string `json:"@type"`
Name string `json:"name"`
TLSCertificate TLSCertificate `json:"tls_certificate"`
}
// TLSCertificate contains the file paths for cert and key in Envoy's SDS format.
+11 -11
View File
@@ -457,13 +457,13 @@ func (c *Connector) DeployCertificate(ctx context.Context, request target.Deploy
Message: "Certificate uploaded and SSL profile updated via iControl REST",
DeployedAt: time.Now(),
Metadata: map[string]string{
"host": c.config.Host,
"partition": c.config.Partition,
"ssl_profile": c.config.SSLProfile,
"cert_object_name": certName,
"key_object_name": keyName,
"host": c.config.Host,
"partition": c.config.Partition,
"ssl_profile": c.config.SSLProfile,
"cert_object_name": certName,
"key_object_name": keyName,
"chain_object_name": chainName,
"duration_ms": fmt.Sprintf("%d", deploymentDuration.Milliseconds()),
"duration_ms": fmt.Sprintf("%d", deploymentDuration.Milliseconds()),
},
}, nil
}
@@ -561,12 +561,12 @@ func (c *Connector) ValidateDeployment(ctx context.Context, request target.Valid
Message: fmt.Sprintf("SSL profile %q has cert %q configured", c.config.SSLProfile, profile.Cert),
ValidatedAt: time.Now(),
Metadata: map[string]string{
"host": c.config.Host,
"ssl_profile": c.config.SSLProfile,
"current_cert": profile.Cert,
"current_key": profile.Key,
"host": c.config.Host,
"ssl_profile": c.config.SSLProfile,
"current_cert": profile.Cert,
"current_key": profile.Key,
"current_chain": profile.Chain,
"duration_ms": fmt.Sprintf("%d", validationDuration.Milliseconds()),
"duration_ms": fmt.Sprintf("%d", validationDuration.Milliseconds()),
},
}, nil
}
+8 -8
View File
@@ -25,14 +25,14 @@ type mockF5Client struct {
calls []mockCall
// Configurable responses per method
authenticateErr error
authenticateCount int // tracks number of Authenticate calls
uploadFileErr error
uploadFileErrOn string // only error when filename contains this substring
installCertErr error
installCertErrOn string
installKeyErr error
createTransactionID string
authenticateErr error
authenticateCount int // tracks number of Authenticate calls
uploadFileErr error
uploadFileErrOn string // only error when filename contains this substring
installCertErr error
installCertErrOn string
installKeyErr error
createTransactionID string
createTransactionErr error
commitTransactionErr error
updateSSLProfileErr error
+3 -3
View File
@@ -59,9 +59,9 @@ func newWinRMExecutor(cfg *WinRMConfig) (*winrmExecutor, error) {
port,
cfg.UseHTTPS,
cfg.Insecure,
nil, // CA cert
nil, // Client cert
nil, // Client key
nil, // CA cert
nil, // Client cert
nil, // Client key
timeout,
)
@@ -263,10 +263,10 @@ func (c *Connector) DeployCertificate(ctx context.Context, request target.Deploy
Message: fmt.Sprintf("Certificate imported to %s (alias: %s, thumbprint: %s)", c.config.KeystorePath, c.config.Alias, thumbprint),
DeployedAt: time.Now(),
Metadata: map[string]string{
"thumbprint": thumbprint,
"alias": c.config.Alias,
"keystore_type": c.config.KeystoreType,
"keystore_path": c.config.KeystorePath,
"thumbprint": thumbprint,
"alias": c.config.Alias,
"keystore_type": c.config.KeystoreType,
"keystore_path": c.config.KeystorePath,
},
}, nil
}
@@ -240,7 +240,7 @@ func TestDeployCertificate_Success(t *testing.T) {
mock := &mockExecutor{
responses: []mockResponse{
{Output: "", Err: nil}, // keytool -delete (alias may not exist)
{Output: "", Err: nil}, // keytool -delete (alias may not exist)
{Output: "Import command completed", Err: nil}, // keytool -importkeystore
},
}
@@ -355,8 +355,8 @@ func TestDeployCertificate_WithReload(t *testing.T) {
mock := &mockExecutor{
responses: []mockResponse{
// No existing keystore → delete skipped → import is call 0, reload is call 1
{Output: "Imported", Err: nil}, // import
{Output: "restarted", Err: nil}, // reload
{Output: "Imported", Err: nil}, // import
{Output: "restarted", Err: nil}, // reload
},
}
c := NewWithExecutor(&Config{
@@ -391,8 +391,8 @@ func TestDeployCertificate_ReloadFailed_NonFatal(t *testing.T) {
mock := &mockExecutor{
responses: []mockResponse{
{Output: "", Err: nil}, // delete
{Output: "Imported", Err: nil}, // import
{Output: "", Err: nil}, // delete
{Output: "Imported", Err: nil}, // import
{Output: "Failed to restart", Err: fmt.Errorf("exit 1")}, // reload fails
},
}
@@ -21,9 +21,9 @@ import (
// Supports in-cluster auth by default (ServiceAccount token auto-mounted) or
// out-of-cluster auth via kubeconfig file.
type Config struct {
Namespace string `json:"namespace"` // Required. Kubernetes namespace.
SecretName string `json:"secret_name"` // Required. Name of the kubernetes.io/tls Secret.
Labels map[string]string `json:"labels,omitempty"` // Optional. Additional labels to add to the Secret.
Namespace string `json:"namespace"` // Required. Kubernetes namespace.
SecretName string `json:"secret_name"` // Required. Name of the kubernetes.io/tls Secret.
Labels map[string]string `json:"labels,omitempty"` // Optional. Additional labels to add to the Secret.
KubeconfigPath string `json:"kubeconfig_path,omitempty"` // Optional. Path to kubeconfig for out-of-cluster auth.
}
@@ -93,7 +93,7 @@ func (m *mockK8sClient) DeleteSecret(ctx context.Context, namespace, name string
func TestValidateConfig_Success_MinimalConfig(t *testing.T) {
cfg := map[string]interface{}{
"namespace": "default",
"namespace": "default",
"secret_name": "my-cert",
}
@@ -644,4 +644,3 @@ func contains(s, substr string) bool {
}
return false
}
+3 -3
View File
@@ -411,9 +411,9 @@ func (c *realSSHClient) Connect(ctx context.Context) error {
}
sshConfig := &ssh.ClientConfig{
User: c.config.User,
Auth: authMethods,
Timeout: time.Duration(c.config.Timeout) * time.Second,
User: c.config.User,
Auth: authMethods,
Timeout: time.Duration(c.config.Timeout) * time.Second,
// InsecureIgnoreHostKey is used intentionally: certctl deploys to known
// infrastructure (the operator explicitly configures each target host).
// This is the same security rationale as network scanner's InsecureSkipVerify
@@ -42,15 +42,15 @@ type fakeSSHServer struct {
user string
password string
wg sync.WaitGroup
mu sync.Mutex
closed bool
wg sync.WaitGroup
mu sync.Mutex
closed bool
// Optional behaviour toggles for failure-mode tests.
rejectAuth bool // reject all auth attempts (auth failure path)
dropOnHandshake bool // close conn before SSH NewServerConn returns (handshake failure)
failExec bool // exec sessions return non-zero exit (Execute error path)
failSFTP bool // refuse sftp subsystem (SFTP failure path)
rejectAuth bool // reject all auth attempts (auth failure path)
dropOnHandshake bool // close conn before SSH NewServerConn returns (handshake failure)
failExec bool // exec sessions return non-zero exit (Execute error path)
failSFTP bool // refuse sftp subsystem (SFTP failure path)
}
// startFakeSSHServer binds a fresh server on a random local port and returns
@@ -310,4 +310,3 @@ func (c *Connector) ValidateDeployment(ctx context.Context, request target.Valid
// Ensure Connector implements target.Connector.
var _ target.Connector = (*Connector)(nil)
@@ -26,10 +26,10 @@ func testLogger() *slog.Logger {
// mockExecutor records PowerShell scripts and returns configurable responses.
type mockExecutor struct {
scripts []string
responses []string
errors []error
callIndex int
scripts []string
responses []string
errors []error
callIndex int
}
func (m *mockExecutor) Execute(ctx context.Context, script string) (string, error) {
+8 -8
View File
@@ -4,14 +4,14 @@ import "testing"
func TestCertificateStatus_Constants(t *testing.T) {
tests := map[string]CertificateStatus{
"Pending": CertificateStatusPending,
"Active": CertificateStatusActive,
"Expiring": CertificateStatusExpiring,
"Expired": CertificateStatusExpired,
"RenewalInProgress": CertificateStatusRenewalInProgress,
"Failed": CertificateStatusFailed,
"Revoked": CertificateStatusRevoked,
"Archived": CertificateStatusArchived,
"Pending": CertificateStatusPending,
"Active": CertificateStatusActive,
"Expiring": CertificateStatusExpiring,
"Expired": CertificateStatusExpired,
"RenewalInProgress": CertificateStatusRenewalInProgress,
"Failed": CertificateStatusFailed,
"Revoked": CertificateStatusRevoked,
"Archived": CertificateStatusArchived,
}
for expected, got := range tests {
if string(got) != expected {
+29 -29
View File
@@ -11,7 +11,7 @@ type Issuer struct {
Name string `json:"name"`
Type IssuerType `json:"type"`
Config json.RawMessage `json:"config"`
EncryptedConfig []byte `json:"-"` // AES-GCM encrypted full config (never exposed via API)
EncryptedConfig []byte `json:"-"` // AES-GCM encrypted full config (never exposed via API)
Enabled bool `json:"enabled"`
LastTestedAt *time.Time `json:"last_tested_at,omitempty"`
TestStatus string `json:"test_status,omitempty"`
@@ -27,13 +27,13 @@ type DeploymentTarget struct {
Type TargetType `json:"type"`
AgentID string `json:"agent_id"`
Config json.RawMessage `json:"config"`
EncryptedConfig []byte `json:"-"` // AES-GCM encrypted full config (never exposed via API)
EncryptedConfig []byte `json:"-"` // AES-GCM encrypted full config (never exposed via API)
Enabled bool `json:"enabled"`
LastTestedAt *time.Time `json:"last_tested_at,omitempty"`
TestStatus string `json:"test_status,omitempty"`
Source string `json:"source,omitempty"`
RetiredAt *time.Time `json:"retired_at,omitempty"` // I-004: soft-retirement timestamp (nil = active)
RetiredReason *string `json:"retired_reason,omitempty"` // I-004: reason captured at cascade retirement
RetiredAt *time.Time `json:"retired_at,omitempty"` // I-004: soft-retirement timestamp (nil = active)
RetiredReason *string `json:"retired_reason,omitempty"` // I-004: reason captured at cascade retirement
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
@@ -65,11 +65,11 @@ type Agent struct {
// docs/architecture.md ER diagram (which documents DB shape, not API
// shape) and coverage-gap-audit-2026-04-24-v5/unified-audit.md
// cat-s5-apikey_leak for the full closure rationale.
APIKeyHash string `json:"-"`
OS string `json:"os"`
Architecture string `json:"architecture"`
IPAddress string `json:"ip_address"`
Version string `json:"version"`
APIKeyHash string `json:"-"`
OS string `json:"os"`
Architecture string `json:"architecture"`
IPAddress string `json:"ip_address"`
Version string `json:"version"`
// I-004: soft-retirement fields. An agent with RetiredAt != nil is the
// canonical "retired" state. The Status column remains as before (Online
// / Offline / Degraded) and is preserved at retirement time as the
@@ -115,9 +115,9 @@ func (a *Agent) IsRetired() bool { return a != nil && a.RetiredAt != nil }
// any non-zero count blocks a default retire with HTTP 409 and requires an
// explicit ?force=true&reason=... escape hatch from the operator.
type AgentDependencyCounts struct {
ActiveTargets int `json:"active_targets"` // deployment_targets.agent_id=id AND retired_at IS NULL
ActiveTargets int `json:"active_targets"` // deployment_targets.agent_id=id AND retired_at IS NULL
ActiveCertificates int `json:"active_certificates"` // certificates currently deployed via one of this agent's active targets
PendingJobs int `json:"pending_jobs"` // jobs.agent_id=id AND status IN (Pending, AwaitingCSR, AwaitingApproval, Running)
PendingJobs int `json:"pending_jobs"` // jobs.agent_id=id AND status IN (Pending, AwaitingCSR, AwaitingApproval, Running)
}
// HasDependencies reports whether any preflight counter is non-zero.
@@ -180,14 +180,14 @@ const (
type IssuerType string
const (
IssuerTypeACME IssuerType = "ACME"
IssuerTypeGenericCA IssuerType = "GenericCA"
IssuerTypeStepCA IssuerType = "StepCA"
IssuerTypeOpenSSL IssuerType = "OpenSSL"
IssuerTypeVault IssuerType = "VaultPKI"
IssuerTypeDigiCert IssuerType = "DigiCert"
IssuerTypeSectigo IssuerType = "Sectigo"
IssuerTypeGoogleCAS IssuerType = "GoogleCAS"
IssuerTypeACME IssuerType = "ACME"
IssuerTypeGenericCA IssuerType = "GenericCA"
IssuerTypeStepCA IssuerType = "StepCA"
IssuerTypeOpenSSL IssuerType = "OpenSSL"
IssuerTypeVault IssuerType = "VaultPKI"
IssuerTypeDigiCert IssuerType = "DigiCert"
IssuerTypeSectigo IssuerType = "Sectigo"
IssuerTypeGoogleCAS IssuerType = "GoogleCAS"
IssuerTypeAWSACMPCA IssuerType = "AWSACMPCA"
IssuerTypeEntrust IssuerType = "Entrust"
IssuerTypeGlobalSign IssuerType = "GlobalSign"
@@ -198,16 +198,16 @@ const (
type TargetType string
const (
TargetTypeNGINX TargetType = "NGINX"
TargetTypeApache TargetType = "Apache"
TargetTypeHAProxy TargetType = "HAProxy"
TargetTypeF5 TargetType = "F5"
TargetTypeIIS TargetType = "IIS"
TargetTypeTraefik TargetType = "Traefik"
TargetTypeCaddy TargetType = "Caddy"
TargetTypeEnvoy TargetType = "Envoy"
TargetTypePostfix TargetType = "Postfix"
TargetTypeDovecot TargetType = "Dovecot"
TargetTypeNGINX TargetType = "NGINX"
TargetTypeApache TargetType = "Apache"
TargetTypeHAProxy TargetType = "HAProxy"
TargetTypeF5 TargetType = "F5"
TargetTypeIIS TargetType = "IIS"
TargetTypeTraefik TargetType = "Traefik"
TargetTypeCaddy TargetType = "Caddy"
TargetTypeEnvoy TargetType = "Envoy"
TargetTypePostfix TargetType = "Postfix"
TargetTypeDovecot TargetType = "Dovecot"
TargetTypeSSH TargetType = "SSH"
TargetTypeWinCertStore TargetType = "WinCertStore"
TargetTypeJavaKeystore TargetType = "JavaKeystore"
+35 -35
View File
@@ -24,34 +24,34 @@ func IsValidHealthStatus(s string) bool {
// EndpointHealthCheck represents a monitored TLS endpoint.
type EndpointHealthCheck struct {
ID string `json:"id"`
Endpoint string `json:"endpoint"`
CertificateID *string `json:"certificate_id,omitempty"`
NetworkScanTargetID *string `json:"network_scan_target_id,omitempty"`
ExpectedFingerprint string `json:"expected_fingerprint"`
ObservedFingerprint string `json:"observed_fingerprint"`
Status HealthStatus `json:"status"`
ConsecutiveFailures int `json:"consecutive_failures"`
ResponseTimeMs int `json:"response_time_ms"`
TLSVersion string `json:"tls_version"`
CipherSuite string `json:"cipher_suite"`
CertSubject string `json:"cert_subject"`
CertIssuer string `json:"cert_issuer"`
CertExpiry *time.Time `json:"cert_expiry,omitempty"`
LastCheckedAt *time.Time `json:"last_checked_at,omitempty"`
LastSuccessAt *time.Time `json:"last_success_at,omitempty"`
LastFailureAt *time.Time `json:"last_failure_at,omitempty"`
LastTransitionAt *time.Time `json:"last_transition_at,omitempty"`
FailureReason string `json:"failure_reason"`
DegradedThreshold int `json:"degraded_threshold"`
DownThreshold int `json:"down_threshold"`
CheckIntervalSecs int `json:"check_interval_seconds"`
Enabled bool `json:"enabled"`
Acknowledged bool `json:"acknowledged"`
AcknowledgedBy string `json:"acknowledged_by,omitempty"`
AcknowledgedAt *time.Time `json:"acknowledged_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ID string `json:"id"`
Endpoint string `json:"endpoint"`
CertificateID *string `json:"certificate_id,omitempty"`
NetworkScanTargetID *string `json:"network_scan_target_id,omitempty"`
ExpectedFingerprint string `json:"expected_fingerprint"`
ObservedFingerprint string `json:"observed_fingerprint"`
Status HealthStatus `json:"status"`
ConsecutiveFailures int `json:"consecutive_failures"`
ResponseTimeMs int `json:"response_time_ms"`
TLSVersion string `json:"tls_version"`
CipherSuite string `json:"cipher_suite"`
CertSubject string `json:"cert_subject"`
CertIssuer string `json:"cert_issuer"`
CertExpiry *time.Time `json:"cert_expiry,omitempty"`
LastCheckedAt *time.Time `json:"last_checked_at,omitempty"`
LastSuccessAt *time.Time `json:"last_success_at,omitempty"`
LastFailureAt *time.Time `json:"last_failure_at,omitempty"`
LastTransitionAt *time.Time `json:"last_transition_at,omitempty"`
FailureReason string `json:"failure_reason"`
DegradedThreshold int `json:"degraded_threshold"`
DownThreshold int `json:"down_threshold"`
CheckIntervalSecs int `json:"check_interval_seconds"`
Enabled bool `json:"enabled"`
Acknowledged bool `json:"acknowledged"`
AcknowledgedBy string `json:"acknowledged_by,omitempty"`
AcknowledgedAt *time.Time `json:"acknowledged_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// TransitionStatus computes the new health status based on the probe result.
@@ -89,13 +89,13 @@ func (h *EndpointHealthCheck) TransitionStatus(probeSuccess bool, observedFinger
// HealthHistoryEntry represents a single probe record.
type HealthHistoryEntry struct {
ID string `json:"id"`
HealthCheckID string `json:"health_check_id"`
Status string `json:"status"`
ResponseTimeMs int `json:"response_time_ms"`
Fingerprint string `json:"fingerprint"`
FailureReason string `json:"failure_reason"`
CheckedAt time.Time `json:"checked_at"`
ID string `json:"id"`
HealthCheckID string `json:"health_check_id"`
Status string `json:"status"`
ResponseTimeMs int `json:"response_time_ms"`
Fingerprint string `json:"fingerprint"`
FailureReason string `json:"failure_reason"`
CheckedAt time.Time `json:"checked_at"`
}
// HealthCheckSummary contains aggregate counts by status.
+17 -17
View File
@@ -7,23 +7,23 @@ import (
// Job represents a unit of work in the certificate control plane.
type Job struct {
ID string `json:"id"`
Type JobType `json:"type"`
CertificateID string `json:"certificate_id"`
TargetID *string `json:"target_id,omitempty"`
AgentID *string `json:"agent_id,omitempty"`
Status JobStatus `json:"status"`
Attempts int `json:"attempts"`
MaxAttempts int `json:"max_attempts"`
LastError *string `json:"last_error,omitempty"`
ScheduledAt time.Time `json:"scheduled_at"`
StartedAt *time.Time `json:"started_at,omitempty"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
VerificationStatus VerificationStatus `json:"verification_status"`
VerifiedAt *time.Time `json:"verified_at,omitempty"`
VerificationError *string `json:"verification_error,omitempty"`
VerificationFp *string `json:"verification_fingerprint,omitempty"`
ID string `json:"id"`
Type JobType `json:"type"`
CertificateID string `json:"certificate_id"`
TargetID *string `json:"target_id,omitempty"`
AgentID *string `json:"agent_id,omitempty"`
Status JobStatus `json:"status"`
Attempts int `json:"attempts"`
MaxAttempts int `json:"max_attempts"`
LastError *string `json:"last_error,omitempty"`
ScheduledAt time.Time `json:"scheduled_at"`
StartedAt *time.Time `json:"started_at,omitempty"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
VerificationStatus VerificationStatus `json:"verification_status"`
VerifiedAt *time.Time `json:"verified_at,omitempty"`
VerificationError *string `json:"verification_error,omitempty"`
VerificationFp *string `json:"verification_fingerprint,omitempty"`
}
// JobType represents the classification of work to be performed.
+9 -9
View File
@@ -104,15 +104,15 @@ func TestNotificationEvent_RetryFields(t *testing.T) {
next := time.Now().Add(2 * time.Minute)
lastErr := "connection refused"
event := &NotificationEvent{
ID: "notif-retry-001",
Type: NotificationTypeExpirationWarning,
Channel: NotificationChannelWebhook,
Recipient: "https://hooks.example.com/certs",
Message: "retry me",
Status: string(NotificationStatusFailed),
RetryCount: 3,
NextRetryAt: &next,
LastError: &lastErr,
ID: "notif-retry-001",
Type: NotificationTypeExpirationWarning,
Channel: NotificationChannelWebhook,
Recipient: "https://hooks.example.com/certs",
Message: "retry me",
Status: string(NotificationStatusFailed),
RetryCount: 3,
NextRetryAt: &next,
LastError: &lastErr,
}
if event.RetryCount != 3 {
+4 -4
View File
@@ -12,10 +12,10 @@ import "time"
type OCSPResponseCacheEntry struct {
IssuerID string `json:"issuer_id"`
SerialHex string `json:"serial_hex"`
ResponseDER []byte `json:"-"` // raw DER, omitted from admin JSON to keep responses lean
CertStatus string `json:"cert_status"` // "good" | "revoked" | "unknown"
RevocationReason int `json:"revocation_reason,omitempty"` // only set when CertStatus == "revoked"
RevokedAt time.Time `json:"revoked_at,omitempty"` // only set when CertStatus == "revoked"
ResponseDER []byte `json:"-"` // raw DER, omitted from admin JSON to keep responses lean
CertStatus string `json:"cert_status"` // "good" | "revoked" | "unknown"
RevocationReason int `json:"revocation_reason,omitempty"` // only set when CertStatus == "revoked"
RevokedAt time.Time `json:"revoked_at,omitempty"` // only set when CertStatus == "revoked"
ThisUpdate time.Time `json:"this_update"`
NextUpdate time.Time `json:"next_update"`
GeneratedAt time.Time `json:"generated_at"`
+6 -6
View File
@@ -21,12 +21,12 @@ type PolicyRule struct {
type PolicyType string
const (
PolicyTypeAllowedIssuers PolicyType = "AllowedIssuers"
PolicyTypeAllowedDomains PolicyType = "AllowedDomains"
PolicyTypeRequiredMetadata PolicyType = "RequiredMetadata"
PolicyTypeAllowedEnvironments PolicyType = "AllowedEnvironments"
PolicyTypeRenewalLeadTime PolicyType = "RenewalLeadTime"
PolicyTypeCertificateLifetime PolicyType = "CertificateLifetime"
PolicyTypeAllowedIssuers PolicyType = "AllowedIssuers"
PolicyTypeAllowedDomains PolicyType = "AllowedDomains"
PolicyTypeRequiredMetadata PolicyType = "RequiredMetadata"
PolicyTypeAllowedEnvironments PolicyType = "AllowedEnvironments"
PolicyTypeRenewalLeadTime PolicyType = "RenewalLeadTime"
PolicyTypeCertificateLifetime PolicyType = "CertificateLifetime"
)
// PolicyViolation records an instance of a certificate violating a policy rule.
+10 -10
View File
@@ -704,17 +704,17 @@ func TestM20EnhancedQueryAPI(t *testing.T) {
// Setup: Create a certificate for testing
now := time.Now()
cert := &domain.ManagedCertificate{
ID: "mc-m20-test-1",
Name: "M20 Test Cert",
CommonName: "m20.example.com",
Environment: "production",
Status: domain.CertificateStatusActive,
IssuerID: "iss-local",
OwnerID: "owner-ops",
TeamID: "team-platform",
ID: "mc-m20-test-1",
Name: "M20 Test Cert",
CommonName: "m20.example.com",
Environment: "production",
Status: domain.CertificateStatusActive,
IssuerID: "iss-local",
OwnerID: "owner-ops",
TeamID: "team-platform",
CertificateProfileID: "prof-standard",
CreatedAt: now,
UpdatedAt: now,
CreatedAt: now,
UpdatedAt: now,
}
certRepo.certs["mc-m20-test-1"] = cert
+1 -1
View File
@@ -1,10 +1,10 @@
package postgres
import (
"github.com/shankar0123/certctl/internal/repository"
"context"
"database/sql"
"fmt"
"github.com/shankar0123/certctl/internal/repository"
"time"
"github.com/google/uuid"
+1 -1
View File
@@ -1,10 +1,10 @@
package postgres
import (
"github.com/shankar0123/certctl/internal/repository"
"context"
"database/sql"
"fmt"
"github.com/shankar0123/certctl/internal/repository"
"time"
"github.com/shankar0123/certctl/internal/domain"
+5 -5
View File
@@ -52,11 +52,11 @@ func TestWrapPingError_AuthFailureGuidance(t *testing.T) {
// Contract elements — the operator-facing string is what we ship.
wantSubstrings := []string{
"SQLSTATE 28P01", // operators grep on this
"POSTGRES_PASSWORD", // names the variable that traps
"first boot", // the mechanism in plain language
"down -v", // destructive remediation
"ALTER ROLE", // non-destructive remediation
"SQLSTATE 28P01", // operators grep on this
"POSTGRES_PASSWORD", // names the variable that traps
"first boot", // the mechanism in plain language
"down -v", // destructive remediation
"ALTER ROLE", // non-destructive remediation
}
for _, s := range wantSubstrings {
if !strings.Contains(got, s) {
@@ -235,8 +235,8 @@ func TestHealthCheckRepository_ListDueForCheck(t *testing.T) {
ctx := context.Background()
now := time.Now().UTC().Truncate(time.Microsecond)
pastDue := now.Add(-10 * time.Minute) // > 300s ago, enabled → due
recent := now.Add(-30 * time.Second) // < 300s ago, enabled → not due
pastDue := now.Add(-10 * time.Minute) // > 300s ago, enabled → due
recent := now.Add(-30 * time.Second) // < 300s ago, enabled → not due
// (a) enabled + null last_checked_at — NULLS FIRST puts this first
a := newHealthCheck("hc-due-a", "a.example.com:443", domain.HealthStatusUnknown, true)
+1 -1
View File
@@ -1,10 +1,10 @@
package postgres
import (
"github.com/shankar0123/certctl/internal/repository"
"context"
"database/sql"
"fmt"
"github.com/shankar0123/certctl/internal/repository"
"github.com/google/uuid"
"github.com/shankar0123/certctl/internal/domain"
+1 -1
View File
@@ -1,10 +1,10 @@
package postgres
import (
"github.com/shankar0123/certctl/internal/repository"
"context"
"database/sql"
"fmt"
"github.com/shankar0123/certctl/internal/repository"
"time"
"github.com/google/uuid"
+1 -1
View File
@@ -1,10 +1,10 @@
package postgres
import (
"github.com/shankar0123/certctl/internal/repository"
"context"
"database/sql"
"fmt"
"github.com/shankar0123/certctl/internal/repository"
"time"
"github.com/lib/pq"
+1 -1
View File
@@ -1,10 +1,10 @@
package postgres
import (
"github.com/shankar0123/certctl/internal/repository"
"context"
"database/sql"
"fmt"
"github.com/shankar0123/certctl/internal/repository"
"github.com/google/uuid"
"github.com/shankar0123/certctl/internal/domain"
+14 -14
View File
@@ -319,7 +319,7 @@ func TestCertificateRepository_GetExpiringCertificates(t *testing.T) {
ID: tc.id, Name: tc.id, CommonName: tc.id + ".example.com",
SANs: []string{}, OwnerID: ownerID, TeamID: teamID,
IssuerID: issuerID, RenewalPolicyID: policyID,
Status: domain.CertificateStatusActive,
Status: domain.CertificateStatusActive,
ExpiresAt: tc.expires, Tags: map[string]string{},
CreatedAt: now, UpdatedAt: now,
}
@@ -780,7 +780,7 @@ func TestJobRepository_CRUD(t *testing.T) {
ID: "mc-job-test", Name: "job-test", CommonName: "job.example.com",
SANs: []string{}, OwnerID: ownerID, TeamID: teamID,
IssuerID: issuerID, RenewalPolicyID: policyID,
Status: domain.CertificateStatusActive,
Status: domain.CertificateStatusActive,
ExpiresAt: now.Add(30 * 24 * time.Hour), Tags: map[string]string{},
CreatedAt: now, UpdatedAt: now,
}
@@ -871,7 +871,7 @@ func TestRevocationRepository_CRUD(t *testing.T) {
ID: "mc-rev-test", Name: "rev-test", CommonName: "rev.example.com",
SANs: []string{}, OwnerID: ownerID, TeamID: teamID,
IssuerID: issuerID, RenewalPolicyID: policyID,
Status: domain.CertificateStatusRevoked,
Status: domain.CertificateStatusRevoked,
ExpiresAt: now.Add(30 * 24 * time.Hour), Tags: map[string]string{},
CreatedAt: now, UpdatedAt: now,
}
@@ -1250,12 +1250,12 @@ func TestProfileRepository_CRUD(t *testing.T) {
{Algorithm: "RSA", MinSize: 2048},
{Algorithm: "ECDSA", MinSize: 256},
},
MaxTTLSeconds: 86400,
AllowedEKUs: []string{"serverAuth"},
AllowShortLived: false,
Enabled: true,
CreatedAt: now,
UpdatedAt: now,
MaxTTLSeconds: 86400,
AllowedEKUs: []string{"serverAuth"},
AllowShortLived: false,
Enabled: true,
CreatedAt: now,
UpdatedAt: now,
}
if err := repo.Create(ctx, profile); err != nil {
@@ -1306,7 +1306,7 @@ func TestNotificationRepository_CRUD(t *testing.T) {
ID: "mc-notif-test", Name: "notif-test", CommonName: "notif.example.com",
SANs: []string{}, OwnerID: ownerID, TeamID: teamID,
IssuerID: issuerID, RenewalPolicyID: policyID,
Status: domain.CertificateStatusActive,
Status: domain.CertificateStatusActive,
ExpiresAt: now.Add(30 * 24 * time.Hour), Tags: map[string]string{},
CreatedAt: now, UpdatedAt: now,
}
@@ -1432,7 +1432,7 @@ func TestDiscoveryRepository_DiscoveredCertCRUD(t *testing.T) {
ID: "mc-linked-cert", Name: "linked-cert", CommonName: "linked.example.com",
SANs: []string{}, OwnerID: ownerID, TeamID: teamID,
IssuerID: issuerID, RenewalPolicyID: policyID,
Status: domain.CertificateStatusActive,
Status: domain.CertificateStatusActive,
ExpiresAt: now.Add(90 * 24 * time.Hour), Tags: map[string]string{},
CreatedAt: now, UpdatedAt: now,
}
@@ -1447,7 +1447,7 @@ func TestDiscoveryRepository_DiscoveredCertCRUD(t *testing.T) {
NotBefore: &notBefore, NotAfter: &notAfter, KeyAlgorithm: "RSA", KeySize: 2048,
IsCA: false, PEMData: "---PEM---", SourcePath: "/etc/ssl/certs/disc.pem",
SourceFormat: "PEM", AgentID: "agent-dcert-test",
Status: domain.DiscoveryStatusUnmanaged,
Status: domain.DiscoveryStatusUnmanaged,
FirstSeenAt: now, LastSeenAt: now, CreatedAt: now, UpdatedAt: now,
}
@@ -1577,8 +1577,8 @@ func TestNetworkScanRepository_CRUD(t *testing.T) {
target := &domain.NetworkScanTarget{
ID: "ns-test-1", Name: "Internal Network",
CIDRs: []string{"10.0.0.0/24", "192.168.1.0/24"},
Ports: []int64{443, 8443},
CIDRs: []string{"10.0.0.0/24", "192.168.1.0/24"},
Ports: []int64{443, 8443},
Enabled: true, ScanIntervalHours: 6, TimeoutMs: 5000,
CreatedAt: now, UpdatedAt: now,
}
+1 -1
View File
@@ -1,10 +1,10 @@
package postgres
import (
"github.com/shankar0123/certctl/internal/repository"
"context"
"database/sql"
"fmt"
"github.com/shankar0123/certctl/internal/repository"
"github.com/shankar0123/certctl/internal/domain"
)
+1 -1
View File
@@ -1,10 +1,10 @@
package postgres
import (
"github.com/shankar0123/certctl/internal/repository"
"context"
"database/sql"
"fmt"
"github.com/shankar0123/certctl/internal/repository"
"github.com/google/uuid"
"github.com/shankar0123/certctl/internal/domain"
+1 -1
View File
@@ -1,10 +1,10 @@
package postgres
import (
"github.com/shankar0123/certctl/internal/repository"
"context"
"database/sql"
"fmt"
"github.com/shankar0123/certctl/internal/repository"
"github.com/google/uuid"
"github.com/shankar0123/certctl/internal/domain"
+8 -9
View File
@@ -11,14 +11,14 @@ import (
// mockRenewalService is a mock implementation for testing.
type mockRenewalService struct {
mu sync.Mutex
callCount int
callTimes []time.Time
expireCallCount int
expireCallTimes []time.Time
slowDelay time.Duration
shouldError bool
blockCh chan struct{} // if non-nil, blocks until closed (ignores context)
mu sync.Mutex
callCount int
callTimes []time.Time
expireCallCount int
expireCallTimes []time.Time
slowDelay time.Duration
shouldError bool
blockCh chan struct{} // if non-nil, blocks until closed (ignores context)
}
func (m *mockRenewalService) CheckExpiringCertificates(ctx context.Context) error {
@@ -138,7 +138,6 @@ func (m *mockJobService) RetryFailedJobs(ctx context.Context, maxRetries int) er
return nil
}
// ReapTimedOutJobs is the scheduler-driven counterpart to ProcessPendingJobs that
// covers coverage gap I-003: JobService.ReapTimedOutJobs (via JobReaperService interface)
// had no runtime caller prior to the jobTimeoutLoop being wired.
+10 -10
View File
@@ -12,16 +12,16 @@ import (
// mockAgentGroupRepo is a test implementation of AgentGroupRepository
type mockAgentGroupRepo struct {
groups map[string]*domain.AgentGroup
members map[string][]*domain.Agent
CreateErr error
UpdateErr error
DeleteErr error
GetErr error
ListErr error
ListMembersErr error
AddMemberErr error
RemoveMemberErr error
groups map[string]*domain.AgentGroup
members map[string][]*domain.Agent
CreateErr error
UpdateErr error
DeleteErr error
GetErr error
ListErr error
ListMembersErr error
AddMemberErr error
RemoveMemberErr error
}
func newMockAgentGroupRepository() *mockAgentGroupRepo {
+3 -3
View File
@@ -109,9 +109,9 @@ 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,
ID: "j-1",
AgentID: strPtr("a-1"),
Status: domain.JobStatusRunning,
}
err := svc.UpdateJobStatus(context.Background(), "a-1", "j-1", "Completed", "")
if err != nil {
-1
View File
@@ -10,7 +10,6 @@ import (
"github.com/shankar0123/certctl/internal/domain"
)
func TestRegisterAgent(t *testing.T) {
ctx := context.Background()
agentRepo := &mockAgentRepo{
+22 -22
View File
@@ -73,29 +73,29 @@ var credentialKeys = map[string]bool{
// Note `ip_address` is debatable — useful for forensics but flagged by
// GDPR Art. 32 — defaulting to redact, operators can audit + adjust.
var piiKeys = map[string]bool{
"email": true,
"email_address": true,
"phone": true,
"phone_number": true,
"telephone": true,
"ssn": true,
"email": true,
"email_address": true,
"phone": true,
"phone_number": true,
"telephone": true,
"ssn": true,
"social_security": true,
"dob": true,
"date_of_birth": true,
"name": true,
"full_name": true,
"first_name": true,
"last_name": true,
"surname": true,
"address": true,
"street": true,
"street_address": true,
"city": true,
"postal_code": true,
"zip": true,
"zipcode": true,
"ip": true,
"ip_address": true,
"dob": true,
"date_of_birth": true,
"name": true,
"full_name": true,
"first_name": true,
"last_name": true,
"surname": true,
"address": true,
"street": true,
"street_address": true,
"city": true,
"postal_code": true,
"zip": true,
"zipcode": true,
"ip": true,
"ip_address": true,
}
// RedactDetailsForAudit walks a details map and returns a NEW map with
+8 -8
View File
@@ -30,8 +30,8 @@ func TestRedactDetailsForAudit_CredentialKeys(t *testing.T) {
for _, key := range cases {
t.Run(key, func(t *testing.T) {
in := map[string]interface{}{
key: "sensitive-value-do-not-leak",
"non_sensitive_id": "ok-public-id",
key: "sensitive-value-do-not-leak",
"non_sensitive_id": "ok-public-id",
}
out := RedactDetailsForAudit(in)
if out[key] != "[REDACTED:CREDENTIAL]" {
@@ -70,7 +70,7 @@ func TestRedactDetailsForAudit_NestedMap(t *testing.T) {
in := map[string]interface{}{
"resource_id": "iss-prod",
"config": map[string]interface{}{
"endpoint": "https://acme.example.com",
"endpoint": "https://acme.example.com",
"eab_secret": "do-not-leak-this-secret",
"contact": map[string]interface{}{
"email": "ops@example.com",
@@ -159,8 +159,8 @@ func TestRedactDetailsForAudit_NoRedactionPath(t *testing.T) {
// Maps with no sensitive keys should NOT have a redacted_keys array
// — clutter-free for the common case.
in := map[string]interface{}{
"action": "create_certificate",
"cert_id": "mc-prod-001",
"action": "create_certificate",
"cert_id": "mc-prod-001",
"latency_ms": float64(42),
}
out := RedactDetailsForAudit(in)
@@ -171,7 +171,7 @@ func TestRedactDetailsForAudit_NoRedactionPath(t *testing.T) {
func TestRedactDetailsForAudit_DoesNotMutateInput(t *testing.T) {
in := map[string]interface{}{
"api_key": "secret-do-not-leak",
"api_key": "secret-do-not-leak",
"resource": "iss-prod",
}
_ = RedactDetailsForAudit(in)
@@ -197,8 +197,8 @@ func TestRedactDetailsForAudit_JSONRoundTrip(t *testing.T) {
// The redacted map MUST round-trip through json.Marshal (the
// AuditService persistence path). Catches type-assertion regressions.
in := map[string]interface{}{
"reason": "compromised-key",
"api_key": "leak-me",
"reason": "compromised-key",
"api_key": "leak-me",
"contacts": []interface{}{
map[string]interface{}{"email": "ops@example.com"},
},
+2 -2
View File
@@ -67,8 +67,8 @@ func TestBulkReassign_HappyPath(t *testing.T) {
func TestBulkReassign_SkipsAlreadyOwned(t *testing.T) {
svc, certRepo, ownerRepo, _ := newBulkReassignmentTestService()
addOwner(ownerRepo, "o-bob")
addOwnedCert(certRepo, "mc-1", "o-bob", "") // already owned by target
addOwnedCert(certRepo, "mc-2", "o-alice", "") // needs reassign
addOwnedCert(certRepo, "mc-1", "o-bob", "") // already owned by target
addOwnedCert(certRepo, "mc-2", "o-alice", "") // needs reassign
res, err := svc.BulkReassign(context.Background(),
domain.BulkReassignmentRequest{
+14 -14
View File
@@ -32,11 +32,11 @@ func newBulkRevocationTestService() (*BulkRevocationService, *mockCertRepo, *moc
func addTestCert(repo *mockCertRepo, id, status, issuerID string) {
cert := &domain.ManagedCertificate{
ID: id,
ID: id,
CommonName: id + ".example.com",
Status: domain.CertificateStatus(status),
IssuerID: issuerID,
ExpiresAt: time.Now().AddDate(0, 6, 0),
Status: domain.CertificateStatus(status),
IssuerID: issuerID,
ExpiresAt: time.Now().AddDate(0, 6, 0),
}
repo.AddCert(cert)
// Add a version with serial number (needed by RevokeCertificateWithActor)
@@ -54,13 +54,13 @@ func addTestCert(repo *mockCertRepo, id, status, issuerID string) {
func addTestCertWithProfile(repo *mockCertRepo, id, status, issuerID, profileID, ownerID string) {
cert := &domain.ManagedCertificate{
ID: id,
CommonName: id + ".example.com",
Status: domain.CertificateStatus(status),
IssuerID: issuerID,
ID: id,
CommonName: id + ".example.com",
Status: domain.CertificateStatus(status),
IssuerID: issuerID,
CertificateProfileID: profileID,
OwnerID: ownerID,
ExpiresAt: time.Now().AddDate(0, 6, 0),
OwnerID: ownerID,
ExpiresAt: time.Now().AddDate(0, 6, 0),
}
repo.AddCert(cert)
repo.Versions[id] = []*domain.CertificateVersion{
@@ -272,11 +272,11 @@ func TestBulkRevoke_PartialFailure(t *testing.T) {
addTestCert(certRepo, "mc-1", "Active", "iss-local")
// mc-2 is active but has NO version — RevokeCertificateWithActor will fail on GetLatestVersion
cert2 := &domain.ManagedCertificate{
ID: "mc-2",
ID: "mc-2",
CommonName: "mc-2.example.com",
Status: domain.CertificateStatusActive,
IssuerID: "iss-local",
ExpiresAt: time.Now().AddDate(0, 6, 0),
Status: domain.CertificateStatusActive,
IssuerID: "iss-local",
ExpiresAt: time.Now().AddDate(0, 6, 0),
}
certRepo.AddCert(cert2)
// Don't add versions for mc-2 so GetLatestVersion returns errNotFound
+11 -11
View File
@@ -266,17 +266,17 @@ func TestDeploymentService_ProcessDeploymentJob_Success(t *testing.T) {
// Add agent with recent heartbeat
now := time.Now()
agent := &domain.Agent{
ID: "agent-1",
Name: "Test Agent",
Hostname: "agent.example.com",
Status: domain.AgentStatusOnline,
LastHeartbeatAt: &now,
RegisteredAt: time.Now(),
APIKeyHash: "hash-1",
OS: "linux",
Architecture: "amd64",
IPAddress: "192.168.1.1",
Version: "1.0.0",
ID: "agent-1",
Name: "Test Agent",
Hostname: "agent.example.com",
Status: domain.AgentStatusOnline,
LastHeartbeatAt: &now,
RegisteredAt: time.Now(),
APIKeyHash: "hash-1",
OS: "linux",
Architecture: "amd64",
IPAddress: "192.168.1.1",
Version: "1.0.0",
}
agentRepo.AddAgent(agent)
+14 -14
View File
@@ -31,20 +31,20 @@ type HTMLEmailSender interface {
// DigestData holds the aggregated data for a digest email.
type DigestData struct {
GeneratedAt time.Time `json:"generated_at"`
TotalCertificates int64 `json:"total_certificates"`
ExpiringCertificates int64 `json:"expiring_certificates"`
ExpiredCertificates int64 `json:"expired_certificates"`
RevokedCertificates int64 `json:"revoked_certificates"`
ActiveAgents int64 `json:"active_agents"`
OfflineAgents int64 `json:"offline_agents"`
TotalAgents int64 `json:"total_agents"`
PendingJobs int64 `json:"pending_jobs"`
FailedJobs int64 `json:"failed_jobs"`
CompletedJobs int64 `json:"completed_jobs"`
ExpiringCerts []DigestCertEntry `json:"expiring_certs"`
RecentFailures []DigestJobEntry `json:"recent_failures"`
StatusCounts []DigestStatusCount `json:"status_counts"`
GeneratedAt time.Time `json:"generated_at"`
TotalCertificates int64 `json:"total_certificates"`
ExpiringCertificates int64 `json:"expiring_certificates"`
ExpiredCertificates int64 `json:"expired_certificates"`
RevokedCertificates int64 `json:"revoked_certificates"`
ActiveAgents int64 `json:"active_agents"`
OfflineAgents int64 `json:"offline_agents"`
TotalAgents int64 `json:"total_agents"`
PendingJobs int64 `json:"pending_jobs"`
FailedJobs int64 `json:"failed_jobs"`
CompletedJobs int64 `json:"completed_jobs"`
ExpiringCerts []DigestCertEntry `json:"expiring_certs"`
RecentFailures []DigestJobEntry `json:"recent_failures"`
StatusCounts []DigestStatusCount `json:"status_counts"`
}
// DigestCertEntry represents a certificate entry in the digest.
+54 -54
View File
@@ -12,17 +12,17 @@ import (
// mockDiscoveryRepo is a test implementation of DiscoveryRepository
type mockDiscoveryRepo struct {
Scans map[string]*domain.DiscoveryScan
Discovered map[string]*domain.DiscoveredCertificate
CreateScanErr error
GetScanErr error
ListScansErr error
Scans map[string]*domain.DiscoveryScan
Discovered map[string]*domain.DiscoveredCertificate
CreateScanErr error
GetScanErr error
ListScansErr error
CreateDiscoveredErr error
GetDiscoveredErr error
ListDiscoveredErr error
UpdateStatusErr error
GetDiscoveredErr error
ListDiscoveredErr error
UpdateStatusErr error
GetByFingerprintErr error
CountByStatusErr error
CountByStatusErr error
}
func newMockDiscoveryRepository() *mockDiscoveryRepo {
@@ -268,20 +268,20 @@ func TestListDiscovered_Success(t *testing.T) {
now := time.Now()
cert1 := &domain.DiscoveredCertificate{
ID: "dcert-1",
AgentID: "agent-1",
CommonName: "example.com",
Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now,
UpdatedAt: now,
ID: "dcert-1",
AgentID: "agent-1",
CommonName: "example.com",
Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now,
UpdatedAt: now,
}
cert2 := &domain.DiscoveredCertificate{
ID: "dcert-2",
AgentID: "agent-1",
CommonName: "api.example.com",
Status: domain.DiscoveryStatusManaged,
CreatedAt: now,
UpdatedAt: now,
ID: "dcert-2",
AgentID: "agent-1",
CommonName: "api.example.com",
Status: domain.DiscoveryStatusManaged,
CreatedAt: now,
UpdatedAt: now,
}
discoveryRepo.Discovered[cert1.ID] = cert1
discoveryRepo.Discovered[cert2.ID] = cert2
@@ -304,20 +304,20 @@ func TestListDiscovered_WithStatusFilter(t *testing.T) {
now := time.Now()
cert1 := &domain.DiscoveredCertificate{
ID: "dcert-1",
AgentID: "agent-1",
ID: "dcert-1",
AgentID: "agent-1",
CommonName: "example.com",
Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now,
UpdatedAt: now,
Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now,
UpdatedAt: now,
}
cert2 := &domain.DiscoveredCertificate{
ID: "dcert-2",
AgentID: "agent-1",
ID: "dcert-2",
AgentID: "agent-1",
CommonName: "api.example.com",
Status: domain.DiscoveryStatusManaged,
CreatedAt: now,
UpdatedAt: now,
Status: domain.DiscoveryStatusManaged,
CreatedAt: now,
UpdatedAt: now,
}
discoveryRepo.Discovered[cert1.ID] = cert1
discoveryRepo.Discovered[cert2.ID] = cert2
@@ -340,11 +340,11 @@ func TestGetDiscovered_Success(t *testing.T) {
now := time.Now()
cert := &domain.DiscoveredCertificate{
ID: "dcert-1",
CommonName: "example.com",
Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now,
UpdatedAt: now,
ID: "dcert-1",
CommonName: "example.com",
Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now,
UpdatedAt: now,
}
discoveryRepo.Discovered[cert.ID] = cert
@@ -363,12 +363,12 @@ func TestClaimDiscovered_Success(t *testing.T) {
now := time.Now()
discoveredCert := &domain.DiscoveredCertificate{
ID: "dcert-1",
CommonName: "example.com",
FingerprintSHA256: "abc123",
Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now,
UpdatedAt: now,
ID: "dcert-1",
CommonName: "example.com",
FingerprintSHA256: "abc123",
Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now,
UpdatedAt: now,
}
discoveryRepo.Discovered[discoveredCert.ID] = discoveredCert
@@ -415,11 +415,11 @@ func TestClaimDiscovered_MissingManagedCertID(t *testing.T) {
now := time.Now()
cert := &domain.DiscoveredCertificate{
ID: "dcert-1",
ID: "dcert-1",
CommonName: "example.com",
Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now,
UpdatedAt: now,
Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now,
UpdatedAt: now,
}
discoveryRepo.Discovered[cert.ID] = cert
@@ -434,11 +434,11 @@ func TestClaimDiscovered_ManagedCertNotFound(t *testing.T) {
now := time.Now()
cert := &domain.DiscoveredCertificate{
ID: "dcert-1",
ID: "dcert-1",
CommonName: "example.com",
Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now,
UpdatedAt: now,
Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now,
UpdatedAt: now,
}
discoveryRepo.Discovered[cert.ID] = cert
@@ -456,11 +456,11 @@ func TestDismissDiscovered_Success(t *testing.T) {
now := time.Now()
cert := &domain.DiscoveredCertificate{
ID: "dcert-1",
ID: "dcert-1",
CommonName: "example.com",
Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now,
UpdatedAt: now,
Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now,
UpdatedAt: now,
}
discoveryRepo.Discovered[cert.ID] = cert
+3 -3
View File
@@ -71,10 +71,10 @@ func TestExportPEM_Success(t *testing.T) {
Status: domain.CertificateStatusActive,
},
&domain.CertificateVersion{
ID: "cv-1",
ID: "cv-1",
CertificateID: "mc-test-1",
SerialNumber: "abc123",
PEMChain: fullPEM,
SerialNumber: "abc123",
PEMChain: fullPEM,
},
)
auditSvc := &AuditService{auditRepo: &mockAuditRepo{}}
+16 -16
View File
@@ -14,19 +14,19 @@ import (
// 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
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 {
@@ -151,9 +151,9 @@ func TestHealthCheckService_Create_Success(t *testing.T) {
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,
Endpoint: "example.com:443",
Status: domain.HealthStatusUnknown,
Enabled: true,
CheckIntervalSecs: 300,
}
+1 -1
View File
@@ -1,12 +1,12 @@
package service
import (
"time"
"context"
"errors"
"fmt"
"log/slog"
"strings"
"time"
"github.com/shankar0123/certctl/internal/domain"
"github.com/shankar0123/certctl/internal/repository"
@@ -36,9 +36,9 @@ func mkRunningJob(id, agentID string) *domain.Job {
a := agentID
now := time.Now()
return &domain.Job{
ID: id,
AgentID: &a,
Status: domain.JobStatusRunning,
ID: id,
AgentID: &a,
Status: domain.JobStatusRunning,
CreatedAt: now.Add(-2 * time.Hour),
}
}
+3 -3
View File
@@ -871,8 +871,8 @@ func TestJobService_ReapTimedOutJobs_ContinuesOnIndividualUpdateFailure(t *testi
jobA.ID: jobA,
jobB.ID: jobB,
},
StatusUpdates: make(map[string]domain.JobStatus),
UpdateErrorByID: make(map[string]error),
StatusUpdates: make(map[string]domain.JobStatus),
UpdateErrorByID: make(map[string]error),
UpdateErrorByIDMu: sync.Mutex{},
}
// Make Update fail only for jobA
@@ -892,7 +892,7 @@ func TestJobService_ReapTimedOutJobs_ContinuesOnIndividualUpdateFailure(t *testi
jobAAfter := jobRepo.Jobs[jobA.ID]
jobBAfter := jobRepo.Jobs[jobB.ID]
if jobAAfter.Status != domain.JobStatusFailed || jobBAfter.Status != domain.JobStatusFailed {
t.Fatalf("expected both jobs status Failed (modified before Update), got A=%s B=%s",
t.Fatalf("expected both jobs status Failed (modified before Update), got A=%s B=%s",
jobAAfter.Status, jobBAfter.Status)
}
+2 -2
View File
@@ -377,8 +377,8 @@ func TestExpandCIDR_AllowsPrivateRanges(t *testing.T) {
cidr string
min int
}{
{"10/8 sample", "10.0.0.0/30", 2}, // 2 usable (after removing network/broadcast)
{"172.16/12 sample", "172.16.0.0/30", 2}, // 2 usable
{"10/8 sample", "10.0.0.0/30", 2}, // 2 usable (after removing network/broadcast)
{"172.16/12 sample", "172.16.0.0/30", 2}, // 2 usable
{"192.168/16 sample", "192.168.1.1/32", 1}, // Single IP
}
+10 -10
View File
@@ -43,16 +43,16 @@ import "sync/atomic"
// New labels MUST also be added to OCSPCounters.Snapshot AND to the
// Prometheus exposer in Phase 8.
type OCSPCounters struct {
requestGET atomic.Uint64
requestPOST atomic.Uint64
requestSuccess atomic.Uint64
requestInvalid atomic.Uint64
issuerNotFound atomic.Uint64
certNotFound atomic.Uint64
signingFailed atomic.Uint64
nonceEchoed atomic.Uint64
nonceMalformed atomic.Uint64
rateLimited atomic.Uint64
requestGET atomic.Uint64
requestPOST atomic.Uint64
requestSuccess atomic.Uint64
requestInvalid atomic.Uint64
issuerNotFound atomic.Uint64
certNotFound atomic.Uint64
signingFailed atomic.Uint64
nonceEchoed atomic.Uint64
nonceMalformed atomic.Uint64
rateLimited atomic.Uint64
}
// NewOCSPCounters constructs a zero-value counter table. The caller
+7 -9
View File
@@ -977,7 +977,7 @@ func TestCheckExpiringCertificates_ARI_ShouldRenewNow(t *testing.T) {
ID: "rp-standard", Name: "Standard", RenewalWindowDays: 30,
AutoRenew: true, MaxRetries: 3, RetryInterval: 300,
AlertThresholdsDays: []int{30, 14, 7, 0},
CreatedAt: time.Now(), UpdatedAt: time.Now(),
CreatedAt: time.Now(), UpdatedAt: time.Now(),
}
policyRepo.AddPolicy(policy)
@@ -1050,7 +1050,7 @@ func TestCheckExpiringCertificates_ARI_NotYet(t *testing.T) {
ID: "rp-standard", Name: "Standard", RenewalWindowDays: 30,
AutoRenew: true, MaxRetries: 3, RetryInterval: 300,
AlertThresholdsDays: []int{30, 14, 7, 0},
CreatedAt: time.Now(), UpdatedAt: time.Now(),
CreatedAt: time.Now(), UpdatedAt: time.Now(),
}
policyRepo.AddPolicy(policy)
@@ -1110,7 +1110,7 @@ func TestCheckExpiringCertificates_ARI_NilResult_FallsThrough(t *testing.T) {
ID: "rp-standard", Name: "Standard", RenewalWindowDays: 30,
AutoRenew: true, MaxRetries: 3, RetryInterval: 300,
AlertThresholdsDays: []int{30, 14, 7, 0},
CreatedAt: time.Now(), UpdatedAt: time.Now(),
CreatedAt: time.Now(), UpdatedAt: time.Now(),
}
policyRepo.AddPolicy(policy)
@@ -1178,7 +1178,7 @@ func TestCheckExpiringCertificates_ARI_Error_FallsThrough(t *testing.T) {
ID: "rp-standard", Name: "Standard", RenewalWindowDays: 30,
AutoRenew: true, MaxRetries: 3, RetryInterval: 300,
AlertThresholdsDays: []int{30, 14, 7, 0},
CreatedAt: time.Now(), UpdatedAt: time.Now(),
CreatedAt: time.Now(), UpdatedAt: time.Now(),
}
policyRepo.AddPolicy(policy)
@@ -1313,7 +1313,6 @@ func TestFailJob_SetsFailedStatus(t *testing.T) {
}
}
// --- CreateDeploymentJobs Tests ---
func TestCreateDeploymentJobs_PartialFailure(t *testing.T) {
@@ -1331,10 +1330,10 @@ func TestCreateDeploymentJobs_PartialFailure(t *testing.T) {
// Create certificate
cert := &domain.ManagedCertificate{
ID: "mc-partial",
ID: "mc-partial",
CommonName: "test.example.com",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
certRepo.AddCert(cert)
@@ -1383,5 +1382,4 @@ func TestCreateDeploymentJobs_PartialFailure(t *testing.T) {
}
}
// stringPtr is defined in notification_test.go
+5 -5
View File
@@ -13,11 +13,11 @@ import (
// RevocationSvc provides revocation-related business logic.
// It handles certificate revocation, revocation notifications, and issuer coordination.
type RevocationSvc struct {
certRepo repository.CertificateRepository
revocationRepo repository.RevocationRepository
auditService *AuditService
notificationSvc *NotificationService
issuerRegistry *IssuerRegistry
certRepo repository.CertificateRepository
revocationRepo repository.RevocationRepository
auditService *AuditService
notificationSvc *NotificationService
issuerRegistry *IssuerRegistry
// ocspCacheInvalidator — production hardening II Phase 2 load-
// bearing security wire. After a successful revocation, the
// service MUST invalidate the OCSP response cache for this
+76 -76
View File
@@ -48,31 +48,31 @@ func TestExpireShortLivedCertificates_Success(t *testing.T) {
// Create a short-lived profile (TTL < 1 hour = 3600 seconds)
shortLivedProfile := &domain.CertificateProfile{
ID: "prof-short",
Name: "Short-Lived",
MaxTTLSeconds: 300, // 5 minutes
AllowShortLived: true,
Enabled: true,
ID: "prof-short",
Name: "Short-Lived",
MaxTTLSeconds: 300, // 5 minutes
AllowShortLived: true,
Enabled: true,
AllowedKeyAlgorithms: domain.DefaultKeyAlgorithms(),
AllowedEKUs: domain.DefaultEKUs(),
CreatedAt: now,
UpdatedAt: now,
AllowedEKUs: domain.DefaultEKUs(),
CreatedAt: now,
UpdatedAt: now,
}
profileRepo.AddProfile(shortLivedProfile)
// Create an active certificate that has already expired
expiredCert := &domain.ManagedCertificate{
ID: "mc-expired-short",
Name: "Expired Short-Lived Cert",
CommonName: "short.example.com",
SANs: []string{},
IssuerID: "iss-test",
CertificateProfileID: "prof-short",
Status: domain.CertificateStatusActive,
ExpiresAt: now.Add(-5 * time.Minute), // Already expired
CreatedAt: now.Add(-15 * time.Minute),
UpdatedAt: now.Add(-5 * time.Minute),
Tags: make(map[string]string),
ID: "mc-expired-short",
Name: "Expired Short-Lived Cert",
CommonName: "short.example.com",
SANs: []string{},
IssuerID: "iss-test",
CertificateProfileID: "prof-short",
Status: domain.CertificateStatusActive,
ExpiresAt: now.Add(-5 * time.Minute), // Already expired
CreatedAt: now.Add(-15 * time.Minute),
UpdatedAt: now.Add(-5 * time.Minute),
Tags: make(map[string]string),
}
certRepo.AddCert(expiredCert)
@@ -218,31 +218,31 @@ func TestExpireShortLivedCertificates_PartialUpdateError(t *testing.T) {
// Create a short-lived profile
shortLivedProfile := &domain.CertificateProfile{
ID: "prof-short",
Name: "Short-Lived",
MaxTTLSeconds: 300,
AllowShortLived: true,
Enabled: true,
ID: "prof-short",
Name: "Short-Lived",
MaxTTLSeconds: 300,
AllowShortLived: true,
Enabled: true,
AllowedKeyAlgorithms: domain.DefaultKeyAlgorithms(),
AllowedEKUs: domain.DefaultEKUs(),
CreatedAt: now,
UpdatedAt: now,
AllowedEKUs: domain.DefaultEKUs(),
CreatedAt: now,
UpdatedAt: now,
}
profileRepo.AddProfile(shortLivedProfile)
// Create a certificate with a failing update
expiredCert := &domain.ManagedCertificate{
ID: "mc-expired-fail",
Name: "Expired Cert That Will Fail",
CommonName: "fail.example.com",
SANs: []string{},
IssuerID: "iss-test",
CertificateProfileID: "prof-short",
Status: domain.CertificateStatusActive,
ExpiresAt: now.Add(-5 * time.Minute),
CreatedAt: now.Add(-15 * time.Minute),
UpdatedAt: now.Add(-5 * time.Minute),
Tags: make(map[string]string),
ID: "mc-expired-fail",
Name: "Expired Cert That Will Fail",
CommonName: "fail.example.com",
SANs: []string{},
IssuerID: "iss-test",
CertificateProfileID: "prof-short",
Status: domain.CertificateStatusActive,
ExpiresAt: now.Add(-5 * time.Minute),
CreatedAt: now.Add(-15 * time.Minute),
UpdatedAt: now.Add(-5 * time.Minute),
Tags: make(map[string]string),
}
certRepo.AddCert(expiredCert)
@@ -275,31 +275,31 @@ func TestExpireShortLivedCertificates_AlreadyExpired(t *testing.T) {
// Create a short-lived profile
shortLivedProfile := &domain.CertificateProfile{
ID: "prof-short",
Name: "Short-Lived",
MaxTTLSeconds: 300,
AllowShortLived: true,
Enabled: true,
ID: "prof-short",
Name: "Short-Lived",
MaxTTLSeconds: 300,
AllowShortLived: true,
Enabled: true,
AllowedKeyAlgorithms: domain.DefaultKeyAlgorithms(),
AllowedEKUs: domain.DefaultEKUs(),
CreatedAt: now,
UpdatedAt: now,
AllowedEKUs: domain.DefaultEKUs(),
CreatedAt: now,
UpdatedAt: now,
}
profileRepo.AddProfile(shortLivedProfile)
// Create a certificate that's already in Expired status
alreadyExpiredCert := &domain.ManagedCertificate{
ID: "mc-already-expired",
Name: "Already Expired Cert",
CommonName: "already-expired.example.com",
SANs: []string{},
IssuerID: "iss-test",
CertificateProfileID: "prof-short",
Status: domain.CertificateStatusExpired, // Already expired
ExpiresAt: now.Add(-30 * time.Minute),
CreatedAt: now.Add(-45 * time.Minute),
UpdatedAt: now.Add(-10 * time.Minute),
Tags: make(map[string]string),
ID: "mc-already-expired",
Name: "Already Expired Cert",
CommonName: "already-expired.example.com",
SANs: []string{},
IssuerID: "iss-test",
CertificateProfileID: "prof-short",
Status: domain.CertificateStatusExpired, // Already expired
ExpiresAt: now.Add(-30 * time.Minute),
CreatedAt: now.Add(-45 * time.Minute),
UpdatedAt: now.Add(-10 * time.Minute),
Tags: make(map[string]string),
}
certRepo.AddCert(alreadyExpiredCert)
@@ -329,31 +329,31 @@ func TestExpireShortLivedCertificates_ProfileNotShortLived(t *testing.T) {
// Create a regular (not short-lived) profile with TTL > 1 hour
regularProfile := &domain.CertificateProfile{
ID: "prof-regular",
Name: "Regular",
MaxTTLSeconds: 86400, // 24 hours
AllowShortLived: false,
Enabled: true,
ID: "prof-regular",
Name: "Regular",
MaxTTLSeconds: 86400, // 24 hours
AllowShortLived: false,
Enabled: true,
AllowedKeyAlgorithms: domain.DefaultKeyAlgorithms(),
AllowedEKUs: domain.DefaultEKUs(),
CreatedAt: now,
UpdatedAt: now,
AllowedEKUs: domain.DefaultEKUs(),
CreatedAt: now,
UpdatedAt: now,
}
profileRepo.AddProfile(regularProfile)
// Create an expired certificate with the regular profile
expiredCert := &domain.ManagedCertificate{
ID: "mc-expired-regular",
Name: "Expired Regular Cert",
CommonName: "regular.example.com",
SANs: []string{},
IssuerID: "iss-test",
CertificateProfileID: "prof-regular",
Status: domain.CertificateStatusActive,
ExpiresAt: now.Add(-1 * time.Hour),
CreatedAt: now.Add(-25 * time.Hour),
UpdatedAt: now.Add(-1 * time.Hour),
Tags: make(map[string]string),
ID: "mc-expired-regular",
Name: "Expired Regular Cert",
CommonName: "regular.example.com",
SANs: []string{},
IssuerID: "iss-test",
CertificateProfileID: "prof-regular",
Status: domain.CertificateStatusActive,
ExpiresAt: now.Add(-1 * time.Hour),
CreatedAt: now.Add(-25 * time.Hour),
UpdatedAt: now.Add(-1 * time.Hour),
Tags: make(map[string]string),
}
certRepo.AddCert(expiredCert)

Some files were not shown because too many files have changed in this diff Show More