import { Client } from 'pg'; /** * Direct database access for the tests, for the one thing the API deliberately * will not give them. * * The password-reset token is only ever delivered by email, which these tests * cannot read. It is read from the database rather than through a helper * endpoint because an endpoint that returns a reset token for an arbitrary * address is account takeover for every customer if it is ever reachable, and * an environment gate is a thin thing to stand between that and production. * Doing it here keeps the capability entirely inside the test process. * * That reasoning is unchanged from when it lived inline in * password-reset.spec.ts. What has changed is that it is no longer sitting in a * spec where it can be copied into the next one that wants a shortcut. */ /** * The default port is 55500, matching scripts/start-local.ps1. * * It used to be 55432, which is the integration suite's disposable Postgres — * a different database, with different credentials, that the app under test is * not connected to. Worse, 55432 is reserved by Hyper-V on at least one machine * here, so the spec failed with a bare ECONNREFUSED naming a port nobody had * chosen. TEST_PGPORT still overrides, for CI and for anyone running the stack * somewhere else. */ function connectionSettings() { return { host: process.env.TEST_PGHOST || 'localhost', port: parseInt(process.env.TEST_PGPORT || '55500', 10), user: process.env.TEST_PGUSER || 'redefined_local', password: process.env.TEST_PGPASSWORD || 'redefined_local', database: process.env.TEST_PGDATABASE || 'redefined_local' }; } /** Opens a connection, runs the query, and closes it whatever happens. */ async function withClient(run: (client: Client) => Promise): Promise { const client = new Client(connectionSettings()); await client.connect(); try { return await run(client); } finally { await client.end(); } } /** * The most recent password-reset token issued to an address. * * Throws rather than returning null: every caller is about to build a URL from * it, and a missing token means the request under test did not do what it said, * which is worth failing loudly at the point it happened. */ export async function readPasswordResetToken(email: string): Promise { return withClient(async (client) => { const { rows } = await client.query( `SELECT t.token FROM customer_tokens t JOIN customers c ON c.id = t.customer_id WHERE c.email = $1 AND t.kind = 'password_reset' ORDER BY t.created_at DESC LIMIT 1`, [email] ); if (!rows.length) throw new Error(`no password_reset token issued for ${email}`); return rows[0].token as string; }); }