334 lines
11 KiB
TypeScript
334 lines
11 KiB
TypeScript
import { CreateOrganizationCommandHandler } from '../../../../src/Application/Organization/commands/CreateOrganizationCommandHandler';
|
|
import { UpdateOrganizationCommandHandler } from '../../../../src/Application/Organization/commands/UpdateOrganizationCommandHandler';
|
|
import { DeleteOrganizationCommandHandler } from '../../../../src/Application/Organization/commands/DeleteOrganizationCommandHandler';
|
|
import { CreateOrganizationCommand } from '../../../../src/Application/Organization/commands/CreateOrganizationCommand';
|
|
import { UpdateOrganizationCommand } from '../../../../src/Application/Organization/commands/UpdateOrganizationCommand';
|
|
import { DeleteOrganizationCommand } from '../../../../src/Application/Organization/commands/DeleteOrganizationCommand';
|
|
import { OrganizationState } from '../../../../src/Domain/Organization/OrganizationAggregate';
|
|
import { createMockOrganizationRepository, createMockOrganization } from '../../../testUtils';
|
|
|
|
describe('Organization Command Handlers - Comprehensive', () => {
|
|
let mockOrganizationRepository: ReturnType<typeof createMockOrganizationRepository>;
|
|
|
|
beforeEach(() => {
|
|
mockOrganizationRepository = createMockOrganizationRepository();
|
|
});
|
|
|
|
describe('CreateOrganizationCommandHandler', () => {
|
|
let handler: CreateOrganizationCommandHandler;
|
|
|
|
beforeEach(() => {
|
|
handler = new CreateOrganizationCommandHandler(mockOrganizationRepository);
|
|
});
|
|
|
|
it('should create organization successfully', async () => {
|
|
// Arrange
|
|
const mockOrgData = createMockOrganization({
|
|
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
name: 'Test Organization',
|
|
contactfname: 'John',
|
|
contactlname: 'Doe',
|
|
contactphone: '+1234567890',
|
|
contactemail: 'john@testorg.com',
|
|
url: null,
|
|
state: OrganizationState.REGISTERED
|
|
});
|
|
|
|
mockOrganizationRepository.create.mockResolvedValue(mockOrgData);
|
|
|
|
const command: CreateOrganizationCommand = {
|
|
name: 'Test Organization',
|
|
contactfname: 'John',
|
|
contactlname: 'Doe',
|
|
contactemail: 'john@testorg.com',
|
|
contactphone: '+1234567890'
|
|
};
|
|
|
|
// Act
|
|
const result = await handler.execute(command);
|
|
|
|
// Assert - Returns ShortOrganizationDto
|
|
expect(result).toEqual({
|
|
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
name: 'Test Organization',
|
|
state: 0,
|
|
userinorg: 0,
|
|
maxOrganizationalDecks: 10
|
|
});
|
|
expect(mockOrganizationRepository.create).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
name: 'Test Organization',
|
|
contactfname: 'John',
|
|
contactlname: 'Doe',
|
|
contactemail: 'john@testorg.com',
|
|
contactphone: '+1234567890',
|
|
state: OrganizationState.REGISTERED
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should create organization with optional URL', async () => {
|
|
// Arrange
|
|
const mockOrgData = createMockOrganization({
|
|
id: '550e8400-e29b-41d4-a716-446655440001',
|
|
name: 'Org with URL',
|
|
contactfname: 'Jane',
|
|
contactlname: 'Smith',
|
|
contactphone: '+1987654321',
|
|
contactemail: 'jane@orgwithurl.com',
|
|
url: 'https://orgwithurl.com',
|
|
state: OrganizationState.REGISTERED
|
|
});
|
|
|
|
mockOrganizationRepository.create.mockResolvedValue(mockOrgData);
|
|
|
|
const command: CreateOrganizationCommand = {
|
|
name: 'Org with URL',
|
|
contactfname: 'Jane',
|
|
contactlname: 'Smith',
|
|
contactemail: 'jane@orgwithurl.com',
|
|
contactphone: '+1987654321',
|
|
url: 'https://orgwithurl.com'
|
|
};
|
|
|
|
// Act
|
|
const result = await handler.execute(command);
|
|
|
|
// Assert
|
|
expect(result).toEqual({
|
|
id: '550e8400-e29b-41d4-a716-446655440001',
|
|
name: 'Org with URL',
|
|
state: 0,
|
|
userinorg: 0,
|
|
maxOrganizationalDecks: 10
|
|
});
|
|
});
|
|
|
|
it('should handle duplicate organization name error', async () => {
|
|
// Arrange
|
|
const command: CreateOrganizationCommand = {
|
|
name: 'Duplicate Org',
|
|
contactfname: 'John',
|
|
contactlname: 'Doe',
|
|
contactemail: 'john@duplicate.com',
|
|
contactphone: '+1234567890'
|
|
};
|
|
|
|
const duplicateError = new Error('duplicate key value violates unique constraint "organization_name_unique"');
|
|
mockOrganizationRepository.create.mockRejectedValue(duplicateError);
|
|
|
|
// Act & Assert
|
|
await expect(handler.execute(command)).rejects.toThrow('Organization with this name or contact email already exists');
|
|
});
|
|
|
|
it('should handle generic database errors', async () => {
|
|
// Arrange
|
|
const command: CreateOrganizationCommand = {
|
|
name: 'Error Org',
|
|
contactfname: 'John',
|
|
contactlname: 'Doe',
|
|
contactemail: 'john@error.com',
|
|
contactphone: '+1234567890'
|
|
};
|
|
|
|
mockOrganizationRepository.create.mockRejectedValue(new Error('Database connection failed'));
|
|
|
|
// Act & Assert
|
|
await expect(handler.execute(command)).rejects.toThrow('Failed to create organization');
|
|
});
|
|
|
|
it('should handle non-Error exceptions', async () => {
|
|
// Arrange
|
|
const command: CreateOrganizationCommand = {
|
|
name: 'Non-Error Exception Org',
|
|
contactfname: 'John',
|
|
contactlname: 'Doe',
|
|
contactemail: 'john@exception.com',
|
|
contactphone: '+1234567890'
|
|
};
|
|
|
|
mockOrganizationRepository.create.mockRejectedValue('String error');
|
|
|
|
// Act & Assert
|
|
await expect(handler.execute(command)).rejects.toThrow('Failed to create organization');
|
|
});
|
|
});
|
|
|
|
describe('UpdateOrganizationCommandHandler', () => {
|
|
let handler: UpdateOrganizationCommandHandler;
|
|
|
|
beforeEach(() => {
|
|
handler = new UpdateOrganizationCommandHandler(mockOrganizationRepository);
|
|
});
|
|
|
|
it('should update organization successfully', async () => {
|
|
// Arrange
|
|
const updatedOrgData = createMockOrganization({
|
|
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
name: 'Updated Organization',
|
|
contactemail: 'john@updated.com',
|
|
url: 'https://updated.com',
|
|
state: OrganizationState.ACTIVE
|
|
});
|
|
|
|
mockOrganizationRepository.update.mockResolvedValue(updatedOrgData);
|
|
|
|
const command: UpdateOrganizationCommand = {
|
|
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
name: 'Updated Organization',
|
|
contactemail: 'john@updated.com',
|
|
url: 'https://updated.com'
|
|
};
|
|
|
|
// Act
|
|
const result = await handler.execute(command);
|
|
|
|
// Assert - Returns ShortOrganizationDto
|
|
expect(result).toEqual({
|
|
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
name: 'Updated Organization',
|
|
state: 1,
|
|
userinorg: 0,
|
|
maxOrganizationalDecks: 10
|
|
});
|
|
expect(mockOrganizationRepository.update).toHaveBeenCalledWith(
|
|
'550e8400-e29b-41d4-a716-446655440000',
|
|
command
|
|
);
|
|
});
|
|
|
|
it('should return null when organization not found', async () => {
|
|
// Arrange
|
|
mockOrganizationRepository.update.mockResolvedValue(null);
|
|
|
|
const command: UpdateOrganizationCommand = {
|
|
id: 'non-existent-id',
|
|
name: 'Non-existent Organization'
|
|
};
|
|
|
|
// Act
|
|
const result = await handler.execute(command);
|
|
|
|
// Assert
|
|
expect(result).toBeNull();
|
|
expect(mockOrganizationRepository.update).toHaveBeenCalledWith('non-existent-id', command);
|
|
});
|
|
|
|
it('should update organization with partial data', async () => {
|
|
// Arrange
|
|
const partialUpdatedOrgData = createMockOrganization({
|
|
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
name: 'Original Name',
|
|
contactemail: 'john@newmail.com',
|
|
state: OrganizationState.ACTIVE
|
|
});
|
|
|
|
mockOrganizationRepository.update.mockResolvedValue(partialUpdatedOrgData);
|
|
|
|
const command: UpdateOrganizationCommand = {
|
|
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
contactemail: 'john@newmail.com'
|
|
};
|
|
|
|
// Act
|
|
const result = await handler.execute(command);
|
|
|
|
// Assert
|
|
expect(result).toEqual({
|
|
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
name: 'Original Name',
|
|
state: 1,
|
|
userinorg: 0,
|
|
maxOrganizationalDecks: 10
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('DeleteOrganizationCommandHandler', () => {
|
|
let handler: DeleteOrganizationCommandHandler;
|
|
|
|
beforeEach(() => {
|
|
handler = new DeleteOrganizationCommandHandler(mockOrganizationRepository);
|
|
});
|
|
|
|
it('should perform soft delete successfully', async () => {
|
|
// Arrange
|
|
mockOrganizationRepository.softDelete.mockResolvedValue(null);
|
|
|
|
const command: DeleteOrganizationCommand = {
|
|
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
soft: true
|
|
};
|
|
|
|
// Act
|
|
const result = await handler.execute(command);
|
|
|
|
// Assert
|
|
expect(result).toBe(true);
|
|
expect(mockOrganizationRepository.softDelete).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440000');
|
|
expect(mockOrganizationRepository.delete).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should perform hard delete successfully', async () => {
|
|
// Arrange
|
|
mockOrganizationRepository.delete.mockResolvedValue(true);
|
|
|
|
const command: DeleteOrganizationCommand = {
|
|
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
soft: false
|
|
};
|
|
|
|
// Act
|
|
const result = await handler.execute(command);
|
|
|
|
// Assert
|
|
expect(result).toBe(true);
|
|
expect(mockOrganizationRepository.delete).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440000');
|
|
expect(mockOrganizationRepository.softDelete).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should default to hard delete when soft flag not specified', async () => {
|
|
// Arrange
|
|
mockOrganizationRepository.delete.mockResolvedValue(true);
|
|
|
|
const command: DeleteOrganizationCommand = {
|
|
id: '550e8400-e29b-41d4-a716-446655440000'
|
|
};
|
|
|
|
// Act
|
|
const result = await handler.execute(command);
|
|
|
|
// Assert
|
|
expect(result).toBe(true);
|
|
expect(mockOrganizationRepository.delete).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440000');
|
|
expect(mockOrganizationRepository.softDelete).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should handle soft delete with repository error gracefully', async () => {
|
|
// Arrange
|
|
mockOrganizationRepository.softDelete.mockRejectedValue(new Error('Database error'));
|
|
|
|
const command: DeleteOrganizationCommand = {
|
|
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
soft: true
|
|
};
|
|
|
|
// Act & Assert - Handler doesn't catch errors, they bubble up
|
|
await expect(handler.execute(command)).rejects.toThrow('Database error');
|
|
});
|
|
|
|
it('should handle hard delete with repository error gracefully', async () => {
|
|
// Arrange
|
|
mockOrganizationRepository.delete.mockRejectedValue(new Error('Database error'));
|
|
|
|
const command: DeleteOrganizationCommand = {
|
|
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
soft: false
|
|
};
|
|
|
|
// Act & Assert - Handler doesn't catch errors, they bubble up
|
|
await expect(handler.execute(command)).rejects.toThrow('Database error');
|
|
});
|
|
});
|
|
});
|