// ═══════════════════════════════════════════════════════════════════════════════ // Toetst de testcliënt zonder relay: tools/relay-client/src/client.js // // Draaien: node tests/test_client_lokaal.mjs // // Anders dan de rest van deze map heeft deze toets wél afhankelijkheden nodig, // namelijk die van tools/relay-client. Staat `node_modules` daar niet, dan slaat // hij zichzelf over en meldt dat, zoals de certificaattoetsen doen als de // certificaatwinkel van het besturingssysteem ontbreekt. Installeren gaat met // `npm install` in tools/relay-client. // // Waarom dit bestand er is: `@evolu/nodejs` bevat geen cliënt, dus de // afhankelijkheden voor `createEvolu` worden in src/evolu-node.js zelf // samengesteld. Dat is de plek die bij een versieverhoging van Evolu als eerste // omvalt, en hij faalt stil: alle vier de fouten die dat bestand tijdens de bouw // opleverde gaven een instantie die het lijkt te doen, tot de eerste // schrijfactie. **Deze toets doet dus een schrijfactie**, en aanmaken alleen is // geen bewijs. // // Geen relay: `openStore` zonder `relayUrl`. Wat hier getoetst wordt is de // cliënt, en die is los van het netwerk te beproeven. Het toegangsbeleid van de // relay staat in test_limiter.mjs. // ═══════════════════════════════════════════════════════════════════════════════ import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; // Een wachthond, en die is hier niet voor de sier. Gaat er iets mis in de // samenstelling, dan komt dat er meestal uit als een schrijfactie die nooit // antwoordt in plaats van een fout: het defect zit in een worker en de // `loadQuery` erna blijft hangen. Zonder deze wachthond blijft de suite dus // staan in plaats van rood te worden, en dat is bij de mutatietoets van // 09-09-2026 letterlijk gebeurd. const WACHTHOND_SECONDEN = 30; setTimeout(() => { console.log(`FOUT: niets meer gehoord na ${WACHTHOND_SECONDEN} seconden.`); console.log(' Dat wijst op een defect in een worker; zie tools/relay-client/src/evolu-node.js.'); console.log(''); console.log('afgebroken'); process.exit(1); }, WACHTHOND_SECONDEN * 1000).unref(); let goed = 0; let fout = 0; const toets = (omschrijving, voorwaarde) => { if (voorwaarde) { goed += 1; return; } fout += 1; console.log(`FOUT: ${omschrijving}`); }; // ── Zijn de afhankelijkheden er? ────────────────────────────────────────────── let client; try { client = await import('../tools/relay-client/src/client.js'); } catch (oorzaak) { console.log('OVERGESLAGEN: de pakketten van tools/relay-client ontbreken.'); console.log(` Draai daar eerst \`npm install\`. (${oorzaak.message})`); console.log(''); console.log('0 goed, 0 fout'); process.exit(0); } const { eigenaarUitMnemonic, nieuweEigenaar, openStore } = client; // ── Een mnemonic levert altijd hetzelfde OwnerId op ─────────────────────────── { // Dit is de toets die omvalt als Evolu onder ons iets anders gaat doen met de // afleiding van een eigenaar. Vaste mnemonic, vast antwoord. Het is geen // geheim: deze eigenaar bestaat alleen in deze toets, en het is bovendien de // bekendste testmnemonic die er is. const MNEMONIC = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'; const eerste = eigenaarUitMnemonic(MNEMONIC); const tweede = eigenaarUitMnemonic(MNEMONIC); toets('dezelfde mnemonic geeft hetzelfde OwnerId', eerste.id === tweede.id); toets('en een OwnerId is een niet-lege string', typeof eerste.id === 'string' && eerste.id.length > 0); toets('de eigenaar houdt zijn mnemonic bij zich', eerste.mnemonic === MNEMONIC); toets('een verse eigenaar krijgt een ander OwnerId', nieuweEigenaar().id !== eerste.id); let geweigerd = false; try { eigenaarUitMnemonic('dit is geen mnemonic'); } catch { geweigerd = true; } // Het niet-gelukkige pad, en dat is hier het belangrijkste: stil een andere // eigenaar maken zou een proef opleveren die nergens over gaat. toets('een onleesbare mnemonic wordt geweigerd', geweigerd); } // ── Schrijven en teruglezen, zonder relay ───────────────────────────────────── const map = mkdtempSync(join(tmpdir(), 'relay-client-toets-')); let store = null; try { const defecten = []; store = await openStore({ directory: map, owner: nieuweEigenaar(), onDefect: (defect) => { defecten.push(defect); }, }); toets('zonder relayUrl is er geen transport', store.transportUrl === null); const bytes = new Uint8Array([0, 1, 2, 253, 254, 255]); const geschreven = store.schrijf('toets', bytes); toets('een blob wegschrijven geeft een id terug', typeof geschreven?.id === 'string'); const rijen = await store.lees(); toets('en die blob komt er weer uit', rijen.length === 1); toets('met hetzelfde label', rijen[0]?.label === 'toets'); toets( 'en met de bytes ongeschonden', rijen[0]?.body?.length === bytes.length && [...(rijen[0]?.body ?? [])].every((waarde, plek) => waarde === bytes[plek]), ); const sluiten = store.sluit; store = null; await sluiten(); // Afsluiten hoort stil te zijn. Ging dat mis, dan bleef er een worker draaien // die zijn eigen run al kwijt was, en dat merk je pas als een proces blijft // hangen. Dat was de vierde valstrik uit PLAN.md §4e. toets(`afsluiten levert geen defect op (${defecten.length} gezien)`, defecten.length === 0); } finally { if (store) await store.sluit(); rmSync(map, { recursive: true, force: true }); } console.log(''); console.log(`${goed} goed, ${fout} fout`); process.exit(fout === 0 ? 0 : 1);