Het plan is gepromoveerd naar Actief/003-Testclient en tier A, en fase 1 is af. De uitkomst die het plan als eerste wilde weten: @evolu/nodejs 3.1.0 bevat geen client. Het pakket levert de relay plus losse bouwstenen, maar er is geen createEvoluDeps voor Node zoals @evolu/web die voor de browser heeft. De afhankelijkheden voor createEvolu worden nu samengesteld in tools/relay-client/src/evolu-node.js, uit de in-memory workers van @evolu/common; src/client.js is de publieke kant met eigenaars en blobs. Vier dingen zaten in de weg en alle vier faalden ze stil, met een instantie die het lijkt te doen tot de eerste schrijfactie: de ontbrekende installPolyfills (Node 24 mist Map.getOrInsertComputed), createEvoluDeps overslaan, de AsyncDisposableStack van initSharedWorker laten vallen, en workers zonder eigen reportDefect. Dat laatste is waarom de andere drie te vinden waren. Uitgeschreven in PLAN.md 4e. tests/test_client_lokaal.mjs schrijft daarom een blob weg en leest hem terug in plaats van alleen een instantie te maken, en heeft een wachthond: bij een defect in een worker blijft de suite anders hangen in plaats van rood te worden, en dat gebeurde bij de mutatietoets letterlijk. Beide mutaties lieten de juiste toets omvallen. Nog niet geprobeerd: praten met de relay. Daarvoor is het adres van de Umbrel nodig en dat komt van de gebruiker; het gaat niet in deze publieke repo. Suite: 405 goed, 0 fout over alle acht de toetsen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
146 lines
6.1 KiB
JavaScript
146 lines
6.1 KiB
JavaScript
// ═══════════════════════════════════════════════════════════════════════════════
|
|
// 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);
|