mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 18:51:32 +00:00
07275bf92f
Agents now report OS, architecture, IP address, hostname, and version via heartbeat using runtime.GOOS, runtime.GOARCH, and net.Dial. New migration adds columns to agents table. Heartbeat handler, service, and repository updated to accept and persist metadata. GUI shows OS/Arch in agent list and full system info in agent detail page. Apache httpd connector: separate cert/chain/key files, apachectl configtest validation, graceful reload. HAProxy connector: combined PEM file (cert+chain+key), optional config validation, reload. Both wired into agent binary's target connector switch. 14 tests for new connectors. All existing tests updated for new Heartbeat/UpdateHeartbeat signatures. Docs updated across README, architecture, concepts, and connectors guides. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
772 lines
18 KiB
Go
772 lines
18 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/shankar0123/certctl/internal/domain"
|
|
"github.com/shankar0123/certctl/internal/repository"
|
|
)
|
|
|
|
var errNotFound = errors.New("not found")
|
|
|
|
// mockCertRepo is a test implementation of CertificateRepository
|
|
type mockCertRepo struct {
|
|
Certs map[string]*domain.ManagedCertificate
|
|
Versions map[string][]*domain.CertificateVersion
|
|
CreateErr error
|
|
UpdateErr error
|
|
GetErr error
|
|
ListErr error
|
|
ListVersionsErr error
|
|
ListVersionsResult []*domain.CertificateVersion
|
|
CreateVersionErr error
|
|
ArchiveErr error
|
|
}
|
|
|
|
func (m *mockCertRepo) List(ctx context.Context, filter *repository.CertificateFilter) ([]*domain.ManagedCertificate, int, error) {
|
|
if m.ListErr != nil {
|
|
return nil, 0, m.ListErr
|
|
}
|
|
var certs []*domain.ManagedCertificate
|
|
for _, c := range m.Certs {
|
|
certs = append(certs, c)
|
|
}
|
|
return certs, len(certs), nil
|
|
}
|
|
|
|
func (m *mockCertRepo) Get(ctx context.Context, id string) (*domain.ManagedCertificate, error) {
|
|
if m.GetErr != nil {
|
|
return nil, m.GetErr
|
|
}
|
|
cert, ok := m.Certs[id]
|
|
if !ok {
|
|
return nil, errNotFound
|
|
}
|
|
return cert, nil
|
|
}
|
|
|
|
func (m *mockCertRepo) Create(ctx context.Context, cert *domain.ManagedCertificate) error {
|
|
if m.CreateErr != nil {
|
|
return m.CreateErr
|
|
}
|
|
m.Certs[cert.ID] = cert
|
|
return nil
|
|
}
|
|
|
|
func (m *mockCertRepo) Update(ctx context.Context, cert *domain.ManagedCertificate) error {
|
|
if m.UpdateErr != nil {
|
|
return m.UpdateErr
|
|
}
|
|
m.Certs[cert.ID] = cert
|
|
return nil
|
|
}
|
|
|
|
func (m *mockCertRepo) Archive(ctx context.Context, id string) error {
|
|
if m.ArchiveErr != nil {
|
|
return m.ArchiveErr
|
|
}
|
|
cert, ok := m.Certs[id]
|
|
if !ok {
|
|
return errNotFound
|
|
}
|
|
cert.Status = domain.CertificateStatusArchived
|
|
return nil
|
|
}
|
|
|
|
func (m *mockCertRepo) ListVersions(ctx context.Context, certID string) ([]*domain.CertificateVersion, error) {
|
|
if m.ListVersionsErr != nil {
|
|
return nil, m.ListVersionsErr
|
|
}
|
|
if m.ListVersionsResult != nil {
|
|
return m.ListVersionsResult, nil
|
|
}
|
|
return m.Versions[certID], nil
|
|
}
|
|
|
|
func (m *mockCertRepo) CreateVersion(ctx context.Context, version *domain.CertificateVersion) error {
|
|
if m.CreateVersionErr != nil {
|
|
return m.CreateVersionErr
|
|
}
|
|
m.Versions[version.CertificateID] = append(m.Versions[version.CertificateID], version)
|
|
return nil
|
|
}
|
|
|
|
func (m *mockCertRepo) GetExpiringCertificates(ctx context.Context, before time.Time) ([]*domain.ManagedCertificate, error) {
|
|
var expiring []*domain.ManagedCertificate
|
|
for _, c := range m.Certs {
|
|
if c.ExpiresAt.Before(before) {
|
|
expiring = append(expiring, c)
|
|
}
|
|
}
|
|
return expiring, nil
|
|
}
|
|
|
|
func (m *mockCertRepo) AddCert(cert *domain.ManagedCertificate) {
|
|
m.Certs[cert.ID] = cert
|
|
}
|
|
|
|
// mockJobRepo is a test implementation of JobRepository
|
|
type mockJobRepo struct {
|
|
Jobs map[string]*domain.Job
|
|
StatusUpdates map[string]domain.JobStatus
|
|
CreateErr error
|
|
UpdateErr error
|
|
UpdateStatusErr error
|
|
GetErr error
|
|
ListErr error
|
|
ListByStatusErr error
|
|
DeleteErr error
|
|
}
|
|
|
|
func (m *mockJobRepo) List(ctx context.Context) ([]*domain.Job, error) {
|
|
if m.ListErr != nil {
|
|
return nil, m.ListErr
|
|
}
|
|
var jobs []*domain.Job
|
|
for _, j := range m.Jobs {
|
|
jobs = append(jobs, j)
|
|
}
|
|
return jobs, nil
|
|
}
|
|
|
|
func (m *mockJobRepo) Get(ctx context.Context, id string) (*domain.Job, error) {
|
|
if m.GetErr != nil {
|
|
return nil, m.GetErr
|
|
}
|
|
job, ok := m.Jobs[id]
|
|
if !ok {
|
|
return nil, errNotFound
|
|
}
|
|
return job, nil
|
|
}
|
|
|
|
func (m *mockJobRepo) Create(ctx context.Context, job *domain.Job) error {
|
|
if m.CreateErr != nil {
|
|
return m.CreateErr
|
|
}
|
|
m.Jobs[job.ID] = job
|
|
return nil
|
|
}
|
|
|
|
func (m *mockJobRepo) Update(ctx context.Context, job *domain.Job) error {
|
|
if m.UpdateErr != nil {
|
|
return m.UpdateErr
|
|
}
|
|
m.Jobs[job.ID] = job
|
|
return nil
|
|
}
|
|
|
|
func (m *mockJobRepo) Delete(ctx context.Context, id string) error {
|
|
if m.DeleteErr != nil {
|
|
return m.DeleteErr
|
|
}
|
|
delete(m.Jobs, id)
|
|
return nil
|
|
}
|
|
|
|
func (m *mockJobRepo) ListByStatus(ctx context.Context, status domain.JobStatus) ([]*domain.Job, error) {
|
|
if m.ListByStatusErr != nil {
|
|
return nil, m.ListByStatusErr
|
|
}
|
|
var jobs []*domain.Job
|
|
for _, j := range m.Jobs {
|
|
if j.Status == status {
|
|
jobs = append(jobs, j)
|
|
}
|
|
}
|
|
return jobs, nil
|
|
}
|
|
|
|
func (m *mockJobRepo) ListByCertificate(ctx context.Context, certID string) ([]*domain.Job, error) {
|
|
var jobs []*domain.Job
|
|
for _, j := range m.Jobs {
|
|
if j.CertificateID == certID {
|
|
jobs = append(jobs, j)
|
|
}
|
|
}
|
|
return jobs, nil
|
|
}
|
|
|
|
func (m *mockJobRepo) UpdateStatus(ctx context.Context, id string, status domain.JobStatus, errMsg string) error {
|
|
if m.UpdateStatusErr != nil {
|
|
return m.UpdateStatusErr
|
|
}
|
|
job, ok := m.Jobs[id]
|
|
if !ok {
|
|
return errNotFound
|
|
}
|
|
job.Status = status
|
|
if errMsg != "" {
|
|
job.LastError = &errMsg
|
|
}
|
|
m.StatusUpdates[id] = status
|
|
return nil
|
|
}
|
|
|
|
func (m *mockJobRepo) GetPendingJobs(ctx context.Context, jobType domain.JobType) ([]*domain.Job, error) {
|
|
var jobs []*domain.Job
|
|
for _, j := range m.Jobs {
|
|
if j.Type == jobType && j.Status == domain.JobStatusPending {
|
|
jobs = append(jobs, j)
|
|
}
|
|
}
|
|
return jobs, nil
|
|
}
|
|
|
|
func (m *mockJobRepo) AddJob(job *domain.Job) {
|
|
m.Jobs[job.ID] = job
|
|
}
|
|
|
|
// mockNotifRepo is a test implementation of NotificationRepository
|
|
type mockNotifRepo struct {
|
|
Notifications []*domain.NotificationEvent
|
|
CreateErr error
|
|
ListErr error
|
|
UpdateErr error
|
|
}
|
|
|
|
func (m *mockNotifRepo) Create(ctx context.Context, notif *domain.NotificationEvent) error {
|
|
if m.CreateErr != nil {
|
|
return m.CreateErr
|
|
}
|
|
m.Notifications = append(m.Notifications, notif)
|
|
return nil
|
|
}
|
|
|
|
func (m *mockNotifRepo) List(ctx context.Context, filter *repository.NotificationFilter) ([]*domain.NotificationEvent, error) {
|
|
if m.ListErr != nil {
|
|
return nil, m.ListErr
|
|
}
|
|
return m.Notifications, nil
|
|
}
|
|
|
|
func (m *mockNotifRepo) UpdateStatus(ctx context.Context, id string, status string, sentAt time.Time) error {
|
|
if m.UpdateErr != nil {
|
|
return m.UpdateErr
|
|
}
|
|
for _, n := range m.Notifications {
|
|
if n.ID == id {
|
|
n.Status = status
|
|
return nil
|
|
}
|
|
}
|
|
return errNotFound
|
|
}
|
|
|
|
func (m *mockNotifRepo) AddNotification(notif *domain.NotificationEvent) {
|
|
m.Notifications = append(m.Notifications, notif)
|
|
}
|
|
|
|
// mockAuditRepo is a test implementation of AuditRepository
|
|
type mockAuditRepo struct {
|
|
Events []*domain.AuditEvent
|
|
CreateErr error
|
|
ListErr error
|
|
}
|
|
|
|
func (m *mockAuditRepo) Create(ctx context.Context, event *domain.AuditEvent) error {
|
|
if m.CreateErr != nil {
|
|
return m.CreateErr
|
|
}
|
|
m.Events = append(m.Events, event)
|
|
return nil
|
|
}
|
|
|
|
func (m *mockAuditRepo) List(ctx context.Context, filter *repository.AuditFilter) ([]*domain.AuditEvent, error) {
|
|
if m.ListErr != nil {
|
|
return nil, m.ListErr
|
|
}
|
|
// Apply filtering like the real repo
|
|
var filtered []*domain.AuditEvent
|
|
for _, e := range m.Events {
|
|
if filter != nil {
|
|
if filter.ResourceType != "" && e.ResourceType != filter.ResourceType {
|
|
continue
|
|
}
|
|
if filter.ResourceID != "" && e.ResourceID != filter.ResourceID {
|
|
continue
|
|
}
|
|
if filter.Actor != "" && e.Actor != filter.Actor {
|
|
continue
|
|
}
|
|
if !filter.From.IsZero() && e.Timestamp.Before(filter.From) {
|
|
continue
|
|
}
|
|
if !filter.To.IsZero() && e.Timestamp.After(filter.To) {
|
|
continue
|
|
}
|
|
}
|
|
filtered = append(filtered, e)
|
|
}
|
|
return filtered, nil
|
|
}
|
|
|
|
func (m *mockAuditRepo) AddEvent(event *domain.AuditEvent) {
|
|
m.Events = append(m.Events, event)
|
|
}
|
|
|
|
// mockPolicyRepo is a test implementation of PolicyRepository
|
|
type mockPolicyRepo struct {
|
|
Rules map[string]*domain.PolicyRule
|
|
Violations []*domain.PolicyViolation
|
|
CreateRuleErr error
|
|
UpdateRuleErr error
|
|
DeleteRuleErr error
|
|
GetRuleErr error
|
|
ListRulesErr error
|
|
CreateViolationErr error
|
|
ListViolationsErr error
|
|
}
|
|
|
|
func (m *mockPolicyRepo) ListRules(ctx context.Context) ([]*domain.PolicyRule, error) {
|
|
if m.ListRulesErr != nil {
|
|
return nil, m.ListRulesErr
|
|
}
|
|
var rules []*domain.PolicyRule
|
|
for _, r := range m.Rules {
|
|
rules = append(rules, r)
|
|
}
|
|
return rules, nil
|
|
}
|
|
|
|
func (m *mockPolicyRepo) GetRule(ctx context.Context, id string) (*domain.PolicyRule, error) {
|
|
if m.GetRuleErr != nil {
|
|
return nil, m.GetRuleErr
|
|
}
|
|
rule, ok := m.Rules[id]
|
|
if !ok {
|
|
return nil, errNotFound
|
|
}
|
|
return rule, nil
|
|
}
|
|
|
|
func (m *mockPolicyRepo) CreateRule(ctx context.Context, rule *domain.PolicyRule) error {
|
|
if m.CreateRuleErr != nil {
|
|
return m.CreateRuleErr
|
|
}
|
|
m.Rules[rule.ID] = rule
|
|
return nil
|
|
}
|
|
|
|
func (m *mockPolicyRepo) UpdateRule(ctx context.Context, rule *domain.PolicyRule) error {
|
|
if m.UpdateRuleErr != nil {
|
|
return m.UpdateRuleErr
|
|
}
|
|
m.Rules[rule.ID] = rule
|
|
return nil
|
|
}
|
|
|
|
func (m *mockPolicyRepo) DeleteRule(ctx context.Context, id string) error {
|
|
if m.DeleteRuleErr != nil {
|
|
return m.DeleteRuleErr
|
|
}
|
|
delete(m.Rules, id)
|
|
return nil
|
|
}
|
|
|
|
func (m *mockPolicyRepo) CreateViolation(ctx context.Context, violation *domain.PolicyViolation) error {
|
|
if m.CreateViolationErr != nil {
|
|
return m.CreateViolationErr
|
|
}
|
|
m.Violations = append(m.Violations, violation)
|
|
return nil
|
|
}
|
|
|
|
func (m *mockPolicyRepo) ListViolations(ctx context.Context, filter *repository.AuditFilter) ([]*domain.PolicyViolation, error) {
|
|
if m.ListViolationsErr != nil {
|
|
return nil, m.ListViolationsErr
|
|
}
|
|
return m.Violations, nil
|
|
}
|
|
|
|
func (m *mockPolicyRepo) AddRule(rule *domain.PolicyRule) {
|
|
m.Rules[rule.ID] = rule
|
|
}
|
|
|
|
// mockRenewalPolicyRepo is a test implementation of RenewalPolicyRepository
|
|
type mockRenewalPolicyRepo struct {
|
|
Policies map[string]*domain.RenewalPolicy
|
|
GetErr error
|
|
ListErr error
|
|
}
|
|
|
|
func (m *mockRenewalPolicyRepo) Get(ctx context.Context, id string) (*domain.RenewalPolicy, error) {
|
|
if m.GetErr != nil {
|
|
return nil, m.GetErr
|
|
}
|
|
policy, ok := m.Policies[id]
|
|
if !ok {
|
|
return nil, errNotFound
|
|
}
|
|
return policy, nil
|
|
}
|
|
|
|
func (m *mockRenewalPolicyRepo) List(ctx context.Context) ([]*domain.RenewalPolicy, error) {
|
|
if m.ListErr != nil {
|
|
return nil, m.ListErr
|
|
}
|
|
var policies []*domain.RenewalPolicy
|
|
for _, p := range m.Policies {
|
|
policies = append(policies, p)
|
|
}
|
|
return policies, nil
|
|
}
|
|
|
|
func (m *mockRenewalPolicyRepo) AddPolicy(policy *domain.RenewalPolicy) {
|
|
m.Policies[policy.ID] = policy
|
|
}
|
|
|
|
// mockAgentRepo is a test implementation of AgentRepository
|
|
type mockAgentRepo struct {
|
|
Agents map[string]*domain.Agent
|
|
HeartbeatUpdates map[string]time.Time
|
|
CreateErr error
|
|
UpdateErr error
|
|
DeleteErr error
|
|
GetErr error
|
|
ListErr error
|
|
UpdateHeartbeatErr error
|
|
GetByAPIKeyErr error
|
|
}
|
|
|
|
func (m *mockAgentRepo) List(ctx context.Context) ([]*domain.Agent, error) {
|
|
if m.ListErr != nil {
|
|
return nil, m.ListErr
|
|
}
|
|
var agents []*domain.Agent
|
|
for _, a := range m.Agents {
|
|
agents = append(agents, a)
|
|
}
|
|
return agents, nil
|
|
}
|
|
|
|
func (m *mockAgentRepo) Get(ctx context.Context, id string) (*domain.Agent, error) {
|
|
if m.GetErr != nil {
|
|
return nil, m.GetErr
|
|
}
|
|
agent, ok := m.Agents[id]
|
|
if !ok {
|
|
return nil, errNotFound
|
|
}
|
|
return agent, nil
|
|
}
|
|
|
|
func (m *mockAgentRepo) Create(ctx context.Context, agent *domain.Agent) error {
|
|
if m.CreateErr != nil {
|
|
return m.CreateErr
|
|
}
|
|
m.Agents[agent.ID] = agent
|
|
return nil
|
|
}
|
|
|
|
func (m *mockAgentRepo) Update(ctx context.Context, agent *domain.Agent) error {
|
|
if m.UpdateErr != nil {
|
|
return m.UpdateErr
|
|
}
|
|
m.Agents[agent.ID] = agent
|
|
return nil
|
|
}
|
|
|
|
func (m *mockAgentRepo) Delete(ctx context.Context, id string) error {
|
|
if m.DeleteErr != nil {
|
|
return m.DeleteErr
|
|
}
|
|
delete(m.Agents, id)
|
|
return nil
|
|
}
|
|
|
|
func (m *mockAgentRepo) UpdateHeartbeat(ctx context.Context, id string, metadata *domain.AgentMetadata) error {
|
|
if m.UpdateHeartbeatErr != nil {
|
|
return m.UpdateHeartbeatErr
|
|
}
|
|
agent, ok := m.Agents[id]
|
|
if !ok {
|
|
return errNotFound
|
|
}
|
|
now := time.Now()
|
|
agent.LastHeartbeatAt = &now
|
|
m.HeartbeatUpdates[id] = now
|
|
return nil
|
|
}
|
|
|
|
func (m *mockAgentRepo) GetByAPIKey(ctx context.Context, keyHash string) (*domain.Agent, error) {
|
|
if m.GetByAPIKeyErr != nil {
|
|
return nil, m.GetByAPIKeyErr
|
|
}
|
|
for _, a := range m.Agents {
|
|
if a.APIKeyHash == keyHash {
|
|
return a, nil
|
|
}
|
|
}
|
|
return nil, errNotFound
|
|
}
|
|
|
|
func (m *mockAgentRepo) AddAgent(agent *domain.Agent) {
|
|
m.Agents[agent.ID] = agent
|
|
}
|
|
|
|
// mockTargetRepo is a test implementation of TargetRepository
|
|
type mockTargetRepo struct {
|
|
Targets map[string]*domain.DeploymentTarget
|
|
CreateErr error
|
|
UpdateErr error
|
|
DeleteErr error
|
|
GetErr error
|
|
ListErr error
|
|
ListByCertErr error
|
|
}
|
|
|
|
func (m *mockTargetRepo) List(ctx context.Context) ([]*domain.DeploymentTarget, error) {
|
|
if m.ListErr != nil {
|
|
return nil, m.ListErr
|
|
}
|
|
var targets []*domain.DeploymentTarget
|
|
for _, t := range m.Targets {
|
|
targets = append(targets, t)
|
|
}
|
|
return targets, nil
|
|
}
|
|
|
|
func (m *mockTargetRepo) Get(ctx context.Context, id string) (*domain.DeploymentTarget, error) {
|
|
if m.GetErr != nil {
|
|
return nil, m.GetErr
|
|
}
|
|
target, ok := m.Targets[id]
|
|
if !ok {
|
|
return nil, errNotFound
|
|
}
|
|
return target, nil
|
|
}
|
|
|
|
func (m *mockTargetRepo) Create(ctx context.Context, target *domain.DeploymentTarget) error {
|
|
if m.CreateErr != nil {
|
|
return m.CreateErr
|
|
}
|
|
m.Targets[target.ID] = target
|
|
return nil
|
|
}
|
|
|
|
func (m *mockTargetRepo) Update(ctx context.Context, target *domain.DeploymentTarget) error {
|
|
if m.UpdateErr != nil {
|
|
return m.UpdateErr
|
|
}
|
|
m.Targets[target.ID] = target
|
|
return nil
|
|
}
|
|
|
|
func (m *mockTargetRepo) Delete(ctx context.Context, id string) error {
|
|
if m.DeleteErr != nil {
|
|
return m.DeleteErr
|
|
}
|
|
delete(m.Targets, id)
|
|
return nil
|
|
}
|
|
|
|
func (m *mockTargetRepo) ListByCertificate(ctx context.Context, certID string) ([]*domain.DeploymentTarget, error) {
|
|
if m.ListByCertErr != nil {
|
|
return nil, m.ListByCertErr
|
|
}
|
|
return m.List(ctx)
|
|
}
|
|
|
|
func (m *mockTargetRepo) AddTarget(target *domain.DeploymentTarget) {
|
|
m.Targets[target.ID] = target
|
|
}
|
|
|
|
// mockIssuerConnector is a test implementation of IssuerConnector
|
|
type mockIssuerConnector struct {
|
|
Result *IssuanceResult
|
|
Err error
|
|
}
|
|
|
|
func (m *mockIssuerConnector) IssueCertificate(ctx context.Context, commonName string, sans []string, csrPEM string) (*IssuanceResult, error) {
|
|
if m.Err != nil {
|
|
return nil, m.Err
|
|
}
|
|
if m.Result != nil {
|
|
return m.Result, nil
|
|
}
|
|
now := time.Now()
|
|
return &IssuanceResult{
|
|
Serial: "test-serial-123",
|
|
CertPEM: "-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----",
|
|
ChainPEM: "-----BEGIN CERTIFICATE-----\nchain\n-----END CERTIFICATE-----",
|
|
NotBefore: now,
|
|
NotAfter: now.AddDate(1, 0, 0),
|
|
}, nil
|
|
}
|
|
|
|
func (m *mockIssuerConnector) RenewCertificate(ctx context.Context, commonName string, sans []string, csrPEM string) (*IssuanceResult, error) {
|
|
if m.Err != nil {
|
|
return nil, m.Err
|
|
}
|
|
return m.IssueCertificate(ctx, commonName, sans, csrPEM)
|
|
}
|
|
|
|
// Constructor functions for mocks
|
|
|
|
func newMockCertificateRepository() *mockCertRepo {
|
|
return &mockCertRepo{
|
|
Certs: make(map[string]*domain.ManagedCertificate),
|
|
Versions: make(map[string][]*domain.CertificateVersion),
|
|
}
|
|
}
|
|
|
|
func newMockJobRepository() *mockJobRepo {
|
|
return &mockJobRepo{
|
|
Jobs: make(map[string]*domain.Job),
|
|
StatusUpdates: make(map[string]domain.JobStatus),
|
|
}
|
|
}
|
|
|
|
func newMockNotificationRepository() *mockNotifRepo {
|
|
return &mockNotifRepo{
|
|
Notifications: make([]*domain.NotificationEvent, 0),
|
|
}
|
|
}
|
|
|
|
func newMockAuditRepository() *mockAuditRepo {
|
|
return &mockAuditRepo{
|
|
Events: make([]*domain.AuditEvent, 0),
|
|
}
|
|
}
|
|
|
|
func newMockPolicyRepository() *mockPolicyRepo {
|
|
return &mockPolicyRepo{
|
|
Rules: make(map[string]*domain.PolicyRule),
|
|
Violations: make([]*domain.PolicyViolation, 0),
|
|
}
|
|
}
|
|
|
|
func newMockRenewalPolicyRepository() *mockRenewalPolicyRepo {
|
|
return &mockRenewalPolicyRepo{
|
|
Policies: make(map[string]*domain.RenewalPolicy),
|
|
}
|
|
}
|
|
|
|
func newMockAgentRepository() *mockAgentRepo {
|
|
return &mockAgentRepo{
|
|
Agents: make(map[string]*domain.Agent),
|
|
HeartbeatUpdates: make(map[string]time.Time),
|
|
}
|
|
}
|
|
|
|
func newMockTargetRepository() *mockTargetRepo {
|
|
return &mockTargetRepo{
|
|
Targets: make(map[string]*domain.DeploymentTarget),
|
|
}
|
|
}
|
|
|
|
func newMockIssuerRepository() *mockIssuerRepository {
|
|
return &mockIssuerRepository{
|
|
issuers: make(map[string]*domain.Issuer),
|
|
}
|
|
}
|
|
|
|
// mockIssuerRepository is a test implementation of IssuerRepository
|
|
type mockIssuerRepository struct {
|
|
issuers map[string]*domain.Issuer
|
|
GetErr error
|
|
ListErr error
|
|
CreateErr error
|
|
UpdateErr error
|
|
DeleteErr error
|
|
}
|
|
|
|
func (m *mockIssuerRepository) List(ctx context.Context) ([]*domain.Issuer, error) {
|
|
if m.ListErr != nil {
|
|
return nil, m.ListErr
|
|
}
|
|
var issuers []*domain.Issuer
|
|
for _, i := range m.issuers {
|
|
issuers = append(issuers, i)
|
|
}
|
|
return issuers, nil
|
|
}
|
|
|
|
func (m *mockIssuerRepository) Get(ctx context.Context, id string) (*domain.Issuer, error) {
|
|
if m.GetErr != nil {
|
|
return nil, m.GetErr
|
|
}
|
|
issuer, ok := m.issuers[id]
|
|
if !ok {
|
|
return nil, errNotFound
|
|
}
|
|
return issuer, nil
|
|
}
|
|
|
|
func (m *mockIssuerRepository) Create(ctx context.Context, issuer *domain.Issuer) error {
|
|
if m.CreateErr != nil {
|
|
return m.CreateErr
|
|
}
|
|
m.issuers[issuer.ID] = issuer
|
|
return nil
|
|
}
|
|
|
|
func (m *mockIssuerRepository) Update(ctx context.Context, issuer *domain.Issuer) error {
|
|
if m.UpdateErr != nil {
|
|
return m.UpdateErr
|
|
}
|
|
m.issuers[issuer.ID] = issuer
|
|
return nil
|
|
}
|
|
|
|
func (m *mockIssuerRepository) Delete(ctx context.Context, id string) error {
|
|
if m.DeleteErr != nil {
|
|
return m.DeleteErr
|
|
}
|
|
delete(m.issuers, id)
|
|
return nil
|
|
}
|
|
|
|
func (m *mockIssuerRepository) AddIssuer(issuer *domain.Issuer) {
|
|
m.issuers[issuer.ID] = issuer
|
|
}
|
|
|
|
// mockNotifier is a simple notifier for testing
|
|
type mockNotifier struct {
|
|
messages []*mockNotifierMessage
|
|
SendErr error
|
|
}
|
|
|
|
type mockNotifierMessage struct {
|
|
Recipient string
|
|
Subject string
|
|
Body string
|
|
}
|
|
|
|
func newMockNotifier() *mockNotifier {
|
|
return &mockNotifier{
|
|
messages: make([]*mockNotifierMessage, 0),
|
|
}
|
|
}
|
|
|
|
func (m *mockNotifier) Send(ctx context.Context, recipient string, subject string, body string) error {
|
|
if m.SendErr != nil {
|
|
return m.SendErr
|
|
}
|
|
m.messages = append(m.messages, &mockNotifierMessage{
|
|
Recipient: recipient,
|
|
Subject: subject,
|
|
Body: body,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func (m *mockNotifier) Channel() string {
|
|
return "Email"
|
|
}
|
|
|
|
func (m *mockNotifier) getSentCount() int {
|
|
return len(m.messages)
|
|
}
|
|
|
|
func (m *mockNotifier) getLastMessage() *mockNotifierMessage {
|
|
if len(m.messages) == 0 {
|
|
return nil
|
|
}
|
|
return m.messages[len(m.messages)-1]
|
|
}
|