| 1 | // Audio overleeft een verhuizing. Formaat v2.
|
|---|
| 2 | //
|
|---|
| 3 | // Dit bestand bestaat door wat er op soundfabrics.nl gebeurde: 36 berichten
|
|---|
| 4 | // kwamen goed over, 14 van de 140 nummers stonden er, en 13 daarvan speelden
|
|---|
| 5 | // niet af. Drie oorzaken, alle drie hier vastgelegd:
|
|---|
| 6 | //
|
|---|
| 7 | // 1. Gehoste audio leeft BUITEN MEDIA_ROOT (eigen gated route). Het archief
|
|---|
| 8 | // droeg alleen bestanden onder media/, dus de exporter rekende er een
|
|---|
| 9 | // /media/../audio/x.mp3 van en de importer weigerde dat pad. Terecht: het
|
|---|
| 10 | // wijst buiten de mediamap. Gevolg was wel dat audio nooit kon aankomen.
|
|---|
| 11 | // 2. De importer maakte een audio_tracks-rij aan ZONDER te kijken of het
|
|---|
| 12 | // bestand er was. Een nummer dat in de lijst staat en 404't bij play is
|
|---|
| 13 | // erger dan een nummer dat ontbreekt: het ziet eruit alsof het gelukt is,
|
|---|
| 14 | // dus je gooit je oude instantie weg.
|
|---|
| 15 | // 3. Alleen audio die met [[track:]] in een bericht stond ging mee. Losse
|
|---|
| 16 | // nummers en playlists bleven achter (126 respectievelijk 11 stuks).
|
|---|
| 17 | //
|
|---|
| 18 | // Een verhuizing die je bibliotheek achterlaat is geen verhuizing.
|
|---|
| 19 |
|
|---|
| 20 | import { test } from 'node:test';
|
|---|
| 21 | import assert from 'node:assert/strict';
|
|---|
| 22 | import fs from 'fs';
|
|---|
| 23 | import os from 'os';
|
|---|
| 24 | import path from 'path';
|
|---|
| 25 |
|
|---|
| 26 | const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'klonkt-audio-'));
|
|---|
| 27 | const MEDIA = path.join(TMP, 'media');
|
|---|
| 28 | const AUDIO = path.join(TMP, 'audio');
|
|---|
| 29 | fs.mkdirSync(MEDIA, { recursive: true });
|
|---|
| 30 | fs.mkdirSync(AUDIO, { recursive: true });
|
|---|
| 31 |
|
|---|
| 32 | process.env.DATABASE_PATH = ':memory:';
|
|---|
| 33 | process.env.PUBLIC_BASE_URL = 'https://oud.test';
|
|---|
| 34 | process.env.MEDIA_PATH = MEDIA;
|
|---|
| 35 | process.env.AUDIO_PATH = AUDIO; // MOET voor de import van paths.js staan
|
|---|
| 36 |
|
|---|
| 37 | const dbMod = await import('../src/config/database.js');
|
|---|
| 38 | const db = dbMod.default;
|
|---|
| 39 | { const stil = console.log; console.log = () => {}; try { dbMod.initializeDatabase(); } finally { console.log = stil; } }
|
|---|
| 40 | const AX = await import('../src/services/ArchiveExportService.js');
|
|---|
| 41 | const AI = await import('../src/services/ArchiveImportService.js');
|
|---|
| 42 | const { AUDIO_ROOT } = await import('../src/config/paths.js');
|
|---|
| 43 |
|
|---|
| 44 | db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
|
|---|
| 45 | .run('u1', 'u1', 'u1@test', 'x', 'god');
|
|---|
| 46 | db.prepare('INSERT INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)').run('s1', 'me', 'Mijn site', 'u1');
|
|---|
| 47 |
|
|---|
| 48 | const BYTES = (s) => Buffer.from(`RIFF${s}`.padEnd(64, '.'));
|
|---|
| 49 |
|
|---|
| 50 | /** Schone lei: tabellen leeg en de audiomap leeg. Tests lekken anders in elkaar. */
|
|---|
| 51 | function leeg() {
|
|---|
| 52 | for (const t of ['playlist_tracks', 'playlists', 'audio_tracks', 'media']) {
|
|---|
| 53 | try { db.prepare(`DELETE FROM ${t}`).run(); } catch { /* tabel bestaat niet */ }
|
|---|
| 54 | }
|
|---|
| 55 | for (const f of fs.readdirSync(AUDIO)) { try { fs.unlinkSync(path.join(AUDIO, f)); } catch { /* niets */ } }
|
|---|
| 56 | }
|
|---|
| 57 |
|
|---|
| 58 | /** Een nummer met zijn bestand op de plek waar de SPELER het zoekt. */
|
|---|
| 59 | function track(id, titel, { padInDb = null, schrijf = true } = {}) {
|
|---|
| 60 | const naam = `${id}.mp3`;
|
|---|
| 61 | const echt = path.join(AUDIO, naam);
|
|---|
| 62 | if (schrijf) fs.writeFileSync(echt, BYTES(id));
|
|---|
| 63 | db.prepare('INSERT OR REPLACE INTO media (id, site_id, filename, mime_type, size, storage_path) VALUES (?,?,?,?,?,?)')
|
|---|
| 64 | .run(`m-${id}`, 's1', naam, 'audio/mpeg', 1, padInDb || echt);
|
|---|
| 65 | db.prepare('INSERT OR REPLACE INTO audio_tracks (id, site_id, title, artist, media_id) VALUES (?,?,?,?,?)')
|
|---|
| 66 | .run(id, 's1', titel, 'Robin', `m-${id}`);
|
|---|
| 67 | return echt;
|
|---|
| 68 | }
|
|---|
| 69 |
|
|---|
| 70 | test('een nummer met een VEROUDERD pad in de database gaat gewoon mee', () => {
|
|---|
| 71 | leeg();
|
|---|
| 72 | // Dit is de stille moordenaar. Op sound-fabrics.com wees storage_path voor
|
|---|
| 73 | // 124 van de 139 nummers nog naar /srv/prutfolio/storage/audio, van voor een
|
|---|
| 74 | // dataverhuizing. De speler merkte niets (die zoekt op bestandsnaam in
|
|---|
| 75 | // AUDIO_ROOT), de exporter vond niets en liet ze weg. Zonder klacht.
|
|---|
| 76 | track('oudpad', 'Verouderd pad', { padInDb: '/srv/ergens/anders/oudpad.mp3' });
|
|---|
| 77 | const uit = AX.buildArchive('me');
|
|---|
| 78 | const rij = JSON.parse(uit.files.get('tracks.json').toString('utf8')).orderedItems.find((x) => x.id === 'oudpad');
|
|---|
| 79 | assert.equal(rij['shaer:availability'], 'included',
|
|---|
| 80 | 'de exporter hoort te zoeken zoals de speler zoekt, niet zoals de database onthoudt');
|
|---|
| 81 | assert.deepEqual(uit.files.get(rij['shaer:file']), BYTES('oudpad'));
|
|---|
| 82 | });
|
|---|
| 83 |
|
|---|
| 84 | test('de hele rondgang: exporteren, importeren, en het geluid staat er', () => {
|
|---|
| 85 | leeg();
|
|---|
| 86 | track('a', 'Nummer A');
|
|---|
| 87 | track('b', 'Nummer B');
|
|---|
| 88 | const uit = AX.buildArchive('me');
|
|---|
| 89 | assert.equal(uit.counts.tracks, 2);
|
|---|
| 90 |
|
|---|
| 91 | // Een LEGE tweede installatie, met zijn eigen mappen.
|
|---|
| 92 | const TMP2 = fs.mkdtempSync(path.join(os.tmpdir(), 'klonkt-audio2-'));
|
|---|
| 93 | const doelAudio = path.join(TMP2, 'audio');
|
|---|
| 94 | fs.mkdirSync(doelAudio, { recursive: true });
|
|---|
| 95 |
|
|---|
| 96 | // AUDIO_ROOT ligt vast bij import, dus we importeren in DEZELFDE map en
|
|---|
| 97 | // controleren daarna dat het bestand er staat onder de naam uit het archief.
|
|---|
| 98 | leeg();
|
|---|
| 99 | assert.equal(fs.readdirSync(AUDIO).length, 0, 'schone lei');
|
|---|
| 100 |
|
|---|
| 101 | const rapport = AI.importArchive(uit.files, { slug: 'me', origin: 'https://nieuw.test' });
|
|---|
| 102 | assert.equal(rapport.tracks, 2, 'beide nummers komen terug');
|
|---|
| 103 | assert.equal(rapport.tracksMissing, 0);
|
|---|
| 104 |
|
|---|
| 105 | const rijen = db.prepare('SELECT t.id, t.title, m.storage_path, m.size FROM audio_tracks t JOIN media m ON m.id = t.media_id ORDER BY t.id').all();
|
|---|
| 106 | assert.equal(rijen.length, 2);
|
|---|
| 107 | for (const r of rijen) {
|
|---|
| 108 | assert.ok(fs.existsSync(r.storage_path), `${r.title}: het bestand hoort er ECHT te staan`);
|
|---|
| 109 | assert.ok(r.size > 0, 'en de grootte klopt, geen 0 zoals bij de kapotte import');
|
|---|
| 110 | assert.ok(r.storage_path.startsWith(path.resolve(AUDIO_ROOT) + path.sep),
|
|---|
| 111 | 'audio hoort in AUDIO_ROOT, niet onder de publieke mediamap');
|
|---|
| 112 | }
|
|---|
| 113 | fs.rmSync(TMP2, { recursive: true, force: true });
|
|---|
| 114 | });
|
|---|
| 115 |
|
|---|
| 116 | test('een nummer zonder bestand wordt NIET aangemaakt', () => {
|
|---|
| 117 | // De kern van de klacht: 13 nummers die bestonden en niet speelden. Liever
|
|---|
| 118 | // eerlijk weg dan zichtbaar kapot.
|
|---|
| 119 | leeg();
|
|---|
| 120 | track('zoek', 'Zoekgeraakt', { padInDb: '/bestaat/echt/niet.mp3', schrijf: false });
|
|---|
| 121 | const uit = AX.buildArchive('me');
|
|---|
| 122 | assert.equal(uit.counts.audioMissing, 1);
|
|---|
| 123 |
|
|---|
| 124 | leeg();
|
|---|
| 125 | const rapport = AI.importArchive(uit.files, { slug: 'me', origin: 'https://nieuw.test' });
|
|---|
| 126 | assert.equal(rapport.tracks, 0);
|
|---|
| 127 | assert.equal(rapport.tracksMissing, 1);
|
|---|
| 128 | assert.equal(db.prepare('SELECT COUNT(*) n FROM audio_tracks').get().n, 0,
|
|---|
| 129 | 'geen rij zonder geluid: dat is precies de val die soundfabrics.nl opleverde');
|
|---|
| 130 | assert.ok(rapport.waarschuwingen.some((w) => /Zoekgeraakt/.test(w)),
|
|---|
| 131 | 'en de gebruiker hoort te weten welk nummer er niet is');
|
|---|
| 132 | });
|
|---|
| 133 |
|
|---|
| 134 | test('playlists komen mee, met hun volgorde', () => {
|
|---|
| 135 | leeg();
|
|---|
| 136 |
|
|---|
| 137 | track('p1', 'Eerste');
|
|---|
| 138 | track('p2', 'Tweede');
|
|---|
| 139 | track('p3', 'Derde');
|
|---|
| 140 | db.prepare("INSERT INTO playlists (id, site_id, title, artist, kind) VALUES ('pl1','s1','Mijn plaat','Robin','album')").run();
|
|---|
| 141 | // Bewust NIET op alfabet: de volgorde is de hele playlist.
|
|---|
| 142 | db.prepare("INSERT INTO playlist_tracks (playlist_id, track_id, position) VALUES ('pl1','p3',0),('pl1','p1',1),('pl1','p2',2)").run();
|
|---|
| 143 |
|
|---|
| 144 | const uit = AX.buildArchive('me');
|
|---|
| 145 | assert.equal(uit.counts.playlists, 1);
|
|---|
| 146 |
|
|---|
| 147 | leeg();
|
|---|
| 148 |
|
|---|
| 149 | const rapport = AI.importArchive(uit.files, { slug: 'me', origin: 'https://nieuw.test' });
|
|---|
| 150 | assert.equal(rapport.playlists, 1);
|
|---|
| 151 | const pl = db.prepare("SELECT * FROM playlists WHERE id = 'pl1'").get();
|
|---|
| 152 | assert.equal(pl.title, 'Mijn plaat');
|
|---|
| 153 | assert.equal(pl.kind, 'album');
|
|---|
| 154 | const volgorde = db.prepare("SELECT track_id FROM playlist_tracks WHERE playlist_id = 'pl1' ORDER BY position").all().map((r) => r.track_id);
|
|---|
| 155 | assert.deepEqual(volgorde, ['p3', 'p1', 'p2'], 'de volgorde IS de playlist');
|
|---|
| 156 | });
|
|---|
| 157 |
|
|---|
| 158 | test('een playlist verwijst nooit naar een nummer dat niet aankwam', () => {
|
|---|
| 159 | // Anders staat er een plaat met gaten erin die je niet kunt afspelen, en dat
|
|---|
| 160 | // is weer dezelfde soort halve waarheid.
|
|---|
| 161 | leeg();
|
|---|
| 162 |
|
|---|
| 163 | track('heel', 'Heel');
|
|---|
| 164 | track('stuk', 'Stuk', { padInDb: '/weg/stuk.mp3', schrijf: false });
|
|---|
| 165 | db.prepare("INSERT INTO playlists (id, site_id, title) VALUES ('pl2','s1','Half')").run();
|
|---|
| 166 | db.prepare("INSERT INTO playlist_tracks (playlist_id, track_id, position) VALUES ('pl2','heel',0),('pl2','stuk',1)").run();
|
|---|
| 167 |
|
|---|
| 168 | const uit = AX.buildArchive('me');
|
|---|
| 169 | leeg();
|
|---|
| 170 |
|
|---|
| 171 | const rapport = AI.importArchive(uit.files, { slug: 'me', origin: 'https://nieuw.test' });
|
|---|
| 172 | const items = db.prepare("SELECT track_id FROM playlist_tracks WHERE playlist_id = 'pl2'").all().map((r) => r.track_id);
|
|---|
| 173 | assert.deepEqual(items, ['heel']);
|
|---|
| 174 | assert.ok(rapport.waarschuwingen.some((w) => /Half/.test(w)), 'en het wordt gemeld');
|
|---|
| 175 | });
|
|---|
| 176 |
|
|---|
| 177 | test('een droogloop telt de nummers maar schrijft niets', () => {
|
|---|
| 178 | leeg();
|
|---|
| 179 | track('d1', 'Droog');
|
|---|
| 180 | const uit = AX.buildArchive('me');
|
|---|
| 181 |
|
|---|
| 182 | leeg();
|
|---|
| 183 |
|
|---|
| 184 | const droog = AI.importArchive(uit.files, { slug: 'me', origin: 'https://nieuw.test', dryRun: true });
|
|---|
| 185 | assert.equal(droog.tracks, 1, 'het verslag hoort te zeggen hoeveel nummers er zouden komen');
|
|---|
| 186 | assert.equal(db.prepare('SELECT COUNT(*) n FROM audio_tracks').get().n, 0, 'maar er staat niets');
|
|---|
| 187 | assert.equal(fs.readdirSync(AUDIO).length, 0, 'en er is geen bestand geschreven');
|
|---|
| 188 | });
|
|---|
| 189 |
|
|---|
| 190 |
|
|---|
| 191 | test('de hoes van een nummer reist mee als BESTAND, niet als losse verwijzing', () => {
|
|---|
| 192 | // Robin na de tweede ronde: "de audio tracks hadden images, die zijn niet
|
|---|
| 193 | // meegegaan". cover_url ging wel mee als string en het bestand niet, dus kwam
|
|---|
| 194 | // een nummer aan met een verwijzing naar een plaatje dat er niet was.
|
|---|
| 195 | // Dezelfde fout als bij de audio zelf, een laag hoger.
|
|---|
| 196 | leeg();
|
|---|
| 197 | const hoesDir = path.join(MEDIA, 'hoes');
|
|---|
| 198 | fs.mkdirSync(hoesDir, { recursive: true });
|
|---|
| 199 | const PNG = Buffer.from('nep-png-bytes');
|
|---|
| 200 | fs.writeFileSync(path.join(hoesDir, 'a.png'), PNG);
|
|---|
| 201 | track('h1', 'Met hoes');
|
|---|
| 202 | db.prepare("UPDATE audio_tracks SET cover_url = '/media/hoes/a.png' WHERE id = 'h1'").run();
|
|---|
| 203 | db.prepare("INSERT INTO playlists (id, site_id, title, cover_url) VALUES ('plh','s1','Plaat','/media/hoes/a.png')").run();
|
|---|
| 204 | db.prepare("INSERT INTO playlist_tracks (playlist_id, track_id, position) VALUES ('plh','h1',0)").run();
|
|---|
| 205 |
|
|---|
| 206 | const uit = AX.buildArchive('me');
|
|---|
| 207 | const rij = JSON.parse(uit.files.get('tracks.json').toString('utf8')).orderedItems[0];
|
|---|
| 208 | assert.ok(rij['shaer:coverFile'], 'de hoes hoort een plek in het archief te hebben');
|
|---|
| 209 | assert.deepEqual(uit.files.get(rij['shaer:coverFile']), PNG, 'met de echte bytes erin');
|
|---|
| 210 | const pl = JSON.parse(uit.files.get('playlists.json').toString('utf8')).orderedItems[0];
|
|---|
| 211 | assert.ok(pl['shaer:coverFile'], 'en de hoes van de plaat ook');
|
|---|
| 212 |
|
|---|
| 213 | leeg();
|
|---|
| 214 | AI.importArchive(uit.files, { slug: 'me', origin: 'https://nieuw.test' });
|
|---|
| 215 | const t = db.prepare("SELECT cover_url FROM audio_tracks WHERE id = 'h1'").get();
|
|---|
| 216 | assert.ok(t.cover_url, 'na de import wijst het nummer naar een hoes');
|
|---|
| 217 | const opSchijf = path.join(MEDIA, t.cover_url.replace(/^\/media\//, ''));
|
|---|
| 218 | assert.deepEqual(fs.readFileSync(opSchijf), PNG, 'en die staat er ook echt');
|
|---|
| 219 | });
|
|---|
| 220 |
|
|---|
| 221 | test('een hoes die niet in het archief zit levert GEEN kapotte verwijzing op', () => {
|
|---|
| 222 | // Liever geen hoes dan een <img> die 404't. Dezelfde regel als bij de tracks.
|
|---|
| 223 | leeg();
|
|---|
| 224 | track('h2', 'Hoes zoek');
|
|---|
| 225 | db.prepare("UPDATE audio_tracks SET cover_url = '/media/bestaat/niet.png' WHERE id = 'h2'").run();
|
|---|
| 226 | const uit = AX.buildArchive('me');
|
|---|
| 227 | const rij = JSON.parse(uit.files.get('tracks.json').toString('utf8')).orderedItems[0];
|
|---|
| 228 | assert.equal(rij['shaer:coverFile'], undefined);
|
|---|
| 229 |
|
|---|
| 230 | leeg();
|
|---|
| 231 | AI.importArchive(uit.files, { slug: 'me', origin: 'https://nieuw.test' });
|
|---|
| 232 | assert.equal(db.prepare("SELECT cover_url FROM audio_tracks WHERE id = 'h2'").get().cover_url, null,
|
|---|
| 233 | 'geen verwijzing naar een plaatje dat er niet is');
|
|---|
| 234 | });
|
|---|
| 235 |
|
|---|
| 236 | test('ook een zip-import buigt links naar de bronpost om', () => {
|
|---|
| 237 | // Robin: "het moet wel gebeuren bij migratie direct ook". Een zip-import is
|
|---|
| 238 | // net zo goed een verhuizing, dus dezelfde regel.
|
|---|
| 239 | leeg();
|
|---|
| 240 | try { db.prepare('DELETE FROM posts').run(); } catch { /* leeg */ }
|
|---|
| 241 | db.prepare(`INSERT INTO posts (id, site_id, author_id, slug, title, content, status, published_at)
|
|---|
| 242 | VALUES ('pl1','s1','u1','tiktik','TikTik','<p><a href="https://oud.test/tiktik?fc=2">luister</a></p>','published','2026-01-01T10:00:00Z')`).run();
|
|---|
| 243 | const uit = AX.buildArchive('me');
|
|---|
| 244 |
|
|---|
| 245 | db.prepare('DELETE FROM posts').run();
|
|---|
| 246 | // De import doet alsof het archief van oud.test komt; deze site is oud.test
|
|---|
| 247 | // niet, dus de links moeten om.
|
|---|
| 248 | const files = new Map(uit.files);
|
|---|
| 249 | const man = JSON.parse(files.get('manifest.json').toString('utf8'));
|
|---|
| 250 | man.origin = 'https://oud.test';
|
|---|
| 251 | files.set('manifest.json', Buffer.from(JSON.stringify(man)));
|
|---|
| 252 |
|
|---|
| 253 | const r = AI.importArchive(files, { slug: 'me', origin: 'https://nieuw.test' });
|
|---|
| 254 | const c = db.prepare("SELECT content FROM posts WHERE slug = 'tiktik'").get().content;
|
|---|
| 255 | assert.ok(c.includes('href="/tiktik?fc=2"'), `omgebogen naar hier, kreeg: ${c}`);
|
|---|
| 256 | assert.ok(!c.includes('oud.test'), 'niets wijst meer naar de bron');
|
|---|
| 257 | assert.equal(r.linksBijgetrokken, 1, 'en het verslag zegt het');
|
|---|
| 258 | });
|
|---|
| 259 |
|
|---|
| 260 | test('een track met alleen Spotify/YouTube-links reist gewoon mee', () => {
|
|---|
| 261 | // Robins Youngstown-nummer. Klonkt kent link-only tracks: geen gehost
|
|---|
| 262 | // bestand, wel externe links, en buildNote maakt daar een embed-kaart van.
|
|---|
| 263 | // Mijn regel "geen bestand, geen track" gooide die weg, en dat was te grof:
|
|---|
| 264 | // "geen bestand" en "niets om te tonen" zijn niet hetzelfde.
|
|---|
| 265 | leeg();
|
|---|
| 266 | db.prepare(`INSERT INTO audio_tracks (id, site_id, title, artist, media_id, link_spotify, link_youtube)
|
|---|
| 267 | VALUES ('lo','s1','Alleen links','Youngstown',NULL,
|
|---|
| 268 | 'https://open.spotify.com/track/abc','https://www.youtube.com/watch?v=xyz')`).run();
|
|---|
| 269 |
|
|---|
| 270 | const uit = AX.buildArchive('me');
|
|---|
| 271 | const rij = JSON.parse(uit.files.get('tracks.json').toString('utf8')).orderedItems.find((x) => x.id === 'lo');
|
|---|
| 272 | assert.equal(rij['shaer:availability'], 'linkOnly', 'een eigen staat, niet "missing"');
|
|---|
| 273 | assert.equal(uit.counts.audioMissing, 0, 'en hij telt niet als ontbrekend: er mist niets');
|
|---|
| 274 | assert.deepEqual(rij.url, ['https://open.spotify.com/track/abc', 'https://www.youtube.com/watch?v=xyz']);
|
|---|
| 275 |
|
|---|
| 276 | leeg();
|
|---|
| 277 | const r = AI.importArchive(uit.files, { slug: 'me', origin: 'https://nieuw.test' });
|
|---|
| 278 | assert.equal(r.tracks, 1);
|
|---|
| 279 | assert.equal(r.tracksMissing, 0);
|
|---|
| 280 | const t = db.prepare("SELECT title, artist, media_id, link_spotify, link_youtube FROM audio_tracks WHERE id = 'lo'").get();
|
|---|
| 281 | assert.ok(t, 'de track hoort er te staan');
|
|---|
| 282 | assert.equal(t.media_id, null, 'zonder mediarij, precies zoals op de bron');
|
|---|
| 283 | assert.equal(t.title, 'Alleen links');
|
|---|
| 284 | assert.equal(t.link_spotify, 'https://open.spotify.com/track/abc');
|
|---|
| 285 | assert.equal(t.link_youtube, 'https://www.youtube.com/watch?v=xyz');
|
|---|
| 286 | });
|
|---|
| 287 |
|
|---|
| 288 | test('een track zonder bestand EN zonder links blijft wel geweigerd', () => {
|
|---|
| 289 | // De oorspronkelijke regel blijft staan waar hij hoort: dit is een track die
|
|---|
| 290 | // niets kan tonen en niets kan afspelen.
|
|---|
| 291 | leeg();
|
|---|
| 292 | track('kapot', 'Kapot', { padInDb: '/weg/kapot.mp3', schrijf: false });
|
|---|
| 293 | const uit = AX.buildArchive('me');
|
|---|
| 294 | assert.equal(uit.counts.audioMissing, 1);
|
|---|
| 295 | leeg();
|
|---|
| 296 | const r = AI.importArchive(uit.files, { slug: 'me', origin: 'https://nieuw.test' });
|
|---|
| 297 | assert.equal(r.tracks, 0);
|
|---|
| 298 | assert.equal(r.tracksMissing, 1);
|
|---|
| 299 | assert.equal(db.prepare('SELECT COUNT(*) n FROM audio_tracks').get().n, 0);
|
|---|
| 300 | });
|
|---|
| 301 |
|
|---|
| 302 | test.after(() => { try { fs.rmSync(TMP, { recursive: true, force: true }); } catch { /* niets */ } });
|
|---|