Files
static-pages/apps/survey.immich.app/backend/test/helpers.ts
T
2026-08-19 14:53:56 +02:00

159 lines
5.3 KiB
TypeScript

const API = process.env.API_URL || 'http://localhost:8787';
// Admin setup is one-time per server. In CI the integration suite and the
// Playwright e2e suite run sequentially against the SAME wrangler dev
// instance, so whichever runs first performs setup and the other must be able
// to log in with the same password. Keep this default in sync with the e2e
// helper (e2e/helpers.ts TEST_PASSWORD); override both via TEST_PASSWORD.
const ADMIN_PASSWORD = process.env.TEST_PASSWORD || 'e2e-test-password-12345';
let adminCookie: string | null = null;
export async function request(path: string, options?: RequestInit & { cookie?: string }): Promise<Response> {
const { cookie, ...fetchOptions } = options ?? {};
const headers: Record<string, string> = {};
if (fetchOptions.body) {
headers['Content-Type'] = 'application/json';
}
if (cookie) {
headers['Cookie'] = cookie;
}
if (fetchOptions.headers) {
Object.assign(headers, fetchOptions.headers);
}
return fetch(`${API}${path}`, { ...fetchOptions, headers });
}
export async function authedRequest(path: string, options?: RequestInit): Promise<Response> {
const cookie = await getAdminCookie();
return request(path, { ...options, cookie });
}
export async function getAdminCookie(): Promise<string> {
if (adminCookie) {return adminCookie;}
const meRes = await request('/api/auth/me');
const me = (await meRes.json()) as { needsSetup?: boolean };
if (me.needsSetup) {
const setupRes = await request('/api/auth/setup', {
method: 'POST',
body: JSON.stringify({ password: ADMIN_PASSWORD }),
});
const setCookie = setupRes.headers.get('set-cookie');
if (setCookie) {
adminCookie = setCookie.split(';', 1)[0];
return adminCookie;
}
}
const loginRes = await request('/api/auth/password-login', {
method: 'POST',
body: JSON.stringify({ password: ADMIN_PASSWORD }),
});
const setCookie = loginRes.headers.get('set-cookie');
if (setCookie) {
adminCookie = setCookie.split(';', 1)[0];
}
if (!adminCookie) {throw new Error('Failed to authenticate for integration tests');}
return adminCookie;
}
export async function createPublishedSurvey(options?: {
title?: string;
slug?: string;
password?: string;
}): Promise<{ surveyId: string; slug: string; sectionId: string; questionIds: string[] }> {
const title = options?.title ?? `Test Survey ${Date.now()}`;
const slug = options?.slug ?? `test-${Date.now()}`;
const surveyRes = await authedRequest('/api/surveys', {
method: 'POST',
body: JSON.stringify({ title }),
});
const survey = (await surveyRes.json()) as { id: string };
await authedRequest(`/api/surveys/${survey.id}`, {
method: 'PUT',
body: JSON.stringify({
slug,
...(options?.password && { password: options.password }),
}),
});
const sectionRes = await authedRequest(`/api/surveys/${survey.id}/sections`, {
method: 'POST',
body: JSON.stringify({ title: 'Section 1' }),
});
const section = (await sectionRes.json()) as { id: string };
const questionIds: string[] = [];
for (const q of [
{
text: 'Pick one',
type: 'radio',
options: [
{ label: 'A', value: 'A' },
{ label: 'B', value: 'B' },
],
},
{ text: 'Your name', type: 'text' },
{ text: 'Rate us', type: 'nps' },
]) {
const qRes = await authedRequest(`/api/surveys/${survey.id}/sections/${section.id}/questions`, {
method: 'POST',
body: JSON.stringify(q),
});
const question = (await qRes.json()) as { id: string };
questionIds.push(question.id);
}
await authedRequest(`/api/surveys/${survey.id}/publish`, { method: 'PUT' });
return { surveyId: survey.id, slug, sectionId: section.id, questionIds };
}
const DEV_SESSION_SECRET = 'dev-session-secret-DO-NOT-USE-IN-PRODUCTION';
const SESSION_COOKIE_NAME = 'survey_session';
// Fail fast if the integration suite is ever pointed at a non-dev server —
// an env-supplied SESSION_SECRET here would silently mint invalid cookies
// and every auth-gated test would 401 with no hint why. The dev server is
// the only environment this forgery should ever succeed against.
if (process.env.SESSION_SECRET && process.env.SESSION_SECRET !== DEV_SESSION_SECRET) {
throw new Error(
'Integration tests must run against the dev wrangler server (SESSION_SECRET=dev-session-secret-DO-NOT-USE-IN-PRODUCTION).',
);
}
export async function createCookieForRole(role: 'admin' | 'editor' | 'viewer'): Promise<string> {
const header = btoa(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
const payload = btoa(
JSON.stringify({
sub: `test-${role}`,
email: `${role}@test.local`,
name: `Test ${role.charAt(0).toUpperCase() + role.slice(1)}`,
role,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 8 * 60 * 60,
}),
);
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(DEV_SESSION_SECRET),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign'],
);
const sig = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(`${header}.${payload}`));
const signature = btoa(String.fromCharCode(...new Uint8Array(sig)))
.replaceAll('+', '-')
.replaceAll('/', '_')
.replace(/=+$/, '');
return `${SESSION_COOKIE_NAME}=${header}.${payload}.${signature}`;
}
export { ADMIN_PASSWORD };