# Sprint 9 Test Plan — Berichtszentrale (Report Center)

**Date:** 2026-06-15
**Author:** Patrick Plate / Lumen (Architect)
**Status:** Draft v2 (updated with panel review advisory items)
**Basis:** cannamanage-sprint9-plan.md v2
**Total Test Cases:** 80

---

## Test Overview

| ID | Description | Type | Class/Location | Status |
|----|-------------|------|----------------|--------|
| T-01 | Destruction record CRUD | Unit | `DestructionRecordServiceTest` | ⬜ |
| T-02 | Transport record CRUD | Unit | `TransportRecordServiceTest` | ⬜ |
| T-03 | Propagation source CRUD | Unit | `PropagationSourceServiceTest` | ⬜ |
| T-04 | Prevention activity CRUD | Unit | `PreventionActivityServiceTest` | ⬜ |
| T-05 | Generated report record keeping | Unit | `GeneratedReportServiceTest` | ⬜ |
| T-06 | Compliance deadline CRUD + status | Unit | `ComplianceDeadlineServiceTest` | ⬜ |
| T-07 | EÜR calculation correctness | Unit | `EurReportGeneratorTest` | ⬜ |
| T-08 | EÜR zero-income edge case | Unit | `EurReportGeneratorTest` | ⬜ |
| T-09 | EÜR multi-category expense grouping | Unit | `EurReportGeneratorTest` | ⬜ |
| T-10 | EÜR CSV encoding ISO-8859-1 | Unit | `EurReportGeneratorTest` | ⬜ |
| T-11 | EÜR decimal comma format | Unit | `EurReportGeneratorTest` | ⬜ |
| T-12 | Kassenbuch export with running balance | Unit | `KassenbuchExportGeneratorTest` | ⬜ |
| T-13 | Kassenbuch period filtering | Unit | `KassenbuchExportGeneratorTest` | ⬜ |
| T-14 | Fee confirmation per member | Unit | `FeeConfirmationGeneratorTest` | ⬜ |
| T-15 | Fee confirmation batch generation | Unit | `FeeConfirmationGeneratorTest` | ⬜ |
| T-16 | Annual authority report - all fields populated | Unit | `AnnualAuthorityReportGeneratorTest` | ⬜ |
| T-17 | Annual authority report - by-strain breakdown | Unit | `AnnualAuthorityReportGeneratorTest` | ⬜ |
| T-18 | Annual authority report - stock reconciliation check | Unit | `AnnualAuthorityReportGeneratorTest` | ⬜ |
| T-19 | Annual authority report - empty year (no activity) | Unit | `AnnualAuthorityReportGeneratorTest` | ⬜ |
| T-20 | Annual authority report - JSON schema validation | Unit | `AnnualAuthorityReportGeneratorTest` | ⬜ |
| T-21 | Distribution log - date range filter | Unit | `DistributionLogGeneratorTest` | ⬜ |
| T-22 | Distribution log - quota violation flagging | Unit | `DistributionLogGeneratorTest` | ⬜ |
| T-23 | Distribution log - Heranwachsende THC limit flag | Unit | `DistributionLogGeneratorTest` | ⬜ |
| T-24 | Distribution log - CSV semicolons + ISO-8859-1 | Unit | `DistributionLogGeneratorTest` | ⬜ |
| T-25 | Stock inventory - point-in-time snapshot | Unit | `StockInventoryReportGeneratorTest` | ⬜ |
| T-26 | Stock inventory - includes propagation material count | Unit | `StockInventoryReportGeneratorTest` | ⬜ |
| T-27 | Destruction protocol - sequential numbering | Unit | `DestructionProtocolGeneratorTest` | ⬜ |
| T-28 | Destruction protocol - witness fields | Unit | `DestructionProtocolGeneratorTest` | ⬜ |
| T-29 | Destruction protocol - annual totals | Unit | `DestructionProtocolGeneratorTest` | ⬜ |
| T-30 | Cultivation report - harvest aggregation by strain | Unit | `CultivationReportGeneratorTest` | ⬜ |
| T-31 | Transport certificate - all §22(4) fields present | Unit | `TransportCertificateGeneratorTest` | ⬜ |
| T-32 | Transport certificate - PDF single page | Unit | `TransportCertificateGeneratorTest` | ⬜ |
| T-33 | Full authority export - ZIP structure valid | Unit | `FullAuthorityExportGeneratorTest` | ⬜ |
| T-34 | Full authority export - JSON files parseable | Unit | `FullAuthorityExportGeneratorTest` | ⬜ |
| T-35 | Full authority export - DSGVO minimization (birth year only) | Unit | `FullAuthorityExportGeneratorTest` | ⬜ |
| T-36 | Full authority export - includes README.txt | Unit | `FullAuthorityExportGeneratorTest` | ⬜ |
| T-37 | Distribution info sheet - all §21(2) fields | Unit | `DistributionInfoSheetGeneratorTest` | ⬜ |
| T-38 | Distribution info sheet - health warnings present | Unit | `DistributionInfoSheetGeneratorTest` | ⬜ |
| T-39 | VVT generator - pre-filled template complete | Unit | `VvtGeneratorTest` | ⬜ |
| T-40 | VVT generator - club-specific data inserted | Unit | `VvtGeneratorTest` | ⬜ |
| T-41 | TOM generator - all 7 control areas present | Unit | `TomGeneratorTest` | ⬜ |
| T-42 | DSFA generator - structure correct | Unit | `DsfaGeneratorTest` | ⬜ |
| T-43 | Deletion concept - all retention categories listed | Unit | `DeletionConceptGeneratorTest` | ⬜ |
| T-44 | Member list registry - §67 BGB format | Unit | `MemberListRegistryGeneratorTest` | ⬜ |
| T-45 | Member list registry - minimal data only | Unit | `MemberListRegistryGeneratorTest` | ⬜ |
| T-46 | Board change notice - old vs new composition | Unit | `BoardChangeNoticeGeneratorTest` | ⬜ |
| T-47 | Annual board report - combines all data sources | Unit | `AnnualBoardReportGeneratorTest` | ⬜ |
| T-48 | Compliance status - GREEN when all obligations met | Unit | `ComplianceDashboardServiceTest` | ⬜ |
| T-49 | Compliance status - YELLOW when deadline within 30 days | Unit | `ComplianceDashboardServiceTest` | ⬜ |
| T-50 | Compliance status - RED when deadline passed | Unit | `ComplianceDashboardServiceTest` | ⬜ |
| T-51 | Compliance status - KCanG area calculation | Unit | `ComplianceDashboardServiceTest` | ⬜ |
| T-52 | Retention service - identifies expired records | Unit | `RetentionServiceTest` | ⬜ |
| T-53 | Retention service - respects different retention periods | Unit | `RetentionServiceTest` | ⬜ |
| T-54 | Retention service - never auto-deletes | Unit | `RetentionServiceTest` | ⬜ |
| T-55 | Deadline scheduler - rolls annual deadlines | Unit | `ComplianceDeadlineSchedulerTest` | ⬜ |
| T-56 | Deadline scheduler - creates notifications | Unit | `ComplianceDeadlineSchedulerTest` | ⬜ |
| T-57 | PDF generation - German umlauts render correctly | Integration | `PdfRenderingTest` | ⬜ |
| T-58 | PDF generation - legal reference in footer | Integration | `PdfRenderingTest` | ⬜ |
| T-59 | PDF generation - club letterhead | Integration | `PdfRenderingTest` | ⬜ |
| T-60 | Report controller - generate EÜR endpoint | Integration | `ReportControllerTest` | ⬜ |
| T-61 | Report controller - authority annual report endpoint | Integration | `ReportControllerTest` | ⬜ |
| T-62 | Report controller - full export ZIP endpoint | Integration | `ReportControllerTest` | ⬜ |
| T-63 | Report controller - permission check (ADMIN only) | Integration | `ReportControllerTest` | ⬜ |
| T-64 | Destruction controller - CRUD + PDF | Integration | `DestructionRecordControllerTest` | ⬜ |
| T-65 | Transport controller - CRUD + certificate | Integration | `TransportRecordControllerTest` | ⬜ |
| T-66 | Sidebar renders grouped navigation | E2E | `navigation.spec.ts` | ⬜ |
| T-67 | Berichtszentrale page loads with compliance cards | E2E | `reports.spec.ts` | ⬜ |
| T-68 | Report download flow (generate → download PDF) | E2E | `reports.spec.ts` | ⬜ |
| T-69 | Rate limiter - 6th report in 1 min returns 429 | Integration | `ReportControllerTest` | ⬜ |
| T-70 | Rate limiter - different tenant not affected | Integration | `ReportControllerTest` | ⬜ |
| T-71 | CSV injection prevention - dangerous cell prefixes escaped | Unit | `CsvExportUtilTest` | ⬜ |
| T-72 | CSV injection - formula in member name does not execute | Unit | `CsvExportUtilTest` | ⬜ |
| T-73 | Authority export re-authentication required | Integration | `ReportControllerTest` | ⬜ |
| T-74 | Authority export - expired reconfirm token rejected | Integration | `ReportControllerTest` | ⬜ |
| T-75 | Authority export - reason field minimum length enforced | Integration | `ReportControllerTest` | ⬜ |
| T-76 | Streaming ZIP - large export does not OOM | Integration | `FullAuthorityExportGeneratorTest` | ⬜ |
| T-77 | Breach notification - Art. 33 section present | Unit | `BreachNotificationGeneratorTest` | ⬜ |
| T-78 | Breach notification - Art. 34 section separate | Unit | `BreachNotificationGeneratorTest` | ⬜ |
| T-79 | Breach notification - 72h deadline reminder included | Unit | `BreachNotificationGeneratorTest` | ⬜ |
| T-80 | Empty-state Berichtszentrale shows onboarding for new club | E2E | `reports.spec.ts` | ⬜ |

