/* User provisioning for the Dolibarr admin UI, driven by Playwright. Why the UI and not raw SQL: - Passwords use MAIN_SECURITY_HASH_ALGO=password_hash (bcrypt), so we let Dolibarr hash them by submitting the create form. - API keys are stored ENCRYPTED with the instance key DOLI_INSTANCE_UNIQUE_ID. Each instance (incl. the sandbox) has its own uuid, so a key can only be produced correctly by that instance — we MUST generate it via the UI and read back the resulting value, never INSERT a raw key. IMPORTANT — selectors below are best-effort and have NOT been verified against a live, installed sandbox (the sandbox Dolibarr is not provisioned yet). Field names / icon controls are taken from Dolibarr 22 conventions and must be confirmed on the first real run. Spots that are guessed are marked GUESS:. */ import type { Page } from "playwright"; import forms from "../forms.ts"; import login from "../login.ts"; type UserCtx = { page: Page; dolibarrAddress: string; adminCredentials: { username: string; password: string }; // imgFolderPath is required by forms.fillForm's signature even though no // file input is used here; provisionSandbox.ts supplies it from globalCtx. imgFolderPath: string; }; type NewUser = { login: string; password: string; lastname?: string; admin?: boolean; }; // The text fields filled via forms.fillForm (the admin flag is a checkbox we // handle separately, so it is intentionally not part of this type). type UserFormFields = { login: string; password: string; lastname?: string; }; // fr_FR Dolibarr user create form (/user/card.php?action=create&mode=create). // GUESS: confirm these input names on first real run. `login` and `lastname` // are stable across Dolibarr versions; the password field has been `password` // for the manual-entry case (the "generate automatically" option is a separate // radio/checkbox we leave untouched so our explicit value is used). const newUserInputNames = new Map([ ["login", "login"], ["lastname", "lastname"], ["password", "password"], ]); /* Create a user via /user/card.php?action=create and return its numeric id. The admin flag is a checkbox (name="admin"); fillForm has no checkbox handler so we toggle it explicitly. After submit Dolibarr redirects to the user card whose URL carries ?id= — we parse the id from there (falling back to a hidden input on the page). */ async function createUser( globalCtx: UserCtx, { login: userLogin, password, lastname, admin = false }: NewUser, ): Promise { const { page, dolibarrAddress, imgFolderPath } = globalCtx; await login.doAdminLogin(globalCtx); // Idempotent: if the login already exists (e.g. a re-run), return its id rather // than submitting a duplicate create (which Dolibarr rejects). const existingId = await findUserId(globalCtx, userLogin); if (existingId !== undefined) return existingId; await page.goto(`${dolibarrAddress}/user/card.php?action=create&mode=create`); // Fill text inputs (login, lastname, password) without submitting yet. const formFields: UserFormFields = { login: userLogin, password, lastname, }; await forms.fillForm( { page, imgFolderPath }, formFields, newUserInputNames, 0, ); // Admin checkbox — only present when the logged-in user is themselves admin. // GUESS: input[name="admin"]; on some setups it's a