Files
dogfather-universe/server/pruef-zeitraum.mjs
T
DogFatherGitandClaude Opus 5 d77dd216c5 Ein Termin von-bis belegt jeden Tag dazwischen
Zwei Fehler, nicht einer.

Der sichtbare: Die Kachel stand nur am Anfangstag. Wer im
Monatsraster auf den Mittwoch sah, sah nichts -- obwohl die Aktion
von Montag bis Freitag lief.

Der unsichtbare, und der ist der schlimmere: Der SERVER suchte
Eintraege, deren ANFANG im sichtbaren Fenster liegt. Eine Aktion vom
28.09. bis zum 05.10. kam im Oktober deshalb ueberhaupt nicht an --
nicht "nur am ersten Tag markiert", sondern gar nicht da. Wer im
Oktober plante, sah eine freie Woche, die belegt war. Jetzt
entscheidet die UEBERSCHNEIDUNG, nicht der Anfang.

Gebaut wurde es in nachTag() -- der einzigen Stelle, an der
Eintraege auf Tage verteilt werden. Monat, Woche, Liste und
Zeitstrahl holen sich alle dort; vier Ansichten einzeln
nachzuruesten waeren vier Stellen, an denen die fuenfte vergessen
wird.

Das Ende wird abgeleitet, nicht gepflegt: aus event_ende ODER aus
Uhrzeit plus Dauer. Ein Live von 22:00 ueber vier Stunden endet um
02:00 am naechsten Tag -- das stand bisher nirgends, obwohl die
Zahlen da waren.

Der Tagesdialog filterte selbst auf den Anfangstag. Im Raster war
der Mittwoch markiert, tippte man ihn an, stand da "An diesem Tag
steht nichts." Jetzt fragt er dieselbe Stelle wie das Raster.

pruef-zeitraum.mjs: 16 Pruefungen, beide Fehler einzeln, mit drei
Gegenproben (vor dem Anfang, nach dem Ende, Punkttermin).
pruef-terminregel.mjs weiterhin 35/0.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-20 19:20:18 +02:00

