De client staat. Een register (owners.json met naam, mnemonic en OwnerId), acht opdrachten op de opdrachtregel, en een bedieningsvlak op 127.0.0.1:4380 met per eigenaar de knoppen uit PLAN.md 4b. Start.bat en Start.command erbij op verzoek van de gebruiker: die draaien npm install als het nodig is, waarschuwen als .env ontbreekt, openen de browser en houden het venster open bij een fout. OPEN.md punt 1 is opgelost voordat het een probleem werd. src/probe.js doet de WebSocket-upgrade met node:http in plaats van met de WebSocket van Node, want die geeft je bij een weigering een error en geen statuscode. De opdracht klop en de knop Aankloppen tonen dus 101 of 401, en dat is precies wat de proeven uit 4c moeten kunnen aflezen. Twee echte fouten gevonden en vastgezet in een toets. De databasemap werd niet aangemaakt, en omdat better-sqlite3 dat in een worker meldt was het symptoom een lees die nooit antwoordde. En evolu.insert geeft meteen een id terug maar zet de schrijfactie in de wachtrij van de worker, dus wie vlak daarna afsluit is de rij kwijt; schrijf wacht nu op een query erachter en de toets sluit af en heropent. tests/test_client_register.mjs heeft geen pakketten nodig, want register.js, argumenten.js en config.js raken Evolu niet aan. Drie mutaties geprobeerd en alle drie lieten de juiste toets omvallen. In de browser nagekeken: schrijven en teruglezen werkt vanuit de pagina, de gegevens zijn dezelfde als die van de opdrachtregel, en licht en donker kloppen. Alles wat de relay raakt is gebouwd en niets ervan is beproefd; daarvoor wacht er een RELAY_URL in .env, en die komt van de gebruiker. Suite: 475 goed, 0 fout over alle negen de toetsen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
163 lines
7.0 KiB
JavaScript
163 lines
7.0 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 = [];
|
|
// Vastgehouden, want verderop wordt met dezelfde eigenaar heropend.
|
|
const eigenaar = nieuweEigenaar();
|
|
store = await openStore({
|
|
directory: map,
|
|
owner: eigenaar,
|
|
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 = await 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();
|
|
|
|
// Opnieuw openen met dezelfde eigenaar en dezelfde map. Dit is de toets die
|
|
// een echte fout van 09-09-2026 vasthoudt: `evolu.insert` zet de schrijfactie
|
|
// in de wachtrij van de database-worker en geeft meteen een id terug, dus wie
|
|
// vlak daarna afsluit is de rij kwijt. Het symptoom was een `lees` die "nog
|
|
// geen blobs" meldde over een blob die zojuist bevestigd was, en dát is precies
|
|
// het soort fout dat een toets binnen één instantie niet vindt.
|
|
store = await openStore({ directory: map, owner: eigenaar });
|
|
const naHeropenen = await store.lees();
|
|
toets('de blob overleeft het afsluiten en heropenen', naHeropenen.length === 1);
|
|
toets('met hetzelfde label', naHeropenen[0]?.label === 'toets');
|
|
|
|
const nogmaalsSluiten = store.sluit;
|
|
store = null;
|
|
await nogmaalsSluiten();
|
|
|
|
// 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);
|