Files
cannamanage/docs/sprint-14/cannamanage-sprint14-testplan.md
T
Patrick Plate dad798a904
Deploy to TrueNAS / deploy (push) Failing after 33s
feat: Sprint 14 — Marketing & Monetization
- Landing page with hero, feature grid, trust signals
- Split-layout login redesign (admin + portal)
- Pricing page with storage tiers (5GB/50GB/unlimited)
- StorageQuotaService backend (V36 migration, 402 on exceeded)
- Frontend storage integration + 402 error handling
- StorageController uses TenantContext for tenant isolation
- onTierChange() hook for subscription tier updates
2026-06-18 20:28:35 +02:00

597 lines
17 KiB
Markdown
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.
# 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** |