import { describe, expect, it } from 'vitest'; import { darfBearbeiten, darfFragenSehen, gesamtpunkte, sichtbareKontrolle, zustandVon, type Bearbeitungsstand, } from './sicht'; import type { Kontrolle } from '../inhalte/schema'; import type { Betrachter } from '../auth/regeln'; const kontrolle: Kontrolle = { id: 'woche-03', woche: 3, titel: 'Lernzielkontrolle Woche 3', dauer_minuten: 45, hinweis: 'Offene Fragen.', fragen: [ { id: 'f1', frage: 'Was ist die Blockgröße bei /27?', punkte: 8, musterloesung: 'GEHEIM-EINS: 32.' }, { id: 'f2', frage: 'Wozu dient DHCP?', punkte: 5, musterloesung: 'GEHEIM-ZWEI: Adressvergabe.' }, ], }; const azubi: Betrachter = { id: 'azu-1', rolle: 'azubi', aktiv: 1 }; const andererAzubi: Betrachter = { id: 'azu-2', rolle: 'azubi', aktiv: 1 }; const ausbilder: Betrachter = { id: 'aus-1', rolle: 'ausbilder', aktiv: 1 }; const fremderAusbilder: Betrachter = { id: 'aus-9', rolle: 'ausbilder', aktiv: 1 }; const bezug = { azubiId: 'azu-1', ausbilderId: 'aus-1' }; function stand(teil: Partial = {}): Bearbeitungsstand { return { azubiId: 'azu-1', freigegebenAm: null, abgegebenAm: null, ...teil }; } /** Findet heraus, ob irgendwo im Ergebnis noch eine Musterlösung steckt. */ function enthaeltGeheimnis(wert: unknown): boolean { return JSON.stringify(wert).includes('GEHEIM'); } describe('Musterlösung vor dem Absenden', () => { it('kommt beim gesperrten Zustand nicht mit', () => { const sicht = sichtbareKontrolle(kontrolle, azubi, stand(), bezug); expect(sicht.musterloesungSichtbar).toBe(false); expect(enthaeltGeheimnis(sicht)).toBe(false); }); it('kommt auch nach der Freigabe nicht mit, solange nicht abgegeben wurde', () => { const sicht = sichtbareKontrolle(kontrolle, azubi, stand({ freigegebenAm: '2026-09-21T07:00:00.000Z' }), bezug); expect(sicht.musterloesungSichtbar).toBe(false); expect(sicht.fragen.every((f) => f.musterloesung === null)).toBe(true); expect(enthaeltGeheimnis(sicht)).toBe(false); }); it('kommt nach dem Absenden mit', () => { const sicht = sichtbareKontrolle( kontrolle, azubi, stand({ freigegebenAm: '2026-09-21T07:00:00.000Z', abgegebenAm: '2026-09-21T08:00:00.000Z' }), bezug, ); expect(sicht.musterloesungSichtbar).toBe(true); expect(sicht.fragen[0]!.musterloesung).toContain('GEHEIM-EINS'); }); it('kommt für den zuständigen Ausbilder jederzeit mit', () => { const sicht = sichtbareKontrolle(kontrolle, ausbilder, stand(), bezug); expect(sicht.musterloesungSichtbar).toBe(true); expect(sicht.fragen[1]!.musterloesung).toContain('GEHEIM-ZWEI'); }); it('kommt für einen fremden Ausbilder nie mit', () => { const sicht = sichtbareKontrolle( kontrolle, fremderAusbilder, stand({ abgegebenAm: '2026-09-21T08:00:00.000Z' }), bezug, ); expect(sicht.musterloesungSichtbar).toBe(false); expect(enthaeltGeheimnis(sicht)).toBe(false); }); it('kommt für einen anderen Azubi nie mit', () => { const sicht = sichtbareKontrolle( kontrolle, andererAzubi, stand({ abgegebenAm: '2026-09-21T08:00:00.000Z' }), bezug, ); expect(sicht.musterloesungSichtbar).toBe(false); expect(enthaeltGeheimnis(sicht)).toBe(false); }); it('kommt bei gesperrtem Konto nie mit', () => { const sicht = sichtbareKontrolle( kontrolle, { ...azubi, aktiv: 0 }, stand({ abgegebenAm: '2026-09-21T08:00:00.000Z' }), bezug, ); expect(enthaeltGeheimnis(sicht)).toBe(false); }); it('kommt ohne Ausbildungsverhältnis für den Ausbilder nicht mit', () => { const sicht = sichtbareKontrolle(kontrolle, ausbilder, stand(), null); expect(enthaeltGeheimnis(sicht)).toBe(false); }); it('lässt Frage, Punkte und Titel immer durch – nur die Lösung fehlt', () => { const sicht = sichtbareKontrolle(kontrolle, azubi, stand(), bezug); expect(sicht.titel).toBe('Lernzielkontrolle Woche 3'); expect(sicht.fragen[0]!.frage).toContain('Blockgröße'); expect(sicht.fragen[0]!.punkte).toBe(8); expect(sicht.gesamtpunkte).toBe(13); }); }); describe('Fragen sehen', () => { it('zeigt dem Azubi vor der Freigabe nichts', () => { expect(darfFragenSehen(azubi, stand(), bezug)).toBe(false); }); it('zeigt sie ihm ab der Freigabe', () => { expect(darfFragenSehen(azubi, stand({ freigegebenAm: '2026-09-21T07:00:00.000Z' }), bezug)).toBe(true); }); it('zeigt sie dem zuständigen Ausbilder jederzeit – er muss sie vorher durchsehen', () => { expect(darfFragenSehen(ausbilder, stand(), bezug)).toBe(true); }); it('zeigt sie einem fremden Ausbilder nie', () => { expect(darfFragenSehen(fremderAusbilder, stand({ freigegebenAm: '2026-09-21T07:00:00.000Z' }), bezug)).toBe(false); }); it('zeigt sie einem anderen Azubi nie', () => { expect(darfFragenSehen(andererAzubi, stand({ freigegebenAm: '2026-09-21T07:00:00.000Z' }), bezug)).toBe(false); }); }); describe('Bearbeiten', () => { it('geht nur zwischen Freigabe und Abgabe', () => { expect(darfBearbeiten(azubi, stand())).toBe(false); expect(darfBearbeiten(azubi, stand({ freigegebenAm: '2026-09-21T07:00:00.000Z' }))).toBe(true); expect( darfBearbeiten(azubi, stand({ freigegebenAm: '2026-09-21T07:00:00.000Z', abgegebenAm: '2026-09-21T08:00:00.000Z' })), ).toBe(false); }); it('geht für den Ausbilder nie – er korrigiert, er schreibt nicht mit', () => { expect(darfBearbeiten(ausbilder, stand({ freigegebenAm: '2026-09-21T07:00:00.000Z' }))).toBe(false); }); it('geht für einen anderen Azubi nie', () => { expect(darfBearbeiten(andererAzubi, stand({ freigegebenAm: '2026-09-21T07:00:00.000Z' }))).toBe(false); }); it('geht bei gesperrtem Konto nicht', () => { expect(darfBearbeiten({ ...azubi, aktiv: 0 }, stand({ freigegebenAm: '2026-09-21T07:00:00.000Z' }))).toBe(false); }); }); describe('Zustand', () => { it('benennt die vier Stufen', () => { expect(zustandVon(stand())).toBe('gesperrt'); expect(zustandVon(stand({ freigegebenAm: 'x' }))).toBe('freigegeben'); expect(zustandVon(stand({ freigegebenAm: 'x', abgegebenAm: 'y' }))).toBe('abgegeben'); expect(zustandVon({ ...stand({ freigegebenAm: 'x', abgegebenAm: 'y' }), korrigiertAm: 'z' })).toBe('korrigiert'); }); }); describe('Gesamtpunkte', () => { it('summiert die Punkte aller Fragen', () => { expect(gesamtpunkte(kontrolle)).toBe(13); }); it('kommt mit einer Kontrolle ohne Fragen zurecht', () => { expect(gesamtpunkte({ fragen: [] })).toBe(0); }); });