feat(test): provision erp-sandbox via Playwright (REST API + write-scoped ai_agent_sandbox user) #14
Reference in New Issue
Block a user
Delete Branch "claude/poc-provision-sandbox"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What this adds
Extends the existing Deno + Playwright UI-automation POC (
test/) to provision theerp-sandboxDolibarr so the AI agent can write to it. New entrypointprovisionSandbox.tsruns end-to-end:moduleSetup.tsgainsenableApiModule(ctx)(activation maps tollx_const.MAIN_MODULE_API=1; UI toggle on/admin/modules.php?mode=commonkanban).ai_agent_sandboxuser (non-admin) —userSetup.tscreateUser.userSetup.tsassignRights.userSetup.tsgenerateApiKey, then emit it safely.New / changed files
test/scripts/admin/moduleSetup.ts— addenableApiModule(ctx). Resilient by design: tries the fr_FR card label"API/Web services REST (serveur)"(anchored exact match, same path asconfigureModule); if that card isn't present, falls back to matching any module card whose title matches/API.*REST|REST.*API/i. Throws a descriptive error if neither matches.test/scripts/admin/userSetup.ts(new):createUser(ctx, {login, password, lastname?, admin?})→ fills/user/card.php?action=createviaforms.fillForm, toggles theadmincheckbox explicitly, submits, and returns the new numeric id (parsed from the resulting?id=URL, falling back to a hiddeninput[name="id"]).assignRights(ctx, userId, rightIds[])→ for each right, navigates/user/perms.php?id=<userId>and clicks thea[href*="action=addrights"][href*="rights=<id>"]link. Idempotent: if no addrights link exists (already granted), it skips.generateApiKey(ctx, userId)→ on the user card edit page, triggers the generate control and reads the value back out ofinput[name="api_key"], returning it as a string. Reuses an existing key if already set. Does not log the value.test/provisionSandbox.ts(new entrypoint —main.tsuntouched) — buildsglobalCtxfrom env (defaultDOLIBARR_ADDRESS=https://erp-sandbox.arcodange.lab), runs the full flow, then writes the key totest/.ai_agent_sandbox.key(gitignored) with the console lineAI_AGENT_SANDBOX API KEY WRITTEN TO test/.ai_agent_sandbox.key. A comment documents the next step (load into the dolibarr skill's sandbox config / Vaultkvv2/erp-sandbox/ai_agent); Vault writes are intentionally not implemented in the POC.test/.gitignore(new) —.env,.ai_agent_sandbox.key,*.key(defense-in-depth; root.gitignorealready covers.env/*.key).test/.env.example— adds a sandbox-provisioning section + the kubectl source for each secret.test/README.md(new) — adds a## Provision the sandboxsection with the run command and the kubectl one-liners for theerp-sandboxnamespace.Why Playwright (UI), not SQL
API keys are stored encrypted with the instance key
DOLI_INSTANCE_UNIQUE_ID(observed stored lengths 66/82 vs the 32-char plaintext). The sandbox has its own instance uuid, so a key can only be produced correctly by the sandbox itself — it must be generated through the UI and read back, neverINSERTed raw. Passwords likewise useMAIN_SECURITY_HASH_ALGO=password_hash(bcrypt), so we let Dolibarr hash them via the create form. This is the whole reason the provisioning is driven through Playwright rather than direct DB writes.Write rights granted to
ai_agent_sandboxRead + create on each module (stable Dolibarr rights ids, verified against prod Dolibarr 22.0.4):
Granting is done by clicking the perm rows in the UI; the ids are used to target the
addrightslinks (and documented as the SQL-fallback reference).Validation status — runtime check deferred
deno check provisionSandbox.ts scripts/admin/userSetup.tspasses (also re-checkedmain.ts+moduleSetup.ts); the repo usescheckJs: true.Selectors / labels GUESSED (confirm on first real run, marked
GUESS:in code)"API/Web services REST (serveur)"(fuzzy/API.*REST|REST.*API/ifallback in place).login,lastname,password, and theadmincheckbox (input[name="admin"]; some setups render it as a<select>).?id=in the post-submit URL, fallback hiddeninput[name="id"].a[href*="action=addrights"][href*="rights=<id>"].input[name="api_key"]plus one of#generate_api_key/a[href*="action=generateapikey"]/span.fa-dice/a.fa-refresh.Secrets hygiene
The generated API key is written only to the gitignored
test/.ai_agent_sandbox.keyand never printed; admin/DB passwords come from env and are never echoed. Staged diff was grep-scanned — no secret values committed.🤖 Generated with Claude Code