Status: ⬜ Pending | ✅ Passed | ❌ Failed | ⏭️ Skipped

---

## Test Cases — Detailed

### T-01: Destruction Record CRUD

**Type:** Unit
**Class:** `DestructionRecordServiceTest`
**Method:** `testCreateDestructionRecord()`, `testListByDateRange()`, `testDeleteNotAllowed()`

**Preconditions:**
- Tenant with active batch in stock

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | Valid destruction (batch, 100g, INCINERATION, reason, witness) | Record created, stock updated (decrease by 100g) |
| b | Destruction with 0 grams | Validation error: grams must be > 0 |
| c | Destruction exceeding batch remaining stock | Validation error: cannot destroy more than available |
| d | List destructions for date range | Only records within range returned |
| e | Delete destruction record | Rejected: destruction records are immutable (audit trail) |

---

### T-07: EÜR Calculation Correctness

**Type:** Unit
**Class:** `EurReportGeneratorTest`
**Method:** `testEurCalculation_normalYear()`

**Preconditions:**
- 12 months of financial transactions (income + expenses)
- Multiple expense categories

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | Year 2025 with €54000 income, €46000 expenses | EÜR shows Überschuss €8000 |
| b | Opening balance €5000, closing balance €13000 | Balance change matches Überschuss |
| c | 6 expense categories | All categories listed with correct subtotals |
| d | Amounts in cents (internal) | Display in Euro with comma decimals (1.234,56) |

