Die Live-Datenbank hat mir den Fehler gezeigt, nicht der Quelltext.
Nach dem Ausliefern ein Blick auf die echten Staende: erledigt 6,
offen 4 -- und abgebrochen 2. Die zwei standen in meiner Pruefung
nicht.
GRUND: Ich hatte die Etappen aus STATUS in workspace-aufgaben.js
abgeleitet, und dort stehen nur vier. "abgebrochen" fehlt dort
ABSICHTLICH, damit der normale Weg es nicht setzen kann -- es kommt
ueber eine eigene Route mit Pflichtbegruendung. Wer die Liste aus
STATUS ableitet, uebersieht also ausgerechnet den Endzustand, in dem
Aufgaben am laengsten liegen bleiben. Dieselbe Luecke hat in dieser
Datei schon einmal zwei Bedingungen erwischt, die nur gegen
'erledigt' prueften.
"In jeder etape" waere damit eine Zusage ueber vier von fuenf
Etappen gewesen.
Zwei Dinge dafuer nachgezogen:
* Die Etappe wird jetzt aus der DATENBANK gelesen, nicht aus der
Liste. Abgebrochene Aufgaben werden an mehreren Stellen bewusst
ausgeblendet -- "Etappe steht wirklich" waere ueber die Liste
gar nicht messbar gewesen.
* Die Auskunft an die Oberflaeche wird nur verlangt, WENN die
Aufgabe in ihrer Liste steht. Blendet das Haus eine Etappe aus,
gibt es dort keinen Knopf zu zeigen; dann zaehlt allein der
Server, und den misst der DELETE. Gemessen: Sie steht drin, und
der Knopf wird auch dort angeboten.
pruef-verteilen 42 -> 44, jetzt 5/5 in allen Zaehlungen.
Co-Authored-By: Claude Opus 5 <[email protected]>
487 lines
24 KiB
JavaScript
487 lines
24 KiB
JavaScript
/* =====================================================================
|
|
pruef-verteilen.mjs — Wer darf Aufgaben an andere geben?
|
|
|
|
Filipe, 20.09.2026: "jeder soll sich selber aufgabe vergeben koennen
|
|
aber nur die rechte hand und dogfather aufgaben an andere verteilen."
|
|
|
|
---------------------------------------------------------------------
|
|
WARUM DAS EINE EIGENE PRUEFUNG BRAUCHT
|
|
|
|
Die Regel hat ZWEI Seiten, und beide muessen dasselbe sagen:
|
|
|
|
* Der Server lehnt ab, wer nicht darf.
|
|
* Die Seite zeigt die Felder nur dem, der darf.
|
|
|
|
Stimmt nur die erste, sieht die rechte Hand Felder, die nichts
|
|
bewirken. Stimmt nur die zweite, kommt jeder durch, der die
|
|
Schnittstelle direkt anspricht. Beides ist schon vorgekommen.
|
|
|
|
UND DIE GEFAEHRLICHE STELLE: `hand` steht bewusst NICHT in
|
|
`istLeitung` -- diese Menge wird an 57 Stellen gefragt, unter
|
|
anderem im vertraulichen Meldeweg. Die Pruefung haelt fest, dass
|
|
das Verteilen eine EIGENE Regel ist und nicht versehentlich zur
|
|
Leitung wird.
|
|
|
|
AUFRUF: node server/pruef-verteilen.mjs
|
|
===================================================================== */
|
|
|
|
import { mkdtempSync, rmSync } 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";
|
|
import { notbremse } from "./helfer-notbremse.mjs";
|
|
|
|
const PORT = await eigenerPort(import.meta, "pruef-verteilen");
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-verteilen-"));
|
|
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(180_000, "pruef-verteilen");
|
|
await new Promise((r) => setTimeout(r, 900));
|
|
|
|
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++; };
|
|
const melde = (t) => console.log(t);
|
|
|
|
console.log("\n=== Wer darf Aufgaben an andere geben? ===");
|
|
|
|
/* ---- 1. Die Regel selbst, ohne Server ----------------------------- */
|
|
melde("\n=== 1. Die Regel ===");
|
|
{
|
|
const { darfAufgabenVerteilen, istLeitung } = await import("./workspace.js");
|
|
const darf = ["admin", "hand", "spicy", "manager"];
|
|
const nicht = ["modi", "scout", "creator", "gast"];
|
|
ok(darf.every((r) => darfAufgabenVerteilen({ rolle: r })),
|
|
`verteilen duerfen: ${darf.join(", ")}`);
|
|
ok(nicht.every((r) => !darfAufgabenVerteilen({ rolle: r })),
|
|
`und nicht: ${nicht.join(", ")}`);
|
|
/* DIE WICHTIGE ABGRENZUNG: Verteilen ist NICHT Leitung. Waere die
|
|
rechte Hand in `istLeitung` gerutscht, saehe sie damit auch
|
|
vertrauliche Meldungen ueber Menschen -- eine Entscheidung, die
|
|
niemand getroffen hat. */
|
|
ok(!istLeitung({ rolle: "hand" }),
|
|
"die rechte Hand darf verteilen, ist aber NICHT „Leitung“ — das bleibt getrennt");
|
|
ok(darfAufgabenVerteilen({ rolle: undefined }) === false
|
|
&& darfAufgabenVerteilen(null) === false,
|
|
"Gegenprobe: ohne Rolle darf niemand");
|
|
}
|
|
|
|
/* ---- 2. Durch die Schnittstelle ----------------------------------- */
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
|
|
const jetzt = new Date().toISOString();
|
|
let idDogi, idHand, idModi, idModi2, idLinke;
|
|
{
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const k = randomBytes(32).toString("hex");
|
|
d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von) VALUES (?,?,?,NULL)"
|
|
+ " ON CONFLICT(schluessel) DO NOTHING").run("code_kennung_schluessel", k, jetzt);
|
|
const 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, code_kennung, aktiv, erstellt)"
|
|
+ " VALUES (?,?,?,?,?,?,1,?)")
|
|
.run(name, rolle, hash, salt, 32768, createHmac("sha256", k).update(code).digest("hex"), jetzt);
|
|
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
};
|
|
idDogi = anlegen("Filipe", "admin", "CODE-A");
|
|
idHand = anlegen("VanVan", "hand", "CODE-H");
|
|
idModi = anlegen("Diene", "modi", "CODE-M");
|
|
idModi2 = anlegen("Ghost", "modi", "CODE-M2");
|
|
/* DIE LINKE HAND IST DIE GEGENPROBE (03.10.2026). `istHand` fasst
|
|
beide Haende zusammen -- haette das neue Loeschrecht daran
|
|
gehangen, bekaeme sie es lautlos mit. */
|
|
idLinke = anlegen("Funny", "linke", "CODE-L");
|
|
d.close();
|
|
}
|
|
|
|
const CREW = "crew.dogfather-universe.com";
|
|
function ruf(pfad, keks, methode = "GET", koerper = null) {
|
|
return new Promise((fertig, schief) => {
|
|
const rumpf = koerper ? JSON.stringify(koerper) : null;
|
|
const a = httpAnfrage({
|
|
host: "127.0.0.1", port: PORT, path: pfad, method: methode,
|
|
headers: {
|
|
Host: CREW, ...(keks ? { Cookie: keks } : {}),
|
|
...(rumpf ? { "Content-Type": "application/json", Origin: `https://${CREW}`,
|
|
"Content-Length": Buffer.byteLength(rumpf) } : {}),
|
|
},
|
|
}, (antwort) => {
|
|
let text = "";
|
|
antwort.on("data", (x) => { text += x; });
|
|
antwort.on("end", () => fertig({
|
|
code: antwort.statusCode, text,
|
|
keks: [].concat(antwort.headers["set-cookie"] || []).map((c) => c.split(";")[0]).join("; "),
|
|
}));
|
|
});
|
|
a.on("error", schief);
|
|
if (rumpf) a.write(rumpf);
|
|
a.end();
|
|
});
|
|
}
|
|
const json = (a) => { try { return JSON.parse(a.text); } catch { return {}; } };
|
|
const anmelden = async (rolle, code) =>
|
|
(await ruf("/workspace/api/anmelden", null, "POST", { rolle, code })).keks;
|
|
|
|
melde("\n=== 2. Durch die Schnittstelle ===");
|
|
const kDogi = await anmelden("admin", "CODE-A");
|
|
const kHand = await anmelden("hand", "CODE-H");
|
|
const kModi = await anmelden("modi", "CODE-M");
|
|
const kLinke = await anmelden("linke", "CODE-L");
|
|
ok(!!kDogi && !!kHand && !!kModi, "DogFather, rechte Hand und ein Modi sind angemeldet");
|
|
|
|
/* Sagt der Server es auch der Seite? */
|
|
for (const [name, keks, soll] of [["DogFather", kDogi, true], ["rechte Hand", kHand, true], ["Modi", kModi, false]]) {
|
|
const ich = json(await ruf("/workspace/api/ich", keks));
|
|
ok(ich.darf_verteilen === soll,
|
|
`„${name}“ bekommt darf_verteilen = ${ich.darf_verteilen} (erwartet ${soll})`);
|
|
}
|
|
|
|
/* Und die Tat: eine Aufgabe FUER JEMAND ANDEREN. */
|
|
async function anlegenFuer(keks, zielId, titel) {
|
|
const a = await ruf("/workspace/api/aufgaben", keks, "POST",
|
|
{ titel, verantwortlich_id: zielId, prioritaet: "mittel" });
|
|
if (a.code !== 201) return { code: a.code, verant: null };
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const z = d.prepare("SELECT verantwortlich_id FROM aufgaben WHERE id = ?").get(json(a).id);
|
|
d.close();
|
|
return { code: a.code, verant: z?.verantwortlich_id ?? null };
|
|
}
|
|
|
|
const vonHand = await anlegenFuer(kHand, idModi, "Von der rechten Hand an Diene");
|
|
ok(vonHand.code === 201 && vonHand.verant === idModi,
|
|
`die rechte Hand gibt eine Aufgabe an Diene (${vonHand.code}, liegt bei ${vonHand.verant})`);
|
|
|
|
const vonDogi = await anlegenFuer(kDogi, idModi2, "Von DogFather an Ghost");
|
|
ok(vonDogi.code === 201 && vonDogi.verant === idModi2,
|
|
`DogFather ebenso (${vonDogi.code}, liegt bei ${vonDogi.verant})`);
|
|
|
|
/* DIE ENTSCHEIDENDE GEGENPROBE: Ein Modi versucht dasselbe.
|
|
---------------------------------------------------------------------
|
|
BIS ZUM 22.09.2026 stand hier, die Aufgabe lande bei ihm selbst --
|
|
"jeder soll sich selber aufgabe vergeben koennen" (20.09.). Filipe
|
|
hat das ausdruecklich umgedreht: "damit wir endlich den modis
|
|
aufgaben anstaendig verteilen koennen und sie sich nicht selber
|
|
aufgaben geben."
|
|
|
|
Im Team Dogi kommen Aufgaben jetzt von der Leitung. Ein Modi
|
|
bekommt 403 -- und das ist der Unterschied, auf den es ankommt: Er
|
|
kann sich keine geben, weder sich selbst noch jemand anderem. */
|
|
const vonModi = await anlegenFuer(kModi, idModi2, "Versuch eines Modi");
|
|
ok(vonModi.code === 403,
|
|
`Gegenprobe: ein Modi legt gar keine Aufgabe mehr an (${vonModi.code})`);
|
|
|
|
/* UND IN DER AGENTUR BLEIBT ES, WIE ES WAR. Dort ist eine Aufgabe eine
|
|
Notiz an sich selbst, kein Auftrag von jemandem -- eine Regel, die
|
|
beide Haeuser ueber einen Kamm schert, waere falsch. */
|
|
{
|
|
const w = await import("./workspace.js");
|
|
ok(w.darfAufgabenAnlegen({ rolle: "creator" }) === true
|
|
&& w.darfAufgabenAnlegen({ rolle: "manager" }) === true,
|
|
"in der Agentur legt weiterhin jeder fuer sich selbst an");
|
|
ok(w.darfAufgabenAnlegen({ rolle: "modi" }) === false,
|
|
"ein Modi nicht");
|
|
ok(w.darfAufgabenAnlegen({ rolle: "hand" }) === true
|
|
&& w.darfAufgabenAnlegen({ rolle: "linke" }) === true
|
|
&& w.darfAufgabenAnlegen({ rolle: "admin" }) === true,
|
|
"die drei, die verteilen duerfen, schon");
|
|
}
|
|
|
|
/* =======================================================================
|
|
DIE FELDER MUESSEN AUCH ZU SEHEN SEIN
|
|
=======================================================================
|
|
|
|
Filipe am 21.09.2026: "ich sehe noch immer nicht dass dogfather oder
|
|
die rechte hand wenn sie aufgaben verteilen die spezifisch an leute
|
|
verteilen können. mach das endlich auch weil das ist seeehr wichtig
|
|
damit wir arbeiten können mit den leuten."
|
|
|
|
ALLES DARUEBER WAR GRUEN -- und er hatte trotzdem recht. Der Server
|
|
nahm die Zuteilung an, die Rechte stimmten, die Leute standen in der
|
|
Auskunft. Nur die rechte Hand BEKAM DAS FELD NIE ZU SEHEN: Das
|
|
Absenden fragte `ich.darf_verteilen` (Server: Leitung ODER rechte
|
|
Hand), das Anzeigen eine andere Menge aus bereiche.js (spicy, admin,
|
|
manager). Zwei Bedingungen fuer dieselbe Sache, und eine davon
|
|
veraltet.
|
|
|
|
EINE PRUEFUNG, DIE NUR DEN SERVER FRAGT, FINDET SO ETWAS NIE. Sie
|
|
sieht eine Erlaubnis, die es gibt -- und uebersieht, dass niemand
|
|
an sie herankommt. Deshalb wird hier der Quelltext der Oberflaeche
|
|
mitgelesen: Es muss an BEIDEN Stellen dieselbe Frage stehen.
|
|
======================================================================= */
|
|
{
|
|
const { readFileSync } = await import("node:fs");
|
|
/* KOMMENTARE ZAEHLEN NICHT MIT.
|
|
|
|
Beim ersten Lauf meldete diese Pruefung fuenf Stellen -- zwei
|
|
davon waren mein eigener ERKLAERTEXT, in dem die alte Bedingung
|
|
woertlich zitiert steht. Eine Pruefung, die ihre eigene
|
|
Begruendung als Befund zaehlt, treibt einen dazu, die Begruendung
|
|
zu loeschen. Der Zerleger aus helfer-ohne-kommentar.mjs trennt
|
|
beides sauber. */
|
|
const { ohneKommentar } = await import("./helfer-ohne-kommentar.mjs");
|
|
const js = ohneKommentar(readFileSync(
|
|
new URL("../workspace/assets/js/aufgaben.js", import.meta.url), "utf8"), ".js");
|
|
|
|
/* Wie oft wird ueberhaupt entschieden, ob jemand verteilen darf? */
|
|
const mitServer = (js.match(/ich\.darf_verteilen/g) || []).length;
|
|
ok(mitServer >= 2,
|
|
`die Oberflaeche fragt ${mitServer}-mal den Server (darf_verteilen)`);
|
|
|
|
/* UND NIRGENDS MEHR ALLEIN DIE ROLLENMENGE. `LEITUNG.has(ich.rolle)`
|
|
darf nur noch als Rueckfall hinter `??` stehen -- also fuer den
|
|
Fall, dass eine aeltere Serverfassung das Feld gar nicht schickt.
|
|
Steht es irgendwo als eigene Bedingung, ist die zweite Wahrheit
|
|
zurueck. */
|
|
const ohneRueckfall = [...js.matchAll(/LEITUNG\.has\(ich\.rolle\)/g)]
|
|
.filter((m) => !/\?\?\s*$/.test(js.slice(Math.max(0, m.index - 24), m.index)));
|
|
ok(ohneRueckfall.length === 0,
|
|
ohneRueckfall.length
|
|
? `${ohneRueckfall.length}-mal entscheidet die Rollenmenge allein -- `
|
|
+ "der Server sagt es besser"
|
|
: "und nirgends entscheidet die Rollenmenge allein");
|
|
|
|
/* DIE BESCHRIFTUNG IST TEIL DER FUNKTION. "Verantwortlich" mit einem
|
|
Strich daneben liest sich wie eine Eigenschaft; gefunden hat
|
|
Filipe das Feld damit nicht. "Wer macht es?" ist eine Frage. */
|
|
const html = readFileSync(
|
|
new URL("../workspace/aufgaben.html", import.meta.url), "utf8");
|
|
const schilder = [...html.matchAll(/for="(?:f|b)-verant">([^<]+)</g)].map((m) => m[1].trim());
|
|
ok(schilder.length === 2,
|
|
`beide Formulare haben das Feld (${schilder.length})`);
|
|
ok(schilder.every((t) => /wer\s+macht/i.test(t)),
|
|
`und beide fragen dasselbe ("${schilder.join('" / "')}")`);
|
|
|
|
/* Und der leere Wert sagt, was er bedeutet. Ein Strich ist kein
|
|
Zustand, sondern ein fehlendes Wort -- wer ihn sieht, weiss nicht,
|
|
ob er etwas versaeumt hat. */
|
|
ok(/— noch niemand —/.test(html),
|
|
"der leere Wert heisst „noch niemand“ und nicht nur „—“");
|
|
}
|
|
|
|
/* =====================================================================
|
|
BEARBEITEN UND LOESCHEN, WAS SIE ANGELEGT HAT (25.09.2026)
|
|
=====================================================================
|
|
|
|
Filipe: „kuemmer dich bitte auch drum dass die rechte hand, wenn sie
|
|
aufgaben an die modis oder linke hand erstellt, will ich dass sie
|
|
die moeglichkeit hat die auch zu bearbeiten und zu loeschen bitte.
|
|
perfektionier das fuer sie und fuer dogfather."
|
|
|
|
WARUM ES VORHER NICHT GING, und es sah nicht danach aus:
|
|
`creator_id` heisst nicht „wer hat sie angelegt", sondern „zu wem
|
|
gehoert sie". Verteilt die rechte Hand an einen Modi, steht dort der
|
|
MODI. Sie erfuellte damit an ihrer eigenen Aufgabe keine der drei
|
|
Bedingungen von `darfAendern` und bekam 403 -- auf einen Knopf, den
|
|
die Oberflaeche ihr trotzdem zeigte. Die Spalte `erstellt_von` gibt
|
|
es seit jeher; sie stand nur nie in dieser Regel.
|
|
|
|
GEPRUEFT WIRD BEIDES: dass sie darf -- und dass ein Modi es NICHT
|
|
darf. Ohne die zweite Haelfte waere „sie darf" auch dann gruen, wenn
|
|
jeder alles duerfte.
|
|
===================================================================== */
|
|
melde("\n=== 5. Die rechte Hand an ihren eigenen Aufgaben ===");
|
|
{
|
|
const neu = await ruf("/workspace/api/aufgaben", kHand, "POST",
|
|
{ titel: "Von der rechten Hand, zum Aendern", verantwortlich_id: idModi,
|
|
prioritaet: "mittel" });
|
|
const id = json(neu).id;
|
|
ok(neu.code === 201 && Number.isInteger(id),
|
|
`die rechte Hand legt eine Aufgabe fuer Diene an (${neu.code}, #${id})`);
|
|
|
|
/* DIE AUSKUNFT AN DIE OBERFLAECHE ZUERST. Sie entscheidet, ob der
|
|
Knopf ueberhaupt erscheint -- und genau sie hat gefehlt: Bisher
|
|
fragte die Seite `darf_verteilen` (eine Auskunft ueber die
|
|
PERSON), waehrend Loeschen an der AUFGABE haengt. */
|
|
const ihre = (json(await ruf("/workspace/api/aufgaben", kHand)).aufgaben || [])
|
|
.find((a) => a.id === id);
|
|
ok(!!ihre && ihre.darf_aendern === true,
|
|
`sie bekommt gesagt, dass sie sie aendern darf (${ihre?.darf_aendern})`);
|
|
ok(!!ihre && ihre.darf_loeschen === true,
|
|
`und dass sie sie loeschen darf (${ihre?.darf_loeschen})`);
|
|
|
|
const geaendert = await ruf(`/workspace/api/aufgaben/${id}`, kHand, "PATCH",
|
|
{ titel: "Von der rechten Hand, geaendert" });
|
|
ok(geaendert.code === 200, `und sie aendert sie wirklich (${geaendert.code})`);
|
|
|
|
/* GEGENPROBE 1: Der Modi, bei dem sie LIEGT, darf sie aendern (das
|
|
ist sein Arbeitsstand) -- aber nicht loeschen. Verschwinden lassen
|
|
ist keine Antwort auf eine Aufgabe; ablehnen und abbrechen sind
|
|
die Wege dafuer. */
|
|
const beiModi = (json(await ruf("/workspace/api/aufgaben", kModi)).aufgaben || [])
|
|
.find((a) => a.id === id);
|
|
ok(!!beiModi && beiModi.darf_loeschen === false,
|
|
`Gegenprobe: dem Modi wird Loeschen NICHT angeboten (${beiModi?.darf_loeschen})`);
|
|
const modiWeg = await ruf(`/workspace/api/aufgaben/${id}`, kModi, "DELETE");
|
|
ok(modiWeg.code === 403,
|
|
` und der Server lehnt es auch ab (${modiWeg.code})`);
|
|
|
|
/* GEGENPROBE 2: Die Aufgabe steht danach noch da. Ein abgelehnter
|
|
Versuch, der trotzdem etwas aendert, waere der schlimmste Ausgang. */
|
|
const d2 = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const nochDa = d2.prepare("SELECT titel FROM aufgaben WHERE id = ?").get(id);
|
|
d2.close();
|
|
ok(!!nochDa, ` und die Aufgabe steht noch ("${nochDa?.titel}")`);
|
|
|
|
/* UND JETZT SIE SELBST. */
|
|
const handWeg = await ruf(`/workspace/api/aufgaben/${id}`, kHand, "DELETE");
|
|
ok(handWeg.code === 200, `die rechte Hand loescht ihre eigene (${handWeg.code})`);
|
|
const d3 = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const weg = d3.prepare("SELECT 1 AS x FROM aufgaben WHERE id = ?").get(id);
|
|
d3.close();
|
|
ok(!weg, " und sie ist wirklich weg");
|
|
|
|
/* UND DOGFATHER UNVERAENDERT -- „perfektionier das fuer sie UND fuer
|
|
dogfather". Seine Rechte kamen aus `istLeitung` und bleiben, wo
|
|
sie waren; das hier ist der Nachweis, dass der Umbau sie nicht
|
|
nebenbei mitgenommen hat. */
|
|
const seine = await ruf("/workspace/api/aufgaben", kDogi, "POST",
|
|
{ titel: "Von DogFather, zum Loeschen", verantwortlich_id: idModi2,
|
|
prioritaet: "mittel" });
|
|
const id2 = json(seine).id;
|
|
const dogiWeg = await ruf(`/workspace/api/aufgaben/${id2}`, kDogi, "DELETE");
|
|
ok(seine.code === 201 && dogiWeg.code === 200,
|
|
`DogFather legt an und loescht wie bisher (${seine.code} / ${dogiWeg.code})`);
|
|
|
|
/* UND EINE FREMDE: Die rechte Hand an einer Aufgabe, die DogFather
|
|
fuer sich selbst angelegt hat. Sie hat sie nicht angelegt, sie
|
|
liegt nicht bei ihr -- also nein. Ohne diese Zeile hiesse „sie
|
|
darf ihre eigenen" in Wahrheit vielleicht „sie darf alle". */
|
|
const fremd = await ruf("/workspace/api/aufgaben", kDogi, "POST",
|
|
{ titel: "Nur fuer DogFather", verantwortlich_id: idDogi, prioritaet: "mittel" });
|
|
const id3 = json(fremd).id;
|
|
const handFremd = await ruf(`/workspace/api/aufgaben/${id3}`, kHand, "DELETE");
|
|
ok(fremd.code === 201 && handFremd.code === 200,
|
|
`und auch eine fremde Aufgabe von DogFather (${handFremd.code})`);
|
|
}
|
|
|
|
/* =====================================================================
|
|
6. IMMER UND IN JEDER ETAPPE (03.10.2026)
|
|
|
|
Filipe: "ich will noch dass die rechte hand und dogfather bei den
|
|
aufgaben immer die option haben aufgaben zu loeschen. die soll es
|
|
immer geben in jeder etape."
|
|
|
|
HIER STAND BIS HEUTE DAS GEGENTEIL. Die letzte Zeile des Abschnitts
|
|
darueber lautete: "Gegenprobe: eine fremde Aufgabe loescht sie
|
|
nicht (403)". Am 25.09. war das richtig -- damals hiess der Auftrag
|
|
"ihre eigenen". Jetzt heisst er "immer", und die Zeile ist
|
|
mitgewandert statt stehenzubleiben. Eine Pruefung, die eine
|
|
zurueckgenommene Regel weiter verteidigt, haelt die Aenderung auf
|
|
und sieht dabei aus wie Sorgfalt.
|
|
|
|
WARUM ALLE VIER ETAPPEN EINZELN: Der Loeschweg fragt heute keinen
|
|
Status ab -- diese Pruefung zielt also nicht auf einen bekannten
|
|
Fehler, sondern auf einen kuenftigen. "In jeder etape" ist eine
|
|
Zusage; ohne die vier Durchlaeufe koennte sie jemand mit einer gut
|
|
gemeinten Statusbedingung aufheben, ohne dass etwas rot wird.
|
|
|
|
GEGENGEWICHT: Die LINKE Hand bekommt das Recht NICHT. `istHand`
|
|
fasst beide Haende zusammen, und haette ich das genommen, waere es
|
|
lautlos an sie mitgegangen -- Filipe hat zwei Rollen genannt, nicht
|
|
drei. Diese Gegenprobe ist der einzige Ort, an dem der Unterschied
|
|
auffallen wuerde.
|
|
===================================================================== */
|
|
melde("\n=== 6. Loeschen: immer, in jeder Etappe ===");
|
|
{
|
|
/* FUENF, NICHT VIER -- und die fuenfte hat mir die Live-Datenbank
|
|
gezeigt (03.10.2026).
|
|
|
|
Mein erster Satz war ["offen","arbeit","review","erledigt"], die
|
|
Liste aus `STATUS` in workspace-aufgaben.js. Beim Nachsehen auf
|
|
dem laufenden Server standen dort aber auch zwei Aufgaben auf
|
|
"abgebrochen". Der Grund: "abgebrochen" steht absichtlich NICHT
|
|
in STATUS, damit der normale Weg es nicht setzen kann -- es
|
|
kommt ueber eine eigene Route. Wer die Etappen aus STATUS
|
|
ableitet, uebersieht also ausgerechnet den Endzustand, in dem
|
|
Aufgaben am laengsten liegen bleiben.
|
|
|
|
Genau dieselbe Luecke hat in dieser Datei schon einmal zwei
|
|
Bedingungen erwischt, die nur gegen 'erledigt' prueften. */
|
|
const ETAPPEN = ["offen", "arbeit", "review", "erledigt", "abgebrochen"];
|
|
let konnteHand = 0, konnteDogi = 0, konnteLinkeNicht = 0, wirklichWeg = 0;
|
|
|
|
for (const stand of ETAPPEN) {
|
|
/* Je Etappe drei Aufgaben, alle von DogFather an einen Modi --
|
|
die rechte Hand hat mit keiner davon etwas zu tun. */
|
|
const bauen = async (titel) => {
|
|
const r = await ruf("/workspace/api/aufgaben", kDogi, "POST",
|
|
{ titel, verantwortlich_id: idModi, prioritaet: "mittel" });
|
|
const id = json(r).id;
|
|
if (stand === "abgebrochen") {
|
|
/* Eigener Weg mit Pflichtbegruendung -- per PATCH laesst sich
|
|
dieser Stand gar nicht setzen. */
|
|
await ruf(`/workspace/api/aufgaben/${id}/abbrechen`, kDogi, "POST",
|
|
{ grund: "Pruefung: Loeschen in jeder Etappe" });
|
|
} else if (stand !== "offen") {
|
|
await ruf(`/workspace/api/aufgaben/${id}`, kDogi, "PATCH", { status: stand });
|
|
}
|
|
return id;
|
|
};
|
|
|
|
const fuerHand = await bauen(`Fremd fuer die rechte Hand (${stand})`);
|
|
const fuerDogi = await bauen(`Fremd fuer DogFather (${stand})`);
|
|
const fuerLinke = await bauen(`Fremd fuer die linke Hand (${stand})`);
|
|
|
|
/* Steht die Etappe wirklich? Ohne das waeren alle vier Durchlaeufe
|
|
in Wahrheit viermal "offen" -- und die Pruefung gruen, ohne je
|
|
etwas anderes gesehen zu haben. */
|
|
/* AUS DER DATENBANK, NICHT AUS DER LISTE. Abgebrochene Aufgaben
|
|
werden an mehreren Stellen bewusst ausgeblendet -- die Liste
|
|
koennte sie also gar nicht zeigen, und "Etappe steht" waere
|
|
dann nicht messbar. Die Tabelle luegt nicht. */
|
|
const dS = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const echt = dS.prepare("SELECT status FROM aufgaben WHERE id = ?").get(fuerHand);
|
|
dS.close();
|
|
ok(echt?.status === stand, `Etappe "${stand}" steht wirklich (${echt?.status})`);
|
|
|
|
/* Und die Oberflaeche bekommt es auch gesagt -- sonst gaebe es den
|
|
Knopf gar nicht, egal was der Server erlaubt. */
|
|
const beiHand = (json(await ruf("/workspace/api/aufgaben", kHand)).aufgaben || [])
|
|
.find((a) => a.id === fuerHand);
|
|
/* Steht sie in IHRER Liste, muss auch der Knopf angeboten werden.
|
|
Blendet das Haus diese Etappe fuer sie aus, gibt es dort keinen
|
|
Knopf zu zeigen -- dann ist der Server allein zustaendig, und
|
|
den misst der DELETE zwei Zeilen weiter. */
|
|
ok(beiHand === undefined || beiHand.darf_loeschen === true,
|
|
beiHand === undefined
|
|
? ` (in dieser Etappe steht sie nicht in ihrer Liste -- nur der Server zaehlt)`
|
|
: ` der rechten Hand wird Loeschen angeboten (${beiHand.darf_loeschen})`);
|
|
|
|
if ((await ruf(`/workspace/api/aufgaben/${fuerHand}`, kHand, "DELETE")).code === 200) konnteHand++;
|
|
if ((await ruf(`/workspace/api/aufgaben/${fuerDogi}`, kDogi, "DELETE")).code === 200) konnteDogi++;
|
|
if ((await ruf(`/workspace/api/aufgaben/${fuerLinke}`, kLinke, "DELETE")).code === 403) konnteLinkeNicht++;
|
|
|
|
/* Weg heisst weg -- in der Datenbank, nicht nur im Rueckgabewert. */
|
|
const d3 = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const da = d3.prepare("SELECT COUNT(*) AS n FROM aufgaben WHERE id IN (?,?)")
|
|
.get(fuerHand, fuerDogi).n;
|
|
const linkeNochDa = d3.prepare("SELECT COUNT(*) AS n FROM aufgaben WHERE id = ?")
|
|
.get(fuerLinke).n;
|
|
d3.close();
|
|
if (da === 0 && linkeNochDa === 1) wirklichWeg++;
|
|
}
|
|
|
|
const n = ETAPPEN.length;
|
|
ok(n === 5 && konnteHand === n,
|
|
`die rechte Hand loescht in allen ${n} Etappen (${konnteHand}/${n})`);
|
|
ok(konnteDogi === n, `DogFather ebenso (${konnteDogi}/${n})`);
|
|
ok(konnteLinkeNicht === n,
|
|
`Gegenprobe: die LINKE Hand darf es in keiner (${konnteLinkeNicht}/${n} mal abgelehnt)`);
|
|
ok(wirklichWeg === n,
|
|
`und geloescht ist geloescht -- die abgelehnte steht noch da (${wirklichWeg}/${n})`);
|
|
}
|
|
|
|
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);
|