Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 47s
Forgejo Docker Build / Root app tests (push) Successful in 49s
Forgejo Android APK / Build signed APK (push) Successful in 2m5s
Forgejo Docker Build / Build Docker image (push) Successful in 10s
Forgejo Docker Build / Deploy to the host (push) Failing after 2s
Two bugs, one cause each. It read the previous answer. setBusy(false) is what announces assistant-answer-done, and it ran before lastAnswer was assigned — so every listener was handed the answer before last. It now fires after the answer exists both in that variable and on the page. A test asserts the order, because the order is the whole bug. And it read the markdown. The better answer than unpicking the markup is not to have any: the rendered bubble is already the answer with its headings, emphasis and tables resolved, so voice mode reads that. It cannot drift from what the reader is looking at, and it needs no rules about what "##" sounds like. Read from a clone, with the parts that are not the answer removed — the action buttons, the sources list, the follow-up suggestions, code blocks and tables — so the page itself is untouched. A bubble still thinking is never read. speakableText() stays as the fallback for when the bubble cannot be found, since raw markdown read aloud is worse than silence. Separately: e2e/seed.js hashed with bcrypt directly, so seeded accounts did not exercise the argon2id path production writes. It uses the app's own hasher now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
84 lines
3.9 KiB
JavaScript
84 lines
3.9 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' }
|
|
];
|
|
|
|
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]);
|
|
console.log('e2e accounts ready');
|
|
process.exit(0);
|
|
} catch (err) {
|
|
console.error('e2e seed failed: ' + err.message);
|
|
process.exit(1);
|
|
}
|
|
})();
|