360 lines
16 KiB
TypeScript
360 lines
16 KiB
TypeScript
/**
|
|
* Integration Test: Sponsor Billing Use Case Orchestration
|
|
*
|
|
* Tests the orchestration logic of sponsor billing-related Use Cases:
|
|
* - GetBillingStatisticsUseCase: Retrieves billing statistics
|
|
* - GetPaymentMethodsUseCase: Retrieves payment methods
|
|
* - SetDefaultPaymentMethodUseCase: Sets default payment method
|
|
* - GetInvoicesUseCase: Retrieves invoices
|
|
* - DownloadInvoiceUseCase: Downloads invoice
|
|
* - Validates that Use Cases correctly interact with their Ports (Repositories, Event Publishers)
|
|
* - Uses In-Memory adapters for fast, deterministic testing
|
|
*
|
|
* Focus: Business logic orchestration, NOT UI rendering
|
|
*/
|
|
|
|
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
|
|
import { InMemorySponsorRepository } from '../../../adapters/sponsors/persistence/inmemory/InMemorySponsorRepository';
|
|
import { InMemoryBillingRepository } from '../../../adapters/billing/persistence/inmemory/InMemoryBillingRepository';
|
|
import { InMemoryEventPublisher } from '../../../adapters/events/InMemoryEventPublisher';
|
|
import { GetBillingStatisticsUseCase } from '../../../core/sponsors/use-cases/GetBillingStatisticsUseCase';
|
|
import { GetPaymentMethodsUseCase } from '../../../core/sponsors/use-cases/GetPaymentMethodsUseCase';
|
|
import { SetDefaultPaymentMethodUseCase } from '../../../core/sponsors/use-cases/SetDefaultPaymentMethodUseCase';
|
|
import { GetInvoicesUseCase } from '../../../core/sponsors/use-cases/GetInvoicesUseCase';
|
|
import { DownloadInvoiceUseCase } from '../../../core/sponsors/use-cases/DownloadInvoiceUseCase';
|
|
import { GetBillingStatisticsQuery } from '../../../core/sponsors/ports/GetBillingStatisticsQuery';
|
|
import { GetPaymentMethodsQuery } from '../../../core/sponsors/ports/GetPaymentMethodsQuery';
|
|
import { SetDefaultPaymentMethodCommand } from '../../../core/sponsors/ports/SetDefaultPaymentMethodCommand';
|
|
import { GetInvoicesQuery } from '../../../core/sponsors/ports/GetInvoicesQuery';
|
|
import { DownloadInvoiceCommand } from '../../../core/sponsors/ports/DownloadInvoiceCommand';
|
|
|
|
describe('Sponsor Billing Use Case Orchestration', () => {
|
|
let sponsorRepository: InMemorySponsorRepository;
|
|
let billingRepository: InMemoryBillingRepository;
|
|
let eventPublisher: InMemoryEventPublisher;
|
|
let getBillingStatisticsUseCase: GetBillingStatisticsUseCase;
|
|
let getPaymentMethodsUseCase: GetPaymentMethodsUseCase;
|
|
let setDefaultPaymentMethodUseCase: SetDefaultPaymentMethodUseCase;
|
|
let getInvoicesUseCase: GetInvoicesUseCase;
|
|
let downloadInvoiceUseCase: DownloadInvoiceUseCase;
|
|
|
|
beforeAll(() => {
|
|
// TODO: Initialize In-Memory repositories and event publisher
|
|
// sponsorRepository = new InMemorySponsorRepository();
|
|
// billingRepository = new InMemoryBillingRepository();
|
|
// eventPublisher = new InMemoryEventPublisher();
|
|
// getBillingStatisticsUseCase = new GetBillingStatisticsUseCase({
|
|
// sponsorRepository,
|
|
// billingRepository,
|
|
// eventPublisher,
|
|
// });
|
|
// getPaymentMethodsUseCase = new GetPaymentMethodsUseCase({
|
|
// sponsorRepository,
|
|
// billingRepository,
|
|
// eventPublisher,
|
|
// });
|
|
// setDefaultPaymentMethodUseCase = new SetDefaultPaymentMethodUseCase({
|
|
// sponsorRepository,
|
|
// billingRepository,
|
|
// eventPublisher,
|
|
// });
|
|
// getInvoicesUseCase = new GetInvoicesUseCase({
|
|
// sponsorRepository,
|
|
// billingRepository,
|
|
// eventPublisher,
|
|
// });
|
|
// downloadInvoiceUseCase = new DownloadInvoiceUseCase({
|
|
// sponsorRepository,
|
|
// billingRepository,
|
|
// eventPublisher,
|
|
// });
|
|
});
|
|
|
|
beforeEach(() => {
|
|
// TODO: Clear all In-Memory repositories before each test
|
|
// sponsorRepository.clear();
|
|
// billingRepository.clear();
|
|
// eventPublisher.clear();
|
|
});
|
|
|
|
describe('GetBillingStatisticsUseCase - Success Path', () => {
|
|
it('should retrieve billing statistics for a sponsor', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Sponsor with billing data
|
|
// Given: A sponsor exists with ID "sponsor-123"
|
|
// And: The sponsor has total spent: $5000
|
|
// And: The sponsor has pending payments: $1000
|
|
// And: The sponsor has next payment date: "2024-02-01"
|
|
// And: The sponsor has monthly average spend: $1250
|
|
// When: GetBillingStatisticsUseCase.execute() is called with sponsor ID
|
|
// Then: The result should show total spent: $5000
|
|
// And: The result should show pending payments: $1000
|
|
// And: The result should show next payment date: "2024-02-01"
|
|
// And: The result should show monthly average spend: $1250
|
|
// And: EventPublisher should emit BillingStatisticsAccessedEvent
|
|
});
|
|
|
|
it('should retrieve statistics with zero values', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Sponsor with no billing data
|
|
// Given: A sponsor exists with ID "sponsor-123"
|
|
// And: The sponsor has no billing history
|
|
// When: GetBillingStatisticsUseCase.execute() is called with sponsor ID
|
|
// Then: The result should show total spent: $0
|
|
// And: The result should show pending payments: $0
|
|
// And: The result should show next payment date: null
|
|
// And: The result should show monthly average spend: $0
|
|
// And: EventPublisher should emit BillingStatisticsAccessedEvent
|
|
});
|
|
});
|
|
|
|
describe('GetBillingStatisticsUseCase - Error Handling', () => {
|
|
it('should throw error when sponsor does not exist', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Non-existent sponsor
|
|
// Given: No sponsor exists with the given ID
|
|
// When: GetBillingStatisticsUseCase.execute() is called with non-existent sponsor ID
|
|
// Then: Should throw SponsorNotFoundError
|
|
// And: EventPublisher should NOT emit any events
|
|
});
|
|
|
|
it('should throw error when sponsor ID is invalid', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Invalid sponsor ID
|
|
// Given: An invalid sponsor ID (e.g., empty string, null, undefined)
|
|
// When: GetBillingStatisticsUseCase.execute() is called with invalid sponsor ID
|
|
// Then: Should throw ValidationError
|
|
// And: EventPublisher should NOT emit any events
|
|
});
|
|
});
|
|
|
|
describe('GetPaymentMethodsUseCase - Success Path', () => {
|
|
it('should retrieve payment methods for a sponsor', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Sponsor with multiple payment methods
|
|
// Given: A sponsor exists with ID "sponsor-123"
|
|
// And: The sponsor has 3 payment methods (1 default, 2 non-default)
|
|
// When: GetPaymentMethodsUseCase.execute() is called with sponsor ID
|
|
// Then: The result should contain all 3 payment methods
|
|
// And: Each payment method should display its details
|
|
// And: The default payment method should be marked
|
|
// And: EventPublisher should emit PaymentMethodsAccessedEvent
|
|
});
|
|
|
|
it('should retrieve payment methods with minimal data', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Sponsor with single payment method
|
|
// Given: A sponsor exists with ID "sponsor-123"
|
|
// And: The sponsor has 1 payment method (default)
|
|
// When: GetPaymentMethodsUseCase.execute() is called with sponsor ID
|
|
// Then: The result should contain the single payment method
|
|
// And: EventPublisher should emit PaymentMethodsAccessedEvent
|
|
});
|
|
|
|
it('should retrieve payment methods with empty result', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Sponsor with no payment methods
|
|
// Given: A sponsor exists with ID "sponsor-123"
|
|
// And: The sponsor has no payment methods
|
|
// When: GetPaymentMethodsUseCase.execute() is called with sponsor ID
|
|
// Then: The result should be empty
|
|
// And: EventPublisher should emit PaymentMethodsAccessedEvent
|
|
});
|
|
});
|
|
|
|
describe('GetPaymentMethodsUseCase - Error Handling', () => {
|
|
it('should throw error when sponsor does not exist', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Non-existent sponsor
|
|
// Given: No sponsor exists with the given ID
|
|
// When: GetPaymentMethodsUseCase.execute() is called with non-existent sponsor ID
|
|
// Then: Should throw SponsorNotFoundError
|
|
// And: EventPublisher should NOT emit any events
|
|
});
|
|
});
|
|
|
|
describe('SetDefaultPaymentMethodUseCase - Success Path', () => {
|
|
it('should set default payment method for a sponsor', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Set default payment method
|
|
// Given: A sponsor exists with ID "sponsor-123"
|
|
// And: The sponsor has 3 payment methods (1 default, 2 non-default)
|
|
// When: SetDefaultPaymentMethodUseCase.execute() is called with payment method ID
|
|
// Then: The payment method should become default
|
|
// And: The previous default should no longer be default
|
|
// And: EventPublisher should emit PaymentMethodUpdatedEvent
|
|
});
|
|
|
|
it('should set default payment method when no default exists', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Set default when none exists
|
|
// Given: A sponsor exists with ID "sponsor-123"
|
|
// And: The sponsor has 2 payment methods (no default)
|
|
// When: SetDefaultPaymentMethodUseCase.execute() is called with payment method ID
|
|
// Then: The payment method should become default
|
|
// And: EventPublisher should emit PaymentMethodUpdatedEvent
|
|
});
|
|
});
|
|
|
|
describe('SetDefaultPaymentMethodUseCase - Error Handling', () => {
|
|
it('should throw error when sponsor does not exist', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Non-existent sponsor
|
|
// Given: No sponsor exists with the given ID
|
|
// When: SetDefaultPaymentMethodUseCase.execute() is called with non-existent sponsor ID
|
|
// Then: Should throw SponsorNotFoundError
|
|
// And: EventPublisher should NOT emit any events
|
|
});
|
|
|
|
it('should throw error when payment method does not exist', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Non-existent payment method
|
|
// Given: A sponsor exists with ID "sponsor-123"
|
|
// And: The sponsor has 2 payment methods
|
|
// When: SetDefaultPaymentMethodUseCase.execute() is called with non-existent payment method ID
|
|
// Then: Should throw PaymentMethodNotFoundError
|
|
// And: EventPublisher should NOT emit any events
|
|
});
|
|
|
|
it('should throw error when payment method does not belong to sponsor', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Payment method belongs to different sponsor
|
|
// Given: Sponsor A exists with ID "sponsor-123"
|
|
// And: Sponsor B exists with ID "sponsor-456"
|
|
// And: Sponsor B has a payment method with ID "pm-789"
|
|
// When: SetDefaultPaymentMethodUseCase.execute() is called with sponsor ID "sponsor-123" and payment method ID "pm-789"
|
|
// Then: Should throw PaymentMethodNotFoundError
|
|
// And: EventPublisher should NOT emit any events
|
|
});
|
|
});
|
|
|
|
describe('GetInvoicesUseCase - Success Path', () => {
|
|
it('should retrieve invoices for a sponsor', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Sponsor with multiple invoices
|
|
// Given: A sponsor exists with ID "sponsor-123"
|
|
// And: The sponsor has 5 invoices (2 pending, 2 paid, 1 overdue)
|
|
// When: GetInvoicesUseCase.execute() is called with sponsor ID
|
|
// Then: The result should contain all 5 invoices
|
|
// And: Each invoice should display its details
|
|
// And: EventPublisher should emit InvoicesAccessedEvent
|
|
});
|
|
|
|
it('should retrieve invoices with minimal data', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Sponsor with single invoice
|
|
// Given: A sponsor exists with ID "sponsor-123"
|
|
// And: The sponsor has 1 invoice
|
|
// When: GetInvoicesUseCase.execute() is called with sponsor ID
|
|
// Then: The result should contain the single invoice
|
|
// And: EventPublisher should emit InvoicesAccessedEvent
|
|
});
|
|
|
|
it('should retrieve invoices with empty result', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Sponsor with no invoices
|
|
// Given: A sponsor exists with ID "sponsor-123"
|
|
// And: The sponsor has no invoices
|
|
// When: GetInvoicesUseCase.execute() is called with sponsor ID
|
|
// Then: The result should be empty
|
|
// And: EventPublisher should emit InvoicesAccessedEvent
|
|
});
|
|
});
|
|
|
|
describe('GetInvoicesUseCase - Error Handling', () => {
|
|
it('should throw error when sponsor does not exist', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Non-existent sponsor
|
|
// Given: No sponsor exists with the given ID
|
|
// When: GetInvoicesUseCase.execute() is called with non-existent sponsor ID
|
|
// Then: Should throw SponsorNotFoundError
|
|
// And: EventPublisher should NOT emit any events
|
|
});
|
|
});
|
|
|
|
describe('DownloadInvoiceUseCase - Success Path', () => {
|
|
it('should download invoice for a sponsor', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Download invoice
|
|
// Given: A sponsor exists with ID "sponsor-123"
|
|
// And: The sponsor has an invoice with ID "inv-456"
|
|
// When: DownloadInvoiceUseCase.execute() is called with invoice ID
|
|
// Then: The invoice should be downloaded
|
|
// And: The invoice should be in PDF format
|
|
// And: EventPublisher should emit InvoiceDownloadedEvent
|
|
});
|
|
|
|
it('should download invoice with correct content', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Download invoice with correct content
|
|
// Given: A sponsor exists with ID "sponsor-123"
|
|
// And: The sponsor has an invoice with ID "inv-456"
|
|
// When: DownloadInvoiceUseCase.execute() is called with invoice ID
|
|
// Then: The downloaded invoice should contain correct invoice number
|
|
// And: The downloaded invoice should contain correct date
|
|
// And: The downloaded invoice should contain correct amount
|
|
// And: EventPublisher should emit InvoiceDownloadedEvent
|
|
});
|
|
});
|
|
|
|
describe('DownloadInvoiceUseCase - Error Handling', () => {
|
|
it('should throw error when invoice does not exist', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Non-existent invoice
|
|
// Given: A sponsor exists with ID "sponsor-123"
|
|
// And: The sponsor has no invoice with ID "inv-999"
|
|
// When: DownloadInvoiceUseCase.execute() is called with non-existent invoice ID
|
|
// Then: Should throw InvoiceNotFoundError
|
|
// And: EventPublisher should NOT emit any events
|
|
});
|
|
|
|
it('should throw error when invoice does not belong to sponsor', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Invoice belongs to different sponsor
|
|
// Given: Sponsor A exists with ID "sponsor-123"
|
|
// And: Sponsor B exists with ID "sponsor-456"
|
|
// And: Sponsor B has an invoice with ID "inv-789"
|
|
// When: DownloadInvoiceUseCase.execute() is called with invoice ID "inv-789"
|
|
// Then: Should throw InvoiceNotFoundError
|
|
// And: EventPublisher should NOT emit any events
|
|
});
|
|
});
|
|
|
|
describe('Billing Data Orchestration', () => {
|
|
it('should correctly aggregate billing statistics', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Billing statistics aggregation
|
|
// Given: A sponsor exists with ID "sponsor-123"
|
|
// And: The sponsor has 3 invoices with amounts: $1000, $2000, $3000
|
|
// And: The sponsor has 1 pending invoice with amount: $500
|
|
// When: GetBillingStatisticsUseCase.execute() is called
|
|
// Then: Total spent should be $6000
|
|
// And: Pending payments should be $500
|
|
// And: EventPublisher should emit BillingStatisticsAccessedEvent
|
|
});
|
|
|
|
it('should correctly set default payment method', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Set default payment method
|
|
// Given: A sponsor exists with ID "sponsor-123"
|
|
// And: The sponsor has 3 payment methods
|
|
// When: SetDefaultPaymentMethodUseCase.execute() is called
|
|
// Then: Only one payment method should be default
|
|
// And: The default payment method should be marked correctly
|
|
// And: EventPublisher should emit PaymentMethodUpdatedEvent
|
|
});
|
|
|
|
it('should correctly retrieve invoices with status', async () => {
|
|
// TODO: Implement test
|
|
// Scenario: Invoice status retrieval
|
|
// Given: A sponsor exists with ID "sponsor-123"
|
|
// And: The sponsor has invoices with different statuses
|
|
// When: GetInvoicesUseCase.execute() is called
|
|
// Then: Each invoice should have correct status
|
|
// And: Pending invoices should be highlighted
|
|
// And: Overdue invoices should show warning
|
|
// And: EventPublisher should emit InvoicesAccessedEvent
|
|
});
|
|
});
|
|
});
|