521 lines
18 KiB
TypeScript
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 });
|
|
});
|
|
});
|
|
}); |