Files
lernbar/src/lib/kontrollen/sicht.test.ts
T
Florian WildeandClaude Opus 5 d226a6bae8 Baustein 8: Lernzielkontrollen mit Freigabe, Abgabe und Korrektur
- Ausbilder gibt frei; vorher sieht der Azubi die Fragen nicht
- Freigabe laesst sich zuruecknehmen, solange nicht abgegeben wurde
- Antworten werden beim Tippen je Frage zwischengespeichert
- Musterloesungen erst nach dem Absenden: sichtbareKontrolle() entfernt sie
  serverseitig, bevor die Daten in die Ausgabe gelangen
- Korrektur mit Punkten und Anmerkung je Frage, Summe und Note laufen mit
- Notenschluessel der IHK; solange eine Frage offen ist, bleibt das Ergebnis
  vorlaeufig und die Kontrolle gilt nicht als korrigiert
- Ergebnis bei beiden sichtbar, Schnitt ueber alle Kontrollen
- Abgegebene Kontrollen unter "Was auf dich wartet" und im Wochenfortschritt
- scripts/pruefungen/musterloesung-dichtheit.mjs prueft die Sperre gegen den
  echten Wortlaut aus der Inhaltsdatei im rohen Seitenquelltext

36 neue Tests, davon 21 fuer die Sichtbarkeit der Musterloesung.
Insgesamt 277.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 08:57:00 +02:00

179 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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> = {}): 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);
});
});