Auftrag: "einen grossen Check machen, ob alles klappt, auf dem Handy und
PC." Dafuer ein neuer Rundgang (pruef-grosscheck.mjs), der stumpf ueber
alles geht: 19 Workspace-Seiten mal 4 Rollen mal 2 Bildschirmgroessen
plus 33 oeffentliche Seiten, zweimal. 208 Seiten, 50 774 Elemente.
Solche Rundgaenge finden andere Fehler als gezielte Pruefungen: nicht
den falsch gerechneten Wert, sondern die Seite, die bei genau einer
Rolle ueberlaeuft.
=== DER WICHTIGSTE FUND: "heute" war in UTC gerechnet ===
Der Check lief um 01:10 Uhr. Ortszeit war der 2. September, in UTC noch
der 1. -- und in diesem Fenster rechnete die Anwendung an ZWOELF Stellen
"heute" als toISOString(), also in UTC. Server und Benutzer stehen beide
auf Europe/Berlin.
Was das im Alltag bedeutete, jede Nacht zwischen 0 und 2 Uhr:
* eine heute faellige Aufgabe galt noch nicht als faellig
* eine um Mitternacht ueberfaellig gewordene erschien erst um 2 Uhr
* der Filter "Heute faellig" zeigte den Vortag
* Datumsfelder schlugen gestern vor
* der Kalender begann seine Vorgabe einen Tag zu frueh
Also genau dann, wenn nach einem Stream gearbeitet wird.
kalender.js machte es die ganze Zeit RICHTIG -- samt Begruendung, warum
die ARITHMETIK trotzdem in UTC laufen muss (UTC-Mittag ueberlebt die
Zeitumstellung; wer lokal rechnet, verliert am 27. Oktober einen Tag).
Diese Trennung gilt jetzt ueberall, aus je einer Quelle:
RECHNEN mit Datumsangaben -> UTC-Mittag, unveraendert
WELCHER TAG IST HEUTE -> Ortszeit (heuteLokal/tagLokal im Server,
window.heuteLokal in kopf.js)
WIE ES AUFFIEL, und das ist die eigentliche Lehre: Zuerst schlugen zwei
Pruefungen fehl -- und die Ursache lag in IHNEN, sie rechneten selbst in
UTC (36 Stellen in 17 Dateien). Nach deren Reparatur schlugen sie WIEDER
fehl, und erst da zeigten sie auf die Anwendung. Wer beim ersten Mal
aufgehoert haette ("ist ja nur die Pruefung"), haette den echten Fehler
nie gesehen.
Nachtrag desselben Musters: pruef-uebersicht legte den Termin weiterhin
in UTC an, waehrend die Erwartung schon auf Ortszeit stand. Wer eine
Datumsrechnung umstellt, muss BEIDE Seiten umstellen -- die, die
schreibt, und die, die prueft.
=== VIER FEHLER AUF HANDY UND PC ===
1. Ein langer Creator-Name ("SpongBobSchwammKopf") schob die Startseite
auf dem Handy um 48 Pixel aus dem Bild -- ein Wort ohne Trennstelle,
und die Seite liess sich seitlich wegschieben. Trifft echte Namen:
Creator heissen selten "Tim".
2. Die klebende Speicherleiste verdeckte auf dem Handy ein Textfeld.
Beim Tippen sieht man die eigene Zeile nicht. Behoben mit
scroll-margin-bottom (WCAG 2.2, 2.4.11 "Focus Not Obscured").
3./4. Zwei Beschriftungen waren mit 9,6 px (Uebersicht: "ueberfaellig",
"dringend", "offen") und 9,9 px (Kalender: "heute") zu klein. Fuers
Handy gab es laengst eine Ausnahme -- nur der Rechner war vergessen
worden. Ausgerechnet die Woerter, die den Zahlen ihre Bedeutung geben.
=== WAS KEINE FEHLER WAREN ===
Der erste Durchgang meldete 19 Maengel, die keine waren. Alle einzeln im
Quelltext nachgeprueft und dem Rundgang beigebracht:
* Kacheln und Kopfzeilen "abgeschnitten" -- das Wasserzeichen ragt
ABSICHTLICH ueber den Rand (steht so im Quelltext)
* "verdeckt: wahl2__echt" -- das echte <select> liegt absichtlich
unsichtbar unter seinem Knopf
* "zurueck-knopf__text abgeschnitten" -- das uebliche Muster fuer
"nur fuer Vorleseprogramme"
* drei "zu kleine" Verweise -- WCAG 2.5.8 nimmt Verweise im Fliesstext
AUSDRUECKLICH aus. Eine Pruefung, die ihre eigene Messlatte nicht
kennt, misst nichts.
Beim vierten Punkt haette ich fast an der falschen Stelle repariert.
Und statt die Sticky-Meldung abzuschalten (dann faende sie auch echte
Ueberdeckungen nie mehr), wurde sie GENAUER: Ueberdeckt etwas Klebendes
ein Eingabefeld, ist das nur in Ordnung, wenn das Feld genug
scroll-margin-bottom hat, um darunter hervorzukommen. Aus einer vagen
Meldung wird eine pruefbare Zusage.
Vier Gegenproben belegen, dass der Rundgang ueberhaupt etwas finden
kann: ein zu breites Element, ein winziger Knopf, ein winziger Verweis
AUSSERHALB eines Satzes und ein wirklich abgeschnittenes Wort werden
alle gemeldet. Ohne diesen Nachweis waere "alles in Ordnung" wertlos.
Co-Authored-By: Claude Opus 5 <[email protected]>
305 lines
15 KiB
JavaScript
305 lines
15 KiB
JavaScript
/* ===================================================================
|
||
Pruefung der Content-Planung: Strecke, Saeulen, Kennzahlen und das
|
||
Ueberfuehren in Aufgabe / Termin / LIVE-Thema.
|
||
=================================================================== */
|
||
import { mkdtempSync, rmSync } from "node:fs";
|
||
import { tmpdir } from "node:os";
|
||
import { join } from "node:path";
|
||
|
||
const ordner = mkdtempSync(join(tmpdir(), "ws-content-"));
|
||
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
||
process.env.PORT = "4184";
|
||
process.env.SITE_ACCESS_SECRET = "lokaler-test";
|
||
process.env.SITE_ACCESS_CODE_DOGI = "DOGI-TEST-1";
|
||
process.env.SITE_ACCESS_CODE_VANVAN = "VANVAN-TEST-1";
|
||
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");
|
||
await new Promise((r) => setTimeout(r, 700));
|
||
const BASIS = "http://127.0.0.1:4184";
|
||
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 200_000).unref?.();
|
||
|
||
/* ORTSZEIT, nicht UTC. Die Anwendung rechnet "heute" nach der Uhr des
|
||
Benutzers; toISOString() liefert dagegen UTC und damit zwischen
|
||
Mitternacht und 2 Uhr den VORTAG. In diesem Fenster schlug diese
|
||
Pruefung fehl, ohne dass etwas kaputt war. */
|
||
const tagText = (d = new Date()) =>
|
||
`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`
|
||
+ `-${String(d.getDate()).padStart(2, "0")}`;
|
||
|
||
let fehler = 0;
|
||
const ok = (b, t) => { console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
||
|
||
await fetch(BASIS + "/workspace/api/anmelden", {
|
||
method: "POST", headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ rolle: "admin", code: "x" }) }).catch(() => {});
|
||
|
||
const { DatabaseSync } = await import("node:sqlite");
|
||
const { scryptSync, randomBytes } = await import("node:crypto");
|
||
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
||
const jetzt = new Date().toISOString();
|
||
const heute = tagText();
|
||
function anlegen(name, rolle, code) {
|
||
const salt = randomBytes(16).toString("hex");
|
||
const hash = scryptSync(code, 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(name, rolle, hash, salt, 32768, jetzt);
|
||
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
||
}
|
||
const idDogi = anlegen("Dogfather", "admin", "CODE-DOGI-0001");
|
||
const idScout = anlegen("Ein Scout", "scout", "CODE-SCOU-0001");
|
||
const idAnna = anlegen("Anna", "creator", "CODE-ANNA-0001");
|
||
const idBen = anlegen("Ben", "creator", "CODE-BENN-0001");
|
||
d.prepare("INSERT INTO betreuung (creator_id, betreuer_id, seit) VALUES (?,?,?)").run(idAnna, idScout, jetzt);
|
||
|
||
/* Ein Alteintrag mit der ALTEN Art -- die Umstellung muss ihn retten,
|
||
sonst faellt er aus jeder Spalte heraus und ist unsichtbar. */
|
||
d.prepare("INSERT INTO eintraege (bereich, art, titel, datum, status, creator_id, erstellt, erstellt_von) VALUES ('content','produktion','Alter Eintrag',?,'offen',?,?,?)")
|
||
.run(heute, idAnna, jetzt, idDogi);
|
||
d.close();
|
||
|
||
/* Die Umstellung laeuft NUR beim Oeffnen der Datenbank -- und die war
|
||
beim ersten Anmeldeversuch oben laengst offen. Der Alteintrag entstand
|
||
also DANACH und waere hier nie umgestellt worden. Genau daran ist die
|
||
erste Fassung dieser Pruefung gescheitert, und sie haette den Eindruck
|
||
erweckt, die Umstellung funktioniere nicht.
|
||
|
||
Deshalb wird jetzt der echte Weg geprueft: ein zweiter Serverstart auf
|
||
derselben Datei, so wie es morgen frueh auf dem Server passiert. */
|
||
{
|
||
const { spawnSync } = await import("node:child_process");
|
||
const zweiter = spawnSync(process.execPath, ["-e", `
|
||
process.env.WORKSPACE_DB = ${JSON.stringify(process.env.WORKSPACE_DB)};
|
||
process.env.PORT = "4194";
|
||
process.env.SITE_ACCESS_SECRET = "lokaler-test";
|
||
process.env.SITE_ACCESS_CODE_DOGI = "X";
|
||
process.env.SITE_ACCESS_CODE_VANVAN = "Y";
|
||
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
|
||
const { db } = await import(${JSON.stringify(
|
||
"file:///" + join(import.meta.dirname, "workspace.js").replace(/\\\\/g, "/"))});
|
||
db(); // oeffnet und stellt um
|
||
process.exit(0);
|
||
`], { encoding: "utf8", timeout: 30000 });
|
||
const ausgabe = (zweiter.stdout || "") + (zweiter.stderr || "");
|
||
console.log(" Zweiter Start:", ausgabe.trim().split("\n").filter((z) => /umgestellt|Spalte/.test(z)).join(" | ") || "(still)");
|
||
}
|
||
|
||
const anmelden = async (rolle, code) => {
|
||
const a = await fetch(BASIS + "/workspace/api/anmelden", {
|
||
method: "POST", headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ rolle, code }), redirect: "manual" });
|
||
return (a.headers.getSetCookie?.() || []).map((c) => c.split(";")[0]).join("; ");
|
||
};
|
||
const ruf = (pfad, keks, opt = {}) => fetch(BASIS + pfad, {
|
||
...opt, headers: { cookie: keks, "Content-Type": "application/json", ...(opt.headers || {}) },
|
||
});
|
||
const jsn = async (a) => { try { return await a.json(); } catch { return {}; } };
|
||
|
||
const kDogi = await anmelden("admin", "CODE-DOGI-0001");
|
||
const kScout = await anmelden("scout", "CODE-SCOU-0001");
|
||
const kCreator = await anmelden("creator", "CODE-ANNA-0001");
|
||
|
||
console.log("\n1) Die Umstellung der alten Arten");
|
||
{
|
||
const j = await jsn(await ruf("/workspace/api/bereich/content", kDogi));
|
||
ok(Object.keys(j.einstellung.arten).length === 5, `fünf Stufen (${Object.keys(j.einstellung.arten).join(", ")})`);
|
||
const alt = j.eintraege.find((e) => e.titel === "Alter Eintrag");
|
||
ok(alt?.art === "gedreht", `der Alteintrag steht jetzt auf 'gedreht' (${alt?.art})`);
|
||
ok(!j.eintraege.some((e) => e.art === "produktion"), "kein Eintrag hängt noch in der alten Art fest");
|
||
}
|
||
|
||
console.log("\n2) Säulen anlegen");
|
||
let saeuleA, saeuleB;
|
||
{
|
||
for (const [name, ziel] of [["Tutorials", 70], ["Community", 20], ["Eigenwerbung", 10]]) {
|
||
const a = await ruf("/workspace/api/content/saeulen", kDogi, {
|
||
method: "POST", body: JSON.stringify({ name, ziel, creator_id: idAnna }) });
|
||
const j = await jsn(a);
|
||
if (name === "Tutorials") saeuleA = j.id;
|
||
if (name === "Community") saeuleB = j.id;
|
||
ok(a.ok, `„${name}" angelegt, Farbplatz ${j.slot} (${a.status})`);
|
||
}
|
||
const doppelt = await ruf("/workspace/api/content/saeulen", kDogi, {
|
||
method: "POST", body: JSON.stringify({ name: "tutorials", creator_id: idAnna }) });
|
||
ok(doppelt.status === 400, `derselbe Name wird abgelehnt (${doppelt.status})`);
|
||
|
||
for (const n of ["Vier", "Fünf"]) {
|
||
await ruf("/workspace/api/content/saeulen", kDogi, {
|
||
method: "POST", body: JSON.stringify({ name: n, creator_id: idAnna }) });
|
||
}
|
||
const sechste = await ruf("/workspace/api/content/saeulen", kDogi, {
|
||
method: "POST", body: JSON.stringify({ name: "Sechs", creator_id: idAnna }) });
|
||
ok(sechste.status === 400, `die sechste Säule wird abgelehnt (${sechste.status})`);
|
||
console.log(" ->", (await jsn(sechste)).fehler);
|
||
}
|
||
|
||
console.log("\n3) Eine Säule gehört genau einem Creator");
|
||
{
|
||
/* Ben eine Saeule anhaengen, die Anna gehoert -- muss scheitern. */
|
||
const a = await ruf("/workspace/api/bereich/content", kDogi, {
|
||
method: "POST",
|
||
body: JSON.stringify({ art: "idee", titel: "Für Ben", datum: heute,
|
||
creator_id: idBen, saeule_id: saeuleA }),
|
||
});
|
||
ok(a.status === 400, `fremde Säule beim Anlegen abgelehnt (${a.status})`);
|
||
console.log(" ->", (await jsn(a)).fehler);
|
||
|
||
const eigen = await ruf("/workspace/api/bereich/content", kDogi, {
|
||
method: "POST",
|
||
body: JSON.stringify({ art: "idee", titel: "Für Ben, ohne Säule", datum: heute, creator_id: idBen }) });
|
||
const bId = (await jsn(eigen)).id;
|
||
const nach = await ruf(`/workspace/api/bereich/content/${bId}`, kDogi, {
|
||
method: "PATCH", body: JSON.stringify({ saeule_id: saeuleA }) });
|
||
ok(nach.status === 400, `und auch beim nachträglichen Anhängen (${nach.status})`);
|
||
}
|
||
|
||
console.log("\n4) Videos durch die Strecke schieben");
|
||
const ids = {};
|
||
{
|
||
const felder = [
|
||
["idee", "Erste Idee", null],
|
||
["skript", "Mit Hook", "Ich hab 4 Monate gebraucht, um das zu kapieren."],
|
||
["gedreht", "Abgedreht", "Das wusste ich vorher auch nicht."],
|
||
["fertig", "Fertig geschnitten", "Guck bis zum Schluss."],
|
||
];
|
||
for (const [art, titel, hook] of felder) {
|
||
const a = await ruf("/workspace/api/bereich/content", kDogi, {
|
||
method: "POST",
|
||
body: JSON.stringify({ art, titel, hook, datum: heute, creator_id: idAnna,
|
||
saeule_id: saeuleA, format: "Tutorial",
|
||
geplant: art === "fertig" ? tagText(new Date(Date.now() + 2 * 86400_000)) : null }),
|
||
});
|
||
ids[art] = (await jsn(a)).id;
|
||
ok(a.ok, `„${titel}" auf Stufe ${art} (${a.status})`);
|
||
}
|
||
/* Etwas Veroeffentlichtes fuer die Auswertung -- eines je Saeule. */
|
||
for (const [s, n] of [[saeuleA, 6], [saeuleB, 2]]) {
|
||
for (let i = 0; i < n; i++) {
|
||
await ruf("/workspace/api/bereich/content", kDogi, {
|
||
method: "POST",
|
||
body: JSON.stringify({ art: "veroeffentlicht", titel: `Draußen ${s}-${i}`,
|
||
datum: heute, creator_id: idAnna, saeule_id: s }),
|
||
});
|
||
}
|
||
}
|
||
const weiter = await ruf(`/workspace/api/bereich/content/${ids.idee}`, kDogi, {
|
||
method: "PATCH", body: JSON.stringify({ art: "skript" }) });
|
||
ok(weiter.ok, `eine Stufe weiter geht (${weiter.status})`);
|
||
await ruf(`/workspace/api/bereich/content/${ids.idee}`, kDogi, {
|
||
method: "PATCH", body: JSON.stringify({ art: "idee" }) });
|
||
}
|
||
|
||
console.log("\n5) Die Kennzahlen");
|
||
{
|
||
const z = await jsn(await ruf(`/workspace/api/content/kennzahlen?creator=${idAnna}`, kDogi));
|
||
console.log(" ->", JSON.stringify({ vorrat: z.vorrat, tage: z.reichweiteTage,
|
||
v30: z.veroeffentlicht30, ohneHook: z.ohneHook, ueberfaellig: z.ueberfaellig }));
|
||
/* Vorrat = gedreht + fertig. Der Alteintrag steht auch auf 'gedreht'. */
|
||
ok(z.vorrat === 3, `Vorrat zählt nur Gedrehtes und Fertiges (${z.vorrat})`);
|
||
ok(z.stufen.idee === 1, `eine Idee (${z.stufen.idee})`);
|
||
ok(z.veroeffentlicht30 === 8, `8 in 30 Tagen veröffentlicht (${z.veroeffentlicht30})`);
|
||
ok(z.reichweiteTage !== null && z.reichweiteTage >= 10,
|
||
`daraus wird eine Reichweite in Tagen (${z.reichweiteTage})`);
|
||
/* Der Alteintrag hat keinen Hook und steht auf 'gedreht'. */
|
||
ok(z.ohneHook === 1, `ein Video in Arbeit ohne Hook (${z.ohneHook})`);
|
||
ok(z.ueberfaellig === 0, `nichts überfällig (${z.ueberfaellig})`);
|
||
|
||
const balance = z.balance.filter((b) => b.n > 0);
|
||
ok(balance.length === 2, `zwei Säulen mit Veröffentlichtem (${balance.length})`);
|
||
const tut = z.balance.find((b) => b.name === "Tutorials");
|
||
ok(tut?.n === 6, `Tutorials: 6 (${tut?.n})`);
|
||
ok(tut?.ziel === 70, `mit Ziel 70 % (${tut?.ziel})`);
|
||
}
|
||
|
||
console.log("\n6) Überfälliges wird erkannt");
|
||
{
|
||
await ruf("/workspace/api/bereich/content", kDogi, {
|
||
method: "POST",
|
||
body: JSON.stringify({ art: "fertig", titel: "Hätte längst raus sollen", hook: "x",
|
||
datum: heute, creator_id: idAnna, geplant: "2020-01-01" }) });
|
||
const z = await jsn(await ruf(`/workspace/api/content/kennzahlen?creator=${idAnna}`, kDogi));
|
||
ok(z.ueberfaellig === 1, `jetzt eines überfällig (${z.ueberfaellig})`);
|
||
}
|
||
|
||
console.log("\n7) Überführen – der Kern aus dem Konzept");
|
||
{
|
||
const a = await ruf(`/workspace/api/content/${ids.fertig}/uebernehmen`, kDogi, {
|
||
method: "POST", body: JSON.stringify({ ziel: "aufgabe" }) });
|
||
const j = await jsn(a);
|
||
ok(a.ok && j.art === "Aufgabe", `als Aufgabe übernommen (${a.status})`);
|
||
const auf = await jsn(await ruf("/workspace/api/aufgaben", kDogi));
|
||
const neue = (auf.aufgaben || []).find((x) => x.id === j.id);
|
||
ok(!!neue, "die Aufgabe steht im Brett");
|
||
ok(neue?.creator_id === idAnna, "und hängt am richtigen Creator");
|
||
|
||
const t = await ruf(`/workspace/api/content/${ids.fertig}/uebernehmen`, kDogi, {
|
||
method: "POST", body: JSON.stringify({ ziel: "termin" }) });
|
||
ok(t.ok, `als Termin übernommen (${t.status})`);
|
||
|
||
const l = await ruf(`/workspace/api/content/${ids.fertig}/uebernehmen`, kDogi, {
|
||
method: "POST", body: JSON.stringify({ ziel: "live" }) });
|
||
ok(l.ok, `als LIVE-Thema übernommen (${l.status})`);
|
||
const live = await jsn(await ruf("/workspace/api/bereich/live", kDogi));
|
||
ok((live.eintraege || []).some((e) => e.titel === "Fertig geschnitten"), "es steht in der LIVE-Analyse");
|
||
|
||
/* Das Video bleibt bestehen und traegt einen Vermerk -- sonst legt
|
||
beim naechsten Durchsehen jemand dieselbe Aufgabe noch einmal an. */
|
||
const c = await jsn(await ruf("/workspace/api/bereich/content", kDogi));
|
||
const video = c.eintraege.find((e) => e.id === ids.fertig);
|
||
ok(!!video, "das Video ist noch da");
|
||
ok(/Übernommen als Aufgabe/.test(video?.text || ""), "und trägt einen Vermerk");
|
||
|
||
/* Ohne geplanten Zeitpunkt gibt es keinen Termin -- lieber eine klare
|
||
Absage als ein Termin auf gut Glueck. */
|
||
const ohne = await ruf(`/workspace/api/content/${ids.idee}/uebernehmen`, kDogi, {
|
||
method: "POST", body: JSON.stringify({ ziel: "termin" }) });
|
||
ok(ohne.status === 400, `ohne Zeitpunkt kein Termin (${ohne.status})`);
|
||
console.log(" ->", (await jsn(ohne)).fehler);
|
||
}
|
||
|
||
console.log("\n8) Der Scout darf – für seine Creator");
|
||
{
|
||
const a = await ruf("/workspace/api/content/saeulen", kScout, {
|
||
method: "POST", body: JSON.stringify({ name: "Scout-Säule", creator_id: idBen }) });
|
||
ok(a.status === 403, `nicht für einen fremden Creator (${a.status})`);
|
||
const u = await ruf(`/workspace/api/content/${ids.gedreht}/uebernehmen`, kScout, {
|
||
method: "POST", body: JSON.stringify({ ziel: "aufgabe" }) });
|
||
ok(u.ok, `aber für Anna, die er betreut (${u.status})`);
|
||
}
|
||
|
||
console.log("\n9) Der Creator sieht alles und ändert nichts");
|
||
{
|
||
const c = await jsn(await ruf("/workspace/api/bereich/content", kCreator));
|
||
ok((c.eintraege || []).length >= 10, `sieht seine Videos (${(c.eintraege || []).length})`);
|
||
const s = await jsn(await ruf("/workspace/api/content/saeulen", kCreator));
|
||
ok((s.saeulen || []).length === 5, `sieht seine Säulen (${(s.saeulen || []).length})`);
|
||
const z = await jsn(await ruf("/workspace/api/content/kennzahlen", kCreator));
|
||
ok(typeof z.vorrat === "number", `sieht seine Kennzahlen (Vorrat ${z.vorrat})`);
|
||
|
||
const neu = await ruf("/workspace/api/content/saeulen", kCreator, {
|
||
method: "POST", body: JSON.stringify({ name: "Meine", creator_id: idAnna }) });
|
||
ok(neu.status === 403, `kann keine Säule anlegen (${neu.status})`);
|
||
const weg = await ruf(`/workspace/api/content/saeulen/${saeuleA}`, kCreator, { method: "DELETE" });
|
||
ok(weg.status === 403, `keine löschen (${weg.status})`);
|
||
const ueb = await ruf(`/workspace/api/content/${ids.idee}/uebernehmen`, kCreator, {
|
||
method: "POST", body: JSON.stringify({ ziel: "aufgabe" }) });
|
||
ok(ueb.status === 403, `nichts überführen (${ueb.status})`);
|
||
}
|
||
|
||
console.log("\n10) Eine Säule löschen darf keine Arbeit löschen");
|
||
{
|
||
const vorher = (await jsn(await ruf("/workspace/api/bereich/content", kDogi))).eintraege.length;
|
||
const a = await ruf(`/workspace/api/content/saeulen/${saeuleB}`, kDogi, { method: "DELETE" });
|
||
ok(a.ok, `Säule entfernt (${a.status})`);
|
||
const nachher = (await jsn(await ruf("/workspace/api/bereich/content", kDogi))).eintraege;
|
||
ok(nachher.length === vorher, `alle ${vorher} Videos sind noch da (${nachher.length})`);
|
||
ok(nachher.every((e) => e.saeule_id !== saeuleB), "sie hängen nur nicht mehr an der Säule");
|
||
}
|
||
|
||
console.log(`\n${fehler === 0 ? "Alles in Ordnung." : fehler + " Prüfung(en) fehlgeschlagen."}`);
|
||
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
|
||
process.exit(fehler === 0 ? 0 : 1);
|