Shuffle/e2e/support/db.ts
cschlaefke f302c56904 test(auth): add Playwright project with api tests for KTO-01 (GoTrue, Mailpit, db helper)
Refs #38

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-03 10:29:18 +01:00

78 lines
2.7 KiB
TypeScript

/**
* Direkter Datenbankzugriff fuer Tests. NUR lokal/CI: der Helfer bricht ab, wenn der Host nicht
* lokal ist (er veraendert `auth.users`). Spec 7.1 Krit. 8 (B0 (i)): Maßgeblich fuer den
* Linkablauf sind `auth.users.recovery_sent_at` und `confirmation_sent_at`, nicht
* `auth.one_time_tokens.created_at`.
*/
import { Client } from 'pg';
import { authConfig, env } from './auth-config';
function assertLocal(url: string): void {
const host = new URL(url).hostname;
if (!['127.0.0.1', 'localhost', '::1', 'host.docker.internal'].includes(host)) {
throw new Error(`e2e/support/db.ts nur lokal/CI: Host "${host}" ist nicht erlaubt`);
}
}
export async function withDb<T>(fn: (c: Client) => Promise<T>): Promise<T> {
assertLocal(env.dbUrl);
const client = new Client({ connectionString: env.dbUrl });
await client.connect();
try {
return await fn(client);
} finally {
await client.end();
}
}
export async function countUsers(): Promise<number> {
return withDb(async (c) => Number((await c.query('select count(*) from auth.users')).rows[0].count));
}
export async function userByEmail(email: string) {
return withDb(async (c) => (await c.query('select * from auth.users where email = $1', [email.toLowerCase()])).rows[0]);
}
export async function profileByEmail(email: string) {
return withDb(
async (c) =>
(
await c.query(
`select p.* from public.profiles p join auth.users u on u.id = p.id where u.email = $1`,
[email.toLowerCase()],
)
).rows[0],
);
}
/** Setzt den Link als abgelaufen: `recovery_sent_at = now() - (otp_expiry + 1 s)` (AUTH-CFG-07). */
export async function expireRecoveryLink(email: string, secondsAgo = authConfig.otpExpirySeconds + 1) {
await withDb((c) =>
c.query(`update auth.users set recovery_sent_at = now() - make_interval(secs => $2) where email = $1`, [
email.toLowerCase(),
secondsAgo,
]),
);
}
/** Wie oben fuer den Bestaetigungslink (`confirmation_sent_at`). */
export async function expireConfirmationLink(email: string, secondsAgo = authConfig.otpExpirySeconds + 1) {
await withDb((c) =>
c.query(`update auth.users set confirmation_sent_at = now() - make_interval(secs => $2) where email = $1`, [
email.toLowerCase(),
secondsAgo,
]),
);
}
/** `not_after` aller Sitzungen des Nutzers (AUTH-CFG-16: nicht gesetzt). */
export async function sessionNotAfter(email: string): Promise<(string | null)[]> {
return withDb(async (c) =>
(
await c.query(
`select s.not_after from auth.sessions s join auth.users u on u.id = s.user_id where u.email = $1`,
[email.toLowerCase()],
)
).rows.map((r) => r.not_after),
);
}