presenter refactoring

This commit is contained in:
2025-12-20 17:06:11 +01:00
parent 92be9d2e1b
commit e9d6f90bb2
109 changed files with 4159 additions and 1283 deletions

View File

@@ -1,5 +1,6 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { SponsorService } from './SponsorService';
import { Result } from '@core/shared/application/Result';
import type { Logger } from '@core/shared/application';
import type { GetSponsorshipPricingUseCase } from '@core/racing/application/use-cases/GetSponsorshipPricingUseCase';
import type { GetSponsorsUseCase } from '@core/racing/application/use-cases/GetSponsorsUseCase';
import type { CreateSponsorUseCase } from '@core/racing/application/use-cases/CreateSponsorUseCase';
@@ -9,8 +10,7 @@ import type { GetSponsorUseCase } from '@core/racing/application/use-cases/GetSp
import type { GetPendingSponsorshipRequestsUseCase } from '@core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase';
import type { AcceptSponsorshipRequestUseCase } from '@core/racing/application/use-cases/AcceptSponsorshipRequestUseCase';
import type { RejectSponsorshipRequestUseCase } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase';
import type { Logger } from '@core/shared/application';
import { Result } from '@core/shared/application/Result';
import { SponsorService } from './SponsorService';
describe('SponsorService', () => {
let service: SponsorService;
@@ -23,12 +23,7 @@ describe('SponsorService', () => {
let getPendingSponsorshipRequestsUseCase: { execute: Mock };
let acceptSponsorshipRequestUseCase: { execute: Mock };
let rejectSponsorshipRequestUseCase: { execute: Mock };
let logger: {
debug: Mock;
info: Mock;
warn: Mock;
error: Mock;
};
let logger: Logger;
beforeEach(() => {
getSponsorshipPricingUseCase = { execute: vi.fn() };
@@ -45,7 +40,7 @@ describe('SponsorService', () => {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
} as unknown as Logger;
service = new SponsorService(
getSponsorshipPricingUseCase as unknown as GetSponsorshipPricingUseCase,
@@ -57,136 +52,199 @@ describe('SponsorService', () => {
getPendingSponsorshipRequestsUseCase as unknown as GetPendingSponsorshipRequestsUseCase,
acceptSponsorshipRequestUseCase as unknown as AcceptSponsorshipRequestUseCase,
rejectSponsorshipRequestUseCase as unknown as RejectSponsorshipRequestUseCase,
logger as unknown as Logger,
logger,
);
});
describe('getEntitySponsorshipPricing', () => {
it('should return sponsorship pricing', async () => {
const mockPresenter = {
viewModel: { entityType: 'season', entityId: 'season-1', pricing: [] },
it('returns presenter with pricing data on success', async () => {
const outputPort = {
entityType: 'season',
entityId: 'season-1',
pricing: [
{ id: 'tier-gold', level: 'Gold', price: 500, currency: 'USD' },
],
};
getSponsorshipPricingUseCase.execute.mockResolvedValue(undefined);
getSponsorshipPricingUseCase.execute.mockResolvedValue(Result.ok(outputPort));
// Mock the presenter
const originalGetSponsorshipPricingPresenter = await import('./presenters/GetSponsorshipPricingPresenter');
const mockPresenterClass = vi.fn().mockImplementation(() => mockPresenter);
vi.doMock('./presenters/GetSponsorshipPricingPresenter', () => ({
GetSponsorshipPricingPresenter: mockPresenterClass,
}));
const presenter = await service.getEntitySponsorshipPricing();
const result = await service.getEntitySponsorshipPricing();
expect(presenter.viewModel).toEqual({
entityType: 'season',
entityId: 'season-1',
pricing: [
{ id: 'tier-gold', level: 'Gold', price: 500, currency: 'USD' },
],
});
});
expect(result).toEqual(mockPresenter.viewModel);
expect(getSponsorshipPricingUseCase.execute).toHaveBeenCalledWith(undefined, mockPresenter);
it('returns empty pricing on error', async () => {
getSponsorshipPricingUseCase.execute.mockResolvedValue(Result.err({ code: 'REPOSITORY_ERROR' }));
const presenter = await service.getEntitySponsorshipPricing();
expect(presenter.viewModel).toEqual({
entityType: 'season',
entityId: '',
pricing: [],
});
});
});
describe('getSponsors', () => {
it('should return sponsors list', async () => {
const mockPresenter = {
viewModel: { sponsors: [] },
};
getSponsorsUseCase.execute.mockResolvedValue(undefined);
it('returns sponsors in presenter on success', async () => {
const outputPort = { sponsors: [{ id: 's1', name: 'S1', contactEmail: 's1@test', createdAt: new Date() }] };
getSponsorsUseCase.execute.mockResolvedValue(Result.ok(outputPort));
const result = await service.getSponsors();
const presenter = await service.getSponsors();
expect(result).toEqual(mockPresenter.viewModel);
expect(getSponsorsUseCase.execute).toHaveBeenCalledWith(undefined, expect.any(Object));
expect(presenter.viewModel).toEqual({ sponsors: outputPort.sponsors });
});
it('returns empty list on error', async () => {
getSponsorsUseCase.execute.mockResolvedValue(Result.err({ code: 'REPOSITORY_ERROR' }));
const presenter = await service.getSponsors();
expect(presenter.viewModel).toEqual({ sponsors: [] });
});
});
describe('createSponsor', () => {
it('should create sponsor successfully', async () => {
const input = { name: 'Test Sponsor', contactEmail: 'test@example.com' };
const mockPresenter = {
viewModel: { id: 'sponsor-1', name: 'Test Sponsor' },
it('returns created sponsor in presenter on success', async () => {
const input = { name: 'Test', contactEmail: 'test@example.com' };
const outputPort = {
sponsor: {
id: 's1',
name: 'Test',
contactEmail: 'test@example.com',
createdAt: new Date(),
},
};
createSponsorUseCase.execute.mockResolvedValue(undefined);
createSponsorUseCase.execute.mockResolvedValue(Result.ok(outputPort));
const result = await service.createSponsor(input);
const presenter = await service.createSponsor(input as any);
expect(result).toEqual(mockPresenter.viewModel);
expect(createSponsorUseCase.execute).toHaveBeenCalledWith(input, expect.any(Object));
expect(presenter.viewModel).toEqual({ sponsor: outputPort.sponsor });
});
it('throws on error', async () => {
const input = { name: 'Test', contactEmail: 'test@example.com' };
createSponsorUseCase.execute.mockResolvedValue(
Result.err({ code: 'VALIDATION_ERROR', details: { message: 'Invalid' } }),
);
await expect(service.createSponsor(input as any)).rejects.toThrow('Invalid');
});
});
describe('getSponsorDashboard', () => {
it('should return sponsor dashboard', async () => {
const params = { sponsorId: 'sponsor-1' };
const mockPresenter = {
viewModel: { sponsorId: 'sponsor-1', metrics: {}, sponsoredLeagues: [] },
it('returns dashboard in presenter on success', async () => {
const params = { sponsorId: 's1' };
const outputPort = {
sponsorId: 's1',
sponsorName: 'S1',
metrics: {} as any,
sponsoredLeagues: [],
investment: {} as any,
};
getSponsorDashboardUseCase.execute.mockResolvedValue(undefined);
getSponsorDashboardUseCase.execute.mockResolvedValue(Result.ok(outputPort));
const result = await service.getSponsorDashboard(params);
const presenter = await service.getSponsorDashboard(params as any);
expect(result).toEqual(mockPresenter.viewModel);
expect(getSponsorDashboardUseCase.execute).toHaveBeenCalledWith(params, expect.any(Object));
expect(presenter.viewModel).toEqual(outputPort);
});
it('returns null in presenter on error', async () => {
const params = { sponsorId: 's1' };
getSponsorDashboardUseCase.execute.mockResolvedValue(Result.err({ code: 'REPOSITORY_ERROR' }));
const presenter = await service.getSponsorDashboard(params as any);
expect(presenter.viewModel).toBeNull();
});
});
describe('getSponsorSponsorships', () => {
it('should return sponsor sponsorships', async () => {
const params = { sponsorId: 'sponsor-1' };
const mockPresenter = {
viewModel: { sponsorId: 'sponsor-1', sponsorships: [] },
it('returns sponsorships in presenter on success', async () => {
const params = { sponsorId: 's1' };
const outputPort = {
sponsorId: 's1',
sponsorName: 'S1',
sponsorships: [],
summary: {
totalSponsorships: 0,
activeSponsorships: 0,
totalInvestment: 0,
totalPlatformFees: 0,
currency: 'USD',
},
};
getSponsorSponsorshipsUseCase.execute.mockResolvedValue(undefined);
getSponsorSponsorshipsUseCase.execute.mockResolvedValue(Result.ok(outputPort));
const result = await service.getSponsorSponsorships(params);
const presenter = await service.getSponsorSponsorships(params as any);
expect(result).toEqual(mockPresenter.viewModel);
expect(getSponsorSponsorshipsUseCase.execute).toHaveBeenCalledWith(params, expect.any(Object));
expect(presenter.viewModel).toEqual(outputPort);
});
it('returns null in presenter on error', async () => {
const params = { sponsorId: 's1' };
getSponsorSponsorshipsUseCase.execute.mockResolvedValue(
Result.err({ code: 'REPOSITORY_ERROR' }),
);
const presenter = await service.getSponsorSponsorships(params as any);
expect(presenter.viewModel).toBeNull();
});
});
describe('getSponsor', () => {
it('should return sponsor when found', async () => {
const sponsorId = 'sponsor-1';
const mockSponsor = { id: sponsorId, name: 'Test Sponsor' };
getSponsorUseCase.execute.mockResolvedValue(Result.ok(mockSponsor));
it('returns sponsor in presenter when found', async () => {
const sponsorId = 's1';
const output = { sponsor: { id: sponsorId, name: 'S1' } };
getSponsorUseCase.execute.mockResolvedValue(Result.ok(output));
const result = await service.getSponsor(sponsorId);
const presenter = await service.getSponsor(sponsorId);
expect(result).toEqual(mockSponsor);
expect(getSponsorUseCase.execute).toHaveBeenCalledWith({ sponsorId });
expect(presenter.viewModel).toEqual({ sponsor: output.sponsor });
});
it('should return null when sponsor not found', async () => {
const sponsorId = 'sponsor-1';
getSponsorUseCase.execute.mockResolvedValue(Result.err({ code: 'NOT_FOUND' }));
it('returns null in presenter when not found', async () => {
const sponsorId = 's1';
getSponsorUseCase.execute.mockResolvedValue(Result.ok(null));
const result = await service.getSponsor(sponsorId);
const presenter = await service.getSponsor(sponsorId);
expect(result).toBeNull();
expect(presenter.viewModel).toBeNull();
});
});
describe('getPendingSponsorshipRequests', () => {
it('should return pending sponsorship requests', async () => {
it('returns requests in presenter on success', async () => {
const params = { entityType: 'season' as const, entityId: 'season-1' };
const mockResult = {
const outputPort = {
entityType: 'season',
entityId: 'season-1',
requests: [],
totalCount: 0,
};
getPendingSponsorshipRequestsUseCase.execute.mockResolvedValue(Result.ok(mockResult));
getPendingSponsorshipRequestsUseCase.execute.mockResolvedValue(Result.ok(outputPort));
const result = await service.getPendingSponsorshipRequests(params);
const presenter = await service.getPendingSponsorshipRequests(params);
expect(result).toEqual(mockResult);
expect(getPendingSponsorshipRequestsUseCase.execute).toHaveBeenCalledWith(params);
expect(presenter.viewModel).toEqual(outputPort);
});
it('should return empty result on error', async () => {
it('returns empty result on error', async () => {
const params = { entityType: 'season' as const, entityId: 'season-1' };
getPendingSponsorshipRequestsUseCase.execute.mockResolvedValue(Result.err({ code: 'REPOSITORY_ERROR' }));
getPendingSponsorshipRequestsUseCase.execute.mockResolvedValue(
Result.err({ code: 'REPOSITORY_ERROR' }),
);
const result = await service.getPendingSponsorshipRequests(params);
const presenter = await service.getPendingSponsorshipRequests(params);
expect(result).toEqual({
expect(presenter.viewModel).toEqual({
entityType: 'season',
entityId: 'season-1',
requests: [],
@@ -196,63 +254,113 @@ describe('SponsorService', () => {
});
describe('acceptSponsorshipRequest', () => {
it('should accept sponsorship request successfully', async () => {
const requestId = 'request-1';
const respondedBy = 'user-1';
const mockResult = {
it('returns accept result in presenter on success', async () => {
const requestId = 'r1';
const respondedBy = 'u1';
const outputPort = {
requestId,
sponsorshipId: 'sponsorship-1',
sponsorshipId: 'sp1',
status: 'accepted' as const,
acceptedAt: new Date(),
platformFee: 10,
netAmount: 90,
};
acceptSponsorshipRequestUseCase.execute.mockResolvedValue(Result.ok(mockResult));
acceptSponsorshipRequestUseCase.execute.mockResolvedValue(Result.ok(outputPort));
const result = await service.acceptSponsorshipRequest(requestId, respondedBy);
const presenter = await service.acceptSponsorshipRequest(requestId, respondedBy);
expect(result).toEqual(mockResult);
expect(acceptSponsorshipRequestUseCase.execute).toHaveBeenCalledWith({ requestId, respondedBy });
expect(presenter.viewModel).toEqual(outputPort);
});
it('should return null on error', async () => {
const requestId = 'request-1';
const respondedBy = 'user-1';
acceptSponsorshipRequestUseCase.execute.mockResolvedValue(Result.err({ code: 'NOT_FOUND' }));
it('returns null in presenter on error', async () => {
const requestId = 'r1';
const respondedBy = 'u1';
acceptSponsorshipRequestUseCase.execute.mockResolvedValue(
Result.err({ code: 'SPONSORSHIP_REQUEST_NOT_FOUND' }),
);
const result = await service.acceptSponsorshipRequest(requestId, respondedBy);
const presenter = await service.acceptSponsorshipRequest(requestId, respondedBy);
expect(result).toBeNull();
expect(presenter.viewModel).toBeNull();
});
});
describe('rejectSponsorshipRequest', () => {
it('should reject sponsorship request successfully', async () => {
const requestId = 'request-1';
const respondedBy = 'user-1';
it('returns reject result in presenter on success', async () => {
const requestId = 'r1';
const respondedBy = 'u1';
const reason = 'Not interested';
const mockResult = {
const output = {
requestId,
status: 'rejected' as const,
rejectedAt: new Date(),
reason,
};
rejectSponsorshipRequestUseCase.execute.mockResolvedValue(Result.ok(mockResult));
rejectSponsorshipRequestUseCase.execute.mockResolvedValue(Result.ok(output));
const result = await service.rejectSponsorshipRequest(requestId, respondedBy, reason);
const presenter = await service.rejectSponsorshipRequest(requestId, respondedBy, reason);
expect(result).toEqual(mockResult);
expect(rejectSponsorshipRequestUseCase.execute).toHaveBeenCalledWith({ requestId, respondedBy, reason });
expect(presenter.viewModel).toEqual(output);
});
it('should return null on error', async () => {
const requestId = 'request-1';
const respondedBy = 'user-1';
rejectSponsorshipRequestUseCase.execute.mockResolvedValue(Result.err({ code: 'NOT_FOUND' }));
it('returns null in presenter on error', async () => {
const requestId = 'r1';
const respondedBy = 'u1';
rejectSponsorshipRequestUseCase.execute.mockResolvedValue(
Result.err({ code: 'SPONSORSHIP_REQUEST_NOT_FOUND' }),
);
const result = await service.rejectSponsorshipRequest(requestId, respondedBy);
const presenter = await service.rejectSponsorshipRequest(requestId, respondedBy);
expect(result).toBeNull();
expect(presenter.viewModel).toBeNull();
});
});
});
describe('getSponsorBilling', () => {
it('returns mock billing data in presenter', async () => {
const presenter = await service.getSponsorBilling('s1');
expect(presenter.viewModel).not.toBeNull();
expect(presenter.viewModel?.paymentMethods).toBeInstanceOf(Array);
expect(presenter.viewModel?.invoices).toBeInstanceOf(Array);
expect(presenter.viewModel?.stats).toBeDefined();
});
});
describe('getAvailableLeagues', () => {
it('returns mock leagues in presenter', async () => {
const presenter = await service.getAvailableLeagues();
expect(presenter.viewModel).not.toBeNull();
expect(presenter.viewModel?.length).toBeGreaterThan(0);
});
});
describe('getLeagueDetail', () => {
it('returns league detail in presenter', async () => {
const presenter = await service.getLeagueDetail('league-1');
expect(presenter.viewModel).not.toBeNull();
expect(presenter.viewModel?.league.id).toBe('league-1');
});
});
describe('getSponsorSettings', () => {
it('returns settings in presenter', async () => {
const presenter = await service.getSponsorSettings('s1');
expect(presenter.viewModel).not.toBeNull();
expect(presenter.viewModel?.profile).toBeDefined();
expect(presenter.viewModel?.notifications).toBeDefined();
expect(presenter.viewModel?.privacy).toBeDefined();
});
});
describe('updateSponsorSettings', () => {
it('returns success result in presenter', async () => {
const presenter = await service.updateSponsorSettings('s1', {});
expect(presenter.viewModel).toEqual({ success: true });
});
});
});