mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-11 13:48:51 +00:00
test: comprehensive test expansion — 330+ to 525+ tests, close M11b coverage gaps
Add 195+ new tests across service, handler, connector, and integration layers: - Service tests: team (23), owner (21), agent_group (25), issuer (18), issuer_adapter (6) - Handler tests: teams (26), owners (21) - NGINX target connector tests (13): config validation, deployment, reload - Integration tests: 19 M11b endpoint subtests (teams, owners, agent groups CRUD) - CI pipeline: add ./internal/connector/target/... to test coverage path - Docs: update test counts to 525+ across README, architecture, CLAUDE.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,699 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/shankar0123/certctl/internal/domain"
|
||||
)
|
||||
|
||||
// mockAgentGroupRepo is a test implementation of AgentGroupRepository
|
||||
type mockAgentGroupRepo struct {
|
||||
groups map[string]*domain.AgentGroup
|
||||
members map[string][]*domain.Agent
|
||||
CreateErr error
|
||||
UpdateErr error
|
||||
DeleteErr error
|
||||
GetErr error
|
||||
ListErr error
|
||||
ListMembersErr error
|
||||
AddMemberErr error
|
||||
RemoveMemberErr error
|
||||
}
|
||||
|
||||
func newMockAgentGroupRepository() *mockAgentGroupRepo {
|
||||
return &mockAgentGroupRepo{
|
||||
groups: make(map[string]*domain.AgentGroup),
|
||||
members: make(map[string][]*domain.Agent),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mockAgentGroupRepo) List(ctx context.Context) ([]*domain.AgentGroup, error) {
|
||||
if m.ListErr != nil {
|
||||
return nil, m.ListErr
|
||||
}
|
||||
var groups []*domain.AgentGroup
|
||||
for _, g := range m.groups {
|
||||
groups = append(groups, g)
|
||||
}
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func (m *mockAgentGroupRepo) Get(ctx context.Context, id string) (*domain.AgentGroup, error) {
|
||||
if m.GetErr != nil {
|
||||
return nil, m.GetErr
|
||||
}
|
||||
group, ok := m.groups[id]
|
||||
if !ok {
|
||||
return nil, errNotFound
|
||||
}
|
||||
return group, nil
|
||||
}
|
||||
|
||||
func (m *mockAgentGroupRepo) Create(ctx context.Context, group *domain.AgentGroup) error {
|
||||
if m.CreateErr != nil {
|
||||
return m.CreateErr
|
||||
}
|
||||
m.groups[group.ID] = group
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockAgentGroupRepo) Update(ctx context.Context, group *domain.AgentGroup) error {
|
||||
if m.UpdateErr != nil {
|
||||
return m.UpdateErr
|
||||
}
|
||||
m.groups[group.ID] = group
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockAgentGroupRepo) Delete(ctx context.Context, id string) error {
|
||||
if m.DeleteErr != nil {
|
||||
return m.DeleteErr
|
||||
}
|
||||
delete(m.groups, id)
|
||||
delete(m.members, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockAgentGroupRepo) ListMembers(ctx context.Context, groupID string) ([]*domain.Agent, error) {
|
||||
if m.ListMembersErr != nil {
|
||||
return nil, m.ListMembersErr
|
||||
}
|
||||
members := m.members[groupID]
|
||||
if members == nil {
|
||||
return make([]*domain.Agent, 0), nil
|
||||
}
|
||||
return members, nil
|
||||
}
|
||||
|
||||
func (m *mockAgentGroupRepo) AddMember(ctx context.Context, groupID, agentID, membershipType string) error {
|
||||
if m.AddMemberErr != nil {
|
||||
return m.AddMemberErr
|
||||
}
|
||||
// For testing purposes, we'll assume a simple mock agent
|
||||
agent := &domain.Agent{
|
||||
ID: agentID,
|
||||
Name: "test-agent-" + agentID,
|
||||
}
|
||||
m.members[groupID] = append(m.members[groupID], agent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockAgentGroupRepo) RemoveMember(ctx context.Context, groupID, agentID string) error {
|
||||
if m.RemoveMemberErr != nil {
|
||||
return m.RemoveMemberErr
|
||||
}
|
||||
members := m.members[groupID]
|
||||
var filtered []*domain.Agent
|
||||
for _, m := range members {
|
||||
if m.ID != agentID {
|
||||
filtered = append(filtered, m)
|
||||
}
|
||||
}
|
||||
m.members[groupID] = filtered
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockAgentGroupRepo) AddGroup(group *domain.AgentGroup) {
|
||||
m.groups[group.ID] = group
|
||||
}
|
||||
|
||||
func (m *mockAgentGroupRepo) AddGroupMembers(groupID string, agents []*domain.Agent) {
|
||||
m.members[groupID] = agents
|
||||
}
|
||||
|
||||
// Test: ListAgentGroups returns groups
|
||||
func TestAgentGroupService_ListAgentGroups(t *testing.T) {
|
||||
repo := newMockAgentGroupRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
svc := NewAgentGroupService(repo, auditSvc)
|
||||
|
||||
group1 := &domain.AgentGroup{
|
||||
ID: "ag-test-1",
|
||||
Name: "Linux Servers",
|
||||
}
|
||||
group2 := &domain.AgentGroup{
|
||||
ID: "ag-test-2",
|
||||
Name: "Windows Servers",
|
||||
}
|
||||
repo.AddGroup(group1)
|
||||
repo.AddGroup(group2)
|
||||
|
||||
groups, total, err := svc.ListAgentGroups(1, 50)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if total != 2 {
|
||||
t.Errorf("expected total=2, got %d", total)
|
||||
}
|
||||
if len(groups) != 2 {
|
||||
t.Errorf("expected 2 groups, got %d", len(groups))
|
||||
}
|
||||
}
|
||||
|
||||
// Test: ListAgentGroups with default pagination
|
||||
func TestAgentGroupService_ListAgentGroups_DefaultPagination(t *testing.T) {
|
||||
repo := newMockAgentGroupRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
svc := NewAgentGroupService(repo, auditSvc)
|
||||
|
||||
group := &domain.AgentGroup{
|
||||
ID: "ag-test-1",
|
||||
Name: "Test Group",
|
||||
}
|
||||
repo.AddGroup(group)
|
||||
|
||||
// page < 1 should default to 1, perPage < 1 should default to 50
|
||||
groups, total, err := svc.ListAgentGroups(-1, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if total != 1 {
|
||||
t.Errorf("expected total=1, got %d", total)
|
||||
}
|
||||
if len(groups) != 1 {
|
||||
t.Errorf("expected 1 group, got %d", len(groups))
|
||||
}
|
||||
}
|
||||
|
||||
// Test: ListAgentGroups with repository error
|
||||
func TestAgentGroupService_ListAgentGroups_RepositoryError(t *testing.T) {
|
||||
repo := newMockAgentGroupRepository()
|
||||
repo.ListErr = errors.New("database error")
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
svc := NewAgentGroupService(repo, auditSvc)
|
||||
|
||||
_, _, err := svc.ListAgentGroups(1, 50)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "failed to list agent groups") {
|
||||
t.Errorf("expected 'failed to list agent groups' in error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: ListAgentGroups with empty result
|
||||
func TestAgentGroupService_ListAgentGroups_EmptyResult(t *testing.T) {
|
||||
repo := newMockAgentGroupRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
svc := NewAgentGroupService(repo, auditSvc)
|
||||
|
||||
groups, total, err := svc.ListAgentGroups(1, 50)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if total != 0 {
|
||||
t.Errorf("expected total=0, got %d", total)
|
||||
}
|
||||
if len(groups) != 0 {
|
||||
t.Errorf("expected 0 groups, got %d", len(groups))
|
||||
}
|
||||
}
|
||||
|
||||
// Test: GetAgentGroup success
|
||||
func TestAgentGroupService_GetAgentGroup(t *testing.T) {
|
||||
repo := newMockAgentGroupRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
svc := NewAgentGroupService(repo, auditSvc)
|
||||
|
||||
group := &domain.AgentGroup{
|
||||
ID: "ag-test-1",
|
||||
Name: "Test Group",
|
||||
}
|
||||
repo.AddGroup(group)
|
||||
|
||||
retrieved, err := svc.GetAgentGroup("ag-test-1")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if retrieved == nil {
|
||||
t.Fatal("expected group, got nil")
|
||||
}
|
||||
if retrieved.ID != "ag-test-1" {
|
||||
t.Errorf("expected ID 'ag-test-1', got %s", retrieved.ID)
|
||||
}
|
||||
if retrieved.Name != "Test Group" {
|
||||
t.Errorf("expected name 'Test Group', got %s", retrieved.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: GetAgentGroup not found
|
||||
func TestAgentGroupService_GetAgentGroup_NotFound(t *testing.T) {
|
||||
repo := newMockAgentGroupRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
svc := NewAgentGroupService(repo, auditSvc)
|
||||
|
||||
_, err := svc.GetAgentGroup("ag-nonexistent")
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !errors.Is(err, errNotFound) {
|
||||
t.Errorf("expected errNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: CreateAgentGroup success with ID generated and timestamps
|
||||
func TestAgentGroupService_CreateAgentGroup(t *testing.T) {
|
||||
repo := newMockAgentGroupRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
svc := NewAgentGroupService(repo, auditSvc)
|
||||
|
||||
group := domain.AgentGroup{
|
||||
Name: "Test Group",
|
||||
}
|
||||
before := time.Now()
|
||||
|
||||
created, err := svc.CreateAgentGroup(group)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if created == nil {
|
||||
t.Fatal("expected group, got nil")
|
||||
}
|
||||
|
||||
// ID should be generated
|
||||
if created.ID == "" {
|
||||
t.Fatal("expected ID to be generated, got empty string")
|
||||
}
|
||||
if !strings.HasPrefix(created.ID, "ag-") {
|
||||
t.Errorf("expected ID to start with 'ag-', got %s", created.ID)
|
||||
}
|
||||
|
||||
// Timestamps should be set
|
||||
if created.CreatedAt.IsZero() {
|
||||
t.Fatal("expected CreatedAt to be set")
|
||||
}
|
||||
if created.UpdatedAt.IsZero() {
|
||||
t.Fatal("expected UpdatedAt to be set")
|
||||
}
|
||||
if created.CreatedAt.Before(before) {
|
||||
t.Errorf("expected CreatedAt >= before, got %v < %v", created.CreatedAt, before)
|
||||
}
|
||||
|
||||
// Should be in repository
|
||||
retrieved, err := repo.Get(context.Background(), created.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if retrieved.ID != created.ID {
|
||||
t.Errorf("expected ID %s, got %s", created.ID, retrieved.ID)
|
||||
}
|
||||
|
||||
// Audit event should be recorded
|
||||
if len(auditRepo.Events) == 0 {
|
||||
t.Fatal("expected audit event to be recorded")
|
||||
}
|
||||
if auditRepo.Events[0].Action != "create_agent_group" {
|
||||
t.Errorf("expected action 'create_agent_group', got %s", auditRepo.Events[0].Action)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: CreateAgentGroup with empty name
|
||||
func TestAgentGroupService_CreateAgentGroup_EmptyName(t *testing.T) {
|
||||
repo := newMockAgentGroupRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
svc := NewAgentGroupService(repo, auditSvc)
|
||||
|
||||
group := domain.AgentGroup{
|
||||
Name: "",
|
||||
}
|
||||
|
||||
_, err := svc.CreateAgentGroup(group)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "agent group name is required") {
|
||||
t.Errorf("expected 'agent group name is required' in error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: CreateAgentGroup with name too long
|
||||
func TestAgentGroupService_CreateAgentGroup_NameTooLong(t *testing.T) {
|
||||
repo := newMockAgentGroupRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
svc := NewAgentGroupService(repo, auditSvc)
|
||||
|
||||
group := domain.AgentGroup{
|
||||
Name: strings.Repeat("a", 256),
|
||||
}
|
||||
|
||||
_, err := svc.CreateAgentGroup(group)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "exceeds 255 characters") {
|
||||
t.Errorf("expected 'exceeds 255 characters' in error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: CreateAgentGroup with existing ID preserves ID
|
||||
func TestAgentGroupService_CreateAgentGroup_WithExistingID(t *testing.T) {
|
||||
repo := newMockAgentGroupRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
svc := NewAgentGroupService(repo, auditSvc)
|
||||
|
||||
group := domain.AgentGroup{
|
||||
ID: "ag-custom-id",
|
||||
Name: "Test Group",
|
||||
}
|
||||
|
||||
created, err := svc.CreateAgentGroup(group)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if created.ID != "ag-custom-id" {
|
||||
t.Errorf("expected ID 'ag-custom-id', got %s", created.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: CreateAgentGroup with dynamic criteria
|
||||
func TestAgentGroupService_CreateAgentGroup_WithDynamicCriteria(t *testing.T) {
|
||||
repo := newMockAgentGroupRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
svc := NewAgentGroupService(repo, auditSvc)
|
||||
|
||||
group := domain.AgentGroup{
|
||||
Name: "Linux x86_64 Servers",
|
||||
MatchOS: "linux",
|
||||
MatchArchitecture: "amd64",
|
||||
}
|
||||
|
||||
created, err := svc.CreateAgentGroup(group)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if created.MatchOS != "linux" {
|
||||
t.Errorf("expected MatchOS 'linux', got %s", created.MatchOS)
|
||||
}
|
||||
if created.MatchArchitecture != "amd64" {
|
||||
t.Errorf("expected MatchArchitecture 'amd64', got %s", created.MatchArchitecture)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: CreateAgentGroup with repository error
|
||||
func TestAgentGroupService_CreateAgentGroup_RepositoryError(t *testing.T) {
|
||||
repo := newMockAgentGroupRepository()
|
||||
repo.CreateErr = errors.New("database error")
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
svc := NewAgentGroupService(repo, auditSvc)
|
||||
|
||||
group := domain.AgentGroup{
|
||||
Name: "Test Group",
|
||||
}
|
||||
|
||||
_, err := svc.CreateAgentGroup(group)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "failed to create agent group") {
|
||||
t.Errorf("expected 'failed to create agent group' in error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: UpdateAgentGroup success
|
||||
func TestAgentGroupService_UpdateAgentGroup(t *testing.T) {
|
||||
repo := newMockAgentGroupRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
svc := NewAgentGroupService(repo, auditSvc)
|
||||
|
||||
existing := &domain.AgentGroup{
|
||||
ID: "ag-test-1",
|
||||
Name: "Old Name",
|
||||
}
|
||||
repo.AddGroup(existing)
|
||||
|
||||
updated := domain.AgentGroup{
|
||||
Name: "New Name",
|
||||
}
|
||||
|
||||
result, err := svc.UpdateAgentGroup("ag-test-1", updated)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if result.ID != "ag-test-1" {
|
||||
t.Errorf("expected ID 'ag-test-1', got %s", result.ID)
|
||||
}
|
||||
if result.Name != "New Name" {
|
||||
t.Errorf("expected name 'New Name', got %s", result.Name)
|
||||
}
|
||||
|
||||
// Audit event should be recorded
|
||||
if len(auditRepo.Events) == 0 {
|
||||
t.Fatal("expected audit event to be recorded")
|
||||
}
|
||||
if auditRepo.Events[0].Action != "update_agent_group" {
|
||||
t.Errorf("expected action 'update_agent_group', got %s", auditRepo.Events[0].Action)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: UpdateAgentGroup with empty name validation error
|
||||
func TestAgentGroupService_UpdateAgentGroup_EmptyName(t *testing.T) {
|
||||
repo := newMockAgentGroupRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
svc := NewAgentGroupService(repo, auditSvc)
|
||||
|
||||
updated := domain.AgentGroup{
|
||||
Name: "",
|
||||
}
|
||||
|
||||
_, err := svc.UpdateAgentGroup("ag-test-1", updated)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "agent group name is required") {
|
||||
t.Errorf("expected 'agent group name is required' in error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: UpdateAgentGroup with repository error
|
||||
func TestAgentGroupService_UpdateAgentGroup_RepositoryError(t *testing.T) {
|
||||
repo := newMockAgentGroupRepository()
|
||||
repo.UpdateErr = errors.New("database error")
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
svc := NewAgentGroupService(repo, auditSvc)
|
||||
|
||||
updated := domain.AgentGroup{
|
||||
Name: "Valid Name",
|
||||
}
|
||||
|
||||
_, err := svc.UpdateAgentGroup("ag-test-1", updated)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "failed to update agent group") {
|
||||
t.Errorf("expected 'failed to update agent group' in error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: DeleteAgentGroup success with audit
|
||||
func TestAgentGroupService_DeleteAgentGroup(t *testing.T) {
|
||||
repo := newMockAgentGroupRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
svc := NewAgentGroupService(repo, auditSvc)
|
||||
|
||||
group := &domain.AgentGroup{
|
||||
ID: "ag-test-1",
|
||||
Name: "Test Group",
|
||||
}
|
||||
repo.AddGroup(group)
|
||||
|
||||
err := svc.DeleteAgentGroup("ag-test-1")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
// Group should be deleted from repository
|
||||
_, err = repo.Get(context.Background(), "ag-test-1")
|
||||
if !errors.Is(err, errNotFound) {
|
||||
t.Errorf("expected errNotFound after delete, got %v", err)
|
||||
}
|
||||
|
||||
// Audit event should be recorded
|
||||
if len(auditRepo.Events) == 0 {
|
||||
t.Fatal("expected audit event to be recorded")
|
||||
}
|
||||
if auditRepo.Events[0].Action != "delete_agent_group" {
|
||||
t.Errorf("expected action 'delete_agent_group', got %s", auditRepo.Events[0].Action)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: DeleteAgentGroup with repository error
|
||||
func TestAgentGroupService_DeleteAgentGroup_RepositoryError(t *testing.T) {
|
||||
repo := newMockAgentGroupRepository()
|
||||
repo.DeleteErr = errors.New("database error")
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
svc := NewAgentGroupService(repo, auditSvc)
|
||||
|
||||
err := svc.DeleteAgentGroup("ag-test-1")
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "failed to delete agent group") {
|
||||
t.Errorf("expected 'failed to delete agent group' in error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: ListMembers returns agents
|
||||
func TestAgentGroupService_ListMembers(t *testing.T) {
|
||||
repo := newMockAgentGroupRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
svc := NewAgentGroupService(repo, auditSvc)
|
||||
|
||||
agents := []*domain.Agent{
|
||||
{
|
||||
ID: "agent-1",
|
||||
Name: "Agent 1",
|
||||
},
|
||||
{
|
||||
ID: "agent-2",
|
||||
Name: "Agent 2",
|
||||
},
|
||||
}
|
||||
repo.AddGroupMembers("ag-test-1", agents)
|
||||
|
||||
result, total, err := svc.ListMembers("ag-test-1")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if total != 2 {
|
||||
t.Errorf("expected total=2, got %d", total)
|
||||
}
|
||||
if len(result) != 2 {
|
||||
t.Errorf("expected 2 agents, got %d", len(result))
|
||||
}
|
||||
if result[0].ID != "agent-1" {
|
||||
t.Errorf("expected first agent ID 'agent-1', got %s", result[0].ID)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: ListMembers returns empty when no agents
|
||||
func TestAgentGroupService_ListMembers_Empty(t *testing.T) {
|
||||
repo := newMockAgentGroupRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
svc := NewAgentGroupService(repo, auditSvc)
|
||||
|
||||
result, total, err := svc.ListMembers("ag-test-1")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if total != 0 {
|
||||
t.Errorf("expected total=0, got %d", total)
|
||||
}
|
||||
if len(result) != 0 {
|
||||
t.Errorf("expected 0 agents, got %d", len(result))
|
||||
}
|
||||
}
|
||||
|
||||
// Test: ListMembers with repository error
|
||||
func TestAgentGroupService_ListMembers_RepositoryError(t *testing.T) {
|
||||
repo := newMockAgentGroupRepository()
|
||||
repo.ListMembersErr = errors.New("database error")
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
svc := NewAgentGroupService(repo, auditSvc)
|
||||
|
||||
_, _, err := svc.ListMembers("ag-test-1")
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "failed to list group members") {
|
||||
t.Errorf("expected 'failed to list group members' in error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: AgentGroup.MatchesAgent with all criteria matching
|
||||
func TestAgentGroup_MatchesAgent(t *testing.T) {
|
||||
group := &domain.AgentGroup{
|
||||
MatchOS: "linux",
|
||||
MatchArchitecture: "amd64",
|
||||
MatchVersion: "1.0.0",
|
||||
}
|
||||
agent := &domain.Agent{
|
||||
OS: "linux",
|
||||
Architecture: "amd64",
|
||||
Version: "1.0.0",
|
||||
}
|
||||
|
||||
matches := group.MatchesAgent(agent)
|
||||
if !matches {
|
||||
t.Fatal("expected agent to match all criteria")
|
||||
}
|
||||
}
|
||||
|
||||
// Test: AgentGroup.MatchesAgent with OS mismatch
|
||||
func TestAgentGroup_MatchesAgent_OSMismatch(t *testing.T) {
|
||||
group := &domain.AgentGroup{
|
||||
MatchOS: "linux",
|
||||
MatchArchitecture: "amd64",
|
||||
}
|
||||
agent := &domain.Agent{
|
||||
OS: "windows",
|
||||
Architecture: "amd64",
|
||||
}
|
||||
|
||||
matches := group.MatchesAgent(agent)
|
||||
if matches {
|
||||
t.Fatal("expected agent NOT to match due to OS mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
// Test: AgentGroup.MatchesAgent with empty criteria matches any agent
|
||||
func TestAgentGroup_MatchesAgent_EmptyCriteria(t *testing.T) {
|
||||
group := &domain.AgentGroup{
|
||||
// All criteria empty (wildcards)
|
||||
}
|
||||
agent := &domain.Agent{
|
||||
OS: "linux",
|
||||
Architecture: "arm64",
|
||||
Version: "2.0.0",
|
||||
}
|
||||
|
||||
matches := group.MatchesAgent(agent)
|
||||
if !matches {
|
||||
t.Fatal("expected agent to match empty criteria (wildcard)")
|
||||
}
|
||||
}
|
||||
|
||||
// Test: AgentGroup.HasDynamicCriteria returns true when criteria set
|
||||
func TestAgentGroup_HasDynamicCriteria(t *testing.T) {
|
||||
group := &domain.AgentGroup{
|
||||
MatchOS: "linux",
|
||||
}
|
||||
|
||||
if !group.HasDynamicCriteria() {
|
||||
t.Fatal("expected HasDynamicCriteria to return true")
|
||||
}
|
||||
}
|
||||
|
||||
// Test: AgentGroup.HasDynamicCriteria returns false when empty
|
||||
func TestAgentGroup_HasDynamicCriteria_Empty(t *testing.T) {
|
||||
group := &domain.AgentGroup{
|
||||
// All criteria empty
|
||||
}
|
||||
|
||||
if group.HasDynamicCriteria() {
|
||||
t.Fatal("expected HasDynamicCriteria to return false")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/shankar0123/certctl/internal/connector/issuer"
|
||||
)
|
||||
|
||||
// mockConnectorLayerIssuer is a test implementation of issuer.Connector
|
||||
type mockConnectorLayerIssuer struct {
|
||||
issueResult *issuer.IssuanceResult
|
||||
issueErr error
|
||||
renewResult *issuer.IssuanceResult
|
||||
renewErr error
|
||||
lastIssueReq *issuer.IssuanceRequest
|
||||
lastRenewReq *issuer.RenewalRequest
|
||||
validateErr error
|
||||
revokeErr error
|
||||
orderStatusErr error
|
||||
orderStatus *issuer.OrderStatus
|
||||
}
|
||||
|
||||
func (m *mockConnectorLayerIssuer) ValidateConfig(ctx context.Context, config []byte) error {
|
||||
return m.validateErr
|
||||
}
|
||||
|
||||
func (m *mockConnectorLayerIssuer) IssueCertificate(ctx context.Context, request issuer.IssuanceRequest) (*issuer.IssuanceResult, error) {
|
||||
m.lastIssueReq = &request
|
||||
if m.issueErr != nil {
|
||||
return nil, m.issueErr
|
||||
}
|
||||
if m.issueResult != nil {
|
||||
return m.issueResult, nil
|
||||
}
|
||||
// Return default result
|
||||
now := time.Now()
|
||||
return &issuer.IssuanceResult{
|
||||
CertPEM: "-----BEGIN CERTIFICATE-----\ndefault-cert\n-----END CERTIFICATE-----",
|
||||
ChainPEM: "-----BEGIN CERTIFICATE-----\ndefault-chain\n-----END CERTIFICATE-----",
|
||||
Serial: "default-serial-123",
|
||||
NotBefore: now,
|
||||
NotAfter: now.AddDate(1, 0, 0),
|
||||
OrderID: "order-default",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *mockConnectorLayerIssuer) RenewCertificate(ctx context.Context, request issuer.RenewalRequest) (*issuer.IssuanceResult, error) {
|
||||
m.lastRenewReq = &request
|
||||
if m.renewErr != nil {
|
||||
return nil, m.renewErr
|
||||
}
|
||||
if m.renewResult != nil {
|
||||
return m.renewResult, nil
|
||||
}
|
||||
// Return default result
|
||||
now := time.Now()
|
||||
return &issuer.IssuanceResult{
|
||||
CertPEM: "-----BEGIN CERTIFICATE-----\ndefault-renewed-cert\n-----END CERTIFICATE-----",
|
||||
ChainPEM: "-----BEGIN CERTIFICATE-----\ndefault-renewed-chain\n-----END CERTIFICATE-----",
|
||||
Serial: "default-renewed-serial-456",
|
||||
NotBefore: now,
|
||||
NotAfter: now.AddDate(1, 0, 0),
|
||||
OrderID: "order-renewed",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *mockConnectorLayerIssuer) RevokeCertificate(ctx context.Context, request issuer.RevocationRequest) error {
|
||||
return m.revokeErr
|
||||
}
|
||||
|
||||
func (m *mockConnectorLayerIssuer) GetOrderStatus(ctx context.Context, orderID string) (*issuer.OrderStatus, error) {
|
||||
if m.orderStatusErr != nil {
|
||||
return nil, m.orderStatusErr
|
||||
}
|
||||
if m.orderStatus != nil {
|
||||
return m.orderStatus, nil
|
||||
}
|
||||
status := "pending"
|
||||
return &issuer.OrderStatus{
|
||||
OrderID: orderID,
|
||||
Status: status,
|
||||
UpdatedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Tests for IssueCertificate
|
||||
|
||||
func TestIssuerConnectorAdapter_IssueCertificate_Success(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
notAfter := now.AddDate(1, 0, 0)
|
||||
|
||||
mock := &mockConnectorLayerIssuer{
|
||||
issueResult: &issuer.IssuanceResult{
|
||||
CertPEM: "-----BEGIN CERTIFICATE-----\ntest-cert\n-----END CERTIFICATE-----",
|
||||
ChainPEM: "-----BEGIN CERTIFICATE-----\ntest-chain\n-----END CERTIFICATE-----",
|
||||
Serial: "test-serial-001",
|
||||
NotBefore: now,
|
||||
NotAfter: notAfter,
|
||||
OrderID: "order-123",
|
||||
},
|
||||
}
|
||||
|
||||
adapter := NewIssuerConnectorAdapter(mock)
|
||||
|
||||
result, err := adapter.IssueCertificate(ctx, "example.com", []string{"www.example.com"}, "-----BEGIN CERTIFICATE REQUEST-----\nCSR\n-----END CERTIFICATE REQUEST-----")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("IssueCertificate failed: %v", err)
|
||||
}
|
||||
|
||||
if result.Serial != "test-serial-001" {
|
||||
t.Errorf("expected serial test-serial-001, got %s", result.Serial)
|
||||
}
|
||||
|
||||
if result.CertPEM != "-----BEGIN CERTIFICATE-----\ntest-cert\n-----END CERTIFICATE-----" {
|
||||
t.Errorf("expected CertPEM test-cert, got %s", result.CertPEM)
|
||||
}
|
||||
|
||||
if result.ChainPEM != "-----BEGIN CERTIFICATE-----\ntest-chain\n-----END CERTIFICATE-----" {
|
||||
t.Errorf("expected ChainPEM test-chain, got %s", result.ChainPEM)
|
||||
}
|
||||
|
||||
if !result.NotBefore.Equal(now) {
|
||||
t.Errorf("expected NotBefore %v, got %v", now, result.NotBefore)
|
||||
}
|
||||
|
||||
if !result.NotAfter.Equal(notAfter) {
|
||||
t.Errorf("expected NotAfter %v, got %v", notAfter, result.NotAfter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuerConnectorAdapter_IssueCertificate_Error(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
testErr := errors.New("issuer connection failed")
|
||||
|
||||
mock := &mockConnectorLayerIssuer{
|
||||
issueErr: testErr,
|
||||
}
|
||||
|
||||
adapter := NewIssuerConnectorAdapter(mock)
|
||||
|
||||
result, err := adapter.IssueCertificate(ctx, "example.com", []string{}, "csr")
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
|
||||
if !errors.Is(err, testErr) {
|
||||
t.Errorf("expected error %v, got %v", testErr, err)
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
t.Errorf("expected nil result, got %v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuerConnectorAdapter_IssueCertificate_RequestTranslation(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
mock := &mockConnectorLayerIssuer{
|
||||
issueResult: &issuer.IssuanceResult{
|
||||
CertPEM: "cert",
|
||||
ChainPEM: "chain",
|
||||
Serial: "serial",
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(1, 0, 0),
|
||||
},
|
||||
}
|
||||
|
||||
adapter := NewIssuerConnectorAdapter(mock)
|
||||
|
||||
commonName := "test.example.com"
|
||||
sans := []string{"www.test.example.com", "api.test.example.com"}
|
||||
csrPEM := "-----BEGIN CERTIFICATE REQUEST-----\nCSR\n-----END CERTIFICATE REQUEST-----"
|
||||
|
||||
_, err := adapter.IssueCertificate(ctx, commonName, sans, csrPEM)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("IssueCertificate failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify request was passed through correctly
|
||||
if mock.lastIssueReq == nil {
|
||||
t.Fatal("expected request to be recorded")
|
||||
}
|
||||
|
||||
if mock.lastIssueReq.CommonName != commonName {
|
||||
t.Errorf("expected CommonName %s, got %s", commonName, mock.lastIssueReq.CommonName)
|
||||
}
|
||||
|
||||
if len(mock.lastIssueReq.SANs) != len(sans) {
|
||||
t.Errorf("expected %d SANs, got %d", len(sans), len(mock.lastIssueReq.SANs))
|
||||
}
|
||||
|
||||
for i, san := range sans {
|
||||
if mock.lastIssueReq.SANs[i] != san {
|
||||
t.Errorf("expected SAN[%d] %s, got %s", i, san, mock.lastIssueReq.SANs[i])
|
||||
}
|
||||
}
|
||||
|
||||
if mock.lastIssueReq.CSRPEM != csrPEM {
|
||||
t.Errorf("expected CSRPEM %s, got %s", csrPEM, mock.lastIssueReq.CSRPEM)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests for RenewCertificate
|
||||
|
||||
func TestIssuerConnectorAdapter_RenewCertificate_Success(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
notAfter := now.AddDate(1, 0, 0)
|
||||
|
||||
mock := &mockConnectorLayerIssuer{
|
||||
renewResult: &issuer.IssuanceResult{
|
||||
CertPEM: "-----BEGIN CERTIFICATE-----\nrenewed-cert\n-----END CERTIFICATE-----",
|
||||
ChainPEM: "-----BEGIN CERTIFICATE-----\nrenewed-chain\n-----END CERTIFICATE-----",
|
||||
Serial: "renewed-serial-002",
|
||||
NotBefore: now,
|
||||
NotAfter: notAfter,
|
||||
OrderID: "order-456",
|
||||
},
|
||||
}
|
||||
|
||||
adapter := NewIssuerConnectorAdapter(mock)
|
||||
|
||||
result, err := adapter.RenewCertificate(ctx, "example.com", []string{"www.example.com"}, "-----BEGIN CERTIFICATE REQUEST-----\nCSR\n-----END CERTIFICATE REQUEST-----")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("RenewCertificate failed: %v", err)
|
||||
}
|
||||
|
||||
if result.Serial != "renewed-serial-002" {
|
||||
t.Errorf("expected serial renewed-serial-002, got %s", result.Serial)
|
||||
}
|
||||
|
||||
if result.CertPEM != "-----BEGIN CERTIFICATE-----\nrenewed-cert\n-----END CERTIFICATE-----" {
|
||||
t.Errorf("expected CertPEM renewed-cert, got %s", result.CertPEM)
|
||||
}
|
||||
|
||||
if result.ChainPEM != "-----BEGIN CERTIFICATE-----\nrenewed-chain\n-----END CERTIFICATE-----" {
|
||||
t.Errorf("expected ChainPEM renewed-chain, got %s", result.ChainPEM)
|
||||
}
|
||||
|
||||
if !result.NotBefore.Equal(now) {
|
||||
t.Errorf("expected NotBefore %v, got %v", now, result.NotBefore)
|
||||
}
|
||||
|
||||
if !result.NotAfter.Equal(notAfter) {
|
||||
t.Errorf("expected NotAfter %v, got %v", notAfter, result.NotAfter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuerConnectorAdapter_RenewCertificate_Error(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
testErr := errors.New("renewal failed")
|
||||
|
||||
mock := &mockConnectorLayerIssuer{
|
||||
renewErr: testErr,
|
||||
}
|
||||
|
||||
adapter := NewIssuerConnectorAdapter(mock)
|
||||
|
||||
result, err := adapter.RenewCertificate(ctx, "example.com", []string{}, "csr")
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
|
||||
if !errors.Is(err, testErr) {
|
||||
t.Errorf("expected error %v, got %v", testErr, err)
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
t.Errorf("expected nil result, got %v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssuerConnectorAdapter_RenewCertificate_RequestTranslation(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
mock := &mockConnectorLayerIssuer{
|
||||
renewResult: &issuer.IssuanceResult{
|
||||
CertPEM: "cert",
|
||||
ChainPEM: "chain",
|
||||
Serial: "serial",
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(1, 0, 0),
|
||||
},
|
||||
}
|
||||
|
||||
adapter := NewIssuerConnectorAdapter(mock)
|
||||
|
||||
commonName := "renew.example.com"
|
||||
sans := []string{"www.renew.example.com"}
|
||||
csrPEM := "-----BEGIN CERTIFICATE REQUEST-----\nRENEW-CSR\n-----END CERTIFICATE REQUEST-----"
|
||||
|
||||
_, err := adapter.RenewCertificate(ctx, commonName, sans, csrPEM)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("RenewCertificate failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify request was passed through correctly
|
||||
if mock.lastRenewReq == nil {
|
||||
t.Fatal("expected request to be recorded")
|
||||
}
|
||||
|
||||
if mock.lastRenewReq.CommonName != commonName {
|
||||
t.Errorf("expected CommonName %s, got %s", commonName, mock.lastRenewReq.CommonName)
|
||||
}
|
||||
|
||||
if len(mock.lastRenewReq.SANs) != len(sans) {
|
||||
t.Errorf("expected %d SANs, got %d", len(sans), len(mock.lastRenewReq.SANs))
|
||||
}
|
||||
|
||||
for i, san := range sans {
|
||||
if mock.lastRenewReq.SANs[i] != san {
|
||||
t.Errorf("expected SAN[%d] %s, got %s", i, san, mock.lastRenewReq.SANs[i])
|
||||
}
|
||||
}
|
||||
|
||||
if mock.lastRenewReq.CSRPEM != csrPEM {
|
||||
t.Errorf("expected CSRPEM %s, got %s", csrPEM, mock.lastRenewReq.CSRPEM)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,601 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/shankar0123/certctl/internal/domain"
|
||||
)
|
||||
|
||||
// TestIssuerService_List tests listing issuers with pagination
|
||||
func TestIssuerService_List(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
issuer1 := &domain.Issuer{
|
||||
ID: "iss-1",
|
||||
Name: "ACME Provider",
|
||||
Type: domain.IssuerTypeACME,
|
||||
Enabled: true,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
issuer2 := &domain.Issuer{
|
||||
ID: "iss-2",
|
||||
Name: "Step CA",
|
||||
Type: domain.IssuerTypeStepCA,
|
||||
Enabled: true,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
issuer3 := &domain.Issuer{
|
||||
ID: "iss-3",
|
||||
Name: "Internal CA",
|
||||
Type: domain.IssuerTypeGenericCA,
|
||||
Enabled: false,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
repo := newMockIssuerRepository()
|
||||
repo.AddIssuer(issuer1)
|
||||
repo.AddIssuer(issuer2)
|
||||
repo.AddIssuer(issuer3)
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
service := NewIssuerService(repo, auditService)
|
||||
|
||||
issuers, total, err := service.List(ctx, 1, 2)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("List failed: %v", err)
|
||||
}
|
||||
|
||||
if total != 3 {
|
||||
t.Errorf("expected total 3, got %d", total)
|
||||
}
|
||||
|
||||
if len(issuers) != 2 {
|
||||
t.Errorf("expected 2 issuers on page 1, got %d", len(issuers))
|
||||
}
|
||||
|
||||
// Test page 2
|
||||
issuers2, _, err := service.List(ctx, 2, 2)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("List page 2 failed: %v", err)
|
||||
}
|
||||
|
||||
if len(issuers2) != 1 {
|
||||
t.Errorf("expected 1 issuer on page 2, got %d", len(issuers2))
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssuerService_List_DefaultPagination tests list with default pagination values
|
||||
func TestIssuerService_List_DefaultPagination(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
repo := newMockIssuerRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
service := NewIssuerService(repo, auditService)
|
||||
|
||||
// Call with invalid page and perPage
|
||||
issuers, total, err := service.List(ctx, 0, 0)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("List failed: %v", err)
|
||||
}
|
||||
|
||||
if total != 0 {
|
||||
t.Errorf("expected total 0, got %d", total)
|
||||
}
|
||||
|
||||
if len(issuers) != 0 {
|
||||
t.Errorf("expected 0 issuers, got %d", len(issuers))
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssuerService_List_RepositoryError tests list when repository returns error
|
||||
func TestIssuerService_List_RepositoryError(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
repo := newMockIssuerRepository()
|
||||
repo.ListErr = errors.New("database connection failed")
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
service := NewIssuerService(repo, auditService)
|
||||
|
||||
_, _, err := service.List(ctx, 1, 50)
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
|
||||
if !errors.Is(err, repo.ListErr) {
|
||||
t.Errorf("expected error %v, got %v", repo.ListErr, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssuerService_List_EmptyResult tests list returning empty list
|
||||
func TestIssuerService_List_EmptyResult(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
repo := newMockIssuerRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
service := NewIssuerService(repo, auditService)
|
||||
|
||||
issuers, total, err := service.List(ctx, 1, 50)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("List failed: %v", err)
|
||||
}
|
||||
|
||||
if total != 0 {
|
||||
t.Errorf("expected total 0, got %d", total)
|
||||
}
|
||||
|
||||
if len(issuers) != 0 {
|
||||
t.Errorf("expected 0 issuers, got %d", len(issuers))
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssuerService_Get tests retrieving an issuer by ID
|
||||
func TestIssuerService_Get(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
issuer := &domain.Issuer{
|
||||
ID: "iss-acme-prod",
|
||||
Name: "ACME Production",
|
||||
Type: domain.IssuerTypeACME,
|
||||
Enabled: true,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
repo := newMockIssuerRepository()
|
||||
repo.AddIssuer(issuer)
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
service := NewIssuerService(repo, auditService)
|
||||
|
||||
retrieved, err := service.Get(ctx, "iss-acme-prod")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Get failed: %v", err)
|
||||
}
|
||||
|
||||
if retrieved.Name != "ACME Production" {
|
||||
t.Errorf("expected name ACME Production, got %s", retrieved.Name)
|
||||
}
|
||||
|
||||
if retrieved.Type != domain.IssuerTypeACME {
|
||||
t.Errorf("expected type ACME, got %s", retrieved.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssuerService_Get_NotFound tests Get when issuer doesn't exist
|
||||
func TestIssuerService_Get_NotFound(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
repo := newMockIssuerRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
service := NewIssuerService(repo, auditService)
|
||||
|
||||
_, err := service.Get(ctx, "nonexistent-issuer")
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected error for nonexistent issuer")
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssuerService_Create tests creating a new issuer
|
||||
func TestIssuerService_Create(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
repo := newMockIssuerRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
service := NewIssuerService(repo, auditService)
|
||||
|
||||
config := map[string]interface{}{"endpoint": "https://acme.example.com/v2/new-account"}
|
||||
configJSON, _ := json.Marshal(config)
|
||||
|
||||
issuer := &domain.Issuer{
|
||||
Name: "Test ACME",
|
||||
Type: domain.IssuerTypeACME,
|
||||
Config: configJSON,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
err := service.Create(ctx, issuer, "user-alice")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Create failed: %v", err)
|
||||
}
|
||||
|
||||
if issuer.ID == "" {
|
||||
t.Error("expected ID to be generated")
|
||||
}
|
||||
|
||||
if issuer.CreatedAt.IsZero() {
|
||||
t.Error("expected CreatedAt to be set")
|
||||
}
|
||||
|
||||
if issuer.UpdatedAt.IsZero() {
|
||||
t.Error("expected UpdatedAt to be set")
|
||||
}
|
||||
|
||||
// Verify stored in repo
|
||||
retrieved, err := repo.Get(ctx, issuer.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve created issuer: %v", err)
|
||||
}
|
||||
|
||||
if retrieved.Name != "Test ACME" {
|
||||
t.Errorf("expected name Test ACME, got %s", retrieved.Name)
|
||||
}
|
||||
|
||||
// Verify audit event recorded
|
||||
if len(auditRepo.Events) != 1 {
|
||||
t.Errorf("expected 1 audit event, got %d", len(auditRepo.Events))
|
||||
}
|
||||
|
||||
if auditRepo.Events[0].Action != "create_issuer" {
|
||||
t.Errorf("expected action create_issuer, got %s", auditRepo.Events[0].Action)
|
||||
}
|
||||
|
||||
if auditRepo.Events[0].Actor != "user-alice" {
|
||||
t.Errorf("expected actor user-alice, got %s", auditRepo.Events[0].Actor)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssuerService_Create_EmptyName tests Create with empty name validation
|
||||
func TestIssuerService_Create_EmptyName(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
repo := newMockIssuerRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
service := NewIssuerService(repo, auditService)
|
||||
|
||||
issuer := &domain.Issuer{
|
||||
Name: "",
|
||||
Type: domain.IssuerTypeACME,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
err := service.Create(ctx, issuer, "user-bob")
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected error for empty name")
|
||||
}
|
||||
|
||||
if err.Error() != "issuer name is required" {
|
||||
t.Errorf("expected 'issuer name is required', got '%v'", err)
|
||||
}
|
||||
|
||||
// Verify no audit event recorded on validation error
|
||||
if len(auditRepo.Events) != 0 {
|
||||
t.Errorf("expected 0 audit events on validation error, got %d", len(auditRepo.Events))
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssuerService_Create_RepositoryError tests Create when repository fails
|
||||
func TestIssuerService_Create_RepositoryError(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
repo := newMockIssuerRepository()
|
||||
repo.CreateErr = errors.New("database error")
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
service := NewIssuerService(repo, auditService)
|
||||
|
||||
issuer := &domain.Issuer{
|
||||
Name: "Test Issuer",
|
||||
Type: domain.IssuerTypeACME,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
err := service.Create(ctx, issuer, "user-charlie")
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected error from repository")
|
||||
}
|
||||
|
||||
if !errors.Is(err, repo.CreateErr) {
|
||||
t.Errorf("expected error %v, got %v", repo.CreateErr, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssuerService_Update tests updating an existing issuer
|
||||
func TestIssuerService_Update(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
repo := newMockIssuerRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
service := NewIssuerService(repo, auditService)
|
||||
|
||||
config := map[string]interface{}{"endpoint": "https://acme.example.com"}
|
||||
configJSON, _ := json.Marshal(config)
|
||||
|
||||
issuer := &domain.Issuer{
|
||||
Name: "Updated ACME",
|
||||
Type: domain.IssuerTypeACME,
|
||||
Config: configJSON,
|
||||
Enabled: false,
|
||||
}
|
||||
|
||||
err := service.Update(ctx, "iss-acme-001", issuer, "user-dave")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Update failed: %v", err)
|
||||
}
|
||||
|
||||
if issuer.ID != "iss-acme-001" {
|
||||
t.Errorf("expected ID to be set to iss-acme-001, got %s", issuer.ID)
|
||||
}
|
||||
|
||||
// Verify audit event recorded
|
||||
if len(auditRepo.Events) != 1 {
|
||||
t.Errorf("expected 1 audit event, got %d", len(auditRepo.Events))
|
||||
}
|
||||
|
||||
if auditRepo.Events[0].Action != "update_issuer" {
|
||||
t.Errorf("expected action update_issuer, got %s", auditRepo.Events[0].Action)
|
||||
}
|
||||
|
||||
if auditRepo.Events[0].ResourceID != "iss-acme-001" {
|
||||
t.Errorf("expected ResourceID iss-acme-001, got %s", auditRepo.Events[0].ResourceID)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssuerService_Update_EmptyName tests Update with empty name validation
|
||||
func TestIssuerService_Update_EmptyName(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
repo := newMockIssuerRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
service := NewIssuerService(repo, auditService)
|
||||
|
||||
issuer := &domain.Issuer{
|
||||
Name: "",
|
||||
Type: domain.IssuerTypeACME,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
err := service.Update(ctx, "iss-acme-001", issuer, "user-eve")
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected error for empty name")
|
||||
}
|
||||
|
||||
if err.Error() != "issuer name is required" {
|
||||
t.Errorf("expected 'issuer name is required', got '%v'", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssuerService_Delete tests deleting an issuer
|
||||
func TestIssuerService_Delete(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
repo := newMockIssuerRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
service := NewIssuerService(repo, auditService)
|
||||
|
||||
err := service.Delete(ctx, "iss-to-delete", "user-frank")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Delete failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify audit event recorded
|
||||
if len(auditRepo.Events) != 1 {
|
||||
t.Errorf("expected 1 audit event, got %d", len(auditRepo.Events))
|
||||
}
|
||||
|
||||
if auditRepo.Events[0].Action != "delete_issuer" {
|
||||
t.Errorf("expected action delete_issuer, got %s", auditRepo.Events[0].Action)
|
||||
}
|
||||
|
||||
if auditRepo.Events[0].ResourceID != "iss-to-delete" {
|
||||
t.Errorf("expected ResourceID iss-to-delete, got %s", auditRepo.Events[0].ResourceID)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssuerService_Delete_RepositoryError tests Delete when repository fails
|
||||
func TestIssuerService_Delete_RepositoryError(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
repo := newMockIssuerRepository()
|
||||
repo.DeleteErr = errors.New("delete failed")
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
service := NewIssuerService(repo, auditService)
|
||||
|
||||
err := service.Delete(ctx, "iss-bad-id", "user-grace")
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected error from repository")
|
||||
}
|
||||
|
||||
if !errors.Is(err, repo.DeleteErr) {
|
||||
t.Errorf("expected error %v, got %v", repo.DeleteErr, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssuerService_TestConnection_Success tests successful connection test
|
||||
func TestIssuerService_TestConnection_Success(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
issuer := &domain.Issuer{
|
||||
ID: "iss-test-conn",
|
||||
Name: "Test Connection",
|
||||
Type: domain.IssuerTypeACME,
|
||||
Enabled: true,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
repo := newMockIssuerRepository()
|
||||
repo.AddIssuer(issuer)
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
service := NewIssuerService(repo, auditService)
|
||||
|
||||
err := service.TestConnectionWithContext(ctx, "iss-test-conn")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("TestConnectionWithContext failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssuerService_TestConnection_NotFound tests connection test when issuer not found
|
||||
func TestIssuerService_TestConnection_NotFound(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
repo := newMockIssuerRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
service := NewIssuerService(repo, auditService)
|
||||
|
||||
err := service.TestConnectionWithContext(ctx, "nonexistent-issuer")
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected error for nonexistent issuer")
|
||||
}
|
||||
|
||||
if !errors.Is(err, errNotFound) {
|
||||
t.Errorf("expected not found error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssuerService_ListIssuers_HandlerInterface tests handler interface method
|
||||
func TestIssuerService_ListIssuers_HandlerInterface(t *testing.T) {
|
||||
issuer1 := &domain.Issuer{
|
||||
ID: "iss-handler-1",
|
||||
Name: "Handler Test 1",
|
||||
Type: domain.IssuerTypeACME,
|
||||
Enabled: true,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
issuer2 := &domain.Issuer{
|
||||
ID: "iss-handler-2",
|
||||
Name: "Handler Test 2",
|
||||
Type: domain.IssuerTypeStepCA,
|
||||
Enabled: true,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
repo := newMockIssuerRepository()
|
||||
repo.AddIssuer(issuer1)
|
||||
repo.AddIssuer(issuer2)
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
service := NewIssuerService(repo, auditService)
|
||||
|
||||
issuers, total, err := service.ListIssuers(1, 50)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("ListIssuers failed: %v", err)
|
||||
}
|
||||
|
||||
if total != 2 {
|
||||
t.Errorf("expected total 2, got %d", total)
|
||||
}
|
||||
|
||||
if len(issuers) != 2 {
|
||||
t.Errorf("expected 2 issuers, got %d", len(issuers))
|
||||
}
|
||||
|
||||
if issuers[0].Name != "Handler Test 1" && issuers[1].Name != "Handler Test 1" {
|
||||
t.Error("expected to find Handler Test 1 in results")
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssuerService_CreateIssuer_HandlerInterface tests handler interface create method
|
||||
func TestIssuerService_CreateIssuer_HandlerInterface(t *testing.T) {
|
||||
repo := newMockIssuerRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
service := NewIssuerService(repo, auditService)
|
||||
|
||||
config := map[string]interface{}{"url": "https://example.com"}
|
||||
configJSON, _ := json.Marshal(config)
|
||||
|
||||
issuer := domain.Issuer{
|
||||
Name: "Handler Create Test",
|
||||
Type: domain.IssuerTypeGenericCA,
|
||||
Config: configJSON,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
result, err := service.CreateIssuer(issuer)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("CreateIssuer failed: %v", err)
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
t.Fatal("expected non-nil result")
|
||||
}
|
||||
|
||||
if result.ID == "" {
|
||||
t.Error("expected ID to be generated")
|
||||
}
|
||||
|
||||
if result.Name != "Handler Create Test" {
|
||||
t.Errorf("expected name Handler Create Test, got %s", result.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssuerService_DeleteIssuer_HandlerInterface tests handler interface delete method
|
||||
func TestIssuerService_DeleteIssuer_HandlerInterface(t *testing.T) {
|
||||
repo := newMockIssuerRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
service := NewIssuerService(repo, auditService)
|
||||
|
||||
err := service.DeleteIssuer("iss-handler-delete")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("DeleteIssuer failed: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,814 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/shankar0123/certctl/internal/domain"
|
||||
)
|
||||
|
||||
// mockOwnerRepo is a test implementation of OwnerRepository
|
||||
type mockOwnerRepo struct {
|
||||
owners map[string]*domain.Owner
|
||||
CreateErr error
|
||||
UpdateErr error
|
||||
DeleteErr error
|
||||
GetErr error
|
||||
ListErr error
|
||||
}
|
||||
|
||||
func (m *mockOwnerRepo) List(ctx context.Context) ([]*domain.Owner, error) {
|
||||
if m.ListErr != nil {
|
||||
return nil, m.ListErr
|
||||
}
|
||||
var owners []*domain.Owner
|
||||
for _, o := range m.owners {
|
||||
owners = append(owners, o)
|
||||
}
|
||||
return owners, nil
|
||||
}
|
||||
|
||||
func (m *mockOwnerRepo) Get(ctx context.Context, id string) (*domain.Owner, error) {
|
||||
if m.GetErr != nil {
|
||||
return nil, m.GetErr
|
||||
}
|
||||
owner, ok := m.owners[id]
|
||||
if !ok {
|
||||
return nil, errNotFound
|
||||
}
|
||||
return owner, nil
|
||||
}
|
||||
|
||||
func (m *mockOwnerRepo) Create(ctx context.Context, owner *domain.Owner) error {
|
||||
if m.CreateErr != nil {
|
||||
return m.CreateErr
|
||||
}
|
||||
m.owners[owner.ID] = owner
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockOwnerRepo) Update(ctx context.Context, owner *domain.Owner) error {
|
||||
if m.UpdateErr != nil {
|
||||
return m.UpdateErr
|
||||
}
|
||||
m.owners[owner.ID] = owner
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockOwnerRepo) Delete(ctx context.Context, id string) error {
|
||||
if m.DeleteErr != nil {
|
||||
return m.DeleteErr
|
||||
}
|
||||
delete(m.owners, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockOwnerRepo) AddOwner(owner *domain.Owner) {
|
||||
m.owners[owner.ID] = owner
|
||||
}
|
||||
|
||||
func newMockOwnerRepository() *mockOwnerRepo {
|
||||
return &mockOwnerRepo{
|
||||
owners: make(map[string]*domain.Owner),
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_List tests paginated listing of owners.
|
||||
func TestOwnerService_List(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
|
||||
owner1 := &domain.Owner{
|
||||
ID: "owner-001",
|
||||
Name: "Alice Smith",
|
||||
Email: "alice@example.com",
|
||||
TeamID: "team-001",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
owner2 := &domain.Owner{
|
||||
ID: "owner-002",
|
||||
Name: "Bob Jones",
|
||||
Email: "bob@example.com",
|
||||
TeamID: "team-001",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
ownerRepo.AddOwner(owner1)
|
||||
ownerRepo.AddOwner(owner2)
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
owners, total, err := ownerService.List(ctx, 1, 50)
|
||||
if err != nil {
|
||||
t.Fatalf("List failed: %v", err)
|
||||
}
|
||||
|
||||
if len(owners) != 2 {
|
||||
t.Errorf("expected 2 owners, got %d", len(owners))
|
||||
}
|
||||
|
||||
if total != 2 {
|
||||
t.Errorf("expected total 2, got %d", total)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_List_DefaultPagination tests that default pagination values are applied.
|
||||
func TestOwnerService_List_DefaultPagination(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
|
||||
owner := &domain.Owner{
|
||||
ID: "owner-001",
|
||||
Name: "Alice Smith",
|
||||
Email: "alice@example.com",
|
||||
TeamID: "team-001",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
ownerRepo.AddOwner(owner)
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
// Test with page < 1 (should default to 1)
|
||||
owners, total, err := ownerService.List(ctx, 0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("List failed: %v", err)
|
||||
}
|
||||
|
||||
if len(owners) != 1 {
|
||||
t.Errorf("expected 1 owner with default pagination, got %d", len(owners))
|
||||
}
|
||||
|
||||
if total != 1 {
|
||||
t.Errorf("expected total 1, got %d", total)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_List_RepositoryError tests handling of repository errors.
|
||||
func TestOwnerService_List_RepositoryError(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
ownerRepo.ListErr = errors.New("database error")
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
_, _, err := ownerService.List(ctx, 1, 50)
|
||||
if err == nil {
|
||||
t.Fatal("expected error from List, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_List_EmptyResult tests listing with no owners.
|
||||
func TestOwnerService_List_EmptyResult(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
owners, total, err := ownerService.List(ctx, 1, 50)
|
||||
if err != nil {
|
||||
t.Fatalf("List failed: %v", err)
|
||||
}
|
||||
|
||||
if len(owners) != 0 {
|
||||
t.Errorf("expected 0 owners, got %d", len(owners))
|
||||
}
|
||||
|
||||
if total != 0 {
|
||||
t.Errorf("expected total 0, got %d", total)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_List_PageBeyondRange tests pagination when page exceeds available data.
|
||||
func TestOwnerService_List_PageBeyondRange(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
|
||||
owner := &domain.Owner{
|
||||
ID: "owner-001",
|
||||
Name: "Alice Smith",
|
||||
Email: "alice@example.com",
|
||||
TeamID: "team-001",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
ownerRepo.AddOwner(owner)
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
// Request page 3 with only 1 owner
|
||||
owners, total, err := ownerService.List(ctx, 3, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("List failed: %v", err)
|
||||
}
|
||||
|
||||
if len(owners) != 0 {
|
||||
t.Errorf("expected 0 owners on page beyond range, got %d", len(owners))
|
||||
}
|
||||
|
||||
if total != 1 {
|
||||
t.Errorf("expected total 1, got %d", total)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_Get tests retrieving a single owner by ID.
|
||||
func TestOwnerService_Get(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
|
||||
owner := &domain.Owner{
|
||||
ID: "owner-001",
|
||||
Name: "Alice Smith",
|
||||
Email: "alice@example.com",
|
||||
TeamID: "team-001",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
ownerRepo.AddOwner(owner)
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
retrieved, err := ownerService.Get(ctx, "owner-001")
|
||||
if err != nil {
|
||||
t.Fatalf("Get failed: %v", err)
|
||||
}
|
||||
|
||||
if retrieved.Name != "Alice Smith" {
|
||||
t.Errorf("expected name Alice Smith, got %s", retrieved.Name)
|
||||
}
|
||||
|
||||
if retrieved.Email != "alice@example.com" {
|
||||
t.Errorf("expected email alice@example.com, got %s", retrieved.Email)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_Get_NotFound tests Get with a nonexistent owner.
|
||||
func TestOwnerService_Get_NotFound(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
_, err := ownerService.Get(ctx, "nonexistent")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for nonexistent owner, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_Create tests creating a new owner with audit recording.
|
||||
func TestOwnerService_Create(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
owner := &domain.Owner{
|
||||
Name: "Alice Smith",
|
||||
Email: "alice@example.com",
|
||||
TeamID: "team-001",
|
||||
}
|
||||
|
||||
err := ownerService.Create(ctx, owner, "user-1")
|
||||
if err != nil {
|
||||
t.Fatalf("Create failed: %v", err)
|
||||
}
|
||||
|
||||
if owner.ID == "" {
|
||||
t.Fatal("expected non-empty owner ID after creation")
|
||||
}
|
||||
|
||||
if !owner.CreatedAt.IsZero() && owner.CreatedAt.After(time.Now().Add(-time.Second)) {
|
||||
// CreatedAt should have been set
|
||||
} else if owner.CreatedAt.IsZero() {
|
||||
t.Fatal("expected CreatedAt to be set")
|
||||
}
|
||||
|
||||
if len(ownerRepo.owners) != 1 {
|
||||
t.Errorf("expected 1 owner in repo, got %d", len(ownerRepo.owners))
|
||||
}
|
||||
|
||||
if len(auditRepo.Events) != 1 {
|
||||
t.Errorf("expected 1 audit event, got %d", len(auditRepo.Events))
|
||||
}
|
||||
|
||||
auditEvent := auditRepo.Events[0]
|
||||
if auditEvent.Action != "create_owner" {
|
||||
t.Errorf("expected action create_owner, got %s", auditEvent.Action)
|
||||
}
|
||||
|
||||
if auditEvent.ResourceType != "owner" {
|
||||
t.Errorf("expected resource type owner, got %s", auditEvent.ResourceType)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_Create_EmptyName tests that Create rejects empty name.
|
||||
func TestOwnerService_Create_EmptyName(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
owner := &domain.Owner{
|
||||
Name: "",
|
||||
Email: "alice@example.com",
|
||||
TeamID: "team-001",
|
||||
}
|
||||
|
||||
err := ownerService.Create(ctx, owner, "user-1")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for empty owner name")
|
||||
}
|
||||
|
||||
if len(ownerRepo.owners) != 0 {
|
||||
t.Errorf("expected 0 owners in repo after validation failure, got %d", len(ownerRepo.owners))
|
||||
}
|
||||
|
||||
if len(auditRepo.Events) != 0 {
|
||||
t.Errorf("expected 0 audit events after validation failure, got %d", len(auditRepo.Events))
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_Create_WithExistingID tests that Create preserves existing ID.
|
||||
func TestOwnerService_Create_WithExistingID(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
owner := &domain.Owner{
|
||||
ID: "custom-id-123",
|
||||
Name: "Alice Smith",
|
||||
Email: "alice@example.com",
|
||||
TeamID: "team-001",
|
||||
}
|
||||
|
||||
err := ownerService.Create(ctx, owner, "user-1")
|
||||
if err != nil {
|
||||
t.Fatalf("Create failed: %v", err)
|
||||
}
|
||||
|
||||
if owner.ID != "custom-id-123" {
|
||||
t.Errorf("expected ID custom-id-123, got %s", owner.ID)
|
||||
}
|
||||
|
||||
stored, ok := ownerRepo.owners["custom-id-123"]
|
||||
if !ok {
|
||||
t.Fatal("expected owner with custom ID in repo")
|
||||
}
|
||||
|
||||
if stored.Name != "Alice Smith" {
|
||||
t.Errorf("expected name Alice Smith, got %s", stored.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_Create_RepositoryError tests Create with repository failure.
|
||||
func TestOwnerService_Create_RepositoryError(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
ownerRepo.CreateErr = errors.New("database error")
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
owner := &domain.Owner{
|
||||
Name: "Alice Smith",
|
||||
Email: "alice@example.com",
|
||||
TeamID: "team-001",
|
||||
}
|
||||
|
||||
err := ownerService.Create(ctx, owner, "user-1")
|
||||
if err == nil {
|
||||
t.Fatal("expected error from Create")
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_Update tests updating an existing owner.
|
||||
func TestOwnerService_Update(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
|
||||
originalOwner := &domain.Owner{
|
||||
ID: "owner-001",
|
||||
Name: "Alice Smith",
|
||||
Email: "alice@example.com",
|
||||
TeamID: "team-001",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
ownerRepo.AddOwner(originalOwner)
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
updatedOwner := &domain.Owner{
|
||||
Name: "Alice Johnson",
|
||||
Email: "alice.j@example.com",
|
||||
TeamID: "team-002",
|
||||
}
|
||||
|
||||
err := ownerService.Update(ctx, "owner-001", updatedOwner, "user-1")
|
||||
if err != nil {
|
||||
t.Fatalf("Update failed: %v", err)
|
||||
}
|
||||
|
||||
stored := ownerRepo.owners["owner-001"]
|
||||
if stored.Name != "Alice Johnson" {
|
||||
t.Errorf("expected updated name Alice Johnson, got %s", stored.Name)
|
||||
}
|
||||
|
||||
if stored.Email != "alice.j@example.com" {
|
||||
t.Errorf("expected updated email alice.j@example.com, got %s", stored.Email)
|
||||
}
|
||||
|
||||
if stored.ID != "owner-001" {
|
||||
t.Errorf("expected ID to remain owner-001, got %s", stored.ID)
|
||||
}
|
||||
|
||||
if len(auditRepo.Events) != 1 {
|
||||
t.Errorf("expected 1 audit event, got %d", len(auditRepo.Events))
|
||||
}
|
||||
|
||||
auditEvent := auditRepo.Events[0]
|
||||
if auditEvent.Action != "update_owner" {
|
||||
t.Errorf("expected action update_owner, got %s", auditEvent.Action)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_Update_EmptyName tests that Update rejects empty name.
|
||||
func TestOwnerService_Update_EmptyName(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
|
||||
originalOwner := &domain.Owner{
|
||||
ID: "owner-001",
|
||||
Name: "Alice Smith",
|
||||
Email: "alice@example.com",
|
||||
TeamID: "team-001",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
ownerRepo.AddOwner(originalOwner)
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
updatedOwner := &domain.Owner{
|
||||
Name: "",
|
||||
Email: "alice.j@example.com",
|
||||
TeamID: "team-002",
|
||||
}
|
||||
|
||||
err := ownerService.Update(ctx, "owner-001", updatedOwner, "user-1")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for empty owner name")
|
||||
}
|
||||
|
||||
if len(auditRepo.Events) != 0 {
|
||||
t.Errorf("expected 0 audit events after validation failure, got %d", len(auditRepo.Events))
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_Update_RepositoryError tests Update with repository failure.
|
||||
func TestOwnerService_Update_RepositoryError(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
ownerRepo.UpdateErr = errors.New("database error")
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
updatedOwner := &domain.Owner{
|
||||
Name: "Alice Johnson",
|
||||
Email: "alice.j@example.com",
|
||||
TeamID: "team-002",
|
||||
}
|
||||
|
||||
err := ownerService.Update(ctx, "owner-001", updatedOwner, "user-1")
|
||||
if err == nil {
|
||||
t.Fatal("expected error from Update")
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_Delete tests deleting an owner with audit recording.
|
||||
func TestOwnerService_Delete(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
|
||||
owner := &domain.Owner{
|
||||
ID: "owner-001",
|
||||
Name: "Alice Smith",
|
||||
Email: "alice@example.com",
|
||||
TeamID: "team-001",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
ownerRepo.AddOwner(owner)
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
err := ownerService.Delete(ctx, "owner-001", "user-1")
|
||||
if err != nil {
|
||||
t.Fatalf("Delete failed: %v", err)
|
||||
}
|
||||
|
||||
if len(ownerRepo.owners) != 0 {
|
||||
t.Errorf("expected 0 owners in repo after delete, got %d", len(ownerRepo.owners))
|
||||
}
|
||||
|
||||
if len(auditRepo.Events) != 1 {
|
||||
t.Errorf("expected 1 audit event, got %d", len(auditRepo.Events))
|
||||
}
|
||||
|
||||
auditEvent := auditRepo.Events[0]
|
||||
if auditEvent.Action != "delete_owner" {
|
||||
t.Errorf("expected action delete_owner, got %s", auditEvent.Action)
|
||||
}
|
||||
|
||||
if auditEvent.ResourceID != "owner-001" {
|
||||
t.Errorf("expected resource ID owner-001, got %s", auditEvent.ResourceID)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_Delete_RepositoryError tests Delete with repository failure.
|
||||
func TestOwnerService_Delete_RepositoryError(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
ownerRepo.DeleteErr = errors.New("database error")
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
err := ownerService.Delete(ctx, "owner-001", "user-1")
|
||||
if err == nil {
|
||||
t.Fatal("expected error from Delete")
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_ListOwners_HandlerInterface tests the handler interface method ListOwners.
|
||||
func TestOwnerService_ListOwners_HandlerInterface(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
owner1 := &domain.Owner{
|
||||
ID: "owner-001",
|
||||
Name: "Alice Smith",
|
||||
Email: "alice@example.com",
|
||||
TeamID: "team-001",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
owner2 := &domain.Owner{
|
||||
ID: "owner-002",
|
||||
Name: "Bob Jones",
|
||||
Email: "bob@example.com",
|
||||
TeamID: "team-001",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
ownerRepo.AddOwner(owner1)
|
||||
ownerRepo.AddOwner(owner2)
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
owners, total, err := ownerService.ListOwners(1, 50)
|
||||
if err != nil {
|
||||
t.Fatalf("ListOwners failed: %v", err)
|
||||
}
|
||||
|
||||
if len(owners) != 2 {
|
||||
t.Errorf("expected 2 owners, got %d", len(owners))
|
||||
}
|
||||
|
||||
if total != 2 {
|
||||
t.Errorf("expected total 2, got %d", total)
|
||||
}
|
||||
|
||||
// Verify value type conversion worked
|
||||
if owners[0].ID == "" {
|
||||
t.Fatal("expected non-empty owner ID in result")
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_GetOwner_HandlerInterface tests the handler interface method GetOwner.
|
||||
func TestOwnerService_GetOwner_HandlerInterface(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
owner := &domain.Owner{
|
||||
ID: "owner-001",
|
||||
Name: "Alice Smith",
|
||||
Email: "alice@example.com",
|
||||
TeamID: "team-001",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
ownerRepo.AddOwner(owner)
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
retrieved, err := ownerService.GetOwner("owner-001")
|
||||
if err != nil {
|
||||
t.Fatalf("GetOwner failed: %v", err)
|
||||
}
|
||||
|
||||
if retrieved.Name != "Alice Smith" {
|
||||
t.Errorf("expected name Alice Smith, got %s", retrieved.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_CreateOwner_HandlerInterface tests the handler interface method CreateOwner.
|
||||
func TestOwnerService_CreateOwner_HandlerInterface(t *testing.T) {
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
owner := domain.Owner{
|
||||
Name: "Alice Smith",
|
||||
Email: "alice@example.com",
|
||||
TeamID: "team-001",
|
||||
}
|
||||
|
||||
created, err := ownerService.CreateOwner(owner)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateOwner failed: %v", err)
|
||||
}
|
||||
|
||||
if created.ID == "" {
|
||||
t.Fatal("expected non-empty owner ID after creation")
|
||||
}
|
||||
|
||||
if created.Name != "Alice Smith" {
|
||||
t.Errorf("expected name Alice Smith, got %s", created.Name)
|
||||
}
|
||||
|
||||
if len(ownerRepo.owners) != 1 {
|
||||
t.Errorf("expected 1 owner in repo, got %d", len(ownerRepo.owners))
|
||||
}
|
||||
|
||||
// Note: handler interface method does NOT record audit events (no actor parameter)
|
||||
if len(auditRepo.Events) != 0 {
|
||||
t.Errorf("expected 0 audit events from handler interface method, got %d", len(auditRepo.Events))
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_UpdateOwner_HandlerInterface tests the handler interface method UpdateOwner.
|
||||
func TestOwnerService_UpdateOwner_HandlerInterface(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
originalOwner := &domain.Owner{
|
||||
ID: "owner-001",
|
||||
Name: "Alice Smith",
|
||||
Email: "alice@example.com",
|
||||
TeamID: "team-001",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
ownerRepo.AddOwner(originalOwner)
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
updatedOwner := domain.Owner{
|
||||
Name: "Alice Johnson",
|
||||
Email: "alice.j@example.com",
|
||||
TeamID: "team-002",
|
||||
}
|
||||
|
||||
updated, err := ownerService.UpdateOwner("owner-001", updatedOwner)
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateOwner failed: %v", err)
|
||||
}
|
||||
|
||||
if updated.ID != "owner-001" {
|
||||
t.Errorf("expected ID owner-001, got %s", updated.ID)
|
||||
}
|
||||
|
||||
if updated.Name != "Alice Johnson" {
|
||||
t.Errorf("expected updated name Alice Johnson, got %s", updated.Name)
|
||||
}
|
||||
|
||||
// Verify in repo
|
||||
stored := ownerRepo.owners["owner-001"]
|
||||
if stored.Email != "alice.j@example.com" {
|
||||
t.Errorf("expected updated email alice.j@example.com, got %s", stored.Email)
|
||||
}
|
||||
|
||||
// Note: handler interface method does NOT record audit events
|
||||
if len(auditRepo.Events) != 0 {
|
||||
t.Errorf("expected 0 audit events from handler interface method, got %d", len(auditRepo.Events))
|
||||
}
|
||||
}
|
||||
|
||||
// TestOwnerService_DeleteOwner_HandlerInterface tests the handler interface method DeleteOwner.
|
||||
func TestOwnerService_DeleteOwner_HandlerInterface(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
owner := &domain.Owner{
|
||||
ID: "owner-001",
|
||||
Name: "Alice Smith",
|
||||
Email: "alice@example.com",
|
||||
TeamID: "team-001",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
ownerRepo := newMockOwnerRepository()
|
||||
ownerRepo.AddOwner(owner)
|
||||
|
||||
auditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
ownerService := NewOwnerService(ownerRepo, auditService)
|
||||
|
||||
err := ownerService.DeleteOwner("owner-001")
|
||||
if err != nil {
|
||||
t.Fatalf("DeleteOwner failed: %v", err)
|
||||
}
|
||||
|
||||
if len(ownerRepo.owners) != 0 {
|
||||
t.Errorf("expected 0 owners in repo after delete, got %d", len(ownerRepo.owners))
|
||||
}
|
||||
|
||||
// Note: handler interface method does NOT record audit events
|
||||
if len(auditRepo.Events) != 0 {
|
||||
t.Errorf("expected 0 audit events from handler interface method, got %d", len(auditRepo.Events))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,691 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/shankar0123/certctl/internal/domain"
|
||||
"github.com/shankar0123/certctl/internal/repository"
|
||||
)
|
||||
|
||||
// mockTeamRepo is a test implementation of TeamRepository
|
||||
type mockTeamRepo struct {
|
||||
teams map[string]*domain.Team
|
||||
CreateErr error
|
||||
UpdateErr error
|
||||
DeleteErr error
|
||||
GetErr error
|
||||
ListErr error
|
||||
}
|
||||
|
||||
func (m *mockTeamRepo) List(ctx context.Context) ([]*domain.Team, error) {
|
||||
if m.ListErr != nil {
|
||||
return nil, m.ListErr
|
||||
}
|
||||
var teams []*domain.Team
|
||||
for _, t := range m.teams {
|
||||
teams = append(teams, t)
|
||||
}
|
||||
return teams, nil
|
||||
}
|
||||
|
||||
func (m *mockTeamRepo) Get(ctx context.Context, id string) (*domain.Team, error) {
|
||||
if m.GetErr != nil {
|
||||
return nil, m.GetErr
|
||||
}
|
||||
team, ok := m.teams[id]
|
||||
if !ok {
|
||||
return nil, errNotFound
|
||||
}
|
||||
return team, nil
|
||||
}
|
||||
|
||||
func (m *mockTeamRepo) Create(ctx context.Context, team *domain.Team) error {
|
||||
if m.CreateErr != nil {
|
||||
return m.CreateErr
|
||||
}
|
||||
m.teams[team.ID] = team
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockTeamRepo) Update(ctx context.Context, team *domain.Team) error {
|
||||
if m.UpdateErr != nil {
|
||||
return m.UpdateErr
|
||||
}
|
||||
m.teams[team.ID] = team
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockTeamRepo) Delete(ctx context.Context, id string) error {
|
||||
if m.DeleteErr != nil {
|
||||
return m.DeleteErr
|
||||
}
|
||||
delete(m.teams, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockTeamRepo) AddTeam(team *domain.Team) {
|
||||
m.teams[team.ID] = team
|
||||
}
|
||||
|
||||
func newMockTeamRepository() *mockTeamRepo {
|
||||
return &mockTeamRepo{
|
||||
teams: make(map[string]*domain.Team),
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_List tests retrieving teams with pagination
|
||||
func TestTeamService_List(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
// Add test teams
|
||||
for i := 0; i < 5; i++ {
|
||||
mockTeamRepo.AddTeam(&domain.Team{
|
||||
ID: "team-" + string(rune(i)),
|
||||
Name: "Team " + string(rune(48+i)),
|
||||
})
|
||||
}
|
||||
|
||||
teams, total, err := teamService.List(ctx, 1, 2)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if total != 5 {
|
||||
t.Errorf("expected total 5, got %d", total)
|
||||
}
|
||||
|
||||
if len(teams) != 2 {
|
||||
t.Errorf("expected 2 teams on page 1, got %d", len(teams))
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_List_DefaultPagination tests default pagination values
|
||||
func TestTeamService_List_DefaultPagination(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
// Add test teams
|
||||
for i := 0; i < 10; i++ {
|
||||
mockTeamRepo.AddTeam(&domain.Team{
|
||||
ID: "team-" + string(rune(i)),
|
||||
Name: "Team " + string(rune(48+i)),
|
||||
})
|
||||
}
|
||||
|
||||
// Test page < 1 defaults to 1
|
||||
teams, total, err := teamService.List(ctx, 0, 5)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if total != 10 {
|
||||
t.Errorf("expected total 10, got %d", total)
|
||||
}
|
||||
|
||||
if len(teams) != 5 {
|
||||
t.Errorf("expected 5 teams, got %d", len(teams))
|
||||
}
|
||||
|
||||
// Test perPage < 1 defaults to 50
|
||||
teams, total, err = teamService.List(ctx, 1, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(teams) != 10 {
|
||||
t.Errorf("expected 10 teams with perPage=50, got %d", len(teams))
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_List_RepositoryError tests error handling from repo
|
||||
func TestTeamService_List_RepositoryError(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
mockTeamRepo.ListErr = errors.New("database error")
|
||||
|
||||
_, _, err := teamService.List(ctx, 1, 50)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
|
||||
if !errors.Is(err, errors.New("database error")) {
|
||||
t.Errorf("expected database error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_List_EmptyResult tests empty list response
|
||||
func TestTeamService_List_EmptyResult(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
teams, total, err := teamService.List(ctx, 1, 50)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if total != 0 {
|
||||
t.Errorf("expected total 0, got %d", total)
|
||||
}
|
||||
|
||||
if len(teams) != 0 {
|
||||
t.Errorf("expected empty slice, got %d teams", len(teams))
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_List_PageBeyondRange tests pagination beyond available data
|
||||
func TestTeamService_List_PageBeyondRange(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
// Add only 3 teams
|
||||
for i := 0; i < 3; i++ {
|
||||
mockTeamRepo.AddTeam(&domain.Team{
|
||||
ID: "team-" + string(rune(i)),
|
||||
Name: "Team " + string(rune(48+i)),
|
||||
})
|
||||
}
|
||||
|
||||
// Request page beyond range
|
||||
teams, total, err := teamService.List(ctx, 10, 2)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if total != 3 {
|
||||
t.Errorf("expected total 3, got %d", total)
|
||||
}
|
||||
|
||||
if teams != nil && len(teams) != 0 {
|
||||
t.Errorf("expected empty slice for page beyond range, got %d teams", len(teams))
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_Get tests retrieving a single team
|
||||
func TestTeamService_Get(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
testTeam := &domain.Team{
|
||||
ID: "team-1",
|
||||
Name: "Test Team",
|
||||
}
|
||||
mockTeamRepo.AddTeam(testTeam)
|
||||
|
||||
team, err := teamService.Get(ctx, "team-1")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if team.ID != "team-1" || team.Name != "Test Team" {
|
||||
t.Errorf("expected team-1/Test Team, got %s/%s", team.ID, team.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_Get_NotFound tests retrieval of nonexistent team
|
||||
func TestTeamService_Get_NotFound(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
_, err := teamService.Get(ctx, "nonexistent")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error for nonexistent team, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_Create tests creating a new team
|
||||
func TestTeamService_Create(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
team := &domain.Team{
|
||||
Name: "New Team",
|
||||
Description: "A test team",
|
||||
}
|
||||
|
||||
err := teamService.Create(ctx, team, "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Verify ID was generated
|
||||
if team.ID == "" {
|
||||
t.Errorf("expected ID to be generated, got empty")
|
||||
}
|
||||
|
||||
if !team.ID[:5] == "team-" {
|
||||
t.Logf("note: generated ID is %s", team.ID)
|
||||
}
|
||||
|
||||
// Verify timestamps were set
|
||||
if team.CreatedAt.IsZero() {
|
||||
t.Errorf("expected CreatedAt to be set")
|
||||
}
|
||||
|
||||
if team.UpdatedAt.IsZero() {
|
||||
t.Errorf("expected UpdatedAt to be set")
|
||||
}
|
||||
|
||||
// Verify team was stored
|
||||
stored, err := teamService.Get(ctx, team.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve created team: %v", err)
|
||||
}
|
||||
|
||||
if stored.Name != "New Team" {
|
||||
t.Errorf("expected name 'New Team', got %s", stored.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_Create_EmptyName tests validation on empty name
|
||||
func TestTeamService_Create_EmptyName(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
team := &domain.Team{
|
||||
Name: "",
|
||||
}
|
||||
|
||||
err := teamService.Create(ctx, team, "test-user")
|
||||
if err == nil {
|
||||
t.Fatalf("expected validation error for empty name, got nil")
|
||||
}
|
||||
|
||||
if !errors.Is(err, errors.New("team name is required")) {
|
||||
t.Logf("error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_Create_WithExistingID tests preserving provided ID
|
||||
func TestTeamService_Create_WithExistingID(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
team := &domain.Team{
|
||||
ID: "custom-team-id",
|
||||
Name: "Custom Team",
|
||||
}
|
||||
|
||||
err := teamService.Create(ctx, team, "test-user")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if team.ID != "custom-team-id" {
|
||||
t.Errorf("expected ID to be preserved as custom-team-id, got %s", team.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_Create_RepositoryError tests repo error handling
|
||||
func TestTeamService_Create_RepositoryError(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
mockTeamRepo.CreateErr = errors.New("database insert failed")
|
||||
|
||||
team := &domain.Team{
|
||||
Name: "Test Team",
|
||||
}
|
||||
|
||||
err := teamService.Create(ctx, team, "test-user")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_Create_AuditRecorded tests audit event recording
|
||||
func TestTeamService_Create_AuditRecorded(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
team := &domain.Team{
|
||||
ID: "audit-test-team",
|
||||
Name: "Audit Test Team",
|
||||
}
|
||||
|
||||
err := teamService.Create(ctx, team, "audit-user")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Verify audit event was recorded
|
||||
if len(mockAuditRepo.Events) != 1 {
|
||||
t.Errorf("expected 1 audit event, got %d", len(mockAuditRepo.Events))
|
||||
}
|
||||
|
||||
if mockAuditRepo.Events[0].Action != "create_team" {
|
||||
t.Errorf("expected action 'create_team', got %s", mockAuditRepo.Events[0].Action)
|
||||
}
|
||||
|
||||
if mockAuditRepo.Events[0].ResourceID != "audit-test-team" {
|
||||
t.Errorf("expected resource ID 'audit-test-team', got %s", mockAuditRepo.Events[0].ResourceID)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_Update tests updating an existing team
|
||||
func TestTeamService_Update(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
// Create initial team
|
||||
initialTeam := &domain.Team{
|
||||
ID: "team-update",
|
||||
Name: "Original Name",
|
||||
Description: "Original description",
|
||||
}
|
||||
mockTeamRepo.AddTeam(initialTeam)
|
||||
|
||||
// Update team
|
||||
updateTeam := &domain.Team{
|
||||
Name: "Updated Name",
|
||||
Description: "Updated description",
|
||||
}
|
||||
|
||||
err := teamService.Update(ctx, "team-update", updateTeam, "update-user")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Verify ID was set correctly
|
||||
if updateTeam.ID != "team-update" {
|
||||
t.Errorf("expected ID to be set to team-update, got %s", updateTeam.ID)
|
||||
}
|
||||
|
||||
// Verify team was updated
|
||||
updated, err := teamService.Get(ctx, "team-update")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve updated team: %v", err)
|
||||
}
|
||||
|
||||
if updated.Name != "Updated Name" {
|
||||
t.Errorf("expected name 'Updated Name', got %s", updated.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_Update_EmptyName tests validation on update
|
||||
func TestTeamService_Update_EmptyName(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
mockTeamRepo.AddTeam(&domain.Team{
|
||||
ID: "team-1",
|
||||
Name: "Original",
|
||||
})
|
||||
|
||||
updateTeam := &domain.Team{
|
||||
Name: "",
|
||||
}
|
||||
|
||||
err := teamService.Update(ctx, "team-1", updateTeam, "user")
|
||||
if err == nil {
|
||||
t.Fatalf("expected validation error for empty name, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_Update_RepositoryError tests repo error handling
|
||||
func TestTeamService_Update_RepositoryError(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
mockTeamRepo.UpdateErr = errors.New("database update failed")
|
||||
|
||||
updateTeam := &domain.Team{
|
||||
Name: "Updated",
|
||||
}
|
||||
|
||||
err := teamService.Update(ctx, "team-1", updateTeam, "user")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_Delete tests deleting a team
|
||||
func TestTeamService_Delete(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
// Create team to delete
|
||||
mockTeamRepo.AddTeam(&domain.Team{
|
||||
ID: "team-delete",
|
||||
Name: "Team to Delete",
|
||||
})
|
||||
|
||||
err := teamService.Delete(ctx, "team-delete", "delete-user")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Verify team was deleted
|
||||
_, err = teamService.Get(ctx, "team-delete")
|
||||
if err == nil {
|
||||
t.Errorf("expected error for deleted team, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_Delete_RepositoryError tests repo error handling
|
||||
func TestTeamService_Delete_RepositoryError(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
mockTeamRepo.DeleteErr = errors.New("database delete failed")
|
||||
|
||||
err := teamService.Delete(ctx, "team-1", "user")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_ListTeams_HandlerInterface tests handler interface method
|
||||
func TestTeamService_ListTeams_HandlerInterface(t *testing.T) {
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
// Add test teams
|
||||
for i := 0; i < 3; i++ {
|
||||
mockTeamRepo.AddTeam(&domain.Team{
|
||||
ID: "team-" + string(rune(i)),
|
||||
Name: "Team " + string(rune(48+i)),
|
||||
})
|
||||
}
|
||||
|
||||
teams, total, err := teamService.ListTeams(1, 2)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if total != 3 {
|
||||
t.Errorf("expected total 3, got %d", total)
|
||||
}
|
||||
|
||||
if len(teams) != 3 {
|
||||
t.Errorf("expected 3 teams (ListTeams doesn't paginate), got %d", len(teams))
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_GetTeam_HandlerInterface tests handler interface method
|
||||
func TestTeamService_GetTeam_HandlerInterface(t *testing.T) {
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
testTeam := &domain.Team{
|
||||
ID: "handler-team",
|
||||
Name: "Handler Test Team",
|
||||
}
|
||||
mockTeamRepo.AddTeam(testTeam)
|
||||
|
||||
team, err := teamService.GetTeam("handler-team")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if team.ID != "handler-team" || team.Name != "Handler Test Team" {
|
||||
t.Errorf("expected handler-team/Handler Test Team, got %s/%s", team.ID, team.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_CreateTeam_HandlerInterface tests handler interface method
|
||||
func TestTeamService_CreateTeam_HandlerInterface(t *testing.T) {
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
team := domain.Team{
|
||||
Name: "Handler Create Team",
|
||||
Description: "Created via handler",
|
||||
}
|
||||
|
||||
result, err := teamService.CreateTeam(team)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if result.ID == "" {
|
||||
t.Errorf("expected ID to be generated")
|
||||
}
|
||||
|
||||
if result.Name != "Handler Create Team" {
|
||||
t.Errorf("expected name 'Handler Create Team', got %s", result.Name)
|
||||
}
|
||||
|
||||
if result.CreatedAt.IsZero() {
|
||||
t.Errorf("expected CreatedAt to be set")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_UpdateTeam_HandlerInterface tests handler interface method
|
||||
func TestTeamService_UpdateTeam_HandlerInterface(t *testing.T) {
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
// Create initial team
|
||||
mockTeamRepo.AddTeam(&domain.Team{
|
||||
ID: "handler-update-team",
|
||||
Name: "Original",
|
||||
})
|
||||
|
||||
updateTeam := domain.Team{
|
||||
Name: "Updated via Handler",
|
||||
Description: "Handler update",
|
||||
}
|
||||
|
||||
result, err := teamService.UpdateTeam("handler-update-team", updateTeam)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if result.ID != "handler-update-team" {
|
||||
t.Errorf("expected ID handler-update-team, got %s", result.ID)
|
||||
}
|
||||
|
||||
if result.Name != "Updated via Handler" {
|
||||
t.Errorf("expected name 'Updated via Handler', got %s", result.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_DeleteTeam_HandlerInterface tests handler interface method
|
||||
func TestTeamService_DeleteTeam_HandlerInterface(t *testing.T) {
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
mockAuditRepo := newMockAuditRepository()
|
||||
auditService := NewAuditService(mockAuditRepo)
|
||||
teamService := NewTeamService(mockTeamRepo, auditService)
|
||||
|
||||
// Create team to delete
|
||||
mockTeamRepo.AddTeam(&domain.Team{
|
||||
ID: "handler-delete-team",
|
||||
Name: "To Delete",
|
||||
})
|
||||
|
||||
err := teamService.DeleteTeam("handler-delete-team")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Verify deletion
|
||||
_, err = mockTeamRepo.Get(context.Background(), "handler-delete-team")
|
||||
if err == nil {
|
||||
t.Errorf("expected error for deleted team")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTeamService_NilAuditService tests behavior when audit service is nil
|
||||
func TestTeamService_NilAuditService(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockTeamRepo := newMockTeamRepository()
|
||||
teamService := NewTeamService(mockTeamRepo, nil)
|
||||
|
||||
team := &domain.Team{
|
||||
Name: "Test Team",
|
||||
}
|
||||
|
||||
// Should not panic with nil audit service
|
||||
err := teamService.Create(ctx, team, "user")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if team.ID == "" {
|
||||
t.Errorf("expected ID to be generated")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user