morphit/apps/ops-cli/scripts/offline-account-lookup-smoke.ts
Morphit Team 7c1c3bb332
Some checks failed
morphit-ci / TypeScript typecheck (sweep all workspaces) (push) Has been cancelled
morphit-ci / apps/web svelte-check (svelte-kit sync + svelte-aware tsc) (push) Has been cancelled
morphit-ci / Integration tests (real Postgres 16) (push) Has been cancelled
morphit-ci / ansible-lint (playbook quality gate) (push) Has been cancelled
morphit-ci / Smoke suite (run-smokes.sh, triple-pulse) (push) Has been cancelled
morphit-release / Build + publish release tarball (push) Has been cancelled
v1.11.1 — registration self-heal + data-fs disk health + offline-install RPC softening + node-health unification (cp707-cp710)
2026-08-11 17:38:41 -07:00

93 lines
3.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env tsx
/**
* offline-account-lookup-smoke (cp709).
*
* The wizard's relay-account step looks the account up on Blurt to
* catch typos. On a deliberately-offline / air-gapped install there
* is no internet — the lookup fails with a connectivity error, which
* is EXPECTED (the wizard needs no network; the node self-verifies the
* account the first time it comes online). Before cp709 the operator
* saw an alarming "⚠ Could not reach any Blurt RPC (fetch failed)"
* that read like a setup failure.
*
* describeAccountLookupFailure now returns a calm message for the
* no-connectivity case and keeps the specific ⚠ line for any other
* error. This smoke locks that:
* - the exact strings an offline install produces classify as
* offline (calm , no scary "⚠"), and
* - a genuine non-connectivity error keeps the ⚠ + the (sanitized)
* detail so a real misconfiguration stays visible.
*/
import { describeAccountLookupFailure } from '../src/init/steps.ts';
let passed = 0;
let failed = 0;
function ok(cond: boolean, label: string): void {
if (cond) {
passed++;
} else {
failed++;
console.error(`${label}`);
}
}
const NAME = 'my-morphit-relay';
// The messages a real offline install actually produces.
const OFFLINE_MESSAGES = [
'Could not reach any Blurt RPC endpoint. Last error: fetch failed',
'Could not reach any Blurt RPC endpoint. Last error: getaddrinfo ENOTFOUND api.blurt.blog',
'fetch failed',
'connect ECONNREFUSED 127.0.0.1:443',
'request to https://rpc.example timed out (ETIMEDOUT)',
'network error'
];
for (const msg of OFFLINE_MESSAGES) {
const d = describeAccountLookupFailure(NAME, msg);
ok(d.offline === true, `offline-classified: ${msg}`);
const joined = d.lines.join('\n');
ok(!joined.includes('\u26a0'), `no scary ⚠ for offline: ${msg}`);
ok(joined.includes('\u2139'), `calm present for offline: ${msg}`);
ok(joined.includes('first time it comes online'), `reassurance present: ${msg}`);
ok(joined.includes(`@${NAME}`), `names the account: ${msg}`);
}
// Genuine non-connectivity errors keep the specific ⚠ line.
const REAL_ERRORS = [
'HTTP 500 from https://rpc.example',
'RPC error: {"code":-32000,"message":"bad request"}'
];
for (const msg of REAL_ERRORS) {
const d = describeAccountLookupFailure(NAME, msg);
ok(d.offline === false, `non-offline-classified: ${msg}`);
const joined = d.lines.join('\n');
ok(joined.includes('\u26a0'), `keeps ⚠ for real error: ${msg}`);
ok(joined.includes('Could not check'), `keeps specific line: ${msg}`);
}
// Dangerous non-SGR control bytes are stripped (SGR colour codes are
// intentionally preserved by sanitizeForTerm; a BEL / OSC title-set is
// not). The raw message is attacker-influenceable RPC body text.
{
const d = describeAccountLookupFailure(NAME, 'HTTP 500 \u0007\u001b]0;pwned\u0007 body');
const joined = d.lines.join('\n');
ok(!joined.includes('\u0007'), 'strips BEL control byte');
ok(!joined.includes('\u001b]'), 'strips OSC escape introducer');
}
// The offline path must still let the operator proceed: it returns
// lines only (no throw), and the caller defaults "use anyway" to yes.
{
const d = describeAccountLookupFailure(NAME, 'fetch failed');
ok(d.lines.length >= 1, 'offline path yields printable lines');
}
console.log('');
console.log(` ${passed} passed, ${failed} failed`);
if (failed > 0) {
console.error('✗ offline-account-lookup smoke failed');
process.exit(1);
}
console.log(`✓ all ${passed} offline-account-lookup scenarios pass`);