- 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>
179 lines
6.5 KiB
TypeScript
179 lines
6.5 KiB
TypeScript
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);
|
||
});
|
||
});
|