integration tests
This commit is contained in:
@@ -1,331 +1,658 @@
|
||||
/**
|
||||
* Integration Test: Sponsor Leagues Use Case Orchestration
|
||||
*
|
||||
*
|
||||
* Tests the orchestration logic of sponsor leagues-related Use Cases:
|
||||
* - GetAvailableLeaguesUseCase: Retrieves available leagues for sponsorship
|
||||
* - GetLeagueStatisticsUseCase: Retrieves league statistics
|
||||
* - FilterLeaguesUseCase: Filters leagues by availability
|
||||
* - SearchLeaguesUseCase: Searches leagues by query
|
||||
* - Validates that Use Cases correctly interact with their Ports (Repositories, Event Publishers)
|
||||
* - GetSponsorSponsorshipsUseCase: Retrieves sponsor's sponsorships/campaigns
|
||||
* - 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 { InMemorySponsorRepository } from '../../../adapters/sponsors/persistence/inmemory/InMemorySponsorRepository';
|
||||
import { InMemoryLeagueRepository } from '../../../adapters/leagues/persistence/inmemory/InMemoryLeagueRepository';
|
||||
import { InMemoryEventPublisher } from '../../../adapters/events/InMemoryEventPublisher';
|
||||
import { GetAvailableLeaguesUseCase } from '../../../core/sponsors/use-cases/GetAvailableLeaguesUseCase';
|
||||
import { GetLeagueStatisticsUseCase } from '../../../core/sponsors/use-cases/GetLeagueStatisticsUseCase';
|
||||
import { FilterLeaguesUseCase } from '../../../core/sponsors/use-cases/FilterLeaguesUseCase';
|
||||
import { SearchLeaguesUseCase } from '../../../core/sponsors/use-cases/SearchLeaguesUseCase';
|
||||
import { GetAvailableLeaguesQuery } from '../../../core/sponsors/ports/GetAvailableLeaguesQuery';
|
||||
import { GetLeagueStatisticsQuery } from '../../../core/sponsors/ports/GetLeagueStatisticsQuery';
|
||||
import { FilterLeaguesCommand } from '../../../core/sponsors/ports/FilterLeaguesCommand';
|
||||
import { SearchLeaguesCommand } from '../../../core/sponsors/ports/SearchLeaguesCommand';
|
||||
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
|
||||
import { InMemorySponsorRepository } from '../../../adapters/racing/persistence/inmemory/InMemorySponsorRepository';
|
||||
import { InMemorySeasonSponsorshipRepository } from '../../../adapters/racing/persistence/inmemory/InMemorySeasonSponsorshipRepository';
|
||||
import { InMemorySeasonRepository } from '../../../adapters/racing/persistence/inmemory/InMemorySeasonRepository';
|
||||
import { InMemoryLeagueRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryLeagueRepository';
|
||||
import { InMemoryLeagueMembershipRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryLeagueMembershipRepository';
|
||||
import { InMemoryRaceRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryRaceRepository';
|
||||
import { GetSponsorSponsorshipsUseCase } from '../../../core/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
|
||||
import { Sponsor } from '../../../core/racing/domain/entities/sponsor/Sponsor';
|
||||
import { SeasonSponsorship } from '../../../core/racing/domain/entities/season/SeasonSponsorship';
|
||||
import { Season } from '../../../core/racing/domain/entities/season/Season';
|
||||
import { League } from '../../../core/racing/domain/entities/League';
|
||||
import { LeagueMembership } from '../../../core/racing/domain/entities/LeagueMembership';
|
||||
import { Race } from '../../../core/racing/domain/entities/Race';
|
||||
import { Money } from '../../../core/racing/domain/value-objects/Money';
|
||||
import { Logger } from '../../../core/shared/domain/Logger';
|
||||
|
||||
describe('Sponsor Leagues Use Case Orchestration', () => {
|
||||
let sponsorRepository: InMemorySponsorRepository;
|
||||
let seasonSponsorshipRepository: InMemorySeasonSponsorshipRepository;
|
||||
let seasonRepository: InMemorySeasonRepository;
|
||||
let leagueRepository: InMemoryLeagueRepository;
|
||||
let eventPublisher: InMemoryEventPublisher;
|
||||
let getAvailableLeaguesUseCase: GetAvailableLeaguesUseCase;
|
||||
let getLeagueStatisticsUseCase: GetLeagueStatisticsUseCase;
|
||||
let filterLeaguesUseCase: FilterLeaguesUseCase;
|
||||
let searchLeaguesUseCase: SearchLeaguesUseCase;
|
||||
let leagueMembershipRepository: InMemoryLeagueMembershipRepository;
|
||||
let raceRepository: InMemoryRaceRepository;
|
||||
let getSponsorSponsorshipsUseCase: GetSponsorSponsorshipsUseCase;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeAll(() => {
|
||||
// TODO: Initialize In-Memory repositories and event publisher
|
||||
// sponsorRepository = new InMemorySponsorRepository();
|
||||
// leagueRepository = new InMemoryLeagueRepository();
|
||||
// eventPublisher = new InMemoryEventPublisher();
|
||||
// getAvailableLeaguesUseCase = new GetAvailableLeaguesUseCase({
|
||||
// sponsorRepository,
|
||||
// leagueRepository,
|
||||
// eventPublisher,
|
||||
// });
|
||||
// getLeagueStatisticsUseCase = new GetLeagueStatisticsUseCase({
|
||||
// sponsorRepository,
|
||||
// leagueRepository,
|
||||
// eventPublisher,
|
||||
// });
|
||||
// filterLeaguesUseCase = new FilterLeaguesUseCase({
|
||||
// sponsorRepository,
|
||||
// leagueRepository,
|
||||
// eventPublisher,
|
||||
// });
|
||||
// searchLeaguesUseCase = new SearchLeaguesUseCase({
|
||||
// sponsorRepository,
|
||||
// leagueRepository,
|
||||
// eventPublisher,
|
||||
// });
|
||||
mockLogger = {
|
||||
info: () => {},
|
||||
debug: () => {},
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
} as unknown as Logger;
|
||||
|
||||
sponsorRepository = new InMemorySponsorRepository(mockLogger);
|
||||
seasonSponsorshipRepository = new InMemorySeasonSponsorshipRepository(mockLogger);
|
||||
seasonRepository = new InMemorySeasonRepository(mockLogger);
|
||||
leagueRepository = new InMemoryLeagueRepository(mockLogger);
|
||||
leagueMembershipRepository = new InMemoryLeagueMembershipRepository(mockLogger);
|
||||
raceRepository = new InMemoryRaceRepository(mockLogger);
|
||||
|
||||
getSponsorSponsorshipsUseCase = new GetSponsorSponsorshipsUseCase(
|
||||
sponsorRepository,
|
||||
seasonSponsorshipRepository,
|
||||
seasonRepository,
|
||||
leagueRepository,
|
||||
leagueMembershipRepository,
|
||||
raceRepository,
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// TODO: Clear all In-Memory repositories before each test
|
||||
// sponsorRepository.clear();
|
||||
// leagueRepository.clear();
|
||||
// eventPublisher.clear();
|
||||
sponsorRepository.clear();
|
||||
seasonSponsorshipRepository.clear();
|
||||
seasonRepository.clear();
|
||||
leagueRepository.clear();
|
||||
leagueMembershipRepository.clear();
|
||||
raceRepository.clear();
|
||||
});
|
||||
|
||||
describe('GetAvailableLeaguesUseCase - Success Path', () => {
|
||||
it('should retrieve available leagues for sponsorship', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Sponsor with available leagues
|
||||
describe('GetSponsorSponsorshipsUseCase - Success Path', () => {
|
||||
it('should retrieve all sponsorships for a sponsor', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: There are 5 leagues available for sponsorship
|
||||
// When: GetAvailableLeaguesUseCase.execute() is called with sponsor ID
|
||||
// Then: The result should contain all 5 leagues
|
||||
// And: Each league should display its details
|
||||
// And: EventPublisher should emit AvailableLeaguesAccessedEvent
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await sponsorRepository.create(sponsor);
|
||||
|
||||
// And: The sponsor has 3 sponsorships with different statuses
|
||||
const league1 = League.create({
|
||||
id: 'league-1',
|
||||
name: 'League 1',
|
||||
description: 'Description 1',
|
||||
ownerId: 'owner-1',
|
||||
});
|
||||
await leagueRepository.create(league1);
|
||||
|
||||
const league2 = League.create({
|
||||
id: 'league-2',
|
||||
name: 'League 2',
|
||||
description: 'Description 2',
|
||||
ownerId: 'owner-2',
|
||||
});
|
||||
await leagueRepository.create(league2);
|
||||
|
||||
const league3 = League.create({
|
||||
id: 'league-3',
|
||||
name: 'League 3',
|
||||
description: 'Description 3',
|
||||
ownerId: 'owner-3',
|
||||
});
|
||||
await leagueRepository.create(league3);
|
||||
|
||||
const season1 = Season.create({
|
||||
id: 'season-1',
|
||||
leagueId: 'league-1',
|
||||
name: 'Season 1',
|
||||
startDate: new Date('2025-01-01'),
|
||||
endDate: new Date('2025-12-31'),
|
||||
});
|
||||
await seasonRepository.create(season1);
|
||||
|
||||
const season2 = Season.create({
|
||||
id: 'season-2',
|
||||
leagueId: 'league-2',
|
||||
name: 'Season 2',
|
||||
startDate: new Date('2025-01-01'),
|
||||
endDate: new Date('2025-12-31'),
|
||||
});
|
||||
await seasonRepository.create(season2);
|
||||
|
||||
const season3 = Season.create({
|
||||
id: 'season-3',
|
||||
leagueId: 'league-3',
|
||||
name: 'Season 3',
|
||||
startDate: new Date('2025-01-01'),
|
||||
endDate: new Date('2025-12-31'),
|
||||
});
|
||||
await seasonRepository.create(season3);
|
||||
|
||||
const sponsorship1 = SeasonSponsorship.create({
|
||||
id: 'sponsorship-1',
|
||||
sponsorId: 'sponsor-123',
|
||||
seasonId: 'season-1',
|
||||
tier: 'main',
|
||||
pricing: Money.create(1000, 'USD'),
|
||||
status: 'active',
|
||||
});
|
||||
await seasonSponsorshipRepository.create(sponsorship1);
|
||||
|
||||
const sponsorship2 = SeasonSponsorship.create({
|
||||
id: 'sponsorship-2',
|
||||
sponsorId: 'sponsor-123',
|
||||
seasonId: 'season-2',
|
||||
tier: 'secondary',
|
||||
pricing: Money.create(500, 'USD'),
|
||||
status: 'pending',
|
||||
});
|
||||
await seasonSponsorshipRepository.create(sponsorship2);
|
||||
|
||||
const sponsorship3 = SeasonSponsorship.create({
|
||||
id: 'sponsorship-3',
|
||||
sponsorId: 'sponsor-123',
|
||||
seasonId: 'season-3',
|
||||
tier: 'secondary',
|
||||
pricing: Money.create(300, 'USD'),
|
||||
status: 'completed',
|
||||
});
|
||||
await seasonSponsorshipRepository.create(sponsorship3);
|
||||
|
||||
// And: The sponsor has different numbers of drivers and races in each league
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
const membership = LeagueMembership.create({
|
||||
id: `membership-1-${i}`,
|
||||
leagueId: 'league-1',
|
||||
driverId: `driver-1-${i}`,
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
});
|
||||
await leagueMembershipRepository.saveMembership(membership);
|
||||
}
|
||||
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const membership = LeagueMembership.create({
|
||||
id: `membership-2-${i}`,
|
||||
leagueId: 'league-2',
|
||||
driverId: `driver-2-${i}`,
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
});
|
||||
await leagueMembershipRepository.saveMembership(membership);
|
||||
}
|
||||
|
||||
for (let i = 1; i <= 8; i++) {
|
||||
const membership = LeagueMembership.create({
|
||||
id: `membership-3-${i}`,
|
||||
leagueId: 'league-3',
|
||||
driverId: `driver-3-${i}`,
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
});
|
||||
await leagueMembershipRepository.saveMembership(membership);
|
||||
}
|
||||
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const race = Race.create({
|
||||
id: `race-1-${i}`,
|
||||
leagueId: 'league-1',
|
||||
track: 'Track 1',
|
||||
scheduledAt: new Date(`2025-0${i}-01`),
|
||||
status: 'completed',
|
||||
});
|
||||
await raceRepository.create(race);
|
||||
}
|
||||
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
const race = Race.create({
|
||||
id: `race-2-${i}`,
|
||||
leagueId: 'league-2',
|
||||
track: 'Track 2',
|
||||
scheduledAt: new Date(`2025-0${i}-01`),
|
||||
status: 'completed',
|
||||
});
|
||||
await raceRepository.create(race);
|
||||
}
|
||||
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
const race = Race.create({
|
||||
id: `race-3-${i}`,
|
||||
leagueId: 'league-3',
|
||||
track: 'Track 3',
|
||||
scheduledAt: new Date(`2025-0${i}-01`),
|
||||
status: 'completed',
|
||||
});
|
||||
await raceRepository.create(race);
|
||||
}
|
||||
|
||||
// When: GetSponsorSponsorshipsUseCase.execute() is called with sponsor ID
|
||||
const result = await getSponsorSponsorshipsUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
// Then: The result should contain sponsor sponsorships
|
||||
expect(result.isOk()).toBe(true);
|
||||
const sponsorships = result.unwrap();
|
||||
|
||||
// And: The sponsor name should be correct
|
||||
expect(sponsorships.sponsor.name.toString()).toBe('Test Company');
|
||||
|
||||
// And: The sponsorships should contain all 3 sponsorships
|
||||
expect(sponsorships.sponsorships).toHaveLength(3);
|
||||
|
||||
// And: The summary should show correct values
|
||||
expect(sponsorships.summary.totalSponsorships).toBe(3);
|
||||
expect(sponsorships.summary.activeSponsorships).toBe(1);
|
||||
expect(sponsorships.summary.totalInvestment.amount).toBe(1800); // 1000 + 500 + 300
|
||||
expect(sponsorships.summary.totalPlatformFees.amount).toBe(180); // 100 + 50 + 30
|
||||
|
||||
// And: Each sponsorship should have correct metrics
|
||||
const sponsorship1Summary = sponsorships.sponsorships.find(s => s.sponsorship.id === 'sponsorship-1');
|
||||
expect(sponsorship1Summary).toBeDefined();
|
||||
expect(sponsorship1Summary?.metrics.drivers).toBe(10);
|
||||
expect(sponsorship1Summary?.metrics.races).toBe(5);
|
||||
expect(sponsorship1Summary?.metrics.completedRaces).toBe(5);
|
||||
expect(sponsorship1Summary?.metrics.impressions).toBe(5000); // 5 * 10 * 100
|
||||
});
|
||||
|
||||
it('should retrieve leagues with minimal data', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Sponsor with minimal leagues
|
||||
it('should retrieve sponsorships with minimal data', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: There is 1 league available for sponsorship
|
||||
// When: GetAvailableLeaguesUseCase.execute() is called with sponsor ID
|
||||
// Then: The result should contain the single league
|
||||
// And: EventPublisher should emit AvailableLeaguesAccessedEvent
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await sponsorRepository.create(sponsor);
|
||||
|
||||
// And: The sponsor has 1 sponsorship
|
||||
const league = League.create({
|
||||
id: 'league-1',
|
||||
name: 'League 1',
|
||||
description: 'Description 1',
|
||||
ownerId: 'owner-1',
|
||||
});
|
||||
await leagueRepository.create(league);
|
||||
|
||||
const season = Season.create({
|
||||
id: 'season-1',
|
||||
leagueId: 'league-1',
|
||||
name: 'Season 1',
|
||||
startDate: new Date('2025-01-01'),
|
||||
endDate: new Date('2025-12-31'),
|
||||
});
|
||||
await seasonRepository.create(season);
|
||||
|
||||
const sponsorship = SeasonSponsorship.create({
|
||||
id: 'sponsorship-1',
|
||||
sponsorId: 'sponsor-123',
|
||||
seasonId: 'season-1',
|
||||
tier: 'main',
|
||||
pricing: Money.create(1000, 'USD'),
|
||||
status: 'active',
|
||||
});
|
||||
await seasonSponsorshipRepository.create(sponsorship);
|
||||
|
||||
// When: GetSponsorSponsorshipsUseCase.execute() is called with sponsor ID
|
||||
const result = await getSponsorSponsorshipsUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
// Then: The result should contain sponsor sponsorships
|
||||
expect(result.isOk()).toBe(true);
|
||||
const sponsorships = result.unwrap();
|
||||
|
||||
// And: The sponsorships should contain 1 sponsorship
|
||||
expect(sponsorships.sponsorships).toHaveLength(1);
|
||||
|
||||
// And: The summary should show correct values
|
||||
expect(sponsorships.summary.totalSponsorships).toBe(1);
|
||||
expect(sponsorships.summary.activeSponsorships).toBe(1);
|
||||
expect(sponsorships.summary.totalInvestment.amount).toBe(1000);
|
||||
expect(sponsorships.summary.totalPlatformFees.amount).toBe(100);
|
||||
});
|
||||
|
||||
it('should retrieve leagues with empty result', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Sponsor with no available leagues
|
||||
it('should retrieve sponsorships with empty result when no sponsorships exist', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: There are no leagues available for sponsorship
|
||||
// When: GetAvailableLeaguesUseCase.execute() is called with sponsor ID
|
||||
// Then: The result should be empty
|
||||
// And: EventPublisher should emit AvailableLeaguesAccessedEvent
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await sponsorRepository.create(sponsor);
|
||||
|
||||
// And: The sponsor has no sponsorships
|
||||
// When: GetSponsorSponsorshipsUseCase.execute() is called with sponsor ID
|
||||
const result = await getSponsorSponsorshipsUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
// Then: The result should contain sponsor sponsorships
|
||||
expect(result.isOk()).toBe(true);
|
||||
const sponsorships = result.unwrap();
|
||||
|
||||
// And: The sponsorships should be empty
|
||||
expect(sponsorships.sponsorships).toHaveLength(0);
|
||||
|
||||
// And: The summary should show zero values
|
||||
expect(sponsorships.summary.totalSponsorships).toBe(0);
|
||||
expect(sponsorships.summary.activeSponsorships).toBe(0);
|
||||
expect(sponsorships.summary.totalInvestment.amount).toBe(0);
|
||||
expect(sponsorships.summary.totalPlatformFees.amount).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetAvailableLeaguesUseCase - Error Handling', () => {
|
||||
it('should throw error when sponsor does not exist', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Non-existent sponsor
|
||||
describe('GetSponsorSponsorshipsUseCase - Error Handling', () => {
|
||||
it('should return error when sponsor does not exist', async () => {
|
||||
// Given: No sponsor exists with the given ID
|
||||
// When: GetAvailableLeaguesUseCase.execute() is called with non-existent sponsor ID
|
||||
// Then: Should throw SponsorNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
// When: GetSponsorSponsorshipsUseCase.execute() is called with non-existent sponsor ID
|
||||
const result = await getSponsorSponsorshipsUseCase.execute({ sponsorId: 'non-existent-sponsor' });
|
||||
|
||||
it('should throw error when sponsor ID is invalid', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Invalid sponsor ID
|
||||
// Given: An invalid sponsor ID (e.g., empty string, null, undefined)
|
||||
// When: GetAvailableLeaguesUseCase.execute() is called with invalid sponsor ID
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
// Then: Should return an error
|
||||
expect(result.isErr()).toBe(true);
|
||||
const error = result.unwrapErr();
|
||||
expect(error.code).toBe('SPONSOR_NOT_FOUND');
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetLeagueStatisticsUseCase - Success Path', () => {
|
||||
it('should retrieve league statistics', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Sponsor with league statistics
|
||||
describe('Sponsor Leagues Data Orchestration', () => {
|
||||
it('should correctly aggregate sponsorship metrics across multiple sponsorships', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: There are 10 leagues available
|
||||
// And: There are 3 main sponsor slots available
|
||||
// And: There are 15 secondary sponsor slots available
|
||||
// And: There are 500 total drivers
|
||||
// And: Average CPM is $50
|
||||
// When: GetLeagueStatisticsUseCase.execute() is called with sponsor ID
|
||||
// Then: The result should show total leagues count: 10
|
||||
// And: The result should show main sponsor slots available: 3
|
||||
// And: The result should show secondary sponsor slots available: 15
|
||||
// And: The result should show total drivers count: 500
|
||||
// And: The result should show average CPM: $50
|
||||
// And: EventPublisher should emit LeagueStatisticsAccessedEvent
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await sponsorRepository.create(sponsor);
|
||||
|
||||
// And: The sponsor has 3 sponsorships with different investments
|
||||
const league1 = League.create({
|
||||
id: 'league-1',
|
||||
name: 'League 1',
|
||||
description: 'Description 1',
|
||||
ownerId: 'owner-1',
|
||||
});
|
||||
await leagueRepository.create(league1);
|
||||
|
||||
const league2 = League.create({
|
||||
id: 'league-2',
|
||||
name: 'League 2',
|
||||
description: 'Description 2',
|
||||
ownerId: 'owner-2',
|
||||
});
|
||||
await leagueRepository.create(league2);
|
||||
|
||||
const league3 = League.create({
|
||||
id: 'league-3',
|
||||
name: 'League 3',
|
||||
description: 'Description 3',
|
||||
ownerId: 'owner-3',
|
||||
});
|
||||
await leagueRepository.create(league3);
|
||||
|
||||
const season1 = Season.create({
|
||||
id: 'season-1',
|
||||
leagueId: 'league-1',
|
||||
name: 'Season 1',
|
||||
startDate: new Date('2025-01-01'),
|
||||
endDate: new Date('2025-12-31'),
|
||||
});
|
||||
await seasonRepository.create(season1);
|
||||
|
||||
const season2 = Season.create({
|
||||
id: 'season-2',
|
||||
leagueId: 'league-2',
|
||||
name: 'Season 2',
|
||||
startDate: new Date('2025-01-01'),
|
||||
endDate: new Date('2025-12-31'),
|
||||
});
|
||||
await seasonRepository.create(season2);
|
||||
|
||||
const season3 = Season.create({
|
||||
id: 'season-3',
|
||||
leagueId: 'league-3',
|
||||
name: 'Season 3',
|
||||
startDate: new Date('2025-01-01'),
|
||||
endDate: new Date('2025-12-31'),
|
||||
});
|
||||
await seasonRepository.create(season3);
|
||||
|
||||
const sponsorship1 = SeasonSponsorship.create({
|
||||
id: 'sponsorship-1',
|
||||
sponsorId: 'sponsor-123',
|
||||
seasonId: 'season-1',
|
||||
tier: 'main',
|
||||
pricing: Money.create(1000, 'USD'),
|
||||
status: 'active',
|
||||
});
|
||||
await seasonSponsorshipRepository.create(sponsorship1);
|
||||
|
||||
const sponsorship2 = SeasonSponsorship.create({
|
||||
id: 'sponsorship-2',
|
||||
sponsorId: 'sponsor-123',
|
||||
seasonId: 'season-2',
|
||||
tier: 'secondary',
|
||||
pricing: Money.create(2000, 'USD'),
|
||||
status: 'active',
|
||||
});
|
||||
await seasonSponsorshipRepository.create(sponsorship2);
|
||||
|
||||
const sponsorship3 = SeasonSponsorship.create({
|
||||
id: 'sponsorship-3',
|
||||
sponsorId: 'sponsor-123',
|
||||
seasonId: 'season-3',
|
||||
tier: 'secondary',
|
||||
pricing: Money.create(3000, 'USD'),
|
||||
status: 'active',
|
||||
});
|
||||
await seasonSponsorshipRepository.create(sponsorship3);
|
||||
|
||||
// And: The sponsor has different numbers of drivers and races in each league
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
const membership = LeagueMembership.create({
|
||||
id: `membership-1-${i}`,
|
||||
leagueId: 'league-1',
|
||||
driverId: `driver-1-${i}`,
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
});
|
||||
await leagueMembershipRepository.saveMembership(membership);
|
||||
}
|
||||
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const membership = LeagueMembership.create({
|
||||
id: `membership-2-${i}`,
|
||||
leagueId: 'league-2',
|
||||
driverId: `driver-2-${i}`,
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
});
|
||||
await leagueMembershipRepository.saveMembership(membership);
|
||||
}
|
||||
|
||||
for (let i = 1; i <= 8; i++) {
|
||||
const membership = LeagueMembership.create({
|
||||
id: `membership-3-${i}`,
|
||||
leagueId: 'league-3',
|
||||
driverId: `driver-3-${i}`,
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
});
|
||||
await leagueMembershipRepository.saveMembership(membership);
|
||||
}
|
||||
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const race = Race.create({
|
||||
id: `race-1-${i}`,
|
||||
leagueId: 'league-1',
|
||||
track: 'Track 1',
|
||||
scheduledAt: new Date(`2025-0${i}-01`),
|
||||
status: 'completed',
|
||||
});
|
||||
await raceRepository.create(race);
|
||||
}
|
||||
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
const race = Race.create({
|
||||
id: `race-2-${i}`,
|
||||
leagueId: 'league-2',
|
||||
track: 'Track 2',
|
||||
scheduledAt: new Date(`2025-0${i}-01`),
|
||||
status: 'completed',
|
||||
});
|
||||
await raceRepository.create(race);
|
||||
}
|
||||
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
const race = Race.create({
|
||||
id: `race-3-${i}`,
|
||||
leagueId: 'league-3',
|
||||
track: 'Track 3',
|
||||
scheduledAt: new Date(`2025-0${i}-01`),
|
||||
status: 'completed',
|
||||
});
|
||||
await raceRepository.create(race);
|
||||
}
|
||||
|
||||
// When: GetSponsorSponsorshipsUseCase.execute() is called
|
||||
const result = await getSponsorSponsorshipsUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
// Then: The metrics should be correctly aggregated
|
||||
expect(result.isOk()).toBe(true);
|
||||
const sponsorships = result.unwrap();
|
||||
|
||||
// Total drivers: 10 + 5 + 8 = 23
|
||||
expect(sponsorships.sponsorships[0].metrics.drivers).toBe(10);
|
||||
expect(sponsorships.sponsorships[1].metrics.drivers).toBe(5);
|
||||
expect(sponsorships.sponsorships[2].metrics.drivers).toBe(8);
|
||||
|
||||
// Total races: 5 + 3 + 4 = 12
|
||||
expect(sponsorships.sponsorships[0].metrics.races).toBe(5);
|
||||
expect(sponsorships.sponsorships[1].metrics.races).toBe(3);
|
||||
expect(sponsorships.sponsorships[2].metrics.races).toBe(4);
|
||||
|
||||
// Total investment: 1000 + 2000 + 3000 = 6000
|
||||
expect(sponsorships.summary.totalInvestment.amount).toBe(6000);
|
||||
|
||||
// Total platform fees: 100 + 200 + 300 = 600
|
||||
expect(sponsorships.summary.totalPlatformFees.amount).toBe(600);
|
||||
});
|
||||
|
||||
it('should retrieve statistics with zero values', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Sponsor with no leagues
|
||||
it('should correctly calculate impressions based on completed races and drivers', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: There are no leagues available
|
||||
// When: GetLeagueStatisticsUseCase.execute() is called with sponsor ID
|
||||
// Then: The result should show all counts as 0
|
||||
// And: The result should show average CPM as 0
|
||||
// And: EventPublisher should emit LeagueStatisticsAccessedEvent
|
||||
});
|
||||
});
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await sponsorRepository.create(sponsor);
|
||||
|
||||
describe('GetLeagueStatisticsUseCase - Error Handling', () => {
|
||||
it('should throw error when sponsor does not exist', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Non-existent sponsor
|
||||
// Given: No sponsor exists with the given ID
|
||||
// When: GetLeagueStatisticsUseCase.execute() is called with non-existent sponsor ID
|
||||
// Then: Should throw SponsorNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
// And: The sponsor has 1 league with 10 drivers and 5 completed races
|
||||
const league = League.create({
|
||||
id: 'league-1',
|
||||
name: 'League 1',
|
||||
description: 'Description 1',
|
||||
ownerId: 'owner-1',
|
||||
});
|
||||
await leagueRepository.create(league);
|
||||
|
||||
describe('FilterLeaguesUseCase - Success Path', () => {
|
||||
it('should filter leagues by "All" availability', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Filter by All
|
||||
const season = Season.create({
|
||||
id: 'season-1',
|
||||
leagueId: 'league-1',
|
||||
name: 'Season 1',
|
||||
startDate: new Date('2025-01-01'),
|
||||
endDate: new Date('2025-12-31'),
|
||||
});
|
||||
await seasonRepository.create(season);
|
||||
|
||||
const sponsorship = SeasonSponsorship.create({
|
||||
id: 'sponsorship-1',
|
||||
sponsorId: 'sponsor-123',
|
||||
seasonId: 'season-1',
|
||||
tier: 'main',
|
||||
pricing: Money.create(1000, 'USD'),
|
||||
status: 'active',
|
||||
});
|
||||
await seasonSponsorshipRepository.create(sponsorship);
|
||||
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
const membership = LeagueMembership.create({
|
||||
id: `membership-${i}`,
|
||||
leagueId: 'league-1',
|
||||
driverId: `driver-${i}`,
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
});
|
||||
await leagueMembershipRepository.saveMembership(membership);
|
||||
}
|
||||
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const race = Race.create({
|
||||
id: `race-${i}`,
|
||||
leagueId: 'league-1',
|
||||
track: 'Track 1',
|
||||
scheduledAt: new Date(`2025-0${i}-01`),
|
||||
status: 'completed',
|
||||
});
|
||||
await raceRepository.create(race);
|
||||
}
|
||||
|
||||
// When: GetSponsorSponsorshipsUseCase.execute() is called
|
||||
const result = await getSponsorSponsorshipsUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
// Then: Impressions should be calculated correctly
|
||||
// Impressions = completed races * drivers * 100 = 5 * 10 * 100 = 5000
|
||||
expect(result.isOk()).toBe(true);
|
||||
const sponsorships = result.unwrap();
|
||||
expect(sponsorships.sponsorships[0].metrics.impressions).toBe(5000);
|
||||
});
|
||||
|
||||
it('should correctly calculate platform fees and net amounts', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: There are 5 leagues (3 with main slot available, 2 with secondary slots available)
|
||||
// When: FilterLeaguesUseCase.execute() is called with availability "All"
|
||||
// Then: The result should contain all 5 leagues
|
||||
// And: EventPublisher should emit LeaguesFilteredEvent
|
||||
});
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await sponsorRepository.create(sponsor);
|
||||
|
||||
it('should filter leagues by "Main Slot Available" availability', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Filter by Main Slot Available
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: There are 5 leagues (3 with main slot available, 2 with secondary slots available)
|
||||
// When: FilterLeaguesUseCase.execute() is called with availability "Main Slot Available"
|
||||
// Then: The result should contain only 3 leagues with main slot available
|
||||
// And: EventPublisher should emit LeaguesFilteredEvent
|
||||
});
|
||||
// And: The sponsor has 1 sponsorship
|
||||
const league = League.create({
|
||||
id: 'league-1',
|
||||
name: 'League 1',
|
||||
description: 'Description 1',
|
||||
ownerId: 'owner-1',
|
||||
});
|
||||
await leagueRepository.create(league);
|
||||
|
||||
it('should filter leagues by "Secondary Slot Available" availability', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Filter by Secondary Slot Available
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: There are 5 leagues (3 with main slot available, 2 with secondary slots available)
|
||||
// When: FilterLeaguesUseCase.execute() is called with availability "Secondary Slot Available"
|
||||
// Then: The result should contain only 2 leagues with secondary slots available
|
||||
// And: EventPublisher should emit LeaguesFilteredEvent
|
||||
});
|
||||
const season = Season.create({
|
||||
id: 'season-1',
|
||||
leagueId: 'league-1',
|
||||
name: 'Season 1',
|
||||
startDate: new Date('2025-01-01'),
|
||||
endDate: new Date('2025-12-31'),
|
||||
});
|
||||
await seasonRepository.create(season);
|
||||
|
||||
it('should return empty result when no leagues match filter', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Filter with no matches
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: There are 2 leagues with main slot available
|
||||
// When: FilterLeaguesUseCase.execute() is called with availability "Secondary Slot Available"
|
||||
// Then: The result should be empty
|
||||
// And: EventPublisher should emit LeaguesFilteredEvent
|
||||
});
|
||||
});
|
||||
const sponsorship = SeasonSponsorship.create({
|
||||
id: 'sponsorship-1',
|
||||
sponsorId: 'sponsor-123',
|
||||
seasonId: 'season-1',
|
||||
tier: 'main',
|
||||
pricing: Money.create(1000, 'USD'),
|
||||
status: 'active',
|
||||
});
|
||||
await seasonSponsorshipRepository.create(sponsorship);
|
||||
|
||||
describe('FilterLeaguesUseCase - Error Handling', () => {
|
||||
it('should throw error when sponsor does not exist', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Non-existent sponsor
|
||||
// Given: No sponsor exists with the given ID
|
||||
// When: FilterLeaguesUseCase.execute() is called with non-existent sponsor ID
|
||||
// Then: Should throw SponsorNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
// When: GetSponsorSponsorshipsUseCase.execute() is called
|
||||
const result = await getSponsorSponsorshipsUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
it('should throw error with invalid availability', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Invalid availability
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// When: FilterLeaguesUseCase.execute() is called with invalid availability
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
// Then: Platform fees and net amounts should be calculated correctly
|
||||
expect(result.isOk()).toBe(true);
|
||||
const sponsorships = result.unwrap();
|
||||
|
||||
describe('SearchLeaguesUseCase - Success Path', () => {
|
||||
it('should search leagues by league name', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Search by league name
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: There are leagues named: "Premier League", "League A", "League B"
|
||||
// When: SearchLeaguesUseCase.execute() is called with query "Premier League"
|
||||
// Then: The result should contain only "Premier League"
|
||||
// And: EventPublisher should emit LeaguesSearchedEvent
|
||||
});
|
||||
// Platform fee = 10% of pricing = 100
|
||||
expect(sponsorships.sponsorships[0].financials.platformFee.amount).toBe(100);
|
||||
|
||||
it('should search leagues by partial match', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Search by partial match
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: There are leagues named: "Premier League", "League A", "League B"
|
||||
// When: SearchLeaguesUseCase.execute() is called with query "League"
|
||||
// Then: The result should contain all three leagues
|
||||
// And: EventPublisher should emit LeaguesSearchedEvent
|
||||
});
|
||||
|
||||
it('should return empty result when no leagues match search', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Search with no matches
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: There are leagues named: "League A", "League B"
|
||||
// When: SearchLeaguesUseCase.execute() is called with query "NonExistent"
|
||||
// Then: The result should be empty
|
||||
// And: EventPublisher should emit LeaguesSearchedEvent
|
||||
});
|
||||
|
||||
it('should return all leagues when search query is empty', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Search with empty query
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: There are 3 leagues available
|
||||
// When: SearchLeaguesUseCase.execute() is called with empty query
|
||||
// Then: The result should contain all 3 leagues
|
||||
// And: EventPublisher should emit LeaguesSearchedEvent
|
||||
});
|
||||
});
|
||||
|
||||
describe('SearchLeaguesUseCase - Error Handling', () => {
|
||||
it('should throw error when sponsor does not exist', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Non-existent sponsor
|
||||
// Given: No sponsor exists with the given ID
|
||||
// When: SearchLeaguesUseCase.execute() is called with non-existent sponsor ID
|
||||
// Then: Should throw SponsorNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should throw error with invalid query', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Invalid query
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// When: SearchLeaguesUseCase.execute() is called with invalid query (e.g., null, undefined)
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
|
||||
describe('League Data Orchestration', () => {
|
||||
it('should correctly aggregate league statistics', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: League statistics aggregation
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: There are 5 leagues with different slot availability
|
||||
// And: There are 3 main sponsor slots available
|
||||
// And: There are 15 secondary sponsor slots available
|
||||
// And: There are 500 total drivers
|
||||
// And: Average CPM is $50
|
||||
// When: GetLeagueStatisticsUseCase.execute() is called
|
||||
// Then: Total leagues should be 5
|
||||
// And: Main sponsor slots available should be 3
|
||||
// And: Secondary sponsor slots available should be 15
|
||||
// And: Total drivers count should be 500
|
||||
// And: Average CPM should be $50
|
||||
// And: EventPublisher should emit LeagueStatisticsAccessedEvent
|
||||
});
|
||||
|
||||
it('should correctly filter leagues by availability', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: League availability filtering
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: There are leagues with different slot availability
|
||||
// When: FilterLeaguesUseCase.execute() is called with "Main Slot Available"
|
||||
// Then: Only leagues with main slot available should be returned
|
||||
// And: Each league should have correct availability
|
||||
// And: EventPublisher should emit LeaguesFilteredEvent
|
||||
});
|
||||
|
||||
it('should correctly search leagues by name', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: League name search
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: There are leagues with different names
|
||||
// When: SearchLeaguesUseCase.execute() is called with league name
|
||||
// Then: Only leagues with matching names should be returned
|
||||
// And: Each league should have correct name
|
||||
// And: EventPublisher should emit LeaguesSearchedEvent
|
||||
// Net amount = pricing - platform fee = 1000 - 100 = 900
|
||||
expect(sponsorships.sponsorships[0].financials.netAmount.amount).toBe(900);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user