source: Klonkt/test/timestamp-ordering.test.js@ d8c5042

main
Last change on this file since d8c5042 was 8151340, checked in by Robin <roboburr@…>, 5 days ago

Tijdstempels, leeskant: sorteren op tijd en niet op tekst (shaer-a937)

Deze database draagt twee spellingen door elkaar: '2026-08-06 09:02:01'
van CURRENT_TIMESTAMP en '2026-08-06T02:08:01.000Z' van toISOString().
SQLite vergelijkt tekst, en op positie 10 staat een T tegenover een
spatie -- dus binnen dezelfde dag wint de ISO-vorm altijd, hoe laat hij
ook is. Gemeten op dev: ap_timeline 80 ISO tegen 80 SQL, ap_mentions 26
tegen 38, ap_interactions 2 tegen 6. De menging is er dus echt.

De helper bestond al (STEMPEL in ap-timeline, gebouwd voor de
gesprekslezing) maar stond in een dienst, terwijl elke plek die sorteert
hem nodig heeft. Hij staat nu als isoSql in config/database.js -- daar
hoort hij, want het gaat over hoe deze OPSLAG met tijd omgaat, en elke
aanroeper importeert db toch al. STEMPEL is er nu een verwijzing naar,
zodat er niet twee kopieen van dezelfde regel bestaan.

Genormaliseerd waar de menging LEEFT: de tijdlijn zelf, de reacties
onder een post (de fout die de bead mat), de berichten en de Cirkel.
Plus de archief-export, die op dezelfde kolommen sorteert.

De terugval blijft de RAUWE waarde en niet leeg: isoSql levert ook de
cursor in de gesprekslezing, en een lege stempel zou een client zijn
plek kosten. Waar een onleesbare waarde dan landt is onbepaald, en dat
staat nu ook zo in het commentaar -- eerst schreef ik daar dat zoiets
onderaan hoort te eindigen, en dat deed de code niet.

Vier toetsen met gemengde data, ISO bewust VROEGER op de dag: precies de
stand waarin een ongenormaliseerde sortering omvalt. Tegenbewijs: maak
isoSql een doorgeefluik en alle vier vallen.

Volle suite 1248 groen.

  • Property mode set to 100644
