Negen punten uit echt gebruik met 0.4.0, alle negen gedaan. Twee ervan waren onderzoeksvragen en die staan onderaan. Evolu Relay 0.5.0 - Een tijdvenster van twee minuten voor nieuwe eigenaars, met een stopknop. De teller zit in het relay-proces en niet in de pagina: een teller in een tabblad dat je sluit, sluit de deur niet. policy.js kreeg learningUntil, isLearningOpen en expireLearning. - decideOwner kijkt naar isLearningOpen en niet naar het veld learning. De lus die een verlopen venster opruimt loopt elke twee seconden, en in dat gat zou een onbekende alsnog binnenkomen. - Zonder STATE_VERSION te verhogen, met een toets die dat verdedigt: een verhoging zou de allowlist van de draaiende installatie laten afwijzen en de deur sluiten voor eigenaars die er al in stonden. - Labels op een eigenaar-id, in een eigen labels.json met de agent als enige schrijver. Een label zegt niets over toegang, dus de relay hoeft het niet te weten; het is daardoor meteen opgeslagen en werkt ook als de relay omligt. - Geblokkeerde en geweigerde eigenaars in een kader, met een badge die zegt welke van de twee het is. De badge staat buiten het hover-blok, anders is dat onderscheid onzichtbaar tenzij je over de regel gaat. - Maatvoering gelijk aan Electrum Gate: 1760px, hetzelfde raster, icoon van 64 pixels, dezelfde kop, versienummer erachter. Uitleg uit de kaders, knoppen pas bij hover, geen voetregel. Electrum Gate 0.0.24 - Menu-item "About this app", in beide apps. - De statuswidget zei "Answering" met "answered in 7 ms, from inside the app" en zegt nu "Running" met de meting eronder. De nuance dat de controle van container naar container loopt is verplaatst naar een eigen kopje in die dialoog, waar er ruimte voor is; vier woorden waren te weinig. Toetsen en gereedschap - tests/test_relay_agent.py (nieuw, 65 toetsen) en tests/test_paginas_parsen.mjs (nieuw). Muteertests gedraaid op de beslissende regels. - Een dollarteken-toets in test_appstore_vorm.py. Het commentaar in drie bestanden beweerde al dat die test bestond; nu is dat waar. - Een toets dat er geen werkbestanden in een app-map staan. umbreld kopieert de hele map naar het apparaat en in de back-up. - tools/voorbeeldpagina.mjs maakt van een *.template een pagina die je in een browser kunt openen. Dat vond meteen twee echte opmaakfouten. De twee onderzoeksvragen - Een geweigerde eigenaar komt niet in de database: isOwnerAllowed zit in de WebSocket-upgrade, dus het is een 401 en een gesloten socket. Het gewenste gevolg treedt wel op, via de client: die is local-first en levert bij toelating de hele geschiedenis. Blokkeren werkt daarentegen pas bij de volgende verbinding, en dat staat als open punt. - De blobs zijn niet met een xpub te ontcijferen; een OwnerId komt daar niet uit. Met de SLIP-21-node van het apparaat kan het wel, maar die geeft volledige zeggenschap, dus dat hoort niet in een relay. Als plan-punt opgenomen bij de tool in HomeGit/Trezor. Nog niet uitgerold: de image 0.5.0 moet gebouwd en geduwd worden. De digest staat daarom niet in de compose, want een oude digest onder een nieuwe tag levert stil de oude relay. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
144 lines
5.8 KiB
JavaScript
144 lines
5.8 KiB
JavaScript
// ═══════════════════════════════════════════════════════════════════════════════
|
|
// Toetst dat de JavaScript in de statuspagina's parseert, en dat hij dat ook nog
|
|
// doet nadat umbreld de template heeft ingevuld.
|
|
//
|
|
// Draaien: node tests/test_paginas_parsen.mjs
|
|
//
|
|
// Waarom dit bestand er is. De pagina's zijn met bijna drieduizend regels het
|
|
// grootste deel van deze repo en er staat geen enkele toets op: alles eraan is
|
|
// handwerk in een browser. Dat blijft zo voor wat het doet, maar één klasse fouten
|
|
// hoeft niet op het apparaat gevonden te worden: een tikfout waardoor het script
|
|
// helemaal niet loopt. Het gevolg daarvan is een pagina die stil op "unknown"
|
|
// blijft staan, en dat lijkt op een agent die niet antwoordt.
|
|
//
|
|
// Aanleiding: op 30-08-2026 is de pagina van Evolu Relay in zijn geheel opnieuw
|
|
// geschreven, negenhonderd regels in één keer. Zo'n wijziging hoort niet ongetoetst
|
|
// naar een apparaat te gaan.
|
|
//
|
|
// De invulling wordt hier nagedaan en niet alleen de bron gelezen, want umbreld
|
|
// haalt elke *.template door envsubst en wat er draait is dus niet wat er staat.
|
|
// Dat vindt de gevallen waar het weghalen van een accolade-vorm de syntaxis breekt.
|
|
//
|
|
// LET OP wat deze helft NIET vindt, want dat is precies de fout die dit project
|
|
// vreest: een template-literal als `iets ${x}` wordt na de invulling `iets ` en dat
|
|
// parseert prima. Hij is dan stil kapot en niet luidruchtig. Wat dát dichthoudt is
|
|
// de dollarteken-toets in tests/test_appstore_vorm.py, die élke accolade-vorm
|
|
// vlagt die umbrelOS niet zelf invult. Deze toets is de tweede zeef en niet de
|
|
// eerste.
|
|
//
|
|
// Wat dit verder NIET toetst: of het script het juiste doet. Alleen dat het loopt.
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import vm from 'node:vm';
|
|
|
|
const HIER = dirname(fileURLToPath(import.meta.url));
|
|
const REPO = join(HIER, '..');
|
|
|
|
let goed = 0;
|
|
let fout = 0;
|
|
|
|
const toets = (omschrijving, voorwaarde, uitleg) => {
|
|
if (voorwaarde) {
|
|
goed += 1;
|
|
return;
|
|
}
|
|
fout += 1;
|
|
console.log(`FOUT: ${omschrijving}${uitleg ? ` - ${uitleg}` : ''}`);
|
|
};
|
|
|
|
/** Elke map in de repo-root met een umbrel-app.yml erin. Zelf gevonden, zoals de
|
|
* Python-toets over de store dat ook doet: een derde app valt er dan vanzelf
|
|
* onder. */
|
|
const appMappen = () =>
|
|
readdirSync(REPO, { withFileTypes: true })
|
|
.filter((regel) => regel.isDirectory())
|
|
.map((regel) => regel.name)
|
|
.filter((naam) => existsSync(join(REPO, naam, 'umbrel-app.yml')))
|
|
.sort();
|
|
|
|
/** De inhoud van elk <script>-blok zonder src-attribuut. */
|
|
const scriptBlokken = (html) => {
|
|
const blokken = [];
|
|
const patroon = /<script(?![^>]*\bsrc=)[^>]*>([\s\S]*?)<\/script>/gi;
|
|
let treffer = patroon.exec(html);
|
|
while (treffer !== null) {
|
|
blokken.push(treffer[1]);
|
|
treffer = patroon.exec(html);
|
|
}
|
|
return blokken;
|
|
};
|
|
|
|
/**
|
|
* Doet na wat umbreld met een template doet.
|
|
*
|
|
* envsubst vervangt `${NAAM}` en `$NAAM` door de waarde uit de omgeving, en door
|
|
* een lege string als die variabele niet bestaat. Wat wij hier invullen is een
|
|
* plausibele waarde voor de variabelen die de pagina's gebruiken; voor de rest de
|
|
* lege string, want dat is precies wat er op het apparaat zou gebeuren.
|
|
*/
|
|
const vulIn = (tekst) => {
|
|
const WAARDEN = { APP_VERSION: '0.5.0' };
|
|
return tekst.replace(/\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?/g, (heel, naam) =>
|
|
Object.prototype.hasOwnProperty.call(WAARDEN, naam) ? WAARDEN[naam] : '',
|
|
);
|
|
};
|
|
|
|
/** Parseert het script zonder het te draaien. Geeft de foutmelding of null. */
|
|
const parseert = (bron) => {
|
|
try {
|
|
// new vm.Script parseert bij het aanmaken en draait niets. Dat is precies wat
|
|
// hier nodig is: draaien zou een document en een window vragen die er niet
|
|
// zijn, en dan toets je je eigen namaakbrowser.
|
|
new vm.Script(bron);
|
|
return null;
|
|
} catch (error) {
|
|
return String(error && error.message ? error.message : error);
|
|
}
|
|
};
|
|
|
|
for (const app of appMappen()) {
|
|
const pad = join(REPO, app, 'index.html.template');
|
|
if (!existsSync(pad)) {
|
|
// Geen pagina is geen fout: een app hoeft er geen te hebben.
|
|
continue;
|
|
}
|
|
|
|
const bron = readFileSync(pad, 'utf8');
|
|
const blokken = scriptBlokken(bron);
|
|
|
|
toets(`${app}: de pagina heeft een scriptblok`, blokken.length > 0);
|
|
|
|
blokken.forEach((blok, index) => {
|
|
const naam = `${app}: scriptblok ${index + 1}`;
|
|
|
|
const rauw = parseert(blok);
|
|
toets(`${naam} parseert zoals hij in de repo staat`, rauw === null, rauw);
|
|
|
|
const ingevuld = parseert(vulIn(blok));
|
|
toets(`${naam} parseert ook nadat umbreld hem invult`, ingevuld === null, ingevuld);
|
|
});
|
|
|
|
// De HTML zelf: geen parser, maar wel de fout die je in een bestand van
|
|
// tweeduizend regels echt maakt. Een dialog of een header die niet gesloten is,
|
|
// laat de rest van de pagina in dat element hangen.
|
|
for (const tag of ['html', 'head', 'body', 'header', 'script', 'style', 'dialog', 'details']) {
|
|
const open = (bron.match(new RegExp(`<${tag}[\\s>]`, 'gi')) || []).length;
|
|
const dicht = (bron.match(new RegExp(`</${tag}>`, 'gi')) || []).length;
|
|
if (open === 0 && dicht === 0) {
|
|
continue;
|
|
}
|
|
toets(
|
|
`${app}: elke <${tag}> is gesloten`,
|
|
open === dicht,
|
|
`${open} keer open, ${dicht} keer dicht`,
|
|
);
|
|
}
|
|
}
|
|
|
|
console.log('');
|
|
console.log(`${goed} goed, ${fout} fout`);
|
|
process.exit(fout === 0 ? 0 : 1);
|