Files
gridpilot.gg/apps/api/src/domain/sponsor/SponsorService.test.ts
2026-01-16 22:32:55 +01:00

521 lines
18 KiB
TypeScript

import type { GetSponsorBillingUseCase } from '@core/payments/application/use-cases/GetSponsorBillingUseCase';
import type { AcceptSponsorshipRequestUseCase } from '@core/racing/application/use-cases/AcceptSponsorshipRequestUseCase';
import type { CreateSponsorUseCase } from '@core/racing/application/use-cases/CreateSponsorUseCase';
import type { GetEntitySponsorshipPricingUseCase } from '@core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase';
import type { GetPendingSponsorshipRequestsUseCase } from '@core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase';
import type { GetSponsorDashboardInput, GetSponsorDashboardUseCase } from '@core/racing/application/use-cases/GetSponsorDashboardUseCase';
import type { GetSponsorSponsorshipsInput, GetSponsorSponsorshipsUseCase } from '@core/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
import type { GetSponsorsUseCase } from '@core/racing/application/use-cases/GetSponsorsUseCase';
import type { GetSponsorUseCase } from '@core/racing/application/use-cases/GetSponsorUseCase';
import type { RejectSponsorshipRequestUseCase } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase';
import { Sponsor } from '@core/racing/domain/entities/sponsor/Sponsor';
import { Money } from '@core/racing/domain/value-objects/Money';
import type { Logger } from '@core/shared/application/UseCaseOutputPort/UseCaseOutputPort/UseCaseOutputPort_TEMP_TEMP';
import { Result } from '@core/shared/domain/Result';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import type { CreateSponsorInputDTO } from './dtos/CreateSponsorInputDTO';
import { SponsorService } from './SponsorService';
describe('SponsorService', () => {
let service: SponsorService;
let getEntitySponsorshipPricingUseCase: { execute: Mock };
let getSponsorsUseCase: { execute: Mock };
let createSponsorUseCase: { execute: Mock };
let getSponsorDashboardUseCase: { execute: Mock };
let getSponsorSponsorshipsUseCase: { execute: Mock };
let getSponsorUseCase: { execute: Mock };
let getPendingSponsorshipRequestsUseCase: { execute: Mock };
let acceptSponsorshipRequestUseCase: { execute: Mock };
let rejectSponsorshipRequestUseCase: { execute: Mock };
let getSponsorBillingUseCase: { execute: Mock };
let logger: Logger;
beforeEach(() => {
getEntitySponsorshipPricingUseCase = { execute: vi.fn() };
getSponsorsUseCase = { execute: vi.fn() };
createSponsorUseCase = { execute: vi.fn() };
getSponsorDashboardUseCase = { execute: vi.fn() };
getSponsorSponsorshipsUseCase = { execute: vi.fn() };
getSponsorUseCase = { execute: vi.fn() };
getPendingSponsorshipRequestsUseCase = { execute: vi.fn() };
acceptSponsorshipRequestUseCase = { execute: vi.fn() };
rejectSponsorshipRequestUseCase = { execute: vi.fn() };
getSponsorBillingUseCase = { execute: vi.fn() };
logger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
} as unknown as Logger;
service = new SponsorService(
getEntitySponsorshipPricingUseCase as unknown as GetEntitySponsorshipPricingUseCase,
getSponsorsUseCase as unknown as GetSponsorsUseCase,
createSponsorUseCase as unknown as CreateSponsorUseCase,
getSponsorDashboardUseCase as unknown as GetSponsorDashboardUseCase,
getSponsorSponsorshipsUseCase as unknown as GetSponsorSponsorshipsUseCase,
getSponsorUseCase as unknown as GetSponsorUseCase,
getPendingSponsorshipRequestsUseCase as unknown as GetPendingSponsorshipRequestsUseCase,
acceptSponsorshipRequestUseCase as unknown as AcceptSponsorshipRequestUseCase,
rejectSponsorshipRequestUseCase as unknown as RejectSponsorshipRequestUseCase,
getSponsorBillingUseCase as unknown as GetSponsorBillingUseCase,
logger,
);
});
describe('getEntitySponsorshipPricing', () => {
it('returns pricing data on success', async () => {
const output = {
entityType: 'season',
entityId: 'season-1',
acceptingApplications: true,
tiers: [{ name: 'Gold', price: { amount: 500, currency: 'USD' }, benefits: ['Main slot'] }],
};
getEntitySponsorshipPricingUseCase.execute.mockResolvedValue(Result.ok(output));
const result = await service.getEntitySponsorshipPricing();
expect(result).toEqual({
entityType: 'season',
entityId: 'season-1',
pricing: [{ id: 'Gold', level: 'Gold', price: 500, currency: 'USD' }],
});
});
it('returns empty pricing on error', async () => {
getEntitySponsorshipPricingUseCase.execute.mockResolvedValue(Result.err({ code: 'REPOSITORY_ERROR' }));
const result = await service.getEntitySponsorshipPricing();
expect(result).toEqual({
entityType: 'season',
entityId: '',
pricing: [],
});
});
});
describe('getSponsors', () => {
it('returns sponsors on success', async () => {
const sponsors = [
Sponsor.create({
id: 'sponsor-1',
name: 'S1',
contactEmail: 's1@test.com',
createdAt: new Date('2024-01-01T00:00:00Z'),
}),
];
getSponsorsUseCase.execute.mockResolvedValue(Result.ok({ sponsors }));
const result = await service.getSponsors();
expect(result).toEqual({
sponsors: [
{
id: 'sponsor-1',
name: 'S1',
contactEmail: 's1@test.com',
createdAt: new Date('2024-01-01T00:00:00Z'),
},
],
});
});
it('returns empty list on error', async () => {
getSponsorsUseCase.execute.mockResolvedValue(Result.err({ code: 'REPOSITORY_ERROR' }));
const result = await service.getSponsors();
expect(result).toEqual({ sponsors: [] });
});
});
describe('createSponsor', () => {
it('returns created sponsor on success', async () => {
const input: CreateSponsorInputDTO = { name: 'Test', contactEmail: 'test@example.com' };
const sponsor = Sponsor.create({
id: 'sponsor-1',
name: 'Test',
contactEmail: 'test@example.com',
createdAt: new Date('2024-01-01T00:00:00Z'),
});
createSponsorUseCase.execute.mockResolvedValue(Result.ok({ sponsor }));
const result = await service.createSponsor(input);
expect(result).toEqual({
sponsor: {
id: 'sponsor-1',
name: 'Test',
contactEmail: 'test@example.com',
createdAt: new Date('2024-01-01T00:00:00Z'),
},
});
});
it('throws on error', async () => {
const input: CreateSponsorInputDTO = { name: 'Test', contactEmail: 'test@example.com' };
createSponsorUseCase.execute.mockResolvedValue(
Result.err({ code: 'VALIDATION_ERROR', details: { message: 'Invalid' } }),
);
await expect(service.createSponsor(input)).rejects.toThrow('Invalid');
});
it('throws using error.message when details.message is missing', async () => {
const input: CreateSponsorInputDTO = { name: 'Test', contactEmail: 'test@example.com' };
createSponsorUseCase.execute.mockResolvedValue(Result.err({ code: 'VALIDATION_ERROR', message: 'Boom' } as never));
await expect(service.createSponsor(input)).rejects.toThrow('Boom');
});
it('throws default message when details.message and message are missing', async () => {
const input: CreateSponsorInputDTO = { name: 'Test', contactEmail: 'test@example.com' };
createSponsorUseCase.execute.mockResolvedValue(Result.err({ code: 'VALIDATION_ERROR' } as never));
await expect(service.createSponsor(input)).rejects.toThrow('Failed to create sponsor');
});
});
describe('getSponsorDashboard', () => {
it('returns dashboard on success', async () => {
const params: GetSponsorDashboardInput = { sponsorId: 's1' };
const output = {
sponsorId: 's1',
sponsorName: 'S1',
metrics: {
impressions: 0,
impressionsChange: 0,
uniqueViewers: 0,
viewersChange: 0,
races: 0,
drivers: 0,
exposure: 0,
exposureChange: 0,
},
sponsoredLeagues: [],
investment: {
activeSponsorships: 0,
totalInvestment: Money.create(0, 'USD'),
costPerThousandViews: 0,
},
sponsorships: {
leagues: [],
teams: [],
drivers: [],
races: [],
platform: [],
},
recentActivity: [],
upcomingRenewals: [],
};
getSponsorDashboardUseCase.execute.mockResolvedValue(Result.ok(output));
const result = await service.getSponsorDashboard(params);
expect(result).toEqual({
sponsorId: 's1',
sponsorName: 'S1',
metrics: output.metrics,
sponsoredLeagues: [],
investment: {
activeSponsorships: 0,
totalInvestment: 0,
costPerThousandViews: 0,
},
sponsorships: {
leagues: [],
teams: [],
drivers: [],
races: [],
platform: [],
},
recentActivity: [],
upcomingRenewals: [],
});
});
it('throws on error', async () => {
const params: GetSponsorDashboardInput = { sponsorId: 's1' };
getSponsorDashboardUseCase.execute.mockResolvedValue(Result.err({ code: 'REPOSITORY_ERROR' }));
await expect(service.getSponsorDashboard(params)).rejects.toThrow('Sponsor dashboard not found');
});
});
describe('getSponsorSponsorships', () => {
it('returns sponsorships on success', async () => {
const params: GetSponsorSponsorshipsInput = { sponsorId: 's1' };
const output = {
sponsor: Sponsor.create({
id: 's1',
name: 'S1',
contactEmail: 's1@example.com',
}),
sponsorships: [],
summary: {
totalSponsorships: 0,
activeSponsorships: 0,
totalInvestment: Money.create(0, 'USD'),
totalPlatformFees: Money.create(0, 'USD'),
},
};
getSponsorSponsorshipsUseCase.execute.mockResolvedValue(Result.ok(output));
const result = await service.getSponsorSponsorships(params);
expect(result).toEqual({
sponsorId: 's1',
sponsorName: 'S1',
sponsorships: [],
summary: {
totalSponsorships: 0,
activeSponsorships: 0,
totalInvestment: 0,
totalPlatformFees: 0,
currency: 'USD',
},
});
});
it('throws on error', async () => {
const params: GetSponsorSponsorshipsInput = { sponsorId: 's1' };
getSponsorSponsorshipsUseCase.execute.mockResolvedValue(Result.err({ code: 'REPOSITORY_ERROR' }));
await expect(service.getSponsorSponsorships(params)).rejects.toThrow(
'Sponsor sponsorships not found',
);
});
});
describe('getSponsor', () => {
it('returns sponsor when found', async () => {
const sponsorId = 's1';
const sponsor = Sponsor.create({
id: sponsorId,
name: 'S1',
contactEmail: 's1@example.com',
createdAt: new Date('2024-01-01T00:00:00Z'),
});
getSponsorUseCase.execute.mockResolvedValue(Result.ok({ sponsor }));
const result = await service.getSponsor(sponsorId);
expect(result).toEqual({
sponsor: {
id: sponsorId,
name: 'S1',
logoUrl: undefined,
websiteUrl: undefined,
},
});
});
it('throws when not found', async () => {
const sponsorId = 's1';
getSponsorUseCase.execute.mockResolvedValue(Result.err({ code: 'SPONSOR_NOT_FOUND' }));
await expect(service.getSponsor(sponsorId)).rejects.toThrow('Sponsor not found');
});
it('throws when viewModel is missing even if use case succeeds', async () => {
const sponsorId = 's1';
getSponsorUseCase.execute.mockResolvedValue(Result.ok(undefined));
await expect(service.getSponsor(sponsorId)).rejects.toThrow('Sponsor not found');
});
});
describe('getPendingSponsorshipRequests', () => {
it('returns requests on success', async () => {
const params = { entityType: 'season' as const, entityId: 'season-1' };
const output = {
entityType: 'season',
entityId: 'season-1',
requests: [],
totalCount: 0,
};
getPendingSponsorshipRequestsUseCase.execute.mockResolvedValue(Result.ok(output));
const result = await service.getPendingSponsorshipRequests(params);
expect(result).toEqual(output);
});
it('returns empty result on error', async () => {
const params = { entityType: 'season' as const, entityId: 'season-1' };
getPendingSponsorshipRequestsUseCase.execute.mockResolvedValue(Result.err({ code: 'REPOSITORY_ERROR' }));
const result = await service.getPendingSponsorshipRequests(params);
expect(result).toEqual({
entityType: 'season',
entityId: 'season-1',
requests: [],
totalCount: 0,
});
});
});
describe('SponsorshipRequest', () => {
it('returns accept result on success', async () => {
const requestId = 'r1';
const respondedBy = 'u1';
const output = {
requestId,
sponsorshipId: 'sp1',
status: 'accepted' as const,
acceptedAt: new Date(),
platformFee: 10,
netAmount: 90,
};
acceptSponsorshipRequestUseCase.execute.mockResolvedValue(Result.ok(output));
const result = await service.acceptSponsorshipRequest(requestId, respondedBy);
expect(result).toEqual(output);
});
it('throws on error', async () => {
const requestId = 'r1';
const respondedBy = 'u1';
acceptSponsorshipRequestUseCase.execute.mockResolvedValue(
Result.err({ code: 'SPONSORSHIP_REQUEST_NOT_FOUND' }),
);
await expect(service.acceptSponsorshipRequest(requestId, respondedBy)).rejects.toThrow(
'Accept sponsorship request failed',
);
});
});
describe('rejectSponsorshipRequest', () => {
it('returns reject result on success', async () => {
const requestId = 'r1';
const respondedBy = 'u1';
const reason = 'Not interested';
const output = {
requestId,
status: 'rejected' as const,
respondedAt: new Date(),
rejectionReason: reason,
};
rejectSponsorshipRequestUseCase.execute.mockResolvedValue(Result.ok(output));
const result = await service.rejectSponsorshipRequest(requestId, respondedBy, reason);
expect(result).toEqual(output);
});
it('passes no reason when reason is undefined', async () => {
const requestId = 'r1';
const respondedBy = 'u1';
const output = {
requestId,
status: 'rejected' as const,
respondedAt: new Date(),
rejectionReason: '',
};
rejectSponsorshipRequestUseCase.execute.mockResolvedValue(Result.ok(output));
const result = await service.rejectSponsorshipRequest(requestId, respondedBy);
expect(result).toMatchObject({
requestId,
status: 'rejected',
});
});
it('throws on error', async () => {
const requestId = 'r1';
const respondedBy = 'u1';
rejectSponsorshipRequestUseCase.execute.mockResolvedValue(
Result.err({ code: 'SPONSORSHIP_REQUEST_NOT_FOUND' }),
);
await expect(service.rejectSponsorshipRequest(requestId, respondedBy)).rejects.toThrow(
'Reject sponsorship request failed',
);
});
});
describe('getSponsorBilling', () => {
it('returns billing data', async () => {
// Mock the use case to return billing data directly
getSponsorBillingUseCase.execute.mockResolvedValue(
Result.ok({
paymentMethods: [],
invoices: [],
stats: {
totalSpent: 0,
pendingAmount: 0,
nextPaymentDate: null,
nextPaymentAmount: null,
activeSponsorships: 0,
averageMonthlySpend: 0,
},
})
);
const result = await service.getSponsorBilling('s1');
expect(result).not.toBeNull();
expect(result.paymentMethods).toBeInstanceOf(Array);
expect(result.invoices).toBeInstanceOf(Array);
expect(result.stats).toBeDefined();
});
it('throws on error', async () => {
getSponsorBillingUseCase.execute.mockResolvedValue(Result.err({ code: 'REPOSITORY_ERROR' } as never));
await expect(service.getSponsorBilling('s1')).rejects.toThrow('Sponsor billing not found');
});
});
describe('getAvailableLeagues', () => {
it('returns mock leagues', async () => {
const result = await service.getAvailableLeagues();
expect(result).not.toBeNull();
expect(result.viewModel).not.toBeNull();
expect(result.viewModel?.length).toBeGreaterThan(0);
});
});
describe('getLeagueDetail', () => {
it('returns league detail', async () => {
const result = await service.getLeagueDetail('league-1');
expect(result).not.toBeNull();
expect(result.viewModel?.league.id).toBe('league-1');
});
});
describe('getSponsorSettings', () => {
it('returns settings', async () => {
const result = await service.getSponsorSettings('s1');
expect(result).not.toBeNull();
expect(result.viewModel?.profile).toBeDefined();
expect(result.viewModel?.notifications).toBeDefined();
expect(result.viewModel?.privacy).toBeDefined();
});
});
describe('updateSponsorSettings', () => {
it('returns success result', async () => {
const result = await service.updateSponsorSettings('s1', {});
expect(result.viewModel).toEqual({ success: true });
});
});
});