# Testplan: Sprint 14 — Marketing & Monetization **Date:** 2026-06-18 **Author:** Patrick Plate / Lumen (Planner) **Status:** v2 **Basis:** cannamanage-sprint14-plan.md --- ## Test Overview | ID | Description | Type | Class/Tool | Status | |----|-------------|------|------------|--------| | T-01 | Landing page renders all sections | E2E | Playwright | ⬜ | | T-02 | Landing page responsive (mobile) | E2E | Playwright | ⬜ | | T-03 | Landing page dark/light mode | E2E | Playwright | ⬜ | | T-04 | Landing page CTA links work | E2E | Playwright | ⬜ | | T-05 | Marketing nav shows Features link | E2E | Playwright | ⬜ | | T-06 | Admin login — split layout on desktop | E2E | Playwright | ⬜ | | T-07 | Admin login — full-width on mobile | E2E | Playwright | ⬜ | | T-08 | Admin login — form still functional | E2E | Playwright | ⬜ | | T-09 | Portal login — member-themed split | E2E | Playwright | ⬜ | | T-10 | Portal login — form functional | E2E | Playwright | ⬜ | | T-11 | Pricing — storage tiers displayed | E2E | Playwright | ⬜ | | T-12 | Pricing — comparison table with storage row | E2E | Playwright | ⬜ | | T-13 | Pricing — FAQ storage entry visible | E2E | Playwright | ⬜ | | T-14 | Storage usage — correct calculation | Unit | `StorageQuotaServiceTest` | ⬜ | | T-15 | Storage quota — allows upload under limit | Unit | `StorageQuotaServiceTest` | ⬜ | | T-16 | Storage quota — rejects upload over limit | Unit | `StorageQuotaServiceTest` | ⬜ | | T-17 | Storage quota — increment on upload | Unit | `StorageQuotaServiceTest` | ⬜ | | T-18 | Storage quota — decrement on delete | Unit | `StorageQuotaServiceTest` | ⬜ | | T-19 | Storage quota — decrement floors at zero | Unit | `StorageQuotaServiceTest` | ⬜ | | T-20 | Storage quota — tier limit mapping | Unit | `StorageQuotaServiceTest` | ⬜ | | T-21 | Storage quota — near-limit detection (80%) | Unit | `StorageQuotaServiceTest` | ⬜ | | T-22 | Storage quota — near-limit detection (95%) | Unit | `StorageQuotaServiceTest` | ⬜ | | T-23 | GET /api/v1/storage/usage — authenticated | Integration | `StorageControllerTest` | ⬜ | | T-24 | GET /api/v1/storage/usage — unauthenticated 401 | Integration | `StorageControllerTest` | ⬜ | | T-25 | GET /api/v1/storage/usage — correct DTO shape | Integration | `StorageControllerTest` | ⬜ | | T-26 | Document upload — quota check integrated | Integration | `DocumentServiceTest` | ⬜ | | T-27 | Document upload — 402 on quota exceeded | Integration | `DocumentControllerTest` | ⬜ | | T-28 | Document delete — usage decremented | Integration | `DocumentServiceTest` | ⬜ | | T-29 | Flyway V36 — migration applies cleanly | Integration | Flyway boot test | ⬜ | | T-30 | Flyway V36 — backfill calculates correctly | Integration | SQL verification | ⬜ | | T-31 | StorageQuotaExceededException — 402 response format | Unit | `GlobalExceptionHandlerTest` | ⬜ | | T-32 | i18n — all marketing.home keys resolve (de + en) | Unit | Lint / next-intl | ⬜ | Status: ⬜ Open | ✅ Passed | ❌ Failed | ⏭️ Skipped --- ## Test Cases ### T-01: Landing Page Renders All Sections **Type:** E2E **Tool:** Playwright **Script:** `e2e/marketing/landing-page.spec.ts` **Preconditions:** - App running at localhost:3000 - No authentication required (public page) **Scenarios:** | # | Action | Expected Result | |---|--------|-----------------| | a | Navigate to `/` | Page loads without errors | | b | Check hero section | Headline text visible, CTA buttons present | | c | Check feature grid | 6 feature cards visible with titles and descriptions | | d | Check trust signals | At least 4 trust badges visible | | e | Check final CTA | "Kostenlos testen" button visible | **Postconditions:** - All i18n keys resolve (no raw key strings visible) - No console errors --- ### T-02: Landing Page Responsive (Mobile) **Type:** E2E **Tool:** Playwright (viewport: 375×812) **Scenarios:** | # | Viewport | Expected Result | |---|----------|-----------------| | a | 375×812 (iPhone) | Feature grid stacks to single column | | b | 375×812 | Hero section full-width, text wraps cleanly | | c | 768×1024 (iPad) | Feature grid shows 2 columns | | d | 1280×720 (desktop) | Feature grid shows 3 columns | --- ### T-03: Landing Page Dark/Light Mode **Type:** E2E **Tool:** Playwright (`colorScheme: 'dark'` / `'light'`) **Scenarios:** | # | Mode | Expected Result | |---|------|-----------------| | a | Dark | Background is dark, text is light, no contrast issues | | b | Light | Background is light, text is dark, cards have proper borders | | c | Switch | Toggle theme mid-page — re-renders correctly | --- ### T-04: Landing Page CTA Links **Type:** E2E **Tool:** Playwright **Scenarios:** | # | Element | Expected Navigation | |---|---------|-------------------| | a | Primary CTA ("Preise ansehen") | Navigates to `/pricing` | | b | Secondary CTA ("Jetzt anmelden") | Navigates to `/login` | | c | Final CTA ("Kostenlos testen") | Navigates to `/pricing` | | d | Header "Features" link | Scrolls to `#features` section or navigates to `/#features` | --- ### T-05: Marketing Nav Features Link **Type:** E2E **Tool:** Playwright **Scenarios:** | # | Page | Expected | |---|------|----------| | a | `/` | Header shows "Features" link | | b | `/pricing` | Header shows "Features" link | | c | Click "Features" from `/pricing` | Navigates to homepage features section | --- ### T-06: Admin Login — Split Layout Desktop **Type:** E2E **Tool:** Playwright (viewport: 1280×720) **Scenarios:** | # | Check | Expected | |---|-------|----------| | a | Left panel visible | Branding panel with logo, tagline, feature bullets visible | | b | Right panel visible | Login form visible | | c | Layout proportions | Left panel ~55%, right panel ~45% | | d | Left panel content | "CannaManage" text, tagline, 3 feature highlights with icons | --- ### T-07: Admin Login — Full-Width Mobile **Type:** E2E **Tool:** Playwright (viewport: 375×812) **Scenarios:** | # | Check | Expected | |---|-------|----------| | a | Left panel | Hidden (`hidden md:flex`) | | b | Form panel | Full width, centered vertically | | c | Form usability | All fields accessible, submit button tappable | --- ### T-08: Admin Login — Form Still Functional **Type:** E2E **Tool:** Playwright **Preconditions:** - Backend running with test credentials available **Scenarios:** | # | Input | Expected | |---|-------|----------| | a | Valid credentials | Redirects to `/dashboard` | | b | Invalid password | Error message displayed | | c | Empty fields | Validation errors shown | --- ### T-09: Portal Login — Member-Themed Split **Type:** E2E **Tool:** Playwright (viewport: 1280×720) **Scenarios:** | # | Check | Expected | |---|-------|----------| | a | Left panel | Member-specific messaging ("Willkommen zurück") | | b | Visual theme | Different gradient than admin login (teal/emerald vs. primary green) | | c | Feature bullets | Member-relevant: Abgabehistorie, Profil, Dokumente | --- ### T-10: Portal Login — Form Functional **Type:** E2E **Tool:** Playwright **Scenarios:** | # | Input | Expected | |---|-------|----------| | a | Submit form | Redirects to `/portal/dashboard` | | b | Invalid input | Error message shown | --- ### T-11: Pricing — Storage Tiers Displayed **Type:** E2E **Tool:** Playwright **Scenarios:** | # | Plan | Expected Storage Display | |---|------|------------------------| | a | Starter card | "5 GB Speicher" visible | | b | Pro card | "50 GB Speicher" visible + overage note | | c | Enterprise card | "Individueller Speicher" visible | --- ### T-12: Pricing — Comparison Table **Type:** E2E **Tool:** Playwright **Scenarios:** | # | Check | Expected | |---|-------|----------| | a | Table exists | Comparison table rendered below plan cards | | b | Storage row | "Speicher" row shows 5 GB / 50 GB / Individuell | | c | Overage row | "Überschreitung" row shows values per plan | | d | Responsive | Table scrollable on mobile | --- ### T-13: Pricing — FAQ Storage Entry **Type:** E2E **Tool:** Playwright **Scenarios:** | # | Check | Expected | |---|-------|----------| | a | FAQ section | Contains storage question | | b | Click expand | Answer mentions Starter upgrade + Pro overage pricing | --- ### T-14: Storage Usage Calculation **Type:** Unit **Class:** `cannamanage-service/src/test/java/de/cannamanage/service/StorageQuotaServiceTest.java` **Method:** `testGetUsage_calculatesCorrectly()` **Scenarios:** | # | Setup | Expected | |---|-------|----------| | a | Club with 1 GB used, 5 GB limit | `{ usedBytes: 1073741824, limitBytes: 5368709120, percentage: 20.0 }` | | b | Club with 0 bytes used | `{ usedBytes: 0, percentage: 0.0 }` | | c | Club with exactly limit used | `{ percentage: 100.0 }` | --- ### T-15: Quota Allows Upload Under Limit **Type:** Unit **Class:** `StorageQuotaServiceTest` **Method:** `testCheckQuota_underLimit_noException()` **Scenarios:** | # | Current Usage | Limit | Upload Size | Expected | |---|---------------|-------|-------------|----------| | a | 1 GB | 5 GB | 100 MB | No exception | | b | 4.9 GB | 5 GB | 50 MB | No exception (4.95 GB < 5 GB) | | c | 0 | 5 GB | 5 GB | No exception (exactly at limit) | --- ### T-16: Quota Rejects Upload Over Limit **Type:** Unit **Class:** `StorageQuotaServiceTest` **Method:** `testCheckQuota_overLimit_throwsQuotaExceeded()` **Scenarios:** | # | Current Usage | Limit | Upload Size | Expected | |---|---------------|-------|-------------|----------| | a | 4.9 GB | 5 GB | 200 MB | `StorageQuotaExceededException` thrown | | b | 5 GB | 5 GB | 1 byte | `StorageQuotaExceededException` thrown | | c | 50 GB | 50 GB | 1 KB | `StorageQuotaExceededException` thrown | --- ### T-17: Increment On Upload **Type:** Unit **Class:** `StorageQuotaServiceTest` **Method:** `testIncrementUsage_addsBytes()` **Scenarios:** | # | Initial | Increment | Expected | |---|---------|-----------|----------| | a | 0 | 1048576 (1 MB) | 1048576 | | b | 1000000 | 500000 | 1500000 | --- ### T-18: Decrement On Delete **Type:** Unit **Class:** `StorageQuotaServiceTest` **Method:** `testDecrementUsage_subtractsBytes()` **Scenarios:** | # | Initial | Decrement | Expected | |---|---------|-----------|----------| | a | 5000000 | 1000000 | 4000000 | | b | 1048576 | 1048576 | 0 | --- ### T-19: Decrement Floors at Zero **Type:** Unit **Class:** `StorageQuotaServiceTest` **Method:** `testDecrementUsage_floorsAtZero()` **Scenarios:** | # | Initial | Decrement | Expected | |---|---------|-----------|----------| | a | 100 | 200 | 0 (not negative) | | b | 0 | 1000 | 0 | --- ### T-20: Tier Limit Mapping **Type:** Unit **Class:** `StorageQuotaServiceTest` **Method:** `testGetLimitForTier()` **Scenarios:** | # | Tier | Expected Limit | |---|------|---------------| | a | TRIAL | 5 GB (5368709120) | | b | STARTER | 5 GB (5368709120) | | c | PRO | 50 GB (53687091200) | | d | ENTERPRISE | Long.MAX_VALUE | --- ### T-21: Near-Limit Detection 80% **Type:** Unit **Class:** `StorageQuotaServiceTest` **Method:** `testIsNearLimit_at80Percent()` **Scenarios:** | # | Usage | Limit | Threshold | Expected | |---|-------|-------|-----------|----------| | a | 4.0 GB | 5 GB | 80% | true (80%) | | b | 3.9 GB | 5 GB | 80% | false (78%) | | c | 4.1 GB | 5 GB | 80% | true (82%) | --- ### T-22: Near-Limit Detection 95% **Type:** Unit **Class:** `StorageQuotaServiceTest` **Method:** `testIsNearLimit_at95Percent()` **Scenarios:** | # | Usage | Limit | Threshold | Expected | |---|-------|-------|-----------|----------| | a | 4.75 GB | 5 GB | 95% | true (95%) | | b | 4.7 GB | 5 GB | 95% | false (94%) | --- ### T-23: Storage Endpoint — Authenticated **Type:** Integration **Class:** `cannamanage-api/src/test/java/de/cannamanage/api/controller/StorageControllerTest.java` **Method:** `testGetUsage_authenticated_returns200()` **Preconditions:** - Test user with ADMIN role and known clubId - Club has pre-set `storageUsedBytes` and `storageLimitBytes` **Scenarios:** | # | Auth | Expected | |---|------|----------| | a | Valid ADMIN JWT | 200 with `{ usedBytes, limitBytes, percentage }` | | b | Valid STAFF JWT | 200 with correct response | --- ### T-24: Storage Endpoint — Unauthenticated **Type:** Integration **Class:** `StorageControllerTest` **Method:** `testGetUsage_unauthenticated_returns401()` **Scenarios:** | # | Auth | Expected | |---|------|----------| | a | No token | 401 Unauthorized | | b | Expired token | 401 Unauthorized | | c | MEMBER role | 403 Forbidden | --- ### T-25: Storage Endpoint — DTO Shape **Type:** Integration **Class:** `StorageControllerTest` **Method:** `testGetUsage_responseShape()` **Scenarios:** | # | Check | Expected | |---|-------|----------| | a | JSON keys | Response contains `usedBytes`, `limitBytes`, `percentage` | | b | Types | `usedBytes` and `limitBytes` are numbers, `percentage` is double | | c | Percentage calculation | Matches `usedBytes / limitBytes * 100` | --- ### T-26: Document Upload — Quota Check Integrated **Type:** Integration **Class:** `cannamanage-service/src/test/java/de/cannamanage/service/DocumentServiceTest.java` **Method:** `testUploadDocument_checksQuotaBeforeWrite()` **Scenarios:** | # | Setup | Expected | |---|-------|----------| | a | Club under quota, upload 1 MB | Upload succeeds, `storageUsedBytes` incremented by file size | | b | Club at quota limit | Upload rejected with `StorageQuotaExceededException` before file write | --- ### T-27: Document Upload — 402 Response **Type:** Integration **Class:** `cannamanage-api/src/test/java/de/cannamanage/api/controller/DocumentControllerTest.java` **Method:** `testUploadDocument_quotaExceeded_returns402()` **Preconditions:** - Club with `storageUsedBytes = storageLimitBytes` (fully used) **Scenarios:** | # | Action | Expected | |---|--------|----------| | a | POST multipart upload (1 byte file) | 402 Payment Required | | b | Response body | RFC 9457 ProblemDetail with `currentUsage`, `limit`, `requestedBytes` | --- ### T-28: Document Delete — Usage Decremented **Type:** Integration **Class:** `DocumentServiceTest` **Method:** `testDeleteDocument_decrementsUsage()` **Scenarios:** | # | Setup | Expected | |---|-------|----------| | a | Club with 5 MB used, delete 2 MB document | `storageUsedBytes` = 3 MB | | b | Club with 1 MB used, delete 1 MB document | `storageUsedBytes` = 0 | --- ### T-29: Flyway V36 — Migration Applies **Type:** Integration **Tool:** Spring Boot test context startup **Scenarios:** | # | Check | Expected | |---|-------|----------| | a | Application starts | V36 migration applies without error | | b | Column exists | `SELECT storage_used_bytes FROM clubs LIMIT 1` succeeds | | c | Default value | New clubs get `storage_used_bytes = 0` and `storage_limit_bytes = 5368709120` | --- ### T-30: Flyway V36 — Backfill **Type:** Integration **Tool:** SQL verification after migration **Preconditions:** - Club exists with 3 documents (sizes: 1MB, 2MB, 3MB) **Scenarios:** | # | Check | Expected | |---|-------|----------| | a | After migration | `storage_used_bytes` = 6291456 (6 MB = sum of document sizes) | | b | Club with no docs | `storage_used_bytes` = 0 | --- ### T-31: StorageQuotaExceededException — 402 Format **Type:** Unit **Class:** `cannamanage-api/src/test/java/de/cannamanage/api/exception/GlobalExceptionHandlerTest.java` **Method:** `testStorageQuotaExceeded_returns402WithProblemDetail()` **Scenarios:** | # | Input | Expected | |---|-------|----------| | a | `StorageQuotaExceededException(1GB, 5GB, 200MB)` | HTTP 402, title="Storage Quota Exceeded" | | b | Response properties | Contains `currentUsage`, `limit`, `requestedBytes` numeric fields | --- ### T-32: i18n Keys Resolve **Type:** Unit / Lint **Tool:** next-intl compile check or custom script **Scenarios:** | # | Namespace | Expected | |---|-----------|----------| | a | `marketing.home.*` | All 20+ keys resolve in `de.json` | | b | `marketing.home.*` | All 20+ keys resolve in `en.json` (English equivalents) | | c | `marketing.pricing.faq.storage.*` | question + answer keys present in both locales | | d | `marketing.pricing.plans.*.storage` | Storage labels present for each plan in both locales | --- ## Test Data ### Backend - **Test club:** UUID `00000000-0000-0000-0000-000000000001`, `storageUsedBytes = 1073741824` (1 GB), `storageLimitBytes = 5368709120` (5 GB) - **Test documents:** 3 documents with `fileSize` = 100MB, 200MB, 773741824 bytes (total = 1 GB) - **Full quota club:** UUID `00000000-0000-0000-0000-000000000002`, `storageUsedBytes = storageLimitBytes = 5368709120` ### Frontend E2E - Landing page, pricing, login pages are public — no auth setup needed for T-01 through T-13 - Login form tests (T-08, T-10) require running backend with test user `admin@gruener-daumen.de` / `TestAdmin123!` --- ## Test Coverage | Component | Unit | Integration | E2E | Total | |-----------|------|-------------|-----|-------| | Landing page | 0 | 0 | 5 | 5 | | Login redesign | 0 | 0 | 5 | 5 | | Pricing update | 0 | 0 | 3 | 3 | | StorageQuotaService | 9 | 0 | 0 | 9 | | StorageController | 0 | 3 | 0 | 3 | | DocumentService (quota) | 0 | 2 | 0 | 2 | | DocumentController (402) | 0 | 1 | 0 | 1 | | Flyway migration | 0 | 2 | 0 | 2 | | Exception handling | 1 | 0 | 0 | 1 | | i18n verification | 1 | 0 | 0 | 1 | | **Total** | **11** | **8** | **13** | **32** |