source: Klonkt/test/timestamp-source.test.js@ 9946a68

main
Last change on this file since 9946a68 was 9946a68, checked in by Robin <roboburr@…>, 4 days ago

Tijdstempels, bronkant: een spelling bij het schrijven, plus migratie (shaer-a937)

De leeskant van 8151340 is het vangnet; dit haalt de oorzaak weg. Zolang
er twee vormen binnenkomen blijft elke nieuwe query een kans om het
opnieuw fout te doen.

ZES schrijfwegen, niet vier: naast ap_timeline, ap_interactions,
ap_outbox en ap_mentions bleken ook guardianship/delivery (directe notes)
en de archief-import in dezelfde kolommen te schrijven. Alle zes leveren
nu ISO via NU_ISO uit config/database.js, waar isoSql ook staat -- wat je
schrijft en waarmee je vergelijkt horen bij elkaar en dus op een plek.

MIGRATIE voor wat er al stond, in het bestaande idempotente patroon in
initializeDatabase: alleen rijen in de CURRENT_TIMESTAMP-vorm, alleen als
strftime ze begrijpt. Een ISO-stempel blijft ongemoeid en een onleesbare
waarde wordt niet weggegooid -- die bewaar je, ook al weet je er niets
mee.

VIER VERGELIJKINGEN die het omgekeerde deden: een kolom RAUW tegen
datetime('now') leggen. Dat is dezelfde fout gespiegeld, en hij zou juist
door deze release gaan bijten. Het gaat om de wachtwoord-reset (de bead
noemt shaer-1evq), het opruimen van geziene notes en twee queries in de
sessieopslag -- de bead schreef al dat daar de opruiming en de telling
ernaast zaten. Alle vier nu datetime(kolom) tegen datetime('now'),
hetzelfde patroon dat Scheduler al gebruikt.

BEWUST NIET: de 49 DEFAULT CURRENT_TIMESTAMP in het schema. Die vragen om
een tabel-herbouw per stuk, en ze vuren alleen als een INSERT de kolom
overslaat -- wat bij de tabellen die het betreft niet gebeurt, want daar
staat created_at expliciet in het statement.

Drie toetsen erbij, elk met eigen tegenbewijs. Een ervan moest overnieuw:
hij schreef zijn eigen ISO en kon dus niet falen op wat ik veranderde;
nu gaat hij door upsertBoostedNote, de echte schrijfweg. Onderweg zette
ik SQL met enkele quotes in een JS-string met enkele quotes -- vandaar
NU_ISO als constante in template literals, en node --check op elk
aangeraakt bestand.

Volle suite 1255 groen.

  • Property mode set to 100644
File size: 4.8 KB
Line 
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.
8import { test } from 'node:test';
9import assert from 'node:assert/strict';
10
11process.env.DATABASE_PATH = ':memory:';
12process.env.PUBLIC_BASE_URL = 'https://test.example';
13
14const dbMod = await import('../src/config/database.js');
15const db = dbMod.default;
16{ const stil = console.log; console.log = () => {}; try { dbMod.initializeDatabase(); } finally { console.log = stil; } }
17
18const ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
19
20test('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
34test('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
69test('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});
Note: See TracBrowser for help on using the repository browser.