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