source: Klonkt/test/following-csv.test.js@ 822d16d

main
Last change on this file since 822d16d was e9ec5e4, checked in by Robin <roboburr@…>, 4 weeks ago

Volglijst: Featured is auto_boost, plus de knoppen om hem mee te nemen

Twee dingen: een correctie op de vorige commit, en de ingang die er nog niet was.

DE CORRECTIE. Ik had auto_boost in de kolom "Show boosts" gezet omdat in beide het
woord boost zit. Dat is fout. Bij Mastodon betekent die kolom "toon de reblogs van
deze persoon in mijn tijdlijn", en dat kent Klonkt niet; onze vlag gaat over hun
EIGEN posts in JOUW Cirkel. Dat is de vlag die in de UI al "Uitgelicht" heet
(tl.autoboost, in het Engels letterlijk "Featured"), dus hij hoort in de kolom
Featured en "Show boosts" blijft leeg.

Daarmee vervalt ook de kolom highlighted die ik in de vorige commit toevoegde:
die was een nieuw begrip naast een bestaand begrip met dezelfde naam. Eén ding, één
naam. Een bestand uit Mastodon levert nu featured: false, en dat is juist: hun
kolom 2 mag hier niet als uitgelicht binnenkomen.

DE INGANG. De vorige commit had de functies maar geen knop, en volgens de eigen
regel is een feature pas af als een eindgebruiker hem kan gebruiken. Op /following
staat nu een uitklapblok met een downloadknop en een plakveld.

De importroute geeft followActor mee als followFn, zodat er één pad naar een
volgrelatie blijft. Daar zat een val in: followActor meldt een fout als VELD en
niet als exception, dus zonder vertaling telde een onvindbaar account als geslaagd
mee. Die vertaling zit er nu in.

Changed files:
src/config/database.js

  • kolom highlighted teruggedraaid; commentaar bij auto_boost zegt nu dat het "Uitgelicht"/"Featured" heet

src/services/ArchiveExportService.js

  • auto_boost naar kolom Featured, Show boosts/Notify/Languages blijven leeg
  • parseFollowingCsv leest alleen kolom 5, en levert featured in plaats van twee velden

src/services/ArchiveImportService.js

  • de uitgelicht-stand gaat mee IN de Follow (derde argument), geen tweede UPDATE erna

src/routes/posts.js

  • GET /news/following.csv, met private/no-store want dit is jouw volglijst
  • POST /news/following/import, urlencoded, roept importFollowing aan met followActor

src/services/i18n.js

  • vijf teksten in nl, en en de

src/views/pages/following.ejs

  • het uitklapblok met de downloadknop en het plakveld

test/following-csv.test.js

  • bijgewerkt naar de juiste kolomindeling; de Mastodon-test controleert nu expliciet dat hun "Show boosts: true" NIET als uitgelicht landt

remarks: 921 groen in UTC en Europe/Amsterdam. Server gestart en de drie routes
nagelopen: alle drie bestaan en sturen naar de login, geen serverfouten. De template
apart gerenderd in nl/en/de, geen onvertaalde sleutels. De pagina zelf achter de
login niet visueel gezien; daar is een account voor nodig. Onderweg per ongeluk
.claude/launch.json overschreven (die zette PUBLIC_BASE_URL) en weer hersteld.

-robo
Co-Authored-By: Claude Opus 4.8 <noreply@…>

  • Property mode set to 100644
