109 lines
4.1 KiB
TypeScript
109 lines
4.1 KiB
TypeScript
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)('shipping flows (real PostgreSQL)', () => {
|
|
const url = hasDb ? getTestDbUrl() : '';
|
|
let pool: pg.Pool;
|
|
let app: Awaited<ReturnType<typeof buildApp>>;
|
|
let cookie = '';
|
|
|
|
beforeAll(async () => {
|
|
await recreateDatabase(url);
|
|
await runMigrations(url, 'up');
|
|
pool = createPool(url);
|
|
app = await buildApp({ logger: silentLogger(), pool, cookieSecure: true });
|
|
const user = { email: 'shipping-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,
|
|
});
|
|
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,
|
|
});
|
|
cookie = cookieValue(login.headers['set-cookie']);
|
|
|
|
const zone = await app.inject({
|
|
method: 'POST',
|
|
url: '/shipping/zones',
|
|
headers: { 'content-type': 'application/json' },
|
|
cookies: { [SESSION_COOKIE_NAME]: cookie },
|
|
payload: { name: 'Peninsula', country: 'ES', postalCodePrefix: '28' },
|
|
});
|
|
const zoneId = (zone.json() as { id: string }).id;
|
|
await app.inject({
|
|
method: 'POST',
|
|
url: '/shipping/methods',
|
|
headers: { 'content-type': 'application/json' },
|
|
cookies: { [SESSION_COOKIE_NAME]: cookie },
|
|
payload: { zoneId, name: 'Standard', baseCostCents: 500, freeShippingThresholdCents: 3000 },
|
|
});
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await app.close();
|
|
await pool.end();
|
|
});
|
|
|
|
it('returns shipping cost for an address in a known zone (AC1)', async () => {
|
|
const response = await app.inject({
|
|
method: 'POST',
|
|
url: '/shipping/calculate',
|
|
headers: { 'content-type': 'application/json' },
|
|
payload: { cartTotalCents: 1000, country: 'ES', postalCode: '28001' },
|
|
});
|
|
expect(response.statusCode).toBe(200);
|
|
expect(response.json()).toMatchObject({ costCents: 500, freeApplied: false });
|
|
});
|
|
|
|
it('returns HTTP 422 SHIPPING_ZONE_NOT_FOUND for an address outside all zones (AC2)', async () => {
|
|
const response = await app.inject({
|
|
method: 'POST',
|
|
url: '/shipping/calculate',
|
|
headers: { 'content-type': 'application/json' },
|
|
payload: { cartTotalCents: 1000, country: 'US', postalCode: '94101' },
|
|
});
|
|
expect(response.statusCode).toBe(422);
|
|
expect((response.json().error as { code: string }).code).toBe('SHIPPING_ZONE_NOT_FOUND');
|
|
});
|
|
|
|
it('applies free shipping when cart total is above threshold (AC3)', async () => {
|
|
const response = await app.inject({
|
|
method: 'POST',
|
|
url: '/shipping/calculate',
|
|
headers: { 'content-type': 'application/json' },
|
|
payload: { cartTotalCents: 5000, country: 'ES', postalCode: '28001' },
|
|
});
|
|
expect(response.statusCode).toBe(200);
|
|
expect(response.json()).toMatchObject({ costCents: 0, freeApplied: true });
|
|
});
|
|
});
|