# Code Review: CannaManage Sprint 12
**Date:** 18.06.2026
**Reviewer:** Roo (Reviewer)
**Branch:** main (uncommitted working tree changes)
**Status:** ⚠️ Approved with comments — 2 blockers, 4 warnings, 3 suggestions
---
## Summary
Sprint 12 delivers two phases: (1) Documents + Board page rewrites with React Query integration and dual-mode pattern, (2) Full integration test infrastructure with Docker, Playwright, and deterministic seed data. The architecture is solid and patterns are consistent. However, there are **2 blockers** that will prevent tests from running: a credential mismatch in global-setup.ts and missing `data-testid` attributes in the actual components.
## Changed Files
| File | Change | Rating |
|------|--------|--------|
| `cannamanage-frontend/src/services/documents.ts` | Modified | ✅ |
| `cannamanage-frontend/src/services/board.ts` | New | ✅ |
| `cannamanage-frontend/src/app/(dashboard-layout)/documents/page.tsx` | Modified | ⚠️ |
| `cannamanage-frontend/src/app/(dashboard-layout)/board/page.tsx` | Modified | ⚠️ |
| `cannamanage-api/.../TestResetController.java` | New | ✅ |
| `cannamanage-api/.../application-test.properties` | New | ⚠️ |
| `cannamanage-api/.../db/testdata/R__seed_test_data.sql` | New | ✅ |
| `docker-compose.test.yml` | Modified | ⚠️ |
| `docker-compose.test.local.yml` | New | ✅ |
| `cannamanage-frontend/Dockerfile.playwright` | New | ✅ |
| `cannamanage-frontend/playwright.config.ts` | Modified | ✅ |
| `cannamanage-frontend/e2e/global-setup.ts` | Modified | ❌ |
| `cannamanage-frontend/e2e/seed-constants.ts` | New | ✅ |
| `cannamanage-frontend/e2e/selectors.ts` | New | ⚠️ |
| `cannamanage-frontend/e2e/api-client.ts` | New | ✅ |
| `cannamanage-frontend/e2e/integration/*.spec.ts` (13 files) | New | ✅ |
## Checklist
| # | Check | Result | Notes |
|---|-------|--------|-------|
| 1 | Plan compliance | ✅ | All plan items implemented |
| 2 | React patterns | ✅ | Proper React Query mutations, `onSuccess`/`onError` handlers, query invalidation |
| 3 | TypeScript | ✅ | No `any` types in new code (except `api-client.ts` — see W-4), proper interfaces |
| 4 | Error handling | ✅ | try/catch on async ops, toast feedback consistent |
| 5 | Security | ✅ | TestResetController properly gated by `@ConditionalOnProperty` |
| 6 | SQL correctness | ✅ | `ON CONFLICT DO NOTHING` throughout, valid PostgreSQL |
| 7 | Docker | ⚠️ | Credential mismatches between properties and compose (W-1) |
| 8 | Test quality | ⚠️ | Good patterns overall, but `data-testid` attrs missing (B-2) |
| 9 | i18n | ⚠️ | Several hardcoded German strings in AlertDialogs (W-2) |
| 10 | Accessibility | ✅ | Buttons have proper labels, form inputs have Label components |
| 11 | Mock mode | ✅ | Dual-mode pattern is consistent across both pages |
| 12 | Build passes | ⏭️ | Not verified in review (no local run) |
---
## Findings
### ❌ Blockers (must fix before merge)
#### B-1: Login credentials mismatch between global-setup and seed data
**[`cannamanage-frontend/e2e/global-setup.ts`](cannamanage-frontend/e2e/global-setup.ts:46)**
The global-setup authenticates with:
```typescript
await page.fill('input[name="email"]...', "admin@test.de")
await page.fill('input[name="password"]...', "test123")
```
But the seed data creates the admin user with:
- Email: `admin@gruener-daumen.de` (in `R__seed_test_data.sql`)
- Password: `TestAdmin123!` (in `seed-constants.ts`)
This means **all integration tests will fail at the setup phase** because authentication will not succeed against the seeded database.
**Fix:** Update `global-setup.ts` to use `SEED.admin.email` and `SEED.admin.password`:
```typescript
import { SEED } from "./seed-constants"
await page.fill('...', SEED.admin.email)
await page.fill('...', SEED.admin.password)
```
---
#### B-2: `data-testid` attributes not present in components
**[`cannamanage-frontend/e2e/selectors.ts`](cannamanage-frontend/e2e/selectors.ts:8)**
The `SEL` object defines selectors like `[data-testid="documents-upload-button"]`, `[data-testid="documents-download-f1000000..."]`, etc. However, the actual components in `documents/page.tsx` and `board/page.tsx` **do not have any `data-testid` attributes**.
All spec files using `page.locator(SEL.documents.uploadButton)` etc. will fail with element-not-found errors.
**Note:** The `selectors.ts` file itself comments (line 7): _"The actual data-testid attributes will be added incrementally to frontend components during Phase 2E as tests are written."_ — but this Phase 2E work was not done yet. Either:
1. Add the `data-testid` attributes to the components now, or
2. Rewrite specs to use accessible selectors (role, label, text) instead
---
### ⚠️ Warnings (should fix)
#### W-1: Credential/secret mismatch between application-test.properties and docker-compose.test.yml
**[`cannamanage-api/src/main/resources/application-test.properties`](cannamanage-api/src/main/resources/application-test.properties:9)** vs **[`docker-compose.test.yml`](docker-compose.test.yml:26)**
| Setting | application-test.properties | docker-compose.test.yml |
|---------|---------------------------|------------------------|
| DB username | `cannamanage_test` | `cannamanage` |
| DB password | `test_password` | `cannamanage_test` |
| JWT secret | `...LXRlc3RzLW9ubHktMzJi` | `...LXRlc3RzLW9ubHktMzJjaGFycw==` |
In Docker, env vars override properties, so Docker-based tests will work. But anyone running `./mvnw test -Ptest` locally against the Docker-compose DB will hit authentication failures.
**Fix:** Align the properties file with the Docker compose values, or add a comment explaining the intentional override.
---
#### W-2: Hardcoded German strings in AlertDialogs (i18n gap)
**[`cannamanage-frontend/src/app/(dashboard-layout)/documents/page.tsx`](cannamanage-frontend/src/app/(dashboard-layout)/documents/page.tsx:568)**
Multiple hardcoded strings bypass the i18n system:
- Line 447: `"Wird hochgeladen..."`
- Line 568: `"Dokument löschen?"`
- Line 570: `"Möchtest du...wirklich löschen?"`
- Line 575: `"Abbrechen"`
- Line 580: `"Löschen..."` / `"Löschen"`
Same pattern in `board/page.tsx`:
- Line 400: `"Wird gespeichert..."`
- Line 587: `"Vorstandsmitglied entfernen?"`
- Lines 593–599: `"Abbrechen"`, `"Entfernen..."`
**Fix:** Use `t("confirmDelete")`, `t("cancel")`, etc. from the `useTranslations` hook already imported.
---
#### W-3: Board page member select shows hardcoded mock options in API mode
**[`cannamanage-frontend/src/app/(dashboard-layout)/board/page.tsx`](cannamanage-frontend/src/app/(dashboard-layout)/board/page.tsx:439)**
The "Elect Member" dialog always shows only 3 hardcoded `
```
This is acceptable for mock mode, but should load real members from the API when not in mock mode.
---
#### W-4: `any` return types in api-client.ts
**[`cannamanage-frontend/e2e/api-client.ts`](cannamanage-frontend/e2e/api-client.ts:29)**
All public methods return `Promise`. Since this is test infrastructure, it's less critical, but typed responses would make spec assertions more robust.
---
### ℹ️ Suggestions (nice to have)
#### S-1: global-setup.ts uses `waitForTimeout(1000)`
**[`cannamanage-frontend/e2e/global-setup.ts`](cannamanage-frontend/e2e/global-setup.ts:57)**
While generally acceptable for auth state setup, consider replacing with `waitForLoadState("networkidle")` for more deterministic behavior.
---
#### S-2: Docker compose frontend service missing healthcheck
**[`docker-compose.test.yml`](docker-compose.test.yml:40)**
The `frontend` service uses `condition: service_started` but has no healthcheck defined. The Playwright container compensates with a wget loop (line 76–77), but a proper healthcheck would be cleaner and allow `condition: service_healthy`.
---
#### S-3: Consider extracting shared mock data to a constants file
Both `documents/page.tsx` (lines 69–135) and `board/page.tsx` (lines 42–158) contain substantial mock data inline. Consider moving these to `src/data/mock/documents.ts` and `src/data/mock/board.ts` to match the existing pattern (`src/data/mock/dashboard.ts`, `src/data/mock/members.ts`, etc.).
---
## Architecture Highlights (Positive)
- **Dual-mode pattern** is well-executed: `const isMockMode = !data` → graceful fallback, operations work locally without backend
- **TestResetController** uses `@ConditionalOnProperty` — correct Spring Boot pattern, impossible to accidentally activate in prod
- **Repeatable migration** (`R__seed_test_data.sql`) with `ON CONFLICT DO NOTHING` — properly idempotent
- **Playwright 3-project config** (setup → authenticated → integration) is a solid pattern
- **Seed constants as single source of truth** — avoids magic strings in specs
- **Docker tmpfs for test DB** — fast, ephemeral, no disk pollution
- **ApiClient with DB reset in `beforeEach`** — ensures test isolation
## Recommendation
**⚠️ Approved with comments** — Fix the 2 blockers (B-1 credential mismatch, B-2 missing data-testid attributes) before running integration tests. The i18n warnings (W-2) and credential alignment (W-1) should be addressed before final merge but are not blocking functionality.