---

### T-10: EÜR CSV Encoding ISO-8859-1

**Type:** Unit
**Class:** `EurReportGeneratorTest`
**Method:** `testCsvEncoding()`

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | Category "Büroausstattung" with umlaut | CSV file readable in ISO-8859-1, ü correctly encoded |
| b | Amount 1234.56 | CSV shows "1234,56" (decimal comma) |
| c | Field separator | Semicolons (;) not commas |
| d | Line ending | CRLF (Windows-compatible for DATEV) |

---

### T-16: Annual Authority Report — All Fields Populated

**Type:** Unit
**Class:** `AnnualAuthorityReportGeneratorTest`
**Method:** `testAnnualReport_complete()`

**Preconditions:**
- Grow module has harvest records for 3 strains
- Distribution records for 2025
- 2 destruction records
- Stock snapshot available for Dec 31

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | Year 2025 | Report contains all 4 sections: cultivated, distributed, destroyed, end-stock |
| b | 3 strains with different THC/CBD | Each strain listed separately with correct averages |
| c | Cross-check: cultivated - distributed - destroyed = stock change | Discrepancy < 1g (rounding) or flagged |
| d | JSON output | Valid JSON matching defined schema |
| e | PDF output | Contains legal reference "§26 Abs. 3 KCanG" in footer |

