138 lines
4.3 KiB
TypeScript
138 lines
4.3 KiB
TypeScript
import { readFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { NotImplementedException } from '@nestjs/common';
|
|
import { Test, TestingModule } from '@nestjs/testing';
|
|
import {
|
|
BILLING_PROVIDER,
|
|
BillingProvider,
|
|
} from './billing-provider.interface';
|
|
import { BillingProviderService } from './billing-provider.service';
|
|
import { BillingModule } from './billing.module';
|
|
import { PaddleBillingProvider } from './paddle-billing.provider';
|
|
|
|
const PROVIDER_METHODS = [
|
|
'createCustomer',
|
|
'createSubscription',
|
|
'updateSubscription',
|
|
'cancelSubscription',
|
|
'reactivateSubscription',
|
|
'getSubscription',
|
|
'createCheckoutSession',
|
|
'createCustomerPortalSession',
|
|
'validateWebhookSignature',
|
|
'handleWebhookEvent',
|
|
] as const satisfies ReadonlyArray<keyof BillingProvider>;
|
|
|
|
describe('BillingModule', () => {
|
|
let moduleRef: TestingModule;
|
|
let provider: PaddleBillingProvider;
|
|
let service: BillingProviderService;
|
|
|
|
beforeAll(async () => {
|
|
moduleRef = await Test.createTestingModule({
|
|
imports: [BillingModule],
|
|
}).compile();
|
|
provider = moduleRef.get(PaddleBillingProvider);
|
|
service = moduleRef.get(BillingProviderService);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await moduleRef.close();
|
|
});
|
|
|
|
it('registers and exports BillingProviderService', () => {
|
|
expect(service).toBeInstanceOf(BillingProviderService);
|
|
});
|
|
|
|
it('implements the BillingProvider contract with PaddleBillingProvider', () => {
|
|
const contract: BillingProvider = provider;
|
|
|
|
expect(contract).toBeInstanceOf(PaddleBillingProvider);
|
|
expect(
|
|
PROVIDER_METHODS.every(
|
|
(method) => typeof contract[method] === 'function',
|
|
),
|
|
).toBe(true);
|
|
});
|
|
|
|
it('selects PaddleBillingProvider for the internal provider token', () => {
|
|
const selectedProvider = moduleRef.get<BillingProvider>(BILLING_PROVIDER);
|
|
|
|
expect(selectedProvider).toBe(provider);
|
|
});
|
|
|
|
it.each(PROVIDER_METHODS)(
|
|
'throws NotImplementedException from provider method %s',
|
|
async (method) => {
|
|
await expect(provider[method]()).rejects.toBeInstanceOf(
|
|
NotImplementedException,
|
|
);
|
|
},
|
|
);
|
|
|
|
it.each(PROVIDER_METHODS)(
|
|
'throws NotImplementedException from service method %s',
|
|
async (method) => {
|
|
await expect(service[method]()).rejects.toBeInstanceOf(
|
|
NotImplementedException,
|
|
);
|
|
},
|
|
);
|
|
|
|
it('does not import a Paddle SDK or declare one as a dependency', () => {
|
|
const sourceFiles = [
|
|
'billing-provider.interface.ts',
|
|
'billing-provider.service.ts',
|
|
'billing.module.ts',
|
|
'paddle-billing.provider.ts',
|
|
];
|
|
const billingSource = sourceFiles
|
|
.map((file) => readFileSync(join(__dirname, file), 'utf8'))
|
|
.join('\n');
|
|
const packageJson = JSON.parse(
|
|
readFileSync(join(__dirname, '../../../package.json'), 'utf8'),
|
|
) as {
|
|
dependencies?: Record<string, string>;
|
|
devDependencies?: Record<string, string>;
|
|
};
|
|
const dependencyNames = [
|
|
...Object.keys(packageJson.dependencies ?? {}),
|
|
...Object.keys(packageJson.devDependencies ?? {}),
|
|
];
|
|
const externalImports = [
|
|
...billingSource.matchAll(/(?:from\s+|require\()['"]([^'"]+)['"]/g),
|
|
]
|
|
.map((match) => match[1])
|
|
.filter((specifier) => !specifier.startsWith('.'));
|
|
|
|
expect(externalImports).not.toEqual(
|
|
expect.arrayContaining([expect.stringMatching(/paddle/i)]),
|
|
);
|
|
expect(dependencyNames).not.toEqual(
|
|
expect.arrayContaining([expect.stringMatching(/paddle/i)]),
|
|
);
|
|
});
|
|
|
|
it('performs no HTTP requests while all operations reject', async () => {
|
|
const http = jest.requireActual<typeof import('node:http')>('node:http');
|
|
const https = jest.requireActual<typeof import('node:https')>('node:https');
|
|
const httpRequest = jest.spyOn(http, 'request');
|
|
const httpsRequest = jest.spyOn(https, 'request');
|
|
const fetchRequest = jest.spyOn(globalThis, 'fetch');
|
|
|
|
for (const method of PROVIDER_METHODS) {
|
|
await expect(service[method]()).rejects.toBeInstanceOf(
|
|
NotImplementedException,
|
|
);
|
|
}
|
|
|
|
expect(httpRequest).not.toHaveBeenCalled();
|
|
expect(httpsRequest).not.toHaveBeenCalled();
|
|
expect(fetchRequest).not.toHaveBeenCalled();
|
|
|
|
httpRequest.mockRestore();
|
|
httpsRequest.mockRestore();
|
|
fetchRequest.mockRestore();
|
|
});
|
|
});
|