- 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
17 KiB
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
storageUsedBytesandstorageLimitBytes
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 |