File size: 6.3 KB
Line 
1// Wie je volgt gaat mee bij een verhuizing.
2//
3// Dit ontbrak: de exporter dekte posts, tracks en antwoorden, maar niet je
4// relaties. De Move vertelt je VOLGERS waar je heen ging; niets vertelde JOU wie
5// jij volgde. Die lijst stond alleen in de database die je achterlaat.
6//
7// Kolomvorm van Mastodon, met een vijfde kolom van ons erachter, zodat de lijst
8// beide kanten op werkt. Een uitwisselformaat dat alleen met zichzelf praat is
9// geen uitwisselformaat.
10import { test, beforeEach } from 'node:test';
11import assert from 'node:assert/strict';
12
13process.env.DATABASE_PATH = ':memory:';
14
15const dbMod = await import('../src/config/database.js');
16const db = dbMod.default;
17{
18 const stil = console.log;
19 console.log = () => {};
20 try { dbMod.initializeDatabase(); } finally { console.log = stil; }
21}
22
23const { followingCsv, parseFollowingCsv } = await import('../src/services/ArchiveExportService.js');
24const { importFollowing } = await import('../src/services/ArchiveImportService.js');
25
26const volg = db.prepare(`INSERT INTO ap_following (slug, actor_uri, handle, status, auto_boost)
27 VALUES (?,?,?,?,?)`);
28
29beforeEach(() => {
30 db.prepare('DELETE FROM ap_following').run();
31});
32
33test('de CSV heeft de Mastodon-koppen, met Featured erachter', () => {
34 volg.run('me', 'https://a.example/ap/users/jason', '@jason@a.example', 'accepted', 1);
35 const csv = followingCsv('me');
36 const [kop, eerste] = csv.trim().split('\n');
37 assert.equal(kop, 'Account address,Show boosts,Notify on new posts,Languages,Featured');
38 // Zonder de leidende @, want zo schrijft Mastodon het adres.
39 assert.equal(eerste, 'jason@a.example,,,,true');
40});
41
42test('een openstaand verzoek gaat NIET mee', () => {
43 volg.run('me', 'https://a.example/ap/users/wacht', '@wacht@a.example', 'pending', 0);
44 volg.run('me', 'https://a.example/ap/users/ja', '@ja@a.example', 'accepted', 0);
45 const regels = followingCsv('me').trim().split('\n').slice(1);
46 assert.equal(regels.length, 1, 'een verzoek dat iemand bewust liet liggen mag niet opnieuw verstuurd worden');
47 assert.match(regels[0], /^ja@a\.example/);
48});
49
50test('zonder handle valt hij terug op de actor-URI', () => {
51 volg.run('me', 'https://a.example/ap/users/naamloos', null, 'accepted', 0);
52 assert.match(followingCsv('me'), /https:\/\/a\.example\/ap\/users\/naamloos/);
53});
54
55test('een lege lijst levert geen bestand op', () => {
56 assert.equal(followingCsv('me'), null);
57});
58
59test('een export leest zichzelf terug', () => {
60 volg.run('me', 'https://a.example/ap/users/a', '@a@a.example', 'accepted', 1);
61 volg.run('me', 'https://b.example/ap/users/b', '@b@b.example', 'accepted', 0);
62 const terug = parseFollowingCsv(followingCsv('me'));
63 assert.deepEqual(terug, [
64 { address: 'a@a.example', featured: true },
65 { address: 'b@b.example', featured: false },
66 ]);
67});
68
69test('een bestand uit Mastodon werkt, ook zonder onze vijfde kolom', () => {
70 const uit = parseFollowingCsv(
71 'Account address,Show boosts,Notify on new posts,Languages\n'
72 + 'iemand@mastodon.social,true,false,\n',
73 );
74 // Hun "Show boosts" staat op true, en dat mag hier NIET als uitgelicht landen.
75 assert.deepEqual(uit, [{ address: 'iemand@mastodon.social', featured: false }]);
76});
77
78test('een kale lijst adressen werkt ook, zonder kopregel', () => {
79 const uit = parseFollowingCsv('@een@a.example\ntwee@b.example\n');
80 assert.deepEqual(uit.map((r) => r.address), ['een@a.example', 'twee@b.example']);
81});
82
83test('een geciteerd veld met een komma erin blijft heel', () => {
84 const uit = parseFollowingCsv('Account address,Show boosts\n"raar,naam@a.example",true\n');
85 assert.equal(uit[0].address, 'raar,naam@a.example');
86});
87
88test('importFollowing volgt elke regel, met de boost-stand mee', async () => {
89 const gezien = [];
90 const r = await importFollowing({ slug: 'me' },
91 'Account address,Show boosts,Notify on new posts,Languages,Featured\n'
92 + 'a@a.example,,,,true\n'
93 + 'b@b.example,,,,false\n',
94 { followFn: async (site, adres, boost) => { gezien.push([adres, boost]); return true; } });
95
96 assert.deepEqual(gezien, [['a@a.example', true], ['b@b.example', false]],
97 'de uitgelicht-stand moet als derde argument mee de Follow in');
98 assert.equal(r.gevolgd, 2);
99 assert.equal(r.mislukt.length, 0);
100});
101
102test('jezelf volgen wordt overgeslagen', async () => {
103 const r = await importFollowing({ slug: 'me' },
104 'Account address\nme@eigen.example\nander@a.example\n',
105 { followFn: async () => true });
106 assert.equal(r.overgeslagen, 1);
107 assert.equal(r.gevolgd, 1);
108});
109
110test('een mislukte follow stopt de rest niet, en wordt gemeld', async () => {
111 const r = await importFollowing({ slug: 'me' },
112 'Account address\neen@a.example\nkapot@b.example\ndrie@c.example\n',
113 { followFn: async (s, adres) => { if (adres.startsWith('kapot')) throw new Error('onbereikbaar'); return true; } });
114
115 assert.equal(r.gevolgd, 2, 'de derde moet nog geprobeerd zijn na de tweede');
116 assert.deepEqual(r.mislukt, [{ adres: 'kapot@b.example', reden: 'onbereikbaar' }]);
117});
118
119test('zonder followFn gebeurt er niets, en dat zegt hij ook', async () => {
120 const r = await importFollowing({ slug: 'me' }, 'Account address\na@a.example\n', {});
121 assert.equal(r.error, 'no_follow_fn');
122 assert.equal(r.gevolgd, 0);
123});
124
125// De koppeling zelf. De losse functies hierboven kunnen prima werken terwijl het
126// archief de CSV nooit ziet, en dat is precies het soort stil gat waar een
127// verhuizing op strandt: alles groen, en je volglijst blijft achter.
128test('buildArchive stopt following.csv er echt in, met hash in het manifest', async () => {
129 db.prepare('INSERT OR IGNORE INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
130 .run('u1', 'u1', 'u1@test', 'x', 'god');
131 db.prepare('INSERT OR IGNORE INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)')
132 .run('s1', 'me', 'Mijn site', 'u1');
133 volg.run('me', 'https://a.example/ap/users/jason', '@jason@a.example', 'accepted', 1);
134
135 const { buildArchive } = await import('../src/services/ArchiveExportService.js');
136 const r = buildArchive('me', { origin: 'https://oud.example' });
137
138 assert.ok(r.files.has('following.csv'), 'het archief moet de volglijst dragen');
139 assert.equal(r.counts.following, 1);
140 assert.ok(r.manifest.files['following.csv'], 'en hij hoort in het manifest, anders telt hij niet mee bij de controle');
141 assert.match(String(r.files.get('following.csv')), /jason@a\.example,,,,true/);
142});
Note: See TracBrowser for help on using the repository browser.