---

### T-22: Distribution Log — Quota Violation Flagging

**Type:** Unit
**Class:** `DistributionLogGeneratorTest`
**Method:** `testQuotaViolationFlags()`

**Preconditions:**
- Member A (age 25): received 26g on one day (exceeds 25g/day limit)
- Member B (age 19): received cannabis with 12% THC (exceeds 10% for Heranwachsende)
- Member C (age 30): received 52g in one month (exceeds 50g/month)

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | Member A daily violation | Report flags: "TAGESLIMIT ÜBERSCHRITTEN: 26g > 25g" |
| b | Member B THC violation | Report flags: "THC-LIMIT HERANWACHSENDE: 12% > 10%" |
| c | Member C monthly violation | Report flags: "MONATSLIMIT ÜBERSCHRITTEN: 52g > 50g" |
| d | Member D (age 25, 20g/day, 45g/month) | No flags — within all limits |

---

### T-33: Full Authority Export — ZIP Structure Valid

**Type:** Unit
**Class:** `FullAuthorityExportGeneratorTest`
**Method:** `testZipStructure()`

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | Generate full export | ZIP contains: README.txt, distributions.json, distributions.csv, stock.json, destructions.json, cultivation.json, transports.json, members.json, summary.pdf |
| b | Each JSON file | Valid JSON, parseable without errors |
| c | members.json | Contains ONLY: name, firstName, birthYear — NO address, NO full DOB, NO phone |
| d | README.txt | Contains: generation date, legal basis reference, file descriptions |

---

### T-35: Full Authority Export — DSGVO Minimization

**Type:** Unit
**Class:** `FullAuthorityExportGeneratorTest`
**Method:** `testDsgvoMinimization()`

**Critical test** — ensures we don't leak unnecessary personal data to authorities.

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | Member with full profile (name, address, phone, email, DOB 1990-05-15) | Export contains only: "Max", "Müller", 1990 |
| b | Distribution record | Contains member name + birth year, NOT member ID or address |
| c | No bank details in any export file | Grep for IBAN patterns returns zero matches |
| d | No email addresses in export | Grep for @ returns zero matches |

---

### T-48: Compliance Status — GREEN When All Obligations Met

**Type:** Unit
**Class:** `ComplianceDashboardServiceTest`
**Method:** `testGreenStatus()`

**Preconditions:**
- Annual authority report generated for previous year
- EÜR generated for previous year
- VVT exists and updated within last 12 months
- Next MV scheduled
- All board terms valid

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | All conditions met | Overall status: GREEN |
| b | KCanG area | GREEN: annual report submitted, records complete |
| c | Finance area | GREEN: EÜR generated, no overdue deadlines |
| d | DSGVO area | GREEN: VVT exists and recent |
| e | Verein area | GREEN: MV scheduled, board terms valid |

---

### T-50: Compliance Status — RED When Deadline Passed

**Type:** Unit
**Class:** `ComplianceDashboardServiceTest`
**Method:** `testRedStatus()`

**Preconditions:**
- Current date: February 15
- Annual authority report for previous year NOT generated (deadline was Jan 31)

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | Authority report overdue | KCanG status: RED |
| b | Overall status | RED (worst of all areas) |
| c | Deadline record | Status marked OVERDUE |

---

