pediatric-ai-scribe-v3/e2e/seed.js
Daniel e58aa1b996
Some checks failed
Forgejo Docker Build / Root app tests (push) Successful in 49s
Forgejo Docker Build / Build Docker image (push) Successful in 7s
Forgejo Docker Build / End-to-end (browser) (push) Failing after 6s
refactor: sign-in codes and registration invitations leave; the SSO has both
Sign-in is email → code at sso.pedshub.com, and new accounts come from an
invitation link minted there, so the app's own code emails and invite codes
recorded a path nobody can take. Gone: the login-code routes and their rate
limiters, the invite admin API and card, the invite field on the register
form, the "email me a code / use my password" choice on the sign-in screen
(an email now leads straight to the password), both utility modules, and
the invite-only setting. A migration drops login_codes and
registration_invites.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-13 06:11:00 +02:00

121 lines
5.5 KiB
JavaScript

// ============================================================
// E2E ACCOUNT SEED
// ============================================================
// Run inside the app container, which is where the database credentials live:
//
// docker exec pediatric-ai-scribe-e2e node e2e/seed.js
//
// Before this existed the e2e user was a registration someone did by hand once
// and the shared Postgres happened to keep. That was enough to log in and no
// more: there was no admin account, so nothing under /api/admin could be tested
// through a real request at all, and the Search Sources screen had to be
// checked by reading its markup.
//
// Reconciles rather than only creating. An account left over from an earlier
// run with the wrong role, an unverified address, a disabled flag or a
// different password is repaired in place, so the suite cannot fail for a
// reason that has nothing to do with the code under test.
//
// The domain guard is the important part. This script updates passwords and
// grants the admin role, so it refuses to touch any address outside
// @ped-ai.test — a mistyped environment variable can then do nothing worse
// than create another test account.
// ============================================================
// The entrypoint fetches secrets from OpenBao and exports them into the server
// process, and nowhere else — not into the image config, not into an env file.
// `docker exec` therefore starts with none of them and the database connection
// refuses on localhost. Borrowing PID 1's environment is what makes this
// runnable the documented way; without it the script only works on a stack
// whose credentials happen to be in plain compose environment.
require('fs').readFileSync('/proc/1/environ', 'utf8').split('\0').forEach(function (pair) {
var i = pair.indexOf('=');
if (i > 0 && !process.env[pair.slice(0, i)]) process.env[pair.slice(0, i)] = pair.slice(i + 1);
});
var db = require('../src/db/database');
// The app's own hasher, not bcrypt directly: production writes argon2id, and a
// seeded account hashed any other way exercises a path real users do not take.
var passwords = require('../src/utils/passwords');
var TEST_DOMAIN = '@ped-ai.test';
var PASSWORD = process.env.E2E_TEST_PASSWORD || 'E2E-testPassword123!';
var ACCOUNTS = [
{ email: process.env.E2E_TEST_EMAIL || 'e2e-user' + TEST_DOMAIN, name: 'E2E User', role: 'user' },
{ email: process.env.E2E_ADMIN_EMAIL || 'e2e-admin' + TEST_DOMAIN, name: 'E2E Admin', role: 'admin' }
];
// ── Configuration ─────────────────────────────────────────────────────
// Settings live in the database, so a throwaway database starts at defaults
// rather than at whatever production happens to be configured with. That is
// the point — a test should not pass because of a setting somebody changed on
// the live system last week — but it does mean anything the suite depends on
// has to be stated here.
//
// This is what made the model pickers empty when the e2e stack stopped sharing
// production's database: models.custom did not exist, so there was nothing to
// put in the <select>. The tests were right; the environment was incomplete.
//
// Fictional ids on purpose. Nothing here reaches a gateway — the specs mock
// the model calls — and a real model name would invite someone to believe a
// green run says something about that model.
var SETTINGS = {
'models.custom': JSON.stringify([
{ id: 'e2e-model-a', name: 'E2E Model A' },
{ id: 'e2e-model-b', name: 'E2E Model B' }
]),
'models.default': 'e2e-model-a',
'models.disabled': '[]',
'stt.model': 'e2e-stt',
'tts.model': 'e2e-tts',
'tts.voice': 'e2e-voice',
// Registration open, so the auth-screen spec can see the register link.
'registration_enabled': 'true'
};
async function seedSettings() {
var keys = Object.keys(SETTINGS);
for (var i = 0; i < keys.length; i++) {
await db.setSetting(keys[i], SETTINGS[keys[i]]);
}
console.log('settings seeded (' + keys.length + ' keys)');
}
async function seed(account) {
var email = String(account.email || '').toLowerCase().trim();
if (email.slice(-TEST_DOMAIN.length) !== TEST_DOMAIN) {
throw new Error('refusing to seed ' + email + ': only ' + TEST_DOMAIN + ' addresses may be seeded');
}
var hash = await passwords.hash(PASSWORD);
var existing = await db.get('SELECT id, role, email_verified, disabled FROM users WHERE email = ?', [email]);
if (!existing) {
await db.run(
'INSERT INTO users (email, password, name, role, email_verified, disabled) VALUES (?, ?, ?, ?, true, false)',
[email, hash, account.name, account.role]
);
console.log('created ' + email + ' (' + account.role + ')');
return;
}
await db.run(
'UPDATE users SET password = ?, name = ?, role = ?, email_verified = true, disabled = false WHERE id = ?',
[hash, account.name, account.role, existing.id]
);
var drift = [];
if (existing.role !== account.role) drift.push('role ' + existing.role + '→' + account.role);
if (!existing.email_verified) drift.push('verified');
if (existing.disabled) drift.push('re-enabled');
console.log('repaired ' + email + ' (' + (drift.length ? drift.join(', ') : 'password reset') + ')');
}
(async function () {
try {
for (var i = 0; i < ACCOUNTS.length; i++) await seed(ACCOUNTS[i]);
await seedSettings();
console.log('e2e accounts ready');
process.exit(0);
} catch (err) {
console.error('e2e seed failed: ' + err.message);
process.exit(1);
}
})();