integration tests

This commit is contained in:
2026-01-23 14:51:33 +01:00
parent 34eae53184
commit 95276df5af
18 changed files with 2875 additions and 3692 deletions

View File

@@ -0,0 +1,175 @@
import { beforeEach, describe, expect, it } from 'vitest';
import { LeaguesTestContext } from '../LeaguesTestContext';
import { League } from '../../../../core/racing/domain/entities/League';
import { Season } from '../../../../core/racing/domain/entities/season/Season';
import { SeasonSponsorship } from '../../../../core/racing/domain/entities/season/SeasonSponsorship';
import { Money } from '../../../../core/racing/domain/value-objects/Money';
import { GetSeasonSponsorshipsUseCase } from '../../../../core/racing/application/use-cases/GetSeasonSponsorshipsUseCase';
import { LeagueMembership } from '../../../../core/racing/domain/entities/LeagueMembership';
import { Race } from '../../../../core/racing/domain/entities/Race';
describe('League Sponsorships - GetSeasonSponsorshipsUseCase', () => {
let context: LeaguesTestContext;
let useCase: GetSeasonSponsorshipsUseCase;
beforeEach(() => {
context = new LeaguesTestContext();
context.clear();
useCase = new GetSeasonSponsorshipsUseCase(
context.seasonSponsorshipRepository,
context.seasonRepository,
context.racingLeagueRepository,
context.leagueMembershipRepository,
context.raceRepository,
);
});
const seedLeague = async (params: { leagueId: string }) => {
const league = League.create({
id: params.leagueId,
name: 'League 1',
description: 'League used for sponsorship integration tests',
ownerId: 'owner-1',
});
await context.racingLeagueRepository.create(league);
return league;
};
const seedSeason = async (params: { seasonId: string; leagueId: string }) => {
const season = Season.create({
id: params.seasonId,
leagueId: params.leagueId,
gameId: 'iracing',
name: 'Season 1',
status: 'active',
startDate: new Date('2025-01-01T00:00:00.000Z'),
endDate: new Date('2025-02-01T00:00:00.000Z'),
});
await context.seasonRepository.create(season);
return season;
};
const seedLeagueMembers = async (params: { leagueId: string; count: number }) => {
for (let i = 0; i < params.count; i++) {
const membership = LeagueMembership.create({
id: `membership-${i + 1}`,
leagueId: params.leagueId,
driverId: `driver-${i + 1}`,
role: 'member',
status: 'active',
});
await context.leagueMembershipRepository.saveMembership(membership);
}
};
const seedRaces = async (params: { leagueId: string }) => {
await context.raceRepository.create(
Race.create({
id: 'race-1',
leagueId: params.leagueId,
track: 'Track 1',
car: 'GT3',
scheduledAt: new Date('2025-01-10T20:00:00.000Z'),
status: 'completed',
}),
);
await context.raceRepository.create(
Race.create({
id: 'race-2',
leagueId: params.leagueId,
track: 'Track 2',
car: 'GT3',
scheduledAt: new Date('2025-01-20T20:00:00.000Z'),
status: 'completed',
}),
);
await context.raceRepository.create(
Race.create({
id: 'race-3',
leagueId: params.leagueId,
track: 'Track 3',
car: 'GT3',
scheduledAt: new Date('2025-01-25T20:00:00.000Z'),
status: 'planned',
}),
);
};
it('returns sponsorships with computed league/season metrics', async () => {
const leagueId = 'league-1';
const seasonId = 'season-1';
await seedLeague({ leagueId });
await seedSeason({ seasonId, leagueId });
await seedLeagueMembers({ leagueId, count: 3 });
await seedRaces({ leagueId });
const sponsorship = SeasonSponsorship.create({
id: 'sponsorship-1',
seasonId,
leagueId,
sponsorId: 'sponsor-1',
tier: 'main',
pricing: Money.create(1000, 'USD'),
status: 'active',
createdAt: new Date('2025-01-01T00:00:00.000Z'),
activatedAt: new Date('2025-01-02T00:00:00.000Z'),
});
await context.seasonSponsorshipRepository.create(sponsorship);
const result = await useCase.execute({ seasonId });
expect(result.isOk()).toBe(true);
const view = result.unwrap();
expect(view.seasonId).toBe(seasonId);
expect(view.sponsorships).toHaveLength(1);
const detail = view.sponsorships[0]!;
expect(detail.id).toBe('sponsorship-1');
expect(detail.leagueId).toBe(leagueId);
expect(detail.leagueName).toBe('League 1');
expect(detail.seasonId).toBe(seasonId);
expect(detail.seasonName).toBe('Season 1');
expect(detail.metrics.drivers).toBe(3);
expect(detail.metrics.races).toBe(3);
expect(detail.metrics.completedRaces).toBe(2);
expect(detail.metrics.impressions).toBe(2 * 3 * 100);
expect(detail.pricing).toEqual({ amount: 1000, currency: 'USD' });
expect(detail.platformFee).toEqual({ amount: 100, currency: 'USD' });
expect(detail.netAmount).toEqual({ amount: 900, currency: 'USD' });
});
it('returns SEASON_NOT_FOUND when season does not exist', async () => {
const result = await useCase.execute({ seasonId: 'missing-season' });
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().code).toBe('SEASON_NOT_FOUND');
});
it('returns LEAGUE_NOT_FOUND when league for season does not exist', async () => {
await context.seasonRepository.create(
Season.create({
id: 'season-1',
leagueId: 'missing-league',
gameId: 'iracing',
name: 'Season 1',
status: 'active',
}),
);
const result = await useCase.execute({ seasonId: 'season-1' });
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().code).toBe('LEAGUE_NOT_FOUND');
});
});

