From 462f3d843502fa21dee5bd09a5ff439f29ba0b69 Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Tue, 28 Jul 2026 11:01:33 -0700 Subject: [PATCH] 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 --- tests/e2e/ui/fixtures/users.ts | 7 ++++++ tests/e2e/ui/globalSetup.ts | 42 ++++++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/tests/e2e/ui/fixtures/users.ts b/tests/e2e/ui/fixtures/users.ts index 731234b5ea9..e8f1ec47db2 100644 --- a/tests/e2e/ui/fixtures/users.ts +++ b/tests/e2e/ui/fixtures/users.ts @@ -39,6 +39,13 @@ export const users: Record = { // 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.ProxyAdmin]: ADMIN_STORAGE_PATH, [Role.ProxyAdminViewer]: ADMIN_VIEWER_STORAGE_PATH, diff --git a/tests/e2e/ui/globalSetup.ts b/tests/e2e/ui/globalSetup.ts index 6068a51c88e..ad5a53310e0 100644 --- a/tests/e2e/ui/globalSetup.ts +++ b/tests/e2e/ui/globalSetup.ts @@ -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 { + 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)) {