/** * Integration Test: Team Creation Use Case Orchestration * * Tests the orchestration logic of team creation-related Use Cases: * - CreateTeamUseCase: Creates a new team with name, description, and leagues * - 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, beforeEach } from 'vitest'; import { InMemoryTeamRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamRepository'; import { InMemoryTeamMembershipRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamMembershipRepository'; import { CreateTeamUseCase } from '../../../core/racing/application/use-cases/CreateTeamUseCase'; import { Team } from '../../../core/racing/domain/entities/Team'; import { Driver } from '../../../core/racing/domain/entities/Driver'; import { League } from '../../../core/racing/domain/entities/League'; import { Logger } from '../../../core/shared/domain/Logger'; describe('Team Creation Use Case Orchestration', () => { let teamRepository: InMemoryTeamRepository; let membershipRepository: InMemoryTeamMembershipRepository; let createTeamUseCase: CreateTeamUseCase; let mockLogger: Logger; beforeAll(() => { mockLogger = { info: () => {}, debug: () => {}, warn: () => {}, error: () => {}, } as unknown as Logger; teamRepository = new InMemoryTeamRepository(mockLogger); membershipRepository = new InMemoryTeamMembershipRepository(mockLogger); createTeamUseCase = new CreateTeamUseCase(teamRepository, membershipRepository, mockLogger); }); beforeEach(() => { teamRepository.clear(); membershipRepository.clear(); }); describe('CreateTeamUseCase - Success Path', () => { it('should create a team with all required fields', async () => { // Scenario: Team creation with complete information // Given: A driver exists const driverId = 'd1'; const driver = Driver.create({ id: driverId, iracingId: '1', name: 'John Doe', country: 'US' }); // And: A league exists const leagueId = 'l1'; const league = League.create({ id: leagueId, name: 'League 1', description: 'Test League', ownerId: 'owner' }); // When: CreateTeamUseCase.execute() is called with valid command const result = await createTeamUseCase.execute({ name: 'Test Team', tag: 'TT', description: 'A test team', ownerId: driverId, leagues: [leagueId] }); // Then: The team should be created successfully expect(result.isOk()).toBe(true); const { team } = result.unwrap(); // And: The team should have the correct properties expect(team.name.toString()).toBe('Test Team'); expect(team.tag.toString()).toBe('TT'); expect(team.description.toString()).toBe('A test team'); expect(team.ownerId.toString()).toBe(driverId); expect(team.leagues.map(l => l.toString())).toContain(leagueId); // And: The team should be in the repository const savedTeam = await teamRepository.findById(team.id.toString()); expect(savedTeam).toBeDefined(); expect(savedTeam?.name.toString()).toBe('Test Team'); // And: The driver should have an owner membership const membership = await membershipRepository.getMembership(team.id.toString(), driverId); expect(membership).toBeDefined(); expect(membership?.role).toBe('owner'); expect(membership?.status).toBe('active'); }); it('should create a team with optional description', async () => { // Scenario: Team creation with description // Given: A driver exists const driverId = 'd2'; const driver = Driver.create({ id: driverId, iracingId: '2', name: 'Jane Doe', country: 'UK' }); // And: A league exists const leagueId = 'l2'; const league = League.create({ id: leagueId, name: 'League 2', description: 'Test League 2', ownerId: 'owner' }); // When: CreateTeamUseCase.execute() is called with description const result = await createTeamUseCase.execute({ name: 'Team With Description', tag: 'TWD', description: 'This team has a detailed description', ownerId: driverId, leagues: [leagueId] }); // Then: The team should be created with the description expect(result.isOk()).toBe(true); const { team } = result.unwrap(); expect(team.description.toString()).toBe('This team has a detailed description'); }); it('should create a team with minimal required fields', async () => { // Scenario: Team creation with minimal information // Given: A driver exists const driverId = 'd3'; const driver = Driver.create({ id: driverId, iracingId: '3', name: 'Bob Smith', country: 'CA' }); // And: A league exists const leagueId = 'l3'; const league = League.create({ id: leagueId, name: 'League 3', description: 'Test League 3', ownerId: 'owner' }); // When: CreateTeamUseCase.execute() is called with only required fields const result = await createTeamUseCase.execute({ name: 'Minimal Team', tag: 'MT', description: '', ownerId: driverId, leagues: [leagueId] }); // Then: The team should be created with default values expect(result.isOk()).toBe(true); const { team } = result.unwrap(); expect(team.name.toString()).toBe('Minimal Team'); expect(team.tag.toString()).toBe('MT'); expect(team.description.toString()).toBe(''); }); }); describe('CreateTeamUseCase - Validation', () => { it('should reject team creation with empty team name', async () => { // Scenario: Team creation with empty name // Given: A driver exists const driverId = 'd4'; const driver = Driver.create({ id: driverId, iracingId: '4', name: 'Test Driver', country: 'US' }); // And: A league exists const leagueId = 'l4'; const league = League.create({ id: leagueId, name: 'League 4', description: 'Test League 4', ownerId: 'owner' }); // When: CreateTeamUseCase.execute() is called with empty team name const result = await createTeamUseCase.execute({ name: '', tag: 'TT', description: 'A test team', ownerId: driverId, leagues: [leagueId] }); // Then: Should return error expect(result.isErr()).toBe(true); const error = result.unwrapErr(); expect(error.code).toBe('VALIDATION_ERROR'); }); it('should reject team creation with invalid team name format', async () => { // Scenario: Team creation with invalid name format // Given: A driver exists const driverId = 'd5'; const driver = Driver.create({ id: driverId, iracingId: '5', name: 'Test Driver', country: 'US' }); // And: A league exists const leagueId = 'l5'; const league = League.create({ id: leagueId, name: 'League 5', description: 'Test League 5', ownerId: 'owner' }); // When: CreateTeamUseCase.execute() is called with invalid team name const result = await createTeamUseCase.execute({ name: 'Invalid!@#$%', tag: 'TT', description: 'A test team', ownerId: driverId, leagues: [leagueId] }); // Then: Should return error expect(result.isErr()).toBe(true); const error = result.unwrapErr(); expect(error.code).toBe('VALIDATION_ERROR'); }); it('should reject team creation when driver already belongs to a team', async () => { // Scenario: Driver already belongs to a team // Given: A driver exists const driverId = 'd6'; const driver = Driver.create({ id: driverId, iracingId: '6', name: 'Test Driver', country: 'US' }); // And: A league exists const leagueId = 'l6'; const league = League.create({ id: leagueId, name: 'League 6', description: 'Test League 6', ownerId: 'owner' }); // And: The driver already belongs to a team const existingTeam = Team.create({ id: 'existing', name: 'Existing Team', tag: 'ET', description: 'Existing', ownerId: driverId, leagues: [] }); await teamRepository.create(existingTeam); await membershipRepository.saveMembership({ teamId: 'existing', driverId: driverId, role: 'driver', status: 'active', joinedAt: new Date() }); // When: CreateTeamUseCase.execute() is called const result = await createTeamUseCase.execute({ name: 'New Team', tag: 'NT', description: 'A new team', ownerId: driverId, leagues: [leagueId] }); // Then: Should return error expect(result.isErr()).toBe(true); const error = result.unwrapErr(); expect(error.code).toBe('VALIDATION_ERROR'); expect(error.details.message).toContain('already belongs to a team'); }); }); describe('CreateTeamUseCase - Error Handling', () => { it('should throw error when driver does not exist', async () => { // Scenario: Non-existent driver // Given: No driver exists with the given ID const nonExistentDriverId = 'nonexistent'; // And: A league exists const leagueId = 'l7'; const league = League.create({ id: leagueId, name: 'League 7', description: 'Test League 7', ownerId: 'owner' }); // When: CreateTeamUseCase.execute() is called with non-existent driver ID const result = await createTeamUseCase.execute({ name: 'Test Team', tag: 'TT', description: 'A test team', ownerId: nonExistentDriverId, leagues: [leagueId] }); // Then: Should return error expect(result.isErr()).toBe(true); const error = result.unwrapErr(); expect(error.code).toBe('VALIDATION_ERROR'); }); it('should throw error when league does not exist', async () => { // Scenario: Non-existent league // Given: A driver exists const driverId = 'd8'; const driver = Driver.create({ id: driverId, iracingId: '8', name: 'Test Driver', country: 'US' }); // And: No league exists with the given ID const nonExistentLeagueId = 'nonexistent'; // When: CreateTeamUseCase.execute() is called with non-existent league ID const result = await createTeamUseCase.execute({ name: 'Test Team', tag: 'TT', description: 'A test team', ownerId: driverId, leagues: [nonExistentLeagueId] }); // Then: Should return error expect(result.isErr()).toBe(true); const error = result.unwrapErr(); expect(error.code).toBe('LEAGUE_NOT_FOUND'); }); it('should throw error when team name already exists', async () => { // Scenario: Duplicate team name // Given: A driver exists const driverId = 'd9'; const driver = Driver.create({ id: driverId, iracingId: '9', name: 'Test Driver', country: 'US' }); // And: A league exists const leagueId = 'l9'; const league = League.create({ id: leagueId, name: 'League 9', description: 'Test League 9', ownerId: 'owner' }); // And: A team with the same name already exists const existingTeam = Team.create({ id: 'existing2', name: 'Duplicate Team', tag: 'DT', description: 'Existing', ownerId: 'other', leagues: [] }); await teamRepository.create(existingTeam); // When: CreateTeamUseCase.execute() is called with duplicate team name const result = await createTeamUseCase.execute({ name: 'Duplicate Team', tag: 'DT2', description: 'A new team', ownerId: driverId, leagues: [leagueId] }); // Then: Should return error expect(result.isErr()).toBe(true); const error = result.unwrapErr(); expect(error.code).toBe('VALIDATION_ERROR'); expect(error.details.message).toContain('already exists'); }); }); describe('CreateTeamUseCase - Business Logic', () => { it('should set the creating driver as team captain', async () => { // Scenario: Driver becomes captain // Given: A driver exists const driverId = 'd10'; const driver = Driver.create({ id: driverId, iracingId: '10', name: 'Captain Driver', country: 'US' }); // And: A league exists const leagueId = 'l10'; const league = League.create({ id: leagueId, name: 'League 10', description: 'Test League 10', ownerId: 'owner' }); // When: CreateTeamUseCase.execute() is called const result = await createTeamUseCase.execute({ name: 'Captain Team', tag: 'CT', description: 'A team with captain', ownerId: driverId, leagues: [leagueId] }); // Then: The creating driver should be set as team captain expect(result.isOk()).toBe(true); const { team } = result.unwrap(); // And: The captain role should be recorded in the team roster const membership = await membershipRepository.getMembership(team.id.toString(), driverId); expect(membership).toBeDefined(); expect(membership?.role).toBe('owner'); }); it('should generate unique team ID', async () => { // Scenario: Unique team ID generation // Given: A driver exists const driverId = 'd11'; const driver = Driver.create({ id: driverId, iracingId: '11', name: 'Unique Driver', country: 'US' }); // And: A league exists const leagueId = 'l11'; const league = League.create({ id: leagueId, name: 'League 11', description: 'Test League 11', ownerId: 'owner' }); // When: CreateTeamUseCase.execute() is called const result = await createTeamUseCase.execute({ name: 'Unique Team', tag: 'UT', description: 'A unique team', ownerId: driverId, leagues: [leagueId] }); // Then: The team should have a unique ID expect(result.isOk()).toBe(true); const { team } = result.unwrap(); expect(team.id.toString()).toBeDefined(); expect(team.id.toString().length).toBeGreaterThan(0); // And: The ID should not conflict with existing teams const existingTeam = await teamRepository.findById(team.id.toString()); expect(existingTeam).toBeDefined(); expect(existingTeam?.id.toString()).toBe(team.id.toString()); }); it('should set creation timestamp', async () => { // Scenario: Creation timestamp // Given: A driver exists const driverId = 'd12'; const driver = Driver.create({ id: driverId, iracingId: '12', name: 'Timestamp Driver', country: 'US' }); // And: A league exists const leagueId = 'l12'; const league = League.create({ id: leagueId, name: 'League 12', description: 'Test League 12', ownerId: 'owner' }); // When: CreateTeamUseCase.execute() is called const beforeCreate = new Date(); const result = await createTeamUseCase.execute({ name: 'Timestamp Team', tag: 'TT', description: 'A team with timestamp', ownerId: driverId, leagues: [leagueId] }); const afterCreate = new Date(); // Then: The team should have a creation timestamp expect(result.isOk()).toBe(true); const { team } = result.unwrap(); expect(team.createdAt).toBeDefined(); // And: The timestamp should be current or recent const createdAt = team.createdAt.toDate(); expect(createdAt.getTime()).toBeGreaterThanOrEqual(beforeCreate.getTime()); expect(createdAt.getTime()).toBeLessThanOrEqual(afterCreate.getTime()); }); }); });