Gemessen: 165 Pruefdateien, 129 verschiedene Nummern -- NEUNZEHN
doppelt, vier davon dreifach. Niemand hatte das gewollt; jede neue
Pruefung wurde von einer vorhandenen abgeschrieben, und die Nummer kam
mit. Meine eigene Notiz sagte "sieben" -- auch eine Bestandsliste
altert.
Der Waechter faengt den Schaden ab, aber er kann nur melden, was schon
passiert ist: Zwei Pruefungen mit derselben Nummer koennen nie
gleichzeitig laufen, und ein liegengebliebener Prozess der einen laesst
die andere abbrechen mit einer Meldung, die wie ein Befund aussieht.
Genau das ist mir am 20.09. zweimal passiert.
Jetzt leitet jede Datei ihre Nummer aus ihrer STELLE IM ALPHABET ab
(eigenerPort in helfer-port.mjs), zwei je Datei. Nicht ueber eine
Pruefsumme: Bei 165 Namen in 4900 Nummern waeren nach dem
Geburtstagsproblem rund DREI Zusammenstoesse zu erwarten -- ein Hash
tauscht eine sichtbare Doppelung gegen eine unsichtbare. Die Stelle im
Alphabet ist eindeutig von der Bauart her.
--- ZWEI FEHLER AUF DEM WEG, BEIDE LEHRREICH -------------------------
1. DER ERSTE VERSUCH WAR GRUEN UND KAPUTT. Ersetzt wurde mit einem
Muster: "([^"]*4231[^"]*)". Das hielt
{ host: "127.0.0.1", port: 4231, path: "/404.html" }
fuer eine Zeichenkette -- ein Muster kann eine oeffnende nicht von
einer schliessenden Anfuehrung unterscheiden. Heraus kam
{ host: "127.0.0.1`, port: ${PORT}, path: `/404.html" }
also GUELTIGER Code ohne port-Feld. `node --check` sagte gruen fuer
alle 164 Dateien. Aufgefallen ist es erst, weil ich vier Vertreter
gegen eine vorher gemessene Grundlinie laufen liess: pruef-schranke
39/0 vorher, 38/1 nachher.
Alles zurueckgenommen und mit einem Zerleger neu gemacht, der weiss,
ob eine Stelle Code, Zeichenkette, Vorlage, Kommentar oder
regulaerer Ausdruck ist. In pruef-ics stand die Nummer in einem
regulaeren Ausdruck -- der wird jetzt gebaut statt hingeschrieben.
2. EIN MODUL, DAS BEIM IMPORTIEREN ARBEITET, IST EINE FALLE. Der
zweite Durchgang importierte den ersten, um seine Mechanik zu
benutzen -- und fuehrte dessen Hauptlauf gleich mit aus. Die
zweiten Nummern wurden dadurch als erste behandelt, zwei Aufrufe
bekamen dieselbe Nummer, und in pruef-content stand `const PORT`
zweimal.
--- WAS DAS DAUERHAFT HAELT -----------------------------------------
pruef-portnummern.mjs (neu, 8 Pruefungen) fragt nicht "welche Nummern
sind doppelt", sondern "wer traegt ueberhaupt noch eine von Hand ein"
und "wer startet einen Server, ohne seine Nummer abzuleiten". Die
zweite Frage hat sofort etwas gefunden, das in KEINER Doppelungsliste
stand: pruef-push-weg belegte 4341 und 4342, rief den Waechter aber
gar nicht auf -- dieselbe Nummer wie pruef-agentur. Eine Liste zeigt
nur, was auf ihr steht.
Mit Gegenprobe: Eine unbekannte Datei bekommt keine geratene Nummer,
sondern einen Abbruch, und eine dritte Nummer je Datei gibt es nicht.
--- NACHGEMESSEN ----------------------------------------------------
Sechzehn Pruefungen gegen ihre vorher gemessene Grundlinie, je eine
Vertreterin jeder umgestellten Bauweise (eine Nummer, zwei Nummern,
Nummer in einer Zeichenkette, in einer Vorlage, in einem regulaeren
Ausdruck, dynamische Einfuhr, Nachtlauf mit zwei Laeufen):
crew-adresse 132/0 · schranke 39/0 · content 45/0 · entwicklung 46/0
anruf 127/0 · ics 37/0 · push-weg 20/0 · arten 28/0 · video 67/0
kanalzeile 14/0 · agentur 62/0 · spicy 83/0 · creator-anlegen 50/0
push-ziel 10/0 · portnummern 8/0
Alle exakt wie vorher. Zwei waren schon vorher rot und sind es
unveraendert geblieben (crew-wand-bild 41/4, teilen 14/3) -- per
`git stash` belegt, nicht angenommen.
Co-Authored-By: Claude Opus 5 <[email protected]>
404 lines
19 KiB
JavaScript
404 lines
19 KiB
JavaScript
/* CHAT — wer darf mit wem, und wer bekommt nichts zu sehen.
|
|
|
|
Wunsch Filipe, 06.09.2026. Auf Nachfrage entschieden: Zweier-
|
|
Gespräche UND Gruppen; Manager und Scouts dürfen sich auch ohne
|
|
gemeinsamen Creator schreiben.
|
|
|
|
WARUM DIESE PRÜFUNG ZUERST KAM, vor der Oberfläche: Ein Chat ist der
|
|
Ort, an dem Leute Dinge schreiben, die nicht jeder lesen soll. Ein
|
|
Fehler in der Sichtbarkeit ist hier teurer als überall sonst im
|
|
Haus -- eine falsche Zahl auf einer Kachel ärgert, ein mitgelesenes
|
|
Gespräch ist ein Vertrauensbruch.
|
|
|
|
GEMESSEN WIRD:
|
|
1. Die Regel selbst: Wer erscheint in wessen Partnerliste?
|
|
2. Kann man sie umgehen, indem man die Anfrage von Hand baut?
|
|
3. Kommt man an einen fremden Verlauf, wenn man die Raumnummer rät?
|
|
4. Ist eine Gruppe ein Schlupfloch zu jemandem, den man einzeln
|
|
nicht anschreiben dürfte?
|
|
5. Und die Alltagsfragen: Gibt es je Paar genau EIN Gespräch?
|
|
Zählt "ungelesen" richtig? Bleibt ein Zurücknehmen sichtbar?
|
|
|
|
GEGENPROBEN durchgehend: Jede Sperre wird auch von der erlaubten
|
|
Seite versucht. Eine Prüfung, die immer "verboten" sagt, beweist
|
|
nichts. */
|
|
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-chat-"));
|
|
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
|
|
|
const { eigenerPort } = await import("./helfer-port.mjs");
|
|
const PORT = await eigenerPort(import.meta, "die Chatpruefung");
|
|
|
|
process.env.PORT = `${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");
|
|
await new Promise((r) => setTimeout(r, 1000));
|
|
const BASIS = `http://127.0.0.1:${PORT}`;
|
|
|
|
let fehler = 0;
|
|
const ok = (b, t) => { console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
|
|
|
/* ---- Ein Bestand mit ECHTER Struktur ------------------------------------
|
|
|
|
Zwei getrennte Ketten, damit "fremd" wirklich fremd ist:
|
|
Cigdem (Manager) → Patrick (Scout) → Luna (Creator)
|
|
Meral (Manager) → Sven (Scout) → Nora (Creator)
|
|
Dazu DogFather über allem.
|
|
|
|
Ohne die zweite Kette liesse sich nicht prüfen, ob ein Creator an
|
|
einen fremden Creator kommt -- es gäbe schlicht keinen. */
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes } = await import("node:crypto");
|
|
await fetch(BASIS + "/workspace/api/anmelden", {
|
|
method: "POST", headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ rolle: "admin", code: "x" }) }).catch(() => {});
|
|
for (let i = 0; i < 60; i++) {
|
|
try {
|
|
const p = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
try { p.prepare("SELECT 1 FROM personen LIMIT 1").get(); break; } finally { p.close(); }
|
|
} catch { await new Promise((r) => setTimeout(r, 250)); }
|
|
}
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const jetzt = new Date().toISOString();
|
|
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("Filipe", "admin", "CODE-DOGI-0001");
|
|
const idCigdem = anlegen("Cigdem", "manager", "CODE-MANA-0001");
|
|
const idMeral = anlegen("Meral", "manager", "CODE-MANA-0002");
|
|
const idPat = anlegen("Patrick", "scout", "CODE-SCOU-0001");
|
|
const idSven = anlegen("Sven", "scout", "CODE-SCOU-0002");
|
|
const idLuna = anlegen("Luna", "creator", "CODE-CREA-0001");
|
|
const idNora = anlegen("Nora", "creator", "CODE-CREA-0002");
|
|
d.prepare("INSERT INTO betreuung (creator_id, betreuer_id, seit) VALUES (?,?,?)").run(idLuna, idPat, jetzt);
|
|
d.prepare("INSERT INTO betreuung (creator_id, betreuer_id, seit) VALUES (?,?,?)").run(idNora, idSven, jetzt);
|
|
d.prepare("INSERT INTO scout_zuteilung (scout_id, manager_id, seit) VALUES (?,?,?)").run(idPat, idCigdem, jetzt);
|
|
d.prepare("INSERT INTO scout_zuteilung (scout_id, manager_id, seit) VALUES (?,?,?)").run(idSven, idMeral, jetzt);
|
|
|
|
async function anmelden(rolle, code) {
|
|
const a = await fetch(BASIS + "/workspace/api/anmelden", {
|
|
method: "POST", headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ rolle, code }) });
|
|
const keks = (a.headers.getSetCookie?.() || []).map((k) => k.split(";")[0]).join("; ");
|
|
return { ok: a.ok, keks };
|
|
}
|
|
const rufe = (keks, weg, koerper, methode = "GET") =>
|
|
fetch(BASIS + weg, {
|
|
method: methode,
|
|
headers: { "Content-Type": "application/json", Cookie: keks },
|
|
body: koerper === undefined ? undefined : JSON.stringify(koerper),
|
|
});
|
|
const holen = async (keks, weg) => (await rufe(keks, weg)).json();
|
|
|
|
const dogi = await anmelden("admin", "CODE-DOGI-0001");
|
|
const cigdem = await anmelden("manager", "CODE-MANA-0001");
|
|
const meral = await anmelden("manager", "CODE-MANA-0002");
|
|
const pat = await anmelden("scout", "CODE-SCOU-0001");
|
|
const luna = await anmelden("creator", "CODE-CREA-0001");
|
|
const nora = await anmelden("creator", "CODE-CREA-0002");
|
|
ok([dogi, cigdem, meral, pat, luna, nora].every((x) => x.ok && x.keks),
|
|
"alle sechs Rollen sind angemeldet");
|
|
|
|
/* =======================================================================
|
|
1. Die Regel: wer erscheint in wessen Liste?
|
|
======================================================================= */
|
|
console.log("\n=== Mit wem kann man schreiben? ===");
|
|
{
|
|
const namen = async (keks) => {
|
|
const { personen } = await holen(keks, "/workspace/api/chat/partner");
|
|
return (personen || []).map((p) => p.name).sort();
|
|
};
|
|
|
|
const beiLuna = await namen(luna.keks);
|
|
/* Ein Creator: sein Scout, dessen Manager, DogFather. Kein fremder
|
|
Creator, kein fremder Scout. */
|
|
ok(JSON.stringify(beiLuna) === JSON.stringify(["Cigdem", "Filipe", "Patrick"]),
|
|
`Creator Luna: ${beiLuna.join(", ")}`);
|
|
ok(!beiLuna.includes("Nora"),
|
|
"Luna sieht die andere Creatorin NICHT — Creator bleiben getrennt");
|
|
ok(!beiLuna.includes("Sven") && !beiLuna.includes("Meral"),
|
|
"und auch nicht den fremden Scout oder dessen Manager");
|
|
|
|
const beiPat = await namen(pat.keks);
|
|
/* Ein Scout: sein Creator, DogFather -- UND das ganze Team
|
|
(Entscheidung vom 06.09.2026: "für Absprachen im Team"). */
|
|
ok(beiPat.includes("Luna") && beiPat.includes("Filipe"),
|
|
`Scout Patrick erreicht seinen Creator und DogFather`);
|
|
ok(beiPat.includes("Meral") && beiPat.includes("Sven"),
|
|
`und das Team: ${beiPat.join(", ")}`);
|
|
ok(!beiPat.includes("Nora"),
|
|
"aber NICHT die fremde Creatorin — die Öffnung gilt nur fürs Team");
|
|
|
|
const beiDogi = await namen(dogi.keks);
|
|
ok(beiDogi.length === 6, `DogFather erreicht alle ${beiDogi.length} anderen`);
|
|
ok(!beiDogi.includes("Filipe"), "aber nicht sich selbst");
|
|
}
|
|
|
|
/* =======================================================================
|
|
2. Lässt sich die Regel von Hand umgehen?
|
|
======================================================================= */
|
|
console.log("\n=== Die Regel sitzt im Server, nicht in der Oberfläche ===");
|
|
{
|
|
/* Luna baut die Anfrage selbst und will Nora anschreiben. */
|
|
const a = await rufe(luna.keks, "/workspace/api/chat/direkt", { mit: idNora }, "POST");
|
|
ok(a.status === 403, `Luna → Nora von Hand: abgelehnt (${a.status})`);
|
|
|
|
const b = await rufe(luna.keks, "/workspace/api/chat/direkt", { mit: idSven }, "POST");
|
|
ok(b.status === 403, `Luna → fremder Scout: abgelehnt (${b.status})`);
|
|
|
|
/* GEGENPROBE: Zu ihrem eigenen Scout geht es. Ohne diesen Nachweis
|
|
bewiese "403" nur, dass die Route überhaupt ablehnt. */
|
|
const c = await rufe(luna.keks, "/workspace/api/chat/direkt", { mit: idPat }, "POST");
|
|
ok(c.ok, `Luna → ihr eigener Scout: geht (${c.status})`);
|
|
|
|
/* Und sich selbst anschreiben ergibt keinen Sinn. */
|
|
const e = await rufe(luna.keks, "/workspace/api/chat/direkt", { mit: idLuna }, "POST");
|
|
ok(e.status === 403, `sich selbst anschreiben: abgelehnt (${e.status})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
3. Ein fremder Verlauf, mit geratener Nummer
|
|
======================================================================= */
|
|
console.log("\n=== Kommt man an fremde Gespräche? ===");
|
|
let raumLunaPat = 0;
|
|
{
|
|
const { raum_id } = await (await rufe(luna.keks, "/workspace/api/chat/direkt",
|
|
{ mit: idPat }, "POST")).json();
|
|
raumLunaPat = raum_id;
|
|
ok(Number.isInteger(raumLunaPat) && raumLunaPat > 0, `Gespräch Luna↔Patrick ist #${raumLunaPat}`);
|
|
|
|
await rufe(luna.keks, `/workspace/api/chat/raeume/${raumLunaPat}/nachrichten`,
|
|
{ text: "Vertraulich: mein Umsatz war 3200." }, "POST");
|
|
|
|
/* Nora rät die Nummer. */
|
|
const a = await rufe(nora.keks, `/workspace/api/chat/raeume/${raumLunaPat}/nachrichten`);
|
|
ok(a.status === 404, `Fremde liest Verlauf: ${a.status} (404, nicht 403 — die Nummer soll nichts verraten)`);
|
|
|
|
/* Und sie versucht hineinzuschreiben. */
|
|
const b = await rufe(nora.keks, `/workspace/api/chat/raeume/${raumLunaPat}/nachrichten`,
|
|
{ text: "Hallo?" }, "POST");
|
|
ok(b.status === 404, `Fremde schreibt hinein: ${b.status}`);
|
|
|
|
/* Auch als gelesen markieren geht nicht -- das wäre sonst ein Weg,
|
|
die Existenz eines Raums zu erfahren. */
|
|
const c = await rufe(nora.keks, `/workspace/api/chat/raeume/${raumLunaPat}/gelesen`,
|
|
{ bis: 999 }, "PUT");
|
|
ok(c.status === 404, `Fremde markiert als gelesen: ${c.status}`);
|
|
|
|
/* GEGENPROBE: Patrick, der drin ist, kommt heran. */
|
|
const e = await holen(pat.keks, `/workspace/api/chat/raeume/${raumLunaPat}/nachrichten`);
|
|
ok(e.nachrichten?.length === 1 && /3200/.test(e.nachrichten[0].text),
|
|
`Patrick liest den Verlauf (${e.nachrichten?.length} Nachricht)`);
|
|
|
|
/* Selbst DogFather kommt nicht ungefragt in ein fremdes Gespräch.
|
|
Das ist Absicht: Ein Chat, in dem der Chef stillschweigend mitliest,
|
|
ist kein Chat. Er darf jederzeit selbst schreiben -- aber sichtbar. */
|
|
const f = await rufe(dogi.keks, `/workspace/api/chat/raeume/${raumLunaPat}/nachrichten`);
|
|
ok(f.status === 404,
|
|
`auch DogFather liest fremde Gespräche NICHT mit (${f.status}) — er ist kein Teilnehmer`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
4. Genau EIN Gespräch je Paar
|
|
======================================================================= */
|
|
console.log("\n=== Ein Paar, ein Gespräch ===");
|
|
{
|
|
/* Von der anderen Seite geöffnet muss derselbe Raum kommen -- sonst
|
|
schreibt einer ins Leere, während der andere wartet. */
|
|
const { raum_id: vonPat } = await (await rufe(pat.keks, "/workspace/api/chat/direkt",
|
|
{ mit: idLuna }, "POST")).json();
|
|
ok(vonPat === raumLunaPat,
|
|
`Patrick öffnet dasselbe Gespräch (#${vonPat} = #${raumLunaPat}), keine zweite Kopie`);
|
|
|
|
/* Und mehrfaches Öffnen legt nichts Neues an. */
|
|
for (let i = 0; i < 3; i++) {
|
|
await rufe(luna.keks, "/workspace/api/chat/direkt", { mit: idPat }, "POST");
|
|
}
|
|
const p = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const n = p.prepare("SELECT COUNT(*) AS n FROM chat_raeume WHERE art = 'direkt'").get().n;
|
|
p.close();
|
|
ok(n === 1, `nach fünfmaligem Öffnen: ${n} Gespräch in der Datenbank`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
5. Gruppen — und ob sie ein Schlupfloch sind
|
|
======================================================================= */
|
|
console.log("\n=== Gruppen ===");
|
|
{
|
|
/* DER PUNKT: Luna darf Nora nicht einzeln anschreiben. Kann sie es
|
|
über eine Gruppe? */
|
|
const a = await rufe(luna.keks, "/workspace/api/chat/gruppe",
|
|
{ name: "Umweg", mit: [idPat, idNora] }, "POST");
|
|
ok(a.status === 403,
|
|
`eine Gruppe ist KEIN Schlupfloch: ${a.status} (Nora war dabei)`);
|
|
|
|
/* GEGENPROBE: Ohne Nora geht dieselbe Gruppe. */
|
|
const b = await rufe(luna.keks, "/workspace/api/chat/gruppe",
|
|
{ name: "Luna, Patrick & Cigdem", mit: [idPat, idCigdem] }, "POST");
|
|
ok(b.status === 201, `mit erlaubten Leuten: angelegt (${b.status})`);
|
|
const { raum_id: gruppe } = await b.json();
|
|
|
|
const sicht = await holen(luna.keks, `/workspace/api/chat/raeume/${gruppe}/nachrichten`);
|
|
ok(sicht.raum?.teilnehmer?.length === 3,
|
|
`die Gruppe hat ${sicht.raum?.teilnehmer?.length} Mitglieder`);
|
|
ok(sicht.raum?.name === "Luna, Patrick & Cigdem",
|
|
`und ihren Namen: "${sicht.raum?.name}"`);
|
|
|
|
/* Wer sie angelegt hat, führt sie. */
|
|
const chefin = sicht.raum.teilnehmer.find((t) => t.leitung);
|
|
ok(chefin?.id === idLuna, `die Leitung liegt bei ${chefin?.name}`);
|
|
|
|
/* Ohne Namen keine Gruppe. */
|
|
const c = await rufe(luna.keks, "/workspace/api/chat/gruppe",
|
|
{ name: "", mit: [idPat] }, "POST");
|
|
ok(c.status === 400, `ohne Namen: abgelehnt (${c.status})`);
|
|
|
|
/* Und ohne Leute auch nicht. */
|
|
const e = await rufe(luna.keks, "/workspace/api/chat/gruppe",
|
|
{ name: "Allein", mit: [] }, "POST");
|
|
ok(e.status === 400, `ohne Mitglieder: abgelehnt (${e.status})`);
|
|
|
|
/* Nora sieht die Gruppe nicht in ihrer Liste. */
|
|
const { raeume } = await holen(nora.keks, "/workspace/api/chat/raeume");
|
|
ok(!(raeume || []).some((r) => r.id === gruppe),
|
|
`Nora sieht die fremde Gruppe nicht (${raeume?.length || 0} eigene Gespräche)`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
6. Ungelesen — die Zahl, auf die man sich verlässt
|
|
======================================================================= */
|
|
console.log("\n=== Ungelesene Nachrichten ===");
|
|
{
|
|
const vorher = (await holen(pat.keks, "/workspace/api/chat/ungelesen")).ungelesen;
|
|
|
|
await rufe(luna.keks, `/workspace/api/chat/raeume/${raumLunaPat}/nachrichten`,
|
|
{ text: "Erste" }, "POST");
|
|
await rufe(luna.keks, `/workspace/api/chat/raeume/${raumLunaPat}/nachrichten`,
|
|
{ text: "Zweite" }, "POST");
|
|
|
|
const nachher = (await holen(pat.keks, "/workspace/api/chat/ungelesen")).ungelesen;
|
|
ok(nachher === vorher + 2, `Patrick: ${vorher} → ${nachher} ungelesen`);
|
|
|
|
/* Die EIGENEN zählen nicht mit -- sonst stünde nach jeder eigenen
|
|
Nachricht eine rote Zahl an der eigenen Kopfleiste. */
|
|
const beiLuna = (await holen(luna.keks, "/workspace/api/chat/ungelesen")).ungelesen;
|
|
ok(beiLuna === 0, `Luna hat ${beiLuna} ungelesene (ihre eigenen zählen nicht)`);
|
|
|
|
/* Lesen setzt sie zurück. */
|
|
const verlauf = await holen(pat.keks, `/workspace/api/chat/raeume/${raumLunaPat}/nachrichten`);
|
|
const letzteId = verlauf.nachrichten.at(-1).id;
|
|
await rufe(pat.keks, `/workspace/api/chat/raeume/${raumLunaPat}/gelesen`,
|
|
{ bis: letzteId }, "PUT");
|
|
const danach = (await holen(pat.keks, "/workspace/api/chat/ungelesen")).ungelesen;
|
|
ok(danach === 0, `nach dem Lesen: ${danach}`);
|
|
|
|
/* NUR VORWÄRTS: Ein verspäteter Aufruf mit einer alten Nummer darf
|
|
den Stand nicht zurückdrehen -- sonst stünde Gelesenes wieder als
|
|
ungelesen da. */
|
|
await rufe(pat.keks, `/workspace/api/chat/raeume/${raumLunaPat}/gelesen`, { bis: 1 }, "PUT");
|
|
const zurueck = (await holen(pat.keks, "/workspace/api/chat/ungelesen")).ungelesen;
|
|
ok(zurueck === 0, `ein alter "gelesen bis 1" dreht nichts zurück (${zurueck})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
7. Zurücknehmen
|
|
======================================================================= */
|
|
console.log("\n=== Eine Nachricht zurücknehmen ===");
|
|
{
|
|
const a = await rufe(luna.keks, `/workspace/api/chat/raeume/${raumLunaPat}/nachrichten`,
|
|
{ text: "Das war ein Versehen" }, "POST");
|
|
const { nachricht } = await a.json();
|
|
|
|
/* Patrick darf sie NICHT zurücknehmen -- fremde Worte aus einem
|
|
Gespräch zu nehmen, in dem Absprachen stehen, wäre ein Eingriff in
|
|
die Geschichte. */
|
|
const b = await rufe(pat.keks, `/workspace/api/chat/nachrichten/${nachricht.id}`, undefined, "DELETE");
|
|
ok(b.status === 403, `fremde Nachricht zurücknehmen: abgelehnt (${b.status})`);
|
|
|
|
/* Auch DogFather nicht. */
|
|
const c = await rufe(dogi.keks, `/workspace/api/chat/nachrichten/${nachricht.id}`, undefined, "DELETE");
|
|
ok(c.status === 404 || c.status === 403,
|
|
`auch DogFather nicht (${c.status})`);
|
|
|
|
/* Die eigene schon. */
|
|
const e = await rufe(luna.keks, `/workspace/api/chat/nachrichten/${nachricht.id}`, undefined, "DELETE");
|
|
ok(e.ok, `die eigene: geht (${e.status})`);
|
|
|
|
/* UND SIE BLEIBT SICHTBAR -- als zurückgenommen, nicht als Loch. */
|
|
const verlauf = await holen(pat.keks, `/workspace/api/chat/raeume/${raumLunaPat}/nachrichten`);
|
|
const weg = verlauf.nachrichten.find((n) => n.id === nachricht.id);
|
|
ok(weg && weg.zurueckgenommen && weg.text === null,
|
|
`sie steht weiterhin im Verlauf, als zurückgenommen und ohne Text`);
|
|
ok(!JSON.stringify(verlauf).includes("Das war ein Versehen"),
|
|
"der Text ist wirklich weg, nicht nur ausgeblendet");
|
|
}
|
|
|
|
/* =======================================================================
|
|
8. Grenzen der Eingabe
|
|
======================================================================= */
|
|
console.log("\n=== Was nicht hineingeht ===");
|
|
{
|
|
for (const [was, koerper, status] of [
|
|
["leerer Text", { text: "" }, 400],
|
|
["nur Leerzeichen", { text: " " }, 400],
|
|
["ohne Feld", {}, 400],
|
|
["zu lang (4001)", { text: "z".repeat(4001) }, 400],
|
|
]) {
|
|
const a = await rufe(luna.keks, `/workspace/api/chat/raeume/${raumLunaPat}/nachrichten`,
|
|
koerper, "POST");
|
|
ok(a.status === status, `${was}: ${a.status}`);
|
|
}
|
|
/* GEGENPROBE: 4000 Zeichen gehen. */
|
|
const a = await rufe(luna.keks, `/workspace/api/chat/raeume/${raumLunaPat}/nachrichten`,
|
|
{ text: "z".repeat(4000) }, "POST");
|
|
ok(a.status === 201, `genau 4000 Zeichen: ${a.status} — die Grenze ist nicht zu streng`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
9. Ohne Anmeldung gar nichts
|
|
======================================================================= */
|
|
console.log("\n=== Ohne Anmeldung ===");
|
|
{
|
|
for (const [was, weg, methode] of [
|
|
["Partnerliste", "/workspace/api/chat/partner", "GET"],
|
|
["Gesprächsliste", "/workspace/api/chat/raeume", "GET"],
|
|
["Verlauf", `/workspace/api/chat/raeume/${raumLunaPat}/nachrichten`, "GET"],
|
|
["Ungelesen", "/workspace/api/chat/ungelesen", "GET"],
|
|
["Strom", "/workspace/api/chat/strom", "GET"],
|
|
]) {
|
|
const a = await fetch(BASIS + weg, { method: methode });
|
|
ok(a.status === 401, `${was}: ${a.status}`);
|
|
}
|
|
}
|
|
|
|
d.close();
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
|
|
|
|
console.log(`\n${fehler === 0 ? "ALLES IN ORDNUNG" : `${fehler} FEHLER`}`);
|
|
/* HIER IST process.exit() RICHTIG -- anders als in pruef-push-weg.
|
|
|
|
Diese Pruefung startet einen Express-Server, und der laeuft von
|
|
selbst weiter: offene Verbindungen, ein Zeitgeber fuer die
|
|
Sicherung, bei dieser Pruefung zusaetzlich die offenen
|
|
Ereignisstroeme des Chats. Mit `exitCode` allein wartet Node ewig
|
|
darauf, dass all das zu Ende geht -- der Lauf lief in die
|
|
Zeitueberschreitung, obwohl alle Pruefungen schon durch waren.
|
|
|
|
Der Absturz, der in pruef-push-weg gegen exit() sprach, kam von
|
|
fetch-Verbindungen mitten im Schliessen. Die gibt es hier nicht
|
|
mehr: Die Browser sind zu, bevor diese Zeile laeuft. */
|
|
process.exit(fehler ? 1 : 0);
|