import { DummyDriver, Kysely, PostgresAdapter, PostgresIntrospector, PostgresQueryCompiler } from "kysely"; import { describe, expect, it } from "vitest"; import type { Schema } from "@/lib/db/schema"; import { sichtbareNotizen } from "@/lib/notes"; import { baueKollegen } from "@/lib/shell-data"; // Wessen Notizen jemand sieht, ist die eine Stelle, an der ein Fehler nicht // auffällt: die Glocke zeigt weiter eine Zahl, nur die falsche. Zu wenig, und // eine Wiedervorlage bleibt liegen; zu viel, und die Einstellung wirkt nicht. // Deshalb wird hier die Abfrage gelesen, die tatsächlich herauskommt. const db = new Kysely({ dialect: { createAdapter: () => new PostgresAdapter(), createDriver: () => new DummyDriver(), createIntrospector: (d) => new PostgresIntrospector(d), createQueryCompiler: () => new PostgresQueryCompiler(), }, }); const ICH = "11111111-1111-1111-1111-111111111111"; function abfrage(userId = ICH) { return db .selectFrom("employee_notes as n") .select("n.id") .where(sichtbareNotizen(userId)) .compile(); } const sql = (userId?: string) => abfrage(userId).sql.replace(/\s+/g, " "); describe("sichtbareNotizen", () => { it("lässt Notizen ohne Verfasser durch", () => { // author_user_id kann leer sein. Eine Notiz auszublenden, weil niemand // weiss, von wem sie ist, wäre genau der stille Verlust, den die Liste // verhindern soll. expect(sql()).toContain('"n"."author_user_id" is null'); }); it("lässt die eigenen Notizen immer durch", () => { expect(sql()).toContain('"n"."author_user_id" = $'); }); it("holt dazu, wer ausdrücklich hinzugewählt wurde", () => { // Vorzeichen und Tabelle zusammen: ein `not exists` auf derselben // Tabelle kehrte die Bedeutung um, ohne dass ein Wort sich änderte. const s = sql(); expect(s).toContain("exists"); expect(s).not.toContain("not exists"); expect(s).toContain("from colleague_subscriptions s"); expect(s).toContain("s.user_id = $"); expect(s).toContain("s.author_user_id = \"n\".\"author_user_id\""); }); it("verknüpft die drei Fälle mit ODER, nicht mit UND", () => { // Mit UND sähe niemand mehr etwas: keine Notiz ist gleichzeitig ohne // Verfasser und von mir. const s = sql(); expect(s).toMatch(/is null\s+or/); expect(s).not.toMatch(/is null\s+and/); }); it("bindet die Kennung als Parameter, nicht in den Text", () => { // Sie kommt aus der Sitzung, nicht aus der Adresse — trotzdem hat sie im // Abfragetext nichts verloren. const { sql: text, parameters } = abfrage("bösartig'; drop table employee_notes; --"); expect(text).not.toContain("drop table"); expect(parameters).toContain("bösartig'; drop table employee_notes; --"); }); it("nennt die Kennung zweimal — für die eigenen Notizen und für die Ausnahmen", () => { expect(abfrage().parameters.filter((p) => p === ICH)).toHaveLength(2); }); it("lässt sich auf einen anderen Aliasnamen setzen", () => { // Die Übersicht bindet dieselbe Tabelle ein; käme sie je unter anderem // Namen, muss die Regel mitkönnen statt kopiert zu werden. const s = db .selectFrom("employee_notes as notiz") .select("notiz.id") .where(sichtbareNotizen(ICH, "notiz.author_user_id")) .compile().sql; expect(s).toContain('"notiz"."author_user_id"'); }); }); describe("baueKollegen", () => { const leute = [ { id: "a", full_name: "Anna Berger", email: "a@example.test" }, { id: "b", full_name: null, email: "b@example.test" }, { id: "c", full_name: " ", email: "c@example.test" }, ]; it("lässt ohne Auswahl alle Haken leer", () => { // Die Vorgabe ist die eigene Person; wer mehr will, wählt hinzu. expect(baueKollegen(leute, []).map((k) => k.sichtbar)).toEqual([false, false, false]); }); it("setzt den Haken, wo ein Abo steht", () => { expect(baueKollegen(leute, ["b"]).map((k) => [k.id, k.sichtbar])).toEqual([ ["a", false], ["b", true], ["c", false], ]); }); it("fällt ohne Namen auf die E-Mail zurück", () => { // Ein Haken ohne Beschriftung wäre einer, von dem niemand weiss, wen er // betrifft. Auch ein Name aus Leerzeichen zählt als keiner. expect(baueKollegen(leute, []).map((k) => k.name)).toEqual([ "Anna Berger", "b@example.test", "c@example.test", ]); }); it("kommt mit einem Abo zurecht, zu dem es niemanden mehr gibt", () => { // Wer die Personalabteilung verlässt, verschwindet aus der Liste; die // Zeile bleibt, bis der Fremdschlüssel sie räumt. expect(baueKollegen(leute, ["längst-weg"])).toHaveLength(3); }); });