mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 14:11:31 +00:00
5553568495
Service layer (63 tests): certificate, agent, audit, job, notification, policy, and renewal services with mock repositories covering threshold alerting, deduplication, status transitions, and job processing. Handler layer (46 tests): certificate and agent HTTP handlers using httptest with mock service interfaces, covering success/error paths, pagination, JSON marshaling, and path parameter extraction. Integration (11 subtests): end-to-end certificate lifecycle test exercising real services and Local CA issuer through HTTP API — create cert, trigger renewal, process jobs, register agent, heartbeat, verify audit trail. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
330 lines
8.0 KiB
Go
330 lines
8.0 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/shankar0123/certctl/internal/domain"
|
|
"github.com/shankar0123/certctl/internal/repository"
|
|
)
|
|
|
|
func TestRecordEvent(t *testing.T) {
|
|
ctx := context.Background()
|
|
auditRepo := &mockAuditRepo{
|
|
Events: []*domain.AuditEvent{},
|
|
}
|
|
service := NewAuditService(auditRepo)
|
|
|
|
err := service.RecordEvent(ctx, "user123", domain.ActorTypeUser, "certificate_created", "certificate", "cert-001", map[string]interface{}{"common_name": "example.com"})
|
|
if err != nil {
|
|
t.Fatalf("RecordEvent failed: %v", err)
|
|
}
|
|
|
|
if len(auditRepo.Events) != 1 {
|
|
t.Errorf("expected 1 event, got %d", len(auditRepo.Events))
|
|
}
|
|
|
|
event := auditRepo.Events[0]
|
|
if event.Actor != "user123" {
|
|
t.Errorf("expected actor user123, got %s", event.Actor)
|
|
}
|
|
if event.ActorType != domain.ActorTypeUser {
|
|
t.Errorf("expected actor type User, got %s", event.ActorType)
|
|
}
|
|
if event.Action != "certificate_created" {
|
|
t.Errorf("expected action certificate_created, got %s", event.Action)
|
|
}
|
|
if event.ResourceType != "certificate" {
|
|
t.Errorf("expected resource type certificate, got %s", event.ResourceType)
|
|
}
|
|
if event.ResourceID != "cert-001" {
|
|
t.Errorf("expected resource ID cert-001, got %s", event.ResourceID)
|
|
}
|
|
}
|
|
|
|
func TestRecordEvent_RepoError(t *testing.T) {
|
|
ctx := context.Background()
|
|
auditRepo := &mockAuditRepo{
|
|
Events: []*domain.AuditEvent{},
|
|
CreateErr: errNotFound,
|
|
}
|
|
service := NewAuditService(auditRepo)
|
|
|
|
err := service.RecordEvent(ctx, "user123", domain.ActorTypeUser, "test_action", "resource", "res-001", map[string]interface{}{})
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
}
|
|
|
|
func TestListByResource(t *testing.T) {
|
|
ctx := context.Background()
|
|
auditRepo := &mockAuditRepo{
|
|
Events: []*domain.AuditEvent{},
|
|
}
|
|
service := NewAuditService(auditRepo)
|
|
|
|
event1 := &domain.AuditEvent{
|
|
ID: "audit-1",
|
|
Actor: "user1",
|
|
ActorType: domain.ActorTypeUser,
|
|
Action: "created",
|
|
ResourceType: "certificate",
|
|
ResourceID: "cert-001",
|
|
Timestamp: time.Now(),
|
|
}
|
|
event2 := &domain.AuditEvent{
|
|
ID: "audit-2",
|
|
Actor: "user2",
|
|
ActorType: domain.ActorTypeUser,
|
|
Action: "updated",
|
|
ResourceType: "certificate",
|
|
ResourceID: "cert-001",
|
|
Timestamp: time.Now(),
|
|
}
|
|
event3 := &domain.AuditEvent{
|
|
ID: "audit-3",
|
|
Actor: "user1",
|
|
ActorType: domain.ActorTypeUser,
|
|
Action: "created",
|
|
ResourceType: "certificate",
|
|
ResourceID: "cert-002",
|
|
Timestamp: time.Now(),
|
|
}
|
|
|
|
auditRepo.AddEvent(event1)
|
|
auditRepo.AddEvent(event2)
|
|
auditRepo.AddEvent(event3)
|
|
|
|
events, err := service.ListByResource(ctx, "certificate", "cert-001")
|
|
if err != nil {
|
|
t.Fatalf("ListByResource failed: %v", err)
|
|
}
|
|
|
|
if len(events) != 2 {
|
|
t.Errorf("expected 2 events, got %d", len(events))
|
|
}
|
|
}
|
|
|
|
func TestListByActor(t *testing.T) {
|
|
ctx := context.Background()
|
|
auditRepo := &mockAuditRepo{
|
|
Events: []*domain.AuditEvent{},
|
|
}
|
|
service := NewAuditService(auditRepo)
|
|
|
|
event1 := &domain.AuditEvent{
|
|
ID: "audit-1",
|
|
Actor: "user1",
|
|
ActorType: domain.ActorTypeUser,
|
|
Action: "created",
|
|
ResourceType: "certificate",
|
|
ResourceID: "cert-001",
|
|
Timestamp: time.Now(),
|
|
}
|
|
event2 := &domain.AuditEvent{
|
|
ID: "audit-2",
|
|
Actor: "user1",
|
|
ActorType: domain.ActorTypeUser,
|
|
Action: "updated",
|
|
ResourceType: "certificate",
|
|
ResourceID: "cert-002",
|
|
Timestamp: time.Now(),
|
|
}
|
|
event3 := &domain.AuditEvent{
|
|
ID: "audit-3",
|
|
Actor: "user2",
|
|
ActorType: domain.ActorTypeUser,
|
|
Action: "created",
|
|
ResourceType: "certificate",
|
|
ResourceID: "cert-003",
|
|
Timestamp: time.Now(),
|
|
}
|
|
|
|
auditRepo.AddEvent(event1)
|
|
auditRepo.AddEvent(event2)
|
|
auditRepo.AddEvent(event3)
|
|
|
|
events, err := service.ListByActor(ctx, "user1")
|
|
if err != nil {
|
|
t.Fatalf("ListByActor failed: %v", err)
|
|
}
|
|
|
|
if len(events) != 2 {
|
|
t.Errorf("expected 2 events, got %d", len(events))
|
|
}
|
|
}
|
|
|
|
func TestListByAction(t *testing.T) {
|
|
ctx := context.Background()
|
|
auditRepo := &mockAuditRepo{
|
|
Events: []*domain.AuditEvent{},
|
|
}
|
|
service := NewAuditService(auditRepo)
|
|
|
|
now := time.Now()
|
|
from := now.Add(-1 * time.Hour)
|
|
to := now.Add(1 * time.Hour)
|
|
|
|
event1 := &domain.AuditEvent{
|
|
ID: "audit-1",
|
|
Actor: "user1",
|
|
ActorType: domain.ActorTypeUser,
|
|
Action: "certificate_created",
|
|
ResourceType: "certificate",
|
|
ResourceID: "cert-001",
|
|
Timestamp: now.Add(-30 * time.Minute),
|
|
}
|
|
event2 := &domain.AuditEvent{
|
|
ID: "audit-2",
|
|
Actor: "user2",
|
|
ActorType: domain.ActorTypeUser,
|
|
Action: "certificate_created",
|
|
ResourceType: "certificate",
|
|
ResourceID: "cert-002",
|
|
Timestamp: now.Add(-20 * time.Minute),
|
|
}
|
|
event3 := &domain.AuditEvent{
|
|
ID: "audit-3",
|
|
Actor: "user1",
|
|
ActorType: domain.ActorTypeUser,
|
|
Action: "certificate_updated",
|
|
ResourceType: "certificate",
|
|
ResourceID: "cert-001",
|
|
Timestamp: now.Add(-10 * time.Minute),
|
|
}
|
|
|
|
auditRepo.AddEvent(event1)
|
|
auditRepo.AddEvent(event2)
|
|
auditRepo.AddEvent(event3)
|
|
|
|
events, err := service.ListByAction(ctx, "certificate_created", from, to)
|
|
if err != nil {
|
|
t.Fatalf("ListByAction failed: %v", err)
|
|
}
|
|
|
|
if len(events) != 2 {
|
|
t.Errorf("expected 2 events, got %d", len(events))
|
|
}
|
|
|
|
for _, e := range events {
|
|
if e.Action != "certificate_created" {
|
|
t.Errorf("expected action certificate_created, got %s", e.Action)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestListByAction_EmptyRange(t *testing.T) {
|
|
ctx := context.Background()
|
|
auditRepo := &mockAuditRepo{
|
|
Events: []*domain.AuditEvent{},
|
|
}
|
|
service := NewAuditService(auditRepo)
|
|
|
|
now := time.Now()
|
|
from := now.Add(1 * time.Hour)
|
|
to := now.Add(2 * time.Hour)
|
|
|
|
event := &domain.AuditEvent{
|
|
ID: "audit-1",
|
|
Actor: "user1",
|
|
ActorType: domain.ActorTypeUser,
|
|
Action: "certificate_created",
|
|
ResourceType: "certificate",
|
|
ResourceID: "cert-001",
|
|
Timestamp: now.Add(-30 * time.Minute),
|
|
}
|
|
auditRepo.AddEvent(event)
|
|
|
|
events, err := service.ListByAction(ctx, "certificate_created", from, to)
|
|
if err != nil {
|
|
t.Fatalf("ListByAction failed: %v", err)
|
|
}
|
|
|
|
if len(events) != 0 {
|
|
t.Errorf("expected 0 events, got %d", len(events))
|
|
}
|
|
}
|
|
|
|
func TestRecordEvent_ComplexDetails(t *testing.T) {
|
|
ctx := context.Background()
|
|
auditRepo := &mockAuditRepo{
|
|
Events: []*domain.AuditEvent{},
|
|
}
|
|
service := NewAuditService(auditRepo)
|
|
|
|
details := map[string]interface{}{
|
|
"common_name": "example.com",
|
|
"sans": []string{"www.example.com", "api.example.com"},
|
|
"issuer_id": "iss-123",
|
|
"count": 5,
|
|
}
|
|
|
|
err := service.RecordEvent(ctx, "user1", domain.ActorTypeUser, "certificate_created", "certificate", "cert-001", details)
|
|
if err != nil {
|
|
t.Fatalf("RecordEvent failed: %v", err)
|
|
}
|
|
|
|
event := auditRepo.Events[0]
|
|
var decoded map[string]interface{}
|
|
err = json.Unmarshal(event.Details, &decoded)
|
|
if err != nil {
|
|
t.Fatalf("failed to unmarshal details: %v", err)
|
|
}
|
|
|
|
if decoded["common_name"] != "example.com" {
|
|
t.Errorf("expected common_name example.com, got %v", decoded["common_name"])
|
|
}
|
|
}
|
|
|
|
func TestList(t *testing.T) {
|
|
ctx := context.Background()
|
|
auditRepo := &mockAuditRepo{
|
|
Events: []*domain.AuditEvent{},
|
|
}
|
|
service := NewAuditService(auditRepo)
|
|
|
|
for i := 0; i < 5; i++ {
|
|
event := &domain.AuditEvent{
|
|
ID: "audit-" + string(rune(i)),
|
|
Actor: "user1",
|
|
ActorType: domain.ActorTypeUser,
|
|
Action: "test",
|
|
ResourceType: "certificate",
|
|
ResourceID: "cert-001",
|
|
Timestamp: time.Now(),
|
|
}
|
|
auditRepo.AddEvent(event)
|
|
}
|
|
|
|
filter := &repository.AuditFilter{
|
|
Page: 1,
|
|
PerPage: 10,
|
|
}
|
|
|
|
events, err := service.List(ctx, filter)
|
|
if err != nil {
|
|
t.Fatalf("List failed: %v", err)
|
|
}
|
|
|
|
if len(events) != 5 {
|
|
t.Errorf("expected 5 events, got %d", len(events))
|
|
}
|
|
}
|
|
|
|
func TestList_RepoError(t *testing.T) {
|
|
ctx := context.Background()
|
|
auditRepo := &mockAuditRepo{
|
|
ListErr: errNotFound,
|
|
}
|
|
service := NewAuditService(auditRepo)
|
|
|
|
filter := &repository.AuditFilter{}
|
|
|
|
_, err := service.List(ctx, filter)
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
}
|