### T-52: Retention Service — Identifies Expired Records

**Type:** Unit
**Class:** `RetentionServiceTest`
**Method:** `testIdentifiesExpiredRecords()`

**Preconditions:**
- Distribution record created 6 years ago (past 5-year KCanG retention)
- Financial transaction created 11 years ago (past 10-year AO retention)
- MV protocol from 20 years ago (indefinite retention — should NOT be flagged)

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | 6-year-old distribution record | Flagged for deletion review |
| b | 11-year-old financial transaction | Flagged for deletion review |
| c | 20-year-old MV protocol | NOT flagged (indefinite retention) |
| d | 4-year-old distribution record | NOT flagged (within 5-year period) |

---

### T-54: Retention Service — Never Auto-Deletes

**Type:** Unit
**Class:** `RetentionServiceTest`
**Method:** `testNeverAutoDeletes()`

**Critical safety test.**

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | Run retention check with expired records | No records deleted from database |
| b | Expired record | Status changed to "RETENTION_EXPIRED", admin notification created |
| c | Admin confirms deletion | THEN record is soft-deleted (retention log entry created) |
| d | Without admin confirmation | Record persists indefinitely |

---

### T-57: PDF Generation — German Umlauts Render Correctly

**Type:** Integration
**Class:** `PdfRenderingTest`
**Method:** `testGermanCharacters()`

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | Text with äöüÄÖÜß | All characters render correctly in PDF |
| b | Club name "Grüner Daumen e.V." | Renders correctly in letterhead |
| c | Category "Büroausstattung" | Renders correctly in EÜR table |
| d | Legal text "§26 Abs. 3 KCanG" | § symbol renders correctly |
| e | Euro amounts "1.234,56 €" | Euro sign renders correctly |

---

### T-63: Report Controller — Permission Check

**Type:** Integration
**Class:** `ReportControllerTest`
**Method:** `testPermissions()`

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | ADMIN requests EÜR | 200 OK + PDF |
| b | STAFF (Kassenwart) requests EÜR | 200 OK + PDF (finance permission) |
| c | MEMBER requests EÜR | 403 Forbidden |
| d | ADMIN requests authority export | 200 OK + ZIP |
| e | STAFF without finance permission requests EÜR | 403 Forbidden |
| f | Unauthenticated request | 401 Unauthorized |

---

### T-66: Sidebar Renders Grouped Navigation

**Type:** E2E (Playwright)
**File:** `cannamanage-frontend/e2e/authenticated/navigation.spec.ts`

**Scenarios:**

| # | Action | Expected Result |
|---|--------|----------------|
| a | Load dashboard page | Sidebar shows 4 groups: Betrieb, Kommunikation, Verwaltung, Compliance |
| b | Click group header "Kommunikation" | Group collapses/expands |
| c | Navigate to /reports | "Berichtszentrale" item highlighted in Compliance group |
| d | All existing URLs still work | /members, /distributions, /stock, /grow, /finance, /assemblies all accessible |

---

### T-67: Berichtszentrale Page Loads

**Type:** E2E (Playwright)
**File:** `cannamanage-frontend/e2e/authenticated/reports.spec.ts`

**Scenarios:**

| # | Action | Expected Result |
|---|--------|----------------|
| a | Navigate to /reports | Page loads with compliance status cards |
| b | Compliance cards visible | 4 cards: KCanG, Finanzen, DSGVO, Verein — each with status color |
| c | Report categories visible | 4 category sections with report listings |
| d | Click "EÜR generieren" | Year picker appears, generate button enabled |
| e | Click "Behörden-Export" | Confirmation dialog shown (due to data sensitivity) |

---

### T-68: Report Download Flow

**Type:** E2E (Playwright)
**File:** `cannamanage-frontend/e2e/authenticated/reports.spec.ts`

**Scenarios:**

| # | Action | Expected Result |
|---|--------|----------------|
| a | Select year 2025, click generate EÜR PDF | Download triggered, file non-empty |
| b | Generate distribution log for last month | Download triggered, PDF contains data |
| c | Report appears in history table | History shows: type, date, generated by |
| d | Re-download from history | Same file downloadable again |

