| 1 | // De BRON schrijft nog maar een spelling, en wat er al stond is omgezet
|
|---|
| 2 | // (shaer-a937, de duurzame helft).
|
|---|
| 3 | //
|
|---|
| 4 | // De leeskant wikkelt inmiddels alles in isoSql, dus een gemengde kolom sorteert
|
|---|
| 5 | // toch goed. Dat is het vangnet, niet de oplossing: zolang er twee vormen
|
|---|
| 6 | // binnenkomen blijft elke NIEUWE query die iemand schrijft een kans om het
|
|---|
| 7 | // opnieuw fout te doen. Deze toets bewaakt dat er niets nieuws bijkomt.
|
|---|
| 8 | import { test } from 'node:test';
|
|---|
| 9 | import assert from 'node:assert/strict';
|
|---|
| 10 |
|
|---|
| 11 | process.env.DATABASE_PATH = ':memory:';
|
|---|
| 12 | process.env.PUBLIC_BASE_URL = 'https://test.example';
|
|---|
| 13 |
|
|---|
| 14 | const dbMod = await import('../src/config/database.js');
|
|---|
| 15 | const db = dbMod.default;
|
|---|
| 16 | { const stil = console.log; console.log = () => {}; try { dbMod.initializeDatabase(); } finally { console.log = stil; } }
|
|---|
| 17 |
|
|---|
| 18 | const ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
|
|---|
| 19 |
|
|---|
| 20 | test('een rij die de APP wegschrijft draagt ISO', async () => {
|
|---|
| 21 | // Door de echte schrijfweg (tlStmts), niet met een eigen INSERT: een toets
|
|---|
| 22 | // die zijn eigen ISO invoegt bewijst alleen dat de kolom tekst opslaat, en
|
|---|
| 23 | // kan per definitie niet falen op wat hier veranderd is.
|
|---|
| 24 | const AP = (await import('../src/services/ActivityPubService.js')).default;
|
|---|
| 25 | AP.upsertBoostedNote('kid', {
|
|---|
| 26 | object_uri: 'https://a.example/n/vers', actor_uri: 'https://a.example/u',
|
|---|
| 27 | actor_name: 'A', content: '<p>vers</p>', media: '[]',
|
|---|
| 28 | });
|
|---|
| 29 | const r = db.prepare("SELECT created_at FROM ap_timeline WHERE id = 'https://a.example/n/vers'").get();
|
|---|
| 30 | assert.ok(r, 'de rij hoort er te zijn');
|
|---|
| 31 | assert.match(r.created_at, ISO, 'een verse rij hoort ISO te dragen, niet de SQL-notatie');
|
|---|
| 32 | });
|
|---|
| 33 |
|
|---|
| 34 | test('geen enkele schrijfweg naar die tabellen schrijft nog CURRENT_TIMESTAMP', async () => {
|
|---|
| 35 | // Op de BRON getoetst en niet op gedrag: de fout is dat iemand later een
|
|---|
| 36 | // INSERT toevoegt met de oude vorm, en dat merk je pas als de volgorde in de
|
|---|
| 37 | // app scheef staat. De tabellen hieronder zijn degene waar de menging in zat.
|
|---|
| 38 | const fs = await import('node:fs');
|
|---|
| 39 | const path = await import('node:path');
|
|---|
| 40 | const { fileURLToPath } = await import('node:url');
|
|---|
| 41 | const wortel = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'src');
|
|---|
| 42 |
|
|---|
| 43 | const bestanden = [];
|
|---|
| 44 | (function loop(map) {
|
|---|
| 45 | for (const naam of fs.readdirSync(map, { withFileTypes: true })) {
|
|---|
| 46 | const p = path.join(map, naam.name);
|
|---|
| 47 | if (naam.isDirectory()) loop(p);
|
|---|
| 48 | else if (naam.name.endsWith('.js')) bestanden.push(p);
|
|---|
| 49 | }
|
|---|
| 50 | })(wortel);
|
|---|
| 51 |
|
|---|
| 52 | const TABELLEN = /INSERT(?:\s+OR\s+\w+)?\s+INTO\s+(ap_timeline|ap_mentions|ap_interactions|ap_outbox)\b/i;
|
|---|
| 53 | const fout = [];
|
|---|
| 54 | for (const p of bestanden) {
|
|---|
| 55 | if (p.endsWith(path.join('config', 'database.js'))) continue; // daar staat het schema zelf
|
|---|
| 56 | const bron = fs.readFileSync(p, 'utf8');
|
|---|
| 57 | // Per statement kijken: een bestand mag elders best CURRENT_TIMESTAMP
|
|---|
| 58 | // gebruiken voor een tabel die hier niet over gaat.
|
|---|
| 59 | for (const stuk of bron.split(/db\.prepare\(/)) {
|
|---|
| 60 | if (TABELLEN.test(stuk) && /CURRENT_TIMESTAMP/.test(stuk.split(')')[0] + stuk.slice(0, 600))) {
|
|---|
| 61 | fout.push(path.relative(wortel, p));
|
|---|
| 62 | }
|
|---|
| 63 | }
|
|---|
| 64 | }
|
|---|
| 65 | assert.deepEqual([...new Set(fout)], [],
|
|---|
| 66 | 'schrijf hier strftime(\'%Y-%m-%dT%H:%M:%SZ\',\'now\'), anders komt de menging terug');
|
|---|
| 67 | });
|
|---|
| 68 |
|
|---|
| 69 | test('de migratie zet oude SQL-rijen om en laat de rest met rust', () => {
|
|---|
| 70 | db.prepare(`INSERT INTO ap_interactions (kind, post_id, object_uri, actor_uri, content, created_at)
|
|---|
| 71 | VALUES ('reply','p1','https://a.example/n/oud','https://a.example/u','<p>oud</p>','2026-08-06 09:02:01')`).run();
|
|---|
| 72 | db.prepare(`INSERT INTO ap_interactions (kind, post_id, object_uri, actor_uri, content, created_at)
|
|---|
| 73 | VALUES ('reply','p1','https://a.example/n/iso','https://a.example/u','<p>iso</p>','2026-08-06T02:08:01.000Z')`).run();
|
|---|
| 74 | db.prepare(`INSERT INTO ap_interactions (kind, post_id, object_uri, actor_uri, content, created_at)
|
|---|
| 75 | VALUES ('reply','p1','https://a.example/n/raar','https://a.example/u','<p>raar</p>','geen datum')`).run();
|
|---|
| 76 |
|
|---|
| 77 | { const stil = console.log; console.log = () => {}; try { dbMod.initializeDatabase(); } finally { console.log = stil; } }
|
|---|
| 78 |
|
|---|
| 79 | const lees = (uri) => db.prepare('SELECT created_at FROM ap_interactions WHERE object_uri = ?').get(uri).created_at;
|
|---|
| 80 | assert.equal(lees('https://a.example/n/oud'), '2026-08-06T09:02:01Z', 'de SQL-vorm is omgezet');
|
|---|
| 81 | assert.equal(lees('https://a.example/n/iso'), '2026-08-06T02:08:01.000Z', 'een ISO-stempel blijft ongemoeid');
|
|---|
| 82 | assert.equal(lees('https://a.example/n/raar'), 'geen datum', 'een onleesbare stempel wordt niet weggegooid');
|
|---|
| 83 |
|
|---|
| 84 | // Idempotent: nog een keer draaien verandert niets meer.
|
|---|
| 85 | { const stil = console.log; console.log = () => {}; try { dbMod.initializeDatabase(); } finally { console.log = stil; } }
|
|---|
| 86 | assert.equal(lees('https://a.example/n/oud'), '2026-08-06T09:02:01Z', 'tweede keer draaien laat het staan');
|
|---|
| 87 | });
|
|---|