mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
fix(e2e): align browser harness with local stack
This commit is contained in:
1 parent
db5980c9bd
commit
cf19d23d8a
20 files changed
+267
-85
No files matched your search
@@ -0,0 +1,82 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { Pool } from 'pg';
|
||||
|
||||
function localDatabaseUrl(): string {
|
||||
const raw = process.env.E2E_DATABASE_URL;
|
||||
if (!raw) throw new Error('Run E2E through npm run verify:e2e');
|
||||
|
||||
const url = new URL(raw);
|
||||
if (url.protocol !== 'postgresql:' || !['127.0.0.1', 'localhost', '[::1]'].includes(url.hostname)) {
|
||||
throw new Error(`E2E refuses non-loopback PostgreSQL: ${url.origin}`);
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
export interface RedemptionFixture {
|
||||
email: string;
|
||||
code: string;
|
||||
userId: string;
|
||||
codeId: string;
|
||||
assertRedeemed(): Promise<void>;
|
||||
cleanup(): Promise<void>;
|
||||
}
|
||||
|
||||
export async function createRedemptionFixture(): Promise<RedemptionFixture> {
|
||||
const pool = new Pool({ connectionString: localDatabaseUrl() });
|
||||
const nonce = randomUUID();
|
||||
const userId = randomUUID();
|
||||
const email = `redeem-${nonce}@example.test`;
|
||||
const code = `E2E-${nonce}`.toUpperCase();
|
||||
|
||||
let codeId: string | undefined;
|
||||
try {
|
||||
const codeResult = await pool.query<{ id: string }>(
|
||||
`insert into redemption_codes (code, description, max_uses, is_active, expires_at)
|
||||
values ($1, 'Playwright loopback fixture', 1, true, now() + interval '1 hour')
|
||||
returning id`,
|
||||
[code],
|
||||
);
|
||||
codeId = codeResult.rows[0]?.id;
|
||||
if (!codeId) throw new Error('Failed to create redemption code fixture');
|
||||
|
||||
await pool.query(
|
||||
`insert into profiles (id, email, name)
|
||||
values ($1, $2, 'Playwright Redemption Fixture')`,
|
||||
[userId, email],
|
||||
);
|
||||
} catch (cause) {
|
||||
if (codeId) await pool.query('delete from redemption_codes where id = $1', [codeId]);
|
||||
await pool.end();
|
||||
throw cause;
|
||||
}
|
||||
|
||||
return {
|
||||
email,
|
||||
code,
|
||||
userId,
|
||||
codeId,
|
||||
async assertRedeemed() {
|
||||
const profile = await pool.query<{ is_premium: boolean }>(
|
||||
'select is_premium from profiles where id = $1',
|
||||
[userId],
|
||||
);
|
||||
if (profile.rows[0]?.is_premium !== true) {
|
||||
throw new Error('Redemption fixture profile was not marked premium');
|
||||
}
|
||||
|
||||
const redemptions = await pool.query<{ count: number }>(
|
||||
`select count(*)::int as count from code_redemptions
|
||||
where code_id = $1 and user_id = $2`,
|
||||
[codeId, userId],
|
||||
);
|
||||
if (redemptions.rows[0]?.count !== 1) {
|
||||
throw new Error('Redemption fixture did not record exactly one redemption');
|
||||
}
|
||||
},
|
||||
async cleanup() {
|
||||
await pool.query('delete from redemption_codes where id = $1', [codeId]);
|
||||
await pool.query('delete from profiles where id = $1', [userId]);
|
||||
await pool.end();
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
|
||||
export function localSupabase() {
|
||||
const url = process.env.SUPABASE_URL;
|
||||
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
||||
if (!url || !serviceKey) throw new Error('Run E2E through npm run verify:e2e');
|
||||
const host = new URL(url).hostname;
|
||||
if (host !== '127.0.0.1' && host !== 'localhost') {
|
||||
throw new Error(`E2E refuses non-local Supabase host: ${host}`);
|
||||
}
|
||||
return createClient(url, serviceKey, {
|
||||
auth: { autoRefreshToken: false, persistSession: false },
|
||||
});
|
||||
}
|
||||
|
||||
export async function createRedemptionFixture() {
|
||||
const client = localSupabase();
|
||||
const nonce = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
const email = `redeem-${nonce}@example.test`;
|
||||
const code = `E2E-${nonce}`.toUpperCase();
|
||||
const { data: created, error: userError } = await client.auth.admin.createUser({
|
||||
email,
|
||||
password: 'Sauci-e2e-only-2026!',
|
||||
email_confirm: true,
|
||||
});
|
||||
if (userError || !created.user) throw userError ?? new Error('User creation failed');
|
||||
const { data: redemptionCode, error: codeError } = await client.from('redemption_codes').insert({
|
||||
code,
|
||||
description: 'Playwright local fixture',
|
||||
max_uses: 1,
|
||||
is_active: true,
|
||||
expires_at: new Date(Date.now() + 3_600_000).toISOString(),
|
||||
}).select().single();
|
||||
if (codeError) throw codeError;
|
||||
return { client, email, code, userId: created.user.id, codeId: redemptionCode.id };
|
||||
}
|
||||
Reference in new issue
Block a user