---

### T-69: Rate Limiter - 6th Report in 1 Minute Returns 429

**Type:** Integration
**Class:** `ReportControllerTest`
**Method:** `testRateLimit_sixthRequestReturns429()`

**Preconditions:**
- Authenticated admin user
- Resilience4j rate limiter configured: 5 permits/minute/tenant

**Scenarios:**

| # | Action | Expected Result |
|---|--------|----------------|
| a | Generate 5 reports in rapid succession | All return 200 OK |
| b | Generate 6th report within same minute | Returns 429 Too Many Requests |
| c | Response body contains "Rate limit exceeded" message | Helpful German error message |
| d | Response includes `Retry-After` header | Header present with seconds value |

---

### T-70: Rate Limiter - Different Tenant Not Affected

**Type:** Integration
**Class:** `ReportControllerTest`
**Method:** `testRateLimit_differentTenantNotBlocked()`

**Preconditions:**
- Two tenants configured in test

**Scenarios:**

| # | Action | Expected Result |
|---|--------|----------------|
| a | Tenant A generates 5 reports (hitting limit) | All return 200 |
| b | Tenant B generates 1 report immediately after | Returns 200 OK (not affected by A's limit) |

---

### T-71: CSV Injection Prevention - Dangerous Cell Prefixes Escaped

**Type:** Unit
**Class:** `CsvExportUtilTest`
**Method:** `testCsvInjection_dangerousPrefixesEscaped()`

**Preconditions:**
- CSV export utility available

**Scenarios:**

| # | Input Cell Value | Expected Output |
|---|-----------------|----------------|
| a | `=SUM(A1:A10)` | `'=SUM(A1:A10)` |
| b | `+cmd\|'/C calc'\|''!A0` | `'+cmd\|'/C calc'\|''!A0` |
| c | `-2+3+cmd\|'/C calc'\|'!A0` | `'-2+3+cmd\|'/C calc'\|'!A0` |
| d | `@SUM(A1:A10)` | `'@SUM(A1:A10)` |
| e | `Normal text value` | `Normal text value` (unchanged) |
| f | `123.45` | `123.45` (numbers unchanged) |

---

### T-72: CSV Injection - Formula in Member Name Does Not Execute

**Type:** Unit
**Class:** `CsvExportUtilTest`
**Method:** `testCsvInjection_memberNameWithFormula()`

**Preconditions:**
- Member with name `=HYPERLINK("http://evil.com","Click")` in database

**Scenarios:**

| # | Action | Expected Result |
|---|--------|----------------|
| a | Export distribution log as CSV with this member | Cell contains `'=HYPERLINK(...)` with leading quote |
| b | Open CSV in LibreOffice Calc | No formula execution, displays as text |

---

### T-73: Authority Export Re-Authentication Required

**Type:** Integration
**Class:** `ReportControllerTest`
**Method:** `testAuthorityExport_requiresReauthentication()`

**Preconditions:**
- Authenticated admin user with valid session

**Scenarios:**

| # | Action | Expected Result |
|---|--------|----------------|
| a | Call `GET /api/reports/authority-export/zip` without reconfirm token | Returns 403 Forbidden |
| b | Call `POST /api/auth/reconfirm` with correct password | Returns fresh token (valid 30s) |
| c | Call authority export with valid reconfirm token within 30s | Returns 200 with ZIP stream |
| d | Audit log entry created | Records: who, when, reason, IP address |

---

### T-74: Authority Export - Expired Reconfirm Token Rejected

**Type:** Integration
**Class:** `ReportControllerTest`
**Method:** `testAuthorityExport_expiredTokenRejected()`

**Preconditions:**
- Reconfirm token obtained but 31+ seconds elapsed

**Scenarios:**

| # | Action | Expected Result |
|---|--------|----------------|
| a | Call authority export with 31-second-old reconfirm token | Returns 403 Forbidden |
| b | Error message indicates re-authentication required | `"Bestätigung abgelaufen. Bitte erneut authentifizieren."` |

---

### T-75: Authority Export - Reason Field Minimum Length Enforced

**Type:** Integration
**Class:** `ReportControllerTest`
**Method:** `testAuthorityExport_reasonFieldValidation()`

**Preconditions:**
- Valid reconfirm token

**Scenarios:**

| # | Reason Value | Expected Result |
|---|-------------|----------------|
| a | `""` (empty) | 400 Bad Request — reason required |
| b | `"test"` (4 chars) | 400 Bad Request — minimum 10 characters |
| c | `"."` (1 char) | 400 Bad Request — minimum 10 characters |
| d | `"Behördenanfrage vom 15.06.2026"` (30 chars) | 200 OK — accepted |
| e | `"Jährliche Prüfung der Dokumentation"` | 200 OK — accepted |

---

### T-76: Streaming ZIP - Large Export Does Not OOM

**Type:** Integration
**Class:** `FullAuthorityExportGeneratorTest`
**Method:** `testStreamingZip_largeDataDoesNotOOM()`

**Preconditions:**
- Test data with 500 members, 5000 distributions, 200 destruction records
- JVM heap limited to 256MB in test configuration

**Scenarios:**

| # | Action | Expected Result |
|---|--------|----------------|
| a | Generate full authority export with large dataset | Completes without OutOfMemoryError |
| b | Response is streamed (chunked transfer encoding) | `Transfer-Encoding: chunked` header present |
| c | ZIP file is valid and contains all expected entries | All 8 files present in ZIP |
| d | Peak memory usage stays below heap limit | No GC pressure spikes above 200MB |

---

### T-77: Breach Notification - Art. 33 Section Present

**Type:** Unit
**Class:** `BreachNotificationGeneratorTest`
**Method:** `testBreachNotification_article33SectionComplete()`

**Preconditions:**
- Club with configured DPO and Landesdatenschutzbehörde contact

**Scenarios:**

| # | Check | Expected Content |
|---|-------|-----------------|
| a | Nature of breach field | Present and editable placeholder |
| b | Categories of data subjects | Pre-filled: "Mitglieder der Anbauvereinigung" |
| c | Approximate number affected | Editable field with club member count pre-filled |
| d | DPO contact details | Auto-filled from club settings |
| e | Likely consequences | Pre-filled with Art. 9 health data risk template |
| f | Measures taken/proposed | Editable placeholder |
| g | 72-hour deadline reminder | Bold text: "Meldung innerhalb 72 Stunden nach Kenntnisnahme" |

---

### T-78: Breach Notification - Art. 34 Section Separate

**Type:** Unit
**Class:** `BreachNotificationGeneratorTest`
**Method:** `testBreachNotification_article34SectionSeparate()`

**Preconditions:**
- Same as T-77

**Scenarios:**

| # | Check | Expected Result |
|---|-------|----------------|
| a | Art. 34 section has separate heading | "Benachrichtigung der Betroffenen (Art. 34 DSGVO)" |
| b | Plain language requirement noted | "In klarer und einfacher Sprache" instruction present |
| c | Template is distinct from Art. 33 section | Not merged — separate page/section in PDF |
| d | Includes contact details for data subjects | Phone, email, postal address fields |

---

### T-79: Breach Notification - 72h Deadline Reminder Included

**Type:** Unit
**Class:** `BreachNotificationGeneratorTest`
**Method:** `testBreachNotification_72hDeadlineReminder()`

**Preconditions:**
- Generated breach notification PDF

**Scenarios:**

| # | Check | Expected Content |
|---|-------|-----------------|
| a | Deadline prominently displayed | "72 Stunden" appears in bold or highlighted |
| b | Authority contact for notification | Landesdatenschutzbehörde name + URL/email |
| c | "Zeitpunkt der Kenntnisnahme" field | Date/time field for when breach was discovered |
| d | Countdown note | "Frist beginnt ab Kenntnisnahme, nicht ab Entdeckung" |

---

### T-80: Empty-State Berichtszentrale Shows Onboarding for New Club

**Type:** E2E (Playwright)
**File:** `cannamanage-frontend/e2e/authenticated/reports.spec.ts`

**Preconditions:**
- New tenant with zero generated reports

**Scenarios:**

| # | Action | Expected Result |
|---|--------|----------------|
| a | Navigate to /reports as new club admin | "Erste Schritte" banner visible |
| b | Compliance cards show neutral state | Gray "Einrichtung erforderlich" — NOT red indicators |
| c | 4-step guide links present | VVT, Jahresbericht, Kassenbuch, Fristen links all working |
| d | Click "Verstanden, Dashboard anzeigen" | Banner dismissed, normal view shown |
| e | Generate one report in any category | After refresh, traffic-light indicators appear |
| f | Banner does not reappear after dismissal | LocalStorage persists dismissal |

---

## Test Data Requirements

### Seed Data Enhancements

For test scenarios to work, seed data must include:

| Data | Quantity | Details |
|------|----------|---------|
| Financial transactions | 50+ | Spanning 12 months, multiple categories |
| Distributions with THC% | 30+ | Various strains, some near-limit quantities |
| Destruction records | 3 | Different methods, dates, witnesses |
| Transport records | 2 | With authority notification timestamps |
| Propagation sources | 2 | One person, one Anbauvereinigung |
| Prevention activities | 5 | Different activity types |
| Grow harvests with THC/CBD | 5 | Different strains, quantities |
| Stock entries | 10+ | Current stock by strain |
| Compliance deadlines | 4 | One per area, various due dates |
| Members aged 18-21 | 2 | For Heranwachsende limit testing |

---

## Test Coverage Summary

| Component | Unit Tests | Integration Tests | E2E Tests | Total |
|-----------|-----------|------------------|-----------|-------|
| Phase 1 (CRUD) | 6 | 2 | 0 | 8 |
| Phase 2 (Financial) | 9 | 1 | 0 | 10 |
| Phase 3 (KCanG) | 23 | 3 | 0 | 26 |
| Phase 4 (DSGVO/Verein) | 12 | 0 | 0 | 12 |
| Phase 5 (Frontend) | 0 | 0 | 4 | 4 |
| Phase 6 (Compliance + Security) | 9 | 8 | 0 | 17 |
| Cross-cutting (CSV util) | 2 | 0 | 0 | 2 |
| Breach notification (v2) | 3 | 0 | 0 | 3 |
| **Total** | **64** | **14** | **4** | **80** (was 68) |

---

## Critical Test Priorities

Tests marked as **CRITICAL** — must pass before sprint completion:

| ID | Test | Why Critical |
|----|------|-------------|
| T-16 | Annual authority report complete | Legal obligation — §26(3) KCanG |
| T-22 | Quota violation flagging | Safety — prevents illegal distribution |
| T-33 | Authority export ZIP structure | Authority inspection readiness |
| T-35 | DSGVO minimization in exports | Privacy violation risk |
| T-54 | Retention service never auto-deletes | Data loss prevention |
| T-57 | German umlauts in PDFs | Usability — broken characters = unprofessional |
| T-63 | Permission checks | Security — unauthorized report access |
| T-69 | Rate limiter enforced | Security — prevents DoS via report generation |
| T-71 | CSV injection prevention | Security — prevents formula injection in exports |
| T-73 | Authority export re-authentication | Security — protects Art. 9 DSGVO health data |
| T-76 | Streaming ZIP no OOM | Reliability — prevents server crash on large exports |
| T-77 | Breach notification Art. 33 complete | Compliance — 72h notification obligation |

---

## Test Naming Convention

- Test class: `<OriginalClass>Test.java`
- Test method: `test<What>_<Scenario>()` or descriptive name
- Location: mirrors source structure under `src/test/java/`
- Assertions: use AssertJ for fluent assertions
- Mocking: Mockito for service dependencies
- PDF verification: extract text from generated PDF bytes using Apache PDFBox in test scope
