chore(infra): update dev compose, add playwright e2e suite & assets
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import { type Page, test } from "@playwright/test";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
export const ADMIN_EMAIL = "admin";
|
||||
export const ADMIN_PASSWORD = "wZW64CUbXbv9679dmR1FIbz3";
|
||||
export const BASE = "https://localhost";
|
||||
|
||||
const AUTH_FILE = path.join(__dirname, ".auth-state.json");
|
||||
|
||||
interface AuthState {
|
||||
available: boolean;
|
||||
accessToken?: string;
|
||||
refreshToken?: string;
|
||||
user?: { id: number; role: string; email: string };
|
||||
}
|
||||
|
||||
export function loadAuth(): AuthState {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(AUTH_FILE, "utf-8")) as AuthState;
|
||||
} catch {
|
||||
return { available: false };
|
||||
}
|
||||
}
|
||||
|
||||
/** Skip this test if auth is not available (CAPTCHA not exposed). */
|
||||
export function requireAuth(): AuthState {
|
||||
const auth = loadAuth();
|
||||
if (!auth.available) {
|
||||
test.skip(true, "Auth not available — restart API with CAPTCHA_EXPOSE_ANSWER=true");
|
||||
}
|
||||
return auth;
|
||||
}
|
||||
|
||||
/** Inject cached auth cookies into the page context. */
|
||||
export async function injectAuth(page: Page): Promise<AuthState> {
|
||||
const auth = requireAuth();
|
||||
// Seed Zustand persist store so the app hydrates with a valid user (skips redirect)
|
||||
await page.addInitScript(({ user }) => {
|
||||
try {
|
||||
localStorage.setItem(
|
||||
"pargar.auth",
|
||||
JSON.stringify({ state: { user }, version: 0 }),
|
||||
);
|
||||
} catch {}
|
||||
}, { user: auth.user });
|
||||
await page.context().addCookies([
|
||||
{ name: "pargar_access", value: auth.accessToken!, domain: "localhost", path: "/", secure: true, sameSite: "Lax" },
|
||||
{ name: "pargar_refresh", value: auth.refreshToken!, domain: "localhost", path: "/", secure: true, httpOnly: true, sameSite: "Lax" },
|
||||
]);
|
||||
return auth;
|
||||
}
|
||||
|
||||
export async function loginAs(page: Page, identity: string, password: string) {
|
||||
const captchaRes = await page.request.get(`${BASE}/api/auth/captcha`);
|
||||
const captchaData = await captchaRes.json();
|
||||
const answer: string = captchaData.answer ?? "";
|
||||
const challengeId: string = captchaData.challengeId ?? "";
|
||||
|
||||
const loginRes = await page.request.post(`${BASE}/api/auth/login`, {
|
||||
data: { username: identity, password, challengeId, captchaAnswer: answer, rememberMe: false },
|
||||
});
|
||||
const loginBody = await loginRes.json();
|
||||
|
||||
if (!loginBody.accessToken) {
|
||||
throw new Error(`Login failed for ${identity}: ${JSON.stringify(loginBody)}`);
|
||||
}
|
||||
|
||||
await page.context().addCookies([
|
||||
{ name: "access_token", value: loginBody.accessToken, domain: "localhost", path: "/", secure: true, sameSite: "Lax" },
|
||||
{ name: "refresh_token", value: loginBody.refreshToken, domain: "localhost", path: "/", secure: true, httpOnly: true, sameSite: "Lax" },
|
||||
]);
|
||||
return loginBody;
|
||||
}
|
||||
|
||||
export async function apiLogin(page: Page, identity: string, password: string) {
|
||||
const captchaRes = await page.request.get(`${BASE}/api/auth/captcha`);
|
||||
const { challengeId, answer } = await captchaRes.json();
|
||||
const res = await page.request.post(`${BASE}/api/auth/login`, {
|
||||
data: { username: identity, password, challengeId, captchaAnswer: answer, rememberMe: false },
|
||||
});
|
||||
return res.json();
|
||||
}
|
||||
Reference in New Issue
Block a user