View File

@@ -0,0 +1,236 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { NotificationService } from '../../../../core/notifications/application/ports/NotificationService';
import type { WalletRepository } from '../../../../core/payments/domain/repositories/WalletRepository';
import type { Logger } from '../../../../core/shared/domain/Logger';
import { LeagueWallet } from '../../../../core/racing/domain/entities/league-wallet/LeagueWallet';
import { League } from '../../../../core/racing/domain/entities/League';
import { Season } from '../../../../core/racing/domain/entities/season/Season';
import { SponsorshipRequest } from '../../../../core/racing/domain/entities/SponsorshipRequest';
import { Money } from '../../../../core/racing/domain/value-objects/Money';
import { Sponsor } from '../../../../core/racing/domain/entities/sponsor/Sponsor';
import { SponsorshipPricing } from '../../../../core/racing/domain/value-objects/SponsorshipPricing';
import { ApplyForSponsorshipUseCase } from '../../../../core/racing/application/use-cases/ApplyForSponsorshipUseCase';
import { GetPendingSponsorshipRequestsUseCase } from '../../../../core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase';
import { AcceptSponsorshipRequestUseCase } from '../../../../core/racing/application/use-cases/AcceptSponsorshipRequestUseCase';
import { RejectSponsorshipRequestUseCase } from '../../../../core/racing/application/use-cases/RejectSponsorshipRequestUseCase';
import { LeaguesTestContext } from '../LeaguesTestContext';
import { SponsorTestContext } from '../../sponsor/SponsorTestContext';
import { InMemorySponsorshipRequestRepository } from '../../../../adapters/racing/persistence/inmemory/InMemorySponsorshipRequestRepository';
import { InMemoryLeagueWalletRepository } from '../../../../adapters/racing/persistence/inmemory/InMemoryLeagueWalletRepository';
import { InMemoryWalletRepository } from '../../../../adapters/payments/persistence/inmemory/InMemoryWalletRepository';
const createNoopNotificationService = (): NotificationService =>
({
sendNotification: vi.fn(async () => undefined),
}) as unknown as NotificationService;
const createNoopLogger = (): Logger =>
({
info: () => {},
debug: () => {},
warn: () => {},
error: () => {},
}) as unknown as Logger;
describe('League Sponsorships - Sponsorship Applications', () => {
let leagues: LeaguesTestContext;
let sponsors: SponsorTestContext;
let sponsorshipRequestRepo: InMemorySponsorshipRequestRepository;
let sponsorWalletRepo: WalletRepository;
let leagueWalletRepo: InMemoryLeagueWalletRepository;
beforeEach(() => {
leagues = new LeaguesTestContext();
leagues.clear();
sponsors = new SponsorTestContext();
sponsors.clear();
sponsorshipRequestRepo = new InMemorySponsorshipRequestRepository(createNoopLogger());
sponsorWalletRepo = new InMemoryWalletRepository(createNoopLogger());
leagueWalletRepo = new InMemoryLeagueWalletRepository(createNoopLogger());
});
const seedLeagueAndSeason = async (params: { leagueId: string; seasonId: string }) => {
const league = League.create({
id: params.leagueId,
name: 'League 1',
description: 'League used for sponsorship integration tests',
ownerId: 'owner-1',
});
await leagues.racingLeagueRepository.create(league);
const season = Season.create({
id: params.seasonId,
leagueId: params.leagueId,
gameId: 'iracing',
name: 'Season 1',
status: 'active',
startDate: new Date('2025-01-01T00:00:00.000Z'),
endDate: new Date('2025-02-01T00:00:00.000Z'),
});
await leagues.seasonRepository.create(season);
return { league, season };
};
it('allows a sponsor to apply for a season sponsorship and lists it as pending', async () => {
const leagueId = 'league-1';
const seasonId = 'season-1';
const sponsorId = 'sponsor-1';
await seedLeagueAndSeason({ leagueId, seasonId });
const sponsor = Sponsor.create({
id: sponsorId,
name: 'Acme',
contactEmail: 'acme@example.com',
});
await sponsors.sponsorRepository.create(sponsor);
const pricing = SponsorshipPricing.create({
acceptingApplications: true,
})
.updateMainSlot({
available: true,
maxSlots: 1,
price: Money.create(1000, 'USD'),
benefits: ['logo'],
})
.updateSecondarySlot({
available: true,
maxSlots: 2,
price: Money.create(500, 'USD'),
benefits: ['mention'],
});
await sponsors.sponsorshipPricingRepository.save('season', seasonId, pricing);
const applyUseCase = new ApplyForSponsorshipUseCase(
sponsorshipRequestRepo,
sponsors.sponsorshipPricingRepository,
sponsors.sponsorRepository,
sponsors.logger,
);
const apply = await applyUseCase.execute({
sponsorId,
entityType: 'season',
entityId: seasonId,
tier: 'main',
offeredAmount: 1000,
currency: 'USD',
message: 'We would like to sponsor',
});
expect(apply.isOk()).toBe(true);
const getPending = new GetPendingSponsorshipRequestsUseCase(sponsorshipRequestRepo, sponsors.sponsorRepository);
const pending = await getPending.execute({ entityType: 'season', entityId: seasonId });
expect(pending.isOk()).toBe(true);
const value = pending.unwrap();
expect(value.totalCount).toBe(1);
expect(value.requests[0]!.request.status).toBe('pending');
expect(value.requests[0]!.sponsor?.id.toString()).toBe(sponsorId);
});
it('accepts a pending season sponsorship request, creates a sponsorship, and updates wallets', async () => {
const leagueId = 'league-1';
const seasonId = 'season-1';
const sponsorId = 'sponsor-1';
await seedLeagueAndSeason({ leagueId, seasonId });
const sponsor = Sponsor.create({
id: sponsorId,
name: 'Acme',
contactEmail: 'acme@example.com',
});
await sponsors.sponsorRepository.create(sponsor);
const request = SponsorshipRequest.create({
id: 'req-1',
sponsorId,
entityType: 'season',
entityId: seasonId,
tier: 'main',
offeredAmount: Money.create(1000, 'USD'),
message: 'Please accept',
});
await sponsorshipRequestRepo.create(request);
await sponsorWalletRepo.create({
id: sponsorId,
leagueId: 'n/a',
balance: 1500,
totalRevenue: 0,
totalPlatformFees: 0,
totalWithdrawn: 0,
currency: 'USD',
createdAt: new Date('2025-01-01T00:00:00.000Z'),
});
const leagueWallet = LeagueWallet.create({
id: leagueId,
leagueId,
balance: Money.create(0, 'USD'),
});
await leagueWalletRepo.create(leagueWallet);
const notificationService = createNoopNotificationService();
const acceptUseCase = new AcceptSponsorshipRequestUseCase(
sponsorshipRequestRepo,
leagues.seasonSponsorshipRepository,
leagues.seasonRepository,
notificationService,
async () => ({ success: true, transactionId: 'tx-1' }),
sponsorWalletRepo,
leagueWalletRepo,
createNoopLogger(),
);
const result = await acceptUseCase.execute({ requestId: 'req-1', respondedBy: 'owner-1' });
expect(result.isOk()).toBe(true);
const updatedSponsorWallet = await sponsorWalletRepo.findById(sponsorId);
expect(updatedSponsorWallet?.balance).toBe(500);
const updatedLeagueWallet = await leagueWalletRepo.findById(leagueId);
expect(updatedLeagueWallet?.balance.amount).toBe(900);
expect((notificationService.sendNotification as unknown as ReturnType<typeof vi.fn>)).toHaveBeenCalledTimes(1);
const sponsorships = await leagues.seasonSponsorshipRepository.findBySeasonId(seasonId);
expect(sponsorships).toHaveLength(1);
expect(sponsorships[0]!.status).toBe('active');
});
it('rejects a pending sponsorship request', async () => {
const sponsorId = 'sponsor-1';
const request = SponsorshipRequest.create({
id: 'req-1',
sponsorId,
entityType: 'season',
entityId: 'season-1',
tier: 'main',
offeredAmount: Money.create(1000, 'USD'),
});
await sponsorshipRequestRepo.create(request);
const rejectUseCase = new RejectSponsorshipRequestUseCase(sponsorshipRequestRepo, createNoopLogger());
const result = await rejectUseCase.execute({ requestId: 'req-1', respondedBy: 'owner-1', reason: 'Not a fit' });
expect(result.isOk()).toBe(true);
const updated = await sponsorshipRequestRepo.findById('req-1');
expect(updated?.status).toBe('rejected');
});
});

