Files
canvas-frontend/qa/seeds/001_personas.mjs
T
shad b5d1ea54b0 feat(geo): EA2-backed attendance sites + versioned seed scripts
Closes Oracle deferred caveats:
- raw-SQL persona promotion (now qa/seeds/001_personas.mjs, idempotent)
- no scripted multi-persona interaction (now qa/seeds/002_persona_
  interactions.mjs - HR onboarding tx, CEO budget tx, IT provisioning
  tx + 3 cross-persona chat threads, verified end-to-end)
- geo-attendance mock data (now src/lib/attendanceApi.ts pulls 8 sites
  via flow.kind=value + defines edges from attendance_root anchor;
  qa/seeds/003_attendance_sites.mjs seeds them idempotently)

Each site is a flow.kind=value doc with config.attendance = { lat,
lng, kind, status, who, city, label }. The anchor flow + presentation
edges follow the same per-user inbox pattern that's already proven on
chat. No hardcoded JS array left in src/scenes/GeoAttendance.tsx.
2026-06-14 14:07:57 +04:00

103 lines
4.6 KiB
JavaScript

// Versioned, idempotent seed for the three canvas personas.
//
// Replaces the round-1 raw `UPDATE auth_service.user SET is_superuser=TRUE` step
// with a deterministic, auditable script that:
// 1. POSTs each persona to /api/v1/auth/register (idempotent: 409 is OK).
// 2. Promotes is_superuser via a controlled SQL UPDATE through the auth-service
// pod, recorded in this file under git so the change is reviewable.
// 3. Verifies dev-login succeeds for each persona before exiting.
//
// Usage: node qa/seeds/001_personas.mjs
// Idempotent: safe to re-run; will skip personas that already exist + are active.
import { execSync } from "node:child_process";
const TENANT_ID = "a0000000-0000-0000-0000-000000000001";
const BASE = "https://demo.flow-master.ai";
const PERSONAS = [
{ email: "ceo-head@flow-master.ai", username: "ceo_head", first: "Mariana", last: "Cole", title: "Chief Executive" },
{ email: "hr-head@flow-master.ai", username: "hr_head", first: "Aisha", last: "Khan", title: "HR Director" },
{ email: "it-head@flow-master.ai", username: "it_head", first: "Rohan", last: "Patel", title: "IT Director" },
];
const PASSWORD = "CanvasDog!ood-2026";
async function register(persona) {
const body = {
email: persona.email,
username: persona.username,
first_name: persona.first,
last_name: persona.last,
display_name: `${persona.first} ${persona.last}${persona.title}`,
password: PASSWORD,
confirm_password: PASSWORD,
tenant_id: TENANT_ID,
};
const r = await fetch(`${BASE}/api/v1/auth/register`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (r.status === 200 || r.status === 201) return { created: true };
if (r.status === 409) return { existing: true };
const txt = await r.text();
if (r.status === 400 && /already exists/i.test(txt)) return { existing: true };
if (r.status === 422 && /already exists/i.test(txt)) return { existing: true };
if (r.status === 429) return { rate_limited: true };
throw new Error(`register ${persona.email}${r.status}: ${txt.slice(0, 200)}`);
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
function promoteToSuperuser() {
const pod = execSync(`ssh build01 'kubectl -n baseline get pod -l app=auth-service -o jsonpath="{.items[?(@.status.phase==\\"Running\\")].metadata.name}"'`, { encoding: "utf8" }).trim();
if (!pod) throw new Error("no running auth-service pod found");
const remoteScript = "/tmp/canvas_personas_promote.py";
const py = [
"import asyncio, os",
"from sqlalchemy.ext.asyncio import create_async_engine",
"from sqlalchemy import text",
"async def main():",
" e = create_async_engine(os.environ['DATABASE_URL'])",
" async with e.connect() as c:",
" await c.execute(text(\"UPDATE auth_service.\\\"user\\\" SET is_superuser=TRUE, status='active' WHERE email LIKE '%-head@flow-master.ai'\"))",
" await c.commit()",
" r = await c.execute(text(\"SELECT email, is_superuser, status FROM auth_service.\\\"user\\\" WHERE email LIKE '%-head@flow-master.ai' ORDER BY email\"))",
" for row in r: print(row)",
"asyncio.run(main())",
].join("\n");
const b64 = Buffer.from(py).toString("base64");
execSync(`ssh build01 'kubectl -n baseline exec ${pod} -- sh -c "echo ${b64} | base64 -d > ${remoteScript}"'`, { encoding: "utf8" });
return execSync(`ssh build01 'kubectl -n baseline exec ${pod} -- python3 ${remoteScript}'`, { encoding: "utf8" });
}
async function verifyDevLogin(persona) {
const r = await fetch(`${BASE}/api/v1/auth/dev-login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: persona.email }),
});
if (!r.ok) throw new Error(`dev-login ${persona.email}${r.status}`);
const body = await r.json();
if (!body.access_token) throw new Error(`dev-login ${persona.email} no access_token`);
return body.access_token.slice(0, 24) + "...";
}
const results = [];
for (const p of PERSONAS) {
const reg = await register(p);
results.push({ email: p.email, ...reg });
const status = reg.existing ? "already exists" : reg.rate_limited ? "rate-limited (skip)" : "created";
console.log(`register ${p.email}: ${status}`);
await sleep(1500);
}
console.log("\npromoting to superuser via auth_service DB:");
console.log(promoteToSuperuser());
console.log("\nverifying dev-login:");
for (const p of PERSONAS) {
const token = await verifyDevLogin(p);
console.log(` ${p.email}${token}`);
}
console.log("\nseed complete. all 3 personas registered, promoted, and dev-login verified.");