Files
gridpilot.gg/tests/integration/leagues/sponsorships/SponsorshipApplications.test.ts
2026-01-23 14:51:33 +01:00

237 lines
8.4 KiB
TypeScript

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');
});
});