feat(ADM-018): completed feature
This commit is contained in:
171
project/src/app/tests/pricing.itest.ts
Normal file
171
project/src/app/tests/pricing.itest.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import type { DestinationStream } from 'pino';
|
||||
import type pg from 'pg';
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
import { buildApp } from '../build-app.js';
|
||||
import { createPool } from '../../infrastructure/db/pool.js';
|
||||
import { createLogger } from '../../infrastructure/logging/logger.js';
|
||||
import {
|
||||
getTestDbUrl,
|
||||
recreateDatabase,
|
||||
runMigrations,
|
||||
} from '../../infrastructure/db/tests/db-test-support.js';
|
||||
import { SESSION_COOKIE_NAME } from '../../modules/identity/index.js';
|
||||
|
||||
const hasDb = Boolean(process.env.TEST_DATABASE_URL);
|
||||
|
||||
function silentLogger() {
|
||||
const destination: DestinationStream = { write: () => undefined };
|
||||
return createLogger({ level: 'info', destination });
|
||||
}
|
||||
|
||||
function cookieValue(setCookieHeader: string | string[] | undefined): string {
|
||||
const raw = Array.isArray(setCookieHeader) ? setCookieHeader[0] : setCookieHeader;
|
||||
expect(raw).toBeDefined();
|
||||
const pair = (raw as string).split(';')[0] as string;
|
||||
return pair.slice(pair.indexOf('=') + 1);
|
||||
}
|
||||
|
||||
describe.skipIf(!hasDb)('pricing flows (real PostgreSQL)', () => {
|
||||
const url = hasDb ? getTestDbUrl() : '';
|
||||
let pool: pg.Pool;
|
||||
let app: Awaited<ReturnType<typeof buildApp>>;
|
||||
let adminCookie = '';
|
||||
|
||||
beforeAll(async () => {
|
||||
await recreateDatabase(url);
|
||||
await runMigrations(url, 'up');
|
||||
pool = createPool(url);
|
||||
app = await buildApp({ logger: silentLogger(), pool, cookieSecure: true });
|
||||
|
||||
const user = { email: 'pricing-admin@example.com', password: 'correct horse battery staple' };
|
||||
const registered = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/auth/register',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
payload: user,
|
||||
});
|
||||
expect(registered.statusCode).toBe(201);
|
||||
const id = (registered.json() as { id: string }).id;
|
||||
await pool.query('UPDATE identity_users SET role = $1 WHERE id = $2', ['admin', id]);
|
||||
|
||||
const login = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/auth/login',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
payload: user,
|
||||
});
|
||||
expect(login.statusCode).toBe(200);
|
||||
adminCookie = cookieValue(login.headers['set-cookie']);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
await pool.end();
|
||||
});
|
||||
|
||||
it('calculates totals with VAT from server-side price (AC1)', async () => {
|
||||
const variantId = randomUUID();
|
||||
const setPrice = await app.inject({
|
||||
method: 'PUT',
|
||||
url: `/pricing/variants/${variantId}`,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
cookies: { [SESSION_COOKIE_NAME]: adminCookie },
|
||||
payload: { netUnitAmountCents: 1000, vatRate: 'general' },
|
||||
});
|
||||
expect(setPrice.statusCode).toBe(200);
|
||||
|
||||
const calculation = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/pricing/calculate',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
payload: { variantId, quantity: 2 },
|
||||
});
|
||||
|
||||
expect(calculation.statusCode).toBe(200);
|
||||
expect(calculation.json()).toMatchObject({
|
||||
variantId,
|
||||
quantity: 2,
|
||||
currency: 'EUR',
|
||||
vatRate: 'general',
|
||||
vatBasisPoints: 2100,
|
||||
netUnitAmountCents: 1000,
|
||||
netSubtotalCents: 2000,
|
||||
vatAmountCents: 420,
|
||||
totalCents: 2420,
|
||||
});
|
||||
});
|
||||
|
||||
it('ignores client-supplied prices and recalculates from the server (AC2)', async () => {
|
||||
const variantId = randomUUID();
|
||||
await app.inject({
|
||||
method: 'PUT',
|
||||
url: `/pricing/variants/${variantId}`,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
cookies: { [SESSION_COOKIE_NAME]: adminCookie },
|
||||
payload: { netUnitAmountCents: 500, vatRate: 'reduced' },
|
||||
});
|
||||
|
||||
const calculation = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/pricing/calculate',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
payload: {
|
||||
variantId,
|
||||
quantity: 3,
|
||||
netUnitAmountCents: 1,
|
||||
totalCents: 1,
|
||||
vatAmountCents: 0,
|
||||
},
|
||||
});
|
||||
|
||||
expect(calculation.statusCode).toBe(200);
|
||||
expect(calculation.json()).toMatchObject({
|
||||
netUnitAmountCents: 500,
|
||||
netSubtotalCents: 1500,
|
||||
vatAmountCents: 150,
|
||||
totalCents: 1650,
|
||||
});
|
||||
});
|
||||
|
||||
it('writes a history row for creation and every price change (AC3)', async () => {
|
||||
const variantId = randomUUID();
|
||||
await app.inject({
|
||||
method: 'PUT',
|
||||
url: `/pricing/variants/${variantId}`,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
cookies: { [SESSION_COOKIE_NAME]: adminCookie },
|
||||
payload: { netUnitAmountCents: 100, vatRate: 'general' },
|
||||
});
|
||||
await app.inject({
|
||||
method: 'PUT',
|
||||
url: `/pricing/variants/${variantId}`,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
cookies: { [SESSION_COOKIE_NAME]: adminCookie },
|
||||
payload: { netUnitAmountCents: 200, vatRate: 'reduced' },
|
||||
});
|
||||
|
||||
const history = await pool.query(
|
||||
`SELECT previous_net_unit_amount_cents, previous_vat_rate,
|
||||
new_net_unit_amount_cents, new_vat_rate
|
||||
FROM pricing_price_history
|
||||
WHERE variant_id = $1
|
||||
ORDER BY created_at, id`,
|
||||
[variantId],
|
||||
);
|
||||
expect(history.rows).toEqual([
|
||||
{
|
||||
previous_net_unit_amount_cents: null,
|
||||
previous_vat_rate: null,
|
||||
new_net_unit_amount_cents: 100,
|
||||
new_vat_rate: 'general',
|
||||
},
|
||||
{
|
||||
previous_net_unit_amount_cents: 100,
|
||||
previous_vat_rate: 'general',
|
||||
new_net_unit_amount_cents: 200,
|
||||
new_vat_rate: 'reduced',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user