UmbrelApps: community app store whatsnext
Electrum Gate, Evolu Relay en Atreides Assets, met de image-recepten in tools/ en de tests in tests/. Begonnen zonder voorgeschiedenis op 09-10-2026. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
85d85a1295
63 files changed
+13987
No files matched your search
@@ -0,0 +1,201 @@
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// 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();
|
||||
|
||||
// ── Twee eigenaars tegelijk ────────────────────────────────────────────────
|
||||
//
|
||||
// Dit is de toets voor de fout die de gebruiker op 09-09-2026 vond, en het is
|
||||
// een fout die met één eigenaar onzichtbaar is. `createEvoluDeps` vraagt het
|
||||
// slot "tab" aan en kondigt de houder aan als tab leader; dat slot kan er maar
|
||||
// één hebben. Werd dat per eigenaar aangeroepen, dan werd de tweede nooit
|
||||
// leider en brak zijn database-worker af met "Expected tab leader port.".
|
||||
const eersteVanTwee = await openStore({
|
||||
directory: map,
|
||||
owner: nieuweEigenaar(),
|
||||
onDefect: (defect) => defecten.push(defect),
|
||||
});
|
||||
const tweedeVanTwee = await openStore({
|
||||
directory: map,
|
||||
owner: nieuweEigenaar(),
|
||||
onDefect: (defect) => defecten.push(defect),
|
||||
});
|
||||
|
||||
try {
|
||||
await eersteVanTwee.schrijf('van de eerste', new Uint8Array([1]));
|
||||
await tweedeVanTwee.schrijf('van de tweede', new Uint8Array([2, 2]));
|
||||
|
||||
const vanEerste = await eersteVanTwee.lees();
|
||||
const vanTweede = await tweedeVanTwee.lees();
|
||||
|
||||
toets('twee eigenaars kunnen tegelijk schrijven', vanEerste.length === 1 && vanTweede.length === 1);
|
||||
// En ze zien elkaars gegevens niet, want dat is de hele belofte van een
|
||||
// eigenaar. Zou dat wél zo zijn, dan deelden ze een database.
|
||||
toets('en ze zien elkaars blobs niet', vanEerste[0]?.label === 'van de eerste' && vanTweede[0]?.label === 'van de tweede');
|
||||
|
||||
// De eerste sluiten mag de tweede niet slopen: de afhankelijkheden zijn
|
||||
// gedeeld, dus ze gaan pas weg als de laatste store dicht is.
|
||||
await eersteVanTwee.sluit();
|
||||
const naSluiten = await tweedeVanTwee.lees();
|
||||
toets('de tweede blijft werken als de eerste sluit', naSluiten.length === 1);
|
||||
} finally {
|
||||
await tweedeVanTwee.sluit();
|
||||
}
|
||||
|
||||
// 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);
|
||||
Reference in new issue
Block a user