f42c166329
- @tanstack/react-query with QueryClientProvider in providers/index.tsx - Typed api-client.ts fetch wrapper with ApiError class + apiDownload - Service modules: members, distributions, stock, reports, dashboard, portal, staff - Offline banner component (onlineManager subscription) - API error boundary with retry button - Loading skeleton components (card, table, chart, form, dashboard) - i18n for error/loading states (de/en)
641 lines
20 KiB
Markdown
641 lines
20 KiB
Markdown
# CannaManage — Sprint 5 Test Plan
|
|
|
|
**Date:** 2026-06-12
|
|
**Author:** Patrick Plate / Lumen (Planner)
|
|
**Status:** Draft v1
|
|
**Basis:** cannamanage-sprint5-plan.md v1
|
|
|
|
---
|
|
|
|
## Test Overview
|
|
|
|
| ID | Description | Type | Target | Status |
|
|
|----|-------------|------|--------|--------|
|
|
| T-01 | Docker Compose full stack starts healthy | System | docker-compose.yml | ⬜ |
|
|
| T-02 | CORS preflight allows localhost:3000 | Integration | SecurityConfig | ⬜ |
|
|
| T-03 | Backend health endpoint responds | Integration | /actuator/health | ⬜ |
|
|
| T-04 | React Query provider mounts without error | Unit | providers.tsx | ⬜ |
|
|
| T-05 | apiFetch uses rewrite proxy correctly | Unit | api-client-browser.ts | ⬜ |
|
|
| T-06 | ApiError formats toast messages | Unit | use-api-error.ts | ⬜ |
|
|
| T-07 | Dashboard loads real club stats | Integration | dashboard/page.tsx | ⬜ |
|
|
| T-08 | Member list renders from API | Integration | members/page.tsx | ⬜ |
|
|
| T-09 | Create member persists to database | Integration | POST /members | ⬜ |
|
|
| T-10 | Edit member updates in database | Integration | PUT /members/{id} | ⬜ |
|
|
| T-11 | Distribution form records to backend | Integration | POST /distributions | ⬜ |
|
|
| T-12 | Quota exceeded returns 409 with details | Integration | ComplianceService | ⬜ |
|
|
| T-13 | Under-21 member enforces 30g limit | Integration | ComplianceService | ⬜ |
|
|
| T-14 | Batch creation adds stock | Integration | POST /stock/batches | ⬜ |
|
|
| T-15 | Batch recall changes status | Integration | PUT /stock/batches/{id}/recall | ⬜ |
|
|
| T-16 | PDF report downloads as binary | Integration | GET /reports/monthly?format=pdf | ⬜ |
|
|
| T-17 | CSV report downloads correctly | Integration | GET /reports/member-list?format=csv | ⬜ |
|
|
| T-18 | Portal login with session auth | Integration | POST /portal/login | ⬜ |
|
|
| T-19 | Portal dashboard shows real quota | Integration | GET /portal/dashboard | ⬜ |
|
|
| T-20 | Staff list loads for ADMIN | Integration | GET /staff | ⬜ |
|
|
| T-21 | Staff invite sends email | Integration | POST /staff/invite | ⬜ |
|
|
| T-22 | Permission update persists | Integration | PUT /staff/{id}/permissions | ⬜ |
|
|
| T-23 | Staff revoke removes access | Integration | DELETE /staff/{id} | ⬜ |
|
|
| T-24 | Non-ADMIN gets 403 on staff endpoints | Security | SecurityConfig | ⬜ |
|
|
| T-25 | Seed data creates deterministic test state | System | R__test_seed.sql | ⬜ |
|
|
| T-26 | Full E2E: login → distribute → verify quota | System | system-test.spec.ts | ⬜ |
|
|
| T-27 | System test exit code 0 on success | System | docker-compose.test.yml | ⬜ |
|
|
| T-28 | Error state renders when backend down | Unit | error-state.tsx | ⬜ |
|
|
| T-29 | Loading skeleton appears during fetch | Unit | skeleton-card.tsx | ⬜ |
|
|
| T-30 | Empty state shown when no data | Unit | dashboard/members pages | ⬜ |
|
|
|
|
Status: ⬜ Open | ✅ Passed | ❌ Failed | ⏭️ Skipped
|
|
|
|
---
|
|
|
|
## Test Cases (Detail)
|
|
|
|
### T-01: Docker Compose full stack starts healthy
|
|
|
|
**Type:** System
|
|
**Target:** `docker-compose.yml`
|
|
|
|
**Preconditions:**
|
|
- Docker Desktop running
|
|
- No port conflicts (5432, 8080, 3000)
|
|
- Images buildable (Maven + Node available)
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | `docker compose up -d` | All 3 containers reach "healthy" within 60s |
|
|
| b | `docker compose ps` | db=healthy, backend=healthy, frontend=running |
|
|
| c | `curl http://localhost:3000` | Returns HTML (login page) |
|
|
| d | `curl http://localhost:8080/actuator/health` | Returns `{"status":"UP"}` |
|
|
|
|
**Post-conditions:**
|
|
- All containers stable for 30s without restart loops
|
|
|
|
---
|
|
|
|
### T-02: CORS preflight allows localhost:3000
|
|
|
|
**Type:** Integration
|
|
**Target:** `SecurityConfig.java` CORS bean
|
|
|
|
**Preconditions:**
|
|
- Backend running on port 8080
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | OPTIONS /api/v1/members with Origin: http://localhost:3000 | 200 + Access-Control-Allow-Origin: http://localhost:3000 |
|
|
| b | OPTIONS /api/v1/members with Origin: http://evil.com | No Access-Control-Allow-Origin header (or 403) |
|
|
| c | GET /api/v1/members with Origin: http://localhost:3000 + valid JWT | 200 + CORS headers present |
|
|
|
|
---
|
|
|
|
### T-03: Backend health endpoint responds
|
|
|
|
**Type:** Integration
|
|
**Target:** Spring Boot Actuator
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | GET /actuator/health (no auth) | 200 `{"status":"UP"}` |
|
|
| b | Backend with DB down | 503 `{"status":"DOWN"}` |
|
|
|
|
---
|
|
|
|
### T-04: React Query provider mounts without error
|
|
|
|
**Type:** Unit
|
|
**Class:** `providers.tsx`
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | Render providers with children | No console errors, children visible |
|
|
| b | Check React Query DevTools in dev mode | DevTools panel accessible (floating button) |
|
|
|
|
---
|
|
|
|
### T-05: apiFetch uses rewrite proxy correctly
|
|
|
|
**Type:** Unit
|
|
**Class:** `api-client-browser.ts`
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | `apiFetch("/members")` | Fetches `/api/backend/members` |
|
|
| b | Backend returns 401 | Throws `ApiError` with status 401 |
|
|
| c | Backend returns 500 | Throws `ApiError` with status 500, message from body |
|
|
| d | Backend returns non-JSON error | Throws `ApiError` with statusText as message |
|
|
| e | Network error (backend down) | Throws TypeError (fetch failure) |
|
|
|
|
---
|
|
|
|
### T-06: ApiError formats toast messages
|
|
|
|
**Type:** Unit
|
|
**Class:** `use-api-error.ts`
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | ApiError(401, "Unauthorized") | Toast: "Sitzung abgelaufen" (destructive) |
|
|
| b | ApiError(403, "Forbidden") | Toast: "Zugriff verweigert" (destructive) |
|
|
| c | ApiError(409, "Quota exceeded: 5g remaining") | Toast: "Kontingent überschritten" with message |
|
|
| d | TypeError (network) | Toast: "Verbindungsfehler — Backend nicht erreichbar" |
|
|
| e | ApiError(500, "Internal error") | Toast: "Fehler" with generic message |
|
|
|
|
---
|
|
|
|
### T-07: Dashboard loads real club stats
|
|
|
|
**Type:** Integration
|
|
**Target:** Dashboard page + `GET /clubs/stats` (or equivalent)
|
|
|
|
**Preconditions:**
|
|
- Backend running with seed data
|
|
- User logged in as ADMIN
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | Navigate to dashboard | KPI cards show numbers matching DB (5 members from seed) |
|
|
| b | Refresh page | Data reloads (React Query refetch) |
|
|
| c | Backend returns error | Error state shown with retry button |
|
|
| d | Slow response (>2s) | Skeleton cards visible during loading |
|
|
|
|
---
|
|
|
|
### T-08: Member list renders from API
|
|
|
|
**Type:** Integration
|
|
**Target:** Members page + `GET /members`
|
|
|
|
**Preconditions:**
|
|
- Seed data loaded (5 members)
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | Navigate to /members | Table shows 5 seed members |
|
|
| b | Search "Müller" | Filtered to Max Müller only |
|
|
| c | Empty DB (no members) | Empty state: "Keine Mitglieder vorhanden" |
|
|
| d | Loading state | Skeleton table rows visible |
|
|
|
|
---
|
|
|
|
### T-09: Create member persists to database
|
|
|
|
**Type:** Integration
|
|
**Target:** `POST /api/v1/members`
|
|
|
|
**Preconditions:**
|
|
- Logged in as ADMIN or STAFF with MANAGE_MEMBERS permission
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | Valid member data (name, email, DOB, phone) | 201 Created, member appears in list |
|
|
| b | Duplicate email | 409 Conflict with error message |
|
|
| c | Missing required field (firstName blank) | 400 Bad Request with validation errors |
|
|
| d | Future date of birth | 400 Bad Request (validation) |
|
|
| e | Under-18 date of birth | 400 Bad Request (CanKG minimum age) |
|
|
|
|
---
|
|
|
|
### T-10: Edit member updates in database
|
|
|
|
**Type:** Integration
|
|
**Target:** `PUT /api/v1/members/{id}`
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | Change phone number | 200 OK, phone updated in DB |
|
|
| b | Change status to SUSPENDED | 200 OK, member shows suspended badge |
|
|
| c | Invalid member ID | 404 Not Found |
|
|
| d | Unauthorized role | 403 Forbidden |
|
|
|
|
---
|
|
|
|
### T-11: Distribution form records to backend
|
|
|
|
**Type:** Integration
|
|
**Target:** `POST /api/v1/distributions`
|
|
|
|
**Preconditions:**
|
|
- Available batch exists (≥ requested grams)
|
|
- Member is ACTIVE
|
|
- Quota not exceeded
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | 10g Amnesia Haze to Max Müller | 201 Created, distribution ID returned |
|
|
| b | Batch available_grams decreases by 10g | GET /stock/batches/{id} shows -10g |
|
|
| c | Member quota updates | Compliance endpoint shows 10g used today |
|
|
| d | Stock chart updates after mutation | React Query invalidates batch cache |
|
|
|
|
---
|
|
|
|
### T-12: Quota exceeded returns 409 with details
|
|
|
|
**Type:** Integration
|
|
**Target:** `ComplianceService` quota enforcement
|
|
|
|
**Preconditions:**
|
|
- Member already at 24g today (daily limit = 25g)
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | Request 5g distribution | 409: "Daily quota exceeded. Remaining: 1g" |
|
|
| b | Request 1g distribution | 201 Created (exactly at limit) |
|
|
| c | Request 26g in single distribution | 409: "Daily limit is 25g" |
|
|
|
|
---
|
|
|
|
### T-13: Under-21 member enforces 30g monthly limit
|
|
|
|
**Type:** Integration
|
|
**Target:** `ComplianceService` age-based quota
|
|
|
|
**Preconditions:**
|
|
- Member Jonas Fischer (DOB: 2005-11-01, age 20 in 2026) in seed data
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | Check quota for Jonas | `monthlyLimitGrams: 30`, `isUnder21: true` |
|
|
| b | Distribute 29g across month | Success |
|
|
| c | Distribute 1g more (total 30g) | Success (at limit) |
|
|
| d | Distribute 1g more (total 31g) | 409: "Monthly quota exceeded for under-21 member" |
|
|
|
|
---
|
|
|
|
### T-14: Batch creation adds stock
|
|
|
|
**Type:** Integration
|
|
**Target:** `POST /api/v1/stock/batches`
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | New batch: 500g Blue Dream, THC 19%, supplier "BioHemp" | 201 Created |
|
|
| b | Stock total increases by 500g | Dashboard total_stock reflects increase |
|
|
| c | Missing strain name | 400 Bad Request |
|
|
| d | Negative grams | 400 Bad Request |
|
|
|
|
---
|
|
|
|
### T-15: Batch recall changes status
|
|
|
|
**Type:** Integration
|
|
**Target:** `PUT /api/v1/stock/batches/{id}/recall`
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | Recall batch b-001 | Status changes to RECALLED |
|
|
| b | Recalled batch not available for distribution | Distribution form excludes recalled batches |
|
|
| c | Recall already-recalled batch | 400 Bad Request (or idempotent 200) |
|
|
| d | Invalid batch ID | 404 Not Found |
|
|
|
|
---
|
|
|
|
### T-16: PDF report downloads as binary
|
|
|
|
**Type:** Integration
|
|
**Target:** `GET /api/v1/reports/monthly?format=pdf`
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | Request monthly report (current month) | 200, Content-Type: application/pdf |
|
|
| b | Response body is valid PDF | First bytes = `%PDF-` |
|
|
| c | Browser triggers file download | Content-Disposition: attachment |
|
|
| d | No data for requested month | 200 with empty report (or 204 No Content) |
|
|
|
|
---
|
|
|
|
### T-17: CSV report downloads correctly
|
|
|
|
**Type:** Integration
|
|
**Target:** `GET /api/v1/reports/member-list?format=csv`
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | Request member list CSV | 200, Content-Type: text/csv |
|
|
| b | CSV has header row | First line: column names |
|
|
| c | 5 seed members in CSV body | 6 lines total (header + 5 data) |
|
|
| d | German umlauts preserved | "Müller" renders correctly (UTF-8 or ISO-8859-1) |
|
|
|
|
---
|
|
|
|
### T-18: Portal login with session auth
|
|
|
|
**Type:** Integration
|
|
**Target:** `POST /portal/login`
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | Valid member credentials (max@example.com / member123) | 200 `{"status":"ok"}` + Set-Cookie session |
|
|
| b | Invalid password | 401 `{"error":"Invalid credentials"}` |
|
|
| c | Non-member email (admin@...) | 401 (not a member) |
|
|
| d | Subsequent /portal/* requests with session cookie | 200 (authenticated) |
|
|
|
|
---
|
|
|
|
### T-19: Portal dashboard shows real quota
|
|
|
|
**Type:** Integration
|
|
**Target:** `GET /portal/dashboard`
|
|
|
|
**Preconditions:**
|
|
- Member logged into portal session
|
|
- Some distributions recorded for this member
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | Fresh member (no distributions) | Quota: 0g used / 50g limit (≥21 years) |
|
|
| b | After 10g distribution recorded | Quota: 10g used / 50g limit |
|
|
| c | Radial chart shows correct percentage | 20% filled (10/50) |
|
|
|
|
---
|
|
|
|
### T-20: Staff list loads for ADMIN
|
|
|
|
**Type:** Integration
|
|
**Target:** `GET /api/v1/staff`
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | ADMIN requests staff list | 200 with array of staff accounts |
|
|
| b | Includes permissions array per staff | Each item has `permissions: [...]` |
|
|
| c | STAFF without MANAGE_STAFF permission | 403 Forbidden |
|
|
| d | MEMBER role | 403 Forbidden |
|
|
|
|
---
|
|
|
|
### T-21: Staff invite sends email
|
|
|
|
**Type:** Integration
|
|
**Target:** `POST /api/v1/staff/invite`
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | Valid email + permissions selection | 201 Created, invite token generated |
|
|
| b | Email already registered as staff | 409 Conflict |
|
|
| c | Invalid email format | 400 Bad Request |
|
|
| d | No permissions selected | 400 Bad Request (at least one required) |
|
|
|
|
---
|
|
|
|
### T-22: Permission update persists
|
|
|
|
**Type:** Integration
|
|
**Target:** `PUT /api/v1/staff/{id}/permissions`
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | Set permissions to [MANAGE_MEMBERS, VIEW_REPORTS] | 200, permissions saved |
|
|
| b | Staff can now access members but not stock | GET /members → 200, GET /stock → 403 |
|
|
| c | Empty permissions array | 200 (staff has no capabilities, effectively read-only) |
|
|
| d | Invalid permission name | 400 Bad Request |
|
|
|
|
---
|
|
|
|
### T-23: Staff revoke removes access
|
|
|
|
**Type:** Integration
|
|
**Target:** `DELETE /api/v1/staff/{id}`
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | Delete staff account | 204 No Content |
|
|
| b | Revoked staff can no longer login | 401 on next auth attempt |
|
|
| c | Staff disappears from list | GET /staff excludes deleted |
|
|
| d | Delete non-existent staff ID | 404 Not Found |
|
|
|
|
---
|
|
|
|
### T-24: Non-ADMIN gets 403 on staff endpoints
|
|
|
|
**Type:** Security
|
|
**Target:** SecurityConfig authorization rules
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | STAFF (no MANAGE_STAFF) → GET /staff | 403 |
|
|
| b | STAFF (no MANAGE_STAFF) → POST /staff/invite | 403 |
|
|
| c | MEMBER → GET /staff | 403 |
|
|
| d | Unauthenticated → GET /staff | 401 |
|
|
| e | ADMIN → GET /staff | 200 |
|
|
| f | STAFF with MANAGE_STAFF → GET /staff | 200 |
|
|
|
|
---
|
|
|
|
### T-25: Seed data creates deterministic test state
|
|
|
|
**Type:** System
|
|
**Target:** `R__test_seed.sql`
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | Backend starts with profile=test-seed | Seed data inserted on startup |
|
|
| b | `SELECT COUNT(*) FROM members WHERE club_id='club-001'` | Returns 5 |
|
|
| c | `SELECT COUNT(*) FROM batches WHERE club_id='club-001'` | Returns 3 |
|
|
| d | Second startup with same profile | No duplicate insert errors (ON CONFLICT DO NOTHING) |
|
|
| e | Admin user can login with seed credentials | JWT returned successfully |
|
|
|
|
---
|
|
|
|
### T-26: Full E2E: login → distribute → verify quota
|
|
|
|
**Type:** System
|
|
**Class:** `e2e/system-test.spec.ts`
|
|
|
|
**Preconditions:**
|
|
- Full Docker stack running with seed data
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Step | Expected Result |
|
|
|---|------|-----------------|
|
|
| a | Login as admin@gruener-daumen.de | Dashboard loads with KPIs |
|
|
| b | Navigate to Members | 5 seed members visible in table |
|
|
| c | Navigate to Distributions | Distribution list renders |
|
|
| d | Record 10g Amnesia Haze → Max Müller | Success toast, distribution in list |
|
|
| e | Navigate to Stock | Amnesia Haze batch shows -10g available |
|
|
| f | Navigate to Reports → download monthly PDF | File downloads (200 response) |
|
|
| g | Logout, login as member (max@example.com) | Portal dashboard loads |
|
|
| h | Portal quota shows 10g used | Radial chart at 20% |
|
|
|
|
---
|
|
|
|
### T-27: System test exit code 0 on success
|
|
|
|
**Type:** System
|
|
**Target:** `docker-compose.test.yml`
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | All tests pass | Playwright container exits with code 0, compose exits 0 |
|
|
| b | A test fails | Playwright exits non-zero, compose exits non-zero |
|
|
| c | Backend fails to start | Tests timeout, compose exits non-zero |
|
|
|
|
---
|
|
|
|
### T-28: Error state renders when backend down
|
|
|
|
**Type:** Unit
|
|
**Target:** `error-state.tsx`
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | API fetch throws network error | Error component visible with message |
|
|
| b | Click "Retry" button | Query refetches |
|
|
| c | Multiple errors on same page | Each section shows own error independently |
|
|
|
|
---
|
|
|
|
### T-29: Loading skeleton appears during fetch
|
|
|
|
**Type:** Unit
|
|
**Target:** `skeleton-card.tsx`, `skeleton-table.tsx`
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | Query in `isLoading` state | Skeleton shimmer visible |
|
|
| b | Query resolves | Skeleton replaced with real content |
|
|
| c | Query in `isFetching` (background refetch) | No skeleton (data still shown) |
|
|
|
|
---
|
|
|
|
### T-30: Empty state shown when no data
|
|
|
|
**Type:** Unit
|
|
**Target:** Dashboard, Members, Distributions pages
|
|
|
|
**Scenarios:**
|
|
|
|
| # | Input | Expected Result |
|
|
|---|-------|-----------------|
|
|
| a | Members list empty (API returns []) | "Keine Mitglieder vorhanden" + Add button |
|
|
| b | Distributions list empty | "Keine Ausgaben erfasst" message |
|
|
| c | Stock empty (no batches) | "Kein Bestand vorhanden" + Add batch button |
|
|
| d | Dashboard with 0 members | KPI cards show 0, no chart data placeholder |
|
|
|
|
---
|
|
|
|
## Test Data Requirements
|
|
|
|
| Entity | Count | Source | Notes |
|
|
|--------|-------|--------|-------|
|
|
| Club | 1 | `R__test_seed.sql` | "Grüner Daumen e.V.", Berlin |
|
|
| Admin user | 1 | Seed | admin@gruener-daumen.de / admin123 |
|
|
| Staff user | 1 | Seed | staff@gruener-daumen.de / staff123 |
|
|
| Members | 5 | Seed | Including 1 under-21 (Jonas Fischer) |
|
|
| Strains | 3 | Seed | Amnesia Haze, White Widow, Northern Lights |
|
|
| Batches | 3 | Seed | 520g, 430g, 380g available |
|
|
| Distributions | 0 | Fresh | Tests create distributions during execution |
|
|
|
|
---
|
|
|
|
## Test Coverage Matrix
|
|
|
|
| Component | Unit | Integration | System | Total |
|
|
|-----------|------|-------------|--------|-------|
|
|
| Docker/Infra | 0 | 1 | 2 | 3 |
|
|
| CORS/Security | 0 | 2 | 0 | 2 |
|
|
| API Client | 3 | 0 | 0 | 3 |
|
|
| Dashboard | 0 | 1 | 1 | 2 |
|
|
| Members | 0 | 2 | 1 | 3 |
|
|
| Distributions | 0 | 3 | 1 | 4 |
|
|
| Stock | 0 | 2 | 1 | 3 |
|
|
| Reports | 0 | 2 | 1 | 3 |
|
|
| Portal | 0 | 2 | 1 | 3 |
|
|
| Staff | 0 | 5 | 0 | 5 |
|
|
| UI States | 3 | 0 | 0 | 3 |
|
|
| **Total** | **6** | **20** | **4** | **30** |
|
|
|
|
---
|
|
|
|
## Execution Strategy
|
|
|
|
### Fast feedback loop (during development):
|
|
```bash
|
|
# Frontend unit tests (Vitest — if configured)
|
|
cd cannamanage-frontend && pnpm test
|
|
|
|
# Backend integration tests (existing Testcontainers suite)
|
|
cd cannamanage-api && mvn test
|
|
|
|
# Existing Playwright E2E (mock backend, ~30s)
|
|
cd cannamanage-frontend && pnpm exec playwright test
|
|
```
|
|
|
|
### Full system test (before merge):
|
|
```bash
|
|
docker compose -f docker-compose.yml -f docker-compose.test.yml up \
|
|
--build --abort-on-container-exit --exit-code-from playwright
|
|
```
|
|
|
|
### Manual smoke test checklist:
|
|
1. `docker compose up` → all healthy
|
|
2. Open http://localhost:3000 → login page
|
|
3. Login as admin → dashboard with real data
|
|
4. Add member → appears in list
|
|
5. Record distribution → quota updates
|
|
6. Download PDF report → valid file
|
|
7. Login as member (portal) → see personal quota
|
|
|
|
---
|
|
|
|
## References
|
|
|
|
- Implementation Plan: `docs/sprint-5/cannamanage-sprint5-plan.md` (v1)
|
|
- Backend Controllers: `cannamanage-api/src/main/java/de/cannamanage/api/controller/`
|
|
- Frontend Types: `cannamanage-frontend/src/types/api.ts`
|
|
- Security Config: `cannamanage-api/src/main/java/de/cannamanage/api/security/SecurityConfig.java`
|
|
- Existing E2E: `cannamanage-frontend/e2e/`
|