integration tests
This commit is contained in:
@@ -2,663 +2,200 @@
|
||||
* Integration Test: Team Admin Use Case Orchestration
|
||||
*
|
||||
* Tests the orchestration logic of team admin-related Use Cases:
|
||||
* - RemoveTeamMemberUseCase: Admin removes team member
|
||||
* - PromoteTeamMemberUseCase: Admin promotes team member to captain
|
||||
* - UpdateTeamDetailsUseCase: Admin updates team details
|
||||
* - DeleteTeamUseCase: Admin deletes team
|
||||
* - Validates that Use Cases correctly interact with their Ports (Repositories, Event Publishers, File Storage)
|
||||
* - UpdateTeamUseCase: Admin updates team details
|
||||
* - 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 { InMemoryTeamRepository } from '../../../adapters/teams/persistence/inmemory/InMemoryTeamRepository';
|
||||
import { InMemoryDriverRepository } from '../../../adapters/drivers/persistence/inmemory/InMemoryDriverRepository';
|
||||
import { InMemoryLeagueRepository } from '../../../adapters/leagues/persistence/inmemory/InMemoryLeagueRepository';
|
||||
import { InMemoryEventPublisher } from '../../../adapters/events/InMemoryEventPublisher';
|
||||
import { InMemoryFileStorage } from '../../../adapters/files/InMemoryFileStorage';
|
||||
import { RemoveTeamMemberUseCase } from '../../../core/teams/use-cases/RemoveTeamMemberUseCase';
|
||||
import { PromoteTeamMemberUseCase } from '../../../core/teams/use-cases/PromoteTeamMemberUseCase';
|
||||
import { UpdateTeamDetailsUseCase } from '../../../core/teams/use-cases/UpdateTeamDetailsUseCase';
|
||||
import { DeleteTeamUseCase } from '../../../core/teams/use-cases/DeleteTeamUseCase';
|
||||
import { RemoveTeamMemberCommand } from '../../../core/teams/ports/RemoveTeamMemberCommand';
|
||||
import { PromoteTeamMemberCommand } from '../../../core/teams/ports/PromoteTeamMemberCommand';
|
||||
import { UpdateTeamDetailsCommand } from '../../../core/teams/ports/UpdateTeamDetailsCommand';
|
||||
import { DeleteTeamCommand } from '../../../core/teams/ports/DeleteTeamCommand';
|
||||
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 { UpdateTeamUseCase } from '../../../core/racing/application/use-cases/UpdateTeamUseCase';
|
||||
import { Team } from '../../../core/racing/domain/entities/Team';
|
||||
import { Logger } from '../../../core/shared/domain/Logger';
|
||||
|
||||
describe('Team Admin Use Case Orchestration', () => {
|
||||
let teamRepository: InMemoryTeamRepository;
|
||||
let driverRepository: InMemoryDriverRepository;
|
||||
let leagueRepository: InMemoryLeagueRepository;
|
||||
let eventPublisher: InMemoryEventPublisher;
|
||||
let fileStorage: InMemoryFileStorage;
|
||||
let removeTeamMemberUseCase: RemoveTeamMemberUseCase;
|
||||
let promoteTeamMemberUseCase: PromoteTeamMemberUseCase;
|
||||
let updateTeamDetailsUseCase: UpdateTeamDetailsUseCase;
|
||||
let deleteTeamUseCase: DeleteTeamUseCase;
|
||||
let membershipRepository: InMemoryTeamMembershipRepository;
|
||||
let updateTeamUseCase: UpdateTeamUseCase;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeAll(() => {
|
||||
// TODO: Initialize In-Memory repositories, event publisher, and file storage
|
||||
// teamRepository = new InMemoryTeamRepository();
|
||||
// driverRepository = new InMemoryDriverRepository();
|
||||
// leagueRepository = new InMemoryLeagueRepository();
|
||||
// eventPublisher = new InMemoryEventPublisher();
|
||||
// fileStorage = new InMemoryFileStorage();
|
||||
// removeTeamMemberUseCase = new RemoveTeamMemberUseCase({
|
||||
// teamRepository,
|
||||
// driverRepository,
|
||||
// eventPublisher,
|
||||
// });
|
||||
// promoteTeamMemberUseCase = new PromoteTeamMemberUseCase({
|
||||
// teamRepository,
|
||||
// driverRepository,
|
||||
// eventPublisher,
|
||||
// });
|
||||
// updateTeamDetailsUseCase = new UpdateTeamDetailsUseCase({
|
||||
// teamRepository,
|
||||
// driverRepository,
|
||||
// leagueRepository,
|
||||
// eventPublisher,
|
||||
// fileStorage,
|
||||
// });
|
||||
// deleteTeamUseCase = new DeleteTeamUseCase({
|
||||
// teamRepository,
|
||||
// driverRepository,
|
||||
// eventPublisher,
|
||||
// });
|
||||
mockLogger = {
|
||||
info: () => {},
|
||||
debug: () => {},
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
} as unknown as Logger;
|
||||
|
||||
teamRepository = new InMemoryTeamRepository(mockLogger);
|
||||
membershipRepository = new InMemoryTeamMembershipRepository(mockLogger);
|
||||
updateTeamUseCase = new UpdateTeamUseCase(teamRepository, membershipRepository);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// TODO: Clear all In-Memory repositories before each test
|
||||
// teamRepository.clear();
|
||||
// driverRepository.clear();
|
||||
// leagueRepository.clear();
|
||||
// eventPublisher.clear();
|
||||
// fileStorage.clear();
|
||||
teamRepository.clear();
|
||||
membershipRepository.clear();
|
||||
});
|
||||
|
||||
describe('RemoveTeamMemberUseCase - Success Path', () => {
|
||||
it('should remove a team member', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Admin removes team member
|
||||
// Given: A team captain exists
|
||||
// And: A team exists with multiple members
|
||||
// And: A driver is a member of the team
|
||||
// When: RemoveTeamMemberUseCase.execute() is called
|
||||
// Then: The driver should be removed from the team roster
|
||||
// And: EventPublisher should emit TeamMemberRemovedEvent
|
||||
describe('UpdateTeamUseCase - Success Path', () => {
|
||||
it('should update team details when called by owner', async () => {
|
||||
// Scenario: Owner updates team details
|
||||
// Given: A team exists
|
||||
const teamId = 't1';
|
||||
const ownerId = 'o1';
|
||||
const team = Team.create({ id: teamId, name: 'Old Name', tag: 'OLD', description: 'Old Desc', ownerId, leagues: [] });
|
||||
await teamRepository.create(team);
|
||||
|
||||
// And: The driver is the owner
|
||||
await membershipRepository.saveMembership({
|
||||
teamId,
|
||||
driverId: ownerId,
|
||||
role: 'owner',
|
||||
status: 'active',
|
||||
joinedAt: new Date()
|
||||
});
|
||||
|
||||
// When: UpdateTeamUseCase.execute() is called
|
||||
const result = await updateTeamUseCase.execute({
|
||||
teamId,
|
||||
updatedBy: ownerId,
|
||||
updates: {
|
||||
name: 'New Name',
|
||||
tag: 'NEW',
|
||||
description: 'New Desc'
|
||||
}
|
||||
});
|
||||
|
||||
// Then: The team should be updated successfully
|
||||
expect(result.isOk()).toBe(true);
|
||||
const { team: updatedTeam } = result.unwrap();
|
||||
expect(updatedTeam.name.toString()).toBe('New Name');
|
||||
expect(updatedTeam.tag.toString()).toBe('NEW');
|
||||
expect(updatedTeam.description.toString()).toBe('New Desc');
|
||||
|
||||
// And: The changes should be in the repository
|
||||
const savedTeam = await teamRepository.findById(teamId);
|
||||
expect(savedTeam?.name.toString()).toBe('New Name');
|
||||
});
|
||||
|
||||
it('should remove a team member with removal reason', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Admin removes team member with reason
|
||||
// Given: A team captain exists
|
||||
// And: A team exists with multiple members
|
||||
// And: A driver is a member of the team
|
||||
// When: RemoveTeamMemberUseCase.execute() is called with removal reason
|
||||
// Then: The driver should be removed from the team roster
|
||||
// And: EventPublisher should emit TeamMemberRemovedEvent
|
||||
});
|
||||
it('should update team details when called by manager', async () => {
|
||||
// Scenario: Manager updates team details
|
||||
// Given: A team exists
|
||||
const teamId = 't2';
|
||||
const managerId = 'm2';
|
||||
const team = Team.create({ id: teamId, name: 'Team 2', tag: 'T2', description: 'Desc', ownerId: 'owner', leagues: [] });
|
||||
await teamRepository.create(team);
|
||||
|
||||
// And: The driver is a manager
|
||||
await membershipRepository.saveMembership({
|
||||
teamId,
|
||||
driverId: managerId,
|
||||
role: 'manager',
|
||||
status: 'active',
|
||||
joinedAt: new Date()
|
||||
});
|
||||
|
||||
it('should remove a team member when team has minimum members', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team has minimum members
|
||||
// Given: A team captain exists
|
||||
// And: A team exists with minimum members (e.g., 2 members)
|
||||
// And: A driver is a member of the team
|
||||
// When: RemoveTeamMemberUseCase.execute() is called
|
||||
// Then: The driver should be removed from the team roster
|
||||
// And: EventPublisher should emit TeamMemberRemovedEvent
|
||||
// When: UpdateTeamUseCase.execute() is called
|
||||
const result = await updateTeamUseCase.execute({
|
||||
teamId,
|
||||
updatedBy: managerId,
|
||||
updates: {
|
||||
name: 'Updated by Manager'
|
||||
}
|
||||
});
|
||||
|
||||
// Then: The team should be updated successfully
|
||||
expect(result.isOk()).toBe(true);
|
||||
const { team: updatedTeam } = result.unwrap();
|
||||
expect(updatedTeam.name.toString()).toBe('Updated by Manager');
|
||||
});
|
||||
});
|
||||
|
||||
describe('RemoveTeamMemberUseCase - Validation', () => {
|
||||
it('should reject removal when removing the captain', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Attempt to remove captain
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// When: RemoveTeamMemberUseCase.execute() is called with captain ID
|
||||
// Then: Should throw CannotRemoveCaptainError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
describe('UpdateTeamUseCase - Validation', () => {
|
||||
it('should reject update when called by regular member', async () => {
|
||||
// Scenario: Regular member tries to update team
|
||||
// Given: A team exists
|
||||
const teamId = 't3';
|
||||
const memberId = 'd3';
|
||||
const team = Team.create({ id: teamId, name: 'Team 3', tag: 'T3', description: 'Desc', ownerId: 'owner', leagues: [] });
|
||||
await teamRepository.create(team);
|
||||
|
||||
// And: The driver is a regular member
|
||||
await membershipRepository.saveMembership({
|
||||
teamId,
|
||||
driverId: memberId,
|
||||
role: 'driver',
|
||||
status: 'active',
|
||||
joinedAt: new Date()
|
||||
});
|
||||
|
||||
// When: UpdateTeamUseCase.execute() is called
|
||||
const result = await updateTeamUseCase.execute({
|
||||
teamId,
|
||||
updatedBy: memberId,
|
||||
updates: {
|
||||
name: 'Unauthorized Update'
|
||||
}
|
||||
});
|
||||
|
||||
// Then: Should return error
|
||||
expect(result.isErr()).toBe(true);
|
||||
const error = result.unwrapErr();
|
||||
expect(error.code).toBe('PERMISSION_DENIED');
|
||||
});
|
||||
|
||||
it('should reject removal when member does not exist', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Non-existent team member
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// And: A driver is not a member of the team
|
||||
// When: RemoveTeamMemberUseCase.execute() is called
|
||||
// Then: Should throw TeamMemberNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
it('should reject update when called by non-member', async () => {
|
||||
// Scenario: Non-member tries to update team
|
||||
// Given: A team exists
|
||||
const teamId = 't4';
|
||||
const team = Team.create({ id: teamId, name: 'Team 4', tag: 'T4', description: 'Desc', ownerId: 'owner', leagues: [] });
|
||||
await teamRepository.create(team);
|
||||
|
||||
// When: UpdateTeamUseCase.execute() is called
|
||||
const result = await updateTeamUseCase.execute({
|
||||
teamId,
|
||||
updatedBy: 'non-member',
|
||||
updates: {
|
||||
name: 'Unauthorized Update'
|
||||
}
|
||||
});
|
||||
|
||||
it('should reject removal with invalid reason length', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Invalid reason length
|
||||
// Given: A team captain exists
|
||||
// And: A team exists with multiple members
|
||||
// And: A driver is a member of the team
|
||||
// When: RemoveTeamMemberUseCase.execute() is called with reason exceeding limit
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
// Then: Should return error
|
||||
expect(result.isErr()).toBe(true);
|
||||
const error = result.unwrapErr();
|
||||
expect(error.code).toBe('PERMISSION_DENIED');
|
||||
});
|
||||
});
|
||||
|
||||
describe('RemoveTeamMemberUseCase - Error Handling', () => {
|
||||
it('should throw error when team captain does not exist', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Non-existent team captain
|
||||
// Given: No team captain exists with the given ID
|
||||
// When: RemoveTeamMemberUseCase.execute() is called with non-existent captain ID
|
||||
// Then: Should throw DriverNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
describe('UpdateTeamUseCase - Error Handling', () => {
|
||||
it('should throw error when team does not exist', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Non-existent team
|
||||
// Given: A team captain exists
|
||||
// And: No team exists with the given ID
|
||||
// When: RemoveTeamMemberUseCase.execute() is called with non-existent team ID
|
||||
// Then: Should throw TeamNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
// Given: A driver exists who is a manager of some team
|
||||
const managerId = 'm5';
|
||||
await membershipRepository.saveMembership({
|
||||
teamId: 'some-team',
|
||||
driverId: managerId,
|
||||
role: 'manager',
|
||||
status: 'active',
|
||||
joinedAt: new Date()
|
||||
});
|
||||
|
||||
it('should handle repository errors gracefully', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Repository throws error
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// And: TeamRepository throws an error during update
|
||||
// When: RemoveTeamMemberUseCase.execute() is called
|
||||
// Then: Should propagate the error appropriately
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
// When: UpdateTeamUseCase.execute() is called with non-existent team ID
|
||||
const result = await updateTeamUseCase.execute({
|
||||
teamId: 'nonexistent',
|
||||
updatedBy: managerId,
|
||||
updates: {
|
||||
name: 'New Name'
|
||||
}
|
||||
});
|
||||
|
||||
describe('PromoteTeamMemberUseCase - Success Path', () => {
|
||||
it('should promote a team member to captain', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Admin promotes member to captain
|
||||
// Given: A team captain exists
|
||||
// And: A team exists with multiple members
|
||||
// And: A driver is a member of the team
|
||||
// When: PromoteTeamMemberUseCase.execute() is called
|
||||
// Then: The driver should become the new captain
|
||||
// And: The previous captain should be demoted to admin
|
||||
// And: EventPublisher should emit TeamMemberPromotedEvent
|
||||
// And: EventPublisher should emit TeamCaptainChangedEvent
|
||||
});
|
||||
|
||||
it('should promote a team member with promotion reason', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Admin promotes member with reason
|
||||
// Given: A team captain exists
|
||||
// And: A team exists with multiple members
|
||||
// And: A driver is a member of the team
|
||||
// When: PromoteTeamMemberUseCase.execute() is called with promotion reason
|
||||
// Then: The driver should become the new captain
|
||||
// And: EventPublisher should emit TeamMemberPromotedEvent
|
||||
});
|
||||
|
||||
it('should promote a team member when team has minimum members', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team has minimum members
|
||||
// Given: A team captain exists
|
||||
// And: A team exists with minimum members (e.g., 2 members)
|
||||
// And: A driver is a member of the team
|
||||
// When: PromoteTeamMemberUseCase.execute() is called
|
||||
// Then: The driver should become the new captain
|
||||
// And: EventPublisher should emit TeamMemberPromotedEvent
|
||||
});
|
||||
});
|
||||
|
||||
describe('PromoteTeamMemberUseCase - Validation', () => {
|
||||
it('should reject promotion when member does not exist', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Non-existent team member
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// And: A driver is not a member of the team
|
||||
// When: PromoteTeamMemberUseCase.execute() is called
|
||||
// Then: Should throw TeamMemberNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should reject promotion with invalid reason length', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Invalid reason length
|
||||
// Given: A team captain exists
|
||||
// And: A team exists with multiple members
|
||||
// And: A driver is a member of the team
|
||||
// When: PromoteTeamMemberUseCase.execute() is called with reason exceeding limit
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
|
||||
describe('PromoteTeamMemberUseCase - Error Handling', () => {
|
||||
it('should throw error when team captain does not exist', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Non-existent team captain
|
||||
// Given: No team captain exists with the given ID
|
||||
// When: PromoteTeamMemberUseCase.execute() is called with non-existent captain ID
|
||||
// Then: Should throw DriverNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should throw error when team does not exist', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Non-existent team
|
||||
// Given: A team captain exists
|
||||
// And: No team exists with the given ID
|
||||
// When: PromoteTeamMemberUseCase.execute() is called with non-existent team ID
|
||||
// Then: Should throw TeamNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should handle repository errors gracefully', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Repository throws error
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// And: TeamRepository throws an error during update
|
||||
// When: PromoteTeamMemberUseCase.execute() is called
|
||||
// Then: Should propagate the error appropriately
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
|
||||
describe('UpdateTeamDetailsUseCase - Success Path', () => {
|
||||
it('should update team details', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Admin updates team details
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called
|
||||
// Then: The team details should be updated
|
||||
// And: EventPublisher should emit TeamDetailsUpdatedEvent
|
||||
});
|
||||
|
||||
it('should update team details with logo', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Admin updates team logo
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// And: A logo file is provided
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called with logo
|
||||
// Then: The logo should be stored in file storage
|
||||
// And: The team should reference the new logo URL
|
||||
// And: EventPublisher should emit TeamDetailsUpdatedEvent
|
||||
});
|
||||
|
||||
it('should update team details with description', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Admin updates team description
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called with description
|
||||
// Then: The team description should be updated
|
||||
// And: EventPublisher should emit TeamDetailsUpdatedEvent
|
||||
});
|
||||
|
||||
it('should update team details with roster size', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Admin updates roster size
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called with roster size
|
||||
// Then: The team roster size should be updated
|
||||
// And: EventPublisher should emit TeamDetailsUpdatedEvent
|
||||
});
|
||||
|
||||
it('should update team details with social links', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Admin updates social links
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called with social links
|
||||
// Then: The team social links should be updated
|
||||
// And: EventPublisher should emit TeamDetailsUpdatedEvent
|
||||
});
|
||||
});
|
||||
|
||||
describe('UpdateTeamDetailsUseCase - Validation', () => {
|
||||
it('should reject update with empty team name', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Update with empty name
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called with empty team name
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should reject update with invalid team name format', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Update with invalid name format
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called with invalid team name
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should reject update with team name exceeding character limit', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Update with name exceeding limit
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called with name exceeding limit
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should reject update with description exceeding character limit', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Update with description exceeding limit
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called with description exceeding limit
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should reject update with invalid roster size', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Update with invalid roster size
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called with invalid roster size
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should reject update with invalid logo format', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Update with invalid logo format
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called with invalid logo format
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should reject update with oversized logo', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Update with oversized logo
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called with oversized logo
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should reject update when team name already exists', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Duplicate team name
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// And: Another team with the same name already exists
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called with duplicate team name
|
||||
// Then: Should throw TeamNameAlreadyExistsError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should reject update with roster size exceeding league limits', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Roster size exceeds league limit
|
||||
// Given: A team captain exists
|
||||
// And: A team exists in a league with max roster size of 10
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called with roster size 15
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
|
||||
describe('UpdateTeamDetailsUseCase - Error Handling', () => {
|
||||
it('should throw error when team captain does not exist', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Non-existent team captain
|
||||
// Given: No team captain exists with the given ID
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called with non-existent captain ID
|
||||
// Then: Should throw DriverNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should throw error when team does not exist', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Non-existent team
|
||||
// Given: A team captain exists
|
||||
// And: No team exists with the given ID
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called with non-existent team ID
|
||||
// Then: Should throw TeamNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should throw error when league does not exist', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Non-existent league
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// And: No league exists with the given ID
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called with non-existent league ID
|
||||
// Then: Should throw LeagueNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should handle repository errors gracefully', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Repository throws error
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// And: TeamRepository throws an error during update
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called
|
||||
// Then: Should propagate the error appropriately
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should handle file storage errors gracefully', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: File storage throws error
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// And: FileStorage throws an error during upload
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called with logo
|
||||
// Then: Should propagate the error appropriately
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
|
||||
describe('DeleteTeamUseCase - Success Path', () => {
|
||||
it('should delete a team', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Admin deletes team
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// When: DeleteTeamUseCase.execute() is called
|
||||
// Then: The team should be deleted from the repository
|
||||
// And: EventPublisher should emit TeamDeletedEvent
|
||||
});
|
||||
|
||||
it('should delete a team with deletion reason', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Admin deletes team with reason
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// When: DeleteTeamUseCase.execute() is called with deletion reason
|
||||
// Then: The team should be deleted
|
||||
// And: EventPublisher should emit TeamDeletedEvent
|
||||
});
|
||||
|
||||
it('should delete a team with members', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Delete team with members
|
||||
// Given: A team captain exists
|
||||
// And: A team exists with multiple members
|
||||
// When: DeleteTeamUseCase.execute() is called
|
||||
// Then: The team should be deleted
|
||||
// And: All team members should be removed from the team
|
||||
// And: EventPublisher should emit TeamDeletedEvent
|
||||
});
|
||||
});
|
||||
|
||||
describe('DeleteTeamUseCase - Validation', () => {
|
||||
it('should reject deletion with invalid reason length', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Invalid reason length
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// When: DeleteTeamUseCase.execute() is called with reason exceeding limit
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
|
||||
describe('DeleteTeamUseCase - Error Handling', () => {
|
||||
it('should throw error when team captain does not exist', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Non-existent team captain
|
||||
// Given: No team captain exists with the given ID
|
||||
// When: DeleteTeamUseCase.execute() is called with non-existent captain ID
|
||||
// Then: Should throw DriverNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should throw error when team does not exist', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Non-existent team
|
||||
// Given: A team captain exists
|
||||
// And: No team exists with the given ID
|
||||
// When: DeleteTeamUseCase.execute() is called with non-existent team ID
|
||||
// Then: Should throw TeamNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should handle repository errors gracefully', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Repository throws error
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// And: TeamRepository throws an error during delete
|
||||
// When: DeleteTeamUseCase.execute() is called
|
||||
// Then: Should propagate the error appropriately
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
|
||||
describe('Team Admin Data Orchestration', () => {
|
||||
it('should correctly track team roster after member removal', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Roster tracking after removal
|
||||
// Given: A team captain exists
|
||||
// And: A team exists with multiple members
|
||||
// When: RemoveTeamMemberUseCase.execute() is called
|
||||
// Then: The team roster should be updated
|
||||
// And: The removed member should not be in the roster
|
||||
});
|
||||
|
||||
it('should correctly track team captain after promotion', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Captain tracking after promotion
|
||||
// Given: A team captain exists
|
||||
// And: A team exists with multiple members
|
||||
// When: PromoteTeamMemberUseCase.execute() is called
|
||||
// Then: The promoted member should be the new captain
|
||||
// And: The previous captain should be demoted to admin
|
||||
});
|
||||
|
||||
it('should correctly update team details', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team details update
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called
|
||||
// Then: The team details should be updated in the repository
|
||||
// And: The updated details should be reflected in the team
|
||||
});
|
||||
|
||||
it('should correctly delete team and all related data', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team deletion
|
||||
// Given: A team captain exists
|
||||
// And: A team exists with members and data
|
||||
// When: DeleteTeamUseCase.execute() is called
|
||||
// Then: The team should be deleted from the repository
|
||||
// And: All team-related data should be removed
|
||||
});
|
||||
|
||||
it('should validate roster size against league limits on update', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Roster size validation on update
|
||||
// Given: A team captain exists
|
||||
// And: A team exists in a league with max roster size of 10
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called with roster size 15
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
|
||||
describe('Team Admin Event Orchestration', () => {
|
||||
it('should emit TeamMemberRemovedEvent with correct payload', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Event emission on member removal
|
||||
// Given: A team captain exists
|
||||
// And: A team exists with multiple members
|
||||
// When: RemoveTeamMemberUseCase.execute() is called
|
||||
// Then: EventPublisher should emit TeamMemberRemovedEvent
|
||||
// And: The event should contain team ID, removed member ID, and captain ID
|
||||
});
|
||||
|
||||
it('should emit TeamMemberPromotedEvent with correct payload', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Event emission on member promotion
|
||||
// Given: A team captain exists
|
||||
// And: A team exists with multiple members
|
||||
// When: PromoteTeamMemberUseCase.execute() is called
|
||||
// Then: EventPublisher should emit TeamMemberPromotedEvent
|
||||
// And: The event should contain team ID, promoted member ID, and captain ID
|
||||
});
|
||||
|
||||
it('should emit TeamCaptainChangedEvent with correct payload', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Event emission on captain change
|
||||
// Given: A team captain exists
|
||||
// And: A team exists with multiple members
|
||||
// When: PromoteTeamMemberUseCase.execute() is called
|
||||
// Then: EventPublisher should emit TeamCaptainChangedEvent
|
||||
// And: The event should contain team ID, new captain ID, and old captain ID
|
||||
});
|
||||
|
||||
it('should emit TeamDetailsUpdatedEvent with correct payload', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Event emission on team details update
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// When: UpdateTeamDetailsUseCase.execute() is called
|
||||
// Then: EventPublisher should emit TeamDetailsUpdatedEvent
|
||||
// And: The event should contain team ID and updated fields
|
||||
});
|
||||
|
||||
it('should emit TeamDeletedEvent with correct payload', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Event emission on team deletion
|
||||
// Given: A team captain exists
|
||||
// And: A team exists
|
||||
// When: DeleteTeamUseCase.execute() is called
|
||||
// Then: EventPublisher should emit TeamDeletedEvent
|
||||
// And: The event should contain team ID and captain ID
|
||||
});
|
||||
|
||||
it('should not emit events on validation failure', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: No events on validation failure
|
||||
// Given: Invalid parameters
|
||||
// When: Any use case is called with invalid data
|
||||
// Then: EventPublisher should NOT emit any events
|
||||
// Then: Should return error
|
||||
expect(result.isErr()).toBe(true);
|
||||
const error = result.unwrapErr();
|
||||
expect(error.code).toBe('PERMISSION_DENIED'); // Because membership check fails first
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,344 +1,403 @@
|
||||
/**
|
||||
* 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, logo, league, tier, and roster size
|
||||
* - Validates that Use Cases correctly interact with their Ports (Repositories, Event Publishers, File Storage)
|
||||
* - 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, afterAll, beforeEach } from 'vitest';
|
||||
import { InMemoryTeamRepository } from '../../../adapters/teams/persistence/inmemory/InMemoryTeamRepository';
|
||||
import { InMemoryDriverRepository } from '../../../adapters/drivers/persistence/inmemory/InMemoryDriverRepository';
|
||||
import { InMemoryLeagueRepository } from '../../../adapters/leagues/persistence/inmemory/InMemoryLeagueRepository';
|
||||
import { InMemoryEventPublisher } from '../../../adapters/events/InMemoryEventPublisher';
|
||||
import { InMemoryFileStorage } from '../../../adapters/files/InMemoryFileStorage';
|
||||
import { CreateTeamUseCase } from '../../../core/teams/use-cases/CreateTeamUseCase';
|
||||
import { CreateTeamCommand } from '../../../core/teams/ports/CreateTeamCommand';
|
||||
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 driverRepository: InMemoryDriverRepository;
|
||||
let leagueRepository: InMemoryLeagueRepository;
|
||||
let eventPublisher: InMemoryEventPublisher;
|
||||
let fileStorage: InMemoryFileStorage;
|
||||
let membershipRepository: InMemoryTeamMembershipRepository;
|
||||
let createTeamUseCase: CreateTeamUseCase;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeAll(() => {
|
||||
// TODO: Initialize In-Memory repositories, event publisher, and file storage
|
||||
// teamRepository = new InMemoryTeamRepository();
|
||||
// driverRepository = new InMemoryDriverRepository();
|
||||
// leagueRepository = new InMemoryLeagueRepository();
|
||||
// eventPublisher = new InMemoryEventPublisher();
|
||||
// fileStorage = new InMemoryFileStorage();
|
||||
// createTeamUseCase = new CreateTeamUseCase({
|
||||
// teamRepository,
|
||||
// driverRepository,
|
||||
// leagueRepository,
|
||||
// eventPublisher,
|
||||
// fileStorage,
|
||||
// });
|
||||
mockLogger = {
|
||||
info: () => {},
|
||||
debug: () => {},
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
} as unknown as Logger;
|
||||
|
||||
teamRepository = new InMemoryTeamRepository(mockLogger);
|
||||
membershipRepository = new InMemoryTeamMembershipRepository(mockLogger);
|
||||
createTeamUseCase = new CreateTeamUseCase(teamRepository, membershipRepository, mockLogger);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// TODO: Clear all In-Memory repositories before each test
|
||||
// teamRepository.clear();
|
||||
// driverRepository.clear();
|
||||
// leagueRepository.clear();
|
||||
// eventPublisher.clear();
|
||||
// fileStorage.clear();
|
||||
teamRepository.clear();
|
||||
membershipRepository.clear();
|
||||
});
|
||||
|
||||
describe('CreateTeamUseCase - Success Path', () => {
|
||||
it('should create a team with all required fields', async () => {
|
||||
// TODO: Implement test
|
||||
// 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
|
||||
// And: A tier 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
|
||||
// Then: The team should be created in the repository
|
||||
// And: The team should have the correct name, description, and settings
|
||||
// And: The team should be associated with the correct driver as captain
|
||||
// And: The team should be associated with the correct league
|
||||
// And: EventPublisher should emit TeamCreatedEvent
|
||||
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 () => {
|
||||
// TODO: Implement test
|
||||
// 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
|
||||
// And: EventPublisher should emit TeamCreatedEvent
|
||||
});
|
||||
|
||||
it('should create a team with custom roster size', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team creation with custom roster size
|
||||
// Given: A driver exists
|
||||
// And: A league exists
|
||||
// When: CreateTeamUseCase.execute() is called with roster size
|
||||
// Then: The team should be created with the specified roster size
|
||||
// And: EventPublisher should emit TeamCreatedEvent
|
||||
});
|
||||
|
||||
it('should create a team with logo upload', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team creation with logo
|
||||
// Given: A driver exists
|
||||
// And: A league exists
|
||||
// And: A logo file is provided
|
||||
// When: CreateTeamUseCase.execute() is called with logo
|
||||
// Then: The logo should be stored in file storage
|
||||
// And: The team should reference the logo URL
|
||||
// And: EventPublisher should emit TeamCreatedEvent
|
||||
});
|
||||
|
||||
it('should create a team with initial member invitations', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team creation with invitations
|
||||
// Given: A driver exists
|
||||
// And: A league exists
|
||||
// And: Other drivers exist to invite
|
||||
// When: CreateTeamUseCase.execute() is called with invitations
|
||||
// Then: The team should be created
|
||||
// And: Invitation records should be created for each invited driver
|
||||
// And: EventPublisher should emit TeamCreatedEvent
|
||||
// And: EventPublisher should emit TeamInvitationCreatedEvent for each invitation
|
||||
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 () => {
|
||||
// TODO: Implement test
|
||||
// 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
|
||||
// Then: The team should be created with default values for optional fields
|
||||
// And: EventPublisher should emit TeamCreatedEvent
|
||||
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 () => {
|
||||
// TODO: Implement test
|
||||
// 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
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
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 () => {
|
||||
// TODO: Implement test
|
||||
// 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
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
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 with team name exceeding character limit', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team creation with name exceeding limit
|
||||
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
|
||||
// When: CreateTeamUseCase.execute() is called with name exceeding limit
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
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]
|
||||
});
|
||||
|
||||
it('should reject team creation with description exceeding character limit', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team creation with description exceeding limit
|
||||
// Given: A driver exists
|
||||
// And: A league exists
|
||||
// When: CreateTeamUseCase.execute() is called with description exceeding limit
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should reject team creation with invalid roster size', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team creation with invalid roster size
|
||||
// Given: A driver exists
|
||||
// And: A league exists
|
||||
// When: CreateTeamUseCase.execute() is called with invalid roster size
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should reject team creation with invalid logo format', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team creation with invalid logo format
|
||||
// Given: A driver exists
|
||||
// And: A league exists
|
||||
// When: CreateTeamUseCase.execute() is called with invalid logo format
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should reject team creation with oversized logo', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team creation with oversized logo
|
||||
// Given: A driver exists
|
||||
// And: A league exists
|
||||
// When: CreateTeamUseCase.execute() is called with oversized logo
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
// 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 () => {
|
||||
// TODO: Implement test
|
||||
// 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
|
||||
// Then: Should throw DriverNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
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 () => {
|
||||
// TODO: Implement test
|
||||
// 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
|
||||
// Then: Should throw LeagueNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
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 () => {
|
||||
// TODO: Implement test
|
||||
// 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
|
||||
// Then: Should throw TeamNameAlreadyExistsError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
const result = await createTeamUseCase.execute({
|
||||
name: 'Duplicate Team',
|
||||
tag: 'DT2',
|
||||
description: 'A new team',
|
||||
ownerId: driverId,
|
||||
leagues: [leagueId]
|
||||
});
|
||||
|
||||
it('should throw error when driver is already captain of another team', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Driver already captain
|
||||
// Given: A driver exists
|
||||
// And: The driver is already captain of another team
|
||||
// When: CreateTeamUseCase.execute() is called
|
||||
// Then: Should throw DriverAlreadyCaptainError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should handle repository errors gracefully', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Repository throws error
|
||||
// Given: A driver exists
|
||||
// And: A league exists
|
||||
// And: TeamRepository throws an error during save
|
||||
// When: CreateTeamUseCase.execute() is called
|
||||
// Then: Should propagate the error appropriately
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should handle file storage errors gracefully', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: File storage throws error
|
||||
// Given: A driver exists
|
||||
// And: A league exists
|
||||
// And: FileStorage throws an error during upload
|
||||
// When: CreateTeamUseCase.execute() is called with logo
|
||||
// Then: Should propagate the error appropriately
|
||||
// And: EventPublisher should NOT emit any events
|
||||
// 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 () => {
|
||||
// TODO: Implement test
|
||||
// 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
|
||||
});
|
||||
|
||||
it('should validate roster size against league limits', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Roster size validation
|
||||
// Given: A driver exists
|
||||
// And: A league exists with max roster size of 10
|
||||
// When: CreateTeamUseCase.execute() is called with roster size 15
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should assign default tier if not specified', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Default tier assignment
|
||||
// Given: A driver exists
|
||||
// And: A league exists
|
||||
// When: CreateTeamUseCase.execute() is called without tier
|
||||
// Then: The team should be assigned a default tier
|
||||
// And: EventPublisher should emit TeamCreatedEvent
|
||||
const membership = await membershipRepository.getMembership(team.id.toString(), driverId);
|
||||
expect(membership).toBeDefined();
|
||||
expect(membership?.role).toBe('owner');
|
||||
});
|
||||
|
||||
it('should generate unique team ID', async () => {
|
||||
// TODO: Implement test
|
||||
// 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 () => {
|
||||
// TODO: Implement test
|
||||
// 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
|
||||
});
|
||||
});
|
||||
|
||||
describe('CreateTeamUseCase - Event Orchestration', () => {
|
||||
it('should emit TeamCreatedEvent with correct payload', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Event emission
|
||||
// Given: A driver exists
|
||||
// And: A league exists
|
||||
// When: CreateTeamUseCase.execute() is called
|
||||
// Then: EventPublisher should emit TeamCreatedEvent
|
||||
// And: The event should contain team ID, name, captain ID, and league ID
|
||||
});
|
||||
|
||||
it('should emit TeamInvitationCreatedEvent for each invitation', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Invitation events
|
||||
// Given: A driver exists
|
||||
// And: A league exists
|
||||
// And: Other drivers exist to invite
|
||||
// When: CreateTeamUseCase.execute() is called with invitations
|
||||
// Then: EventPublisher should emit TeamInvitationCreatedEvent for each invitation
|
||||
// And: Each event should contain invitation ID, team ID, and invited driver ID
|
||||
});
|
||||
|
||||
it('should not emit events on validation failure', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: No events on validation failure
|
||||
// Given: A driver exists
|
||||
// And: A league exists
|
||||
// When: CreateTeamUseCase.execute() is called with invalid data
|
||||
// Then: EventPublisher should NOT emit any events
|
||||
const createdAt = team.createdAt.toDate();
|
||||
expect(createdAt.getTime()).toBeGreaterThanOrEqual(beforeCreate.getTime());
|
||||
expect(createdAt.getTime()).toBeLessThanOrEqual(afterCreate.getTime());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,346 +2,130 @@
|
||||
* Integration Test: Team Detail Use Case Orchestration
|
||||
*
|
||||
* Tests the orchestration logic of team detail-related Use Cases:
|
||||
* - GetTeamDetailUseCase: Retrieves detailed team information including roster, performance, achievements, and history
|
||||
* - Validates that Use Cases correctly interact with their Ports (Repositories, Event Publishers)
|
||||
* - GetTeamDetailsUseCase: Retrieves detailed team information including roster and management permissions
|
||||
* - 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 { InMemoryTeamRepository } from '../../../adapters/teams/persistence/inmemory/InMemoryTeamRepository';
|
||||
import { InMemoryDriverRepository } from '../../../adapters/drivers/persistence/inmemory/InMemoryDriverRepository';
|
||||
import { InMemoryLeagueRepository } from '../../../adapters/leagues/persistence/inmemory/InMemoryLeagueRepository';
|
||||
import { InMemoryEventPublisher } from '../../../adapters/events/InMemoryEventPublisher';
|
||||
import { GetTeamDetailUseCase } from '../../../core/teams/use-cases/GetTeamDetailUseCase';
|
||||
import { GetTeamDetailQuery } from '../../../core/teams/ports/GetTeamDetailQuery';
|
||||
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 { GetTeamDetailsUseCase } from '../../../core/racing/application/use-cases/GetTeamDetailsUseCase';
|
||||
import { Team } from '../../../core/racing/domain/entities/Team';
|
||||
import { Logger } from '../../../core/shared/domain/Logger';
|
||||
|
||||
describe('Team Detail Use Case Orchestration', () => {
|
||||
let teamRepository: InMemoryTeamRepository;
|
||||
let driverRepository: InMemoryDriverRepository;
|
||||
let leagueRepository: InMemoryLeagueRepository;
|
||||
let eventPublisher: InMemoryEventPublisher;
|
||||
let getTeamDetailUseCase: GetTeamDetailUseCase;
|
||||
let membershipRepository: InMemoryTeamMembershipRepository;
|
||||
let getTeamDetailsUseCase: GetTeamDetailsUseCase;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeAll(() => {
|
||||
// TODO: Initialize In-Memory repositories and event publisher
|
||||
// teamRepository = new InMemoryTeamRepository();
|
||||
// driverRepository = new InMemoryDriverRepository();
|
||||
// leagueRepository = new InMemoryLeagueRepository();
|
||||
// eventPublisher = new InMemoryEventPublisher();
|
||||
// getTeamDetailUseCase = new GetTeamDetailUseCase({
|
||||
// teamRepository,
|
||||
// driverRepository,
|
||||
// leagueRepository,
|
||||
// eventPublisher,
|
||||
// });
|
||||
mockLogger = {
|
||||
info: () => {},
|
||||
debug: () => {},
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
} as unknown as Logger;
|
||||
|
||||
teamRepository = new InMemoryTeamRepository(mockLogger);
|
||||
membershipRepository = new InMemoryTeamMembershipRepository(mockLogger);
|
||||
getTeamDetailsUseCase = new GetTeamDetailsUseCase(teamRepository, membershipRepository);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// TODO: Clear all In-Memory repositories before each test
|
||||
// teamRepository.clear();
|
||||
// driverRepository.clear();
|
||||
// leagueRepository.clear();
|
||||
// eventPublisher.clear();
|
||||
teamRepository.clear();
|
||||
membershipRepository.clear();
|
||||
});
|
||||
|
||||
describe('GetTeamDetailUseCase - Success Path', () => {
|
||||
it('should retrieve complete team detail with all information', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team with complete information
|
||||
// Given: A team exists with multiple members
|
||||
// And: The team has captain, admins, and drivers
|
||||
// And: The team has performance statistics
|
||||
// And: The team has achievements
|
||||
// And: The team has race history
|
||||
// When: GetTeamDetailUseCase.execute() is called with team ID
|
||||
// Then: The result should contain all team information
|
||||
// And: The result should show team name, description, and logo
|
||||
// And: The result should show team roster with roles
|
||||
// And: The result should show team performance statistics
|
||||
// And: The result should show team achievements
|
||||
// And: The result should show team race history
|
||||
// And: EventPublisher should emit TeamDetailAccessedEvent
|
||||
describe('GetTeamDetailsUseCase - Success Path', () => {
|
||||
it('should retrieve team detail with membership and management permissions for owner', async () => {
|
||||
// Scenario: Team owner views team details
|
||||
// Given: A team exists
|
||||
const teamId = 't1';
|
||||
const ownerId = 'd1';
|
||||
const team = Team.create({ id: teamId, name: 'Team 1', tag: 'T1', description: 'Desc', ownerId, leagues: [] });
|
||||
await teamRepository.create(team);
|
||||
|
||||
// And: The driver is the owner
|
||||
await membershipRepository.saveMembership({
|
||||
teamId,
|
||||
driverId: ownerId,
|
||||
role: 'owner',
|
||||
status: 'active',
|
||||
joinedAt: new Date()
|
||||
});
|
||||
|
||||
// When: GetTeamDetailsUseCase.execute() is called
|
||||
const result = await getTeamDetailsUseCase.execute({ teamId, driverId: ownerId });
|
||||
|
||||
// Then: The result should contain team information and management permissions
|
||||
expect(result.isOk()).toBe(true);
|
||||
const data = result.unwrap();
|
||||
expect(data.team.id.toString()).toBe(teamId);
|
||||
expect(data.membership?.role).toBe('owner');
|
||||
expect(data.canManage).toBe(true);
|
||||
});
|
||||
|
||||
it('should retrieve team detail with minimal roster', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team with minimal roster
|
||||
// Given: A team exists with only the captain
|
||||
// When: GetTeamDetailUseCase.execute() is called with team ID
|
||||
// Then: The result should contain team information
|
||||
// And: The roster should show only the captain
|
||||
// And: EventPublisher should emit TeamDetailAccessedEvent
|
||||
it('should retrieve team detail for a non-member', async () => {
|
||||
// Scenario: Non-member views team details
|
||||
// Given: A team exists
|
||||
const teamId = 't2';
|
||||
const team = Team.create({ id: teamId, name: 'Team 2', tag: 'T2', description: 'Desc', ownerId: 'owner', leagues: [] });
|
||||
await teamRepository.create(team);
|
||||
|
||||
// When: GetTeamDetailsUseCase.execute() is called with a driver who is not a member
|
||||
const result = await getTeamDetailsUseCase.execute({ teamId, driverId: 'non-member' });
|
||||
|
||||
// Then: The result should contain team information but no membership and no management permissions
|
||||
expect(result.isOk()).toBe(true);
|
||||
const data = result.unwrap();
|
||||
expect(data.team.id.toString()).toBe(teamId);
|
||||
expect(data.membership).toBeNull();
|
||||
expect(data.canManage).toBe(false);
|
||||
});
|
||||
|
||||
it('should retrieve team detail with pending join requests', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team with pending requests
|
||||
// Given: A team exists with pending join requests
|
||||
// When: GetTeamDetailUseCase.execute() is called with team ID
|
||||
// Then: The result should contain pending requests
|
||||
// And: Each request should display driver name and request date
|
||||
// And: EventPublisher should emit TeamDetailAccessedEvent
|
||||
});
|
||||
it('should retrieve team detail for a regular member', async () => {
|
||||
// Scenario: Regular member views team details
|
||||
// Given: A team exists
|
||||
const teamId = 't3';
|
||||
const memberId = 'd3';
|
||||
const team = Team.create({ id: teamId, name: 'Team 3', tag: 'T3', description: 'Desc', ownerId: 'owner', leagues: [] });
|
||||
await teamRepository.create(team);
|
||||
|
||||
// And: The driver is a regular member
|
||||
await membershipRepository.saveMembership({
|
||||
teamId,
|
||||
driverId: memberId,
|
||||
role: 'driver',
|
||||
status: 'active',
|
||||
joinedAt: new Date()
|
||||
});
|
||||
|
||||
it('should retrieve team detail with team performance statistics', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team with performance statistics
|
||||
// Given: A team exists with performance data
|
||||
// When: GetTeamDetailUseCase.execute() is called with team ID
|
||||
// Then: The result should show win rate
|
||||
// And: The result should show podium finishes
|
||||
// And: The result should show total races
|
||||
// And: The result should show championship points
|
||||
// And: EventPublisher should emit TeamDetailAccessedEvent
|
||||
});
|
||||
// When: GetTeamDetailsUseCase.execute() is called
|
||||
const result = await getTeamDetailsUseCase.execute({ teamId, driverId: memberId });
|
||||
|
||||
it('should retrieve team detail with team achievements', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team with achievements
|
||||
// Given: A team exists with achievements
|
||||
// When: GetTeamDetailUseCase.execute() is called with team ID
|
||||
// Then: The result should show achievement badges
|
||||
// And: The result should show achievement names
|
||||
// And: The result should show achievement dates
|
||||
// And: EventPublisher should emit TeamDetailAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve team detail with team race history', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team with race history
|
||||
// Given: A team exists with race history
|
||||
// When: GetTeamDetailUseCase.execute() is called with team ID
|
||||
// Then: The result should show past races
|
||||
// And: The result should show race results
|
||||
// And: The result should show race dates
|
||||
// And: The result should show race tracks
|
||||
// And: EventPublisher should emit TeamDetailAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve team detail with league information', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team with league information
|
||||
// Given: A team exists in a league
|
||||
// When: GetTeamDetailUseCase.execute() is called with team ID
|
||||
// Then: The result should show league name
|
||||
// And: The result should show league tier
|
||||
// And: The result should show league season
|
||||
// And: EventPublisher should emit TeamDetailAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve team detail with social links', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team with social links
|
||||
// Given: A team exists with social links
|
||||
// When: GetTeamDetailUseCase.execute() is called with team ID
|
||||
// Then: The result should show social media links
|
||||
// And: The result should show website link
|
||||
// And: The result should show Discord link
|
||||
// And: EventPublisher should emit TeamDetailAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve team detail with roster size limit', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team with roster size limit
|
||||
// Given: A team exists with roster size limit
|
||||
// When: GetTeamDetailUseCase.execute() is called with team ID
|
||||
// Then: The result should show current roster size
|
||||
// And: The result should show maximum roster size
|
||||
// And: EventPublisher should emit TeamDetailAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve team detail with team full indicator', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team is full
|
||||
// Given: A team exists and is full
|
||||
// When: GetTeamDetailUseCase.execute() is called with team ID
|
||||
// Then: The result should show team is full
|
||||
// And: The result should not show join request option
|
||||
// And: EventPublisher should emit TeamDetailAccessedEvent
|
||||
// Then: The result should contain team information and membership but no management permissions
|
||||
expect(result.isOk()).toBe(true);
|
||||
const data = result.unwrap();
|
||||
expect(data.team.id.toString()).toBe(teamId);
|
||||
expect(data.membership?.role).toBe('driver');
|
||||
expect(data.canManage).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetTeamDetailUseCase - Edge Cases', () => {
|
||||
it('should handle team with no career history', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team with no career history
|
||||
// Given: A team exists
|
||||
// And: The team has no career history
|
||||
// When: GetTeamDetailUseCase.execute() is called with team ID
|
||||
// Then: The result should contain team detail
|
||||
// And: Career history section should be empty
|
||||
// And: EventPublisher should emit TeamDetailAccessedEvent
|
||||
});
|
||||
|
||||
it('should handle team with no recent race results', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team with no recent race results
|
||||
// Given: A team exists
|
||||
// And: The team has no recent race results
|
||||
// When: GetTeamDetailUseCase.execute() is called with team ID
|
||||
// Then: The result should contain team detail
|
||||
// And: Recent race results section should be empty
|
||||
// And: EventPublisher should emit TeamDetailAccessedEvent
|
||||
});
|
||||
|
||||
it('should handle team with no championship standings', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team with no championship standings
|
||||
// Given: A team exists
|
||||
// And: The team has no championship standings
|
||||
// When: GetTeamDetailUseCase.execute() is called with team ID
|
||||
// Then: The result should contain team detail
|
||||
// And: Championship standings section should be empty
|
||||
// And: EventPublisher should emit TeamDetailAccessedEvent
|
||||
});
|
||||
|
||||
it('should handle team with no data at all', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team with absolutely no data
|
||||
// Given: A team exists
|
||||
// And: The team has no statistics
|
||||
// And: The team has no career history
|
||||
// And: The team has no recent race results
|
||||
// And: The team has no championship standings
|
||||
// And: The team has no social links
|
||||
// When: GetTeamDetailUseCase.execute() is called with team ID
|
||||
// Then: The result should contain basic team info
|
||||
// And: All sections should be empty or show default values
|
||||
// And: EventPublisher should emit TeamDetailAccessedEvent
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetTeamDetailUseCase - Error Handling', () => {
|
||||
describe('GetTeamDetailsUseCase - Error Handling', () => {
|
||||
it('should throw error when team does not exist', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Non-existent team
|
||||
// Given: No team exists with the given ID
|
||||
// When: GetTeamDetailUseCase.execute() is called with non-existent team ID
|
||||
// Then: Should throw TeamNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
// When: GetTeamDetailsUseCase.execute() is called with non-existent team ID
|
||||
const result = await getTeamDetailsUseCase.execute({ teamId: 'nonexistent', driverId: 'any' });
|
||||
|
||||
it('should throw error when team ID is invalid', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Invalid team ID
|
||||
// Given: An invalid team ID (e.g., empty string, null, undefined)
|
||||
// When: GetTeamDetailUseCase.execute() is called with invalid team ID
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should handle repository errors gracefully', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Repository throws error
|
||||
// Given: A team exists
|
||||
// And: TeamRepository throws an error during query
|
||||
// When: GetTeamDetailUseCase.execute() is called
|
||||
// Then: Should propagate the error appropriately
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
|
||||
describe('Team Detail Data Orchestration', () => {
|
||||
it('should correctly calculate team statistics from race results', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team statistics calculation
|
||||
// Given: A team exists
|
||||
// And: The team has 10 completed races
|
||||
// And: The team has 3 wins
|
||||
// And: The team has 5 podiums
|
||||
// When: GetTeamDetailUseCase.execute() is called
|
||||
// Then: Team statistics should show:
|
||||
// - Starts: 10
|
||||
// - Wins: 3
|
||||
// - Podiums: 5
|
||||
// - Rating: Calculated based on performance
|
||||
// - Rank: Calculated based on rating
|
||||
});
|
||||
|
||||
it('should correctly format career history with league and team information', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Career history formatting
|
||||
// Given: A team exists
|
||||
// And: The team has participated in 2 leagues
|
||||
// And: The team has been on 3 teams across seasons
|
||||
// When: GetTeamDetailUseCase.execute() is called
|
||||
// Then: Career history should show:
|
||||
// - League A: Season 2024, Team X
|
||||
// - League B: Season 2024, Team Y
|
||||
// - League A: Season 2023, Team Z
|
||||
});
|
||||
|
||||
it('should correctly format recent race results with proper details', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Recent race results formatting
|
||||
// Given: A team exists
|
||||
// And: The team has 5 recent race results
|
||||
// When: GetTeamDetailUseCase.execute() is called
|
||||
// Then: Recent race results should show:
|
||||
// - Race name
|
||||
// - Track name
|
||||
// - Finishing position
|
||||
// - Points earned
|
||||
// - Race date (sorted newest first)
|
||||
});
|
||||
|
||||
it('should correctly aggregate championship standings across leagues', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Championship standings aggregation
|
||||
// Given: A team exists
|
||||
// And: The team is in 2 championships
|
||||
// And: In Championship A: Position 5, 150 points, 20 drivers
|
||||
// And: In Championship B: Position 12, 85 points, 15 drivers
|
||||
// When: GetTeamDetailUseCase.execute() is called
|
||||
// Then: Championship standings should show:
|
||||
// - League A: Position 5, 150 points, 20 drivers
|
||||
// - League B: Position 12, 85 points, 15 drivers
|
||||
});
|
||||
|
||||
it('should correctly format social links with proper URLs', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Social links formatting
|
||||
// Given: A team exists
|
||||
// And: The team has social links (Discord, Twitter, iRacing)
|
||||
// When: GetTeamDetailUseCase.execute() is called
|
||||
// Then: Social links should show:
|
||||
// - Discord: https://discord.gg/username
|
||||
// - Twitter: https://twitter.com/username
|
||||
// - iRacing: https://members.iracing.com/membersite/member/profile?username=username
|
||||
});
|
||||
|
||||
it('should correctly format team roster with roles', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team roster formatting
|
||||
// Given: A team exists
|
||||
// And: The team has captain, admins, and drivers
|
||||
// When: GetTeamDetailUseCase.execute() is called
|
||||
// Then: Team roster should show:
|
||||
// - Captain: Highlighted with badge
|
||||
// - Admins: Listed with admin role
|
||||
// - Drivers: Listed with driver role
|
||||
// - Each member should show name, avatar, and join date
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetTeamDetailUseCase - Event Orchestration', () => {
|
||||
it('should emit TeamDetailAccessedEvent with correct payload', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Event emission
|
||||
// Given: A team exists
|
||||
// When: GetTeamDetailUseCase.execute() is called
|
||||
// Then: EventPublisher should emit TeamDetailAccessedEvent
|
||||
// And: The event should contain team ID and requesting driver ID
|
||||
});
|
||||
|
||||
it('should not emit events on validation failure', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: No events on validation failure
|
||||
// Given: No team exists
|
||||
// When: GetTeamDetailUseCase.execute() is called with invalid data
|
||||
// Then: EventPublisher should NOT emit any events
|
||||
// Then: Should return error
|
||||
expect(result.isErr()).toBe(true);
|
||||
const error = result.unwrapErr();
|
||||
expect(error.code).toBe('TEAM_NOT_FOUND');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,323 +2,97 @@
|
||||
* Integration Test: Team Leaderboard Use Case Orchestration
|
||||
*
|
||||
* Tests the orchestration logic of team leaderboard-related Use Cases:
|
||||
* - GetTeamLeaderboardUseCase: Retrieves ranked list of teams with performance metrics
|
||||
* - Validates that Use Cases correctly interact with their Ports (Repositories, Event Publishers)
|
||||
* - GetTeamsLeaderboardUseCase: Retrieves ranked list of teams with performance metrics
|
||||
* - 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 { InMemoryTeamRepository } from '../../../adapters/teams/persistence/inmemory/InMemoryTeamRepository';
|
||||
import { InMemoryLeagueRepository } from '../../../adapters/leagues/persistence/inmemory/InMemoryLeagueRepository';
|
||||
import { InMemoryEventPublisher } from '../../../adapters/events/InMemoryEventPublisher';
|
||||
import { GetTeamLeaderboardUseCase } from '../../../core/teams/use-cases/GetTeamLeaderboardUseCase';
|
||||
import { GetTeamLeaderboardQuery } from '../../../core/teams/ports/GetTeamLeaderboardQuery';
|
||||
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 { GetTeamsLeaderboardUseCase } from '../../../core/racing/application/use-cases/GetTeamsLeaderboardUseCase';
|
||||
import { Team } from '../../../core/racing/domain/entities/Team';
|
||||
import { Logger } from '../../../core/shared/domain/Logger';
|
||||
|
||||
describe('Team Leaderboard Use Case Orchestration', () => {
|
||||
let teamRepository: InMemoryTeamRepository;
|
||||
let leagueRepository: InMemoryLeagueRepository;
|
||||
let eventPublisher: InMemoryEventPublisher;
|
||||
let getTeamLeaderboardUseCase: GetTeamLeaderboardUseCase;
|
||||
let membershipRepository: InMemoryTeamMembershipRepository;
|
||||
let getTeamsLeaderboardUseCase: GetTeamsLeaderboardUseCase;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeAll(() => {
|
||||
// TODO: Initialize In-Memory repositories and event publisher
|
||||
// teamRepository = new InMemoryTeamRepository();
|
||||
// leagueRepository = new InMemoryLeagueRepository();
|
||||
// eventPublisher = new InMemoryEventPublisher();
|
||||
// getTeamLeaderboardUseCase = new GetTeamLeaderboardUseCase({
|
||||
// teamRepository,
|
||||
// leagueRepository,
|
||||
// eventPublisher,
|
||||
// });
|
||||
mockLogger = {
|
||||
info: () => {},
|
||||
debug: () => {},
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
} as unknown as Logger;
|
||||
|
||||
teamRepository = new InMemoryTeamRepository(mockLogger);
|
||||
membershipRepository = new InMemoryTeamMembershipRepository(mockLogger);
|
||||
|
||||
// Mock driver stats provider
|
||||
const getDriverStats = (driverId: string) => {
|
||||
const statsMap: Record<string, { rating: number, wins: number, totalRaces: number }> = {
|
||||
'd1': { rating: 2000, wins: 10, totalRaces: 50 },
|
||||
'd2': { rating: 1500, wins: 5, totalRaces: 30 },
|
||||
'd3': { rating: 1000, wins: 2, totalRaces: 20 },
|
||||
};
|
||||
return statsMap[driverId] || null;
|
||||
};
|
||||
|
||||
getTeamsLeaderboardUseCase = new GetTeamsLeaderboardUseCase(
|
||||
teamRepository,
|
||||
membershipRepository,
|
||||
getDriverStats,
|
||||
mockLogger
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// TODO: Clear all In-Memory repositories before each test
|
||||
// teamRepository.clear();
|
||||
// leagueRepository.clear();
|
||||
// eventPublisher.clear();
|
||||
teamRepository.clear();
|
||||
membershipRepository.clear();
|
||||
});
|
||||
|
||||
describe('GetTeamLeaderboardUseCase - Success Path', () => {
|
||||
it('should retrieve complete team leaderboard with all teams', async () => {
|
||||
// TODO: Implement test
|
||||
describe('GetTeamsLeaderboardUseCase - Success Path', () => {
|
||||
it('should retrieve ranked team leaderboard with performance metrics', async () => {
|
||||
// Scenario: Leaderboard with multiple teams
|
||||
// Given: Multiple teams exist with different performance metrics
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called
|
||||
// Then: The result should contain all teams
|
||||
// And: Teams should be ranked by points
|
||||
// And: Each team should show position, name, and points
|
||||
// And: EventPublisher should emit TeamLeaderboardAccessedEvent
|
||||
// Given: Multiple teams exist
|
||||
const team1 = Team.create({ id: 't1', name: 'Pro Team', tag: 'PRO', description: 'Desc', ownerId: 'o1', leagues: [] });
|
||||
const team2 = Team.create({ id: 't2', name: 'Am Team', tag: 'AM', description: 'Desc', ownerId: 'o2', leagues: [] });
|
||||
await teamRepository.create(team1);
|
||||
await teamRepository.create(team2);
|
||||
|
||||
// And: Teams have members with different stats
|
||||
await membershipRepository.saveMembership({ teamId: 't1', driverId: 'd1', role: 'owner', status: 'active', joinedAt: new Date() });
|
||||
await membershipRepository.saveMembership({ teamId: 't2', driverId: 'd3', role: 'owner', status: 'active', joinedAt: new Date() });
|
||||
|
||||
// When: GetTeamsLeaderboardUseCase.execute() is called
|
||||
const result = await getTeamsLeaderboardUseCase.execute({ leagueId: 'any' });
|
||||
|
||||
// Then: The result should contain ranked teams
|
||||
expect(result.isOk()).toBe(true);
|
||||
const { items, topItems } = result.unwrap();
|
||||
expect(items).toHaveLength(2);
|
||||
|
||||
// And: Teams should be ranked by rating (Pro Team has d1 with 2000, Am Team has d3 with 1000)
|
||||
expect(topItems[0]?.team.id.toString()).toBe('t1');
|
||||
expect(topItems[0]?.rating).toBe(2000);
|
||||
expect(topItems[1]?.team.id.toString()).toBe('t2');
|
||||
expect(topItems[1]?.rating).toBe(1000);
|
||||
});
|
||||
|
||||
it('should retrieve team leaderboard with performance metrics', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Leaderboard with performance metrics
|
||||
// Given: Teams exist with performance data
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called
|
||||
// Then: Each team should show total points
|
||||
// And: Each team should show win count
|
||||
// And: Each team should show podium count
|
||||
// And: Each team should show race count
|
||||
// And: EventPublisher should emit TeamLeaderboardAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve team leaderboard filtered by league', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Leaderboard filtered by league
|
||||
// Given: Teams exist in multiple leagues
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called with league filter
|
||||
// Then: The result should contain only teams from that league
|
||||
// And: Teams should be ranked by points within the league
|
||||
// And: EventPublisher should emit TeamLeaderboardAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve team leaderboard filtered by season', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Leaderboard filtered by season
|
||||
// Given: Teams exist with data from multiple seasons
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called with season filter
|
||||
// Then: The result should contain only teams from that season
|
||||
// And: Teams should be ranked by points within the season
|
||||
// And: EventPublisher should emit TeamLeaderboardAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve team leaderboard filtered by tier', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Leaderboard filtered by tier
|
||||
// Given: Teams exist in different tiers
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called with tier filter
|
||||
// Then: The result should contain only teams from that tier
|
||||
// And: Teams should be ranked by points within the tier
|
||||
// And: EventPublisher should emit TeamLeaderboardAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve team leaderboard sorted by different criteria', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Leaderboard sorted by different criteria
|
||||
// Given: Teams exist with various metrics
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called with sort criteria
|
||||
// Then: Teams should be sorted by the specified criteria
|
||||
// And: The sort order should be correct
|
||||
// And: EventPublisher should emit TeamLeaderboardAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve team leaderboard with pagination', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Leaderboard with pagination
|
||||
// Given: Many teams exist
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called with pagination
|
||||
// Then: The result should contain only the specified page
|
||||
// And: The result should show total count
|
||||
// And: EventPublisher should emit TeamLeaderboardAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve team leaderboard with top teams highlighted', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Top teams highlighted
|
||||
// Given: Teams exist with rankings
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called
|
||||
// Then: Top 3 teams should be highlighted
|
||||
// And: Top teams should have gold, silver, bronze badges
|
||||
// And: EventPublisher should emit TeamLeaderboardAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve team leaderboard with own team highlighted', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Own team highlighted
|
||||
// Given: Teams exist and driver is member of a team
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called with driver ID
|
||||
// Then: The driver's team should be highlighted
|
||||
// And: The team should have a "Your Team" indicator
|
||||
// And: EventPublisher should emit TeamLeaderboardAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve team leaderboard with filters applied', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Multiple filters applied
|
||||
// Given: Teams exist in multiple leagues and seasons
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called with multiple filters
|
||||
// Then: The result should show active filters
|
||||
// And: The result should contain only matching teams
|
||||
// And: EventPublisher should emit TeamLeaderboardAccessedEvent
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetTeamLeaderboardUseCase - Edge Cases', () => {
|
||||
it('should handle empty leaderboard', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: No teams exist
|
||||
// Given: No teams exist
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called
|
||||
// When: GetTeamsLeaderboardUseCase.execute() is called
|
||||
const result = await getTeamsLeaderboardUseCase.execute({ leagueId: 'any' });
|
||||
|
||||
// Then: The result should be empty
|
||||
// And: EventPublisher should emit TeamLeaderboardAccessedEvent
|
||||
});
|
||||
|
||||
it('should handle empty leaderboard after filtering', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: No teams match filters
|
||||
// Given: Teams exist but none match the filters
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called with filters
|
||||
// Then: The result should be empty
|
||||
// And: EventPublisher should emit TeamLeaderboardAccessedEvent
|
||||
});
|
||||
|
||||
it('should handle leaderboard with single team', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Only one team exists
|
||||
// Given: Only one team exists
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called
|
||||
// Then: The result should contain only that team
|
||||
// And: The team should be ranked 1st
|
||||
// And: EventPublisher should emit TeamLeaderboardAccessedEvent
|
||||
});
|
||||
|
||||
it('should handle leaderboard with teams having equal points', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Teams with equal points
|
||||
// Given: Multiple teams have the same points
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called
|
||||
// Then: Teams should be ranked by tie-breaker criteria
|
||||
// And: EventPublisher should emit TeamLeaderboardAccessedEvent
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetTeamLeaderboardUseCase - Error Handling', () => {
|
||||
it('should throw error when league does not exist', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Non-existent league
|
||||
// Given: No league exists with the given ID
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called with non-existent league ID
|
||||
// Then: Should throw LeagueNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should throw error when league ID is invalid', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Invalid league ID
|
||||
// Given: An invalid league ID (e.g., empty string, null, undefined)
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called with invalid league ID
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should handle repository errors gracefully', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Repository throws error
|
||||
// Given: Teams exist
|
||||
// And: TeamRepository throws an error during query
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called
|
||||
// Then: Should propagate the error appropriately
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
|
||||
describe('Team Leaderboard Data Orchestration', () => {
|
||||
it('should correctly calculate team rankings from performance metrics', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team ranking calculation
|
||||
// Given: Teams exist with different performance metrics
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called
|
||||
// Then: Teams should be ranked by points
|
||||
// And: Teams with more wins should rank higher when points are equal
|
||||
// And: Teams with more podiums should rank higher when wins are equal
|
||||
});
|
||||
|
||||
it('should correctly format team performance metrics', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Performance metrics formatting
|
||||
// Given: Teams exist with performance data
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called
|
||||
// Then: Each team should show:
|
||||
// - Total points (formatted as number)
|
||||
// - Win count (formatted as number)
|
||||
// - Podium count (formatted as number)
|
||||
// - Race count (formatted as number)
|
||||
// - Win rate (formatted as percentage)
|
||||
});
|
||||
|
||||
it('should correctly filter teams by league', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: League filtering
|
||||
// Given: Teams exist in multiple leagues
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called with league filter
|
||||
// Then: Only teams from the specified league should be included
|
||||
// And: Teams should be ranked by points within the league
|
||||
});
|
||||
|
||||
it('should correctly filter teams by season', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Season filtering
|
||||
// Given: Teams exist with data from multiple seasons
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called with season filter
|
||||
// Then: Only teams from the specified season should be included
|
||||
// And: Teams should be ranked by points within the season
|
||||
});
|
||||
|
||||
it('should correctly filter teams by tier', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Tier filtering
|
||||
// Given: Teams exist in different tiers
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called with tier filter
|
||||
// Then: Only teams from the specified tier should be included
|
||||
// And: Teams should be ranked by points within the tier
|
||||
});
|
||||
|
||||
it('should correctly sort teams by different criteria', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Sorting by different criteria
|
||||
// Given: Teams exist with various metrics
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called with sort criteria
|
||||
// Then: Teams should be sorted by the specified criteria
|
||||
// And: The sort order should be correct
|
||||
});
|
||||
|
||||
it('should correctly paginate team leaderboard', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Pagination
|
||||
// Given: Many teams exist
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called with pagination
|
||||
// Then: Only the specified page should be returned
|
||||
// And: Total count should be accurate
|
||||
});
|
||||
|
||||
it('should correctly highlight top teams', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Top team highlighting
|
||||
// Given: Teams exist with rankings
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called
|
||||
// Then: Top 3 teams should be marked as top teams
|
||||
// And: Top teams should have appropriate badges
|
||||
});
|
||||
|
||||
it('should correctly highlight own team', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Own team highlighting
|
||||
// Given: Teams exist and driver is member of a team
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called with driver ID
|
||||
// Then: The driver's team should be marked as own team
|
||||
// And: The team should have a "Your Team" indicator
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetTeamLeaderboardUseCase - Event Orchestration', () => {
|
||||
it('should emit TeamLeaderboardAccessedEvent with correct payload', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Event emission
|
||||
// Given: Teams exist
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called
|
||||
// Then: EventPublisher should emit TeamLeaderboardAccessedEvent
|
||||
// And: The event should contain filter and sort parameters
|
||||
});
|
||||
|
||||
it('should not emit events on validation failure', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: No events on validation failure
|
||||
// Given: Invalid parameters
|
||||
// When: GetTeamLeaderboardUseCase.execute() is called with invalid data
|
||||
// Then: EventPublisher should NOT emit any events
|
||||
expect(result.isOk()).toBe(true);
|
||||
const { items } = result.unwrap();
|
||||
expect(items).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,328 +2,104 @@
|
||||
* Integration Test: Teams List Use Case Orchestration
|
||||
*
|
||||
* Tests the orchestration logic of teams list-related Use Cases:
|
||||
* - GetTeamsListUseCase: Retrieves list of teams with filtering, sorting, and search capabilities
|
||||
* - Validates that Use Cases correctly interact with their Ports (Repositories, Event Publishers)
|
||||
* - GetAllTeamsUseCase: Retrieves list of teams with enrichment (member count, stats)
|
||||
* - 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 { InMemoryTeamRepository } from '../../../adapters/teams/persistence/inmemory/InMemoryTeamRepository';
|
||||
import { InMemoryLeagueRepository } from '../../../adapters/leagues/persistence/inmemory/InMemoryLeagueRepository';
|
||||
import { InMemoryEventPublisher } from '../../../adapters/events/InMemoryEventPublisher';
|
||||
import { GetTeamsListUseCase } from '../../../core/teams/use-cases/GetTeamsListUseCase';
|
||||
import { GetTeamsListQuery } from '../../../core/teams/ports/GetTeamsListQuery';
|
||||
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 { InMemoryTeamStatsRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamStatsRepository';
|
||||
import { GetAllTeamsUseCase } from '../../../core/racing/application/use-cases/GetAllTeamsUseCase';
|
||||
import { Team } from '../../../core/racing/domain/entities/Team';
|
||||
import { Logger } from '../../../core/shared/domain/Logger';
|
||||
|
||||
describe('Teams List Use Case Orchestration', () => {
|
||||
let teamRepository: InMemoryTeamRepository;
|
||||
let leagueRepository: InMemoryLeagueRepository;
|
||||
let eventPublisher: InMemoryEventPublisher;
|
||||
let getTeamsListUseCase: GetTeamsListUseCase;
|
||||
let membershipRepository: InMemoryTeamMembershipRepository;
|
||||
let statsRepository: InMemoryTeamStatsRepository;
|
||||
let getAllTeamsUseCase: GetAllTeamsUseCase;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeAll(() => {
|
||||
// TODO: Initialize In-Memory repositories and event publisher
|
||||
// teamRepository = new InMemoryTeamRepository();
|
||||
// leagueRepository = new InMemoryLeagueRepository();
|
||||
// eventPublisher = new InMemoryEventPublisher();
|
||||
// getTeamsListUseCase = new GetTeamsListUseCase({
|
||||
// teamRepository,
|
||||
// leagueRepository,
|
||||
// eventPublisher,
|
||||
// });
|
||||
mockLogger = {
|
||||
info: () => {},
|
||||
debug: () => {},
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
} as unknown as Logger;
|
||||
|
||||
teamRepository = new InMemoryTeamRepository(mockLogger);
|
||||
membershipRepository = new InMemoryTeamMembershipRepository(mockLogger);
|
||||
statsRepository = new InMemoryTeamStatsRepository();
|
||||
getAllTeamsUseCase = new GetAllTeamsUseCase(teamRepository, membershipRepository, statsRepository, mockLogger);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// TODO: Clear all In-Memory repositories before each test
|
||||
// teamRepository.clear();
|
||||
// leagueRepository.clear();
|
||||
// eventPublisher.clear();
|
||||
teamRepository.clear();
|
||||
membershipRepository.clear();
|
||||
statsRepository.clear();
|
||||
});
|
||||
|
||||
describe('GetTeamsListUseCase - Success Path', () => {
|
||||
it('should retrieve complete teams list with all teams', async () => {
|
||||
// TODO: Implement test
|
||||
describe('GetAllTeamsUseCase - Success Path', () => {
|
||||
it('should retrieve complete teams list with all teams and enrichment', async () => {
|
||||
// Scenario: Teams list with multiple teams
|
||||
// Given: Multiple teams exist
|
||||
// When: GetTeamsListUseCase.execute() is called
|
||||
// Then: The result should contain all teams
|
||||
// And: Each team should show name, logo, and member count
|
||||
// And: EventPublisher should emit TeamsListAccessedEvent
|
||||
const team1 = Team.create({ id: 't1', name: 'Team 1', tag: 'T1', description: 'Desc 1', ownerId: 'o1', leagues: [] });
|
||||
const team2 = Team.create({ id: 't2', name: 'Team 2', tag: 'T2', description: 'Desc 2', ownerId: 'o2', leagues: [] });
|
||||
await teamRepository.create(team1);
|
||||
await teamRepository.create(team2);
|
||||
|
||||
// And: Teams have members
|
||||
await membershipRepository.saveMembership({ teamId: 't1', driverId: 'd1', role: 'owner', status: 'active', joinedAt: new Date() });
|
||||
await membershipRepository.saveMembership({ teamId: 't1', driverId: 'd2', role: 'driver', status: 'active', joinedAt: new Date() });
|
||||
await membershipRepository.saveMembership({ teamId: 't2', driverId: 'd3', role: 'owner', status: 'active', joinedAt: new Date() });
|
||||
|
||||
// And: Teams have stats
|
||||
await statsRepository.saveTeamStats('t1', {
|
||||
totalWins: 5,
|
||||
totalRaces: 20,
|
||||
rating: 1500,
|
||||
performanceLevel: 'intermediate',
|
||||
specialization: 'sprint',
|
||||
region: 'EU',
|
||||
languages: ['en'],
|
||||
isRecruiting: true
|
||||
});
|
||||
|
||||
// When: GetAllTeamsUseCase.execute() is called
|
||||
const result = await getAllTeamsUseCase.execute({});
|
||||
|
||||
// Then: The result should contain all teams with enrichment
|
||||
expect(result.isOk()).toBe(true);
|
||||
const { teams, totalCount } = result.unwrap();
|
||||
expect(totalCount).toBe(2);
|
||||
|
||||
const enriched1 = teams.find(t => t.team.id.toString() === 't1');
|
||||
expect(enriched1).toBeDefined();
|
||||
expect(enriched1?.memberCount).toBe(2);
|
||||
expect(enriched1?.totalWins).toBe(5);
|
||||
expect(enriched1?.rating).toBe(1500);
|
||||
|
||||
const enriched2 = teams.find(t => t.team.id.toString() === 't2');
|
||||
expect(enriched2).toBeDefined();
|
||||
expect(enriched2?.memberCount).toBe(1);
|
||||
expect(enriched2?.totalWins).toBe(0); // Default value
|
||||
});
|
||||
|
||||
it('should retrieve teams list with team details', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Teams list with detailed information
|
||||
// Given: Teams exist with various details
|
||||
// When: GetTeamsListUseCase.execute() is called
|
||||
// Then: Each team should show team name
|
||||
// And: Each team should show team logo
|
||||
// And: Each team should show number of members
|
||||
// And: Each team should show performance stats
|
||||
// And: EventPublisher should emit TeamsListAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve teams list with search filter', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Teams list with search
|
||||
// Given: Teams exist with various names
|
||||
// When: GetTeamsListUseCase.execute() is called with search term
|
||||
// Then: The result should contain only matching teams
|
||||
// And: The result should show search results count
|
||||
// And: EventPublisher should emit TeamsListAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve teams list filtered by league', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Teams list filtered by league
|
||||
// Given: Teams exist in multiple leagues
|
||||
// When: GetTeamsListUseCase.execute() is called with league filter
|
||||
// Then: The result should contain only teams from that league
|
||||
// And: EventPublisher should emit TeamsListAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve teams list filtered by performance tier', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Teams list filtered by tier
|
||||
// Given: Teams exist in different tiers
|
||||
// When: GetTeamsListUseCase.execute() is called with tier filter
|
||||
// Then: The result should contain only teams from that tier
|
||||
// And: EventPublisher should emit TeamsListAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve teams list sorted by different criteria', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Teams list sorted by different criteria
|
||||
// Given: Teams exist with various metrics
|
||||
// When: GetTeamsListUseCase.execute() is called with sort criteria
|
||||
// Then: Teams should be sorted by the specified criteria
|
||||
// And: The sort order should be correct
|
||||
// And: EventPublisher should emit TeamsListAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve teams list with pagination', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Teams list with pagination
|
||||
// Given: Many teams exist
|
||||
// When: GetTeamsListUseCase.execute() is called with pagination
|
||||
// Then: The result should contain only the specified page
|
||||
// And: The result should show total count
|
||||
// And: EventPublisher should emit TeamsListAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve teams list with team achievements', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Teams list with achievements
|
||||
// Given: Teams exist with achievements
|
||||
// When: GetTeamsListUseCase.execute() is called
|
||||
// Then: Each team should show achievement badges
|
||||
// And: Each team should show number of achievements
|
||||
// And: EventPublisher should emit TeamsListAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve teams list with team performance metrics', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Teams list with performance metrics
|
||||
// Given: Teams exist with performance data
|
||||
// When: GetTeamsListUseCase.execute() is called
|
||||
// Then: Each team should show win rate
|
||||
// And: Each team should show podium finishes
|
||||
// And: Each team should show recent race results
|
||||
// And: EventPublisher should emit TeamsListAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve teams list with team roster preview', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Teams list with roster preview
|
||||
// Given: Teams exist with members
|
||||
// When: GetTeamsListUseCase.execute() is called
|
||||
// Then: Each team should show preview of team members
|
||||
// And: Each team should show the team captain
|
||||
// And: EventPublisher should emit TeamsListAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve teams list with filters applied', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Multiple filters applied
|
||||
// Given: Teams exist in multiple leagues and tiers
|
||||
// When: GetTeamsListUseCase.execute() is called with multiple filters
|
||||
// Then: The result should show active filters
|
||||
// And: The result should contain only matching teams
|
||||
// And: EventPublisher should emit TeamsListAccessedEvent
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetTeamsListUseCase - Edge Cases', () => {
|
||||
it('should handle empty teams list', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: No teams exist
|
||||
// Given: No teams exist
|
||||
// When: GetTeamsListUseCase.execute() is called
|
||||
// When: GetAllTeamsUseCase.execute() is called
|
||||
const result = await getAllTeamsUseCase.execute({});
|
||||
|
||||
// Then: The result should be empty
|
||||
// And: EventPublisher should emit TeamsListAccessedEvent
|
||||
});
|
||||
|
||||
it('should handle empty teams list after filtering', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: No teams match filters
|
||||
// Given: Teams exist but none match the filters
|
||||
// When: GetTeamsListUseCase.execute() is called with filters
|
||||
// Then: The result should be empty
|
||||
// And: EventPublisher should emit TeamsListAccessedEvent
|
||||
});
|
||||
|
||||
it('should handle empty teams list after search', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: No teams match search
|
||||
// Given: Teams exist but none match the search term
|
||||
// When: GetTeamsListUseCase.execute() is called with search term
|
||||
// Then: The result should be empty
|
||||
// And: EventPublisher should emit TeamsListAccessedEvent
|
||||
});
|
||||
|
||||
it('should handle teams list with single team', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Only one team exists
|
||||
// Given: Only one team exists
|
||||
// When: GetTeamsListUseCase.execute() is called
|
||||
// Then: The result should contain only that team
|
||||
// And: EventPublisher should emit TeamsListAccessedEvent
|
||||
});
|
||||
|
||||
it('should handle teams list with teams having equal metrics', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Teams with equal metrics
|
||||
// Given: Multiple teams have the same metrics
|
||||
// When: GetTeamsListUseCase.execute() is called
|
||||
// Then: Teams should be sorted by tie-breaker criteria
|
||||
// And: EventPublisher should emit TeamsListAccessedEvent
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetTeamsListUseCase - Error Handling', () => {
|
||||
it('should throw error when league does not exist', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Non-existent league
|
||||
// Given: No league exists with the given ID
|
||||
// When: GetTeamsListUseCase.execute() is called with non-existent league ID
|
||||
// Then: Should throw LeagueNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should throw error when league ID is invalid', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Invalid league ID
|
||||
// Given: An invalid league ID (e.g., empty string, null, undefined)
|
||||
// When: GetTeamsListUseCase.execute() is called with invalid league ID
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should handle repository errors gracefully', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Repository throws error
|
||||
// Given: Teams exist
|
||||
// And: TeamRepository throws an error during query
|
||||
// When: GetTeamsListUseCase.execute() is called
|
||||
// Then: Should propagate the error appropriately
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
|
||||
describe('Teams List Data Orchestration', () => {
|
||||
it('should correctly filter teams by league', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: League filtering
|
||||
// Given: Teams exist in multiple leagues
|
||||
// When: GetTeamsListUseCase.execute() is called with league filter
|
||||
// Then: Only teams from the specified league should be included
|
||||
// And: Teams should be sorted by the specified criteria
|
||||
});
|
||||
|
||||
it('should correctly filter teams by tier', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Tier filtering
|
||||
// Given: Teams exist in different tiers
|
||||
// When: GetTeamsListUseCase.execute() is called with tier filter
|
||||
// Then: Only teams from the specified tier should be included
|
||||
// And: Teams should be sorted by the specified criteria
|
||||
});
|
||||
|
||||
it('should correctly search teams by name', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Team name search
|
||||
// Given: Teams exist with various names
|
||||
// When: GetTeamsListUseCase.execute() is called with search term
|
||||
// Then: Only teams matching the search term should be included
|
||||
// And: Search should be case-insensitive
|
||||
});
|
||||
|
||||
it('should correctly sort teams by different criteria', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Sorting by different criteria
|
||||
// Given: Teams exist with various metrics
|
||||
// When: GetTeamsListUseCase.execute() is called with sort criteria
|
||||
// Then: Teams should be sorted by the specified criteria
|
||||
// And: The sort order should be correct
|
||||
});
|
||||
|
||||
it('should correctly paginate teams list', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Pagination
|
||||
// Given: Many teams exist
|
||||
// When: GetTeamsListUseCase.execute() is called with pagination
|
||||
// Then: Only the specified page should be returned
|
||||
// And: Total count should be accurate
|
||||
});
|
||||
|
||||
it('should correctly format team achievements', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Achievement formatting
|
||||
// Given: Teams exist with achievements
|
||||
// When: GetTeamsListUseCase.execute() is called
|
||||
// Then: Each team should show achievement badges
|
||||
// And: Each team should show number of achievements
|
||||
});
|
||||
|
||||
it('should correctly format team performance metrics', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Performance metrics formatting
|
||||
// Given: Teams exist with performance data
|
||||
// When: GetTeamsListUseCase.execute() is called
|
||||
// Then: Each team should show:
|
||||
// - Win rate (formatted as percentage)
|
||||
// - Podium finishes (formatted as number)
|
||||
// - Recent race results (formatted with position and points)
|
||||
});
|
||||
|
||||
it('should correctly format team roster preview', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Roster preview formatting
|
||||
// Given: Teams exist with members
|
||||
// When: GetTeamsListUseCase.execute() is called
|
||||
// Then: Each team should show preview of team members
|
||||
// And: Each team should show the team captain
|
||||
// And: Preview should be limited to a few members
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetTeamsListUseCase - Event Orchestration', () => {
|
||||
it('should emit TeamsListAccessedEvent with correct payload', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Event emission
|
||||
// Given: Teams exist
|
||||
// When: GetTeamsListUseCase.execute() is called
|
||||
// Then: EventPublisher should emit TeamsListAccessedEvent
|
||||
// And: The event should contain filter, sort, and search parameters
|
||||
});
|
||||
|
||||
it('should not emit events on validation failure', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: No events on validation failure
|
||||
// Given: Invalid parameters
|
||||
// When: GetTeamsListUseCase.execute() is called with invalid data
|
||||
// Then: EventPublisher should NOT emit any events
|
||||
expect(result.isOk()).toBe(true);
|
||||
const { teams, totalCount } = result.unwrap();
|
||||
expect(totalCount).toBe(0);
|
||||
expect(teams).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user