Files
erp/test/scripts/admin/moduleSetup.ts
Gabriel Radureau 523f0cf001 feat(test): provision erp-sandbox via Playwright (REST API + write-scoped ai_agent_sandbox user)
Extend the Deno + Playwright UI-automation POC to provision the erp-sandbox
Dolibarr for the AI agent:

- moduleSetup.ts: add enableApiModule(ctx) — toggles the REST API / Web services
  module on /admin/modules.php (kanban). Resilient: tries the fr_FR card label
  "API/Web services REST (serveur)" first, falls back to a /API.*REST|REST.*API/i
  title match if the exact label is absent.
- userSetup.ts (new): createUser (returns the new numeric id), assignRights
  (clicks each addrights link on /user/perms.php, idempotent), generateApiKey
  (triggers Dolibarr's generate control on the user card and reads the value back).
- provisionSandbox.ts (new entrypoint, main.ts untouched): login → enable API →
  create ai_agent_sandbox (non-admin) → grant write rights → generate API key,
  then write the key to test/.ai_agent_sandbox.key (gitignored) instead of
  printing it.
- .gitignore (new), .env.example + README.md: sandbox vars, the
  deno run --allow-all provisionSandbox.ts command, and kubectl one-liners to
  pull DOLI_ADMIN_PASSWORD (secretkv) / DOLI_DB_PASSWORD (vso-db-credentials)
  from the erp-sandbox namespace.

Why UI not SQL: API keys are encrypted with the instance's DOLI_INSTANCE_UNIQUE_ID,
so the key must be generated by the sandbox itself, not INSERTed raw.

deno check passes for provisionSandbox.ts and scripts/admin/userSetup.ts.
NOT run end-to-end: the sandbox Dolibarr is not installed yet (empty DB / fresh
install wizard), so the selectors are best-effort Dolibarr 22 conventions and
must be confirmed on the first real run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-29 07:34:21 +02:00

104 lines
3.3 KiB
TypeScript

// info-box
import { Locator, Page } from "playwright";
import forms from "../forms.ts";
import login from "../login.ts";
// span.info-box-title has-text /moduleName/
// span.fa-cog[title="Configuration"]
type ModuleCtx = {
page: Page;
dolibarrAddress: string;
adminCredentials: { username: string; password: string };
};
async function configureModule(
globalCtx: ModuleCtx,
{
moduleName,
enabled,
onConfiguration,
}: {
moduleName: string;
enabled: boolean;
onConfiguration?: CallableFunction;
}
): Promise<void> {
const {page, dolibarrAddress}= globalCtx;
await login.doAdminLogin(globalCtx);
await page.goto(`${dolibarrAddress}/admin/modules.php?mode=commonkanban`);
const moduleDiv = page.locator('.info-box', {
has: page.locator('.info-box-title',{
hasText: new RegExp('^'+moduleName+'\n?$','i')
})
});
await forms.toggleOnOff(moduleDiv, enabled);
if(enabled && onConfiguration!==undefined) {
await moduleDiv.locator(`span.fa-cog[title="Configuration"]`).click();
await onConfiguration();
}
}
/*
Enable Dolibarr's REST API / Web services module.
Activation maps to llx_const.MAIN_MODULE_API=1; in the UI this is the module
card on /admin/modules.php (kanban mode). In fr_FR the card title is
"API/Web services REST (serveur)" (family "Interfaces").
NOTE: confirm the exact card title on the first real run against an installed
sandbox — the label has not been verified live here. To stay resilient we try
the known fr_FR label first (anchored exact match, same as configureModule),
and if that card is not present we fall back to matching ANY module card whose
title contains "API ... REST" / "REST ... API" in either order.
*/
async function enableApiModule(globalCtx: ModuleCtx): Promise<void> {
const {page, dolibarrAddress}= globalCtx;
// fr_FR label as observed in the module catalogue. Confirm on first real run.
const knownLabel = "API/Web services REST (serveur)";
await login.doAdminLogin(globalCtx);
await page.goto(`${dolibarrAddress}/admin/modules.php?mode=commonkanban`);
const exactCard = page.locator('.info-box', {
has: page.locator('.info-box-title',{
hasText: new RegExp('^'+knownLabel+'\n?$','i')
})
});
if(await exactCard.count() > 0) {
await forms.toggleOnOff(exactCard, true);
return;
}
// Fallback: the exact fr_FR label was not found (different Dolibarr version,
// locale, or wording). Match any card whose title looks like the REST API
// module regardless of word order.
const fuzzyCard = page.locator('.info-box', {
has: page.locator('.info-box-title',{
hasText: /API.*REST|REST.*API/i
})
});
if(await fuzzyCard.count() === 0) {
throw new Error(
`REST API module card not found on ${dolibarrAddress}/admin/modules.php `+
`(tried exact label "${knownLabel}" and /API.*REST|REST.*API/i). `+
`Confirm the card title in the running sandbox.`,
);
}
// If several cards match the fuzzy pattern, toggle the first one only.
await forms.toggleOnOff(fuzzyCard.first() as Locator, true);
}
export default {
configureModule,
enableApiModule,
}