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
+7 -7
View File
@@ -692,10 +692,10 @@ func TestMakeRequest_InvalidURL(t *testing.T) {
// TestCertKeyInfo tests extraction of key algorithm and size from certificates. // TestCertKeyInfo tests extraction of key algorithm and size from certificates.
func TestCertKeyInfo(t *testing.T) { func TestCertKeyInfo(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
genKey func() interface{} genKey func() interface{}
expectedAlg string expectedAlg string
minBitSize int minBitSize int
}{ }{
{ {
name: "ECDSA P-256", name: "ECDSA P-256",
@@ -1503,9 +1503,9 @@ func TestValidateHTTPSScheme(t *testing.T) {
wantErrSub: "plaintext http://", wantErrSub: "plaintext http://",
}, },
{ {
name: "bare host missing scheme falls through to unsupported", name: "bare host missing scheme falls through to unsupported",
serverURL: "localhost:8443", serverURL: "localhost:8443",
wantErr: true, wantErr: true,
// url.Parse treats "localhost:8443" as scheme=localhost, // url.Parse treats "localhost:8443" as scheme=localhost,
// opaque=8443 — exercises the default arm (unsupported scheme) // opaque=8443 — exercises the default arm (unsupported scheme)
// rather than the empty-scheme arm. Both are fail-closed, which // rather than the empty-scheme arm. Both are fail-closed, which
+9 -9
View File
@@ -34,16 +34,16 @@ import (
"github.com/shankar0123/certctl/internal/connector/target/apache" "github.com/shankar0123/certctl/internal/connector/target/apache"
"github.com/shankar0123/certctl/internal/connector/target/caddy" "github.com/shankar0123/certctl/internal/connector/target/caddy"
"github.com/shankar0123/certctl/internal/connector/target/envoy" "github.com/shankar0123/certctl/internal/connector/target/envoy"
pf "github.com/shankar0123/certctl/internal/connector/target/postfix"
sshconn "github.com/shankar0123/certctl/internal/connector/target/ssh"
"github.com/shankar0123/certctl/internal/connector/target/f5" "github.com/shankar0123/certctl/internal/connector/target/f5"
jks "github.com/shankar0123/certctl/internal/connector/target/javakeystore"
k8s "github.com/shankar0123/certctl/internal/connector/target/k8ssecret"
wcs "github.com/shankar0123/certctl/internal/connector/target/wincertstore"
"github.com/shankar0123/certctl/internal/connector/target/haproxy" "github.com/shankar0123/certctl/internal/connector/target/haproxy"
"github.com/shankar0123/certctl/internal/connector/target/iis" "github.com/shankar0123/certctl/internal/connector/target/iis"
jks "github.com/shankar0123/certctl/internal/connector/target/javakeystore"
k8s "github.com/shankar0123/certctl/internal/connector/target/k8ssecret"
"github.com/shankar0123/certctl/internal/connector/target/nginx" "github.com/shankar0123/certctl/internal/connector/target/nginx"
pf "github.com/shankar0123/certctl/internal/connector/target/postfix"
sshconn "github.com/shankar0123/certctl/internal/connector/target/ssh"
"github.com/shankar0123/certctl/internal/connector/target/traefik" "github.com/shankar0123/certctl/internal/connector/target/traefik"
wcs "github.com/shankar0123/certctl/internal/connector/target/wincertstore"
) )
// AgentConfig represents the agent-side configuration. // AgentConfig represents the agent-side configuration.
@@ -80,10 +80,10 @@ type Agent struct {
client *http.Client client *http.Client
// Configuration // Configuration
heartbeatInterval time.Duration heartbeatInterval time.Duration
pollInterval time.Duration pollInterval time.Duration
discoveryInterval time.Duration discoveryInterval time.Duration
consecutiveFailures int consecutiveFailures int
// I-004: terminal retirement signal. retiredSignal is closed exactly once // I-004: terminal retirement signal. retiredSignal is closed exactly once
// (guarded by retiredOnce) when either sendHeartbeat or pollForWork // (guarded by retiredOnce) when either sendHeartbeat or pollForWork
+9 -9
View File
@@ -75,8 +75,8 @@ func verifyDeployment(
// calls, issuer connector communication, or any operation that trusts the // calls, issuer connector communication, or any operation that trusts the
// certificate. The verification result compares SHA-256 fingerprints only. // certificate. The verification result compares SHA-256 fingerprints only.
// See TICKET-016 for full security audit rationale. // See TICKET-016 for full security audit rationale.
InsecureSkipVerify: true, //nolint:gosec // verification probe; documented above + docs/tls.md L-001 table InsecureSkipVerify: true, //nolint:gosec // verification probe; documented above + docs/tls.md L-001 table
ServerName: targetHost, // For SNI ServerName: targetHost, // For SNI
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to connect to %s: %w", address, err) return nil, fmt.Errorf("failed to connect to %s: %w", address, err)
@@ -161,11 +161,11 @@ func (a *Agent) reportVerificationResult(
// Build the request payload // Build the request payload
payload := map[string]interface{}{ payload := map[string]interface{}{
"target_id": targetID, "target_id": targetID,
"expected_fingerprint": result.ExpectedFingerprint, "expected_fingerprint": result.ExpectedFingerprint,
"actual_fingerprint": result.ActualFingerprint, "actual_fingerprint": result.ActualFingerprint,
"verified": result.Verified, "verified": result.Verified,
"error": result.Error, "error": result.Error,
} }
body, err := json.Marshal(payload) body, err := json.Marshal(payload)
@@ -247,7 +247,7 @@ func (a *Agent) verifyAndReportDeployment(
) { ) {
// Perform verification with configured timeout and delay // Perform verification with configured timeout and delay
result, err := verifyDeployment(ctx, targetHost, targetPort, certPEM, result, err := verifyDeployment(ctx, targetHost, targetPort, certPEM,
2*time.Second, // delay before probing 2*time.Second, // delay before probing
10*time.Second, // timeout for TLS connection 10*time.Second, // timeout for TLS connection
a.logger) a.logger)
@@ -261,7 +261,7 @@ func (a *Agent) verifyAndReportDeployment(
} }
// Probe failure: report error but continue // Probe failure: report error but continue
result = &VerificationResult{ result = &VerificationResult{
Error: err.Error(), Error: err.Error(),
VerifiedAt: time.Now().UTC(), VerifiedAt: time.Now().UTC(),
} }
} }
+3 -3
View File
@@ -114,9 +114,9 @@ func TestExtractTargetHostAndPort_InvalidJSON(t *testing.T) {
func TestExtractTargetHostAndPort_AlternativeFieldNames(t *testing.T) { func TestExtractTargetHostAndPort_AlternativeFieldNames(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
config map[string]interface{} config map[string]interface{}
expected string expected string
}{ }{
{"host", map[string]interface{}{"host": "host1.com"}, "host1.com"}, {"host", map[string]interface{}{"host": "host1.com"}, "host1.com"},
{"hostname", map[string]interface{}{"hostname": "host2.com"}, "host2.com"}, {"hostname", map[string]interface{}{"hostname": "host2.com"}, "host2.com"},
+3 -3
View File
@@ -53,9 +53,9 @@ func TestValidateHTTPSScheme(t *testing.T) {
wantErrSub: "plaintext http://", wantErrSub: "plaintext http://",
}, },
{ {
name: "bare host missing scheme rejected", name: "bare host missing scheme rejected",
serverURL: "localhost:8443", serverURL: "localhost:8443",
wantErr: true, wantErr: true,
// url.Parse treats "localhost:8443" as scheme=localhost, opaque=8443 // url.Parse treats "localhost:8443" as scheme=localhost, opaque=8443
// — exercises the default arm (unsupported scheme) rather than the // — exercises the default arm (unsupported scheme) rather than the
// empty-scheme arm. Both are fail-closed, which is what we care about. // empty-scheme arm. Both are fail-closed, which is what we care about.
+3 -3
View File
@@ -47,9 +47,9 @@ func TestValidateHTTPSScheme(t *testing.T) {
wantErrSub: "plaintext http://", wantErrSub: "plaintext http://",
}, },
{ {
name: "bare host missing scheme rejected", name: "bare host missing scheme rejected",
serverURL: "localhost:8443", serverURL: "localhost:8443",
wantErr: true, wantErr: true,
// url.Parse treats "localhost:8443" as scheme=localhost, opaque=8443 // url.Parse treats "localhost:8443" as scheme=localhost, opaque=8443
// — exercises the default arm (unsupported scheme) rather than the // — exercises the default arm (unsupported scheme) rather than the
// empty-scheme arm. Both are fail-closed, which is what we care about. // empty-scheme arm. Both are fail-closed, which is what we care about.
+14 -14
View File
@@ -149,10 +149,10 @@ func (c *qaClient) do(method, path string, body string) (*http.Response, error)
return c.http.Do(req) return c.http.Do(req)
} }
func (c *qaClient) get(path string) (*http.Response, error) { return c.do("GET", path, "") } func (c *qaClient) get(path string) (*http.Response, error) { return c.do("GET", path, "") }
func (c *qaClient) post(path, body string) (*http.Response, error) { return c.do("POST", path, body) } func (c *qaClient) post(path, body string) (*http.Response, error) { return c.do("POST", path, body) }
func (c *qaClient) put(path, body string) (*http.Response, error) { return c.do("PUT", path, body) } func (c *qaClient) put(path, body string) (*http.Response, error) { return c.do("PUT", path, body) }
func (c *qaClient) delete(path string) (*http.Response, error) { return c.do("DELETE", path, "") } func (c *qaClient) delete(path string) (*http.Response, error) { return c.do("DELETE", path, "") }
// statusCode makes a request and returns the HTTP status code. // statusCode makes a request and returns the HTTP status code.
func (c *qaClient) statusCode(method, path, body string) (int, error) { func (c *qaClient) statusCode(method, path, body string) (int, error) {
@@ -228,11 +228,11 @@ type qaCert struct {
} }
type qaJob struct { type qaJob struct {
ID string `json:"id"` ID string `json:"id"`
Type string `json:"type"` Type string `json:"type"`
Status string `json:"status"` Status string `json:"status"`
CertificateID string `json:"certificate_id"` CertificateID string `json:"certificate_id"`
AgentID *string `json:"agent_id"` AgentID *string `json:"agent_id"`
} }
type qaIssuer struct { type qaIssuer struct {
@@ -261,15 +261,15 @@ type qaAgent struct {
} }
type qaNotification struct { type qaNotification struct {
ID string `json:"id"` ID string `json:"id"`
Read bool `json:"read"` Read bool `json:"read"`
} }
type qaStats struct { type qaStats struct {
TotalCertificates int `json:"total_certificates"` TotalCertificates int `json:"total_certificates"`
ActiveCertificates int `json:"active_certificates"` ActiveCertificates int `json:"active_certificates"`
ExpiringCertificates int `json:"expiring_certificates"` ExpiringCertificates int `json:"expiring_certificates"`
TotalAgents int `json:"total_agents"` TotalAgents int `json:"total_agents"`
} }
type qaMetrics struct { type qaMetrics struct {
@@ -56,8 +56,8 @@ func adversarialPathInputs() []struct {
{"null_byte_trailing", "mc-001\x00"}, {"null_byte_trailing", "mc-001\x00"},
{"null_byte_embedded", "mc-\x00-001"}, {"null_byte_embedded", "mc-\x00-001"},
{"long_id_10k", strings.Repeat("A", 10000)}, {"long_id_10k", strings.Repeat("A", 10000)},
{"unicode_homoglyph_hyphen", "mc\u2010001"}, // U+2010 HYPHEN {"unicode_homoglyph_hyphen", "mc\u2010001"}, // U+2010 HYPHEN
{"unicode_homoglyph_fullwidth", "mc\uFF0D001"}, // U+FF0D FULLWIDTH HYPHEN-MINUS {"unicode_homoglyph_fullwidth", "mc\uFF0D001"}, // U+FF0D FULLWIDTH HYPHEN-MINUS
{"control_char_newline", "mc-001\n"}, {"control_char_newline", "mc-001\n"},
{"control_char_tab", "mc\t001"}, {"control_char_tab", "mc\t001"},
{"control_char_bell", "mc\x07001"}, {"control_char_bell", "mc\x07001"},
+2 -2
View File
@@ -1,10 +1,10 @@
package handler package handler
import ( import (
"github.com/shankar0123/certctl/internal/repository"
"errors"
"context" "context"
"encoding/json" "encoding/json"
"errors"
"github.com/shankar0123/certctl/internal/repository"
"net/http" "net/http"
"strconv" "strconv"
"strings" "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 // 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 // results and nil errors, which mirrors the safest default (no-op) for
// unrelated suites that mock only the legacy surface. // unrelated suites that mock only the legacy surface.
RetireAgentFn func(agentID, actor string, force bool, reason string) (*service.AgentRetirementResult, error) RetireAgentFn func(agentID, actor string, force bool, reason string) (*service.AgentRetirementResult, error)
ListRetiredAgentsFn func(page, perPage int) ([]domain.Agent, int64, error) ListRetiredAgentsFn func(page, perPage int) ([]domain.Agent, int64, error)
} }
func (m *MockAgentService) ListAgents(_ context.Context, 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 { var body struct {
RetiredAt time.Time `json:"retired_at"` RetiredAt time.Time `json:"retired_at"`
AlreadyRetired bool `json:"already_retired"` AlreadyRetired bool `json:"already_retired"`
Cascade bool `json:"cascade"` Cascade bool `json:"cascade"`
Counts domain.AgentDependencyCounts `json:"counts"` Counts domain.AgentDependencyCounts `json:"counts"`
} }
if err := json.NewDecoder(w.Body).Decode(&body); err != nil { if err := json.NewDecoder(w.Body).Decode(&body); err != nil {
t.Fatalf("decode 200 body: %v", err) t.Fatalf("decode 200 body: %v", err)
@@ -273,10 +273,10 @@ func TestRetireAgentHandler_ForceCascade_200(t *testing.T) {
} }
var body struct { var body struct {
RetiredAt time.Time `json:"retired_at"` RetiredAt time.Time `json:"retired_at"`
AlreadyRetired bool `json:"already_retired"` AlreadyRetired bool `json:"already_retired"`
Cascade bool `json:"cascade"` Cascade bool `json:"cascade"`
Counts domain.AgentDependencyCounts `json:"counts"` Counts domain.AgentDependencyCounts `json:"counts"`
} }
if err := json.NewDecoder(w.Body).Decode(&body); err != nil { if err := json.NewDecoder(w.Body).Decode(&body); err != nil {
t.Fatalf("decode force-cascade 200 body: %v", err) t.Fatalf("decode force-cascade 200 body: %v", err)
+1 -1
View File
@@ -1,10 +1,10 @@
package handler package handler
import ( import (
"github.com/shankar0123/certctl/internal/repository"
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/shankar0123/certctl/internal/repository"
"log/slog" "log/slog"
"net/http" "net/http"
"strconv" "strconv"
+3 -3
View File
@@ -9,14 +9,14 @@ import (
"testing" "testing"
"time" "time"
"github.com/shankar0123/certctl/internal/domain"
"github.com/shankar0123/certctl/internal/api/middleware" "github.com/shankar0123/certctl/internal/api/middleware"
"github.com/shankar0123/certctl/internal/domain"
) )
// mockAuditService implements AuditService for testing. // mockAuditService implements AuditService for testing.
type mockAuditService struct { type mockAuditService struct {
listFunc func(page, perPage int) ([]domain.AuditEvent, int64, error) listFunc func(page, perPage int) ([]domain.AuditEvent, int64, error)
getFunc func(id string) (*domain.AuditEvent, error) getFunc func(id string) (*domain.AuditEvent, error)
} }
func (m *mockAuditService) ListAuditEvents(_ context.Context, page, perPage int) ([]domain.AuditEvent, int64, 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{ svc := &mockBulkRenewalService{
BulkRenewFn: func(ctx context.Context, criteria domain.BulkRenewalCriteria, actor string) (*domain.BulkRenewalResult, error) { BulkRenewFn: func(ctx context.Context, criteria domain.BulkRenewalCriteria, actor string) (*domain.BulkRenewalResult, error) {
return &domain.BulkRenewalResult{ return &domain.BulkRenewalResult{
TotalMatched: 3, TotalMatched: 3,
TotalEnqueued: 2, TotalEnqueued: 2,
TotalSkipped: 0, TotalSkipped: 0,
TotalFailed: 1, TotalFailed: 1,
Errors: []domain.BulkOperationError{ Errors: []domain.BulkOperationError{
{CertificateID: "mc-failed", Error: "renewal job enqueue failed: db timeout"}, {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) t.Fatalf("failed to generate key: %v", err)
} }
template := &x509.Certificate{ template := &x509.Certificate{
SerialNumber: big.NewInt(1), SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "Test CA"}, Subject: pkix.Name{CommonName: "Test CA"},
NotBefore: time.Now().Add(-1 * time.Hour), NotBefore: time.Now().Add(-1 * time.Hour),
NotAfter: time.Now().Add(24 * time.Hour), NotAfter: time.Now().Add(24 * time.Hour),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
IsCA: true, IsCA: true,
BasicConstraintsValid: true, BasicConstraintsValid: true,
} }
certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key) certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
+2 -2
View File
@@ -1,11 +1,11 @@
package handler package handler
import ( import (
"github.com/shankar0123/certctl/internal/repository"
"errors"
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"github.com/shankar0123/certctl/internal/repository"
"log/slog" "log/slog"
"net/http" "net/http"
"strings" "strings"
+6 -6
View File
@@ -59,12 +59,12 @@ func (h *HealthCheckHandler) ListHealthChecks(w http.ResponseWriter, r *http.Req
} }
filter := &repository.HealthCheckFilter{ filter := &repository.HealthCheckFilter{
Status: status, Status: status,
CertificateID: certificateID, CertificateID: certificateID,
NetworkScanTargetID: networkScanTargetID, NetworkScanTargetID: networkScanTargetID,
Enabled: enabledFilter, Enabled: enabledFilter,
Page: page, Page: page,
PerPage: perPage, PerPage: perPage,
} }
checks, total, err := h.service.List(r.Context(), filter) checks, total, err := h.service.List(r.Context(), filter)
+2 -2
View File
@@ -1,10 +1,10 @@
package handler package handler
import ( import (
"github.com/shankar0123/certctl/internal/repository"
"errors"
"context" "context"
"encoding/json" "encoding/json"
"errors"
"github.com/shankar0123/certctl/internal/repository"
"log/slog" "log/slog"
"net/http" "net/http"
"strconv" "strconv"
+1 -1
View File
@@ -1,10 +1,10 @@
package handler package handler
import ( import (
"github.com/shankar0123/certctl/internal/repository"
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/shankar0123/certctl/internal/repository"
"io" "io"
"net/http" "net/http"
"strconv" "strconv"
+2 -2
View File
@@ -1,9 +1,9 @@
package handler package handler
import ( import (
"github.com/shankar0123/certctl/internal/repository"
"errors"
"context" "context"
"errors"
"github.com/shankar0123/certctl/internal/repository"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
+2 -2
View File
@@ -1,10 +1,10 @@
package handler package handler
import ( import (
"github.com/shankar0123/certctl/internal/repository"
"errors"
"context" "context"
"encoding/json" "encoding/json"
"errors"
"github.com/shankar0123/certctl/internal/repository"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
+3 -3
View File
@@ -237,9 +237,9 @@ func TestCreateProfile_Success(t *testing.T) {
} }
body := map[string]interface{}{ body := map[string]interface{}{
"name": "New Profile", "name": "New Profile",
"max_ttl_seconds": 86400, "max_ttl_seconds": 86400,
"allowed_ekus": []string{"serverAuth"}, "allowed_ekus": []string{"serverAuth"},
} }
bodyBytes, _ := json.Marshal(body) bodyBytes, _ := json.Marshal(body)
@@ -331,7 +331,7 @@ func TestUpdateProfile_Success(t *testing.T) {
} }
body := map[string]interface{}{ body := map[string]interface{}{
"name": "Updated Profile", "name": "Updated Profile",
"max_ttl_seconds": 172800, "max_ttl_seconds": 172800,
} }
bodyBytes, _ := json.Marshal(body) bodyBytes, _ := json.Marshal(body)
+2 -2
View File
@@ -1,10 +1,10 @@
package handler package handler
import ( import (
"github.com/shankar0123/certctl/internal/repository"
"errors"
"context" "context"
"encoding/json" "encoding/json"
"errors"
"github.com/shankar0123/certctl/internal/repository"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
+1 -1
View File
@@ -1,10 +1,10 @@
package handler package handler
import ( import (
"github.com/shankar0123/certctl/internal/repository"
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/shankar0123/certctl/internal/repository"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@@ -26,11 +26,11 @@ import (
// MockRenewalPolicyService is a mock implementation of RenewalPolicyService. // MockRenewalPolicyService is a mock implementation of RenewalPolicyService.
type MockRenewalPolicyService struct { type MockRenewalPolicyService struct {
ListRenewalPoliciesFn func(page, perPage int) ([]domain.RenewalPolicy, int64, error) ListRenewalPoliciesFn func(page, perPage int) ([]domain.RenewalPolicy, int64, error)
GetRenewalPolicyFn func(id string) (*domain.RenewalPolicy, error) GetRenewalPolicyFn func(id string) (*domain.RenewalPolicy, error)
CreateRenewalPolicyFn func(rp domain.RenewalPolicy) (*domain.RenewalPolicy, error) CreateRenewalPolicyFn func(rp domain.RenewalPolicy) (*domain.RenewalPolicy, error)
UpdateRenewalPolicyFn func(id string, rp domain.RenewalPolicy) (*domain.RenewalPolicy, error) UpdateRenewalPolicyFn func(id string, rp domain.RenewalPolicy) (*domain.RenewalPolicy, error)
DeleteRenewalPolicyFn func(id string) error DeleteRenewalPolicyFn func(id string) error
} }
func (m *MockRenewalPolicyService) ListRenewalPolicies(_ context.Context, page, perPage int) ([]domain.RenewalPolicy, int64, 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{}{ body := map[string]interface{}{
"name": "New Policy", "name": "New Policy",
"renewal_window_days": 30, "renewal_window_days": 30,
"max_retries": 3, "max_retries": 3,
"retry_interval_seconds": 3600, "retry_interval_seconds": 3600,
"auto_renew": true, "auto_renew": true,
} }
bodyBytes, _ := json.Marshal(body) bodyBytes, _ := json.Marshal(body)
@@ -221,8 +221,8 @@ func TestCreateRenewalPolicy_Success(t *testing.T) {
func TestCreateRenewalPolicy_MissingName(t *testing.T) { func TestCreateRenewalPolicy_MissingName(t *testing.T) {
body := map[string]interface{}{ body := map[string]interface{}{
"renewal_window_days": 30, "renewal_window_days": 30,
"max_retries": 3, "max_retries": 3,
"retry_interval_seconds": 3600, "retry_interval_seconds": 3600,
} }
bodyBytes, _ := json.Marshal(body) bodyBytes, _ := json.Marshal(body)
@@ -261,9 +261,9 @@ func TestCreateRenewalPolicy_DuplicateName(t *testing.T) {
} }
body := map[string]interface{}{ body := map[string]interface{}{
"name": "Duplicate", "name": "Duplicate",
"renewal_window_days": 30, "renewal_window_days": 30,
"max_retries": 3, "max_retries": 3,
"retry_interval_seconds": 3600, "retry_interval_seconds": 3600,
} }
bodyBytes, _ := json.Marshal(body) bodyBytes, _ := json.Marshal(body)
@@ -308,11 +308,11 @@ func TestUpdateRenewalPolicy_Success(t *testing.T) {
} }
body := map[string]interface{}{ body := map[string]interface{}{
"name": "Updated Policy", "name": "Updated Policy",
"renewal_window_days": 45, "renewal_window_days": 45,
"max_retries": 5, "max_retries": 5,
"retry_interval_seconds": 1800, "retry_interval_seconds": 1800,
"auto_renew": true, "auto_renew": true,
} }
bodyBytes, _ := json.Marshal(body) bodyBytes, _ := json.Marshal(body)
@@ -336,9 +336,9 @@ func TestUpdateRenewalPolicy_NotFound(t *testing.T) {
} }
body := map[string]interface{}{ body := map[string]interface{}{
"name": "Updated", "name": "Updated",
"renewal_window_days": 30, "renewal_window_days": 30,
"max_retries": 3, "max_retries": 3,
"retry_interval_seconds": 3600, "retry_interval_seconds": 3600,
} }
bodyBytes, _ := json.Marshal(body) bodyBytes, _ := json.Marshal(body)
+6 -6
View File
@@ -346,8 +346,8 @@ func TestCursorPagedResponse_EmptyNextCursor(t *testing.T) {
func TestFilterFields_SingleObject(t *testing.T) { func TestFilterFields_SingleObject(t *testing.T) {
data := map[string]interface{}{ data := map[string]interface{}{
"id": "cert-123", "id": "cert-123",
"name": "My Cert", "name": "My Cert",
"expiry": "2025-01-01", "expiry": "2025-01-01",
"status": "active", "status": "active",
} }
@@ -379,8 +379,8 @@ func TestFilterFields_SingleObject(t *testing.T) {
func TestFilterFields_EmptyFields(t *testing.T) { func TestFilterFields_EmptyFields(t *testing.T) {
// Empty fields list should return data unchanged // Empty fields list should return data unchanged
data := map[string]interface{}{ data := map[string]interface{}{
"id": "cert-123", "id": "cert-123",
"name": "My Cert", "name": "My Cert",
} }
result := filterFields(data, []string{}) result := filterFields(data, []string{})
@@ -398,8 +398,8 @@ func TestFilterFields_EmptyFields(t *testing.T) {
func TestFilterFields_NoMatchingFields(t *testing.T) { func TestFilterFields_NoMatchingFields(t *testing.T) {
data := map[string]interface{}{ data := map[string]interface{}{
"id": "cert-123", "id": "cert-123",
"name": "My Cert", "name": "My Cert",
} }
result := filterFields(data, []string{"nonexistent", "also-not-there"}) 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. // TestValidatePolicyType_ValidTypes tests valid policy types.
func TestValidatePolicyType_ValidTypes(t *testing.T) { func TestValidatePolicyType_ValidTypes(t *testing.T) {
validTypes := []struct { validTypes := []struct {
name string name string
ptype interface{} 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.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{ json.NewEncoder(w).Encode(map[string]interface{}{
"job_id": jobID, "job_id": jobID,
"verified": req.Verified, "verified": req.Verified,
"verified_at": result.VerifiedAt, "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. // 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 { func (a *AuditServiceAdapter) RecordAPICall(ctx context.Context, method, path, actor string, bodyHash string, status int, latencyMs int64) error {
details := map[string]interface{}{ details := map[string]interface{}{
"method": method, "method": method,
"path": path, "path": path,
"body_hash": bodyHash, "body_hash": bodyHash,
"status": status, "status": status,
"latency_ms": latencyMs, "latency_ms": latencyMs,
} }
action := fmt.Sprintf("api_%s", strings.ToLower(method)) action := fmt.Sprintf("api_%s", strings.ToLower(method))
+4 -4
View File
@@ -15,10 +15,10 @@ import (
// mockAuditRecorder captures RecordAPICall invocations for testing. // mockAuditRecorder captures RecordAPICall invocations for testing.
type mockAuditRecorder struct { type mockAuditRecorder struct {
mu sync.Mutex mu sync.Mutex
calls []auditCall calls []auditCall
err error // if non-nil, RecordAPICall returns this err error // if non-nil, RecordAPICall returns this
block chan struct{} // if non-nil, RecordAPICall blocks on receive before returning block chan struct{} // if non-nil, RecordAPICall blocks on receive before returning
} }
type auditCall struct { type auditCall struct {
+6 -6
View File
@@ -40,9 +40,9 @@ func TestNewCORS_NilOriginsDeniesAll(t *testing.T) {
// TestNewCORS_M013_ContractDocumentedInOrder pins the documented dispatch // TestNewCORS_M013_ContractDocumentedInOrder pins the documented dispatch
// order so a refactor cannot silently invert the cases: // order so a refactor cannot silently invert the cases:
// //
// 1. len(AllowedOrigins) == 0 → deny (no CORS headers) // 1. len(AllowedOrigins) == 0 → deny (no CORS headers)
// 2. AllowedOrigins == ["*"] → allow all (Access-Control-Allow-Origin: *) // 2. AllowedOrigins == ["*"] → allow all (Access-Control-Allow-Origin: *)
// 3. else → exact-match allowlist with Vary: Origin // 3. else → exact-match allowlist with Vary: Origin
// //
// If a refactor accidentally falls through to the allow-all branch when // If a refactor accidentally falls through to the allow-all branch when
// AllowedOrigins is empty, this test fails on case 1. // AllowedOrigins is empty, this test fails on case 1.
@@ -293,9 +293,9 @@ func TestNewCORS_MultipleOrigins(t *testing.T) {
})) }))
tests := []struct { tests := []struct {
origin string origin string
shouldAllow bool shouldAllow bool
description string description string
}{ }{
{"https://app.example.com", true, "first origin in list"}, {"https://app.example.com", true, "first origin in list"},
{"https://admin.example.com", true, "second 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{ limiter := &keyedRateLimiter{
ipRate: cfg.RPS, ipRate: cfg.RPS,
ipBurst: float64(cfg.BurstSize), ipBurst: float64(cfg.BurstSize),
userRate: perUserRPS, userRate: perUserRPS,
userBurst: perUserBurst, userBurst: perUserBurst,
buckets: make(map[string]*tokenBucket), buckets: make(map[string]*tokenBucket),
} }
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
+4 -3
View File
@@ -990,9 +990,9 @@ func TestGetLogLevel_AllLevels(t *testing.T) {
{"info", slog.LevelInfo}, {"info", slog.LevelInfo},
{"warn", slog.LevelWarn}, {"warn", slog.LevelWarn},
{"error", slog.LevelError}, {"error", slog.LevelError},
{"unknown", slog.LevelInfo}, // default fallback {"unknown", slog.LevelInfo}, // default fallback
{"", slog.LevelInfo}, // empty string {"", slog.LevelInfo}, // empty string
{"DEBUG", slog.LevelInfo}, // case-sensitive, no match → default {"DEBUG", slog.LevelInfo}, // case-sensitive, no match → default
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.level, func(t *testing.T) { t.Run(tt.level, func(t *testing.T) {
@@ -1091,6 +1091,7 @@ func TestGetEnvBool(t *testing.T) {
}) })
} }
} }
// I-003: Job timeout reaper configuration tests // I-003: Job timeout reaper configuration tests
func TestConfig_Scheduler_JobTimeoutDefaults(t *testing.T) { func TestConfig_Scheduler_JobTimeoutDefaults(t *testing.T) {
clearCertctlEnv(t) clearCertctlEnv(t)
@@ -20,10 +20,10 @@ import (
// mockSMClient is a mock implementation of SMClient for testing. // mockSMClient is a mock implementation of SMClient for testing.
type mockSMClient struct { type mockSMClient struct {
secrets map[string]string // secret name -> secret value secrets map[string]string // secret name -> secret value
secretMetadata map[string]SecretMetadata // secret name -> metadata secretMetadata map[string]SecretMetadata // secret name -> metadata
listError error listError error
getErrors map[string]error // secret name -> error getErrors map[string]error // secret name -> error
} }
func newMockSMClient() *mockSMClient { 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) 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 { Value []struct {
ID string `json:"id"` ID string `json:"id"`
Attributes struct { Attributes struct {
Enabled int64 `json:"enabled"` Enabled int64 `json:"enabled"`
Created int64 `json:"created"` Created int64 `json:"created"`
Updated int64 `json:"updated"` Updated int64 `json:"updated"`
Exp int64 `json:"exp"` Exp int64 `json:"exp"`
} `json:"attributes,omitempty"` } `json:"attributes,omitempty"`
Tags map[string]string `json:"tags,omitempty"` Tags map[string]string `json:"tags,omitempty"`
} `json:"value"` } `json:"value"`
@@ -84,10 +84,10 @@ type certificateBundle struct {
ID string `json:"id"` ID string `json:"id"`
CER string `json:"cer"` CER string `json:"cer"`
Attributes struct { Attributes struct {
Enabled int64 `json:"enabled"` Enabled int64 `json:"enabled"`
Created int64 `json:"created"` Created int64 `json:"created"`
Updated int64 `json:"updated"` Updated int64 `json:"updated"`
Exp int64 `json:"exp"` Exp int64 `json:"exp"`
} `json:"attributes,omitempty"` } `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) s.logger.Info("starting Azure Key Vault discovery", "vault_url", s.config.VaultURL)
report := &domain.DiscoveryReport{ report := &domain.DiscoveryReport{
AgentID: "cloud-azure-kv", AgentID: "cloud-azure-kv",
Directories: []string{fmt.Sprintf("azure-kv://%s/", s.config.VaultURL)}, Directories: []string{fmt.Sprintf("azure-kv://%s/", s.config.VaultURL)},
Certificates: []domain.DiscoveredCertEntry{}, Certificates: []domain.DiscoveredCertEntry{},
Errors: []string{}, Errors: []string{},
} }
startTime := time.Now() startTime := time.Now()
@@ -82,7 +82,7 @@ func generateTestCertificate(cn string, expire time.Duration) (*x509.Certificate
ExtKeyUsage: []x509.ExtKeyUsage{ ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageServerAuth,
}, },
DNSNames: []string{"example.com", "*.example.com"}, DNSNames: []string{"example.com", "*.example.com"},
EmailAddresses: []string{"test@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()) c := New(nil, testLogger())
cfg, _ := json.Marshal(map[string]string{ cfg, _ := json.Marshal(map[string]string{
"directory_url": srv.URL, "directory_url": srv.URL,
"email": "test@example.com", "email": "test@example.com",
"challenge_type": "dns-persist-01", "challenge_type": "dns-persist-01",
"dns_present_script": "/tmp/script.sh", "dns_present_script": "/tmp/script.sh",
// Missing dns_persist_issuer_domain // Missing dns_persist_issuer_domain
}) })
@@ -870,9 +870,9 @@ func TestValidateConfig_DNS01WithPresentScript(t *testing.T) {
c := New(nil, testLogger()) c := New(nil, testLogger())
cfg, _ := json.Marshal(map[string]string{ cfg, _ := json.Marshal(map[string]string{
"directory_url": srv.URL, "directory_url": srv.URL,
"email": "test@example.com", "email": "test@example.com",
"challenge_type": "dns-01", "challenge_type": "dns-01",
"dns_present_script": "/bin/sh", "dns_present_script": "/bin/sh",
"dns_cleanup_script": "/bin/sh", "dns_cleanup_script": "/bin/sh",
}) })
@@ -897,10 +897,10 @@ func TestValidateConfig_DNSPersist01WithAllFields(t *testing.T) {
c := New(nil, testLogger()) c := New(nil, testLogger())
cfg, _ := json.Marshal(map[string]string{ cfg, _ := json.Marshal(map[string]string{
"directory_url": srv.URL, "directory_url": srv.URL,
"email": "test@example.com", "email": "test@example.com",
"challenge_type": "dns-persist-01", "challenge_type": "dns-persist-01",
"dns_present_script": "/bin/sh", "dns_present_script": "/bin/sh",
"dns_persist_issuer_domain": "letsencrypt.org", "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 { if r.URL.Path == "/directory" && r.Method == http.MethodGet {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{ json.NewEncoder(w).Encode(map[string]string{
"newOrder": "/acme/new-order", "newOrder": "/acme/new-order",
"newAccount": "/acme/new-account", "newAccount": "/acme/new-account",
}) })
return return
} }
@@ -115,8 +115,8 @@ func TestGetRenewalInfo_ServerError(t *testing.T) {
if r.URL.Path == "/directory" && r.Method == http.MethodGet { if r.URL.Path == "/directory" && r.Method == http.MethodGet {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{ json.NewEncoder(w).Encode(map[string]string{
"newOrder": "/acme/new-order", "newOrder": "/acme/new-order",
"newAccount": "/acme/new-account", "newAccount": "/acme/new-account",
}) })
return return
} }
@@ -106,7 +106,7 @@ type pebbleMockServer struct {
idSeq int64 idSeq int64
// Behavior toggles for failure-mode tests. // Behavior toggles for failure-mode tests.
failNewAccount bool 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" 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 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") 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 { type mockDNSSolver struct {
mu sync.Mutex mu sync.Mutex
presented map[string]string // domain → keyAuth (or recordValue) presented map[string]string // domain → keyAuth (or recordValue)
cleanedUp map[string]bool cleanedUp map[string]bool
presentErr error presentErr error
cleanErr error cleanErr error
presentDelay time.Duration presentDelay time.Duration
} }
func newMockDNSSolver() *mockDNSSolver { func newMockDNSSolver() *mockDNSSolver {
@@ -249,4 +249,3 @@ func signJWS(key *ecdsa.PrivateKey, kid, nonce, targetURL string, payload []byte
return json.Marshal(jws) return json.Marshal(jws)
} }
@@ -105,9 +105,9 @@ type GetCertificateOutput struct {
// RevokeCertificateInput represents the request to revoke a certificate. // RevokeCertificateInput represents the request to revoke a certificate.
type RevokeCertificateInput struct { type RevokeCertificateInput struct {
CAArn string CAArn string
CertificateSerial string CertificateSerial string
RevocationReason string RevocationReason string
} }
// GetCACertificateInput represents the request to retrieve the CA certificate. // GetCACertificateInput represents the request to retrieve the CA certificate.
@@ -395,14 +395,14 @@ func mapRevocationReason(reason *string) string {
} }
reasonMap := map[string]string{ reasonMap := map[string]string{
"unspecified": "UNSPECIFIED", "unspecified": "UNSPECIFIED",
"keyCompromise": "KEY_COMPROMISE", "keyCompromise": "KEY_COMPROMISE",
"caCompromise": "CERTIFICATE_AUTHORITY_COMPROMISE", "caCompromise": "CERTIFICATE_AUTHORITY_COMPROMISE",
"affiliationChanged": "AFFILIATION_CHANGED", "affiliationChanged": "AFFILIATION_CHANGED",
"superseded": "SUPERSEDED", "superseded": "SUPERSEDED",
"cessationOfOperation": "CESSATION_OF_OPERATION", "cessationOfOperation": "CESSATION_OF_OPERATION",
"certificateHold": "CERTIFICATE_HOLD", "certificateHold": "CERTIFICATE_HOLD",
"privilegeWithdrawn": "PRIVILEGE_WITHDRAWN", "privilegeWithdrawn": "PRIVILEGE_WITHDRAWN",
} }
if mapped, ok := reasonMap[*reason]; ok { if mapped, ok := reasonMap[*reason]; ok {
@@ -22,15 +22,15 @@ import (
// mockACMPCAClient implements the ACMPCAClient interface for testing. // mockACMPCAClient implements the ACMPCAClient interface for testing.
type mockACMPCAClient struct { type mockACMPCAClient struct {
issueCertificateErr error issueCertificateErr error
getCertificateErr error getCertificateErr error
revokeCertificateErr error revokeCertificateErr error
getCACertificateErr error getCACertificateErr error
issuedCertPEM string issuedCertPEM string
issuedChainPEM string issuedChainPEM string
caCertPEM string caCertPEM string
caCertChainPEM string caCertChainPEM string
lastIssueCertificateInput *awsacmpca.IssueCertificateInput lastIssueCertificateInput *awsacmpca.IssueCertificateInput
lastRevokeCertificateInput *awsacmpca.RevokeCertificateInput 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. // orderRequest is the JSON body for DigiCert certificate order submission.
type orderRequest struct { type orderRequest struct {
Certificate orderCert `json:"certificate"` Certificate orderCert `json:"certificate"`
Organization orderOrg `json:"organization"` Organization orderOrg `json:"organization"`
ValidityYears int `json:"validity_years"` ValidityYears int `json:"validity_years"`
} }
type orderCert struct { 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) csrBase64 := base64.StdEncoding.EncodeToString(csrBlock.Bytes)
enrollReq := map[string]interface{}{ enrollReq := map[string]interface{}{
"certificate_request": csrBase64, "certificate_request": csrBase64,
"certificate_authority_name": c.config.CAName, "certificate_authority_name": c.config.CAName,
} }
@@ -175,7 +175,7 @@ func TestEJBCAConnector(t *testing.T) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
respData := map[string]interface{}{ respData := map[string]interface{}{
"certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes), "certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes),
"certificate_chain": []string{base64.StdEncoding.EncodeToString(chainBlock.Bytes)}, "certificate_chain": []string{base64.StdEncoding.EncodeToString(chainBlock.Bytes)},
"serial_number": "123456", "serial_number": "123456",
} }
@@ -242,7 +242,7 @@ func TestEJBCAConnector(t *testing.T) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
respData := map[string]interface{}{ respData := map[string]interface{}{
"certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes), "certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes),
"certificate_chain": []string{}, "certificate_chain": []string{},
"serial_number": "789012", "serial_number": "789012",
} }
@@ -314,7 +314,7 @@ func TestEJBCAConnector(t *testing.T) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
respData := map[string]interface{}{ respData := map[string]interface{}{
"certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes), "certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes),
"certificate_chain": []string{}, "certificate_chain": []string{},
"serial_number": "123456", "serial_number": "123456",
} }
@@ -356,7 +356,7 @@ func TestEJBCAConnector(t *testing.T) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
respData := map[string]interface{}{ respData := map[string]interface{}{
"certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes), "certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes),
"certificate_chain": []string{}, "certificate_chain": []string{},
"serial_number": "654321", "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. // enrollmentRequest is the JSON body for Entrust enrollment submission.
type enrollmentRequest struct { type enrollmentRequest struct {
CSR string `json:"csr"` CSR string `json:"csr"`
ProfileId string `json:"profileId,omitempty"` ProfileId string `json:"profileId,omitempty"`
SubjectAltNames []san `json:"subjectAltNames,omitempty"` SubjectAltNames []san `json:"subjectAltNames,omitempty"`
CertificateAuthority string `json:"certificateAuthority,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. // certificateRequest is the JSON body for GlobalSign certificate order submission.
type certificateRequest struct { type certificateRequest struct {
CSR string `json:"csr"` CSR string `json:"csr"`
SubjectDN subjectDNRequest `json:"subject_dn"` SubjectDN subjectDNRequest `json:"subject_dn"`
SAN sanRequest `json:"san,omitempty"` SAN sanRequest `json:"san,omitempty"`
} }
type subjectDNRequest struct { type subjectDNRequest struct {
@@ -93,10 +93,10 @@ type Connector struct {
httpClient *http.Client httpClient *http.Client
// OAuth2 token caching // OAuth2 token caching
mu sync.Mutex mu sync.Mutex
tokenCache *cachedToken tokenCache *cachedToken
saKey *serviceAccountKey saKey *serviceAccountKey
rsaKey *rsa.PrivateKey rsaKey *rsa.PrivateKey
} }
// New creates a new Google CAS connector with the given configuration and logger. // 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": ...}, ...] // Format: [{"serial": "...", "revoked_at": "...", "reason_code": ...}, ...]
func (c *Connector) marshalRevokedSerials(revokedCerts []issuer.RevokedCertEntry) ([]byte, error) { func (c *Connector) marshalRevokedSerials(revokedCerts []issuer.RevokedCertEntry) ([]byte, error) {
type RevokedEntry struct { type RevokedEntry struct {
Serial string `json:"serial"` Serial string `json:"serial"`
RevokedAt string `json:"revoked_at"` RevokedAt string `json:"revoked_at"`
ReasonCode int `json:"reason_code"` ReasonCode int `json:"reason_code"`
} }
entries := make([]RevokedEntry, len(revokedCerts)) entries := make([]RevokedEntry, len(revokedCerts))
@@ -643,7 +643,7 @@ func generateTestCSR(cn string) (*x509.CertificateRequest, string, error) {
} }
csrTemplate := x509.CertificateRequest{ csrTemplate := x509.CertificateRequest{
Subject: subject, Subject: subject,
DNSNames: []string{cn, "www." + cn}, 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. // signResponse is the JSON response from the step-ca /sign endpoint.
type signResponse struct { type signResponse struct {
ServerPEM certificateChain `json:"serverPEM,omitempty"` ServerPEM certificateChain `json:"serverPEM,omitempty"`
CaPEM certificateChain `json:"caPEM,omitempty"` CaPEM certificateChain `json:"caPEM,omitempty"`
CertChainPEM []certBlock `json:"certChainPEM,omitempty"` CertChainPEM []certBlock `json:"certChainPEM,omitempty"`
} }
type certificateChain struct { 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) // step-ca expects: aud = <ca-url>/1.0/sign (the sign endpoint audience)
claims := map[string]interface{}{ claims := map[string]interface{}{
"sub": subject, "sub": subject,
"iss": c.config.ProvisionerName, "iss": c.config.ProvisionerName,
"aud": c.config.CAURL + "/1.0/sign", "aud": c.config.CAURL + "/1.0/sign",
"nbf": now.Unix(), "nbf": now.Unix(),
"iat": now.Unix(), "iat": now.Unix(),
"exp": now.Add(5 * time.Minute).Unix(), "exp": now.Add(5 * time.Minute).Unix(),
"jti": generateJTI(), "jti": generateJTI(),
"sha": kid, // step-ca uses this to look up the provisioner by key fingerprint "sha": kid, // step-ca uses this to look up the provisioner by key fingerprint
} }
if len(sans) > 0 { 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 // Test with a provisioner key path that can't be read
config := stepca.Config{ config := stepca.Config{
CAURL: srv.URL, CAURL: srv.URL,
ProvisionerName: "test-provisioner", ProvisionerName: "test-provisioner",
ProvisionerKeyPath: "/root/.ssh/no_such_key", // Permission denied or doesn't exist ProvisionerKeyPath: "/root/.ssh/no_such_key", // Permission denied or doesn't exist
ProvisionerPassword: "password", ProvisionerPassword: "password",
} }
@@ -1770,4 +1770,3 @@ func TestIntegration_FullLifecycle(t *testing.T) {
t.Errorf("Expected status 'completed', got '%s'", status.Status) 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. // vaultResponse is the standard Vault API response wrapper.
type vaultResponse struct { type vaultResponse struct {
Data json.RawMessage `json:"data"` Data json.RawMessage `json:"data"`
Errors []string `json:"errors,omitempty"` Errors []string `json:"errors,omitempty"`
Warnings []string `json:"warnings,omitempty"` Warnings []string `json:"warnings,omitempty"`
} }
// signData holds the data returned from the /sign endpoint. // 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) { func TestEmail_ValidateConfig_MissingFromAddress(t *testing.T) {
cfg := &Config{ cfg := &Config{
SMTPHost: "smtp.example.com", SMTPHost: "smtp.example.com",
SMTPPort: 587, SMTPPort: 587,
Username: "user", Username: "user",
Password: "pass", Password: "pass",
UseTLS: true, UseTLS: true,
} }
rawConfig, _ := json.Marshal(cfg) 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) // to a directory that Envoy watches via its SDS (Secret Discovery Service)
// file-based configuration or static filename references in the bootstrap config. // file-based configuration or static filename references in the bootstrap config.
type Config struct { type Config struct {
CertDir string `json:"cert_dir"` // Directory where Envoy watches for cert files (required) CertDir string `json:"cert_dir"` // Directory where Envoy watches for cert files (required)
CertFilename string `json:"cert_filename"` // Filename for certificate (default: cert.pem) CertFilename string `json:"cert_filename"` // Filename for certificate (default: cert.pem)
KeyFilename string `json:"key_filename"` // Filename for private key (default: key.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) 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 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. // 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. // SDSTLSCertificate represents a single SDS tls_certificate entry.
type SDSTLSCertificate struct { type SDSTLSCertificate struct {
Type string `json:"@type"` Type string `json:"@type"`
Name string `json:"name"` Name string `json:"name"`
TLSCertificate TLSCertificate `json:"tls_certificate"` TLSCertificate TLSCertificate `json:"tls_certificate"`
} }
// TLSCertificate contains the file paths for cert and key in Envoy's SDS format. // 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", Message: "Certificate uploaded and SSL profile updated via iControl REST",
DeployedAt: time.Now(), DeployedAt: time.Now(),
Metadata: map[string]string{ Metadata: map[string]string{
"host": c.config.Host, "host": c.config.Host,
"partition": c.config.Partition, "partition": c.config.Partition,
"ssl_profile": c.config.SSLProfile, "ssl_profile": c.config.SSLProfile,
"cert_object_name": certName, "cert_object_name": certName,
"key_object_name": keyName, "key_object_name": keyName,
"chain_object_name": chainName, "chain_object_name": chainName,
"duration_ms": fmt.Sprintf("%d", deploymentDuration.Milliseconds()), "duration_ms": fmt.Sprintf("%d", deploymentDuration.Milliseconds()),
}, },
}, nil }, 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), Message: fmt.Sprintf("SSL profile %q has cert %q configured", c.config.SSLProfile, profile.Cert),
ValidatedAt: time.Now(), ValidatedAt: time.Now(),
Metadata: map[string]string{ Metadata: map[string]string{
"host": c.config.Host, "host": c.config.Host,
"ssl_profile": c.config.SSLProfile, "ssl_profile": c.config.SSLProfile,
"current_cert": profile.Cert, "current_cert": profile.Cert,
"current_key": profile.Key, "current_key": profile.Key,
"current_chain": profile.Chain, "current_chain": profile.Chain,
"duration_ms": fmt.Sprintf("%d", validationDuration.Milliseconds()), "duration_ms": fmt.Sprintf("%d", validationDuration.Milliseconds()),
}, },
}, nil }, nil
} }
+8 -8
View File
@@ -25,14 +25,14 @@ type mockF5Client struct {
calls []mockCall calls []mockCall
// Configurable responses per method // Configurable responses per method
authenticateErr error authenticateErr error
authenticateCount int // tracks number of Authenticate calls authenticateCount int // tracks number of Authenticate calls
uploadFileErr error uploadFileErr error
uploadFileErrOn string // only error when filename contains this substring uploadFileErrOn string // only error when filename contains this substring
installCertErr error installCertErr error
installCertErrOn string installCertErrOn string
installKeyErr error installKeyErr error
createTransactionID string createTransactionID string
createTransactionErr error createTransactionErr error
commitTransactionErr error commitTransactionErr error
updateSSLProfileErr error updateSSLProfileErr error
+3 -3
View File
@@ -59,9 +59,9 @@ func newWinRMExecutor(cfg *WinRMConfig) (*winrmExecutor, error) {
port, port,
cfg.UseHTTPS, cfg.UseHTTPS,
cfg.Insecure, cfg.Insecure,
nil, // CA cert nil, // CA cert
nil, // Client cert nil, // Client cert
nil, // Client key nil, // Client key
timeout, 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), Message: fmt.Sprintf("Certificate imported to %s (alias: %s, thumbprint: %s)", c.config.KeystorePath, c.config.Alias, thumbprint),
DeployedAt: time.Now(), DeployedAt: time.Now(),
Metadata: map[string]string{ Metadata: map[string]string{
"thumbprint": thumbprint, "thumbprint": thumbprint,
"alias": c.config.Alias, "alias": c.config.Alias,
"keystore_type": c.config.KeystoreType, "keystore_type": c.config.KeystoreType,
"keystore_path": c.config.KeystorePath, "keystore_path": c.config.KeystorePath,
}, },
}, nil }, nil
} }
@@ -240,7 +240,7 @@ func TestDeployCertificate_Success(t *testing.T) {
mock := &mockExecutor{ mock := &mockExecutor{
responses: []mockResponse{ 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 {Output: "Import command completed", Err: nil}, // keytool -importkeystore
}, },
} }
@@ -355,8 +355,8 @@ func TestDeployCertificate_WithReload(t *testing.T) {
mock := &mockExecutor{ mock := &mockExecutor{
responses: []mockResponse{ responses: []mockResponse{
// No existing keystore → delete skipped → import is call 0, reload is call 1 // No existing keystore → delete skipped → import is call 0, reload is call 1
{Output: "Imported", Err: nil}, // import {Output: "Imported", Err: nil}, // import
{Output: "restarted", Err: nil}, // reload {Output: "restarted", Err: nil}, // reload
}, },
} }
c := NewWithExecutor(&Config{ c := NewWithExecutor(&Config{
@@ -391,8 +391,8 @@ func TestDeployCertificate_ReloadFailed_NonFatal(t *testing.T) {
mock := &mockExecutor{ mock := &mockExecutor{
responses: []mockResponse{ responses: []mockResponse{
{Output: "", Err: nil}, // delete {Output: "", Err: nil}, // delete
{Output: "Imported", Err: nil}, // import {Output: "Imported", Err: nil}, // import
{Output: "Failed to restart", Err: fmt.Errorf("exit 1")}, // reload fails {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 // Supports in-cluster auth by default (ServiceAccount token auto-mounted) or
// out-of-cluster auth via kubeconfig file. // out-of-cluster auth via kubeconfig file.
type Config struct { type Config struct {
Namespace string `json:"namespace"` // Required. Kubernetes namespace. Namespace string `json:"namespace"` // Required. Kubernetes namespace.
SecretName string `json:"secret_name"` // Required. Name of the kubernetes.io/tls Secret. 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. 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. 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) { func TestValidateConfig_Success_MinimalConfig(t *testing.T) {
cfg := map[string]interface{}{ cfg := map[string]interface{}{
"namespace": "default", "namespace": "default",
"secret_name": "my-cert", "secret_name": "my-cert",
} }
@@ -644,4 +644,3 @@ func contains(s, substr string) bool {
} }
return false return false
} }
+3 -3
View File
@@ -411,9 +411,9 @@ func (c *realSSHClient) Connect(ctx context.Context) error {
} }
sshConfig := &ssh.ClientConfig{ sshConfig := &ssh.ClientConfig{
User: c.config.User, User: c.config.User,
Auth: authMethods, Auth: authMethods,
Timeout: time.Duration(c.config.Timeout) * time.Second, Timeout: time.Duration(c.config.Timeout) * time.Second,
// InsecureIgnoreHostKey is used intentionally: certctl deploys to known // InsecureIgnoreHostKey is used intentionally: certctl deploys to known
// infrastructure (the operator explicitly configures each target host). // infrastructure (the operator explicitly configures each target host).
// This is the same security rationale as network scanner's InsecureSkipVerify // This is the same security rationale as network scanner's InsecureSkipVerify
@@ -42,15 +42,15 @@ type fakeSSHServer struct {
user string user string
password string password string
wg sync.WaitGroup wg sync.WaitGroup
mu sync.Mutex mu sync.Mutex
closed bool closed bool
// Optional behaviour toggles for failure-mode tests. // Optional behaviour toggles for failure-mode tests.
rejectAuth bool // reject all auth attempts (auth failure path) rejectAuth bool // reject all auth attempts (auth failure path)
dropOnHandshake bool // close conn before SSH NewServerConn returns (handshake failure) dropOnHandshake bool // close conn before SSH NewServerConn returns (handshake failure)
failExec bool // exec sessions return non-zero exit (Execute error path) failExec bool // exec sessions return non-zero exit (Execute error path)
failSFTP bool // refuse sftp subsystem (SFTP failure path) failSFTP bool // refuse sftp subsystem (SFTP failure path)
} }
// startFakeSSHServer binds a fresh server on a random local port and returns // 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. // Ensure Connector implements target.Connector.
var _ target.Connector = (*Connector)(nil) var _ target.Connector = (*Connector)(nil)
@@ -26,10 +26,10 @@ func testLogger() *slog.Logger {
// mockExecutor records PowerShell scripts and returns configurable responses. // mockExecutor records PowerShell scripts and returns configurable responses.
type mockExecutor struct { type mockExecutor struct {
scripts []string scripts []string
responses []string responses []string
errors []error errors []error
callIndex int callIndex int
} }
func (m *mockExecutor) Execute(ctx context.Context, script string) (string, error) { 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) { func TestCertificateStatus_Constants(t *testing.T) {
tests := map[string]CertificateStatus{ tests := map[string]CertificateStatus{
"Pending": CertificateStatusPending, "Pending": CertificateStatusPending,
"Active": CertificateStatusActive, "Active": CertificateStatusActive,
"Expiring": CertificateStatusExpiring, "Expiring": CertificateStatusExpiring,
"Expired": CertificateStatusExpired, "Expired": CertificateStatusExpired,
"RenewalInProgress": CertificateStatusRenewalInProgress, "RenewalInProgress": CertificateStatusRenewalInProgress,
"Failed": CertificateStatusFailed, "Failed": CertificateStatusFailed,
"Revoked": CertificateStatusRevoked, "Revoked": CertificateStatusRevoked,
"Archived": CertificateStatusArchived, "Archived": CertificateStatusArchived,
} }
for expected, got := range tests { for expected, got := range tests {
if string(got) != expected { if string(got) != expected {
+29 -29
View File
@@ -11,7 +11,7 @@ type Issuer struct {
Name string `json:"name"` Name string `json:"name"`
Type IssuerType `json:"type"` Type IssuerType `json:"type"`
Config json.RawMessage `json:"config"` 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"` Enabled bool `json:"enabled"`
LastTestedAt *time.Time `json:"last_tested_at,omitempty"` LastTestedAt *time.Time `json:"last_tested_at,omitempty"`
TestStatus string `json:"test_status,omitempty"` TestStatus string `json:"test_status,omitempty"`
@@ -27,13 +27,13 @@ type DeploymentTarget struct {
Type TargetType `json:"type"` Type TargetType `json:"type"`
AgentID string `json:"agent_id"` AgentID string `json:"agent_id"`
Config json.RawMessage `json:"config"` 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"` Enabled bool `json:"enabled"`
LastTestedAt *time.Time `json:"last_tested_at,omitempty"` LastTestedAt *time.Time `json:"last_tested_at,omitempty"`
TestStatus string `json:"test_status,omitempty"` TestStatus string `json:"test_status,omitempty"`
Source string `json:"source,omitempty"` Source string `json:"source,omitempty"`
RetiredAt *time.Time `json:"retired_at,omitempty"` // I-004: soft-retirement timestamp (nil = active) 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 RetiredReason *string `json:"retired_reason,omitempty"` // I-004: reason captured at cascade retirement
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_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 // docs/architecture.md ER diagram (which documents DB shape, not API
// shape) and coverage-gap-audit-2026-04-24-v5/unified-audit.md // shape) and coverage-gap-audit-2026-04-24-v5/unified-audit.md
// cat-s5-apikey_leak for the full closure rationale. // cat-s5-apikey_leak for the full closure rationale.
APIKeyHash string `json:"-"` APIKeyHash string `json:"-"`
OS string `json:"os"` OS string `json:"os"`
Architecture string `json:"architecture"` Architecture string `json:"architecture"`
IPAddress string `json:"ip_address"` IPAddress string `json:"ip_address"`
Version string `json:"version"` Version string `json:"version"`
// I-004: soft-retirement fields. An agent with RetiredAt != nil is the // I-004: soft-retirement fields. An agent with RetiredAt != nil is the
// canonical "retired" state. The Status column remains as before (Online // canonical "retired" state. The Status column remains as before (Online
// / Offline / Degraded) and is preserved at retirement time as the // / 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 // any non-zero count blocks a default retire with HTTP 409 and requires an
// explicit ?force=true&reason=... escape hatch from the operator. // explicit ?force=true&reason=... escape hatch from the operator.
type AgentDependencyCounts struct { 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 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. // HasDependencies reports whether any preflight counter is non-zero.
@@ -180,14 +180,14 @@ const (
type IssuerType string type IssuerType string
const ( const (
IssuerTypeACME IssuerType = "ACME" IssuerTypeACME IssuerType = "ACME"
IssuerTypeGenericCA IssuerType = "GenericCA" IssuerTypeGenericCA IssuerType = "GenericCA"
IssuerTypeStepCA IssuerType = "StepCA" IssuerTypeStepCA IssuerType = "StepCA"
IssuerTypeOpenSSL IssuerType = "OpenSSL" IssuerTypeOpenSSL IssuerType = "OpenSSL"
IssuerTypeVault IssuerType = "VaultPKI" IssuerTypeVault IssuerType = "VaultPKI"
IssuerTypeDigiCert IssuerType = "DigiCert" IssuerTypeDigiCert IssuerType = "DigiCert"
IssuerTypeSectigo IssuerType = "Sectigo" IssuerTypeSectigo IssuerType = "Sectigo"
IssuerTypeGoogleCAS IssuerType = "GoogleCAS" IssuerTypeGoogleCAS IssuerType = "GoogleCAS"
IssuerTypeAWSACMPCA IssuerType = "AWSACMPCA" IssuerTypeAWSACMPCA IssuerType = "AWSACMPCA"
IssuerTypeEntrust IssuerType = "Entrust" IssuerTypeEntrust IssuerType = "Entrust"
IssuerTypeGlobalSign IssuerType = "GlobalSign" IssuerTypeGlobalSign IssuerType = "GlobalSign"
@@ -198,16 +198,16 @@ const (
type TargetType string type TargetType string
const ( const (
TargetTypeNGINX TargetType = "NGINX" TargetTypeNGINX TargetType = "NGINX"
TargetTypeApache TargetType = "Apache" TargetTypeApache TargetType = "Apache"
TargetTypeHAProxy TargetType = "HAProxy" TargetTypeHAProxy TargetType = "HAProxy"
TargetTypeF5 TargetType = "F5" TargetTypeF5 TargetType = "F5"
TargetTypeIIS TargetType = "IIS" TargetTypeIIS TargetType = "IIS"
TargetTypeTraefik TargetType = "Traefik" TargetTypeTraefik TargetType = "Traefik"
TargetTypeCaddy TargetType = "Caddy" TargetTypeCaddy TargetType = "Caddy"
TargetTypeEnvoy TargetType = "Envoy" TargetTypeEnvoy TargetType = "Envoy"
TargetTypePostfix TargetType = "Postfix" TargetTypePostfix TargetType = "Postfix"
TargetTypeDovecot TargetType = "Dovecot" TargetTypeDovecot TargetType = "Dovecot"
TargetTypeSSH TargetType = "SSH" TargetTypeSSH TargetType = "SSH"
TargetTypeWinCertStore TargetType = "WinCertStore" TargetTypeWinCertStore TargetType = "WinCertStore"
TargetTypeJavaKeystore TargetType = "JavaKeystore" TargetTypeJavaKeystore TargetType = "JavaKeystore"
+35 -35
View File
@@ -24,34 +24,34 @@ func IsValidHealthStatus(s string) bool {
// EndpointHealthCheck represents a monitored TLS endpoint. // EndpointHealthCheck represents a monitored TLS endpoint.
type EndpointHealthCheck struct { type EndpointHealthCheck struct {
ID string `json:"id"` ID string `json:"id"`
Endpoint string `json:"endpoint"` Endpoint string `json:"endpoint"`
CertificateID *string `json:"certificate_id,omitempty"` CertificateID *string `json:"certificate_id,omitempty"`
NetworkScanTargetID *string `json:"network_scan_target_id,omitempty"` NetworkScanTargetID *string `json:"network_scan_target_id,omitempty"`
ExpectedFingerprint string `json:"expected_fingerprint"` ExpectedFingerprint string `json:"expected_fingerprint"`
ObservedFingerprint string `json:"observed_fingerprint"` ObservedFingerprint string `json:"observed_fingerprint"`
Status HealthStatus `json:"status"` Status HealthStatus `json:"status"`
ConsecutiveFailures int `json:"consecutive_failures"` ConsecutiveFailures int `json:"consecutive_failures"`
ResponseTimeMs int `json:"response_time_ms"` ResponseTimeMs int `json:"response_time_ms"`
TLSVersion string `json:"tls_version"` TLSVersion string `json:"tls_version"`
CipherSuite string `json:"cipher_suite"` CipherSuite string `json:"cipher_suite"`
CertSubject string `json:"cert_subject"` CertSubject string `json:"cert_subject"`
CertIssuer string `json:"cert_issuer"` CertIssuer string `json:"cert_issuer"`
CertExpiry *time.Time `json:"cert_expiry,omitempty"` CertExpiry *time.Time `json:"cert_expiry,omitempty"`
LastCheckedAt *time.Time `json:"last_checked_at,omitempty"` LastCheckedAt *time.Time `json:"last_checked_at,omitempty"`
LastSuccessAt *time.Time `json:"last_success_at,omitempty"` LastSuccessAt *time.Time `json:"last_success_at,omitempty"`
LastFailureAt *time.Time `json:"last_failure_at,omitempty"` LastFailureAt *time.Time `json:"last_failure_at,omitempty"`
LastTransitionAt *time.Time `json:"last_transition_at,omitempty"` LastTransitionAt *time.Time `json:"last_transition_at,omitempty"`
FailureReason string `json:"failure_reason"` FailureReason string `json:"failure_reason"`
DegradedThreshold int `json:"degraded_threshold"` DegradedThreshold int `json:"degraded_threshold"`
DownThreshold int `json:"down_threshold"` DownThreshold int `json:"down_threshold"`
CheckIntervalSecs int `json:"check_interval_seconds"` CheckIntervalSecs int `json:"check_interval_seconds"`
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
Acknowledged bool `json:"acknowledged"` Acknowledged bool `json:"acknowledged"`
AcknowledgedBy string `json:"acknowledged_by,omitempty"` AcknowledgedBy string `json:"acknowledged_by,omitempty"`
AcknowledgedAt *time.Time `json:"acknowledged_at,omitempty"` AcknowledgedAt *time.Time `json:"acknowledged_at,omitempty"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
} }
// TransitionStatus computes the new health status based on the probe result. // 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. // HealthHistoryEntry represents a single probe record.
type HealthHistoryEntry struct { type HealthHistoryEntry struct {
ID string `json:"id"` ID string `json:"id"`
HealthCheckID string `json:"health_check_id"` HealthCheckID string `json:"health_check_id"`
Status string `json:"status"` Status string `json:"status"`
ResponseTimeMs int `json:"response_time_ms"` ResponseTimeMs int `json:"response_time_ms"`
Fingerprint string `json:"fingerprint"` Fingerprint string `json:"fingerprint"`
FailureReason string `json:"failure_reason"` FailureReason string `json:"failure_reason"`
CheckedAt time.Time `json:"checked_at"` CheckedAt time.Time `json:"checked_at"`
} }
// HealthCheckSummary contains aggregate counts by status. // 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. // Job represents a unit of work in the certificate control plane.
type Job struct { type Job struct {
ID string `json:"id"` ID string `json:"id"`
Type JobType `json:"type"` Type JobType `json:"type"`
CertificateID string `json:"certificate_id"` CertificateID string `json:"certificate_id"`
TargetID *string `json:"target_id,omitempty"` TargetID *string `json:"target_id,omitempty"`
AgentID *string `json:"agent_id,omitempty"` AgentID *string `json:"agent_id,omitempty"`
Status JobStatus `json:"status"` Status JobStatus `json:"status"`
Attempts int `json:"attempts"` Attempts int `json:"attempts"`
MaxAttempts int `json:"max_attempts"` MaxAttempts int `json:"max_attempts"`
LastError *string `json:"last_error,omitempty"` LastError *string `json:"last_error,omitempty"`
ScheduledAt time.Time `json:"scheduled_at"` ScheduledAt time.Time `json:"scheduled_at"`
StartedAt *time.Time `json:"started_at,omitempty"` StartedAt *time.Time `json:"started_at,omitempty"`
CompletedAt *time.Time `json:"completed_at,omitempty"` CompletedAt *time.Time `json:"completed_at,omitempty"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
VerificationStatus VerificationStatus `json:"verification_status"` VerificationStatus VerificationStatus `json:"verification_status"`
VerifiedAt *time.Time `json:"verified_at,omitempty"` VerifiedAt *time.Time `json:"verified_at,omitempty"`
VerificationError *string `json:"verification_error,omitempty"` VerificationError *string `json:"verification_error,omitempty"`
VerificationFp *string `json:"verification_fingerprint,omitempty"` VerificationFp *string `json:"verification_fingerprint,omitempty"`
} }
// JobType represents the classification of work to be performed. // 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) next := time.Now().Add(2 * time.Minute)
lastErr := "connection refused" lastErr := "connection refused"
event := &NotificationEvent{ event := &NotificationEvent{
ID: "notif-retry-001", ID: "notif-retry-001",
Type: NotificationTypeExpirationWarning, Type: NotificationTypeExpirationWarning,
Channel: NotificationChannelWebhook, Channel: NotificationChannelWebhook,
Recipient: "https://hooks.example.com/certs", Recipient: "https://hooks.example.com/certs",
Message: "retry me", Message: "retry me",
Status: string(NotificationStatusFailed), Status: string(NotificationStatusFailed),
RetryCount: 3, RetryCount: 3,
NextRetryAt: &next, NextRetryAt: &next,
LastError: &lastErr, LastError: &lastErr,
} }
if event.RetryCount != 3 { if event.RetryCount != 3 {
+4 -4
View File
@@ -12,10 +12,10 @@ import "time"
type OCSPResponseCacheEntry struct { type OCSPResponseCacheEntry struct {
IssuerID string `json:"issuer_id"` IssuerID string `json:"issuer_id"`
SerialHex string `json:"serial_hex"` SerialHex string `json:"serial_hex"`
ResponseDER []byte `json:"-"` // raw DER, omitted from admin JSON to keep responses lean ResponseDER []byte `json:"-"` // raw DER, omitted from admin JSON to keep responses lean
CertStatus string `json:"cert_status"` // "good" | "revoked" | "unknown" CertStatus string `json:"cert_status"` // "good" | "revoked" | "unknown"
RevocationReason int `json:"revocation_reason,omitempty"` // only set when CertStatus == "revoked" RevocationReason int `json:"revocation_reason,omitempty"` // only set when CertStatus == "revoked"
RevokedAt time.Time `json:"revoked_at,omitempty"` // only set when CertStatus == "revoked" RevokedAt time.Time `json:"revoked_at,omitempty"` // only set when CertStatus == "revoked"
ThisUpdate time.Time `json:"this_update"` ThisUpdate time.Time `json:"this_update"`
NextUpdate time.Time `json:"next_update"` NextUpdate time.Time `json:"next_update"`
GeneratedAt time.Time `json:"generated_at"` GeneratedAt time.Time `json:"generated_at"`
+6 -6
View File
@@ -21,12 +21,12 @@ type PolicyRule struct {
type PolicyType string type PolicyType string
const ( const (
PolicyTypeAllowedIssuers PolicyType = "AllowedIssuers" PolicyTypeAllowedIssuers PolicyType = "AllowedIssuers"
PolicyTypeAllowedDomains PolicyType = "AllowedDomains" PolicyTypeAllowedDomains PolicyType = "AllowedDomains"
PolicyTypeRequiredMetadata PolicyType = "RequiredMetadata" PolicyTypeRequiredMetadata PolicyType = "RequiredMetadata"
PolicyTypeAllowedEnvironments PolicyType = "AllowedEnvironments" PolicyTypeAllowedEnvironments PolicyType = "AllowedEnvironments"
PolicyTypeRenewalLeadTime PolicyType = "RenewalLeadTime" PolicyTypeRenewalLeadTime PolicyType = "RenewalLeadTime"
PolicyTypeCertificateLifetime PolicyType = "CertificateLifetime" PolicyTypeCertificateLifetime PolicyType = "CertificateLifetime"
) )
// PolicyViolation records an instance of a certificate violating a policy rule. // 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 // Setup: Create a certificate for testing
now := time.Now() now := time.Now()
cert := &domain.ManagedCertificate{ cert := &domain.ManagedCertificate{
ID: "mc-m20-test-1", ID: "mc-m20-test-1",
Name: "M20 Test Cert", Name: "M20 Test Cert",
CommonName: "m20.example.com", CommonName: "m20.example.com",
Environment: "production", Environment: "production",
Status: domain.CertificateStatusActive, Status: domain.CertificateStatusActive,
IssuerID: "iss-local", IssuerID: "iss-local",
OwnerID: "owner-ops", OwnerID: "owner-ops",
TeamID: "team-platform", TeamID: "team-platform",
CertificateProfileID: "prof-standard", CertificateProfileID: "prof-standard",
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }
certRepo.certs["mc-m20-test-1"] = cert certRepo.certs["mc-m20-test-1"] = cert
+1 -1
View File
@@ -1,10 +1,10 @@
package postgres package postgres
import ( import (
"github.com/shankar0123/certctl/internal/repository"
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/shankar0123/certctl/internal/repository"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
+1 -1
View File
@@ -1,10 +1,10 @@
package postgres package postgres
import ( import (
"github.com/shankar0123/certctl/internal/repository"
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/shankar0123/certctl/internal/repository"
"time" "time"
"github.com/shankar0123/certctl/internal/domain" "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. // Contract elements — the operator-facing string is what we ship.
wantSubstrings := []string{ wantSubstrings := []string{
"SQLSTATE 28P01", // operators grep on this "SQLSTATE 28P01", // operators grep on this
"POSTGRES_PASSWORD", // names the variable that traps "POSTGRES_PASSWORD", // names the variable that traps
"first boot", // the mechanism in plain language "first boot", // the mechanism in plain language
"down -v", // destructive remediation "down -v", // destructive remediation
"ALTER ROLE", // non-destructive remediation "ALTER ROLE", // non-destructive remediation
} }
for _, s := range wantSubstrings { for _, s := range wantSubstrings {
if !strings.Contains(got, s) { if !strings.Contains(got, s) {
@@ -235,8 +235,8 @@ func TestHealthCheckRepository_ListDueForCheck(t *testing.T) {
ctx := context.Background() ctx := context.Background()
now := time.Now().UTC().Truncate(time.Microsecond) now := time.Now().UTC().Truncate(time.Microsecond)
pastDue := now.Add(-10 * time.Minute) // > 300s ago, enabled → due pastDue := now.Add(-10 * time.Minute) // > 300s ago, enabled → due
recent := now.Add(-30 * time.Second) // < 300s ago, enabled → not due recent := now.Add(-30 * time.Second) // < 300s ago, enabled → not due
// (a) enabled + null last_checked_at — NULLS FIRST puts this first // (a) enabled + null last_checked_at — NULLS FIRST puts this first
a := newHealthCheck("hc-due-a", "a.example.com:443", domain.HealthStatusUnknown, true) a := newHealthCheck("hc-due-a", "a.example.com:443", domain.HealthStatusUnknown, true)
+1 -1
View File
@@ -1,10 +1,10 @@
package postgres package postgres
import ( import (
"github.com/shankar0123/certctl/internal/repository"
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/shankar0123/certctl/internal/repository"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/shankar0123/certctl/internal/domain" "github.com/shankar0123/certctl/internal/domain"
+1 -1
View File
@@ -1,10 +1,10 @@
package postgres package postgres
import ( import (
"github.com/shankar0123/certctl/internal/repository"
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/shankar0123/certctl/internal/repository"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
+1 -1
View File
@@ -1,10 +1,10 @@
package postgres package postgres
import ( import (
"github.com/shankar0123/certctl/internal/repository"
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/shankar0123/certctl/internal/repository"
"time" "time"
"github.com/lib/pq" "github.com/lib/pq"
+1 -1
View File
@@ -1,10 +1,10 @@
package postgres package postgres
import ( import (
"github.com/shankar0123/certctl/internal/repository"
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/shankar0123/certctl/internal/repository"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/shankar0123/certctl/internal/domain" "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", ID: tc.id, Name: tc.id, CommonName: tc.id + ".example.com",
SANs: []string{}, OwnerID: ownerID, TeamID: teamID, SANs: []string{}, OwnerID: ownerID, TeamID: teamID,
IssuerID: issuerID, RenewalPolicyID: policyID, IssuerID: issuerID, RenewalPolicyID: policyID,
Status: domain.CertificateStatusActive, Status: domain.CertificateStatusActive,
ExpiresAt: tc.expires, Tags: map[string]string{}, ExpiresAt: tc.expires, Tags: map[string]string{},
CreatedAt: now, UpdatedAt: now, 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", ID: "mc-job-test", Name: "job-test", CommonName: "job.example.com",
SANs: []string{}, OwnerID: ownerID, TeamID: teamID, SANs: []string{}, OwnerID: ownerID, TeamID: teamID,
IssuerID: issuerID, RenewalPolicyID: policyID, IssuerID: issuerID, RenewalPolicyID: policyID,
Status: domain.CertificateStatusActive, Status: domain.CertificateStatusActive,
ExpiresAt: now.Add(30 * 24 * time.Hour), Tags: map[string]string{}, ExpiresAt: now.Add(30 * 24 * time.Hour), Tags: map[string]string{},
CreatedAt: now, UpdatedAt: now, 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", ID: "mc-rev-test", Name: "rev-test", CommonName: "rev.example.com",
SANs: []string{}, OwnerID: ownerID, TeamID: teamID, SANs: []string{}, OwnerID: ownerID, TeamID: teamID,
IssuerID: issuerID, RenewalPolicyID: policyID, IssuerID: issuerID, RenewalPolicyID: policyID,
Status: domain.CertificateStatusRevoked, Status: domain.CertificateStatusRevoked,
ExpiresAt: now.Add(30 * 24 * time.Hour), Tags: map[string]string{}, ExpiresAt: now.Add(30 * 24 * time.Hour), Tags: map[string]string{},
CreatedAt: now, UpdatedAt: now, CreatedAt: now, UpdatedAt: now,
} }
@@ -1250,12 +1250,12 @@ func TestProfileRepository_CRUD(t *testing.T) {
{Algorithm: "RSA", MinSize: 2048}, {Algorithm: "RSA", MinSize: 2048},
{Algorithm: "ECDSA", MinSize: 256}, {Algorithm: "ECDSA", MinSize: 256},
}, },
MaxTTLSeconds: 86400, MaxTTLSeconds: 86400,
AllowedEKUs: []string{"serverAuth"}, AllowedEKUs: []string{"serverAuth"},
AllowShortLived: false, AllowShortLived: false,
Enabled: true, Enabled: true,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }
if err := repo.Create(ctx, profile); err != nil { 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", ID: "mc-notif-test", Name: "notif-test", CommonName: "notif.example.com",
SANs: []string{}, OwnerID: ownerID, TeamID: teamID, SANs: []string{}, OwnerID: ownerID, TeamID: teamID,
IssuerID: issuerID, RenewalPolicyID: policyID, IssuerID: issuerID, RenewalPolicyID: policyID,
Status: domain.CertificateStatusActive, Status: domain.CertificateStatusActive,
ExpiresAt: now.Add(30 * 24 * time.Hour), Tags: map[string]string{}, ExpiresAt: now.Add(30 * 24 * time.Hour), Tags: map[string]string{},
CreatedAt: now, UpdatedAt: now, 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", ID: "mc-linked-cert", Name: "linked-cert", CommonName: "linked.example.com",
SANs: []string{}, OwnerID: ownerID, TeamID: teamID, SANs: []string{}, OwnerID: ownerID, TeamID: teamID,
IssuerID: issuerID, RenewalPolicyID: policyID, IssuerID: issuerID, RenewalPolicyID: policyID,
Status: domain.CertificateStatusActive, Status: domain.CertificateStatusActive,
ExpiresAt: now.Add(90 * 24 * time.Hour), Tags: map[string]string{}, ExpiresAt: now.Add(90 * 24 * time.Hour), Tags: map[string]string{},
CreatedAt: now, UpdatedAt: now, CreatedAt: now, UpdatedAt: now,
} }
@@ -1447,7 +1447,7 @@ func TestDiscoveryRepository_DiscoveredCertCRUD(t *testing.T) {
NotBefore: &notBefore, NotAfter: &notAfter, KeyAlgorithm: "RSA", KeySize: 2048, NotBefore: &notBefore, NotAfter: &notAfter, KeyAlgorithm: "RSA", KeySize: 2048,
IsCA: false, PEMData: "---PEM---", SourcePath: "/etc/ssl/certs/disc.pem", IsCA: false, PEMData: "---PEM---", SourcePath: "/etc/ssl/certs/disc.pem",
SourceFormat: "PEM", AgentID: "agent-dcert-test", SourceFormat: "PEM", AgentID: "agent-dcert-test",
Status: domain.DiscoveryStatusUnmanaged, Status: domain.DiscoveryStatusUnmanaged,
FirstSeenAt: now, LastSeenAt: now, CreatedAt: now, UpdatedAt: now, FirstSeenAt: now, LastSeenAt: now, CreatedAt: now, UpdatedAt: now,
} }
@@ -1577,8 +1577,8 @@ func TestNetworkScanRepository_CRUD(t *testing.T) {
target := &domain.NetworkScanTarget{ target := &domain.NetworkScanTarget{
ID: "ns-test-1", Name: "Internal Network", ID: "ns-test-1", Name: "Internal Network",
CIDRs: []string{"10.0.0.0/24", "192.168.1.0/24"}, CIDRs: []string{"10.0.0.0/24", "192.168.1.0/24"},
Ports: []int64{443, 8443}, Ports: []int64{443, 8443},
Enabled: true, ScanIntervalHours: 6, TimeoutMs: 5000, Enabled: true, ScanIntervalHours: 6, TimeoutMs: 5000,
CreatedAt: now, UpdatedAt: now, CreatedAt: now, UpdatedAt: now,
} }
+1 -1
View File
@@ -1,10 +1,10 @@
package postgres package postgres
import ( import (
"github.com/shankar0123/certctl/internal/repository"
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/shankar0123/certctl/internal/repository"
"github.com/shankar0123/certctl/internal/domain" "github.com/shankar0123/certctl/internal/domain"
) )
+1 -1
View File
@@ -1,10 +1,10 @@
package postgres package postgres
import ( import (
"github.com/shankar0123/certctl/internal/repository"
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/shankar0123/certctl/internal/repository"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/shankar0123/certctl/internal/domain" "github.com/shankar0123/certctl/internal/domain"
+1 -1
View File
@@ -1,10 +1,10 @@
package postgres package postgres
import ( import (
"github.com/shankar0123/certctl/internal/repository"
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/shankar0123/certctl/internal/repository"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/shankar0123/certctl/internal/domain" "github.com/shankar0123/certctl/internal/domain"
+8 -9
View File
@@ -11,14 +11,14 @@ import (
// mockRenewalService is a mock implementation for testing. // mockRenewalService is a mock implementation for testing.
type mockRenewalService struct { type mockRenewalService struct {
mu sync.Mutex mu sync.Mutex
callCount int callCount int
callTimes []time.Time callTimes []time.Time
expireCallCount int expireCallCount int
expireCallTimes []time.Time expireCallTimes []time.Time
slowDelay time.Duration slowDelay time.Duration
shouldError bool shouldError bool
blockCh chan struct{} // if non-nil, blocks until closed (ignores context) blockCh chan struct{} // if non-nil, blocks until closed (ignores context)
} }
func (m *mockRenewalService) CheckExpiringCertificates(ctx context.Context) error { func (m *mockRenewalService) CheckExpiringCertificates(ctx context.Context) error {
@@ -138,7 +138,6 @@ func (m *mockJobService) RetryFailedJobs(ctx context.Context, maxRetries int) er
return nil return nil
} }
// ReapTimedOutJobs is the scheduler-driven counterpart to ProcessPendingJobs that // ReapTimedOutJobs is the scheduler-driven counterpart to ProcessPendingJobs that
// covers coverage gap I-003: JobService.ReapTimedOutJobs (via JobReaperService interface) // covers coverage gap I-003: JobService.ReapTimedOutJobs (via JobReaperService interface)
// had no runtime caller prior to the jobTimeoutLoop being wired. // 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 // mockAgentGroupRepo is a test implementation of AgentGroupRepository
type mockAgentGroupRepo struct { type mockAgentGroupRepo struct {
groups map[string]*domain.AgentGroup groups map[string]*domain.AgentGroup
members map[string][]*domain.Agent members map[string][]*domain.Agent
CreateErr error CreateErr error
UpdateErr error UpdateErr error
DeleteErr error DeleteErr error
GetErr error GetErr error
ListErr error ListErr error
ListMembersErr error ListMembersErr error
AddMemberErr error AddMemberErr error
RemoveMemberErr error RemoveMemberErr error
} }
func newMockAgentGroupRepository() *mockAgentGroupRepo { func newMockAgentGroupRepository() *mockAgentGroupRepo {
+3 -3
View File
@@ -109,9 +109,9 @@ func TestAgentService_UpdateJobStatus_DelegatesToReportJobStatus(t *testing.T) {
svc, repo, _, jobRepo, _ := newTestAgentSvc(t) svc, repo, _, jobRepo, _ := newTestAgentSvc(t)
repo.Agents["a-1"] = &domain.Agent{ID: "a-1", Status: domain.AgentStatusOnline} repo.Agents["a-1"] = &domain.Agent{ID: "a-1", Status: domain.AgentStatusOnline}
jobRepo.Jobs["j-1"] = &domain.Job{ jobRepo.Jobs["j-1"] = &domain.Job{
ID: "j-1", ID: "j-1",
AgentID: strPtr("a-1"), AgentID: strPtr("a-1"),
Status: domain.JobStatusRunning, Status: domain.JobStatusRunning,
} }
err := svc.UpdateJobStatus(context.Background(), "a-1", "j-1", "Completed", "") err := svc.UpdateJobStatus(context.Background(), "a-1", "j-1", "Completed", "")
if err != nil { if err != nil {
-1
View File
@@ -10,7 +10,6 @@ import (
"github.com/shankar0123/certctl/internal/domain" "github.com/shankar0123/certctl/internal/domain"
) )
func TestRegisterAgent(t *testing.T) { func TestRegisterAgent(t *testing.T) {
ctx := context.Background() ctx := context.Background()
agentRepo := &mockAgentRepo{ 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 // Note `ip_address` is debatable — useful for forensics but flagged by
// GDPR Art. 32 — defaulting to redact, operators can audit + adjust. // GDPR Art. 32 — defaulting to redact, operators can audit + adjust.
var piiKeys = map[string]bool{ var piiKeys = map[string]bool{
"email": true, "email": true,
"email_address": true, "email_address": true,
"phone": true, "phone": true,
"phone_number": true, "phone_number": true,
"telephone": true, "telephone": true,
"ssn": true, "ssn": true,
"social_security": true, "social_security": true,
"dob": true, "dob": true,
"date_of_birth": true, "date_of_birth": true,
"name": true, "name": true,
"full_name": true, "full_name": true,
"first_name": true, "first_name": true,
"last_name": true, "last_name": true,
"surname": true, "surname": true,
"address": true, "address": true,
"street": true, "street": true,
"street_address": true, "street_address": true,
"city": true, "city": true,
"postal_code": true, "postal_code": true,
"zip": true, "zip": true,
"zipcode": true, "zipcode": true,
"ip": true, "ip": true,
"ip_address": true, "ip_address": true,
} }
// RedactDetailsForAudit walks a details map and returns a NEW map with // 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 { for _, key := range cases {
t.Run(key, func(t *testing.T) { t.Run(key, func(t *testing.T) {
in := map[string]interface{}{ in := map[string]interface{}{
key: "sensitive-value-do-not-leak", key: "sensitive-value-do-not-leak",
"non_sensitive_id": "ok-public-id", "non_sensitive_id": "ok-public-id",
} }
out := RedactDetailsForAudit(in) out := RedactDetailsForAudit(in)
if out[key] != "[REDACTED:CREDENTIAL]" { if out[key] != "[REDACTED:CREDENTIAL]" {
@@ -70,7 +70,7 @@ func TestRedactDetailsForAudit_NestedMap(t *testing.T) {
in := map[string]interface{}{ in := map[string]interface{}{
"resource_id": "iss-prod", "resource_id": "iss-prod",
"config": map[string]interface{}{ "config": map[string]interface{}{
"endpoint": "https://acme.example.com", "endpoint": "https://acme.example.com",
"eab_secret": "do-not-leak-this-secret", "eab_secret": "do-not-leak-this-secret",
"contact": map[string]interface{}{ "contact": map[string]interface{}{
"email": "ops@example.com", "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 // Maps with no sensitive keys should NOT have a redacted_keys array
// — clutter-free for the common case. // — clutter-free for the common case.
in := map[string]interface{}{ in := map[string]interface{}{
"action": "create_certificate", "action": "create_certificate",
"cert_id": "mc-prod-001", "cert_id": "mc-prod-001",
"latency_ms": float64(42), "latency_ms": float64(42),
} }
out := RedactDetailsForAudit(in) out := RedactDetailsForAudit(in)
@@ -171,7 +171,7 @@ func TestRedactDetailsForAudit_NoRedactionPath(t *testing.T) {
func TestRedactDetailsForAudit_DoesNotMutateInput(t *testing.T) { func TestRedactDetailsForAudit_DoesNotMutateInput(t *testing.T) {
in := map[string]interface{}{ in := map[string]interface{}{
"api_key": "secret-do-not-leak", "api_key": "secret-do-not-leak",
"resource": "iss-prod", "resource": "iss-prod",
} }
_ = RedactDetailsForAudit(in) _ = RedactDetailsForAudit(in)
@@ -197,8 +197,8 @@ func TestRedactDetailsForAudit_JSONRoundTrip(t *testing.T) {
// The redacted map MUST round-trip through json.Marshal (the // The redacted map MUST round-trip through json.Marshal (the
// AuditService persistence path). Catches type-assertion regressions. // AuditService persistence path). Catches type-assertion regressions.
in := map[string]interface{}{ in := map[string]interface{}{
"reason": "compromised-key", "reason": "compromised-key",
"api_key": "leak-me", "api_key": "leak-me",
"contacts": []interface{}{ "contacts": []interface{}{
map[string]interface{}{"email": "ops@example.com"}, 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) { func TestBulkReassign_SkipsAlreadyOwned(t *testing.T) {
svc, certRepo, ownerRepo, _ := newBulkReassignmentTestService() svc, certRepo, ownerRepo, _ := newBulkReassignmentTestService()
addOwner(ownerRepo, "o-bob") addOwner(ownerRepo, "o-bob")
addOwnedCert(certRepo, "mc-1", "o-bob", "") // already owned by target addOwnedCert(certRepo, "mc-1", "o-bob", "") // already owned by target
addOwnedCert(certRepo, "mc-2", "o-alice", "") // needs reassign addOwnedCert(certRepo, "mc-2", "o-alice", "") // needs reassign
res, err := svc.BulkReassign(context.Background(), res, err := svc.BulkReassign(context.Background(),
domain.BulkReassignmentRequest{ domain.BulkReassignmentRequest{
+14 -14
View File
@@ -32,11 +32,11 @@ func newBulkRevocationTestService() (*BulkRevocationService, *mockCertRepo, *moc
func addTestCert(repo *mockCertRepo, id, status, issuerID string) { func addTestCert(repo *mockCertRepo, id, status, issuerID string) {
cert := &domain.ManagedCertificate{ cert := &domain.ManagedCertificate{
ID: id, ID: id,
CommonName: id + ".example.com", CommonName: id + ".example.com",
Status: domain.CertificateStatus(status), Status: domain.CertificateStatus(status),
IssuerID: issuerID, IssuerID: issuerID,
ExpiresAt: time.Now().AddDate(0, 6, 0), ExpiresAt: time.Now().AddDate(0, 6, 0),
} }
repo.AddCert(cert) repo.AddCert(cert)
// Add a version with serial number (needed by RevokeCertificateWithActor) // 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) { func addTestCertWithProfile(repo *mockCertRepo, id, status, issuerID, profileID, ownerID string) {
cert := &domain.ManagedCertificate{ cert := &domain.ManagedCertificate{
ID: id, ID: id,
CommonName: id + ".example.com", CommonName: id + ".example.com",
Status: domain.CertificateStatus(status), Status: domain.CertificateStatus(status),
IssuerID: issuerID, IssuerID: issuerID,
CertificateProfileID: profileID, CertificateProfileID: profileID,
OwnerID: ownerID, OwnerID: ownerID,
ExpiresAt: time.Now().AddDate(0, 6, 0), ExpiresAt: time.Now().AddDate(0, 6, 0),
} }
repo.AddCert(cert) repo.AddCert(cert)
repo.Versions[id] = []*domain.CertificateVersion{ repo.Versions[id] = []*domain.CertificateVersion{
@@ -272,11 +272,11 @@ func TestBulkRevoke_PartialFailure(t *testing.T) {
addTestCert(certRepo, "mc-1", "Active", "iss-local") addTestCert(certRepo, "mc-1", "Active", "iss-local")
// mc-2 is active but has NO version — RevokeCertificateWithActor will fail on GetLatestVersion // mc-2 is active but has NO version — RevokeCertificateWithActor will fail on GetLatestVersion
cert2 := &domain.ManagedCertificate{ cert2 := &domain.ManagedCertificate{
ID: "mc-2", ID: "mc-2",
CommonName: "mc-2.example.com", CommonName: "mc-2.example.com",
Status: domain.CertificateStatusActive, Status: domain.CertificateStatusActive,
IssuerID: "iss-local", IssuerID: "iss-local",
ExpiresAt: time.Now().AddDate(0, 6, 0), ExpiresAt: time.Now().AddDate(0, 6, 0),
} }
certRepo.AddCert(cert2) certRepo.AddCert(cert2)
// Don't add versions for mc-2 so GetLatestVersion returns errNotFound // 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 // Add agent with recent heartbeat
now := time.Now() now := time.Now()
agent := &domain.Agent{ agent := &domain.Agent{
ID: "agent-1", ID: "agent-1",
Name: "Test Agent", Name: "Test Agent",
Hostname: "agent.example.com", Hostname: "agent.example.com",
Status: domain.AgentStatusOnline, Status: domain.AgentStatusOnline,
LastHeartbeatAt: &now, LastHeartbeatAt: &now,
RegisteredAt: time.Now(), RegisteredAt: time.Now(),
APIKeyHash: "hash-1", APIKeyHash: "hash-1",
OS: "linux", OS: "linux",
Architecture: "amd64", Architecture: "amd64",
IPAddress: "192.168.1.1", IPAddress: "192.168.1.1",
Version: "1.0.0", Version: "1.0.0",
} }
agentRepo.AddAgent(agent) agentRepo.AddAgent(agent)
+14 -14
View File
@@ -31,20 +31,20 @@ type HTMLEmailSender interface {
// DigestData holds the aggregated data for a digest email. // DigestData holds the aggregated data for a digest email.
type DigestData struct { type DigestData struct {
GeneratedAt time.Time `json:"generated_at"` GeneratedAt time.Time `json:"generated_at"`
TotalCertificates int64 `json:"total_certificates"` TotalCertificates int64 `json:"total_certificates"`
ExpiringCertificates int64 `json:"expiring_certificates"` ExpiringCertificates int64 `json:"expiring_certificates"`
ExpiredCertificates int64 `json:"expired_certificates"` ExpiredCertificates int64 `json:"expired_certificates"`
RevokedCertificates int64 `json:"revoked_certificates"` RevokedCertificates int64 `json:"revoked_certificates"`
ActiveAgents int64 `json:"active_agents"` ActiveAgents int64 `json:"active_agents"`
OfflineAgents int64 `json:"offline_agents"` OfflineAgents int64 `json:"offline_agents"`
TotalAgents int64 `json:"total_agents"` TotalAgents int64 `json:"total_agents"`
PendingJobs int64 `json:"pending_jobs"` PendingJobs int64 `json:"pending_jobs"`
FailedJobs int64 `json:"failed_jobs"` FailedJobs int64 `json:"failed_jobs"`
CompletedJobs int64 `json:"completed_jobs"` CompletedJobs int64 `json:"completed_jobs"`
ExpiringCerts []DigestCertEntry `json:"expiring_certs"` ExpiringCerts []DigestCertEntry `json:"expiring_certs"`
RecentFailures []DigestJobEntry `json:"recent_failures"` RecentFailures []DigestJobEntry `json:"recent_failures"`
StatusCounts []DigestStatusCount `json:"status_counts"` StatusCounts []DigestStatusCount `json:"status_counts"`
} }
// DigestCertEntry represents a certificate entry in the digest. // DigestCertEntry represents a certificate entry in the digest.
+54 -54
View File
@@ -12,17 +12,17 @@ import (
// mockDiscoveryRepo is a test implementation of DiscoveryRepository // mockDiscoveryRepo is a test implementation of DiscoveryRepository
type mockDiscoveryRepo struct { type mockDiscoveryRepo struct {
Scans map[string]*domain.DiscoveryScan Scans map[string]*domain.DiscoveryScan
Discovered map[string]*domain.DiscoveredCertificate Discovered map[string]*domain.DiscoveredCertificate
CreateScanErr error CreateScanErr error
GetScanErr error GetScanErr error
ListScansErr error ListScansErr error
CreateDiscoveredErr error CreateDiscoveredErr error
GetDiscoveredErr error GetDiscoveredErr error
ListDiscoveredErr error ListDiscoveredErr error
UpdateStatusErr error UpdateStatusErr error
GetByFingerprintErr error GetByFingerprintErr error
CountByStatusErr error CountByStatusErr error
} }
func newMockDiscoveryRepository() *mockDiscoveryRepo { func newMockDiscoveryRepository() *mockDiscoveryRepo {
@@ -268,20 +268,20 @@ func TestListDiscovered_Success(t *testing.T) {
now := time.Now() now := time.Now()
cert1 := &domain.DiscoveredCertificate{ cert1 := &domain.DiscoveredCertificate{
ID: "dcert-1", ID: "dcert-1",
AgentID: "agent-1", AgentID: "agent-1",
CommonName: "example.com", CommonName: "example.com",
Status: domain.DiscoveryStatusUnmanaged, Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }
cert2 := &domain.DiscoveredCertificate{ cert2 := &domain.DiscoveredCertificate{
ID: "dcert-2", ID: "dcert-2",
AgentID: "agent-1", AgentID: "agent-1",
CommonName: "api.example.com", CommonName: "api.example.com",
Status: domain.DiscoveryStatusManaged, Status: domain.DiscoveryStatusManaged,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }
discoveryRepo.Discovered[cert1.ID] = cert1 discoveryRepo.Discovered[cert1.ID] = cert1
discoveryRepo.Discovered[cert2.ID] = cert2 discoveryRepo.Discovered[cert2.ID] = cert2
@@ -304,20 +304,20 @@ func TestListDiscovered_WithStatusFilter(t *testing.T) {
now := time.Now() now := time.Now()
cert1 := &domain.DiscoveredCertificate{ cert1 := &domain.DiscoveredCertificate{
ID: "dcert-1", ID: "dcert-1",
AgentID: "agent-1", AgentID: "agent-1",
CommonName: "example.com", CommonName: "example.com",
Status: domain.DiscoveryStatusUnmanaged, Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }
cert2 := &domain.DiscoveredCertificate{ cert2 := &domain.DiscoveredCertificate{
ID: "dcert-2", ID: "dcert-2",
AgentID: "agent-1", AgentID: "agent-1",
CommonName: "api.example.com", CommonName: "api.example.com",
Status: domain.DiscoveryStatusManaged, Status: domain.DiscoveryStatusManaged,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }
discoveryRepo.Discovered[cert1.ID] = cert1 discoveryRepo.Discovered[cert1.ID] = cert1
discoveryRepo.Discovered[cert2.ID] = cert2 discoveryRepo.Discovered[cert2.ID] = cert2
@@ -340,11 +340,11 @@ func TestGetDiscovered_Success(t *testing.T) {
now := time.Now() now := time.Now()
cert := &domain.DiscoveredCertificate{ cert := &domain.DiscoveredCertificate{
ID: "dcert-1", ID: "dcert-1",
CommonName: "example.com", CommonName: "example.com",
Status: domain.DiscoveryStatusUnmanaged, Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }
discoveryRepo.Discovered[cert.ID] = cert discoveryRepo.Discovered[cert.ID] = cert
@@ -363,12 +363,12 @@ func TestClaimDiscovered_Success(t *testing.T) {
now := time.Now() now := time.Now()
discoveredCert := &domain.DiscoveredCertificate{ discoveredCert := &domain.DiscoveredCertificate{
ID: "dcert-1", ID: "dcert-1",
CommonName: "example.com", CommonName: "example.com",
FingerprintSHA256: "abc123", FingerprintSHA256: "abc123",
Status: domain.DiscoveryStatusUnmanaged, Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }
discoveryRepo.Discovered[discoveredCert.ID] = discoveredCert discoveryRepo.Discovered[discoveredCert.ID] = discoveredCert
@@ -415,11 +415,11 @@ func TestClaimDiscovered_MissingManagedCertID(t *testing.T) {
now := time.Now() now := time.Now()
cert := &domain.DiscoveredCertificate{ cert := &domain.DiscoveredCertificate{
ID: "dcert-1", ID: "dcert-1",
CommonName: "example.com", CommonName: "example.com",
Status: domain.DiscoveryStatusUnmanaged, Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }
discoveryRepo.Discovered[cert.ID] = cert discoveryRepo.Discovered[cert.ID] = cert
@@ -434,11 +434,11 @@ func TestClaimDiscovered_ManagedCertNotFound(t *testing.T) {
now := time.Now() now := time.Now()
cert := &domain.DiscoveredCertificate{ cert := &domain.DiscoveredCertificate{
ID: "dcert-1", ID: "dcert-1",
CommonName: "example.com", CommonName: "example.com",
Status: domain.DiscoveryStatusUnmanaged, Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }
discoveryRepo.Discovered[cert.ID] = cert discoveryRepo.Discovered[cert.ID] = cert
@@ -456,11 +456,11 @@ func TestDismissDiscovered_Success(t *testing.T) {
now := time.Now() now := time.Now()
cert := &domain.DiscoveredCertificate{ cert := &domain.DiscoveredCertificate{
ID: "dcert-1", ID: "dcert-1",
CommonName: "example.com", CommonName: "example.com",
Status: domain.DiscoveryStatusUnmanaged, Status: domain.DiscoveryStatusUnmanaged,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }
discoveryRepo.Discovered[cert.ID] = cert discoveryRepo.Discovered[cert.ID] = cert
+3 -3
View File
@@ -71,10 +71,10 @@ func TestExportPEM_Success(t *testing.T) {
Status: domain.CertificateStatusActive, Status: domain.CertificateStatusActive,
}, },
&domain.CertificateVersion{ &domain.CertificateVersion{
ID: "cv-1", ID: "cv-1",
CertificateID: "mc-test-1", CertificateID: "mc-test-1",
SerialNumber: "abc123", SerialNumber: "abc123",
PEMChain: fullPEM, PEMChain: fullPEM,
}, },
) )
auditSvc := &AuditService{auditRepo: &mockAuditRepo{}} auditSvc := &AuditService{auditRepo: &mockAuditRepo{}}
+16 -16
View File
@@ -14,19 +14,19 @@ import (
// mockHealthCheckRepo implements the HealthCheckRepository interface for testing. // mockHealthCheckRepo implements the HealthCheckRepository interface for testing.
type mockHealthCheckRepo struct { type mockHealthCheckRepo struct {
checks map[string]*domain.EndpointHealthCheck checks map[string]*domain.EndpointHealthCheck
history []*domain.HealthHistoryEntry history []*domain.HealthHistoryEntry
createErr error createErr error
getErr error getErr error
updateErr error updateErr error
deleteErr error deleteErr error
listErr error listErr error
listDueErr error listDueErr error
getHistoryErr error getHistoryErr error
recordHistoryErr error recordHistoryErr error
purgeHistoryErr error purgeHistoryErr error
getSummaryErr error getSummaryErr error
getSummaryResult *domain.HealthCheckSummary getSummaryResult *domain.HealthCheckSummary
} }
func newMockHealthCheckRepo() *mockHealthCheckRepo { 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) svc := NewHealthCheckService(repo, nil, logger, 10, 5*time.Second, 30*24*time.Hour, false)
check := &domain.EndpointHealthCheck{ check := &domain.EndpointHealthCheck{
Endpoint: "example.com:443", Endpoint: "example.com:443",
Status: domain.HealthStatusUnknown, Status: domain.HealthStatusUnknown,
Enabled: true, Enabled: true,
CheckIntervalSecs: 300, CheckIntervalSecs: 300,
} }
+1 -1
View File
@@ -1,12 +1,12 @@
package service package service
import ( import (
"time"
"context" "context"
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
"strings" "strings"
"time"
"github.com/shankar0123/certctl/internal/domain" "github.com/shankar0123/certctl/internal/domain"
"github.com/shankar0123/certctl/internal/repository" "github.com/shankar0123/certctl/internal/repository"

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