import { describe, it, expect, vi, beforeEach } from 'vitest'; import { SponsorshipService } from './SponsorshipService'; import type { SponsorsApiClient } from '../../api/sponsors/SponsorsApiClient'; import type { SponsorshipPricingPresenter } from '../../presenters/SponsorshipPricingPresenter'; import type { SponsorSponsorshipsPresenter } from '../../presenters/SponsorSponsorshipsPresenter'; import type { GetEntitySponsorshipPricingResultDto, SponsorSponsorshipsDto, } from '../../dtos'; import type { SponsorshipPricingViewModel, SponsorSponsorshipsViewModel } from '../../view-models'; describe('SponsorshipService', () => { let service: SponsorshipService; let mockApiClient: SponsorsApiClient; let mockSponsorshipPricingPresenter: SponsorshipPricingPresenter; let mockSponsorSponsorshipsPresenter: SponsorSponsorshipsPresenter; beforeEach(() => { mockApiClient = { getPricing: vi.fn(), getSponsorships: vi.fn(), } as unknown as SponsorsApiClient; mockSponsorshipPricingPresenter = { present: vi.fn(), } as unknown as SponsorshipPricingPresenter; mockSponsorSponsorshipsPresenter = { present: vi.fn(), } as unknown as SponsorSponsorshipsPresenter; service = new SponsorshipService( mockApiClient, mockSponsorshipPricingPresenter, mockSponsorSponsorshipsPresenter ); }); describe('constructor', () => { it('should create instance with injected dependencies', () => { expect(service).toBeInstanceOf(SponsorshipService); }); }); describe('getSponsorshipPricing', () => { it('should fetch sponsorship pricing from API and transform via presenter', async () => { // Arrange const mockDto: GetEntitySponsorshipPricingResultDto = { mainSlotPrice: 10000, secondarySlotPrice: 5000, currency: 'USD', }; const mockViewModel: SponsorshipPricingViewModel = { mainSlotPrice: 10000, secondarySlotPrice: 5000, currency: 'USD', formattedMainSlotPrice: 'USD 10,000', formattedSecondarySlotPrice: 'USD 5,000', priceDifference: 5000, formattedPriceDifference: 'USD 5,000', secondaryDiscountPercentage: 50, } as SponsorshipPricingViewModel; vi.mocked(mockApiClient.getPricing).mockResolvedValue(mockDto); vi.mocked(mockSponsorshipPricingPresenter.present).mockReturnValue(mockViewModel); // Act const result = await service.getSponsorshipPricing(); // Assert expect(mockApiClient.getPricing).toHaveBeenCalled(); expect(mockSponsorshipPricingPresenter.present).toHaveBeenCalledWith(mockDto); expect(result).toEqual(mockViewModel); }); it('should handle different currencies', async () => { // Arrange const mockDto: GetEntitySponsorshipPricingResultDto = { mainSlotPrice: 8000, secondarySlotPrice: 4000, currency: 'EUR', }; const mockViewModel: SponsorshipPricingViewModel = { mainSlotPrice: 8000, secondarySlotPrice: 4000, currency: 'EUR', formattedMainSlotPrice: 'EUR 8,000', formattedSecondarySlotPrice: 'EUR 4,000', priceDifference: 4000, formattedPriceDifference: 'EUR 4,000', secondaryDiscountPercentage: 50, } as SponsorshipPricingViewModel; vi.mocked(mockApiClient.getPricing).mockResolvedValue(mockDto); vi.mocked(mockSponsorshipPricingPresenter.present).mockReturnValue(mockViewModel); // Act const result = await service.getSponsorshipPricing(); // Assert expect(mockApiClient.getPricing).toHaveBeenCalled(); expect(mockSponsorshipPricingPresenter.present).toHaveBeenCalledWith(mockDto); expect(result).toEqual(mockViewModel); expect(result.currency).toBe('EUR'); }); it('should propagate errors from API client', async () => { // Arrange const error = new Error('Failed to fetch pricing'); vi.mocked(mockApiClient.getPricing).mockRejectedValue(error); // Act & Assert await expect(service.getSponsorshipPricing()).rejects.toThrow('Failed to fetch pricing'); expect(mockApiClient.getPricing).toHaveBeenCalled(); expect(mockSponsorshipPricingPresenter.present).not.toHaveBeenCalled(); }); it('should handle zero prices', async () => { // Arrange const mockDto: GetEntitySponsorshipPricingResultDto = { mainSlotPrice: 0, secondarySlotPrice: 0, currency: 'USD', }; const mockViewModel: SponsorshipPricingViewModel = { mainSlotPrice: 0, secondarySlotPrice: 0, currency: 'USD', formattedMainSlotPrice: 'USD 0', formattedSecondarySlotPrice: 'USD 0', priceDifference: 0, formattedPriceDifference: 'USD 0', secondaryDiscountPercentage: 0, } as SponsorshipPricingViewModel; vi.mocked(mockApiClient.getPricing).mockResolvedValue(mockDto); vi.mocked(mockSponsorshipPricingPresenter.present).mockReturnValue(mockViewModel); // Act const result = await service.getSponsorshipPricing(); // Assert expect(mockApiClient.getPricing).toHaveBeenCalled(); expect(mockSponsorshipPricingPresenter.present).toHaveBeenCalledWith(mockDto); expect(result).toEqual(mockViewModel); }); }); describe('getSponsorSponsorships', () => { it('should fetch sponsor sponsorships and transform via presenter', async () => { // Arrange const sponsorId = 'sponsor-123'; const mockDto: SponsorSponsorshipsDto = { sponsorId, sponsorName: 'Sponsor Alpha', sponsorships: [ { id: 'sponsorship-1', leagueId: 'league-1', leagueName: 'League One', seasonId: 'season-1', tier: 'main', status: 'active', amount: 10000, currency: 'USD', }, { id: 'sponsorship-2', leagueId: 'league-2', leagueName: 'League Two', seasonId: 'season-2', tier: 'secondary', status: 'active', amount: 5000, currency: 'USD', }, ], }; const mockViewModel: SponsorSponsorshipsViewModel = { sponsorId, sponsorName: 'Sponsor Alpha', sponsorships: [], totalCount: 2, activeCount: 2, hasSponsorships: true, totalInvestment: 15000, formattedTotalInvestment: 'USD 15,000', } as SponsorSponsorshipsViewModel; vi.mocked(mockApiClient.getSponsorships).mockResolvedValue(mockDto); vi.mocked(mockSponsorSponsorshipsPresenter.present).mockReturnValue(mockViewModel); // Act const result = await service.getSponsorSponsorships(sponsorId); // Assert expect(mockApiClient.getSponsorships).toHaveBeenCalledWith(sponsorId); expect(mockSponsorSponsorshipsPresenter.present).toHaveBeenCalledWith(mockDto); expect(result).toEqual(mockViewModel); }); it('should return null when sponsorships are not found', async () => { // Arrange const sponsorId = 'non-existent'; vi.mocked(mockApiClient.getSponsorships).mockResolvedValue(null); // Act const result = await service.getSponsorSponsorships(sponsorId); // Assert expect(mockApiClient.getSponsorships).toHaveBeenCalledWith(sponsorId); expect(mockSponsorSponsorshipsPresenter.present).not.toHaveBeenCalled(); expect(result).toBeNull(); }); it('should handle empty sponsorships list', async () => { // Arrange const sponsorId = 'sponsor-123'; const mockDto: SponsorSponsorshipsDto = { sponsorId, sponsorName: 'Sponsor Alpha', sponsorships: [], }; const mockViewModel: SponsorSponsorshipsViewModel = { sponsorId, sponsorName: 'Sponsor Alpha', sponsorships: [], totalCount: 0, activeCount: 0, hasSponsorships: false, totalInvestment: 0, formattedTotalInvestment: 'USD 0', } as SponsorSponsorshipsViewModel; vi.mocked(mockApiClient.getSponsorships).mockResolvedValue(mockDto); vi.mocked(mockSponsorSponsorshipsPresenter.present).mockReturnValue(mockViewModel); // Act const result = await service.getSponsorSponsorships(sponsorId); // Assert expect(mockApiClient.getSponsorships).toHaveBeenCalledWith(sponsorId); expect(mockSponsorSponsorshipsPresenter.present).toHaveBeenCalledWith(mockDto); expect(result).toEqual(mockViewModel); expect(result?.totalCount).toBe(0); }); it('should propagate errors from API client', async () => { // Arrange const sponsorId = 'sponsor-123'; const error = new Error('Failed to fetch sponsorships'); vi.mocked(mockApiClient.getSponsorships).mockRejectedValue(error); // Act & Assert await expect(service.getSponsorSponsorships(sponsorId)).rejects.toThrow('Failed to fetch sponsorships'); expect(mockApiClient.getSponsorships).toHaveBeenCalledWith(sponsorId); expect(mockSponsorSponsorshipsPresenter.present).not.toHaveBeenCalled(); }); it('should handle multiple sponsorship tiers', async () => { // Arrange const sponsorId = 'sponsor-456'; const mockDto: SponsorSponsorshipsDto = { sponsorId, sponsorName: 'Sponsor Beta', sponsorships: [ { id: 'sponsorship-1', leagueId: 'league-1', leagueName: 'League One', seasonId: 'season-1', tier: 'main', status: 'active', amount: 10000, currency: 'USD', }, { id: 'sponsorship-2', leagueId: 'league-2', leagueName: 'League Two', seasonId: 'season-2', tier: 'secondary', status: 'pending', amount: 5000, currency: 'USD', }, { id: 'sponsorship-3', leagueId: 'league-3', leagueName: 'League Three', seasonId: 'season-3', tier: 'main', status: 'expired', amount: 10000, currency: 'USD', }, ], }; const mockViewModel: SponsorSponsorshipsViewModel = { sponsorId, sponsorName: 'Sponsor Beta', sponsorships: [], totalCount: 3, activeCount: 1, hasSponsorships: true, totalInvestment: 25000, formattedTotalInvestment: 'USD 25,000', } as SponsorSponsorshipsViewModel; vi.mocked(mockApiClient.getSponsorships).mockResolvedValue(mockDto); vi.mocked(mockSponsorSponsorshipsPresenter.present).mockReturnValue(mockViewModel); // Act const result = await service.getSponsorSponsorships(sponsorId); // Assert expect(mockApiClient.getSponsorships).toHaveBeenCalledWith(sponsorId); expect(mockSponsorSponsorshipsPresenter.present).toHaveBeenCalledWith(mockDto); expect(result).toEqual(mockViewModel); expect(result?.totalCount).toBe(3); expect(result?.activeCount).toBe(1); }); }); });