integration tests
This commit is contained in:
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user