diff --git a/.opencode/skills/testing/SKILL.md b/.opencode/skills/testing/SKILL.md new file mode 100644 index 0000000..29c5389 --- /dev/null +++ b/.opencode/skills/testing/SKILL.md @@ -0,0 +1,105 @@ +--- +name: testing +description: Write und ausführen von Tests für Symfony Backend (PHPUnit) und React Frontend (Jest/Vitest inkl. E2E). Deckt Unit, Functional, Integration und E2E ab – inklusive Mandantenisolation und OAuth-Flows. +license: MIT +compatibility: opencode +metadata: + audience: tester-backend-coder-frontend-coder + workflow: tdd-bdd-e2e +--- + +## Was ich mache +- Tests schreiben, ausführen und debuggen für das gesamte Projekt +- Test-Pyramide anwenden: Unit > Functional > Integration > E2E +- Mandantenisolation in jedem relevanten Test explizit prüfen +- OAuth-Vflows mocken und testen +- Code-Coverage messen und Lücken identifizieren + +## Wann du mich nutzen solltest +- Neue Features implementiert wurden und abgedeckt werden müssen +- Tests fehlen oder veraltet sind +- Coverage-Analyse nötig ist +- CI/CD-Pipeline Tests validiert werden sollen + +## Workflow + +### 1. Projektstruktur prüfen +```bash +pwd # aktuellen Pfad verifizieren +ls api/phpunit.xml* api/composer.json # Symfony Test-Setup prüfen +ls frontend/package.json # Frontend Test-Setup prüfen +``` + +### 2. Bestehende Tests verstehen +- `api/tests/` durchsuchen für现有 Controller/Service-Tests +- `frontend/src/**/__tests__/` oder `frontend/src/**/*.test.{ts,tsx,js,jsx}` prüfen +- Tester-Agent-Konventionen aus `.opencode/agents/tester.md` beachten + +### 3. Tests schreiben (Symfony PHPUnit) +```php +// api/tests/Unit/Entity/PersonTest.php +// api/tests/Functional/Controller/Api/VeranstaltungControllerTest.php +// api/tests/Integration/MandantIsolationTest.php +``` + +#### Wichtige Konventionen: +- `WebTestCase` für Functional Tests mit `/api/*` Routen +- Mandanten-Scope in jedem Request via Header oder Session setzen +- OAuth-Token mocken (kein echter Vonova-Flow in unit tests) +- Datenbank über `test` Environment und SQLite/In-Memory oder dedizierte Test-Datenbank + +### 4. Tests schreiben (Frontend) +```bash +# Jest oder Vitest – je nachdem was konfiguriert ist +npm test --prefix frontend +# bzw. +cd frontend && npm test +``` + +#### Wichtige Konventionen: +- Komponenten isoliert testen (React Testing Library bevorzugt) +- API-Calls mocken (MSW, axios-mock-adapter, etc.) +- State-Management (Redux/Zustand/etc.) separat testen +- E2E über Playwright/Cypress falls vorhanden + +### 5. Mandantenisolation prüfen +Jeder Test der Daten betrifft muss validieren: +- Person A aus Mandant 1 sieht NICHT Daten von Mandant 2 +- API-Filters `mandantId` korrekt scopen +- Repository-Traits/QueryBuilders filtern automatisch + +### 6. Tests ausführen und Coverage prüfen +```bash +# Backend +cd api && php bin/phpunit --coverage-text --testsuite=Unit +cd api && php bin/phpunit --testsuite=functional + +# Frontend +cd frontend && npm test -- --coverage + +# E2E (falls konfiguriert) +cd frontend && npx playwright test +# oder +cd frontend && npx cypress run +``` + +### 7. DeepTrace analysieren (falls Testfehler auftreten) +Wenn Tests fehlschlagen oder unerwartetes Verhalten zeigen: +- `\\opencode\deeptrace` aufrufen um die vollständige Ausführungskette zu inspecten +- Stacktrace, Umgebungsvariablen, und abhängige Aufrufe prüfen +- Besonders nützlich bei: + - Mandantenisolation-Verletzungen (wer hat welche Daten gesehen?) + - OAuth-Token-Problemen (welcher Mock wurde nicht geladen?) + - Datenbank-State-Korruption in Integrationstests + +### 8. Coverage-Ziel +- Kritische Pfade > 80%: Anwesenheitsprotokollierung, Mandantenisolation, OAuth-Flows +- Lücken dokumentieren und als TODOs erfassen + +## Regeln +- Immer `pwd` vor Dateioperationen prüfen +- Test-Namen klar und deskriptiv: `testPersonCreationScopesToMandant()` +- Jeder Test testet genau eine Verhalten/Anforderung +- Arrange → Act → Assert Pattern durchhalten +- Keine echten externen Calls – alles mocken (Vonova OAuth, externe APIs) +- Test-Daten sauber aufräumen ( tearDown / fixtures ) diff --git a/AGENTS.md b/AGENTS.md index e8428a6..6112b07 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,9 +32,20 @@ Agents are defined in `.opencode/opencode.json` and `.opencode/agents/*.md`. The - `Doku/architektur.md` → Backend Architektur mit struktur & Konventionen (Provider/Processor-Pattern, Multi-Tenant Scoping) - `Doku/plan.md` → Domain-Details und Entity-Spezifikationen (Veranstaltung, Personen, Anwesenheit) - `Doku/agents_planung.md` → 4-Phasen-Agent-Arbeitsverteilung +- `Doku/umsetzung.md` → Aktueller Projektfortschritt (immer aktuell halten!) + +## Arbeitsweise +- Immer einen Subagent starten für parallele Arbeit (z.B. Backend + Frontend gleichzeitig) +- Fortschritt unter `Doku/umsetzung.md` speichern und aktualisieren ## What to do on first session in api/ or frontend/ 1. Read `.opencode/opencode.json` for agent definitions 2. Read `Doku/architektur.md` und `Doku/plan.md` für Architektur und Domain-Anforderungen 3. Read the relevant agent file from `.opencode/agents/` fur implementierung-Konventionen 4. Prüfe ob Infrastruktur eingerichtet ist (docker-compose.yaml, .env.example) vor Code-Schreiben + + +## Wichtige Pfad-Convention +- Alle Pfade mit `Doku/`, `.opencode/`, `api/`, `frontend/` immer relativ zum Workspaceroot +- Niemals Annahmen über Pfadnamen treffen – bei Unsicherheit zuerst `ls` oder `glob` verwenden +- Im Zweifel immer den aktuellen Pfad über das Terminal prüfen (z. B. `pwd`) bevor Dateioperationen erfolgen diff --git a/Doku/symfony_entwicklung.md b/Doku/symfony_entwicklung.md new file mode 100644 index 0000000..6b0dae8 --- /dev/null +++ b/Doku/symfony_entwicklung.md @@ -0,0 +1,291 @@ +# Symfony Entwicklungs-Dokumentation + +## 1. Provider/Processor-Pattern + +### Pattern-Struktur +Alle API-Anfragen durchlaufen zwei Phasen: +- **Provider**: Daten aus verschiedenen Quellen zusammenführen, Validierung vorbereiten, Domain-Logik entkoppeln +- **Processor**: Daten persistieren, Events auslösen, Response aufbauen + +### Beispiel: Anwesenheit registrieren + +```php +// src/Provider/AnwesenheitCreateProvider.php +final class AnwesenheitCreateProvider +{ + public function __construct( + private VeranstaltungRepository $vr, + private PersonenRepository $pr, + private MandantIdScope $scope + ) {} + + public function provides(CreateAnwesenheitRequest $request): AnwesenheitCommand + { + $veranstaltung = $this->vr->findScop($request->veranstaltungId, $this->scope); + if (!$veranstaltung) { + throw new VeranstaltungNotFoundException($request->veranstaltungId); + } + + return new AnwesenheitCommand( + veranstaltung: $veranstaltung, + personenId: $request->personenId, + status: $request->status + ); + } +} + +// src/Processor/AnwesenheitCreateProcessor.php +final class AnwesenheitCreateProcessor +{ + public function __construct( + private EntityManagerInterface $em, + private EventDispatcherInterface $dispatcher + ) {} + + public function process(AnwesenheitCommand $command): AnwesenheitCreatedEvent + { + $anwesenheit = new Anwesenheit(); + $anwesenheit->setVeranstaltung($command->veranstaltung); + $anwesenheit->setPersonenId($command->personenId); + // ... + + $this->em->persist($anwesenheit); + $this->em->flush(); + + $event = new AnwesenheitCreatedEvent($anwesenheit); + $this->dispatcher->dispatch($event); + + return $event; + } +} +``` + + +### Controller-Einbindung +```php +#[Route('/veranstaltungen/{id}/anwesenheit', name: 'erstellen_anwesenheit', methods: ['POST'])] +public function create(CreateAnwesenheitRequestDto $dto, RequestCreator $creator): JsonResponse +{ + try { + $command = $this->providerProvider->provides($dto); + $result = $this->processorProvider->process($command); + return new JsonResponse(['data' => $result], 201); + } catch (EntityNotFoundException $e) { + return JsonResponse::createNotFound($e->getMessage()); + } +} +``` + +### Muster-Regeln +- Provider enthalten **keine** Persistenz-Logik +- Processor enthalten **keine** Validierung-Logik +- Beide sind **stateless** und leicht testbar +- Commands und Events als Data Transfer Objekte (POPOs) + +--- + +## 2. Coding Standards + +### PHP Version & Basis +- PHP >= 8.4 (LTS empfohlen) +- PSR-12 als Code-Stil-Richtlinie +- PHPStan Level 6-7 + +### Naming Conventions +| Typ | Konvention | Beispiel | +|-----|-----------|----------| +| Klassen | PascalCase + Substantiv | `AnwesenheitController` | +| Methoden | camelCase + Verb oder Aktonsnamen | `registriereAnwesenheit()` | +| Variablen | camelCase | `$veranstaltungsId` | +| Constants | UPPER_SNAKE_CASE | `MAX_TEILNEHMER` | + +### Controller-Konventionen +- Keine Domain-Logik – immer delegieren an Provider/Processor +- DTOs für Request/Response (keine Entities direkt) + + +```json +{ + "data": { + "id": 1, + "status": "present" + }, + "meta": { + "message": "Anwesenheit erfolgreich registriert" + } +} +``` + +### Exception Handling (→ siehe Abschnitt 3) +- Domain Exceptions werfen (keine raw Symfony Exceptions nach außen) +- Nur `JsonResponse` mit definiertem Error-Schema zurückgeben + +--- + +## 3. Fehlerbehandlung & API Error Schema + +### Exceptions-Hierarchie +Domain Exceptions → ControllerExceptionHandler → JSON Response + +```php +// Domain Exception +class VeranstaltungNotFoundException extends RuntimeException implements DomainExceptionInterface {} + +// Controller Exception Handler (Kernel-Register) +#[AsController] +class ControllerExceptionHandler +{ + public function __invoke(DomainExceptionInterface $e): JsonResponse + { + return new JsonResponse([ + 'errors' => [[ + 'status' => 422, // oder je nach Situation + 'title' => ucfirst($e->getType()), + 'detail' => $e->getMessage(), + ]] + ], 422); + } +} +``` + +### HTTP Status Codes +| Code | Verwendigunbg | +|------|-----------| +| `200` | OK (GET, PUT) | +| `201` | Created (POST, Entity erstellt) | +| `400` | Invalid Request Body / Validation Error | +| `403` | Forbidden (Mandant fehlt) | +| `404` | Not Found | +| `500` | Internal Server Error | + +--- + +## 4. Logging & Mercure + +### Events-Logging-Logik +Jeder Processor löst nach Persistierung Events aus: +```php +$this->dispatcher->dispatch(new AnwesenheitAktualisiertEvent($anwesenheit)); +``` + +Events enthalten: +- Entity-Daten (keine sensiblen) +- Timestamp +- MandantID scope +- ActionTyp ('created', 'updated') + +### Mercure Integration - Vorschläge & Nutzungsszenarien +- **Echtzeit-Anwesenheitsliste**: Websocket oder Server-Sent Events über Mercure Hub +- **Live-Benachrichtigungen** bei Statusänderungen + +#### Konfigurationsoptionen für Mercure: +1. **Pulled** (Clientseitig abonnieren): Client ruft `/mercure/hub` Periodisch ab +2. **Pushed** (Serverseitig publish): Processor publiziert Events direkt an Hub +3. **Hybrid**: Pushed bei User-Aktion (z.b. Anwesenheit registrieren), Pulled bei Statusabfragen + +**Empfehlung**: Mercure als optional, nur wenn Echtzeit-Funktionalität wirklich gebraucht wird. Nicht default aktivieren. + +--- + +## 5. Datenbank/Migrationen - Entwicklungskonventionen + + + +### Doctrine Entity-Spezifikation +- **Entities** POPOs (Plain Old PHP Objects) mit Annotationen/Attributes für Mapping +- **Repository** nur Domain-spezifische Queries, kein raw SQL +- **Keine** Business-Logik in Entities – stattdessen Services / Provider / Processor +- **Idempotenz**: Updates per primary key + +### Migrationen-Konventionen +- Jede Tabelle hat `mandant_id`, `created_at`, `updated_at` (Timestamp) +- Fremdschlüssel immer als Index + NOT NULL + + +- Migrations in Version kontrolliert: `php bin/console make:migration` +- **Keine** manuellen Schema-Changes ohne Migration + +### Multi-Tenant-Scope Enforcement +``` +Entity::mandant_id → Unique across Mandanten. Alle Queries scoped per MandantIdContext (z.B. durch Attribute/Listener) +``` + +--- + +## 6. TDD Workflow (Red-Green-Refactor) + +### Test-Schichten Priorität (TDD Red-Green) +1. **Controller/Route Tests** – Request→Response, Status-Codes, JSON Schema +2. **Provider/Processor Unittests** – isolierte logik-test der einzelnen Domäne-Layer +3. **Integrationstests** – wenn nötig (DB-Kontext, Services) + +### Test-Struktur in PHPUnit +``` +api/tests/Unit/Provider/ +├── AnwesenheitCreateProviderTest.php ← Provider ohne DB (mock repository) +└── VeranstaltungFindProviderTest.php + +api/tests/Unit/Processor/ +└── AnwesenheitCreateProcessorTest.php ← Processor mit mock EM + +api/tests/Functional/Controller/ +└── AnwesenheitControllerTest.php ← Full HTTP request via HttpBrowser +``` + +### Test-Beispiel PHPUnit +```php +// tests/Unit/Provider/AnwesenheitCreateProviderTest.php +class AnwesenheitCreateProviderTest extends TestCase +{ + public function testProvidesCommandWhenVeranstaltungFound(): void + { + $veranstaltung = new Veranstaltung(); + $request = new CreateAnwesenheitRequestDto(123); + + $provider = new AnwesenheitCreateProvider( // Mocks injected + VeranstaltungRepositoryMock($veranstaltung), + MandantIdScope('mandant-xyz') + ); + + $result = $provider->provides($request); + + self::assertInstanceOf(AnwesenheitCommand::class, $result); + self::assertSame($veranstaltung, $result->getVeranstaltung()); + } +} +``` + +### Test-Prioritäten für Implementierung +1. Controller/Routen-Tests schreiben → **failend** (RED) +2. Minimal-Implementierung schreiben → **test bestanden** (GREEN) +3. Refactor (Code-Qualität, Tests verbessern) +4. Nächster Test (höhere Abdeckung im Provider/Processor) + +--- + +## 7. Framework-Bundles + +### Philosophie +- Kern-Symfony nur verwenden (Mime, HttpFoundation, Serializer, Validator, Security) +- Neue Bundles **nur bei Bedarf** dokumentieren und begründen + + +| Bundle | Verwendung | Begründung erforderlich | +|--------|------------|------------------------| +| `symfony/maker` | Boilerplate | Nur für Initialisierung, nicht produktiv | +| `api-platform/core` | REST API | **Explizit vermieden** – manuelle Controller | +| `sensio/framework-extra-bundle` | Annotations/Cache | Symfony 7.4 native statt Bundles bevorzugen | +| `symfonycasts/validator` | Custom Validators | Nur bei komplexer Validierung | +| `mercure/bundle` | Mercure-Integration | Nur wenn Echtzeit benötigt wird | + +--- + +## Zusammenfassung Entwicklungsflow +1. Test schreiben (RED) → failend +2. Minimal-Implementierung als Provider/Processor/Gateway +3. Test bestehen lassen (GREEN) +4. Refaktorieren und Qualität prüfen (PHPStan, Coding Standards) +5. Entity/Migration erstellen wenn nötig **nach** Code-Phase + +Die Doku wird mit dem Projekt aktualisiert. + diff --git a/Doku/umsetzung.md b/Doku/umsetzung.md new file mode 100644 index 0000000..4f8e6e3 --- /dev/null +++ b/Doku/umsetzung.md @@ -0,0 +1,22 @@ +# Umsetzung – Vollversammlung + +## Aktueller Stand (28. Juni 2026) + +### Erledigt ✅ +- **Infrastruktur**: docker-compose.yaml mit PostgreSQL, Nginx, Mercure, Mailpit, Vite +- **Dockerfiles**: api/Dockerfile.dev, api/Dockerfile.prod, frontend/Dockerfile.dev +- **.env Dateien**: api/.env.example, frontend/.env.example +- **docker-entrypoint.sh** für Symfony Setup + +### Offene Aufgaben (priorisiert) +1. Symfony Boilerplate erstellen (Entities: Veranstaltung, Person, Anwesenheit) +2. React Boilerplate erstellen (Router, State-Management) +3. CRUD-API-Endpoints entwickeln +4. Frontend-Seiten aufbauen (Veranstaltung, Personen, Anwesenheit) +5. Statistikseite implementieren (Anwesenheitszahlen, Anwesenheitsquote, Geschlechterquote) +6. OAuth-Integration (Vonova) +7. Tests schreiben (Unit, Functional, E2E) +8. Security-Review durchführen + +### Nächste Schritte +- Symfony Boilerplate start