Filipe, 22.09.2026: "ersetze die zentrale durch, Die IrrenAnstalt.
genau so geschrieben bitte. und alles was treff heißt oder wo treff
steht soll durch Rudel ersetzt werden bitte."
Die Schreibweise "IrrenAnstalt" mit grossem A in der Mitte ist so
gewollt. Das steht als Hinweis daneben, damit sie beim naechsten Mal
niemand "korrigiert".
WAS UMBENANNT WURDE: alles, was jemand LIEST -- Kachelnamen,
Ueberschriften, Markenzeilen, Saetze, Meldungen, Aufgabenvorlagen.
116 Zeilen in 42 Dateien.
WAS BLEIBT: Adressen (treff-regeln.html), Bezeichner (TREFF_ROLLEN),
Datenbankwerte (bereich = 'treff'), Abfrageteile (b=treff). Eine
Adresse umzubenennen bricht jedes Lesezeichen, und ein Datenbankwert
umzuschreiben waere eine Umstellung ohne Gegenwert. Dieselbe
Entscheidung wie heute frueh bei Dogi-Media, wo material.html auch
material.html geblieben ist.
DREI GRAMMATIKFEHLER IM EIGENEN ENTWURF, alle vor dem Ausliefern
gefunden -- ein blindes Ersetzen reicht hier nicht:
1. "der Treff" ist maennlich, "das Rudel" saechlich. Ohne Tabelle
waere ueberall "Der Rudel" gestanden. (Und "Treffer" waere zu
"Rudeler" geworden, "Treffen" zu "Rudelen" -- deshalb greift die
Regel nur bei Wortgrenze und nie vor einem Kleinbuchstaben.)
2. BINDESTRICH-ZUSAMMENSETZUNGEN. In "der Treff-Chat" gehoert der
Artikel zu "Chat", nicht zu "Treff". Der erste Durchlauf machte
daraus "das Rudel-Chat", "das Rudel-Kacheln" und "ein Rudel-Raum".
Jetzt greift die Artikelregel nur, wenn das Wort allein steht.
3. GESCHUETZTE LEERZEICHEN. In den Markenzeilen steht
`Der Treff`, damit die zwei Woerter nicht umbrechen. Das
Muster hat daran vorbeigegriffen: "Der Rudel", auf fuenf
Seiten. Jetzt wird das Trennzeichen mitgefasst und unveraendert
wieder eingesetzt.
Gefunden wurden alle drei, weil jede einzelne der 116 Zeilen vor dem
Schreiben als ALT/NEU ausgegeben und gelesen wurde -- und danach
gezielt nach falschen Artikeln gesucht ("den Rudel", "der Rudel",
"einen Rudel"). Uebrig blieben sechs Treffer, und die sind alle
richtig: "einen Rudel-Raum", "der Rudel-Chat", "den Rudel-Regeln" --
Zusammensetzungen, bei denen der Artikel zum letzten Wort gehoert.
UND ZWEI PRUEFUNGEN, die die Umbenennung nicht mitbekommen haetten:
`/Treff/.test(...)` und `/Treff/i.test(...)`. Ein regulaerer Ausdruck
ist keine Zeichenkette -- sie haetten ab sofort nach einem Wort
gesucht, das die Seite nicht mehr sagt, und waeren still rot geworden,
ohne dass etwas kaputt ist.
Geprueft, alle gruen: pruef-treff, pruef-treffchat,
pruef-deutsche-texte, pruef-uebernahme, pruef-crew-adresse,
pruef-willkommen, pruef-wege-nach-draussen, pruef-vorlagen,
pruef-start-ansicht, pruef-struktur.
Co-Authored-By: Claude Opus 5 <[email protected]>
408 lines
19 KiB
JavaScript
408 lines
19 KiB
JavaScript
/* =====================================================================
|
|
DRAUSSEN -- der Puls auf der Community-Seite (21.09.2026)
|
|
|
|
Filipe: "mach was anderes aus dieser seite oder mach es viel krass
|
|
und geiler ... erstell was was mich von den socken schmeisst. es
|
|
soll wirklich sogar ein wow effekt fuer die community ergeben."
|
|
|
|
---------------------------------------------------------------------
|
|
WAS HIER GEPRUEFT WIRD, UND WARUM AUSGERECHNET DAS
|
|
|
|
Die Seite beantwortet jetzt die Frage "laeuft er gerade?". Eine
|
|
Anzeige, die diese Frage beantwortet, hat genau eine Art, wirklich
|
|
Schaden anzurichten: Sie sagt "nicht live", ohne nachgesehen zu
|
|
haben. Wer das liest, macht die Seite zu -- und verpasst den Stream.
|
|
|
|
DAS IST KEIN GEDANKENSPIEL. Genau so macht es die oeffentliche
|
|
Seite: `streamplan.js` faengt jeden Fehler ab und setzt
|
|
`istLive = false`. Aus "wir konnten nicht fragen" wird "er sendet
|
|
nicht". Der Dienst liefert sogar `autoHealthy` mit, damit man das
|
|
unterscheiden kann -- gelesen wird es dort nicht.
|
|
|
|
DESHALB WIRD DIE STOERUNG HIER ABSICHTLICH HERGESTELLT. Vier Lagen,
|
|
vier Messungen, und die wichtigste ist die, in der nichts antwortet:
|
|
|
|
1. er sendet -> "JETZT live", Weg zum Stream
|
|
2. er sendet nicht -> Countdown
|
|
3. der Pruefer ist gestoert -> "konnten wir nicht nachsehen"
|
|
4. gar keine Antwort -> dasselbe, nicht "nicht live"
|
|
|
|
Die Lagen 3 und 4 tragen zusaetzlich eine GEGENPROBE: Es reicht
|
|
nicht, dass irgendein Text dasteht -- es darf ausdruecklich NICHT
|
|
"Gerade nicht live" dort stehen. Ohne diese Zeile waere die Pruefung
|
|
gruen, sobald ueberhaupt etwas angezeigt wird.
|
|
|
|
---------------------------------------------------------------------
|
|
UND DIE ZAHL, DIE ES ZWEIMAL GIBT
|
|
|
|
Die Sendezeit (20 Uhr) steht im Server und in `streamplan.js` der
|
|
oeffentlichen Seite. Zwei Zahlen, die dasselbe bedeuten, laufen
|
|
irgendwann auseinander -- beide sehen fuer sich plausibel aus, und
|
|
es faellt erst auf, wenn jemand vergeblich wartet. Abschnitt 4 haelt
|
|
sie gegeneinander, ohne Netz.
|
|
|
|
Aufrufen mit: node server/pruef-draussen.mjs
|
|
===================================================================== */
|
|
|
|
import { mkdtempSync, rmSync, readFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { request as httpAnfrage } from "node:http";
|
|
import { eigenerPort } from "./helfer-port.mjs";
|
|
|
|
const PORT = await eigenerPort(import.meta, "pruef-draussen");
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-draussen-"));
|
|
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
|
process.env.PORT = String(PORT);
|
|
process.env.SITE_ACCESS_SECRET = "lokaler-test";
|
|
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
|
|
|
|
const express = (await import("express")).default;
|
|
const ec = express.response.cookie;
|
|
express.response.cookie = function (n, w, o) {
|
|
return ec.call(this, n, w, { ...(o || {}), secure: false });
|
|
};
|
|
import { notbremse } from "./helfer-notbremse.mjs";
|
|
await import("./index.js");
|
|
notbremse(180_000, "pruef-draussen");
|
|
await new Promise((r) => setTimeout(r, 700));
|
|
|
|
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); });
|
|
|
|
let fehler = 0, geprueft = 0;
|
|
const melde = (t) => console.log(t);
|
|
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
|
|
|
const CREW = "crew.dogfather-universe.com";
|
|
const WURZEL = join(import.meta.dirname, "..");
|
|
|
|
function roh(pfad, host, kopf = {}, koerper = null) {
|
|
return new Promise((fertig, schief) => {
|
|
const a = httpAnfrage({
|
|
host: "127.0.0.1", port: PORT, path: pfad,
|
|
method: koerper ? "POST" : "GET",
|
|
headers: { Host: host, ...(koerper ? { "Content-Type": "application/json" } : {}), ...kopf },
|
|
}, (antwort) => {
|
|
let text = "";
|
|
antwort.on("data", (s) => { text += s; });
|
|
antwort.on("end", () => fertig({ code: antwort.statusCode, text, kopf: antwort.headers }));
|
|
});
|
|
a.on("error", schief);
|
|
if (koerper) a.write(JSON.stringify(koerper));
|
|
a.end();
|
|
});
|
|
}
|
|
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes } = await import("node:crypto");
|
|
{
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
for (const [name, rolle, code] of [["Mara", "gast", "CODE-GAST-0001"]]) {
|
|
const salz = randomBytes(16).toString("hex");
|
|
const hash = scryptSync(code, salz, 64,
|
|
{ N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
|
|
d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, aktiv, erstellt)"
|
|
+ " VALUES (?,?,?,?,?,1,?)")
|
|
.run(name, rolle, hash, salz, 32768, new Date().toISOString());
|
|
}
|
|
d.close();
|
|
}
|
|
|
|
async function anmelden(rolle, code, host) {
|
|
let a = await roh("/workspace/api/anmelden", host, {}, { rolle, code });
|
|
if (a.code === 400 && a.text.includes("alter_offen")) {
|
|
a = await roh("/workspace/api/anmelden", host, {}, { rolle, code, alter_ok: true });
|
|
}
|
|
if (a.code !== 200) throw new Error(`Anmeldung ${rolle}: ${a.code} ${a.text}`);
|
|
return [].concat(a.kopf["set-cookie"] || []).map((z) => z.split(";")[0]).join("; ");
|
|
}
|
|
const kGast = await anmelden("gast", "CODE-GAST-0001", CREW);
|
|
|
|
const { KANAELE } = await import("./workspace.js");
|
|
const { SENDEZEIT_STUNDE } = await import("./workspace-draussen.js");
|
|
|
|
/* =======================================================================
|
|
1. DER WEG ZU DEN KANAELEN
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 1. Die Kanaele kommen vom Server ===");
|
|
{
|
|
const zu = await roh("/workspace/api/draussen", CREW);
|
|
ok(zu.code === 401, `ohne Anmeldung verschlossen (${zu.code})`);
|
|
|
|
const a = await roh("/workspace/api/draussen", CREW, { Cookie: kGast });
|
|
ok(a.code === 200, `ein Mitglied bekommt sie (${a.code})`);
|
|
const d = JSON.parse(a.text || "{}");
|
|
const liste = d.kanaele || [];
|
|
|
|
ok(liste.length === KANAELE.length && liste.length > 0,
|
|
`es sind ${liste.length} Kanaele, so viele wie im Haus (${KANAELE.length})`);
|
|
|
|
/* NICHT ABGESCHRIEBEN, SONDERN VERGLICHEN. Wuerde hier ein Handle
|
|
als Text stehen, waere diese Pruefung genau so lange gruen wie der
|
|
Tippfehler, den sie finden soll -- am 21.09. war das bei zwei
|
|
anderen Pruefungen der Fall. */
|
|
const falsch = liste.filter((k, i) => k.handle !== KANAELE[i].handle);
|
|
ok(falsch.length === 0,
|
|
falsch.length ? `ABWEICHUNG: ${falsch.map((k) => k.handle).join(", ")}`
|
|
: `die Handles stimmen mit KANAELE ueberein (${liste.map((k) => k.handle).join(", ")})`);
|
|
|
|
const schlechteAdresse = liste.filter(
|
|
(k) => !k.adresse || !k.adresse.startsWith("https://www.tiktok.com/@")
|
|
|| !k.adresse.endsWith(k.handle.replace(/^@/, "")));
|
|
ok(schlechteAdresse.length === 0,
|
|
schlechteAdresse.length
|
|
? `ADRESSE PASST NICHT ZUM HANDLE: ${schlechteAdresse.map((k) => k.adresse).join(", ")}`
|
|
: "jede Adresse endet auf ihrem eigenen Handle");
|
|
|
|
const ohneSatz = liste.filter((k) => !k.satz || k.satz.length < 10);
|
|
ok(ohneSatz.length === 0,
|
|
ohneSatz.length ? `ohne Satz: ${ohneSatz.map((k) => k.name).join(", ")}`
|
|
: "jeder Kanal sagt in einem Satz, was es dort gibt");
|
|
|
|
/* Die Saetze fuer draussen sind ANDERE als die fuer das Team. Stuende
|
|
dort derselbe Text, waere das Feld ueberfluessig -- und jemand
|
|
wuerde es beim naechsten Kanal weglassen. */
|
|
const gleich = liste.filter((k, i) => k.satz === KANAELE[i].text);
|
|
ok(gleich.length === 0,
|
|
gleich.length ? `${gleich.length} Kanaele zeigen den internen Text`
|
|
: "und es ist nicht der interne Text aus der Aufgabenverteilung");
|
|
}
|
|
|
|
/* =======================================================================
|
|
2. DIE INHALTSRICHTLINIE MUSS DEN ABRUF ERLAUBEN
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 2. Darf die Seite den Live-Dienst ueberhaupt fragen? ===");
|
|
{
|
|
/* WARUM DAS EINE EIGENE PRUEFUNG IST: Verbietet `connect-src` den
|
|
Abruf, bricht `fetch` im Browser ab -- und zwar mit demselben
|
|
Fehler wie ein toter Server. Die Seite landet in "unklar" und
|
|
sieht dabei voellig in Ordnung aus. Der Unterschied steht nur in
|
|
der Browserkonsole, die niemand aufmacht. */
|
|
const a = await roh("/workspace/unsere-seiten.html", CREW, { Cookie: kGast });
|
|
const regel = String(a.kopf["content-security-policy"] || "");
|
|
const zeile = regel.split(";").map((t) => t.trim())
|
|
.find((t) => t.startsWith("connect-src")) || "";
|
|
ok(zeile.length > 0, `es gibt eine connect-src-Regel (${zeile.slice(0, 60)}…)`);
|
|
ok(zeile.includes("postfach.dogfather-universe.com"),
|
|
"und sie fuehrt postfach.dogfather-universe.com");
|
|
ok(!zeile.includes("*") || zeile.includes("postfach"),
|
|
"Gegenprobe: die Erlaubnis steht namentlich da, nicht als Sternchen");
|
|
}
|
|
|
|
/* =======================================================================
|
|
3. DIE SEITE SELBST
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 3. Was im Quelltext der Seite steht ===");
|
|
{
|
|
const html = readFileSync(join(WURZEL, "workspace/unsere-seiten.html"), "utf8");
|
|
|
|
ok(/id="puls"/.test(html), "der Puls steht auf der Seite");
|
|
ok(/data-lage="laedt"/.test(html),
|
|
"und beginnt in der Lage „laedt“ -- nicht in „nicht live“");
|
|
|
|
/* KEIN HANDLE IM QUELLTEXT DER KANALREIHE. Der Stream-Knopf traegt
|
|
bewusst einen: Er ist der eine feste Weg zum Hauptkanal und muss
|
|
auch dann funktionieren, wenn der Server-Abruf scheitert. Alles
|
|
andere kommt vom Server. */
|
|
const kanalReihe = (html.match(/id="kanaele-reihe"[^>]*>([\s\S]*?)<\/div>/) || [])[1] || "";
|
|
ok(kanalReihe.trim() === "",
|
|
"die Kanalreihe ist im Quelltext leer -- sie kommt vom Server");
|
|
|
|
const streamKnopf = (html.match(/id="puls-knopf"[\s\S]{0,200}?href="([^"]+)"/) || [])[1] || "";
|
|
const haupt = KANAELE[0].handle.replace(/^@/, "");
|
|
ok(streamKnopf.includes(haupt),
|
|
`der Weg zum Stream zeigt auf den Hauptkanal (${streamKnopf})`);
|
|
|
|
const css = readFileSync(join(WURZEL, "workspace/assets/css/unsere-seiten.css"), "utf8");
|
|
ok(/prefers-reduced-motion/.test(css),
|
|
"wer keine Bewegung moechte, bekommt keine");
|
|
ok(/\[data-lage="live"\][\s\S]{0,400}animation/.test(css),
|
|
"und die Bewegung haengt am Live-Zustand, laeuft also nicht dauernd");
|
|
}
|
|
|
|
/* =======================================================================
|
|
4. EINE ZAHL, ZWEI ORTE
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 4. Die Sendezeit steht nicht zweimal verschieden da ===");
|
|
{
|
|
const streamplan = readFileSync(join(WURZEL, "assets/js/streamplan.js"), "utf8");
|
|
const treffer = streamplan.match(/FIX_STUNDE\s*=\s*(\d+)/);
|
|
/* DRITTER AUSGANG: Findet sich die Zahl nicht, ist das kein "in
|
|
Ordnung" -- dann hat jemand `streamplan.js` umgebaut, und dieser
|
|
Vergleich misst ab sofort nichts mehr. Das muss auffallen. */
|
|
ok(!!treffer,
|
|
treffer ? `die oeffentliche Seite sendet um ${treffer[1]} Uhr`
|
|
: "FIX_STUNDE steht nicht mehr in assets/js/streamplan.js -- "
|
|
+ "dieser Vergleich kann nichts mehr messen");
|
|
ok(!!treffer && Number(treffer[1]) === SENDEZEIT_STUNDE,
|
|
`und das Rudel rechnet mit derselben Stunde (${SENDEZEIT_STUNDE})`);
|
|
ok(SENDEZEIT_STUNDE >= 0 && SENDEZEIT_STUNDE <= 23,
|
|
`Gegenprobe: die Stunde ist ueberhaupt eine Uhrzeit (${SENDEZEIT_STUNDE})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
5. DIE VIER LAGEN -- IM ECHTEN BROWSER
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 5. Was ein Mensch wirklich sieht ===");
|
|
{
|
|
let pw = null;
|
|
try {
|
|
pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
|
|
} catch { /* dritter Ausgang unten */ }
|
|
|
|
if (!pw) {
|
|
melde(" -- Playwright ist nicht da -- im Browser wurde NICHT nachgesehen.");
|
|
melde(" Das ist kein Befund. Die Abschnitte 1-4 gelten weiter.");
|
|
} else {
|
|
const browser = await pw.chromium.launch();
|
|
try {
|
|
const ctx = await browser.newContext({ viewport: { width: 412, height: 950 },
|
|
isMobile: true, hasTouch: true });
|
|
await ctx.addCookies(kGast.split("; ").filter(Boolean).map((z) => {
|
|
const [name, ...rest] = z.split("=");
|
|
return { name, value: rest.join("="), domain: "127.0.0.1", path: "/" };
|
|
}));
|
|
const seite = await ctx.newPage();
|
|
const kaputt = [];
|
|
seite.on("pageerror", (e) => kaputt.push(String(e).slice(0, 140)));
|
|
|
|
/** Stellt die Antwort des Live-Dienstes her und laedt neu.
|
|
* `antwort === null` heisst: gar keine Antwort -- so verhaelt
|
|
* sich ein Ausfall, und genau dieser Fall wird sonst nie
|
|
* geprueft, weil man ihn nicht herbeifuehren kann, ohne ihn
|
|
* herbeizufuehren. */
|
|
async function lage(antwort, event = null) {
|
|
await seite.unrouteAll?.().catch(() => {});
|
|
await seite.route("**/postfach.dogfather-universe.com/**", async (weg) => {
|
|
const u = weg.request().url();
|
|
if (u.includes("/special-event")) {
|
|
await weg.fulfill({ status: 200, contentType: "application/json",
|
|
body: JSON.stringify({ ok: true, event }) });
|
|
return;
|
|
}
|
|
if (antwort === null) { await weg.abort("failed"); return; }
|
|
await weg.fulfill({ status: 200, contentType: "application/json",
|
|
body: JSON.stringify(antwort) });
|
|
});
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/unsere-seiten.html`,
|
|
{ waitUntil: "domcontentloaded" });
|
|
await seite.waitForTimeout(1200);
|
|
return seite.evaluate(() => ({
|
|
lage: document.getElementById("puls")?.dataset.lage,
|
|
satz: document.getElementById("puls-lage")?.textContent?.trim(),
|
|
zahl: document.getElementById("puls-zahl")?.textContent?.trim(),
|
|
klein: document.getElementById("puls-klein")?.textContent?.trim(),
|
|
knopfDa: !document.getElementById("puls-knopf")?.hidden,
|
|
eventDa: !document.getElementById("event")?.hidden,
|
|
eventZahl: document.getElementById("event-zahl")?.textContent?.trim(),
|
|
kanaele: [...document.querySelectorAll(".kanal")].map((a) => ({
|
|
handle: a.querySelector(".kanal__handle")?.textContent?.trim(),
|
|
ziel: a.getAttribute("href"),
|
|
})),
|
|
}));
|
|
}
|
|
|
|
/* ---- 5a. Er sendet ------------------------------------------ */
|
|
melde("");
|
|
melde(" --- Er sendet ---");
|
|
{
|
|
const s = await lage({ ok: true, live: true, source: "automatisch",
|
|
title: "Casper raeumt das Wohnzimmer aus", autoHealthy: true });
|
|
ok(s.lage === "live", `die Lage ist „live“ (${s.lage})`);
|
|
ok(/JETZT live/i.test(s.satz || ""), `und es steht dort „${s.satz}“`);
|
|
ok((s.zahl || "").includes("Casper"),
|
|
`der Titel des Streams steht da („${s.zahl}“)`);
|
|
ok(s.knopfDa === true, "und ein Weg zum Stream ist da");
|
|
}
|
|
|
|
/* ---- 5b. Er sendet nicht ------------------------------------ */
|
|
melde("");
|
|
melde(" --- Er sendet nicht ---");
|
|
{
|
|
const s = await lage({ ok: true, live: false, source: "automatisch",
|
|
title: null, autoHealthy: true });
|
|
ok(s.lage === "wartet", `die Lage ist „wartet“ (${s.lage})`);
|
|
ok(/^\d{2}:\d{2}:\d{2}$/.test(s.zahl || ""),
|
|
`ein Countdown laeuft („${s.zahl}“)`);
|
|
ok((s.klein || "").includes(`${SENDEZEIT_STUNDE}:00`),
|
|
`und er nennt den naechsten Termin („${s.klein}“)`);
|
|
}
|
|
|
|
/* ---- 5c. Der Pruefer ist gestoert --------------------------- */
|
|
melde("");
|
|
melde(" --- Der Live-Pruefer meldet eine Stoerung ---");
|
|
{
|
|
const s = await lage({ ok: true, live: false, source: "automatisch",
|
|
title: null, autoHealthy: false });
|
|
ok(s.lage === "unklar", `die Lage ist „unklar“ (${s.lage})`);
|
|
/* DIE EIGENTLICHE GEGENPROBE. Ohne sie waere die Zeile darueber
|
|
auch dann gruen, wenn dort "Gerade nicht live" stuende -- und
|
|
genau das ist der Fehler, um den es geht. */
|
|
ok(!/Gerade nicht live/i.test(s.satz || ""),
|
|
`es steht NICHT „nicht live“ da, sondern „${s.satz}“`);
|
|
ok(/^\d{2}:\d{2}:\d{2}$/.test(s.zahl || ""),
|
|
`der Countdown laeuft trotzdem weiter („${s.zahl}“) -- er braucht kein Netz`);
|
|
}
|
|
|
|
/* ---- 5d. Gar keine Antwort ---------------------------------- */
|
|
melde("");
|
|
melde(" --- Der Dienst antwortet gar nicht ---");
|
|
{
|
|
const s = await lage(null);
|
|
ok(s.lage === "unklar", `die Lage ist „unklar“ (${s.lage})`);
|
|
ok(!/Gerade nicht live/i.test(s.satz || ""),
|
|
`auch hier steht NICHT „nicht live“ da, sondern „${s.satz}“`);
|
|
ok(/TikTok/i.test(s.klein || ""),
|
|
`und es steht da, was man stattdessen tun kann („${s.klein}“)`);
|
|
ok(s.knopfDa === true, "der Weg zu TikTok bleibt offen");
|
|
|
|
/* Die Kanaele haengen NICHT am Live-Dienst -- sie kommen vom
|
|
eigenen Server. Faellt der eine aus, muss der andere
|
|
weiterhin dastehen. */
|
|
ok(s.kanaele.length === KANAELE.length,
|
|
`die ${s.kanaele.length} Kanaele stehen trotzdem da`);
|
|
const falsch = s.kanaele.filter((k, i) => k.handle !== KANAELE[i].handle);
|
|
ok(falsch.length === 0,
|
|
falsch.length ? `falsche Handles: ${falsch.map((k) => k.handle).join(", ")}`
|
|
: `mit den richtigen Handles (${s.kanaele.map((k) => k.handle).join(", ")})`);
|
|
}
|
|
|
|
/* ---- 5e. Das besondere Event -------------------------------- */
|
|
melde("");
|
|
melde(" --- Das besondere Event ---");
|
|
{
|
|
const ohne = await lage({ ok: true, live: false, source: "automatisch",
|
|
autoHealthy: true }, null);
|
|
ok(ohne.eventDa === false,
|
|
"ohne Event ist der Kasten nicht da (kein „nichts geplant“)");
|
|
|
|
const inZweiTagen = new Date(Date.now() + 2 * 86400000 + 3600000).toISOString();
|
|
const mit = await lage({ ok: true, live: false, source: "automatisch",
|
|
autoHealthy: true }, { titel: { de: "Die grosse Halloween-Nacht" },
|
|
zieldatum: inZweiTagen, link: "https://dogfather-universe.com/streamplan.html" });
|
|
ok(mit.eventDa === true, "mit Event ist er da");
|
|
ok(/noch 2 Tage/.test(mit.eventZahl || ""),
|
|
`und zaehlt in Tagen herunter („${mit.eventZahl}“)`);
|
|
}
|
|
|
|
ok(kaputt.length === 0,
|
|
kaputt.length ? `Fehler in der Browserkonsole: ${kaputt.join(" | ")}`
|
|
: "keine Fehler in der Browserkonsole");
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
melde("");
|
|
melde(`${geprueft} Pruefungen, ${fehler} Fehler`);
|
|
melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ }
|
|
process.exit(fehler ? 1 : 0);
|