Der Eingang des Spiegels ist die einzige Tuer, die ohne Anmeldung aufgeht. Vier Faelle: ohne Signatur, falsch signiert, zu alt, und der gute Fall — bei dem eine nicht mehr geschickte Belegung auch hier verschwinden muss. Der Testserver bekommt dafuer dasselbe Geheimnis wie der Test. Eine Pruefung, die immer uebersprungen wird, prueft nichts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
588 lines
19 KiB
TypeScript
588 lines
19 KiB
TypeScript
/**
|
||
* Integrationstest gegen eine echte Datenbank.
|
||
*
|
||
* Prüft, was Vitest nicht abdeckt: Eindeutigkeit, Fremdschlüssel, Kaskaden
|
||
* und dass das Audit-Protokoll den Benutzer überlebt.
|
||
*
|
||
* Bewusst ohne umschließende Transaktion: Der Test prüft absichtlich Fälle,
|
||
* die scheitern müssen, und ein gescheiterter Befehl bricht in PostgreSQL die
|
||
* ganze Transaktion ab. Stattdessen tragen alle Testdaten dieselbe Kennung
|
||
* und werden am Ende wieder entfernt — auch wenn der Test unterwegs abbricht.
|
||
*
|
||
* Aufruf: npm run test:integration
|
||
*/
|
||
import 'dotenv/config'
|
||
import { PrismaPg } from '@prisma/adapter-pg'
|
||
import { PrismaClient } from '../src/generated/prisma/client.js'
|
||
|
||
const verbindung = process.env.DATABASE_URL
|
||
if (!verbindung) {
|
||
console.error('DATABASE_URL ist nicht gesetzt.')
|
||
process.exit(1)
|
||
}
|
||
|
||
const db = new PrismaClient({ adapter: new PrismaPg({ connectionString: verbindung }) })
|
||
|
||
type Pruefung = { name: string; ok: boolean; hinweis?: string }
|
||
const ergebnisse: Pruefung[] = []
|
||
|
||
function pruefe(name: string, bedingung: boolean, hinweis?: string): void {
|
||
ergebnisse.push(hinweis ? { name, ok: bedingung, hinweis } : { name, ok: bedingung })
|
||
}
|
||
|
||
/** Erwartet, dass die Aktion scheitert. */
|
||
async function scheitert(name: string, tun: () => Promise<unknown>): Promise<void> {
|
||
try {
|
||
await tun()
|
||
pruefe(name, false, 'Die Aktion war erlaubt, obwohl sie scheitern sollte.')
|
||
} catch {
|
||
pruefe(name, true)
|
||
}
|
||
}
|
||
|
||
const kennung = `zzz-test-${Date.now()}`
|
||
// Eigene Chatkennung je Lauf, damit das Aufraeumen genau diese Zeile trifft
|
||
// und nicht irgendeine, die zufaellig aehnlich aussieht.
|
||
const testChatId = BigInt(Date.now())
|
||
const testEmail = `${kennung}@example.invalid`
|
||
|
||
async function aufraeumen(): Promise<void> {
|
||
// Erst die Aufgaben, dann Projekt und Bereich – sonst blieben Verweise
|
||
// stehen, die den Bereich am Löschen hindern.
|
||
await db.aufgabe.deleteMany({ where: { titel: { startsWith: kennung } } })
|
||
await db.aufgabe.deleteMany({ where: { quelleRef: { startsWith: kennung } } })
|
||
await db.projekt.deleteMany({ where: { name: { startsWith: kennung } } })
|
||
await db.auditEintrag.deleteMany({ where: { benutzerEmail: testEmail } })
|
||
// Kalenderkonten hängen am Benutzer und gehen mit ihm; falls der Test
|
||
// vorher abbricht, hier trotzdem noch einmal aufräumen.
|
||
await db.kalenderkonto.deleteMany({ where: { extKontoId: { startsWith: kennung } } })
|
||
await db.benutzer.deleteMany({ where: { email: testEmail } })
|
||
await db.bereich.deleteMany({ where: { name: { startsWith: kennung } } })
|
||
await db.webauthnChallenge.deleteMany({ where: { kennung } })
|
||
await db.pushGeraet.deleteMany({ where: { endpunkt: { contains: kennung } } })
|
||
await db.meldungsnachweis.deleteMany({ where: { schluessel: { contains: kennung } } })
|
||
await db.spiegelbelegung.deleteMany({ where: { quelleRef: { contains: kennung } } })
|
||
await db.telegramKopplung.deleteMany({
|
||
where: { code: { contains: kennung.slice(-8) } },
|
||
})
|
||
await db.telegramChat.deleteMany({ where: { chatId: testChatId } })
|
||
await db.integrationszustand.deleteMany({ where: { quelle: 'TELEGRAM' } })
|
||
}
|
||
|
||
async function main(): Promise<void> {
|
||
// --- Benutzer und Eindeutigkeit der Adresse ------------------------------
|
||
const benutzer = await db.benutzer.create({
|
||
data: { email: testEmail, name: 'Testkonto' },
|
||
})
|
||
pruefe('Benutzer anlegen', benutzer.id.length > 0)
|
||
pruefe('Rolle ist im Zweifel BENUTZER', benutzer.rolle === 'BENUTZER')
|
||
pruefe('Konto ist im Zweifel aktiv', benutzer.aktiv === true)
|
||
|
||
await scheitert('E-Mail-Adresse ist eindeutig', () =>
|
||
db.benutzer.create({ data: { email: testEmail } }),
|
||
)
|
||
|
||
// --- Bereich und Eindeutigkeit des Namens --------------------------------
|
||
const bereich = await db.bereich.create({
|
||
data: { name: `${kennung}-bereich`, farbe: '#2563eb', kapazitaetMo: 120 },
|
||
})
|
||
pruefe('Bereich anlegen', bereich.name.endsWith('-bereich'))
|
||
pruefe(
|
||
'Arbeitsfenster hat brauchbare Standardwerte',
|
||
bereich.arbeitsfensterVon === 480 && bereich.arbeitsfensterBis === 1020,
|
||
)
|
||
|
||
await scheitert('Bereichsname ist eindeutig', () =>
|
||
db.bereich.create({ data: { name: bereich.name, farbe: '#000000' } }),
|
||
)
|
||
|
||
// --- Passkey, Eindeutigkeit und Fremdschlüssel ---------------------------
|
||
const passkey = await db.passkey.create({
|
||
data: {
|
||
benutzerId: benutzer.id,
|
||
credentialId: `${kennung}-cred`,
|
||
publicKey: Buffer.from([1, 2, 3]),
|
||
bezeichnung: 'Testgerät',
|
||
},
|
||
})
|
||
pruefe('Passkey anlegen', passkey.counter === 0n)
|
||
pruefe(
|
||
'Zähler ist ein BigInt und beginnt bei null',
|
||
typeof passkey.counter === 'bigint',
|
||
)
|
||
|
||
await scheitert('Credential-ID ist eindeutig', () =>
|
||
db.passkey.create({
|
||
data: {
|
||
benutzerId: benutzer.id,
|
||
credentialId: passkey.credentialId,
|
||
publicKey: Buffer.from([4]),
|
||
bezeichnung: 'Zweitgerät',
|
||
},
|
||
}),
|
||
)
|
||
|
||
await scheitert('Passkey ohne gültigen Benutzer ist nicht möglich', () =>
|
||
db.passkey.create({
|
||
data: {
|
||
benutzerId: 'gibt-es-nicht',
|
||
credentialId: `${kennung}-waise`,
|
||
publicKey: Buffer.from([9]),
|
||
bezeichnung: 'Waise',
|
||
},
|
||
}),
|
||
)
|
||
|
||
// --- Öffentlicher Schlüssel kommt unverändert zurück ---------------------
|
||
const gelesen = await db.passkey.findUnique({ where: { id: passkey.id } })
|
||
pruefe(
|
||
'Der öffentliche Schlüssel kommt Byte für Byte zurück',
|
||
Buffer.from(gelesen?.publicKey ?? []).equals(Buffer.from([1, 2, 3])),
|
||
)
|
||
|
||
// --- Konto und Eindeutigkeit je Anbieter ---------------------------------
|
||
await db.konto.create({
|
||
data: {
|
||
benutzerId: benutzer.id,
|
||
type: 'oidc',
|
||
provider: 'oidc',
|
||
providerAccountId: kennung,
|
||
},
|
||
})
|
||
await scheitert('Ein Anbieterkonto ist nur einmal verknüpfbar', () =>
|
||
db.konto.create({
|
||
data: {
|
||
benutzerId: benutzer.id,
|
||
type: 'oidc',
|
||
provider: 'oidc',
|
||
providerAccountId: kennung,
|
||
},
|
||
}),
|
||
)
|
||
|
||
// --- Kalenderkonto und Termine ------------------------------------------
|
||
const kalenderkonto = await db.kalenderkonto.create({
|
||
data: {
|
||
benutzerId: benutzer.id,
|
||
anbieter: 'GRAPH',
|
||
anzeigename: `${kennung} Firma Outlook`,
|
||
extKontoId: `${kennung}-konto`,
|
||
},
|
||
})
|
||
pruefe(
|
||
'Kalenderkonto zeigt im Zweifel auf den Standardkalender',
|
||
kalenderkonto.kalenderId === 'standard',
|
||
)
|
||
pruefe(
|
||
'Ein frisches Konto hat noch keinen Abgleichverweis',
|
||
kalenderkonto.deltaVerweis === null,
|
||
)
|
||
|
||
await scheitert('Dasselbe Konto und derselbe Kalender nur einmal', () =>
|
||
db.kalenderkonto.create({
|
||
data: {
|
||
benutzerId: benutzer.id,
|
||
anbieter: 'GRAPH',
|
||
anzeigename: `${kennung} Doppelt`,
|
||
extKontoId: `${kennung}-konto`,
|
||
},
|
||
}),
|
||
)
|
||
|
||
// Dasselbe Konto mit einem anderen Kalender ist dagegen in Ordnung.
|
||
const zweiterKalender = await db.kalenderkonto.create({
|
||
data: {
|
||
benutzerId: benutzer.id,
|
||
anbieter: 'GRAPH',
|
||
anzeigename: `${kennung} Zweiter Kalender`,
|
||
extKontoId: `${kennung}-konto`,
|
||
kalenderId: 'AAMkAD-zweiter',
|
||
},
|
||
})
|
||
pruefe(
|
||
'Dasselbe Konto mit anderem Kalender ist erlaubt',
|
||
zweiterKalender.id !== kalenderkonto.id,
|
||
)
|
||
|
||
const termin = await db.termin.create({
|
||
data: {
|
||
kalenderkontoId: kalenderkonto.id,
|
||
extId: `${kennung}-termin`,
|
||
betreff: 'Wochenbesprechung',
|
||
beginn: new Date('2026-03-17T08:00:00Z'),
|
||
ende: new Date('2026-03-17T09:00:00Z'),
|
||
},
|
||
})
|
||
pruefe('Termin anlegen', termin.zeigtAls === 'GEBUCHT' && termin.ganztaegig === false)
|
||
|
||
await scheitert('Derselbe Termin je Konto nur einmal', () =>
|
||
db.termin.create({
|
||
data: {
|
||
kalenderkontoId: kalenderkonto.id,
|
||
extId: `${kennung}-termin`,
|
||
beginn: new Date('2026-03-17T08:00:00Z'),
|
||
ende: new Date('2026-03-17T09:00:00Z'),
|
||
},
|
||
}),
|
||
)
|
||
|
||
// So arbeitet der Abgleich: upsert statt anlegen.
|
||
const gewandelterTermin = await db.termin.upsert({
|
||
where: {
|
||
kalenderkontoId_extId: {
|
||
kalenderkontoId: kalenderkonto.id,
|
||
extId: `${kennung}-termin`,
|
||
},
|
||
},
|
||
update: { betreff: 'Wochenbesprechung, verschoben', zeigtAls: 'MIT_VORBEHALT' },
|
||
create: {
|
||
kalenderkontoId: kalenderkonto.id,
|
||
extId: `${kennung}-termin`,
|
||
beginn: new Date('2026-03-17T08:00:00Z'),
|
||
ende: new Date('2026-03-17T09:00:00Z'),
|
||
},
|
||
})
|
||
pruefe(
|
||
'Ein Upsert aktualisiert den Termin, statt ihn zu doppeln',
|
||
gewandelterTermin.id === termin.id &&
|
||
gewandelterTermin.zeigtAls === 'MIT_VORBEHALT' &&
|
||
(await db.termin.count({ where: { kalenderkontoId: kalenderkonto.id } })) === 1,
|
||
)
|
||
|
||
// --- Standard-Kalenderkonto eines Bereichs -------------------------------
|
||
await db.bereich.update({
|
||
where: { id: bereich.id },
|
||
data: { standardKalenderkontoId: kalenderkonto.id },
|
||
})
|
||
await db.kalenderkonto.delete({ where: { id: zweiterKalender.id } })
|
||
|
||
// --- Audit überlebt den Benutzer ----------------------------------------
|
||
const audit = await db.auditEintrag.create({
|
||
data: {
|
||
benutzerId: benutzer.id,
|
||
benutzerEmail: benutzer.email,
|
||
aktion: 'test.durchgefuehrt',
|
||
objektTyp: 'Bereich',
|
||
objektId: bereich.id,
|
||
details: { name: bereich.name },
|
||
instanz: 'daheim',
|
||
},
|
||
})
|
||
pruefe('Audit-Eintrag anlegen', audit.id.length > 0)
|
||
|
||
// --- Benachrichtigungen --------------------------------------------------
|
||
await db.benachrichtigungseinstellung.create({
|
||
data: { benutzerId: benutzer.id },
|
||
})
|
||
await scheitert('Je Benutzer nur eine Benachrichtigungseinstellung', () =>
|
||
db.benachrichtigungseinstellung.create({ data: { benutzerId: benutzer.id } }),
|
||
)
|
||
|
||
const voreinstellung = await db.benachrichtigungseinstellung.findUniqueOrThrow({
|
||
where: { benutzerId: benutzer.id },
|
||
})
|
||
pruefe(
|
||
'Ab Werk ist kein Weg eingeschaltet',
|
||
voreinstellung.perPush === false && voreinstellung.perMail === false,
|
||
)
|
||
|
||
await db.pushGeraet.create({
|
||
data: {
|
||
benutzerId: benutzer.id,
|
||
endpunkt: `https://push.invalid/${kennung}`,
|
||
p256dh: 'x',
|
||
auth: 'y',
|
||
},
|
||
})
|
||
await scheitert('Derselbe Push-Endpunkt geht nur einmal', () =>
|
||
db.pushGeraet.create({
|
||
data: {
|
||
benutzerId: benutzer.id,
|
||
endpunkt: `https://push.invalid/${kennung}`,
|
||
p256dh: 'x',
|
||
auth: 'y',
|
||
},
|
||
}),
|
||
)
|
||
|
||
await db.meldungsnachweis.create({
|
||
data: { benutzerId: benutzer.id, schluessel: `faelligkeit:${kennung}:2026-03-17` },
|
||
})
|
||
await scheitert('Derselbe Meldungsschluessel geht nur einmal', () =>
|
||
db.meldungsnachweis.create({
|
||
data: { benutzerId: benutzer.id, schluessel: `faelligkeit:${kennung}:2026-03-17` },
|
||
}),
|
||
)
|
||
pruefe(
|
||
'Derselbe Schlüssel am nächsten Tag ist erlaubt',
|
||
(
|
||
await db.meldungsnachweis.create({
|
||
data: {
|
||
benutzerId: benutzer.id,
|
||
schluessel: `faelligkeit:${kennung}:2026-03-18`,
|
||
},
|
||
})
|
||
).id.length > 0,
|
||
)
|
||
|
||
// --- Integrationen -------------------------------------------------------
|
||
// Der Zustand haengt an der Quelle: eine Zeile je Integration, nicht mehr.
|
||
await db.integrationszustand.create({
|
||
data: { quelle: 'TELEGRAM', merker: '42' },
|
||
})
|
||
await scheitert('Je Quelle nur ein Integrationszustand', () =>
|
||
db.integrationszustand.create({ data: { quelle: 'TELEGRAM' } }),
|
||
)
|
||
await db.integrationszustand.deleteMany({ where: { quelle: 'TELEGRAM' } })
|
||
|
||
// Zwei Aufgaben aus derselben Quelle mit derselben Kennung darf es nicht
|
||
// geben – sonst legte jeder Lauf dieselbe Sache erneut an.
|
||
const ausZammad = await db.aufgabe.create({
|
||
data: { titel: `${kennung}-ticket`, quelle: 'ZAMMAD', quelleRef: `${kennung}-1` },
|
||
})
|
||
pruefe('Aufgabe aus einer Integration anlegen', ausZammad.id.length > 0)
|
||
pruefe('Ohne Freigabe wird nicht gespiegelt', ausZammad.spiegeln === false)
|
||
|
||
await scheitert('Dieselbe Quelle mit derselben Kennung nur einmal (Integration)', () =>
|
||
db.aufgabe.create({
|
||
data: {
|
||
titel: `${kennung}-ticket-doppelt`,
|
||
quelle: 'ZAMMAD',
|
||
quelleRef: `${kennung}-1`,
|
||
},
|
||
}),
|
||
)
|
||
|
||
await db.spiegelbelegung.create({
|
||
data: {
|
||
quelleRef: `${kennung}-zb`,
|
||
beginn: new Date(Date.UTC(2026, 2, 17, 8, 0)),
|
||
ende: new Date(Date.UTC(2026, 2, 17, 9, 0)),
|
||
},
|
||
})
|
||
await scheitert('Dieselbe gespiegelte Belegung nur einmal', () =>
|
||
db.spiegelbelegung.create({
|
||
data: {
|
||
quelleRef: `${kennung}-zb`,
|
||
beginn: new Date(Date.UTC(2026, 2, 18, 8, 0)),
|
||
ende: new Date(Date.UTC(2026, 2, 18, 9, 0)),
|
||
},
|
||
}),
|
||
)
|
||
|
||
const chat = await db.telegramChat.create({
|
||
data: { chatId: testChatId, benutzerId: benutzer.id },
|
||
})
|
||
pruefe('Telegram-Chat empfaengt im Zweifel Meldungen', chat.meldungen === true)
|
||
await scheitert('Dieselbe Chatkennung nur einmal', () =>
|
||
db.telegramChat.create({ data: { chatId: chat.chatId } }),
|
||
)
|
||
|
||
await db.telegramKopplung.create({
|
||
data: {
|
||
code: `${kennung.slice(-8)}`,
|
||
benutzerId: benutzer.id,
|
||
gueltigBis: new Date(Date.now() + 600_000),
|
||
},
|
||
})
|
||
await scheitert('Denselben Kopplungscode nur einmal', () =>
|
||
db.telegramKopplung.create({
|
||
data: {
|
||
code: `${kennung.slice(-8)}`,
|
||
benutzerId: benutzer.id,
|
||
gueltigBis: new Date(Date.now() + 600_000),
|
||
},
|
||
}),
|
||
)
|
||
|
||
await db.benutzer.delete({ where: { id: benutzer.id } })
|
||
|
||
const nachher = await db.auditEintrag.findUnique({ where: { id: audit.id } })
|
||
pruefe('Audit-Eintrag überlebt das Löschen des Benutzers', nachher !== null)
|
||
pruefe(
|
||
'Die Kennung wird gelöst, die Adresse bleibt als Nachweis stehen',
|
||
nachher?.benutzerId === null && nachher?.benutzerEmail === testEmail,
|
||
)
|
||
|
||
pruefe(
|
||
'Passkeys verschwinden mit dem Benutzer',
|
||
(await db.passkey.count({ where: { credentialId: passkey.credentialId } })) === 0,
|
||
)
|
||
pruefe(
|
||
'Verknüpfte Konten verschwinden mit dem Benutzer',
|
||
(await db.konto.count({ where: { providerAccountId: kennung } })) === 0,
|
||
)
|
||
pruefe(
|
||
'Kalenderkonten verschwinden mit dem Benutzer',
|
||
(await db.kalenderkonto.count({ where: { id: kalenderkonto.id } })) === 0,
|
||
)
|
||
pruefe(
|
||
'Push-Geräte verschwinden mit dem Benutzer',
|
||
(await db.pushGeraet.count({ where: { endpunkt: { contains: kennung } } })) === 0,
|
||
)
|
||
pruefe(
|
||
'Telegram-Chats verschwinden nicht, verlieren aber den Benutzer',
|
||
(await db.telegramChat.count({ where: { benutzerId: benutzer.id } })) === 0,
|
||
)
|
||
pruefe(
|
||
'Meldungsnachweise verschwinden mit dem Benutzer',
|
||
(await db.meldungsnachweis.count({
|
||
where: { schluessel: { contains: kennung } },
|
||
})) === 0,
|
||
)
|
||
pruefe(
|
||
'Mit dem Kalenderkonto verschwinden auch die gespiegelten Termine',
|
||
(await db.termin.count({ where: { kalenderkontoId: kalenderkonto.id } })) === 0,
|
||
)
|
||
pruefe(
|
||
'Der Bereich überlebt das Löschen seines Standard-Kalenderkontos',
|
||
(await db.bereich.findUnique({ where: { id: bereich.id } }))
|
||
?.standardKalenderkontoId === null,
|
||
)
|
||
|
||
// --- Aufgaben: Standardwerte und Inbox -----------------------------------
|
||
const aufgabe = await db.aufgabe.create({
|
||
data: { titel: `${kennung} Angebot schreiben` },
|
||
})
|
||
pruefe('Aufgabe anlegen', aufgabe.id.length > 0)
|
||
pruefe(
|
||
'Ohne Angabe ist eine Aufgabe offen, normal und von Hand erfasst',
|
||
aufgabe.status === 'OFFEN' &&
|
||
aufgabe.typ === 'AUFGABE' &&
|
||
aufgabe.prioritaet === 3 &&
|
||
aufgabe.quelle === 'MANUELL',
|
||
)
|
||
pruefe('Ohne Bereich liegt sie in der Inbox', aufgabe.bereichId === null)
|
||
|
||
// --- Eindeutigkeit der Quelle -------------------------------------------
|
||
// Von Hand erfasste Aufgaben haben quelleRef = null. PostgreSQL sieht
|
||
// NULL-Werte als verschieden an, deshalb behindern sie sich nicht.
|
||
const zweiteOhneRef = await db.aufgabe.create({
|
||
data: { titel: `${kennung} Noch eine von Hand` },
|
||
})
|
||
pruefe(
|
||
'Mehrere Aufgaben ohne Quellverweis sind erlaubt',
|
||
zweiteOhneRef.id !== aufgabe.id,
|
||
)
|
||
|
||
await db.aufgabe.create({
|
||
data: {
|
||
titel: `${kennung} Ticket 4711`,
|
||
quelle: 'ZAMMAD',
|
||
quelleRef: `${kennung}-4711`,
|
||
quelleUrl: 'https://zammad.example.invalid/4711',
|
||
},
|
||
})
|
||
await scheitert('Dieselbe Quelle mit derselben Kennung nur einmal', () =>
|
||
db.aufgabe.create({
|
||
data: {
|
||
titel: `${kennung} Ticket 4711 doppelt`,
|
||
quelle: 'ZAMMAD',
|
||
quelleRef: `${kennung}-4711`,
|
||
},
|
||
}),
|
||
)
|
||
|
||
// Dieselbe Kennung bei anderer Quelle ist dagegen in Ordnung.
|
||
const andereQuelle = await db.aufgabe.create({
|
||
data: {
|
||
titel: `${kennung} Telegram 4711`,
|
||
quelle: 'TELEGRAM',
|
||
quelleRef: `${kennung}-4711`,
|
||
},
|
||
})
|
||
pruefe('Dieselbe Kennung bei anderer Quelle ist erlaubt', andereQuelle.id.length > 0)
|
||
|
||
// So werden Integrationen es tun: upsert statt anlegen.
|
||
const gewandelt = await db.aufgabe.upsert({
|
||
where: {
|
||
quelle_quelleRef: { quelle: 'ZAMMAD', quelleRef: `${kennung}-4711` },
|
||
},
|
||
update: { titel: `${kennung} Ticket 4711 aktualisiert` },
|
||
create: {
|
||
titel: `${kennung} Ticket 4711 neu`,
|
||
quelle: 'ZAMMAD',
|
||
quelleRef: `${kennung}-4711`,
|
||
},
|
||
})
|
||
pruefe(
|
||
'Ein Upsert über [quelle, quelleRef] aktualisiert statt zu doppeln',
|
||
gewandelt.titel.endsWith('aktualisiert') &&
|
||
(await db.aufgabe.count({
|
||
where: { quelle: 'ZAMMAD', quelleRef: `${kennung}-4711` },
|
||
})) === 1,
|
||
)
|
||
|
||
// --- Projekt: Zuordnung überlebt das Löschen nicht, die Aufgabe schon ----
|
||
const projekt = await db.projekt.create({ data: { name: `${kennung} Website` } })
|
||
await db.aufgabe.update({
|
||
where: { id: aufgabe.id },
|
||
data: { projektId: projekt.id },
|
||
})
|
||
|
||
await scheitert('Projektname ist eindeutig', () =>
|
||
db.projekt.create({ data: { name: projekt.name } }),
|
||
)
|
||
|
||
await db.projekt.delete({ where: { id: projekt.id } })
|
||
const nachProjektLoeschung = await db.aufgabe.findUnique({
|
||
where: { id: aufgabe.id },
|
||
select: { id: true, projektId: true },
|
||
})
|
||
pruefe('Die Aufgabe überlebt das Löschen ihres Projekts', nachProjektLoeschung !== null)
|
||
pruefe(
|
||
'Nur die Zuordnung zum Projekt wird gelöst',
|
||
nachProjektLoeschung?.projektId === null,
|
||
)
|
||
|
||
// --- Bereich: dasselbe Verhalten ----------------------------------------
|
||
const bereich2 = await db.bereich.create({
|
||
data: { name: `${kennung}-zweiter`, farbe: '#16a34a' },
|
||
})
|
||
await db.aufgabe.update({
|
||
where: { id: aufgabe.id },
|
||
data: { bereichId: bereich2.id },
|
||
})
|
||
await db.bereich.delete({ where: { id: bereich2.id } })
|
||
|
||
const nachBereichLoeschung = await db.aufgabe.findUnique({
|
||
where: { id: aufgabe.id },
|
||
select: { bereichId: true },
|
||
})
|
||
pruefe(
|
||
'Beim Löschen eines Bereichs fällt die Aufgabe zurück in die Inbox',
|
||
nachBereichLoeschung?.bereichId === null,
|
||
)
|
||
}
|
||
|
||
let abbruch: unknown = null
|
||
|
||
main()
|
||
.catch((fehler: unknown) => {
|
||
abbruch = fehler
|
||
})
|
||
.finally(async () => {
|
||
await aufraeumen()
|
||
|
||
const uebrig =
|
||
(await db.benutzer.count({ where: { email: testEmail } })) +
|
||
(await db.bereich.count({ where: { name: { startsWith: kennung } } }))
|
||
pruefe('Aufräumen hat nichts übrig gelassen', uebrig === 0)
|
||
|
||
await db.$disconnect()
|
||
|
||
if (abbruch) {
|
||
console.error('Der Integrationstest ist abgebrochen:', abbruch)
|
||
process.exit(1)
|
||
}
|
||
|
||
const gefallen = ergebnisse.filter((e) => !e.ok)
|
||
for (const e of ergebnisse) {
|
||
console.log(`${e.ok ? '✓' : '✗'} ${e.name}${e.hinweis ? ` – ${e.hinweis}` : ''}`)
|
||
}
|
||
console.log(
|
||
`\n${ergebnisse.length - gefallen.length} von ${ergebnisse.length} Prüfungen bestanden.`,
|
||
)
|
||
process.exit(gefallen.length === 0 ? 0 : 1)
|
||
})
|