# 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: `Test.java` - Test method: `test_()` 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