Claude dc09999472
feat(ai-act-kompass): verified evidence chain for the AI literacy test
- Manual participant entry for paper tests: name + correct answers →
  register entry with source 'paper' (app runs are tagged 'app')
- Scan upload per register entry: marked paper sheets (image/PDF,
  max 1 MB) stored locally as data URL and anchored with a SHA-256
  checksum of the original file; view scan in-app, checksum shown
- Document verification: certificate and training register printouts
  carry a SHA-256 checksum footer over the document content — a
  presented printout can be re-verified against the tool; certificate
  additionally references its register entry ID (evidence chain
  certificate ↔ register ↔ scan)
- Register printout extended with source, evidence checksum and ID
  columns; record deletion with confirmation warning
- UI labels in all nine languages; 73 tests green

Claude-Session: https://claude.ai/code/session_017YjohVNx1ZGNM4MX7tHpfv
2026-07-04 11:35:58 +00:00

140 lines
5.3 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 { CONTENT_PACK } from '../content/pack';
import {
answerKeyDoc,
certificateDoc,
getQuiz,
handoutDoc,
printableTestDoc,
scoreQuiz,
trainingLogDoc,
} from './quiz';
import { QUIZ_PASS_PCT, QUIZ_QUESTIONS } from './quizContent';
import type { CompanyProfile } from './types';
const profile: CompanyProfile = {
name: 'Muster GmbH',
street: '',
zipCity: '',
contactPerson: '',
email: '',
phone: '',
website: '',
logoDataUrl: '',
countries: [],
};
describe('KI-Kompetenztest', () => {
it('hat 12 einfache Fragen mit je 3 Optionen und gültigem Lösungsindex', () => {
expect(QUIZ_QUESTIONS).toHaveLength(12);
for (const q of QUIZ_QUESTIONS) {
expect(q.options).toHaveLength(3);
expect([0, 1, 2]).toContain(q.correctIndex);
}
});
it('löst Fragen lokalisiert auf (DE und EN unterscheiden sich)', () => {
const de = getQuiz('de');
const en = getQuiz('en');
expect(de).toHaveLength(en.length);
expect(de[0]?.question).not.toBe(en[0]?.question);
});
it('wertet korrekt aus: alle richtig = bestanden, alle falsch = durchgefallen', () => {
const perfect = scoreQuiz(QUIZ_QUESTIONS.map((q) => q.correctIndex));
expect(perfect.pct).toBe(100);
expect(perfect.passed).toBe(true);
const wrong = scoreQuiz(QUIZ_QUESTIONS.map((q) => ((q.correctIndex + 1) % 3) as 0 | 1 | 2));
expect(wrong.correct).toBe(0);
expect(wrong.passed).toBe(false);
});
it('wendet die Bestehensgrenze an (knapp darunter = durchgefallen)', () => {
// 8 von 12 = 67 % < 70 % → nicht bestanden; 9 von 12 = 75 % → bestanden
const eight = scoreQuiz(
QUIZ_QUESTIONS.map((q, i) => (i < 8 ? q.correctIndex : ((q.correctIndex + 1) % 3) as 0 | 1 | 2)),
);
expect(eight.passed).toBe(false);
const nine = scoreQuiz(
QUIZ_QUESTIONS.map((q, i) => (i < 9 ? q.correctIndex : ((q.correctIndex + 1) % 3) as 0 | 1 | 2)),
);
expect(nine.passed).toBe(true);
expect(QUIZ_PASS_PCT).toBe(70);
});
it('erzeugt ein Zertifikat mit Name, Ergebnis, Art.-4-Bezug und Disclaimer', () => {
const score = scoreQuiz(QUIZ_QUESTIONS.map((q) => q.correctIndex));
const md = certificateDoc('Max Mustermann', score, CONTENT_PACK, '2026-07-04', profile);
expect(md).toContain('Nachweis KI-Kompetenz (Art. 4');
expect(md).toContain('Max Mustermann');
expect(md).toContain('BESTANDEN');
expect(md).toContain('Muster GmbH');
expect(md).toContain('keine Rechtsberatung');
});
it('Papierfassung enthält alle Fragen, Ankreuzfelder und Auswertungszeile', () => {
const md = printableTestDoc(CONTENT_PACK, '2026-07-04', profile);
expect(md).toContain('Papierfassung');
expect((md.match(/☐/g) ?? []).length).toBe(36); // 12 Fragen × 3 Optionen
expect(md).toContain('**Name:**');
expect(md).toContain('Ergebnis: ______ / 12');
expect(md).not.toContain('richtige Antwort'); // keine Lösungen im Testbogen
});
it('Lösungsbogen enthält je Frage den Fragetext, die richtige Antwort im Wortlaut und die Erklärung', () => {
const md = answerKeyDoc(CONTENT_PACK, '2026-07-04', profile);
expect(md).toContain('Lösungsbogen');
// Fragetext enthalten (selbsterklärend, ohne Testbogen daneben):
expect((md.match(/^## \d+\./gm) ?? []).length).toBe(12);
expect(md).toContain('Was macht ein KI-System');
// Richtige Antwort mit Buchstabe UND Wortlaut:
expect((md.match(/\*\*Richtige Antwort: [ABC]\)\*\*/g) ?? []).length).toBe(12);
expect(md).toContain('Nein — nur in vom Unternehmen freigegebene KI-Systeme.');
// Erklärungen enthalten:
expect((md.match(/_Erklärung:_/g) ?? []).length).toBe(12);
});
it('Schulungshandout enthält die 12 Erklärungen und ist lokalisiert', () => {
const de = handoutDoc(CONTENT_PACK, '2026-07-04', profile, 'de');
expect(de).toContain('Schulungshandout');
expect((de.match(/^## \d+\./gm) ?? []).length).toBe(12);
const en = handoutDoc(CONTENT_PACK, '2026-07-04', profile, 'en');
expect(en).toContain('Training handout');
expect(en).not.toBe(de);
});
it('erzeugt das Schulungsregister mit Quelle, Beleg-Prüfsumme und ID', () => {
const md = trainingLogDoc(
[
{
id: 'abcdef12-3456',
name: 'Max Mustermann',
date: '2026-07-04',
scorePct: 92,
passed: true,
source: 'paper',
attachment: { fileName: 'scan.pdf', sha256: 'f'.repeat(64), dataUrl: 'data:application/pdf;base64,x' },
},
{ id: '2', name: 'Erika Musterfrau', date: '2026-07-03', scorePct: 58, passed: false },
],
CONTENT_PACK,
'2026-07-04',
profile,
);
expect(md).toContain('Schulungsregister');
expect(md).toContain('| Papier |'); // Quelle Papier-Test
expect(md).toContain('| App |'); // Alt-Einträge ohne source = App
expect(md).toContain('`ffffffffffffffff…`'); // Beleg-Prüfsumme (gekürzt)
expect(md).toContain('`abcdef12`'); // Register-ID (gekürzt)
expect(md).toContain('NICHT BESTANDEN');
});
it('Zertifikat enthält die Register-ID für die Nachweiskette', () => {
const score = scoreQuiz(QUIZ_QUESTIONS.map((q) => q.correctIndex));
const md = certificateDoc('Max', score, CONTENT_PACK, '2026-07-04', profile, 'de', 'abcdef12-3456-7890');
expect(md).toContain('Register-ID');
expect(md).toContain('`abcdef12`');
});
});