219 lines
9.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =====================================================================
pruef-zeitraum.mjs — Ein Termin von–bis belegt jeden Tag dazwischen
Filipe, 20.09.2026: „wenn ich termine eintrage mit von bis, will ich
dass die ganze zeit markiert ist und nicht nur der anfang."
---------------------------------------------------------------------
ZWEI FEHLER, NICHT EINER
Der sichtbare: Die Kachel stand nur am Anfangstag. Wer im
Monatsraster auf den Mittwoch sah, sah nichts — obwohl die Aktion
von Montag bis Freitag lief.
Der unsichtbare, und der ist der schlimmere: Der SERVER suchte
Einträge, deren ANFANG im sichtbaren Fenster liegt. Eine Aktion vom
28.09. bis zum 05.10. kam im Oktober deshalb überhaupt nicht an —
nicht „nur am ersten Tag markiert", sondern gar nicht da. Wer im
Oktober plante, sah eine freie Woche, die belegt war.
Deshalb wird hier BEIDES gemessen, und der zweite Fall zuerst.
AUFRUF: node server/pruef-zeitraum.mjs
===================================================================== */
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { portMussFreiSein } from "./helfer-port.mjs";
import { notbremse } from "./helfer-notbremse.mjs";
const PORT = await portMussFreiSein(4437, "pruef-zeitraum");
const ordner = mkdtempSync(join(tmpdir(), "ws-zeitraum-"));
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 }); };
await import("./index.js");
notbremse(230_000, "pruef-zeitraum");
await new Promise((r) => setTimeout(r, 900));
const BASIS = `http://127.0.0.1:${PORT}`;
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); });
process.on("unhandledRejection", (f) => { console.error("ABSTURZ (Versprechen):", f); process.exit(7); });
let fehler = 0, geprueft = 0;
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
console.log("\n=== Termine über mehrere Tage ===\n");
/* ---- Daten anlegen ------------------------------------------------
RELATIV ZU HEUTE, nie mit einem festen Datum: Ein fest eingetragener
Termin wird vom Kalender eingeholt, und dann schlägt die Prüfung
fehl, ohne dass etwas kaputt ist (Hausregel, 06.09.2026). */
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes } = await import("node:crypto");
const d = new DatabaseSync(process.env.WORKSPACE_DB);
const salt = randomBytes(16).toString("hex");
const hash = scryptSync("CODE-DOGI-0001", salt, 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("Filipe", "admin", hash, salt, 32768, new Date().toISOString());
const tagPlus = (n) => {
const x = new Date();
x.setHours(12, 0, 0, 0);
x.setDate(x.getDate() + n);
return x.toISOString().slice(0, 10);
};
/* Eine Aktion über fünf Tage, die GESTERN angefangen hat — genau der
Fall, den der Server vorher verschluckt hat. */
const LAUF_START = tagPlus(-1), LAUF_ENDE = tagPlus(3);
/* Und ein Punkttermin als Gegenprobe. */
const EINZEL = tagPlus(5);
const spalten = new Set(d.prepare("PRAGMA table_info(eintraege)").all().map((z) => z.name));
ok(spalten.has("event_ende") && spalten.has("geplant"),
"die Tabelle kennt Anfang und Ende (event_ende, geplant)");
const einf = (titel, von, bis) => d.prepare(
"INSERT INTO eintraege (bereich, art, titel, text, status, dringlichkeit,"
+ " datum, geplant, event_ende, erstellt_von, erstellt)"
+ " VALUES (?,?,?,?,?,?,?,?,?,?,?)")
.run("live", "idee", titel, "", "offen", "mittel", von, von, bis, 1,
new Date().toISOString());
einf("Wochenaktion quer", LAUF_START, LAUF_ENDE);
einf("Nur ein Tag", EINZEL, null);
d.close();
const an = await fetch(BASIS + "/workspace/api/anmelden", {
method: "POST", headers: { "Content-Type": "application/json", Origin: BASIS },
body: JSON.stringify({ rolle: "admin", code: "CODE-DOGI-0001" }),
});
const keks = (an.headers.getSetCookie?.() || []).map((z) => z.split(";")[0]).join("; ");
ok(an.ok && !!keks, "angemeldet");
/* ---- 1. DER SERVER: Überschneidung statt Anfang ------------------- */
const hole = async (von, tage) => {
const a = await fetch(`${BASIS}/workspace/api/termine?von=${von}&tage=${tage}`,
{ headers: { Cookie: keks } });
return a.ok ? a.json() : null;
};
const titel = (j) => (j?.brettsachen || []).map((b) => b.titel);
/* Ein Fenster, das MITTEN im Zeitraum liegt — der Anfang ist davor. */
const mitten = await hole(tagPlus(1), 2);
ok(titel(mitten).includes("Wochenaktion quer"),
"ein laufender Zeitraum kommt mit, auch wenn sein Anfang vor dem Fenster liegt");
console.log(` Fenster ${tagPlus(1)} + 2 Tage, Aktion läuft ${LAUF_START}–${LAUF_ENDE}`);
/* GEGENPROBE: Ein Fenster NACH dem Ende darf ihn nicht bringen. Ohne
diese Probe würde auch ein Server bestehen, der einfach alles
schickt — und dann stünde jeder je getippte Zettel im Kalender. */
const danach = await hole(tagPlus(10), 5);
ok(!titel(danach).includes("Wochenaktion quer"),
"Gegenprobe: nach dem Ende kommt er nicht mehr — es wird nicht einfach alles geschickt");
const davor = await hole(tagPlus(-30), 20);
ok(!titel(davor).includes("Wochenaktion quer"),
"Gegenprobe: und vor dem Anfang auch nicht");
/* Der Punkttermin muss sich genauso verhalten wie vorher. */
const beimEinzel = await hole(EINZEL, 1);
ok(titel(beimEinzel).includes("Nur ein Tag"),
"der Punkttermin kommt weiterhin an seinem Tag");
/* ---- 2. DER BILDSCHIRM: jeder Tag ist markiert -------------------- */
const pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
const browser = await pw.chromium.launch();
try {
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1000 } });
const s = await ctx.newPage();
await s.goto(BASIS + "/workspace/", { waitUntil: "networkidle" });
await s.click('.rolle[data-rolle="admin"]');
await s.fill("#code", "CODE-DOGI-0001");
await s.click("button[type=submit]");
await s.waitForURL("**/start.html", { timeout: 25000 });
await s.goto(BASIS + "/workspace/kalender.html", { waitUntil: "networkidle" });
await s.waitForTimeout(1800);
/* Wie viele Tageszellen tragen die Aktion? */
const lage = await s.evaluate(() => {
const zellen = [...document.querySelectorAll(".k-tag")];
const treffer = [];
for (const z of zellen) {
for (const p of z.querySelectorAll(".k-pille")) {
if (/Wochenaktion quer/.test(p.textContent)) {
treffer.push({
zahl: z.querySelector(".k-tag__zahl")?.textContent || "?",
spanne: p.dataset.spanne || null,
text: p.textContent.replace(/\s+/g, " ").trim(),
breit: Math.round(p.getBoundingClientRect().width),
});
}
}
}
const einzel = [...document.querySelectorAll(".k-pille")]
.filter((p) => /Nur ein Tag/.test(p.textContent));
return { zellen: zellen.length, treffer,
einzel: einzel.length, einzelSpanne: einzel[0]?.dataset.spanne ?? null };
});
ok(lage.zellen > 0, `das Monatsraster steht (${lage.zellen} Tageszellen)`);
ok(lage.treffer.length === 5,
`die Aktion ist an allen fünf Tagen markiert (${lage.treffer.length})`);
console.log(" " + lage.treffer.map((t) => `${t.zahl}.[${t.spanne}]`).join(" "));
const pos = lage.treffer.map((t) => t.spanne);
ok(pos[0] === "start" && pos.at(-1) === "ende",
`Anfang und Ende sind als solche erkennbar (${pos[0]} … ${pos.at(-1)})`);
ok(pos.slice(1, -1).every((p) => p === "mitte"),
"dazwischen steht überall 'mitte' — die Form sagt, dass es weitergeht");
/* Auf dem ersten Tag steht die Uhrzeit-Spalte, danach die Lage. Eine
Uhrzeit am Mittwoch zu wiederholen behauptet, es ginge am Mittwoch
wieder los. */
ok(/Tag 2\/5/.test(lage.treffer[1]?.text || ""),
`der zweite Tag sagt, der wievielte er ist ("${lage.treffer[1]?.text.slice(0, 34)}")`);
ok(!/Tag \d+\/\d+/.test(lage.treffer[0]?.text || ""),
"am ersten Tag steht das NICHT — dort gehört die Startzeit hin");
/* GEGENPROBE AM BILDSCHIRM: Der Punkttermin darf sich nicht
angesteckt haben. Ohne sie bestünde auch eine Fassung, die jede
Kachel über den halben Monat zieht. */
ok(lage.einzel === 1,
`Gegenprobe: der Punkttermin steht an genau einem Tag (${lage.einzel})`);
ok(lage.einzelSpanne === null,
"Gegenprobe: und trägt keine Spannen-Kennzeichnung");
/* ---- 3. Der Tagesdialog widerspricht dem Raster nicht ----------- */
/* Vorher filterte er selbst auf den Anfangstag: Im Raster war der
Mittwoch markiert, tippte man ihn an, stand da „An diesem Tag
steht nichts." */
const mitteTag = lage.treffer[2]?.zahl;
await s.evaluate((zahl) => {
for (const z of document.querySelectorAll(".k-tag")) {
if (z.querySelector(".k-tag__zahl")?.textContent === zahl) { z.click(); return; }
}
}, mitteTag);
await s.waitForTimeout(700);
const dialog = await s.evaluate(() => {
const dl = document.querySelector("#tagdialog[open]");
return dl ? dl.textContent.replace(/\s+/g, " ").trim() : null;
});
ok(!!dialog && /Wochenaktion quer/.test(dialog),
`der Tagesdialog am ${mitteTag}. zeigt die Aktion auch`);
ok(!/An diesem Tag steht nichts/.test(dialog || ""),
" und sagt nicht mehr 'An diesem Tag steht nichts'");
await ctx.close();
} finally {
await browser.close();
}
console.log(`\n${fehler ? fehler + " FEHLER" : "Alles in Ordnung"} — ${geprueft} Prüfungen\n`);
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
process.exit(fehler ? 1 : 0);