integration tests
Some checks failed
CI / lint-typecheck (pull_request) Failing after 4m50s
CI / tests (pull_request) Has been skipped
CI / contract-tests (pull_request) Has been skipped
CI / e2e-tests (pull_request) Has been skipped
CI / comment-pr (pull_request) Has been skipped
CI / commit-types (pull_request) Has been skipped
Some checks failed
CI / lint-typecheck (pull_request) Failing after 4m50s
CI / tests (pull_request) Has been skipped
CI / contract-tests (pull_request) Has been skipped
CI / e2e-tests (pull_request) Has been skipped
CI / comment-pr (pull_request) Has been skipped
CI / commit-types (pull_request) Has been skipped
This commit is contained in:
54
tests/integration/sponsor/SponsorTestContext.ts
Normal file
54
tests/integration/sponsor/SponsorTestContext.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Logger } from '../../../core/shared/domain/Logger';
|
||||
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 { InMemoryPaymentRepository } from '../../../adapters/payments/persistence/inmemory/InMemoryPaymentRepository';
|
||||
import { InMemorySponsorshipPricingRepository } from '../../../adapters/racing/persistence/inmemory/InMemorySponsorshipPricingRepository';
|
||||
import { InMemoryEventPublisher } from '../../../adapters/events/InMemoryEventPublisher';
|
||||
|
||||
export class SponsorTestContext {
|
||||
public readonly logger: Logger;
|
||||
public readonly sponsorRepository: InMemorySponsorRepository;
|
||||
public readonly seasonSponsorshipRepository: InMemorySeasonSponsorshipRepository;
|
||||
public readonly seasonRepository: InMemorySeasonRepository;
|
||||
public readonly leagueRepository: InMemoryLeagueRepository;
|
||||
public readonly leagueMembershipRepository: InMemoryLeagueMembershipRepository;
|
||||
public readonly raceRepository: InMemoryRaceRepository;
|
||||
public readonly paymentRepository: InMemoryPaymentRepository;
|
||||
public readonly sponsorshipPricingRepository: InMemorySponsorshipPricingRepository;
|
||||
public readonly eventPublisher: InMemoryEventPublisher;
|
||||
|
||||
constructor() {
|
||||
this.logger = {
|
||||
info: () => {},
|
||||
debug: () => {},
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
} as unknown as Logger;
|
||||
|
||||
this.sponsorRepository = new InMemorySponsorRepository(this.logger);
|
||||
this.seasonSponsorshipRepository = new InMemorySeasonSponsorshipRepository(this.logger);
|
||||
this.seasonRepository = new InMemorySeasonRepository(this.logger);
|
||||
this.leagueRepository = new InMemoryLeagueRepository(this.logger);
|
||||
this.leagueMembershipRepository = new InMemoryLeagueMembershipRepository(this.logger);
|
||||
this.raceRepository = new InMemoryRaceRepository(this.logger);
|
||||
this.paymentRepository = new InMemoryPaymentRepository(this.logger);
|
||||
this.sponsorshipPricingRepository = new InMemorySponsorshipPricingRepository(this.logger);
|
||||
this.eventPublisher = new InMemoryEventPublisher();
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this.sponsorRepository.clear();
|
||||
this.seasonSponsorshipRepository.clear();
|
||||
this.seasonRepository.clear();
|
||||
this.leagueRepository.clear();
|
||||
this.leagueMembershipRepository.clear();
|
||||
this.raceRepository.clear();
|
||||
this.paymentRepository.clear();
|
||||
this.sponsorshipPricingRepository.clear();
|
||||
this.eventPublisher.clear();
|
||||
}
|
||||
}
|
||||
181
tests/integration/sponsor/billing/sponsor-billing.test.ts
Normal file
181
tests/integration/sponsor/billing/sponsor-billing.test.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { GetSponsorBillingUseCase } from '../../../../core/payments/application/use-cases/GetSponsorBillingUseCase';
|
||||
import { Sponsor } from '../../../../core/racing/domain/entities/sponsor/Sponsor';
|
||||
import { SeasonSponsorship } from '../../../../core/racing/domain/entities/season/SeasonSponsorship';
|
||||
import { Payment, PaymentType, PaymentStatus } from '../../../../core/payments/domain/entities/Payment';
|
||||
import { Money } from '../../../../core/racing/domain/value-objects/Money';
|
||||
import { SponsorTestContext } from '../SponsorTestContext';
|
||||
|
||||
describe('Sponsor Billing Use Case Orchestration', () => {
|
||||
let context: SponsorTestContext;
|
||||
let getSponsorBillingUseCase: GetSponsorBillingUseCase;
|
||||
|
||||
beforeEach(() => {
|
||||
context = new SponsorTestContext();
|
||||
getSponsorBillingUseCase = new GetSponsorBillingUseCase(
|
||||
context.paymentRepository,
|
||||
context.seasonSponsorshipRepository,
|
||||
context.sponsorRepository,
|
||||
);
|
||||
});
|
||||
|
||||
describe('GetSponsorBillingUseCase - Success Path', () => {
|
||||
it('should retrieve billing statistics for a sponsor with paid invoices', async () => {
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await context.sponsorRepository.create(sponsor);
|
||||
|
||||
const sponsorship1 = SeasonSponsorship.create({
|
||||
id: 'sponsorship-1',
|
||||
sponsorId: 'sponsor-123',
|
||||
seasonId: 'season-1',
|
||||
tier: 'main',
|
||||
pricing: Money.create(1000, 'USD'),
|
||||
status: 'active',
|
||||
});
|
||||
await context.seasonSponsorshipRepository.create(sponsorship1);
|
||||
|
||||
const sponsorship2 = SeasonSponsorship.create({
|
||||
id: 'sponsorship-2',
|
||||
sponsorId: 'sponsor-123',
|
||||
seasonId: 'season-2',
|
||||
tier: 'secondary',
|
||||
pricing: Money.create(500, 'USD'),
|
||||
status: 'active',
|
||||
});
|
||||
await context.seasonSponsorshipRepository.create(sponsorship2);
|
||||
|
||||
const payment1: Payment = {
|
||||
id: 'payment-1',
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: 1000,
|
||||
platformFee: 100,
|
||||
netAmount: 900,
|
||||
payerId: 'sponsor-123',
|
||||
payerType: 'sponsor',
|
||||
leagueId: 'league-1',
|
||||
seasonId: 'season-1',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2025-01-15'),
|
||||
completedAt: new Date('2025-01-15'),
|
||||
};
|
||||
await context.paymentRepository.create(payment1);
|
||||
|
||||
const payment2: Payment = {
|
||||
id: 'payment-2',
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: 2000,
|
||||
platformFee: 200,
|
||||
netAmount: 1800,
|
||||
payerId: 'sponsor-123',
|
||||
payerType: 'sponsor',
|
||||
leagueId: 'league-2',
|
||||
seasonId: 'season-2',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2025-02-15'),
|
||||
completedAt: new Date('2025-02-15'),
|
||||
};
|
||||
await context.paymentRepository.create(payment2);
|
||||
|
||||
const payment3: Payment = {
|
||||
id: 'payment-3',
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: 3000,
|
||||
platformFee: 300,
|
||||
netAmount: 2700,
|
||||
payerId: 'sponsor-123',
|
||||
payerType: 'sponsor',
|
||||
leagueId: 'league-3',
|
||||
seasonId: 'season-3',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2025-03-15'),
|
||||
completedAt: new Date('2025-03-15'),
|
||||
};
|
||||
await context.paymentRepository.create(payment3);
|
||||
|
||||
const result = await getSponsorBillingUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
const billing = result.unwrap();
|
||||
|
||||
expect(billing.invoices).toHaveLength(3);
|
||||
// Total spent = (1000 + 190) + (2000 + 380) + (3000 + 570) = 1190 + 2380 + 3570 = 7140
|
||||
expect(billing.stats.totalSpent).toBe(7140);
|
||||
expect(billing.stats.pendingAmount).toBe(0);
|
||||
expect(billing.stats.activeSponsorships).toBe(2);
|
||||
});
|
||||
|
||||
it('should retrieve billing statistics with pending invoices', async () => {
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await context.sponsorRepository.create(sponsor);
|
||||
|
||||
const sponsorship = SeasonSponsorship.create({
|
||||
id: 'sponsorship-1',
|
||||
sponsorId: 'sponsor-123',
|
||||
seasonId: 'season-1',
|
||||
tier: 'main',
|
||||
pricing: Money.create(1000, 'USD'),
|
||||
status: 'active',
|
||||
});
|
||||
await context.seasonSponsorshipRepository.create(sponsorship);
|
||||
|
||||
const payment1: Payment = {
|
||||
id: 'payment-1',
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: 1000,
|
||||
platformFee: 100,
|
||||
netAmount: 900,
|
||||
payerId: 'sponsor-123',
|
||||
payerType: 'sponsor',
|
||||
leagueId: 'league-1',
|
||||
seasonId: 'season-1',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2025-01-15'),
|
||||
completedAt: new Date('2025-01-15'),
|
||||
};
|
||||
await context.paymentRepository.create(payment1);
|
||||
|
||||
const payment2: Payment = {
|
||||
id: 'payment-2',
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: 500,
|
||||
platformFee: 50,
|
||||
netAmount: 450,
|
||||
payerId: 'sponsor-123',
|
||||
payerType: 'sponsor',
|
||||
leagueId: 'league-2',
|
||||
seasonId: 'season-2',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2025-02-15'),
|
||||
};
|
||||
await context.paymentRepository.create(payment2);
|
||||
|
||||
const result = await getSponsorBillingUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
const billing = result.unwrap();
|
||||
|
||||
expect(billing.invoices).toHaveLength(2);
|
||||
expect(billing.stats.totalSpent).toBe(1190);
|
||||
expect(billing.stats.pendingAmount).toBe(595);
|
||||
expect(billing.stats.nextPaymentAmount).toBe(595);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetSponsorBillingUseCase - Error Handling', () => {
|
||||
it('should return error when sponsor does not exist', async () => {
|
||||
const result = await getSponsorBillingUseCase.execute({ sponsorId: 'non-existent-sponsor' });
|
||||
|
||||
expect(result.isErr()).toBe(true);
|
||||
const error = result.unwrapErr();
|
||||
expect(error.code).toBe('SPONSOR_NOT_FOUND');
|
||||
});
|
||||
});
|
||||
});
|
||||
131
tests/integration/sponsor/campaigns/sponsor-campaigns.test.ts
Normal file
131
tests/integration/sponsor/campaigns/sponsor-campaigns.test.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
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 { SponsorTestContext } from '../SponsorTestContext';
|
||||
|
||||
describe('Sponsor Campaigns Use Case Orchestration', () => {
|
||||
let context: SponsorTestContext;
|
||||
let getSponsorSponsorshipsUseCase: GetSponsorSponsorshipsUseCase;
|
||||
|
||||
beforeEach(() => {
|
||||
context = new SponsorTestContext();
|
||||
getSponsorSponsorshipsUseCase = new GetSponsorSponsorshipsUseCase(
|
||||
context.sponsorRepository,
|
||||
context.seasonSponsorshipRepository,
|
||||
context.seasonRepository,
|
||||
context.leagueRepository,
|
||||
context.leagueMembershipRepository,
|
||||
context.raceRepository,
|
||||
);
|
||||
});
|
||||
|
||||
describe('GetSponsorSponsorshipsUseCase - Success Path', () => {
|
||||
it('should retrieve all sponsorships for a sponsor', async () => {
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await context.sponsorRepository.create(sponsor);
|
||||
|
||||
const league1 = League.create({
|
||||
id: 'league-1',
|
||||
name: 'League 1',
|
||||
description: 'Description 1',
|
||||
ownerId: 'owner-1',
|
||||
});
|
||||
await context.leagueRepository.create(league1);
|
||||
|
||||
const season1 = Season.create({
|
||||
id: 'season-1',
|
||||
leagueId: 'league-1',
|
||||
gameId: 'game-1',
|
||||
name: 'Season 1',
|
||||
startDate: new Date('2025-01-01'),
|
||||
endDate: new Date('2025-12-31'),
|
||||
});
|
||||
await context.seasonRepository.create(season1);
|
||||
|
||||
const sponsorship1 = SeasonSponsorship.create({
|
||||
id: 'sponsorship-1',
|
||||
sponsorId: 'sponsor-123',
|
||||
seasonId: 'season-1',
|
||||
tier: 'main',
|
||||
pricing: Money.create(1000, 'USD'),
|
||||
status: 'active',
|
||||
});
|
||||
await context.seasonSponsorshipRepository.create(sponsorship1);
|
||||
|
||||
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 context.leagueMembershipRepository.saveMembership(membership);
|
||||
}
|
||||
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const race = Race.create({
|
||||
id: `race-1-${i}`,
|
||||
leagueId: 'league-1',
|
||||
track: 'Track 1',
|
||||
car: 'GT3',
|
||||
scheduledAt: new Date(`2025-0${i}-01`),
|
||||
status: 'completed',
|
||||
});
|
||||
await context.raceRepository.create(race);
|
||||
}
|
||||
|
||||
const result = await getSponsorSponsorshipsUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
const sponsorships = result.unwrap();
|
||||
|
||||
expect(sponsorships.sponsor.name.toString()).toBe('Test Company');
|
||||
expect(sponsorships.sponsorships).toHaveLength(1);
|
||||
expect(sponsorships.summary.totalSponsorships).toBe(1);
|
||||
expect(sponsorships.summary.activeSponsorships).toBe(1);
|
||||
expect(sponsorships.summary.totalInvestment.amount).toBe(1000);
|
||||
|
||||
const s1 = sponsorships.sponsorships[0];
|
||||
expect(s1.metrics.drivers).toBe(10);
|
||||
expect(s1.metrics.races).toBe(5);
|
||||
expect(s1.metrics.impressions).toBe(5000);
|
||||
});
|
||||
|
||||
it('should retrieve sponsorships with empty result when no sponsorships exist', async () => {
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await context.sponsorRepository.create(sponsor);
|
||||
|
||||
const result = await getSponsorSponsorshipsUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
const sponsorships = result.unwrap();
|
||||
expect(sponsorships.sponsorships).toHaveLength(0);
|
||||
expect(sponsorships.summary.totalSponsorships).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetSponsorSponsorshipsUseCase - Error Handling', () => {
|
||||
it('should return error when sponsor does not exist', async () => {
|
||||
const result = await getSponsorSponsorshipsUseCase.execute({ sponsorId: 'non-existent-sponsor' });
|
||||
|
||||
expect(result.isErr()).toBe(true);
|
||||
const error = result.unwrapErr();
|
||||
expect(error.code).toBe('SPONSOR_NOT_FOUND');
|
||||
});
|
||||
});
|
||||
});
|
||||
127
tests/integration/sponsor/dashboard/sponsor-dashboard.test.ts
Normal file
127
tests/integration/sponsor/dashboard/sponsor-dashboard.test.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { GetSponsorDashboardUseCase } from '../../../../core/racing/application/use-cases/GetSponsorDashboardUseCase';
|
||||
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 { SponsorTestContext } from '../SponsorTestContext';
|
||||
|
||||
describe('Sponsor Dashboard Use Case Orchestration', () => {
|
||||
let context: SponsorTestContext;
|
||||
let getSponsorDashboardUseCase: GetSponsorDashboardUseCase;
|
||||
|
||||
beforeEach(() => {
|
||||
context = new SponsorTestContext();
|
||||
getSponsorDashboardUseCase = new GetSponsorDashboardUseCase(
|
||||
context.sponsorRepository,
|
||||
context.seasonSponsorshipRepository,
|
||||
context.seasonRepository,
|
||||
context.leagueRepository,
|
||||
context.leagueMembershipRepository,
|
||||
context.raceRepository,
|
||||
);
|
||||
});
|
||||
|
||||
describe('GetSponsorDashboardUseCase - Success Path', () => {
|
||||
it('should retrieve dashboard metrics for a sponsor with active sponsorships', async () => {
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await context.sponsorRepository.create(sponsor);
|
||||
|
||||
const league1 = League.create({
|
||||
id: 'league-1',
|
||||
name: 'League 1',
|
||||
description: 'Description 1',
|
||||
ownerId: 'owner-1',
|
||||
});
|
||||
await context.leagueRepository.create(league1);
|
||||
|
||||
const season1 = Season.create({
|
||||
id: 'season-1',
|
||||
leagueId: 'league-1',
|
||||
gameId: 'game-1',
|
||||
name: 'Season 1',
|
||||
startDate: new Date('2025-01-01'),
|
||||
endDate: new Date('2025-12-31'),
|
||||
});
|
||||
await context.seasonRepository.create(season1);
|
||||
|
||||
const sponsorship1 = SeasonSponsorship.create({
|
||||
id: 'sponsorship-1',
|
||||
sponsorId: 'sponsor-123',
|
||||
seasonId: 'season-1',
|
||||
tier: 'main',
|
||||
pricing: Money.create(1000, 'USD'),
|
||||
status: 'active',
|
||||
});
|
||||
await context.seasonSponsorshipRepository.create(sponsorship1);
|
||||
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const membership = LeagueMembership.create({
|
||||
id: `membership-1-${i}`,
|
||||
leagueId: 'league-1',
|
||||
driverId: `driver-1-${i}`,
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
});
|
||||
await context.leagueMembershipRepository.saveMembership(membership);
|
||||
}
|
||||
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
const race = Race.create({
|
||||
id: `race-1-${i}`,
|
||||
leagueId: 'league-1',
|
||||
track: 'Track 1',
|
||||
car: 'GT3',
|
||||
scheduledAt: new Date(`2025-0${i}-01`),
|
||||
status: 'completed',
|
||||
});
|
||||
await context.raceRepository.create(race);
|
||||
}
|
||||
|
||||
const result = await getSponsorDashboardUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
const dashboard = result.unwrap();
|
||||
|
||||
expect(dashboard.sponsorName).toBe('Test Company');
|
||||
expect(dashboard.metrics.races).toBe(3);
|
||||
expect(dashboard.metrics.drivers).toBe(5);
|
||||
expect(dashboard.sponsoredLeagues).toHaveLength(1);
|
||||
expect(dashboard.investment.activeSponsorships).toBe(1);
|
||||
expect(dashboard.investment.totalInvestment.amount).toBe(1000);
|
||||
});
|
||||
|
||||
it('should retrieve dashboard with zero values when sponsor has no sponsorships', async () => {
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await context.sponsorRepository.create(sponsor);
|
||||
|
||||
const result = await getSponsorDashboardUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
const dashboard = result.unwrap();
|
||||
expect(dashboard.metrics.impressions).toBe(0);
|
||||
expect(dashboard.sponsoredLeagues).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetSponsorDashboardUseCase - Error Handling', () => {
|
||||
it('should return error when sponsor does not exist', async () => {
|
||||
const result = await getSponsorDashboardUseCase.execute({ sponsorId: 'non-existent-sponsor' });
|
||||
|
||||
expect(result.isErr()).toBe(true);
|
||||
const error = result.unwrapErr();
|
||||
expect(error.code).toBe('SPONSOR_NOT_FOUND');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { GetEntitySponsorshipPricingUseCase } from '../../../../core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase';
|
||||
import { SponsorTestContext } from '../SponsorTestContext';
|
||||
|
||||
describe('Sponsor League Detail Use Case Orchestration', () => {
|
||||
let context: SponsorTestContext;
|
||||
let getEntitySponsorshipPricingUseCase: GetEntitySponsorshipPricingUseCase;
|
||||
|
||||
beforeEach(() => {
|
||||
context = new SponsorTestContext();
|
||||
getEntitySponsorshipPricingUseCase = new GetEntitySponsorshipPricingUseCase(
|
||||
context.sponsorshipPricingRepository,
|
||||
context.logger,
|
||||
);
|
||||
});
|
||||
|
||||
describe('GetEntitySponsorshipPricingUseCase - Success Path', () => {
|
||||
it('should retrieve sponsorship pricing for a league', async () => {
|
||||
const leagueId = 'league-123';
|
||||
const pricing = {
|
||||
entityType: 'league' as const,
|
||||
entityId: leagueId,
|
||||
acceptingApplications: true,
|
||||
mainSlot: {
|
||||
price: { amount: 10000, currency: 'USD' },
|
||||
benefits: ['Primary logo placement', 'League page header banner'],
|
||||
},
|
||||
secondarySlots: {
|
||||
price: { amount: 2000, currency: 'USD' },
|
||||
benefits: ['Secondary logo on liveries', 'League page sidebar placement'],
|
||||
},
|
||||
};
|
||||
await context.sponsorshipPricingRepository.create(pricing);
|
||||
|
||||
const result = await getEntitySponsorshipPricingUseCase.execute({
|
||||
entityType: 'league',
|
||||
entityId: leagueId,
|
||||
});
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
const pricingResult = result.unwrap();
|
||||
|
||||
expect(pricingResult.entityType).toBe('league');
|
||||
expect(pricingResult.entityId).toBe(leagueId);
|
||||
expect(pricingResult.acceptingApplications).toBe(true);
|
||||
expect(pricingResult.tiers).toHaveLength(2);
|
||||
expect(pricingResult.tiers[0].name).toBe('main');
|
||||
expect(pricingResult.tiers[0].price.amount).toBe(10000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetEntitySponsorshipPricingUseCase - Error Handling', () => {
|
||||
it('should return error when pricing is not configured', async () => {
|
||||
const result = await getEntitySponsorshipPricingUseCase.execute({
|
||||
entityType: 'league',
|
||||
entityId: 'non-existent',
|
||||
});
|
||||
|
||||
expect(result.isErr()).toBe(true);
|
||||
const error = result.unwrapErr();
|
||||
expect(error.code).toBe('PRICING_NOT_CONFIGURED');
|
||||
});
|
||||
});
|
||||
});
|
||||
42
tests/integration/sponsor/settings/sponsor-settings.test.ts
Normal file
42
tests/integration/sponsor/settings/sponsor-settings.test.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { Sponsor } from '../../../../core/racing/domain/entities/sponsor/Sponsor';
|
||||
import { SponsorTestContext } from '../SponsorTestContext';
|
||||
import { GetSponsorUseCase } from '../../../../core/racing/application/use-cases/GetSponsorUseCase';
|
||||
|
||||
describe('Sponsor Settings Use Case Orchestration', () => {
|
||||
let context: SponsorTestContext;
|
||||
let getSponsorUseCase: GetSponsorUseCase;
|
||||
|
||||
beforeEach(() => {
|
||||
context = new SponsorTestContext();
|
||||
getSponsorUseCase = new GetSponsorUseCase(context.sponsorRepository);
|
||||
});
|
||||
|
||||
describe('GetSponsorUseCase - Success Path', () => {
|
||||
it('should retrieve sponsor profile information', async () => {
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'john@example.com',
|
||||
});
|
||||
await context.sponsorRepository.create(sponsor);
|
||||
|
||||
const result = await getSponsorUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
const { sponsor: retrievedSponsor } = result.unwrap();
|
||||
expect(retrievedSponsor.name.toString()).toBe('Test Company');
|
||||
expect(retrievedSponsor.contactEmail.toString()).toBe('john@example.com');
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetSponsorUseCase - Error Handling', () => {
|
||||
it('should return error when sponsor does not exist', async () => {
|
||||
const result = await getSponsorUseCase.execute({ sponsorId: 'non-existent' });
|
||||
|
||||
expect(result.isErr()).toBe(true);
|
||||
const error = result.unwrapErr();
|
||||
expect(error.code).toBe('SPONSOR_NOT_FOUND');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,45 +1,19 @@
|
||||
/**
|
||||
* Integration Test: Sponsor Signup Use Case Orchestration
|
||||
*
|
||||
* Tests the orchestration logic of sponsor signup-related Use Cases:
|
||||
* - CreateSponsorUseCase: Creates a new sponsor account
|
||||
* - Validates that Use Cases correctly interact with their Ports (Repositories)
|
||||
* - Uses In-Memory adapters for fast, deterministic testing
|
||||
*
|
||||
* Focus: Business logic orchestration, NOT UI rendering
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
|
||||
import { InMemorySponsorRepository } from '../../../adapters/racing/persistence/inmemory/InMemorySponsorRepository';
|
||||
import { CreateSponsorUseCase } from '../../../core/racing/application/use-cases/CreateSponsorUseCase';
|
||||
import { Sponsor } from '../../../core/racing/domain/entities/sponsor/Sponsor';
|
||||
import { Logger } from '../../../core/shared/domain/Logger';
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { CreateSponsorUseCase } from '../../../../core/racing/application/use-cases/CreateSponsorUseCase';
|
||||
import { Sponsor } from '../../../../core/racing/domain/entities/sponsor/Sponsor';
|
||||
import { SponsorTestContext } from '../SponsorTestContext';
|
||||
|
||||
describe('Sponsor Signup Use Case Orchestration', () => {
|
||||
let sponsorRepository: InMemorySponsorRepository;
|
||||
let context: SponsorTestContext;
|
||||
let createSponsorUseCase: CreateSponsorUseCase;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeAll(() => {
|
||||
mockLogger = {
|
||||
info: () => {},
|
||||
debug: () => {},
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
} as unknown as Logger;
|
||||
|
||||
sponsorRepository = new InMemorySponsorRepository(mockLogger);
|
||||
createSponsorUseCase = new CreateSponsorUseCase(sponsorRepository, mockLogger);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
sponsorRepository.clear();
|
||||
context = new SponsorTestContext();
|
||||
createSponsorUseCase = new CreateSponsorUseCase(context.sponsorRepository, context.logger);
|
||||
});
|
||||
|
||||
describe('CreateSponsorUseCase - Success Path', () => {
|
||||
it('should create a new sponsor account with valid information', async () => {
|
||||
// Given: No sponsor exists with the given email
|
||||
const sponsorId = 'sponsor-123';
|
||||
const sponsorData = {
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
@@ -47,116 +21,82 @@ describe('Sponsor Signup Use Case Orchestration', () => {
|
||||
logoUrl: 'https://testcompany.com/logo.png',
|
||||
};
|
||||
|
||||
// When: CreateSponsorUseCase.execute() is called with valid sponsor data
|
||||
const result = await createSponsorUseCase.execute(sponsorData);
|
||||
|
||||
// Then: The sponsor should be created successfully
|
||||
expect(result.isOk()).toBe(true);
|
||||
const createdSponsor = result.unwrap().sponsor;
|
||||
|
||||
// And: The sponsor should have a unique ID
|
||||
expect(createdSponsor.id.toString()).toBeDefined();
|
||||
|
||||
// And: The sponsor should have the provided company name
|
||||
expect(createdSponsor.name.toString()).toBe('Test Company');
|
||||
|
||||
// And: The sponsor should have the provided contact email
|
||||
expect(createdSponsor.contactEmail.toString()).toBe('test@example.com');
|
||||
|
||||
// And: The sponsor should have the provided website URL
|
||||
expect(createdSponsor.websiteUrl?.toString()).toBe('https://testcompany.com');
|
||||
|
||||
// And: The sponsor should have the provided logo URL
|
||||
expect(createdSponsor.logoUrl?.toString()).toBe('https://testcompany.com/logo.png');
|
||||
|
||||
// And: The sponsor should have a created timestamp
|
||||
expect(createdSponsor.createdAt).toBeDefined();
|
||||
|
||||
// And: The sponsor should be retrievable from the repository
|
||||
const retrievedSponsor = await sponsorRepository.findById(createdSponsor.id.toString());
|
||||
const retrievedSponsor = await context.sponsorRepository.findById(createdSponsor.id.toString());
|
||||
expect(retrievedSponsor).toBeDefined();
|
||||
expect(retrievedSponsor?.name.toString()).toBe('Test Company');
|
||||
});
|
||||
|
||||
it('should create a sponsor with minimal data', async () => {
|
||||
// Given: No sponsor exists
|
||||
const sponsorData = {
|
||||
name: 'Minimal Company',
|
||||
contactEmail: 'minimal@example.com',
|
||||
};
|
||||
|
||||
// When: CreateSponsorUseCase.execute() is called with minimal data
|
||||
const result = await createSponsorUseCase.execute(sponsorData);
|
||||
|
||||
// Then: The sponsor should be created successfully
|
||||
expect(result.isOk()).toBe(true);
|
||||
const createdSponsor = result.unwrap().sponsor;
|
||||
|
||||
// And: The sponsor should have the provided company name
|
||||
expect(createdSponsor.name.toString()).toBe('Minimal Company');
|
||||
|
||||
// And: The sponsor should have the provided contact email
|
||||
expect(createdSponsor.contactEmail.toString()).toBe('minimal@example.com');
|
||||
|
||||
// And: Optional fields should be undefined
|
||||
expect(createdSponsor.websiteUrl).toBeUndefined();
|
||||
expect(createdSponsor.logoUrl).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should create a sponsor with optional fields only', async () => {
|
||||
// Given: No sponsor exists
|
||||
const sponsorData = {
|
||||
name: 'Optional Fields Company',
|
||||
contactEmail: 'optional@example.com',
|
||||
websiteUrl: 'https://optional.com',
|
||||
};
|
||||
|
||||
// When: CreateSponsorUseCase.execute() is called with optional fields
|
||||
const result = await createSponsorUseCase.execute(sponsorData);
|
||||
|
||||
// Then: The sponsor should be created successfully
|
||||
expect(result.isOk()).toBe(true);
|
||||
const createdSponsor = result.unwrap().sponsor;
|
||||
|
||||
// And: The sponsor should have the provided website URL
|
||||
expect(createdSponsor.websiteUrl?.toString()).toBe('https://optional.com');
|
||||
|
||||
// And: Logo URL should be undefined
|
||||
expect(createdSponsor.logoUrl).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('CreateSponsorUseCase - Validation', () => {
|
||||
it('should reject sponsor creation with duplicate email', async () => {
|
||||
// Given: A sponsor exists with email "sponsor@example.com"
|
||||
const existingSponsor = Sponsor.create({
|
||||
id: 'existing-sponsor',
|
||||
name: 'Existing Company',
|
||||
contactEmail: 'sponsor@example.com',
|
||||
});
|
||||
await sponsorRepository.create(existingSponsor);
|
||||
await context.sponsorRepository.create(existingSponsor);
|
||||
|
||||
// When: CreateSponsorUseCase.execute() is called with the same email
|
||||
const result = await createSponsorUseCase.execute({
|
||||
name: 'New Company',
|
||||
contactEmail: 'sponsor@example.com',
|
||||
});
|
||||
|
||||
// Then: Should return an error
|
||||
expect(result.isErr()).toBe(true);
|
||||
const error = result.unwrapErr();
|
||||
expect(error.code).toBe('REPOSITORY_ERROR');
|
||||
});
|
||||
|
||||
it('should reject sponsor creation with invalid email format', async () => {
|
||||
// Given: No sponsor exists
|
||||
// When: CreateSponsorUseCase.execute() is called with invalid email
|
||||
const result = await createSponsorUseCase.execute({
|
||||
name: 'Test Company',
|
||||
contactEmail: 'invalid-email',
|
||||
});
|
||||
|
||||
// Then: Should return an error
|
||||
expect(result.isErr()).toBe(true);
|
||||
const error = result.unwrapErr();
|
||||
expect(error.code).toBe('VALIDATION_ERROR');
|
||||
@@ -164,14 +104,11 @@ describe('Sponsor Signup Use Case Orchestration', () => {
|
||||
});
|
||||
|
||||
it('should reject sponsor creation with missing required fields', async () => {
|
||||
// Given: No sponsor exists
|
||||
// When: CreateSponsorUseCase.execute() is called without company name
|
||||
const result = await createSponsorUseCase.execute({
|
||||
name: '',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
|
||||
// Then: Should return an error
|
||||
expect(result.isErr()).toBe(true);
|
||||
const error = result.unwrapErr();
|
||||
expect(error.code).toBe('VALIDATION_ERROR');
|
||||
@@ -179,15 +116,12 @@ describe('Sponsor Signup Use Case Orchestration', () => {
|
||||
});
|
||||
|
||||
it('should reject sponsor creation with invalid website URL', async () => {
|
||||
// Given: No sponsor exists
|
||||
// When: CreateSponsorUseCase.execute() is called with invalid URL
|
||||
const result = await createSponsorUseCase.execute({
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
websiteUrl: 'not-a-valid-url',
|
||||
});
|
||||
|
||||
// Then: Should return an error
|
||||
expect(result.isErr()).toBe(true);
|
||||
const error = result.unwrapErr();
|
||||
expect(error.code).toBe('VALIDATION_ERROR');
|
||||
@@ -195,14 +129,11 @@ describe('Sponsor Signup Use Case Orchestration', () => {
|
||||
});
|
||||
|
||||
it('should reject sponsor creation with missing email', async () => {
|
||||
// Given: No sponsor exists
|
||||
// When: CreateSponsorUseCase.execute() is called without email
|
||||
const result = await createSponsorUseCase.execute({
|
||||
name: 'Test Company',
|
||||
contactEmail: '',
|
||||
});
|
||||
|
||||
// Then: Should return an error
|
||||
expect(result.isErr()).toBe(true);
|
||||
const error = result.unwrapErr();
|
||||
expect(error.code).toBe('VALIDATION_ERROR');
|
||||
@@ -212,7 +143,6 @@ describe('Sponsor Signup Use Case Orchestration', () => {
|
||||
|
||||
describe('Sponsor Data Orchestration', () => {
|
||||
it('should correctly create sponsor with all optional fields', async () => {
|
||||
// Given: No sponsor exists
|
||||
const sponsorData = {
|
||||
name: 'Full Featured Company',
|
||||
contactEmail: 'full@example.com',
|
||||
@@ -220,10 +150,8 @@ describe('Sponsor Signup Use Case Orchestration', () => {
|
||||
logoUrl: 'https://fullfeatured.com/logo.png',
|
||||
};
|
||||
|
||||
// When: CreateSponsorUseCase.execute() is called with all fields
|
||||
const result = await createSponsorUseCase.execute(sponsorData);
|
||||
|
||||
// Then: The sponsor should be created with all fields
|
||||
expect(result.isOk()).toBe(true);
|
||||
const createdSponsor = result.unwrap().sponsor;
|
||||
|
||||
@@ -235,7 +163,6 @@ describe('Sponsor Signup Use Case Orchestration', () => {
|
||||
});
|
||||
|
||||
it('should generate unique IDs for each sponsor', async () => {
|
||||
// Given: No sponsors exist
|
||||
const sponsorData1 = {
|
||||
name: 'Company 1',
|
||||
contactEmail: 'company1@example.com',
|
||||
@@ -245,11 +172,9 @@ describe('Sponsor Signup Use Case Orchestration', () => {
|
||||
contactEmail: 'company2@example.com',
|
||||
};
|
||||
|
||||
// When: Creating two sponsors
|
||||
const result1 = await createSponsorUseCase.execute(sponsorData1);
|
||||
const result2 = await createSponsorUseCase.execute(sponsorData2);
|
||||
|
||||
// Then: Both should succeed and have unique IDs
|
||||
expect(result1.isOk()).toBe(true);
|
||||
expect(result2.isOk()).toBe(true);
|
||||
|
||||
@@ -260,20 +185,17 @@ describe('Sponsor Signup Use Case Orchestration', () => {
|
||||
});
|
||||
|
||||
it('should persist sponsor in repository after creation', async () => {
|
||||
// Given: No sponsor exists
|
||||
const sponsorData = {
|
||||
name: 'Persistent Company',
|
||||
contactEmail: 'persistent@example.com',
|
||||
};
|
||||
|
||||
// When: Creating a sponsor
|
||||
const result = await createSponsorUseCase.execute(sponsorData);
|
||||
|
||||
// Then: The sponsor should be retrievable from the repository
|
||||
expect(result.isOk()).toBe(true);
|
||||
const createdSponsor = result.unwrap().sponsor;
|
||||
|
||||
const retrievedSponsor = await sponsorRepository.findById(createdSponsor.id.toString());
|
||||
const retrievedSponsor = await context.sponsorRepository.findById(createdSponsor.id.toString());
|
||||
expect(retrievedSponsor).toBeDefined();
|
||||
expect(retrievedSponsor?.name.toString()).toBe('Persistent Company');
|
||||
expect(retrievedSponsor?.contactEmail.toString()).toBe('persistent@example.com');
|
||||
@@ -1,568 +0,0 @@
|
||||
/**
|
||||
* Integration Test: Sponsor Billing Use Case Orchestration
|
||||
*
|
||||
* Tests the orchestration logic of sponsor billing-related Use Cases:
|
||||
* - GetSponsorBillingUseCase: Retrieves sponsor billing information
|
||||
* - Validates that Use Cases correctly interact with their Ports (Repositories)
|
||||
* - Uses In-Memory adapters for fast, deterministic testing
|
||||
*
|
||||
* Focus: Business logic orchestration, NOT UI rendering
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
|
||||
import { InMemorySponsorRepository } from '../../../adapters/racing/persistence/inmemory/InMemorySponsorRepository';
|
||||
import { InMemorySeasonSponsorshipRepository } from '../../../adapters/racing/persistence/inmemory/InMemorySeasonSponsorshipRepository';
|
||||
import { InMemoryPaymentRepository } from '../../../adapters/payments/persistence/inmemory/InMemoryPaymentRepository';
|
||||
import { GetSponsorBillingUseCase } from '../../../core/payments/application/use-cases/GetSponsorBillingUseCase';
|
||||
import { Sponsor } from '../../../core/racing/domain/entities/sponsor/Sponsor';
|
||||
import { SeasonSponsorship } from '../../../core/racing/domain/entities/season/SeasonSponsorship';
|
||||
import { Payment, PaymentType, PaymentStatus } from '../../../core/payments/domain/entities/Payment';
|
||||
import { Money } from '../../../core/racing/domain/value-objects/Money';
|
||||
import { Logger } from '../../../core/shared/domain/Logger';
|
||||
|
||||
describe('Sponsor Billing Use Case Orchestration', () => {
|
||||
let sponsorRepository: InMemorySponsorRepository;
|
||||
let seasonSponsorshipRepository: InMemorySeasonSponsorshipRepository;
|
||||
let paymentRepository: InMemoryPaymentRepository;
|
||||
let getSponsorBillingUseCase: GetSponsorBillingUseCase;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeAll(() => {
|
||||
mockLogger = {
|
||||
info: () => {},
|
||||
debug: () => {},
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
} as unknown as Logger;
|
||||
|
||||
sponsorRepository = new InMemorySponsorRepository(mockLogger);
|
||||
seasonSponsorshipRepository = new InMemorySeasonSponsorshipRepository(mockLogger);
|
||||
paymentRepository = new InMemoryPaymentRepository(mockLogger);
|
||||
|
||||
getSponsorBillingUseCase = new GetSponsorBillingUseCase(
|
||||
paymentRepository,
|
||||
seasonSponsorshipRepository,
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
sponsorRepository.clear();
|
||||
seasonSponsorshipRepository.clear();
|
||||
paymentRepository.clear();
|
||||
});
|
||||
|
||||
describe('GetSponsorBillingUseCase - Success Path', () => {
|
||||
it('should retrieve billing statistics for a sponsor with paid invoices', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await sponsorRepository.create(sponsor);
|
||||
|
||||
// And: The sponsor has 2 active sponsorships
|
||||
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: 'active',
|
||||
});
|
||||
await seasonSponsorshipRepository.create(sponsorship2);
|
||||
|
||||
// And: The sponsor has 3 paid invoices
|
||||
const payment1: Payment = {
|
||||
id: 'payment-1',
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: 1000,
|
||||
platformFee: 100,
|
||||
netAmount: 900,
|
||||
payerId: 'sponsor-123',
|
||||
payerType: 'sponsor',
|
||||
leagueId: 'league-1',
|
||||
seasonId: 'season-1',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2025-01-15'),
|
||||
completedAt: new Date('2025-01-15'),
|
||||
};
|
||||
await paymentRepository.create(payment1);
|
||||
|
||||
const payment2: Payment = {
|
||||
id: 'payment-2',
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: 2000,
|
||||
platformFee: 200,
|
||||
netAmount: 1800,
|
||||
payerId: 'sponsor-123',
|
||||
payerType: 'sponsor',
|
||||
leagueId: 'league-2',
|
||||
seasonId: 'season-2',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2025-02-15'),
|
||||
completedAt: new Date('2025-02-15'),
|
||||
};
|
||||
await paymentRepository.create(payment2);
|
||||
|
||||
const payment3: Payment = {
|
||||
id: 'payment-3',
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: 3000,
|
||||
platformFee: 300,
|
||||
netAmount: 2700,
|
||||
payerId: 'sponsor-123',
|
||||
payerType: 'sponsor',
|
||||
leagueId: 'league-3',
|
||||
seasonId: 'season-3',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2025-03-15'),
|
||||
completedAt: new Date('2025-03-15'),
|
||||
};
|
||||
await paymentRepository.create(payment3);
|
||||
|
||||
// When: GetSponsorBillingUseCase.execute() is called with sponsor ID
|
||||
const result = await getSponsorBillingUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
// Then: The result should contain billing data
|
||||
expect(result.isOk()).toBe(true);
|
||||
const billing = result.unwrap();
|
||||
|
||||
// And: The invoices should contain all 3 paid invoices
|
||||
expect(billing.invoices).toHaveLength(3);
|
||||
expect(billing.invoices[0].status).toBe('paid');
|
||||
expect(billing.invoices[1].status).toBe('paid');
|
||||
expect(billing.invoices[2].status).toBe('paid');
|
||||
|
||||
// And: The stats should show correct total spent
|
||||
// Total spent = 1000 + 2000 + 3000 = 6000
|
||||
expect(billing.stats.totalSpent).toBe(6000);
|
||||
|
||||
// And: The stats should show no pending payments
|
||||
expect(billing.stats.pendingAmount).toBe(0);
|
||||
|
||||
// And: The stats should show no next payment date
|
||||
expect(billing.stats.nextPaymentDate).toBeNull();
|
||||
expect(billing.stats.nextPaymentAmount).toBeNull();
|
||||
|
||||
// And: The stats should show correct active sponsorships
|
||||
expect(billing.stats.activeSponsorships).toBe(2);
|
||||
|
||||
// And: The stats should show correct average monthly spend
|
||||
// Average monthly spend = total / months = 6000 / 3 = 2000
|
||||
expect(billing.stats.averageMonthlySpend).toBe(2000);
|
||||
});
|
||||
|
||||
it('should retrieve billing statistics with pending invoices', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await sponsorRepository.create(sponsor);
|
||||
|
||||
// And: The sponsor has 1 active sponsorship
|
||||
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);
|
||||
|
||||
// And: The sponsor has 1 paid invoice and 1 pending invoice
|
||||
const payment1: Payment = {
|
||||
id: 'payment-1',
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: 1000,
|
||||
platformFee: 100,
|
||||
netAmount: 900,
|
||||
payerId: 'sponsor-123',
|
||||
payerType: 'sponsor',
|
||||
leagueId: 'league-1',
|
||||
seasonId: 'season-1',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2025-01-15'),
|
||||
completedAt: new Date('2025-01-15'),
|
||||
};
|
||||
await paymentRepository.create(payment1);
|
||||
|
||||
const payment2: Payment = {
|
||||
id: 'payment-2',
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: 500,
|
||||
platformFee: 50,
|
||||
netAmount: 450,
|
||||
payerId: 'sponsor-123',
|
||||
payerType: 'sponsor',
|
||||
leagueId: 'league-2',
|
||||
seasonId: 'season-2',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2025-02-15'),
|
||||
};
|
||||
await paymentRepository.create(payment2);
|
||||
|
||||
// When: GetSponsorBillingUseCase.execute() is called with sponsor ID
|
||||
const result = await getSponsorBillingUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
// Then: The result should contain billing data
|
||||
expect(result.isOk()).toBe(true);
|
||||
const billing = result.unwrap();
|
||||
|
||||
// And: The invoices should contain both invoices
|
||||
expect(billing.invoices).toHaveLength(2);
|
||||
|
||||
// And: The stats should show correct total spent (only paid invoices)
|
||||
expect(billing.stats.totalSpent).toBe(1000);
|
||||
|
||||
// And: The stats should show correct pending amount
|
||||
expect(billing.stats.pendingAmount).toBe(550); // 500 + 50
|
||||
|
||||
// And: The stats should show next payment date
|
||||
expect(billing.stats.nextPaymentDate).toBeDefined();
|
||||
expect(billing.stats.nextPaymentAmount).toBe(550);
|
||||
|
||||
// And: The stats should show correct active sponsorships
|
||||
expect(billing.stats.activeSponsorships).toBe(1);
|
||||
});
|
||||
|
||||
it('should retrieve billing statistics with zero values when no invoices exist', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await sponsorRepository.create(sponsor);
|
||||
|
||||
// And: The sponsor has 1 active sponsorship
|
||||
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);
|
||||
|
||||
// And: The sponsor has no invoices
|
||||
// When: GetSponsorBillingUseCase.execute() is called with sponsor ID
|
||||
const result = await getSponsorBillingUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
// Then: The result should contain billing data
|
||||
expect(result.isOk()).toBe(true);
|
||||
const billing = result.unwrap();
|
||||
|
||||
// And: The invoices should be empty
|
||||
expect(billing.invoices).toHaveLength(0);
|
||||
|
||||
// And: The stats should show zero values
|
||||
expect(billing.stats.totalSpent).toBe(0);
|
||||
expect(billing.stats.pendingAmount).toBe(0);
|
||||
expect(billing.stats.nextPaymentDate).toBeNull();
|
||||
expect(billing.stats.nextPaymentAmount).toBeNull();
|
||||
expect(billing.stats.activeSponsorships).toBe(1);
|
||||
expect(billing.stats.averageMonthlySpend).toBe(0);
|
||||
});
|
||||
|
||||
it('should retrieve billing statistics with mixed invoice statuses', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await sponsorRepository.create(sponsor);
|
||||
|
||||
// And: The sponsor has 1 active sponsorship
|
||||
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);
|
||||
|
||||
// And: The sponsor has invoices with different statuses
|
||||
const payment1: Payment = {
|
||||
id: 'payment-1',
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: 1000,
|
||||
platformFee: 100,
|
||||
netAmount: 900,
|
||||
payerId: 'sponsor-123',
|
||||
payerType: 'sponsor',
|
||||
leagueId: 'league-1',
|
||||
seasonId: 'season-1',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2025-01-15'),
|
||||
completedAt: new Date('2025-01-15'),
|
||||
};
|
||||
await paymentRepository.create(payment1);
|
||||
|
||||
const payment2: Payment = {
|
||||
id: 'payment-2',
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: 500,
|
||||
platformFee: 50,
|
||||
netAmount: 450,
|
||||
payerId: 'sponsor-123',
|
||||
payerType: 'sponsor',
|
||||
leagueId: 'league-2',
|
||||
seasonId: 'season-2',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2025-02-15'),
|
||||
};
|
||||
await paymentRepository.create(payment2);
|
||||
|
||||
const payment3: Payment = {
|
||||
id: 'payment-3',
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: 300,
|
||||
platformFee: 30,
|
||||
netAmount: 270,
|
||||
payerId: 'sponsor-123',
|
||||
payerType: 'sponsor',
|
||||
leagueId: 'league-3',
|
||||
seasonId: 'season-3',
|
||||
status: PaymentStatus.FAILED,
|
||||
createdAt: new Date('2025-03-15'),
|
||||
};
|
||||
await paymentRepository.create(payment3);
|
||||
|
||||
// When: GetSponsorBillingUseCase.execute() is called with sponsor ID
|
||||
const result = await getSponsorBillingUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
// Then: The result should contain billing data
|
||||
expect(result.isOk()).toBe(true);
|
||||
const billing = result.unwrap();
|
||||
|
||||
// And: The invoices should contain all 3 invoices
|
||||
expect(billing.invoices).toHaveLength(3);
|
||||
|
||||
// And: The stats should show correct total spent (only paid invoices)
|
||||
expect(billing.stats.totalSpent).toBe(1000);
|
||||
|
||||
// And: The stats should show correct pending amount (pending + failed)
|
||||
expect(billing.stats.pendingAmount).toBe(550); // 500 + 50
|
||||
|
||||
// And: The stats should show correct active sponsorships
|
||||
expect(billing.stats.activeSponsorships).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetSponsorBillingUseCase - Error Handling', () => {
|
||||
it('should return error when sponsor does not exist', async () => {
|
||||
// Given: No sponsor exists with the given ID
|
||||
// When: GetSponsorBillingUseCase.execute() is called with non-existent sponsor ID
|
||||
const result = await getSponsorBillingUseCase.execute({ sponsorId: 'non-existent-sponsor' });
|
||||
|
||||
// Then: Should return an error
|
||||
expect(result.isErr()).toBe(true);
|
||||
const error = result.unwrapErr();
|
||||
expect(error.code).toBe('SPONSOR_NOT_FOUND');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sponsor Billing Data Orchestration', () => {
|
||||
it('should correctly aggregate billing statistics across multiple invoices', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await sponsorRepository.create(sponsor);
|
||||
|
||||
// And: The sponsor has 1 active sponsorship
|
||||
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);
|
||||
|
||||
// And: The sponsor has 5 invoices with different amounts and statuses
|
||||
const invoices = [
|
||||
{ id: 'payment-1', amount: 1000, status: PaymentStatus.COMPLETED, date: new Date('2025-01-15') },
|
||||
{ id: 'payment-2', amount: 2000, status: PaymentStatus.COMPLETED, date: new Date('2025-02-15') },
|
||||
{ id: 'payment-3', amount: 1500, status: PaymentStatus.PENDING, date: new Date('2025-03-15') },
|
||||
{ id: 'payment-4', amount: 3000, status: PaymentStatus.COMPLETED, date: new Date('2025-04-15') },
|
||||
{ id: 'payment-5', amount: 500, status: PaymentStatus.FAILED, date: new Date('2025-05-15') },
|
||||
];
|
||||
|
||||
for (const invoice of invoices) {
|
||||
const payment: Payment = {
|
||||
id: invoice.id,
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: invoice.amount,
|
||||
platformFee: invoice.amount * 0.1,
|
||||
netAmount: invoice.amount * 0.9,
|
||||
payerId: 'sponsor-123',
|
||||
payerType: 'sponsor',
|
||||
leagueId: 'league-1',
|
||||
seasonId: 'season-1',
|
||||
status: invoice.status,
|
||||
createdAt: invoice.date,
|
||||
completedAt: invoice.status === PaymentStatus.COMPLETED ? invoice.date : undefined,
|
||||
};
|
||||
await paymentRepository.create(payment);
|
||||
}
|
||||
|
||||
// When: GetSponsorBillingUseCase.execute() is called
|
||||
const result = await getSponsorBillingUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
// Then: The billing statistics should be correctly aggregated
|
||||
expect(result.isOk()).toBe(true);
|
||||
const billing = result.unwrap();
|
||||
|
||||
// Total spent = 1000 + 2000 + 3000 = 6000
|
||||
expect(billing.stats.totalSpent).toBe(6000);
|
||||
|
||||
// Pending amount = 1500 + 500 = 2000
|
||||
expect(billing.stats.pendingAmount).toBe(2000);
|
||||
|
||||
// Average monthly spend = 6000 / 5 = 1200
|
||||
expect(billing.stats.averageMonthlySpend).toBe(1200);
|
||||
|
||||
// Active sponsorships = 1
|
||||
expect(billing.stats.activeSponsorships).toBe(1);
|
||||
});
|
||||
|
||||
it('should correctly calculate average monthly spend over time', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await sponsorRepository.create(sponsor);
|
||||
|
||||
// And: The sponsor has 1 active sponsorship
|
||||
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);
|
||||
|
||||
// And: The sponsor has invoices spanning 6 months
|
||||
const invoices = [
|
||||
{ id: 'payment-1', amount: 1000, date: new Date('2025-01-15') },
|
||||
{ id: 'payment-2', amount: 1500, date: new Date('2025-02-15') },
|
||||
{ id: 'payment-3', amount: 2000, date: new Date('2025-03-15') },
|
||||
{ id: 'payment-4', amount: 2500, date: new Date('2025-04-15') },
|
||||
{ id: 'payment-5', amount: 3000, date: new Date('2025-05-15') },
|
||||
{ id: 'payment-6', amount: 3500, date: new Date('2025-06-15') },
|
||||
];
|
||||
|
||||
for (const invoice of invoices) {
|
||||
const payment: Payment = {
|
||||
id: invoice.id,
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: invoice.amount,
|
||||
platformFee: invoice.amount * 0.1,
|
||||
netAmount: invoice.amount * 0.9,
|
||||
payerId: 'sponsor-123',
|
||||
payerType: 'sponsor',
|
||||
leagueId: 'league-1',
|
||||
seasonId: 'season-1',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: invoice.date,
|
||||
completedAt: invoice.date,
|
||||
};
|
||||
await paymentRepository.create(payment);
|
||||
}
|
||||
|
||||
// When: GetSponsorBillingUseCase.execute() is called
|
||||
const result = await getSponsorBillingUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
// Then: The average monthly spend should be calculated correctly
|
||||
expect(result.isOk()).toBe(true);
|
||||
const billing = result.unwrap();
|
||||
|
||||
// Total = 1000 + 1500 + 2000 + 2500 + 3000 + 3500 = 13500
|
||||
// Months = 6 (Jan to Jun)
|
||||
// Average = 13500 / 6 = 2250
|
||||
expect(billing.stats.averageMonthlySpend).toBe(2250);
|
||||
});
|
||||
|
||||
it('should correctly identify next payment date from pending invoices', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await sponsorRepository.create(sponsor);
|
||||
|
||||
// And: The sponsor has 1 active sponsorship
|
||||
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);
|
||||
|
||||
// And: The sponsor has multiple pending invoices with different due dates
|
||||
const invoices = [
|
||||
{ id: 'payment-1', amount: 500, date: new Date('2025-03-15') },
|
||||
{ id: 'payment-2', amount: 1000, date: new Date('2025-02-15') },
|
||||
{ id: 'payment-3', amount: 750, date: new Date('2025-01-15') },
|
||||
];
|
||||
|
||||
for (const invoice of invoices) {
|
||||
const payment: Payment = {
|
||||
id: invoice.id,
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: invoice.amount,
|
||||
platformFee: invoice.amount * 0.1,
|
||||
netAmount: invoice.amount * 0.9,
|
||||
payerId: 'sponsor-123',
|
||||
payerType: 'sponsor',
|
||||
leagueId: 'league-1',
|
||||
seasonId: 'season-1',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: invoice.date,
|
||||
};
|
||||
await paymentRepository.create(payment);
|
||||
}
|
||||
|
||||
// When: GetSponsorBillingUseCase.execute() is called
|
||||
const result = await getSponsorBillingUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
// Then: The next payment should be the earliest pending invoice
|
||||
expect(result.isOk()).toBe(true);
|
||||
const billing = result.unwrap();
|
||||
|
||||
// Next payment should be from payment-3 (earliest date)
|
||||
expect(billing.stats.nextPaymentDate).toBe('2025-01-15T00:00:00.000Z');
|
||||
expect(billing.stats.nextPaymentAmount).toBe(825); // 750 + 75
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,658 +0,0 @@
|
||||
/**
|
||||
* Integration Test: Sponsor Campaigns Use Case Orchestration
|
||||
*
|
||||
* Tests the orchestration logic of sponsor campaigns-related Use Cases:
|
||||
* - 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, 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 Campaigns Use Case Orchestration', () => {
|
||||
let sponsorRepository: InMemorySponsorRepository;
|
||||
let seasonSponsorshipRepository: InMemorySeasonSponsorshipRepository;
|
||||
let seasonRepository: InMemorySeasonRepository;
|
||||
let leagueRepository: InMemoryLeagueRepository;
|
||||
let leagueMembershipRepository: InMemoryLeagueMembershipRepository;
|
||||
let raceRepository: InMemoryRaceRepository;
|
||||
let getSponsorSponsorshipsUseCase: GetSponsorSponsorshipsUseCase;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeAll(() => {
|
||||
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(() => {
|
||||
sponsorRepository.clear();
|
||||
seasonSponsorshipRepository.clear();
|
||||
seasonRepository.clear();
|
||||
leagueRepository.clear();
|
||||
leagueMembershipRepository.clear();
|
||||
raceRepository.clear();
|
||||
});
|
||||
|
||||
describe('GetSponsorSponsorshipsUseCase - Success Path', () => {
|
||||
it('should retrieve all sponsorships for a sponsor', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
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 sponsorships with minimal data', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
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 sponsorships with empty result when no sponsorships exist', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
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('GetSponsorSponsorshipsUseCase - Error Handling', () => {
|
||||
it('should return error when sponsor does not exist', async () => {
|
||||
// Given: No sponsor exists with the given ID
|
||||
// When: GetSponsorSponsorshipsUseCase.execute() is called with non-existent sponsor ID
|
||||
const result = await getSponsorSponsorshipsUseCase.execute({ sponsorId: 'non-existent-sponsor' });
|
||||
|
||||
// Then: Should return an error
|
||||
expect(result.isErr()).toBe(true);
|
||||
const error = result.unwrapErr();
|
||||
expect(error.code).toBe('SPONSOR_NOT_FOUND');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sponsor Campaigns Data Orchestration', () => {
|
||||
it('should correctly aggregate sponsorship metrics across multiple sponsorships', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
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 correctly calculate impressions based on completed races and drivers', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await sponsorRepository.create(sponsor);
|
||||
|
||||
// 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);
|
||||
|
||||
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"
|
||||
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
|
||||
const result = await getSponsorSponsorshipsUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
// Then: Platform fees and net amounts should be calculated correctly
|
||||
expect(result.isOk()).toBe(true);
|
||||
const sponsorships = result.unwrap();
|
||||
|
||||
// Platform fee = 10% of pricing = 100
|
||||
expect(sponsorships.sponsorships[0].financials.platformFee.amount).toBe(100);
|
||||
|
||||
// Net amount = pricing - platform fee = 1000 - 100 = 900
|
||||
expect(sponsorships.sponsorships[0].financials.netAmount.amount).toBe(900);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,709 +0,0 @@
|
||||
/**
|
||||
* Integration Test: Sponsor Dashboard Use Case Orchestration
|
||||
*
|
||||
* Tests the orchestration logic of sponsor dashboard-related Use Cases:
|
||||
* - GetSponsorDashboardUseCase: Retrieves sponsor dashboard 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, 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 { GetSponsorDashboardUseCase } from '../../../core/racing/application/use-cases/GetSponsorDashboardUseCase';
|
||||
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 Dashboard Use Case Orchestration', () => {
|
||||
let sponsorRepository: InMemorySponsorRepository;
|
||||
let seasonSponsorshipRepository: InMemorySeasonSponsorshipRepository;
|
||||
let seasonRepository: InMemorySeasonRepository;
|
||||
let leagueRepository: InMemoryLeagueRepository;
|
||||
let leagueMembershipRepository: InMemoryLeagueMembershipRepository;
|
||||
let raceRepository: InMemoryRaceRepository;
|
||||
let getSponsorDashboardUseCase: GetSponsorDashboardUseCase;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeAll(() => {
|
||||
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);
|
||||
|
||||
getSponsorDashboardUseCase = new GetSponsorDashboardUseCase(
|
||||
sponsorRepository,
|
||||
seasonSponsorshipRepository,
|
||||
seasonRepository,
|
||||
leagueRepository,
|
||||
leagueMembershipRepository,
|
||||
raceRepository,
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
sponsorRepository.clear();
|
||||
seasonSponsorshipRepository.clear();
|
||||
seasonRepository.clear();
|
||||
leagueRepository.clear();
|
||||
leagueMembershipRepository.clear();
|
||||
raceRepository.clear();
|
||||
});
|
||||
|
||||
describe('GetSponsorDashboardUseCase - Success Path', () => {
|
||||
it('should retrieve dashboard metrics for a sponsor with active sponsorships', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await sponsorRepository.create(sponsor);
|
||||
|
||||
// And: The sponsor has 2 active sponsorships
|
||||
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 season1 = Season.create({
|
||||
id: 'season-1',
|
||||
leagueId: 'league-1',
|
||||
gameId: 'game-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',
|
||||
gameId: 'game-1',
|
||||
name: 'Season 2',
|
||||
startDate: new Date('2025-01-01'),
|
||||
endDate: new Date('2025-12-31'),
|
||||
});
|
||||
await seasonRepository.create(season2);
|
||||
|
||||
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: 'active',
|
||||
});
|
||||
await seasonSponsorshipRepository.create(sponsorship2);
|
||||
|
||||
// And: The sponsor has 5 drivers in league 1 and 3 drivers in league 2
|
||||
for (let i = 1; i <= 5; 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 <= 3; i++) {
|
||||
const membership = LeagueMembership.create({
|
||||
id: `membership-2-${i}`,
|
||||
leagueId: 'league-2',
|
||||
driverId: `driver-2-${i}`,
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
});
|
||||
await leagueMembershipRepository.saveMembership(membership);
|
||||
}
|
||||
|
||||
// And: The sponsor has 3 completed races in league 1 and 2 completed races in league 2
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
const race = Race.create({
|
||||
id: `race-1-${i}`,
|
||||
leagueId: 'league-1',
|
||||
track: 'Track 1',
|
||||
car: 'GT3',
|
||||
scheduledAt: new Date(`2025-0${i}-01`),
|
||||
status: 'completed',
|
||||
});
|
||||
await raceRepository.create(race);
|
||||
}
|
||||
|
||||
for (let i = 1; i <= 2; i++) {
|
||||
const race = Race.create({
|
||||
id: `race-2-${i}`,
|
||||
leagueId: 'league-2',
|
||||
track: 'Track 2',
|
||||
car: 'GT3',
|
||||
scheduledAt: new Date(`2025-0${i}-01`),
|
||||
status: 'completed',
|
||||
});
|
||||
await raceRepository.create(race);
|
||||
}
|
||||
|
||||
// When: GetSponsorDashboardUseCase.execute() is called with sponsor ID
|
||||
const result = await getSponsorDashboardUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
// Then: The result should contain dashboard metrics
|
||||
expect(result.isOk()).toBe(true);
|
||||
const dashboard = result.unwrap();
|
||||
|
||||
// And: The sponsor name should be correct
|
||||
expect(dashboard.sponsorName).toBe('Test Company');
|
||||
|
||||
// And: The metrics should show correct values
|
||||
expect(dashboard.metrics.impressions).toBeGreaterThan(0);
|
||||
expect(dashboard.metrics.races).toBe(5); // 3 + 2
|
||||
expect(dashboard.metrics.drivers).toBe(8); // 5 + 3
|
||||
expect(dashboard.metrics.exposure).toBeGreaterThan(0);
|
||||
|
||||
// And: The sponsored leagues should contain both leagues
|
||||
expect(dashboard.sponsoredLeagues).toHaveLength(2);
|
||||
expect(dashboard.sponsoredLeagues[0].leagueName).toBe('League 1');
|
||||
expect(dashboard.sponsoredLeagues[1].leagueName).toBe('League 2');
|
||||
|
||||
// And: The investment summary should show correct values
|
||||
expect(dashboard.investment.activeSponsorships).toBe(2);
|
||||
expect(dashboard.investment.totalInvestment.amount).toBe(1500); // 1000 + 500
|
||||
expect(dashboard.investment.costPerThousandViews).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should retrieve dashboard with zero values when sponsor has no sponsorships', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
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: GetSponsorDashboardUseCase.execute() is called with sponsor ID
|
||||
const result = await getSponsorDashboardUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
// Then: The result should contain dashboard metrics with zero values
|
||||
expect(result.isOk()).toBe(true);
|
||||
const dashboard = result.unwrap();
|
||||
|
||||
// And: The sponsor name should be correct
|
||||
expect(dashboard.sponsorName).toBe('Test Company');
|
||||
|
||||
// And: The metrics should show zero values
|
||||
expect(dashboard.metrics.impressions).toBe(0);
|
||||
expect(dashboard.metrics.races).toBe(0);
|
||||
expect(dashboard.metrics.drivers).toBe(0);
|
||||
expect(dashboard.metrics.exposure).toBe(0);
|
||||
|
||||
// And: The sponsored leagues should be empty
|
||||
expect(dashboard.sponsoredLeagues).toHaveLength(0);
|
||||
|
||||
// And: The investment summary should show zero values
|
||||
expect(dashboard.investment.activeSponsorships).toBe(0);
|
||||
expect(dashboard.investment.totalInvestment.amount).toBe(0);
|
||||
expect(dashboard.investment.costPerThousandViews).toBe(0);
|
||||
});
|
||||
|
||||
it('should retrieve dashboard with mixed sponsorship statuses', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await sponsorRepository.create(sponsor);
|
||||
|
||||
// And: The sponsor has 1 active, 1 pending, and 1 completed sponsorship
|
||||
const league1 = League.create({
|
||||
id: 'league-1',
|
||||
name: 'League 1',
|
||||
description: 'Description 1',
|
||||
ownerId: 'owner-1',
|
||||
});
|
||||
await leagueRepository.create(league1);
|
||||
|
||||
const season1 = Season.create({
|
||||
id: 'season-1',
|
||||
leagueId: 'league-1',
|
||||
gameId: 'game-1',
|
||||
name: 'Season 1',
|
||||
startDate: new Date('2025-01-01'),
|
||||
endDate: new Date('2025-12-31'),
|
||||
});
|
||||
await seasonRepository.create(season1);
|
||||
|
||||
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-1',
|
||||
tier: 'secondary',
|
||||
pricing: Money.create(500, 'USD'),
|
||||
status: 'active',
|
||||
});
|
||||
await seasonSponsorshipRepository.create(sponsorship2);
|
||||
|
||||
const sponsorship3 = SeasonSponsorship.create({
|
||||
id: 'sponsorship-3',
|
||||
sponsorId: 'sponsor-123',
|
||||
seasonId: 'season-1',
|
||||
tier: 'secondary',
|
||||
pricing: Money.create(300, 'USD'),
|
||||
status: 'active',
|
||||
});
|
||||
await seasonSponsorshipRepository.create(sponsorship3);
|
||||
|
||||
// When: GetSponsorDashboardUseCase.execute() is called with sponsor ID
|
||||
const result = await getSponsorDashboardUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
// Then: The result should contain dashboard metrics
|
||||
expect(result.isOk()).toBe(true);
|
||||
const dashboard = result.unwrap();
|
||||
|
||||
// And: The investment summary should show only active sponsorships
|
||||
expect(dashboard.investment.activeSponsorships).toBe(3);
|
||||
expect(dashboard.investment.totalInvestment.amount).toBe(1800); // 1000 + 500 + 300
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetSponsorDashboardUseCase - Error Handling', () => {
|
||||
it('should return error when sponsor does not exist', async () => {
|
||||
// Given: No sponsor exists with the given ID
|
||||
// When: GetSponsorDashboardUseCase.execute() is called with non-existent sponsor ID
|
||||
const result = await getSponsorDashboardUseCase.execute({ sponsorId: 'non-existent-sponsor' });
|
||||
|
||||
// Then: Should return an error
|
||||
expect(result.isErr()).toBe(true);
|
||||
const error = result.unwrapErr();
|
||||
expect(error.code).toBe('SPONSOR_NOT_FOUND');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sponsor Dashboard Data Orchestration', () => {
|
||||
it('should correctly aggregate dashboard metrics across multiple sponsorships', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
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',
|
||||
gameId: 'game-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',
|
||||
gameId: 'game-1',
|
||||
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',
|
||||
gameId: 'game-1',
|
||||
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',
|
||||
car: 'GT3',
|
||||
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',
|
||||
car: 'GT3',
|
||||
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',
|
||||
car: 'GT3',
|
||||
scheduledAt: new Date(`2025-0${i}-01`),
|
||||
status: 'completed',
|
||||
});
|
||||
await raceRepository.create(race);
|
||||
}
|
||||
|
||||
// When: GetSponsorDashboardUseCase.execute() is called
|
||||
const result = await getSponsorDashboardUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
// Then: The metrics should be correctly aggregated
|
||||
expect(result.isOk()).toBe(true);
|
||||
const dashboard = result.unwrap();
|
||||
|
||||
// Total drivers: 10 + 5 + 8 = 23
|
||||
expect(dashboard.metrics.drivers).toBe(23);
|
||||
|
||||
// Total races: 5 + 3 + 4 = 12
|
||||
expect(dashboard.metrics.races).toBe(12);
|
||||
|
||||
// Total investment: 1000 + 2000 + 3000 = 6000
|
||||
expect(dashboard.investment.totalInvestment.amount).toBe(6000);
|
||||
|
||||
// Total sponsorships: 3
|
||||
expect(dashboard.investment.activeSponsorships).toBe(3);
|
||||
|
||||
// Cost per thousand views should be calculated correctly
|
||||
expect(dashboard.investment.costPerThousandViews).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should correctly calculate impressions based on completed races and drivers', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await sponsorRepository.create(sponsor);
|
||||
|
||||
// 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);
|
||||
|
||||
const season = Season.create({
|
||||
id: 'season-1',
|
||||
leagueId: 'league-1',
|
||||
gameId: 'game-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',
|
||||
car: 'GT3',
|
||||
scheduledAt: new Date(`2025-0${i}-01`),
|
||||
status: 'completed',
|
||||
});
|
||||
await raceRepository.create(race);
|
||||
}
|
||||
|
||||
// When: GetSponsorDashboardUseCase.execute() is called
|
||||
const result = await getSponsorDashboardUseCase.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 dashboard = result.unwrap();
|
||||
expect(dashboard.metrics.impressions).toBe(5000);
|
||||
});
|
||||
|
||||
it('should correctly determine sponsorship status based on season dates', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await sponsorRepository.create(sponsor);
|
||||
|
||||
// And: The sponsor has sponsorships with different season dates
|
||||
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);
|
||||
|
||||
// Active season (current date is between start and end)
|
||||
const season1 = Season.create({
|
||||
id: 'season-1',
|
||||
leagueId: 'league-1',
|
||||
gameId: 'game-1',
|
||||
name: 'Season 1',
|
||||
startDate: new Date(Date.now() - 86400000),
|
||||
endDate: new Date(Date.now() + 86400000),
|
||||
});
|
||||
await seasonRepository.create(season1);
|
||||
|
||||
// Upcoming season (start date is in the future)
|
||||
const season2 = Season.create({
|
||||
id: 'season-2',
|
||||
leagueId: 'league-2',
|
||||
gameId: 'game-1',
|
||||
name: 'Season 2',
|
||||
startDate: new Date(Date.now() + 86400000),
|
||||
endDate: new Date(Date.now() + 172800000),
|
||||
});
|
||||
await seasonRepository.create(season2);
|
||||
|
||||
// Completed season (end date is in the past)
|
||||
const season3 = Season.create({
|
||||
id: 'season-3',
|
||||
leagueId: 'league-3',
|
||||
gameId: 'game-1',
|
||||
name: 'Season 3',
|
||||
startDate: new Date(Date.now() - 172800000),
|
||||
endDate: new Date(Date.now() - 86400000),
|
||||
});
|
||||
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: 'active',
|
||||
});
|
||||
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: 'active',
|
||||
});
|
||||
await seasonSponsorshipRepository.create(sponsorship3);
|
||||
|
||||
// When: GetSponsorDashboardUseCase.execute() is called
|
||||
const result = await getSponsorDashboardUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
// Then: The sponsored leagues should have correct status
|
||||
expect(result.isOk()).toBe(true);
|
||||
const dashboard = result.unwrap();
|
||||
|
||||
expect(dashboard.sponsoredLeagues).toHaveLength(3);
|
||||
|
||||
// League 1 should be active (current date is between start and end)
|
||||
expect(dashboard.sponsoredLeagues[0].status).toBe('active');
|
||||
|
||||
// League 2 should be upcoming (start date is in the future)
|
||||
expect(dashboard.sponsoredLeagues[1].status).toBe('upcoming');
|
||||
|
||||
// League 3 should be completed (end date is in the past)
|
||||
expect(dashboard.sponsoredLeagues[2].status).toBe('completed');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,339 +0,0 @@
|
||||
/**
|
||||
* Integration Test: Sponsor League Detail Use Case Orchestration
|
||||
*
|
||||
* Tests the orchestration logic of sponsor league detail-related Use Cases:
|
||||
* - GetEntitySponsorshipPricingUseCase: Retrieves sponsorship pricing for leagues
|
||||
* - Validates that Use Cases correctly interact with their Ports (Repositories)
|
||||
* - Uses In-Memory adapters for fast, deterministic testing
|
||||
*
|
||||
* Focus: Business logic orchestration, NOT UI rendering
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
|
||||
import { InMemorySponsorshipPricingRepository } from '../../../adapters/racing/persistence/inmemory/InMemorySponsorshipPricingRepository';
|
||||
import { GetEntitySponsorshipPricingUseCase } from '../../../core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase';
|
||||
import { Logger } from '../../../core/shared/domain/Logger';
|
||||
|
||||
describe('Sponsor League Detail Use Case Orchestration', () => {
|
||||
let sponsorshipPricingRepository: InMemorySponsorshipPricingRepository;
|
||||
let getEntitySponsorshipPricingUseCase: GetEntitySponsorshipPricingUseCase;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeAll(() => {
|
||||
mockLogger = {
|
||||
info: () => {},
|
||||
debug: () => {},
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
} as unknown as Logger;
|
||||
|
||||
sponsorshipPricingRepository = new InMemorySponsorshipPricingRepository(mockLogger);
|
||||
getEntitySponsorshipPricingUseCase = new GetEntitySponsorshipPricingUseCase(
|
||||
sponsorshipPricingRepository,
|
||||
mockLogger,
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
sponsorshipPricingRepository.clear();
|
||||
});
|
||||
|
||||
describe('GetEntitySponsorshipPricingUseCase - Success Path', () => {
|
||||
it('should retrieve sponsorship pricing for a league', async () => {
|
||||
// Given: A league exists with ID "league-123"
|
||||
const leagueId = 'league-123';
|
||||
|
||||
// And: The league has sponsorship pricing configured
|
||||
const pricing = {
|
||||
entityType: 'league' as const,
|
||||
entityId: leagueId,
|
||||
acceptingApplications: true,
|
||||
mainSlot: {
|
||||
price: { amount: 10000, currency: 'USD' },
|
||||
benefits: ['Primary logo placement', 'League page header banner'],
|
||||
},
|
||||
secondarySlots: {
|
||||
price: { amount: 2000, currency: 'USD' },
|
||||
benefits: ['Secondary logo on liveries', 'League page sidebar placement'],
|
||||
},
|
||||
};
|
||||
await sponsorshipPricingRepository.create(pricing);
|
||||
|
||||
// When: GetEntitySponsorshipPricingUseCase.execute() is called
|
||||
const result = await getEntitySponsorshipPricingUseCase.execute({
|
||||
entityType: 'league',
|
||||
entityId: leagueId,
|
||||
});
|
||||
|
||||
// Then: The result should contain sponsorship pricing
|
||||
expect(result.isOk()).toBe(true);
|
||||
const pricingResult = result.unwrap();
|
||||
|
||||
// And: The entity type should be correct
|
||||
expect(pricingResult.entityType).toBe('league');
|
||||
|
||||
// And: The entity ID should be correct
|
||||
expect(pricingResult.entityId).toBe(leagueId);
|
||||
|
||||
// And: The league should be accepting applications
|
||||
expect(pricingResult.acceptingApplications).toBe(true);
|
||||
|
||||
// And: The tiers should contain main slot
|
||||
expect(pricingResult.tiers).toHaveLength(2);
|
||||
expect(pricingResult.tiers[0].name).toBe('main');
|
||||
expect(pricingResult.tiers[0].price.amount).toBe(10000);
|
||||
expect(pricingResult.tiers[0].price.currency).toBe('USD');
|
||||
expect(pricingResult.tiers[0].benefits).toContain('Primary logo placement');
|
||||
|
||||
// And: The tiers should contain secondary slot
|
||||
expect(pricingResult.tiers[1].name).toBe('secondary');
|
||||
expect(pricingResult.tiers[1].price.amount).toBe(2000);
|
||||
expect(pricingResult.tiers[1].price.currency).toBe('USD');
|
||||
expect(pricingResult.tiers[1].benefits).toContain('Secondary logo on liveries');
|
||||
});
|
||||
|
||||
it('should retrieve sponsorship pricing with only main slot', async () => {
|
||||
// Given: A league exists with ID "league-123"
|
||||
const leagueId = 'league-123';
|
||||
|
||||
// And: The league has sponsorship pricing configured with only main slot
|
||||
const pricing = {
|
||||
entityType: 'league' as const,
|
||||
entityId: leagueId,
|
||||
acceptingApplications: true,
|
||||
mainSlot: {
|
||||
price: { amount: 10000, currency: 'USD' },
|
||||
benefits: ['Primary logo placement', 'League page header banner'],
|
||||
},
|
||||
};
|
||||
await sponsorshipPricingRepository.create(pricing);
|
||||
|
||||
// When: GetEntitySponsorshipPricingUseCase.execute() is called
|
||||
const result = await getEntitySponsorshipPricingUseCase.execute({
|
||||
entityType: 'league',
|
||||
entityId: leagueId,
|
||||
});
|
||||
|
||||
// Then: The result should contain sponsorship pricing
|
||||
expect(result.isOk()).toBe(true);
|
||||
const pricingResult = result.unwrap();
|
||||
|
||||
// And: The tiers should contain only main slot
|
||||
expect(pricingResult.tiers).toHaveLength(1);
|
||||
expect(pricingResult.tiers[0].name).toBe('main');
|
||||
expect(pricingResult.tiers[0].price.amount).toBe(10000);
|
||||
});
|
||||
|
||||
it('should retrieve sponsorship pricing with custom requirements', async () => {
|
||||
// Given: A league exists with ID "league-123"
|
||||
const leagueId = 'league-123';
|
||||
|
||||
// And: The league has sponsorship pricing configured with custom requirements
|
||||
const pricing = {
|
||||
entityType: 'league' as const,
|
||||
entityId: leagueId,
|
||||
acceptingApplications: true,
|
||||
customRequirements: 'Must have racing experience',
|
||||
mainSlot: {
|
||||
price: { amount: 10000, currency: 'USD' },
|
||||
benefits: ['Primary logo placement'],
|
||||
},
|
||||
};
|
||||
await sponsorshipPricingRepository.create(pricing);
|
||||
|
||||
// When: GetEntitySponsorshipPricingUseCase.execute() is called
|
||||
const result = await getEntitySponsorshipPricingUseCase.execute({
|
||||
entityType: 'league',
|
||||
entityId: leagueId,
|
||||
});
|
||||
|
||||
// Then: The result should contain sponsorship pricing
|
||||
expect(result.isOk()).toBe(true);
|
||||
const pricingResult = result.unwrap();
|
||||
|
||||
// And: The custom requirements should be included
|
||||
expect(pricingResult.customRequirements).toBe('Must have racing experience');
|
||||
});
|
||||
|
||||
it('should retrieve sponsorship pricing with not accepting applications', async () => {
|
||||
// Given: A league exists with ID "league-123"
|
||||
const leagueId = 'league-123';
|
||||
|
||||
// And: The league has sponsorship pricing configured but not accepting applications
|
||||
const pricing = {
|
||||
entityType: 'league' as const,
|
||||
entityId: leagueId,
|
||||
acceptingApplications: false,
|
||||
mainSlot: {
|
||||
price: { amount: 10000, currency: 'USD' },
|
||||
benefits: ['Primary logo placement'],
|
||||
},
|
||||
};
|
||||
await sponsorshipPricingRepository.create(pricing);
|
||||
|
||||
// When: GetEntitySponsorshipPricingUseCase.execute() is called
|
||||
const result = await getEntitySponsorshipPricingUseCase.execute({
|
||||
entityType: 'league',
|
||||
entityId: leagueId,
|
||||
});
|
||||
|
||||
// Then: The result should contain sponsorship pricing
|
||||
expect(result.isOk()).toBe(true);
|
||||
const pricingResult = result.unwrap();
|
||||
|
||||
// And: The league should not be accepting applications
|
||||
expect(pricingResult.acceptingApplications).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetEntitySponsorshipPricingUseCase - Error Handling', () => {
|
||||
it('should return error when pricing is not configured', async () => {
|
||||
// Given: A league exists with ID "league-123"
|
||||
const leagueId = 'league-123';
|
||||
|
||||
// And: The league has no sponsorship pricing configured
|
||||
// When: GetEntitySponsorshipPricingUseCase.execute() is called
|
||||
const result = await getEntitySponsorshipPricingUseCase.execute({
|
||||
entityType: 'league',
|
||||
entityId: leagueId,
|
||||
});
|
||||
|
||||
// Then: Should return an error
|
||||
expect(result.isErr()).toBe(true);
|
||||
const error = result.unwrapErr();
|
||||
expect(error.code).toBe('PRICING_NOT_CONFIGURED');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sponsor League Detail Data Orchestration', () => {
|
||||
it('should correctly retrieve sponsorship pricing with all tiers', async () => {
|
||||
// Given: A league exists with ID "league-123"
|
||||
const leagueId = 'league-123';
|
||||
|
||||
// And: The league has sponsorship pricing configured with both main and secondary slots
|
||||
const pricing = {
|
||||
entityType: 'league' as const,
|
||||
entityId: leagueId,
|
||||
acceptingApplications: true,
|
||||
customRequirements: 'Must have racing experience',
|
||||
mainSlot: {
|
||||
price: { amount: 10000, currency: 'USD' },
|
||||
benefits: [
|
||||
'Primary logo placement on all liveries',
|
||||
'League page header banner',
|
||||
'Race results page branding',
|
||||
'Social media feature posts',
|
||||
'Newsletter sponsor spot',
|
||||
],
|
||||
},
|
||||
secondarySlots: {
|
||||
price: { amount: 2000, currency: 'USD' },
|
||||
benefits: [
|
||||
'Secondary logo on liveries',
|
||||
'League page sidebar placement',
|
||||
'Race results mention',
|
||||
'Social media mentions',
|
||||
],
|
||||
},
|
||||
};
|
||||
await sponsorshipPricingRepository.create(pricing);
|
||||
|
||||
// When: GetEntitySponsorshipPricingUseCase.execute() is called
|
||||
const result = await getEntitySponsorshipPricingUseCase.execute({
|
||||
entityType: 'league',
|
||||
entityId: leagueId,
|
||||
});
|
||||
|
||||
// Then: The sponsorship pricing should be correctly retrieved
|
||||
expect(result.isOk()).toBe(true);
|
||||
const pricingResult = result.unwrap();
|
||||
|
||||
// And: The entity type should be correct
|
||||
expect(pricingResult.entityType).toBe('league');
|
||||
|
||||
// And: The entity ID should be correct
|
||||
expect(pricingResult.entityId).toBe(leagueId);
|
||||
|
||||
// And: The league should be accepting applications
|
||||
expect(pricingResult.acceptingApplications).toBe(true);
|
||||
|
||||
// And: The custom requirements should be included
|
||||
expect(pricingResult.customRequirements).toBe('Must have racing experience');
|
||||
|
||||
// And: The tiers should contain both main and secondary slots
|
||||
expect(pricingResult.tiers).toHaveLength(2);
|
||||
|
||||
// And: The main slot should have correct price and benefits
|
||||
expect(pricingResult.tiers[0].name).toBe('main');
|
||||
expect(pricingResult.tiers[0].price.amount).toBe(10000);
|
||||
expect(pricingResult.tiers[0].price.currency).toBe('USD');
|
||||
expect(pricingResult.tiers[0].benefits).toHaveLength(5);
|
||||
expect(pricingResult.tiers[0].benefits).toContain('Primary logo placement on all liveries');
|
||||
|
||||
// And: The secondary slot should have correct price and benefits
|
||||
expect(pricingResult.tiers[1].name).toBe('secondary');
|
||||
expect(pricingResult.tiers[1].price.amount).toBe(2000);
|
||||
expect(pricingResult.tiers[1].price.currency).toBe('USD');
|
||||
expect(pricingResult.tiers[1].benefits).toHaveLength(4);
|
||||
expect(pricingResult.tiers[1].benefits).toContain('Secondary logo on liveries');
|
||||
});
|
||||
|
||||
it('should correctly retrieve sponsorship pricing for different entity types', async () => {
|
||||
// Given: A league exists with ID "league-123"
|
||||
const leagueId = 'league-123';
|
||||
|
||||
// And: The league has sponsorship pricing configured
|
||||
const leaguePricing = {
|
||||
entityType: 'league' as const,
|
||||
entityId: leagueId,
|
||||
acceptingApplications: true,
|
||||
mainSlot: {
|
||||
price: { amount: 10000, currency: 'USD' },
|
||||
benefits: ['Primary logo placement'],
|
||||
},
|
||||
};
|
||||
await sponsorshipPricingRepository.create(leaguePricing);
|
||||
|
||||
// And: A team exists with ID "team-456"
|
||||
const teamId = 'team-456';
|
||||
|
||||
// And: The team has sponsorship pricing configured
|
||||
const teamPricing = {
|
||||
entityType: 'team' as const,
|
||||
entityId: teamId,
|
||||
acceptingApplications: true,
|
||||
mainSlot: {
|
||||
price: { amount: 5000, currency: 'USD' },
|
||||
benefits: ['Team logo placement'],
|
||||
},
|
||||
};
|
||||
await sponsorshipPricingRepository.create(teamPricing);
|
||||
|
||||
// When: GetEntitySponsorshipPricingUseCase.execute() is called for league
|
||||
const leagueResult = await getEntitySponsorshipPricingUseCase.execute({
|
||||
entityType: 'league',
|
||||
entityId: leagueId,
|
||||
});
|
||||
|
||||
// Then: The league pricing should be retrieved
|
||||
expect(leagueResult.isOk()).toBe(true);
|
||||
const leaguePricingResult = leagueResult.unwrap();
|
||||
expect(leaguePricingResult.entityType).toBe('league');
|
||||
expect(leaguePricingResult.entityId).toBe(leagueId);
|
||||
expect(leaguePricingResult.tiers[0].price.amount).toBe(10000);
|
||||
|
||||
// When: GetEntitySponsorshipPricingUseCase.execute() is called for team
|
||||
const teamResult = await getEntitySponsorshipPricingUseCase.execute({
|
||||
entityType: 'team',
|
||||
entityId: teamId,
|
||||
});
|
||||
|
||||
// Then: The team pricing should be retrieved
|
||||
expect(teamResult.isOk()).toBe(true);
|
||||
const teamPricingResult = teamResult.unwrap();
|
||||
expect(teamPricingResult.entityType).toBe('team');
|
||||
expect(teamPricingResult.entityId).toBe(teamId);
|
||||
expect(teamPricingResult.tiers[0].price.amount).toBe(5000);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,658 +0,0 @@
|
||||
/**
|
||||
* Integration Test: Sponsor Leagues Use Case Orchestration
|
||||
*
|
||||
* Tests the orchestration logic of sponsor leagues-related Use Cases:
|
||||
* - 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, 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 leagueMembershipRepository: InMemoryLeagueMembershipRepository;
|
||||
let raceRepository: InMemoryRaceRepository;
|
||||
let getSponsorSponsorshipsUseCase: GetSponsorSponsorshipsUseCase;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeAll(() => {
|
||||
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(() => {
|
||||
sponsorRepository.clear();
|
||||
seasonSponsorshipRepository.clear();
|
||||
seasonRepository.clear();
|
||||
leagueRepository.clear();
|
||||
leagueMembershipRepository.clear();
|
||||
raceRepository.clear();
|
||||
});
|
||||
|
||||
describe('GetSponsorSponsorshipsUseCase - Success Path', () => {
|
||||
it('should retrieve all sponsorships for a sponsor', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
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 sponsorships with minimal data', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
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 sponsorships with empty result when no sponsorships exist', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
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('GetSponsorSponsorshipsUseCase - Error Handling', () => {
|
||||
it('should return error when sponsor does not exist', async () => {
|
||||
// Given: No sponsor exists with the given ID
|
||||
// When: GetSponsorSponsorshipsUseCase.execute() is called with non-existent sponsor ID
|
||||
const result = await getSponsorSponsorshipsUseCase.execute({ sponsorId: 'non-existent-sponsor' });
|
||||
|
||||
// Then: Should return an error
|
||||
expect(result.isErr()).toBe(true);
|
||||
const error = result.unwrapErr();
|
||||
expect(error.code).toBe('SPONSOR_NOT_FOUND');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sponsor Leagues Data Orchestration', () => {
|
||||
it('should correctly aggregate sponsorship metrics across multiple sponsorships', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
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 correctly calculate impressions based on completed races and drivers', async () => {
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
const sponsor = Sponsor.create({
|
||||
id: 'sponsor-123',
|
||||
name: 'Test Company',
|
||||
contactEmail: 'test@example.com',
|
||||
});
|
||||
await sponsorRepository.create(sponsor);
|
||||
|
||||
// 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);
|
||||
|
||||
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"
|
||||
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
|
||||
const result = await getSponsorSponsorshipsUseCase.execute({ sponsorId: 'sponsor-123' });
|
||||
|
||||
// Then: Platform fees and net amounts should be calculated correctly
|
||||
expect(result.isOk()).toBe(true);
|
||||
const sponsorships = result.unwrap();
|
||||
|
||||
// Platform fee = 10% of pricing = 100
|
||||
expect(sponsorships.sponsorships[0].financials.platformFee.amount).toBe(100);
|
||||
|
||||
// Net amount = pricing - platform fee = 1000 - 100 = 900
|
||||
expect(sponsorships.sponsorships[0].financials.netAmount.amount).toBe(900);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,392 +0,0 @@
|
||||
/**
|
||||
* Integration Test: Sponsor Settings Use Case Orchestration
|
||||
*
|
||||
* Tests the orchestration logic of sponsor settings-related Use Cases:
|
||||
* - GetSponsorProfileUseCase: Retrieves sponsor profile information
|
||||
* - UpdateSponsorProfileUseCase: Updates sponsor profile information
|
||||
* - GetNotificationPreferencesUseCase: Retrieves notification preferences
|
||||
* - UpdateNotificationPreferencesUseCase: Updates notification preferences
|
||||
* - GetPrivacySettingsUseCase: Retrieves privacy settings
|
||||
* - UpdatePrivacySettingsUseCase: Updates privacy settings
|
||||
* - DeleteSponsorAccountUseCase: Deletes sponsor account
|
||||
* - Validates that Use Cases correctly interact with their Ports (Repositories, Event Publishers)
|
||||
* - 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 { InMemoryEventPublisher } from '../../../adapters/events/InMemoryEventPublisher';
|
||||
import { GetSponsorProfileUseCase } from '../../../core/sponsors/use-cases/GetSponsorProfileUseCase';
|
||||
import { UpdateSponsorProfileUseCase } from '../../../core/sponsors/use-cases/UpdateSponsorProfileUseCase';
|
||||
import { GetNotificationPreferencesUseCase } from '../../../core/sponsors/use-cases/GetNotificationPreferencesUseCase';
|
||||
import { UpdateNotificationPreferencesUseCase } from '../../../core/sponsors/use-cases/UpdateNotificationPreferencesUseCase';
|
||||
import { GetPrivacySettingsUseCase } from '../../../core/sponsors/use-cases/GetPrivacySettingsUseCase';
|
||||
import { UpdatePrivacySettingsUseCase } from '../../../core/sponsors/use-cases/UpdatePrivacySettingsUseCase';
|
||||
import { DeleteSponsorAccountUseCase } from '../../../core/sponsors/use-cases/DeleteSponsorAccountUseCase';
|
||||
import { GetSponsorProfileQuery } from '../../../core/sponsors/ports/GetSponsorProfileQuery';
|
||||
import { UpdateSponsorProfileCommand } from '../../../core/sponsors/ports/UpdateSponsorProfileCommand';
|
||||
import { GetNotificationPreferencesQuery } from '../../../core/sponsors/ports/GetNotificationPreferencesQuery';
|
||||
import { UpdateNotificationPreferencesCommand } from '../../../core/sponsors/ports/UpdateNotificationPreferencesCommand';
|
||||
import { GetPrivacySettingsQuery } from '../../../core/sponsors/ports/GetPrivacySettingsQuery';
|
||||
import { UpdatePrivacySettingsCommand } from '../../../core/sponsors/ports/UpdatePrivacySettingsCommand';
|
||||
import { DeleteSponsorAccountCommand } from '../../../core/sponsors/ports/DeleteSponsorAccountCommand';
|
||||
|
||||
describe('Sponsor Settings Use Case Orchestration', () => {
|
||||
let sponsorRepository: InMemorySponsorRepository;
|
||||
let eventPublisher: InMemoryEventPublisher;
|
||||
let getSponsorProfileUseCase: GetSponsorProfileUseCase;
|
||||
let updateSponsorProfileUseCase: UpdateSponsorProfileUseCase;
|
||||
let getNotificationPreferencesUseCase: GetNotificationPreferencesUseCase;
|
||||
let updateNotificationPreferencesUseCase: UpdateNotificationPreferencesUseCase;
|
||||
let getPrivacySettingsUseCase: GetPrivacySettingsUseCase;
|
||||
let updatePrivacySettingsUseCase: UpdatePrivacySettingsUseCase;
|
||||
let deleteSponsorAccountUseCase: DeleteSponsorAccountUseCase;
|
||||
|
||||
beforeAll(() => {
|
||||
// TODO: Initialize In-Memory repositories and event publisher
|
||||
// sponsorRepository = new InMemorySponsorRepository();
|
||||
// eventPublisher = new InMemoryEventPublisher();
|
||||
// getSponsorProfileUseCase = new GetSponsorProfileUseCase({
|
||||
// sponsorRepository,
|
||||
// eventPublisher,
|
||||
// });
|
||||
// updateSponsorProfileUseCase = new UpdateSponsorProfileUseCase({
|
||||
// sponsorRepository,
|
||||
// eventPublisher,
|
||||
// });
|
||||
// getNotificationPreferencesUseCase = new GetNotificationPreferencesUseCase({
|
||||
// sponsorRepository,
|
||||
// eventPublisher,
|
||||
// });
|
||||
// updateNotificationPreferencesUseCase = new UpdateNotificationPreferencesUseCase({
|
||||
// sponsorRepository,
|
||||
// eventPublisher,
|
||||
// });
|
||||
// getPrivacySettingsUseCase = new GetPrivacySettingsUseCase({
|
||||
// sponsorRepository,
|
||||
// eventPublisher,
|
||||
// });
|
||||
// updatePrivacySettingsUseCase = new UpdatePrivacySettingsUseCase({
|
||||
// sponsorRepository,
|
||||
// eventPublisher,
|
||||
// });
|
||||
// deleteSponsorAccountUseCase = new DeleteSponsorAccountUseCase({
|
||||
// sponsorRepository,
|
||||
// eventPublisher,
|
||||
// });
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// TODO: Clear all In-Memory repositories before each test
|
||||
// sponsorRepository.clear();
|
||||
// eventPublisher.clear();
|
||||
});
|
||||
|
||||
describe('GetSponsorProfileUseCase - Success Path', () => {
|
||||
it('should retrieve sponsor profile information', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Sponsor with complete profile
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: The sponsor has company name "Test Company"
|
||||
// And: The sponsor has contact name "John Doe"
|
||||
// And: The sponsor has contact email "john@example.com"
|
||||
// And: The sponsor has contact phone "+1234567890"
|
||||
// And: The sponsor has website URL "https://testcompany.com"
|
||||
// And: The sponsor has company description "Test description"
|
||||
// And: The sponsor has industry "Technology"
|
||||
// And: The sponsor has address "123 Test St"
|
||||
// And: The sponsor has tax ID "TAX123"
|
||||
// When: GetSponsorProfileUseCase.execute() is called with sponsor ID
|
||||
// Then: The result should show all profile information
|
||||
// And: EventPublisher should emit SponsorProfileAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve profile with minimal data', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Sponsor with minimal profile
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: The sponsor has company name "Test Company"
|
||||
// And: The sponsor has contact email "john@example.com"
|
||||
// When: GetSponsorProfileUseCase.execute() is called with sponsor ID
|
||||
// Then: The result should show available profile information
|
||||
// And: EventPublisher should emit SponsorProfileAccessedEvent
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetSponsorProfileUseCase - 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: GetSponsorProfileUseCase.execute() is called with non-existent sponsor ID
|
||||
// Then: Should throw SponsorNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
|
||||
describe('UpdateSponsorProfileUseCase - Success Path', () => {
|
||||
it('should update sponsor profile information', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Update sponsor profile
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// When: UpdateSponsorProfileUseCase.execute() is called with updated profile data
|
||||
// Then: The sponsor profile should be updated
|
||||
// And: The updated data should be retrievable
|
||||
// And: EventPublisher should emit SponsorProfileUpdatedEvent
|
||||
});
|
||||
|
||||
it('should update sponsor profile with partial data', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Update partial profile
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// When: UpdateSponsorProfileUseCase.execute() is called with partial profile data
|
||||
// Then: Only the provided fields should be updated
|
||||
// And: Other fields should remain unchanged
|
||||
// And: EventPublisher should emit SponsorProfileUpdatedEvent
|
||||
});
|
||||
});
|
||||
|
||||
describe('UpdateSponsorProfileUseCase - Validation', () => {
|
||||
it('should reject update with invalid email', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Invalid email format
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// When: UpdateSponsorProfileUseCase.execute() is called with invalid email
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should reject update with invalid phone', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Invalid phone format
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// When: UpdateSponsorProfileUseCase.execute() is called with invalid phone
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
|
||||
it('should reject update with invalid URL', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Invalid URL format
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// When: UpdateSponsorProfileUseCase.execute() is called with invalid URL
|
||||
// Then: Should throw ValidationError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
|
||||
describe('UpdateSponsorProfileUseCase - 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: UpdateSponsorProfileUseCase.execute() is called with non-existent sponsor ID
|
||||
// Then: Should throw SponsorNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetNotificationPreferencesUseCase - Success Path', () => {
|
||||
it('should retrieve notification preferences', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Sponsor with notification preferences
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: The sponsor has notification preferences configured
|
||||
// When: GetNotificationPreferencesUseCase.execute() is called with sponsor ID
|
||||
// Then: The result should show all notification options
|
||||
// And: Each option should show its enabled/disabled status
|
||||
// And: EventPublisher should emit NotificationPreferencesAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve default notification preferences', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Sponsor with default preferences
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: The sponsor has default notification preferences
|
||||
// When: GetNotificationPreferencesUseCase.execute() is called with sponsor ID
|
||||
// Then: The result should show default preferences
|
||||
// And: EventPublisher should emit NotificationPreferencesAccessedEvent
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetNotificationPreferencesUseCase - 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: GetNotificationPreferencesUseCase.execute() is called with non-existent sponsor ID
|
||||
// Then: Should throw SponsorNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
|
||||
describe('UpdateNotificationPreferencesUseCase - Success Path', () => {
|
||||
it('should update notification preferences', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Update notification preferences
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// When: UpdateNotificationPreferencesUseCase.execute() is called with updated preferences
|
||||
// Then: The notification preferences should be updated
|
||||
// And: The updated preferences should be retrievable
|
||||
// And: EventPublisher should emit NotificationPreferencesUpdatedEvent
|
||||
});
|
||||
|
||||
it('should toggle individual notification preferences', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Toggle notification preference
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// When: UpdateNotificationPreferencesUseCase.execute() is called to toggle a preference
|
||||
// Then: Only the toggled preference should change
|
||||
// And: Other preferences should remain unchanged
|
||||
// And: EventPublisher should emit NotificationPreferencesUpdatedEvent
|
||||
});
|
||||
});
|
||||
|
||||
describe('UpdateNotificationPreferencesUseCase - 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: UpdateNotificationPreferencesUseCase.execute() is called with non-existent sponsor ID
|
||||
// Then: Should throw SponsorNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetPrivacySettingsUseCase - Success Path', () => {
|
||||
it('should retrieve privacy settings', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Sponsor with privacy settings
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: The sponsor has privacy settings configured
|
||||
// When: GetPrivacySettingsUseCase.execute() is called with sponsor ID
|
||||
// Then: The result should show all privacy options
|
||||
// And: Each option should show its enabled/disabled status
|
||||
// And: EventPublisher should emit PrivacySettingsAccessedEvent
|
||||
});
|
||||
|
||||
it('should retrieve default privacy settings', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Sponsor with default privacy settings
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: The sponsor has default privacy settings
|
||||
// When: GetPrivacySettingsUseCase.execute() is called with sponsor ID
|
||||
// Then: The result should show default privacy settings
|
||||
// And: EventPublisher should emit PrivacySettingsAccessedEvent
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetPrivacySettingsUseCase - 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: GetPrivacySettingsUseCase.execute() is called with non-existent sponsor ID
|
||||
// Then: Should throw SponsorNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
|
||||
describe('UpdatePrivacySettingsUseCase - Success Path', () => {
|
||||
it('should update privacy settings', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Update privacy settings
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// When: UpdatePrivacySettingsUseCase.execute() is called with updated settings
|
||||
// Then: The privacy settings should be updated
|
||||
// And: The updated settings should be retrievable
|
||||
// And: EventPublisher should emit PrivacySettingsUpdatedEvent
|
||||
});
|
||||
|
||||
it('should toggle individual privacy settings', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Toggle privacy setting
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// When: UpdatePrivacySettingsUseCase.execute() is called to toggle a setting
|
||||
// Then: Only the toggled setting should change
|
||||
// And: Other settings should remain unchanged
|
||||
// And: EventPublisher should emit PrivacySettingsUpdatedEvent
|
||||
});
|
||||
});
|
||||
|
||||
describe('UpdatePrivacySettingsUseCase - 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: UpdatePrivacySettingsUseCase.execute() is called with non-existent sponsor ID
|
||||
// Then: Should throw SponsorNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
|
||||
describe('DeleteSponsorAccountUseCase - Success Path', () => {
|
||||
it('should delete sponsor account', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Delete sponsor account
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// When: DeleteSponsorAccountUseCase.execute() is called with sponsor ID
|
||||
// Then: The sponsor account should be deleted
|
||||
// And: The sponsor should no longer be retrievable
|
||||
// And: EventPublisher should emit SponsorAccountDeletedEvent
|
||||
});
|
||||
});
|
||||
|
||||
describe('DeleteSponsorAccountUseCase - 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: DeleteSponsorAccountUseCase.execute() is called with non-existent sponsor ID
|
||||
// Then: Should throw SponsorNotFoundError
|
||||
// And: EventPublisher should NOT emit any events
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sponsor Settings Data Orchestration', () => {
|
||||
it('should correctly update sponsor profile', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Profile update orchestration
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: The sponsor has initial profile data
|
||||
// When: UpdateSponsorProfileUseCase.execute() is called with new data
|
||||
// Then: The profile should be updated in the repository
|
||||
// And: The updated data should be retrievable
|
||||
// And: EventPublisher should emit SponsorProfileUpdatedEvent
|
||||
});
|
||||
|
||||
it('should correctly update notification preferences', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Notification preferences update orchestration
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: The sponsor has initial notification preferences
|
||||
// When: UpdateNotificationPreferencesUseCase.execute() is called with new preferences
|
||||
// Then: The preferences should be updated in the repository
|
||||
// And: The updated preferences should be retrievable
|
||||
// And: EventPublisher should emit NotificationPreferencesUpdatedEvent
|
||||
});
|
||||
|
||||
it('should correctly update privacy settings', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Privacy settings update orchestration
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// And: The sponsor has initial privacy settings
|
||||
// When: UpdatePrivacySettingsUseCase.execute() is called with new settings
|
||||
// Then: The settings should be updated in the repository
|
||||
// And: The updated settings should be retrievable
|
||||
// And: EventPublisher should emit PrivacySettingsUpdatedEvent
|
||||
});
|
||||
|
||||
it('should correctly delete sponsor account', async () => {
|
||||
// TODO: Implement test
|
||||
// Scenario: Account deletion orchestration
|
||||
// Given: A sponsor exists with ID "sponsor-123"
|
||||
// When: DeleteSponsorAccountUseCase.execute() is called
|
||||
// Then: The sponsor should be deleted from the repository
|
||||
// And: The sponsor should no longer be retrievable
|
||||
// And: EventPublisher should emit SponsorAccountDeletedEvent
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user