Pruefskripte: wiederholbar und ehrlich machen

Die Dichtheitspruefung der Musterloesungen las die Server-Nutzlast aus
self.__next_f - das Feld leert Next nach der Verarbeitung, es war also
immer leer. Die Pruefung lief an dieser Stelle ins Leere, ohne dass es
auffiel; nur der HTML-Quelltext wurde wirklich geprueft. Gelesen wird
jetzt der Quelltext der eingebetteten Skripte. Damit zeigt die Ausgabe,
was sie zeigen soll: vor der Abgabe steht fuenfmal "musterloesung":null
in der Nutzlast, danach stehen dort die Loesungen.

Ausserdem stellt das Skript seine Ausgangslage selbst her und laeuft
gegen den Port, der ueber LERNBAR_BASIS eingestellt ist - vorher war
3311 fest eingetragen und ein zweiter Lauf scheiterte daran, dass die
Kontrolle schon abgegeben war.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-09-11 19:36:29 +02:00
co-authored by Claude Opus 5
parent c61d5711e0
commit 6181ec4bd7
+46 -8
View File
@@ -11,7 +11,7 @@ import path from 'node:path';
* im sichtbaren Text. * im sichtbaren Text.
*/ */
const BASIS = 'http://localhost:3311'; const BASIS = process.env.LERNBAR_BASIS ?? 'http://localhost:3000';
const AZUBI = { email: 'jonas.berger@betrieb.de', pw: 'azubi-start-2026' }; const AZUBI = { email: 'jonas.berger@betrieb.de', pw: 'azubi-start-2026' };
const AUSBILDER = { email: 'fw@wildeflorian.de', pw: 'ausbilder-start' }; const AUSBILDER = { email: 'fw@wildeflorian.de', pw: 'ausbilder-start' };
@@ -26,6 +26,29 @@ const bruchstuecke = loesungen.map((l) => l.slice(40, 110)).filter((b) => b.leng
console.log(`${loesungen.length} Musterlösungen aus woche-01.yaml gelesen.`); console.log(`${loesungen.length} Musterlösungen aus woche-01.yaml gelesen.`);
console.log('Geprüft wird auf Bruchstücke wie:', JSON.stringify(bruchstuecke[0]?.slice(0, 60))); console.log('Geprüft wird auf Bruchstücke wie:', JSON.stringify(bruchstuecke[0]?.slice(0, 60)));
// --- Ausgangslage herstellen ----------------------------------------------
//
// Der entscheidende Fall ist „freigegeben, aber noch nicht abgegeben". Den gibt
// es nur einmal je Kontrolle. Damit die Prüfung wiederholbar bleibt, wird die
// Kontrolle „woche-01" vorher zurückgesetzt – das ist ein Testaufbau, kein Teil
// der Anwendung, und greift deshalb ausdrücklich nur auf die lokale
// Beispieldatenbank zu.
const { default: Datenbank } = await import('better-sqlite3');
const dbPfad = process.env.LERNBAR_DB ?? path.join(process.cwd(), 'daten', 'lernbar.sqlite');
if (fs.existsSync(dbPfad)) {
const db = new Datenbank(dbPfad);
const betroffen = db.prepare("select id from kontrolle where kontrolle_id = 'woche-01'").all();
for (const eintrag of betroffen) {
db.prepare('delete from kontroll_antwort where kontrolle_id = ?').run(eintrag.id);
db.prepare('delete from kontrolle where id = ?').run(eintrag.id);
}
db.close();
console.log(`Ausgangslage hergestellt: ${betroffen.length} frühere Bearbeitung(en) von woche-01 entfernt.`);
} else {
console.log(`Keine Datenbank unter ${dbPfad} – die Ausgangslage wird nicht zurückgesetzt.`);
}
const browser = await chromium.launch(); const browser = await chromium.launch();
const seite = await (await browser.newContext({ locale: 'de-DE' })).newPage(); const seite = await (await browser.newContext({ locale: 'de-DE' })).newPage();
@@ -41,13 +64,19 @@ async function abmelden() {
await seite.waitForURL(/anmelden/); await seite.waitForURL(/anmelden/);
} }
/** Sucht im rohen HTML **und** in der RSC-Nutzlast. */ /** Sucht im rohen HTML **und** in der Server-Nutzlast. */
async function lecksuche(beschriftung) { async function lecksuche(beschriftung) {
const html = await seite.content(); const html = await seite.content();
// Die Nutzlast steht in eingebetteten Skripten, die `self.__next_f` füllen.
// Dort **nachträglich** nachzusehen bringt nichts: Next leert das Feld, sobald
// es verarbeitet ist – es wäre immer leer, und die Prüfung liefe ins Leere,
// ohne dass es auffällt. Deshalb wird der Quelltext der Skripte gelesen.
const rsc = await seite.evaluate(() => const rsc = await seite.evaluate(() =>
// Next legt die Server-Nutzlast in self.__next_f ab – dort steht alles, [...document.querySelectorAll('script:not([src])')]
// was der Server an den Browser geschickt hat. .map((element) => element.textContent ?? '')
(globalThis.self?.__next_f ?? []).map((t) => (Array.isArray(t) ? String(t[1] ?? '') : '')).join(''), .filter((text) => text.includes('__next_f'))
.join(''),
); );
const treffer = bruchstuecke.filter((b) => html.includes(b) || rsc.includes(b)); const treffer = bruchstuecke.filter((b) => html.includes(b) || rsc.includes(b));
@@ -55,7 +84,7 @@ async function lecksuche(beschriftung) {
console.log( console.log(
` ${beschriftung.padEnd(34)} Leck: ${treffer.length === 0 ? 'nein' : `JA (${treffer.length})`}` + ` ${beschriftung.padEnd(34)} Leck: ${treffer.length === 0 ? 'nein' : `JA (${treffer.length})`}` +
` | RSC-Größe ${rsc.length} | "musterloesung":null ${nullFelder}x`, ` | Nutzlast ${rsc.length} Zeichen | "musterloesung":null ${nullFelder}x`,
); );
return treffer; return treffer;
} }
@@ -71,8 +100,17 @@ await abmelden();
await anmelden(AUSBILDER); await anmelden(AUSBILDER);
await seite.goto(`${BASIS}/kontrollen`); await seite.goto(`${BASIS}/kontrollen`);
await seite.waitForSelector('.kontrollzeile'); await seite.waitForSelector('.kontrollzeile');
await seite.locator('.kontrollzeile', { hasText: 'Woche 1' }).locator('button:has-text("Freigeben")').click(); const freigeben = seite
await seite.waitForTimeout(1500); .locator('.kontrollzeile', { hasText: 'Woche 1' })
.locator('button:has-text("Freigeben")');
if ((await freigeben.count()) > 0) {
await freigeben.click();
await seite.waitForTimeout(1500);
} else {
// Bei einem wiederholten Lauf ist sie schon freigegeben. Das ist kein
// Grund abzubrechen – der entscheidende Fall kommt erst danach.
console.log(' (war bereits freigegeben)');
}
console.log('\n3) Nach der Freigabe, vor der Abgabe – der entscheidende Fall'); console.log('\n3) Nach der Freigabe, vor der Abgabe – der entscheidende Fall');
await abmelden(); await abmelden();