File size: 4.6 KB
RevLine 
[8151340]1// Gemengde tijdstempels sorteren chronologisch (shaer-a937).
2//
3// Deze database draagt twee spellingen: '2026-08-06 09:02:01' van
4// CURRENT_TIMESTAMP en '2026-08-06T02:08:01.000Z' van toISOString(). SQLite
5// vergelijkt TEKST, en op positie 10 staat een 'T' (0x54) tegenover een spatie
6// (0x20) -- dus binnen dezelfde dag wint de ISO-vorm altijd, hoe laat hij ook
7// is. Gemeten op de live database van sound-fabrics: een antwoord van 02:08
8// stond boven likes van 09:02 diezelfde dag, zichtbaar onder elke post met
9// reacties EN likes.
10//
11// De toets zet de twee vormen bewust door elkaar met de ISO-rij VROEGER op de
12// dag: precies de stand waarin een ongenormaliseerde sortering omvalt.
13import { test } from 'node:test';
14import assert from 'node:assert/strict';
15
16process.env.DATABASE_PATH = ':memory:';
17process.env.PUBLIC_BASE_URL = 'https://test.example';
18
19const dbMod = await import('../src/config/database.js');
20const db = dbMod.default;
21{ const stil = console.log; console.log = () => {}; try { dbMod.initializeDatabase(); } finally { console.log = stil; } }
22const AP = (await import('../src/services/ActivityPubService.js')).default;
23
24db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
25 .run('u1', 'robin', 'u1@t', 'x', 'god');
26db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_primary) VALUES (?,?,?,?,1)').run('s1', 'kid', 'kid', 'u1');
27db.prepare(`INSERT INTO posts (id, site_id, author_id, slug, title, content, status, published_at)
28 VALUES ('p1','s1','u1','post','Post','<p>x</p>','published','2026-08-01T00:00:00Z')`).run();
29
30// SQL-notatie om 09:02, ISO om 02:08 -- dezelfde dag. Chronologisch hoort de
31// SQL-rij bovenaan; op tekst gesorteerd wint de ISO-rij.
32const LAAT_SQL = '2026-08-06 09:02:01';
33const VROEG_ISO = '2026-08-06T02:08:01.000Z';
34
35const insI = db.prepare(`INSERT INTO ap_interactions (kind, post_id, object_uri, actor_uri, actor_name, content, published, created_at)
36 VALUES (?,?,?,?,?,?,?,?)`);
37insI.run('reply', 'p1', 'https://elders.example/n/vroeg', 'https://elders.example/u/a', 'A', '<p>vroeg</p>', VROEG_ISO, VROEG_ISO);
38insI.run('reply', 'p1', 'https://elders.example/n/laat', 'https://elders.example/u/b', 'B', '<p>laat</p>', null, LAAT_SQL);
39
40const insT = db.prepare(`INSERT INTO ap_timeline (id, slug, author_uri, author_name, content, published, created_at)
41 VALUES (?,?,?,?,?,?,?)`);
42insT.run('https://elders.example/t/vroeg', 'kid', 'https://elders.example/u/a', 'A', '<p>vroeg</p>', VROEG_ISO, VROEG_ISO);
43insT.run('https://elders.example/t/laat', 'kid', 'https://elders.example/u/b', 'B', '<p>laat</p>', null, LAAT_SQL);
44
45const insM = db.prepare(`INSERT INTO ap_mentions (slug, object_uri, actor_uri, actor_name, content, published, created_at)
46 VALUES (?,?,?,?,?,?,?)`);
47insM.run('kid', 'https://elders.example/m/vroeg', 'https://elders.example/u/a', 'A', '<p>vroeg</p>', VROEG_ISO, VROEG_ISO);
48insM.run('kid', 'https://elders.example/m/laat', 'https://elders.example/u/b', 'B', '<p>laat</p>', null, LAAT_SQL);
49
50/** Het nieuwste eerst: hoort de LAAT-rij (09:02) te zijn, niet de ISO van 02:08. */
51const nieuwsteIs = (rijen, veld, verwacht) => {
52 assert.ok(rijen.length >= 2, 'beide rijen horen terug te komen');
53 assert.match(String(rijen[0][veld]), verwacht,
54 `de rij van 09:02 hoort bovenaan; kreeg ${rijen[0][veld]} -- dan sorteert de tekst en niet de tijd`);
55};
56
57test('de tijdlijn zet 09:02 boven 02:08, ook in twee spellingen', () => {
58 nieuwsteIs(AP.getTimeline('kid', 10, 0), 'id', /laat/);
59});
60
61test('de reacties onder een post ook -- de fout die gemeten werd', () => {
62 nieuwsteIs(AP.getReplyMessages('kid', 10), 'object_uri', /laat/);
63});
64
65test('en de berichten', () => {
66 nieuwsteIs(AP.getDirectMessages('kid', 10), 'object_uri', /laat/);
67});
68
69test('een onleesbare stempel valt niet weg, en bederft de rest niet', () => {
70 // strftime geeft NULL op iets dat het niet als tijd herkent; de COALESCE valt
71 // dan terug op de rauwe waarde. WAAR die rij landt is onbepaald -- als tekst
72 // staat 'geen datum' nu eenmaal boven '2026-...' -- en dat is de goede ruil:
73 // data die je niet begrijpt bewaar je. Wat hier telt is dat de rij blijft
74 // bestaan en dat de twee ECHTE spellingen onderling nog kloppen.
75 insT.run('https://elders.example/t/kapot', 'kid', 'https://elders.example/u/c', 'C', '<p>kapot</p>', 'geen datum', 'geen datum');
76 const rijen = AP.getTimeline('kid', 10, 0);
77 assert.equal(rijen.length, 3, 'de rij met de onleesbare stempel valt niet weg');
78 const ids = rijen.map((r) => r.id).filter((id) => !/kapot/.test(id));
79 assert.match(ids[0], /laat/, 'onder de leesbare rijen staat 09:02 nog steeds boven 02:08');
80});
Note: See TracBrowser for help on using the repository browser.