integration tests
Some checks failed
Contract Testing / contract-tests (pull_request) Failing after 4m51s
Contract Testing / contract-snapshot (pull_request) Has been skipped

This commit is contained in:
2026-01-22 17:29:06 +01:00
parent f61ebda9b7
commit 597bb48248
68 changed files with 11832 additions and 3498 deletions

View File

@@ -2,456 +2,83 @@
* Integration Test: Onboarding Personal Information Use Case Orchestration
*
* Tests the orchestration logic of personal information-related Use Cases:
* - ValidatePersonalInfoUseCase: Validates personal information
* - SavePersonalInfoUseCase: Saves personal information to repository
* - UpdatePersonalInfoUseCase: Updates existing personal information
* - GetPersonalInfoUseCase: Retrieves personal information
* - CompleteDriverOnboardingUseCase: Handles the initial driver profile creation
*
* Validates that Use Cases correctly interact with their Ports (Repositories, Event Publishers)
* Validates that Use Cases correctly interact with their Ports (Repositories)
* Uses In-Memory adapters for fast, deterministic testing
*
* Focus: Business logic orchestration, NOT UI rendering
*/
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import { InMemoryUserRepository } from '../../../adapters/users/persistence/inmemory/InMemoryUserRepository';
import { InMemoryEventPublisher } from '../../../adapters/events/InMemoryEventPublisher';
import { ValidatePersonalInfoUseCase } from '../../../core/onboarding/use-cases/ValidatePersonalInfoUseCase';
import { SavePersonalInfoUseCase } from '../../../core/onboarding/use-cases/SavePersonalInfoUseCase';
import { UpdatePersonalInfoUseCase } from '../../../core/onboarding/use-cases/UpdatePersonalInfoUseCase';
import { GetPersonalInfoUseCase } from '../../../core/onboarding/use-cases/GetPersonalInfoUseCase';
import { PersonalInfoCommand } from '../../../core/onboarding/ports/PersonalInfoCommand';
import { PersonalInfoQuery } from '../../../core/onboarding/ports/PersonalInfoQuery';
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
import { InMemoryDriverRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryDriverRepository';
import { CompleteDriverOnboardingUseCase } from '../../../core/racing/application/use-cases/CompleteDriverOnboardingUseCase';
import { Logger } from '../../../core/shared/domain/Logger';
describe('Onboarding Personal Information Use Case Orchestration', () => {
let userRepository: InMemoryUserRepository;
let eventPublisher: InMemoryEventPublisher;
let validatePersonalInfoUseCase: ValidatePersonalInfoUseCase;
let savePersonalInfoUseCase: SavePersonalInfoUseCase;
let updatePersonalInfoUseCase: UpdatePersonalInfoUseCase;
let getPersonalInfoUseCase: GetPersonalInfoUseCase;
let driverRepository: InMemoryDriverRepository;
let completeDriverOnboardingUseCase: CompleteDriverOnboardingUseCase;
let mockLogger: Logger;
beforeAll(() => {
// TODO: Initialize In-Memory repositories and event publisher
// userRepository = new InMemoryUserRepository();
// eventPublisher = new InMemoryEventPublisher();
// validatePersonalInfoUseCase = new ValidatePersonalInfoUseCase({
// userRepository,
// eventPublisher,
// });
// savePersonalInfoUseCase = new SavePersonalInfoUseCase({
// userRepository,
// eventPublisher,
// });
// updatePersonalInfoUseCase = new UpdatePersonalInfoUseCase({
// userRepository,
// eventPublisher,
// });
// getPersonalInfoUseCase = new GetPersonalInfoUseCase({
// userRepository,
// eventPublisher,
// });
mockLogger = {
info: () => {},
debug: () => {},
warn: () => {},
error: () => {},
} as unknown as Logger;
driverRepository = new InMemoryDriverRepository(mockLogger);
completeDriverOnboardingUseCase = new CompleteDriverOnboardingUseCase(
driverRepository,
mockLogger
);
});
beforeEach(() => {
// TODO: Clear all In-Memory repositories before each test
// userRepository.clear();
// eventPublisher.clear();
beforeEach(async () => {
await driverRepository.clear();
});
describe('ValidatePersonalInfoUseCase - Success Path', () => {
it('should validate personal info with all required fields', async () => {
// TODO: Implement test
describe('CompleteDriverOnboardingUseCase - Personal Info Scenarios', () => {
it('should create driver with valid personal information', async () => {
// Scenario: Valid personal info
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with valid personal info
// Then: Validation should pass
// And: EventPublisher should emit PersonalInfoValidatedEvent
// Given: A new user
const input = {
userId: 'user-789',
firstName: 'Alice',
lastName: 'Wonderland',
displayName: 'AliceRacer',
country: 'UK',
};
// When: CompleteDriverOnboardingUseCase.execute() is called
const result = await completeDriverOnboardingUseCase.execute(input);
// Then: Validation should pass and driver be created
expect(result.isOk()).toBe(true);
const { driver } = result.unwrap();
expect(driver.name.toString()).toBe('AliceRacer');
expect(driver.country.toString()).toBe('UK');
});
it('should validate personal info with minimum length display name', async () => {
// TODO: Implement test
// Scenario: Minimum length display name
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with 3-character display name
// Then: Validation should pass
// And: EventPublisher should emit PersonalInfoValidatedEvent
});
it('should handle bio as optional personal information', async () => {
// Scenario: Optional bio field
// Given: Personal info with bio
const input = {
userId: 'user-bio',
firstName: 'Bob',
lastName: 'Builder',
displayName: 'BobBuilds',
country: 'AU',
bio: 'I build fast cars',
};
it('should validate personal info with maximum length display name', async () => {
// TODO: Implement test
// Scenario: Maximum length display name
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with 50-character display name
// Then: Validation should pass
// And: EventPublisher should emit PersonalInfoValidatedEvent
});
// When: CompleteDriverOnboardingUseCase.execute() is called
const result = await completeDriverOnboardingUseCase.execute(input);
it('should validate personal info with special characters in display name', async () => {
// TODO: Implement test
// Scenario: Special characters in display name
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with display name containing special characters
// Then: Validation should pass
// And: EventPublisher should emit PersonalInfoValidatedEvent
});
it('should validate personal info with various countries', async () => {
// TODO: Implement test
// Scenario: Various countries
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with different countries
// Then: Validation should pass
// And: EventPublisher should emit PersonalInfoValidatedEvent
});
it('should validate personal info with various timezones', async () => {
// TODO: Implement test
// Scenario: Various timezones
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with different timezones
// Then: Validation should pass
// And: EventPublisher should emit PersonalInfoValidatedEvent
});
});
describe('ValidatePersonalInfoUseCase - Validation', () => {
it('should reject personal info with empty first name', async () => {
// TODO: Implement test
// Scenario: Empty first name
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with empty first name
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit PersonalInfoValidatedEvent
});
it('should reject personal info with empty last name', async () => {
// TODO: Implement test
// Scenario: Empty last name
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with empty last name
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit PersonalInfoValidatedEvent
});
it('should reject personal info with empty display name', async () => {
// TODO: Implement test
// Scenario: Empty display name
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with empty display name
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit PersonalInfoValidatedEvent
});
it('should reject personal info with display name too short', async () => {
// TODO: Implement test
// Scenario: Display name too short
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with display name less than 3 characters
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit PersonalInfoValidatedEvent
});
it('should reject personal info with display name too long', async () => {
// TODO: Implement test
// Scenario: Display name too long
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with display name more than 50 characters
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit PersonalInfoValidatedEvent
});
it('should reject personal info with empty country', async () => {
// TODO: Implement test
// Scenario: Empty country
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with empty country
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit PersonalInfoValidatedEvent
});
it('should reject personal info with invalid characters in first name', async () => {
// TODO: Implement test
// Scenario: Invalid characters in first name
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with numbers in first name
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit PersonalInfoValidatedEvent
});
it('should reject personal info with invalid characters in last name', async () => {
// TODO: Implement test
// Scenario: Invalid characters in last name
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with numbers in last name
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit PersonalInfoValidatedEvent
});
it('should reject personal info with profanity in display name', async () => {
// TODO: Implement test
// Scenario: Profanity in display name
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with profanity in display name
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit PersonalInfoValidatedEvent
});
it('should reject personal info with duplicate display name', async () => {
// TODO: Implement test
// Scenario: Duplicate display name
// Given: A user with display name "RacerJohn" already exists
// And: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with display name "RacerJohn"
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit PersonalInfoValidatedEvent
});
it('should reject personal info with display name containing only spaces', async () => {
// TODO: Implement test
// Scenario: Display name with only spaces
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with display name containing only spaces
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit PersonalInfoValidatedEvent
});
it('should reject personal info with display name with leading/trailing spaces', async () => {
// TODO: Implement test
// Scenario: Display name with leading/trailing spaces
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with display name " John "
// Then: Should throw ValidationError (after trimming)
// And: EventPublisher should NOT emit PersonalInfoValidatedEvent
});
it('should reject personal info with email format in display name', async () => {
// TODO: Implement test
// Scenario: Email format in display name
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with email in display name
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit PersonalInfoValidatedEvent
});
});
describe('SavePersonalInfoUseCase - Success Path', () => {
it('should save personal info with all required fields', async () => {
// TODO: Implement test
// Scenario: Save valid personal info
// Given: A new user exists
// And: Personal info is validated
// When: SavePersonalInfoUseCase.execute() is called with valid personal info
// Then: Personal info should be saved
// And: EventPublisher should emit PersonalInfoSavedEvent
});
it('should save personal info with optional fields', async () => {
// TODO: Implement test
// Scenario: Save personal info with optional fields
// Given: A new user exists
// And: Personal info is validated
// When: SavePersonalInfoUseCase.execute() is called with optional fields
// Then: Personal info should be saved
// And: Optional fields should be saved
// And: EventPublisher should emit PersonalInfoSavedEvent
});
it('should save personal info with different timezones', async () => {
// TODO: Implement test
// Scenario: Save personal info with different timezones
// Given: A new user exists
// And: Personal info is validated
// When: SavePersonalInfoUseCase.execute() is called with different timezones
// Then: Personal info should be saved
// And: Timezone should be saved correctly
// And: EventPublisher should emit PersonalInfoSavedEvent
});
});
describe('SavePersonalInfoUseCase - Validation', () => {
it('should reject saving personal info without validation', async () => {
// TODO: Implement test
// Scenario: Save without validation
// Given: A new user exists
// When: SavePersonalInfoUseCase.execute() is called without validation
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit PersonalInfoSavedEvent
});
it('should reject saving personal info for already onboarded user', async () => {
// TODO: Implement test
// Scenario: Already onboarded user
// Given: A user has already completed onboarding
// When: SavePersonalInfoUseCase.execute() is called
// Then: Should throw AlreadyOnboardedError
// And: EventPublisher should NOT emit PersonalInfoSavedEvent
});
});
describe('UpdatePersonalInfoUseCase - Success Path', () => {
it('should update personal info with valid data', async () => {
// TODO: Implement test
// Scenario: Update personal info
// Given: A user exists with personal info
// When: UpdatePersonalInfoUseCase.execute() is called with new valid data
// Then: Personal info should be updated
// And: EventPublisher should emit PersonalInfoUpdatedEvent
});
it('should update personal info with partial data', async () => {
// TODO: Implement test
// Scenario: Update with partial data
// Given: A user exists with personal info
// When: UpdatePersonalInfoUseCase.execute() is called with partial data
// Then: Only specified fields should be updated
// And: EventPublisher should emit PersonalInfoUpdatedEvent
});
it('should update personal info with timezone change', async () => {
// TODO: Implement test
// Scenario: Update timezone
// Given: A user exists with personal info
// When: UpdatePersonalInfoUseCase.execute() is called with new timezone
// Then: Timezone should be updated
// And: EventPublisher should emit PersonalInfoUpdatedEvent
});
});
describe('UpdatePersonalInfoUseCase - Validation', () => {
it('should reject update with invalid data', async () => {
// TODO: Implement test
// Scenario: Invalid update data
// Given: A user exists with personal info
// When: UpdatePersonalInfoUseCase.execute() is called with invalid data
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit PersonalInfoUpdatedEvent
});
it('should reject update for non-existent user', async () => {
// TODO: Implement test
// Scenario: Non-existent user
// Given: No user exists
// When: UpdatePersonalInfoUseCase.execute() is called
// Then: Should throw UserNotFoundError
// And: EventPublisher should NOT emit PersonalInfoUpdatedEvent
});
it('should reject update with duplicate display name', async () => {
// TODO: Implement test
// Scenario: Duplicate display name
// Given: User A has display name "RacerJohn"
// And: User B exists
// When: UpdatePersonalInfoUseCase.execute() is called for User B with display name "RacerJohn"
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit PersonalInfoUpdatedEvent
});
});
describe('GetPersonalInfoUseCase - Success Path', () => {
it('should retrieve personal info for existing user', async () => {
// TODO: Implement test
// Scenario: Retrieve personal info
// Given: A user exists with personal info
// When: GetPersonalInfoUseCase.execute() is called
// Then: Personal info should be returned
// And: EventPublisher should emit PersonalInfoRetrievedEvent
});
it('should retrieve personal info with all fields', async () => {
// TODO: Implement test
// Scenario: Retrieve with all fields
// Given: A user exists with complete personal info
// When: GetPersonalInfoUseCase.execute() is called
// Then: All personal info fields should be returned
// And: EventPublisher should emit PersonalInfoRetrievedEvent
});
it('should retrieve personal info with minimal fields', async () => {
// TODO: Implement test
// Scenario: Retrieve with minimal fields
// Given: A user exists with minimal personal info
// When: GetPersonalInfoUseCase.execute() is called
// Then: Available personal info fields should be returned
// And: EventPublisher should emit PersonalInfoRetrievedEvent
});
});
describe('GetPersonalInfoUseCase - Validation', () => {
it('should reject retrieval for non-existent user', async () => {
// TODO: Implement test
// Scenario: Non-existent user
// Given: No user exists
// When: GetPersonalInfoUseCase.execute() is called
// Then: Should throw UserNotFoundError
// And: EventPublisher should NOT emit PersonalInfoRetrievedEvent
});
it('should reject retrieval for user without personal info', async () => {
// TODO: Implement test
// Scenario: User without personal info
// Given: A user exists without personal info
// When: GetPersonalInfoUseCase.execute() is called
// Then: Should throw PersonalInfoNotFoundError
// And: EventPublisher should NOT emit PersonalInfoRetrievedEvent
});
});
describe('Personal Info Orchestration - Error Handling', () => {
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository error
// Given: UserRepository throws an error
// When: ValidatePersonalInfoUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
it('should handle concurrent updates gracefully', async () => {
// TODO: Implement test
// Scenario: Concurrent updates
// Given: A user exists with personal info
// When: UpdatePersonalInfoUseCase.execute() is called multiple times concurrently
// Then: Updates should be handled appropriately
// And: EventPublisher should emit appropriate events
});
});
describe('Personal Info Orchestration - Edge Cases', () => {
it('should handle timezone edge cases', async () => {
// TODO: Implement test
// Scenario: Edge case timezones
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with edge case timezones
// Then: Validation should pass
// And: EventPublisher should emit PersonalInfoValidatedEvent
});
it('should handle country edge cases', async () => {
// TODO: Implement test
// Scenario: Edge case countries
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with edge case countries
// Then: Validation should pass
// And: EventPublisher should emit PersonalInfoValidatedEvent
});
it('should handle display name edge cases', async () => {
// TODO: Implement test
// Scenario: Edge case display names
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with edge case display names
// Then: Validation should pass
// And: EventPublisher should emit PersonalInfoValidatedEvent
});
it('should handle special characters in names', async () => {
// TODO: Implement test
// Scenario: Special characters in names
// Given: A new user exists
// When: ValidatePersonalInfoUseCase.execute() is called with special characters in names
// Then: Validation should pass
// And: EventPublisher should emit PersonalInfoValidatedEvent
// Then: Bio should be saved
expect(result.isOk()).toBe(true);
expect(result.unwrap().driver.bio?.toString()).toBe('I build fast cars');
});
});
});