/** * 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) * - 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'; 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; 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, // }); }); beforeEach(() => { // TODO: Clear all In-Memory repositories before each test // teamRepository.clear(); // driverRepository.clear(); // leagueRepository.clear(); // eventPublisher.clear(); // fileStorage.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 }); 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 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 }); }); 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 }); 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 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 }); }); 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 }); 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 }); 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 }); }); 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 }); }); });