fix(e2e/ui): seed the role users via API in global setup

The five UI login roles only ever existed as rows in fixtures/seed.sql, which
runs solely in the local runner's throwaway postgres. The stage job targets
the shared gateway, whose DB nobody seeds, so the first DB-backed role
(proxy_admin_viewer) got the no-row 401 from /v2/login, global setup timed
out on /ui/login, and playwright exited 1 before a single UI test ran, every
run. Global setup now ensures each role user exists through /user/new with
the master key, falling back to /user/update when the row already exists so
reruns and the seeded local DB stay consistent
This commit is contained in:
ryan-crabbe-berri 2026-07-28 11:01:33 -07:00
parent b930e2fc2b
commit 462f3d8435
2 changed files with 47 additions and 2 deletions

View file

@ -39,6 +39,13 @@ export const users: Record<Role, { email: string; password: string }> = {
// Re-exported from constants so the paths have one definition; they must honor
// ARTIFACT_DIR, since the suite runs from a read-only cwd in the e2e image.
export const DB_ROLE_USERS: ReadonlyArray<{ role: Role; userId: string; dbRole: string }> = [
{ role: Role.ProxyAdminViewer, userId: "e2e-admin-viewer", dbRole: "proxy_admin_viewer" },
{ role: Role.InternalUser, userId: "e2e-internal-user", dbRole: "internal_user" },
{ role: Role.InternalUserViewer, userId: "e2e-internal-viewer", dbRole: "internal_user_viewer" },
{ role: Role.TeamAdmin, userId: "e2e-team-admin", dbRole: "internal_user" },
];
export const STORAGE_PATHS: Record<Role, string> = {
[Role.ProxyAdmin]: ADMIN_STORAGE_PATH,
[Role.ProxyAdminViewer]: ADMIN_VIEWER_STORAGE_PATH,

View file

@ -1,9 +1,44 @@
import { chromium, expect, request } from "@playwright/test";
import { users, Role, STORAGE_PATHS } from "./fixtures/users";
import { chromium, expect, request, APIRequestContext } from "@playwright/test";
import { users, Role, STORAGE_PATHS, DB_ROLE_USERS } from "./fixtures/users";
import { ARTIFACT_DIR, UI_BASE_URL } from "./constants";
import * as fs from "fs";
import * as path from "path";
async function ensureDbRoleUser(
api: APIRequestContext,
baseUrl: string,
masterKey: string,
entry: { role: Role; userId: string; dbRole: string },
): Promise<void> {
const { email, password } = users[entry.role];
const auth = { Authorization: `Bearer ${masterKey}` };
const created = await api.post(`${baseUrl}/user/new`, {
headers: auth,
data: {
user_id: entry.userId,
user_email: email,
user_role: entry.dbRole,
password,
auto_create_key: false,
},
});
if (created.ok()) return;
const body = await created.text();
if (created.status() === 400 && body.includes("already exists")) {
const updated = await api.post(`${baseUrl}/user/update`, {
headers: auth,
data: { user_id: entry.userId, user_role: entry.dbRole, password },
});
if (!updated.ok()) {
throw new Error(
`Ensuring UI role user ${entry.userId} failed on /user/update (${updated.status()}): ${await updated.text()}`,
);
}
return;
}
throw new Error(`Ensuring UI role user ${entry.userId} failed on /user/new (${created.status()}): ${body}`);
}
async function globalSetup() {
const browser = await chromium.launch();
const rootPath = process.env.SERVER_ROOT_PATH ?? "";
@ -29,6 +64,9 @@ async function globalSetup() {
if (!settingsRes.ok()) {
throw new Error(`Enabling enable_projects_ui failed (${settingsRes.status()}): ${await settingsRes.text()}`);
}
for (const entry of DB_ROLE_USERS) {
await ensureDbRoleUser(api, `${UI_BASE_URL}${rootPath}`, masterKey, entry);
}
await api.dispose();
for (const role of Object.values(Role)) {