first commit

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
michilis
2026-08-20 22:41:25 +02:00
co-authored by Cursor
commit aa1771ea20
136 changed files with 27069 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
/**
* Acceptance criterion #1, as a runnable check.
*
* pnpm --filter ./api test:offline
*
* A mint whose /v1/info is unreachable must still serve its full detail payload from
* cached metadata. This is the single most important fix over the old site, so it gets
* a test that actually kills a mint rather than a unit test of a helper.
*
* Runs against a throwaway copy of the seeded database, so the real one is untouched.
*/
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
const source = process.env['DB_PATH'] ?? path.resolve(import.meta.dirname, '..', 'data', 'cashumints.db');
if (!fs.existsSync(source)) {
console.error(`No seeded database at ${source}. Run "pnpm seed" first.`);
process.exit(1);
}
// Point the whole process at a copy before anything opens the real database.
const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'cashumints-offline-'));
const copy = path.join(scratch, 'test.db');
fs.copyFileSync(source, copy);
process.env['DB_PATH'] = copy;
const { getDb, closeDb } = await import('./db.ts');
const { probeMint } = await import('./probe.ts');
const { getMintDetail } = await import('./queries.ts');
const { mintByUrl } = await import('./mints.ts');
const db = getDb();
// Pick a mint that is online and has real cached metadata to lose.
const victim = db
.prepare(
`SELECT * FROM mints
WHERE status = 'online' AND info_json IS NOT NULL AND name IS NOT NULL
ORDER BY LENGTH(info_json) DESC LIMIT 1`,
)
.get() as { url: string; host: string; name: string } | undefined;
assert.ok(victim, 'seeded database has no online mint with cached metadata');
console.log(`victim: ${victim.name} (${victim.host})`);
const before = getMintDetail(victim.host);
assert.ok(before, 'detail must exist before the mint dies');
assert.equal(before.status, 'online');
assert.ok(before.info, 'must have cached /v1/info before');
assert.ok(before.nuts.length > 0, 'must have parsed NUTs before');
// Rug it: same row, dead URL. This is the "point a mint row at a dead URL" test.
const DEAD = 'https://this-mint-is-gone.invalid';
db.prepare('UPDATE mints SET url = ? WHERE host = ?').run(DEAD, victim.host);
db.prepare('UPDATE reviews SET mint_url = ? WHERE mint_url = ?').run(DEAD, victim.url);
// Three failures is the offline threshold.
for (let i = 0; i < 3; i++) {
const row = mintByUrl(DEAD);
assert.ok(row, 'row must survive the URL change');
const result = await probeMint(row);
assert.equal(result.ok, false, 'a dead URL must not probe ok');
}
const after = getMintDetail(victim.host);
assert.ok(after, 'the mint page must still resolve when the mint is dead');
assert.equal(after.status, 'offline', 'three failures means offline');
// The whole point: none of the cached metadata was touched by the failed probes.
assert.equal(after.name, before.name, 'name survives');
assert.equal(after.description, before.description, 'description survives');
assert.equal(after.version, before.version, 'version survives');
assert.equal(after.pubkey, before.pubkey, 'pubkey survives');
assert.deepEqual(after.nuts, before.nuts, 'supported NUTs survive');
assert.deepEqual(after.info, before.info, 'full cached /v1/info survives');
assert.equal(after.icon, before.icon, 'cached icon survives');
// Reviews must still be readable, so the mint stays reviewable.
assert.equal(after.review_count, before.review_count, 'reviews survive');
assert.deepEqual(after.rating_distribution, before.rating_distribution, 'distribution survives');
// The page needs a date for the "Offline since {date}" banner.
assert.ok(after.last_online !== null, 'last_online must be set so the banner has a date');
assert.ok(after.last_online <= Math.floor(Date.now() / 1000));
// And it must sink below every online mint without disappearing.
const { listMints } = await import('./queries.ts');
const list = listMints();
const index = list.findIndex((m) => m.host === victim.host);
assert.ok(index >= 0, 'an offline mint must stay listed so people can find and review it');
const lastOnline = list.map((m) => m.status).lastIndexOf('online');
assert.ok(index > lastOnline, 'offline mints rank below every online mint');
closeDb();
fs.rmSync(scratch, { recursive: true, force: true });
console.log(
`ok, offline mint still serves ${after.nuts.length} NUTs, ${after.review_count} reviews, ` +
`and full cached metadata (offline since ${new Date(after.last_online * 1000).toISOString()})`,
);