View File

@@ -0,0 +1,116 @@
import { beforeEach, describe, expect, it } from 'vitest';
import { LeaguesTestContext } from '../LeaguesTestContext';
import { League } from '../../../../core/racing/domain/entities/League';
import { Season } from '../../../../core/racing/domain/entities/season/Season';
import { SeasonSponsorship } from '../../../../core/racing/domain/entities/season/SeasonSponsorship';
import { Money } from '../../../../core/racing/domain/value-objects/Money';
describe('League Sponsorships - Sponsorship Management', () => {
let context: LeaguesTestContext;
beforeEach(() => {
context = new LeaguesTestContext();
context.clear();
});
const seedLeagueAndSeason = async (params: { leagueId: string; seasonId: string }) => {
const league = League.create({
id: params.leagueId,
name: 'League 1',
description: 'League used for sponsorship integration tests',
ownerId: 'owner-1',
});
await context.racingLeagueRepository.create(league);
const season = Season.create({
id: params.seasonId,
leagueId: params.leagueId,
gameId: 'iracing',
name: 'Season 1',
status: 'active',
startDate: new Date('2025-01-01T00:00:00.000Z'),
endDate: new Date('2025-02-01T00:00:00.000Z'),
});
await context.seasonRepository.create(season);
return { league, season };
};
it('adds a season sponsorship to the repository', async () => {
const leagueId = 'league-1';
const seasonId = 'season-1';
await seedLeagueAndSeason({ leagueId, seasonId });
const sponsorship = SeasonSponsorship.create({
id: 'sponsorship-1',
seasonId,
leagueId,
sponsorId: 'sponsor-1',
tier: 'main',
pricing: Money.create(1000, 'USD'),
status: 'active',
});
await context.seasonSponsorshipRepository.create(sponsorship);
const found = await context.seasonSponsorshipRepository.findById('sponsorship-1');
expect(found).not.toBeNull();
expect(found?.id).toBe('sponsorship-1');
expect(found?.seasonId).toBe(seasonId);
expect(found?.leagueId).toBe(leagueId);
});
it('edits sponsorship pricing via repository update', async () => {
const leagueId = 'league-1';
const seasonId = 'season-1';
await seedLeagueAndSeason({ leagueId, seasonId });
const sponsorship = SeasonSponsorship.create({
id: 'sponsorship-1',
seasonId,
leagueId,
sponsorId: 'sponsor-1',
tier: 'main',
pricing: Money.create(1000, 'USD'),
status: 'active',
});
await context.seasonSponsorshipRepository.create(sponsorship);
const updated = sponsorship.withPricing(Money.create(1500, 'USD'));
await context.seasonSponsorshipRepository.update(updated);
const found = await context.seasonSponsorshipRepository.findById('sponsorship-1');
expect(found).not.toBeNull();
expect(found?.pricing.amount).toBe(1500);
expect(found?.pricing.currency).toBe('USD');
});
it('deletes a sponsorship from the repository', async () => {
const leagueId = 'league-1';
const seasonId = 'season-1';
await seedLeagueAndSeason({ leagueId, seasonId });
const sponsorship = SeasonSponsorship.create({
id: 'sponsorship-1',
seasonId,
leagueId,
sponsorId: 'sponsor-1',
tier: 'main',
pricing: Money.create(1000, 'USD'),
status: 'active',
});
await context.seasonSponsorshipRepository.create(sponsorship);
expect(await context.seasonSponsorshipRepository.exists('sponsorship-1')).toBe(true);
await context.seasonSponsorshipRepository.delete('sponsorship-1');
expect(await context.seasonSponsorshipRepository.exists('sponsorship-1')).toBe(false);
const found = await context.seasonSponsorshipRepository.findById('sponsorship-1');
expect(found).toBeNull();
});
});