diff --git a/CLAUDE.md b/CLAUDE.md index 843a925..d511170 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,4 +1,4 @@ -# CLAUDE.md — CoreCore (ImmobilienMaklerVerkaufssoftware) +# CLAUDE.md — CoreCore Multi-tenant admin application built on MintyPHP. Manages tenants, users, departments, roles, permissions, address books, mail, CSV imports, scheduled jobs, and Microsoft Entra ID SSO. diff --git a/agent-system/runs/CODE-DEDUP-REPOSITORY-001/plan.json b/agent-system/runs/CODE-DEDUP-REPOSITORY-001/plan.json new file mode 100644 index 0000000..b6cff0c --- /dev/null +++ b/agent-system/runs/CODE-DEDUP-REPOSITORY-001/plan.json @@ -0,0 +1,124 @@ +{ + "task_id": "CODE-DEDUP-REPOSITORY-001", + "summary": "Extract repeated repository boilerplate (ID extraction, array sanitization, unwrap/normalization) into shared support utilities to eliminate ~290 lines of duplication across 6+ repository files.", + "assumptions": [ + "MintyPHP DB layer returns nested arrays with table-name keys (e.g. $row['tenants']).", + "All repositories use the same unwrap, unwrapList, extractIds, and sanitizePositiveIds patterns.", + "A trait or static helper class is the right abstraction — no base repository class needed (repositories are thin)." + ], + "scope": { + "in": [ + "lib/Repository/Tenant/TenantRepository.php — unwrap, unwrapList, listIds extraction", + "lib/Repository/Access/RoleRepository.php — unwrap, unwrapList, listIds extraction", + "lib/Repository/Org/DepartmentRepository.php — unwrap, unwrapList, listIds extraction", + "lib/Repository/Access/PermissionRepository.php — ID extraction patterns", + "lib/Repository/Access/UserRoleRepository.php — ID sanitization", + "lib/Repository/Org/UserDepartmentRepository.php — ID sanitization", + "New file: lib/Repository/Support/RepositoryArrayHelper.php" + ], + "out": [ + "Repository method signatures (public API unchanged)", + "Repository SQL queries (not touched)", + "Service layer changes", + "Frontend changes" + ] + }, + "guardrails": { + "required_guard_ids": [ + "GR-CORE-001", + "GR-LANG-001", + "GR-LANG-002", + "GR-TEST-001", + "GR-TEST-002" + ], + "required_quality_gate_ids": [ + "QG-001", + "QG-002", + "QG-003", + "QG-006" + ] + }, + "success_criteria": [ + { + "id": "SC-001", + "criterion": "A single RepositoryArrayHelper class exists in lib/Repository/Support/ providing extractIds(), sanitizePositiveIds(), unwrap(), and unwrapList() as static methods." + }, + { + "id": "SC-002", + "criterion": "All 6+ repository files use RepositoryArrayHelper instead of inline implementations. No duplicate unwrap/extractIds/sanitize logic remains." + }, + { + "id": "SC-003", + "criterion": "Repository public method signatures and return values are identical before and after refactoring (pure internal refactor)." + }, + { + "id": "SC-004", + "criterion": "PHPUnit, PHPStan, and CS checks pass green." + } + ], + "implementation_steps": [ + { + "id": "S1", + "title": "Create RepositoryArrayHelper", + "description": "Create lib/Repository/Support/RepositoryArrayHelper.php with static methods: extractIds(array $rows, string $tableKey): array, sanitizePositiveIds(array $ids): array, unwrap(?array $row, string $tableKey): ?array, unwrapList(mixed $rows, string $tableKey): array.", + "guard_refs": ["GR-CORE-001", "GR-LANG-001"] + }, + { + "id": "S2", + "title": "Write unit tests for RepositoryArrayHelper", + "description": "Test all 4 methods with edge cases: empty arrays, null input, missing table keys, mixed types, duplicate IDs, negative IDs.", + "guard_refs": ["GR-TEST-001", "GR-TEST-002"] + }, + { + "id": "S3", + "title": "Refactor TenantRepository", + "description": "Replace inline unwrap/unwrapList/extractIds with RepositoryArrayHelper calls. Verify no signature changes.", + "guard_refs": ["GR-CORE-001"] + }, + { + "id": "S4", + "title": "Refactor RoleRepository and PermissionRepository", + "description": "Replace inline implementations with RepositoryArrayHelper calls.", + "guard_refs": ["GR-CORE-001"] + }, + { + "id": "S5", + "title": "Refactor DepartmentRepository and UserDepartmentRepository", + "description": "Replace inline implementations with RepositoryArrayHelper calls.", + "guard_refs": ["GR-CORE-001"] + }, + { + "id": "S6", + "title": "Refactor UserRoleRepository", + "description": "Replace inline ID sanitization with RepositoryArrayHelper::sanitizePositiveIds().", + "guard_refs": ["GR-CORE-001"] + }, + { + "id": "S7", + "title": "Run all quality gates", + "description": "QG-001 PHPUnit, QG-002 PHPStan, QG-003 Architecture, QG-006 CS check.", + "guard_refs": ["GR-LANG-002"] + } + ], + "tests": [ + "tests/Repository/Support/RepositoryArrayHelperTest.php — new, covers all 4 static methods with edge cases", + "tests/Architecture/CoreStarterkitContractTest.php — must stay green", + "Existing service tests that call repository methods indirectly — must stay green" + ], + "acceptance_checks": [ + "SC-001: File lib/Repository/Support/RepositoryArrayHelper.php exists with 4 public static methods", + "SC-002: rg 'foreach.*\\$row.*\\[.*id.*\\]' lib/Repository/ returns only RepositoryArrayHelper, not inline implementations", + "SC-003: git diff shows no changes to public method signatures in repository files", + "SC-004: QG-001 + QG-002 + QG-006 exit 0" + ], + "risks": [ + { + "risk": "Subtle behavioral difference in edge cases between inline and helper implementations", + "mitigation": "Write comprehensive helper tests first (TDD). Run full QG-001 after each repository file change." + }, + { + "risk": "Table key naming inconsistency across repositories", + "mitigation": "Audit all unwrap calls first to confirm table key naming. The helper accepts the key as parameter, so naming differences are handled." + } + ] +} diff --git a/agent-system/runs/TEST-COVERAGE-GATEWAYS-001/plan.json b/agent-system/runs/TEST-COVERAGE-GATEWAYS-001/plan.json new file mode 100644 index 0000000..991a4fa --- /dev/null +++ b/agent-system/runs/TEST-COVERAGE-GATEWAYS-001/plan.json @@ -0,0 +1,116 @@ +{ + "task_id": "TEST-COVERAGE-GATEWAYS-001", + "summary": "Add PHPUnit tests for 4 critical untested gateways: AuthCryptoGateway, SettingsCryptoGateway, OidcHttpGateway, PermissionGateway. These are security-critical components with 0% test coverage.", + "assumptions": [ + "AuthCryptoGateway and SettingsCryptoGateway wrap lib/Support/Crypto.php — tests verify correct delegation and error handling.", + "OidcHttpGateway makes HTTP calls to Microsoft OIDC endpoints — tests will mock the HTTP client.", + "PermissionGateway may be refactored in ARCH-GATEWAY-COMPLIANCE-001. If so, test the refactored version. If not yet done, test current version.", + "Focus on security-critical gateways first; remaining 19 untested gateways can follow in a later task." + ], + "scope": { + "in": [ + "New: tests/Service/Auth/AuthCryptoGatewayTest.php — encryption/decryption delegation", + "New: tests/Service/Settings/SettingsCryptoGatewayTest.php — settings value encryption", + "New: tests/Service/Auth/OidcHttpGatewayTest.php — OIDC token validation, HTTP error handling", + "New: tests/Service/Access/PermissionGatewayTest.php — permission lookup delegation" + ], + "out": [ + "Non-critical gateways (AddressBook, Import, Directory scope gateways)", + "Production code changes", + "Repository tests", + "Integration tests with real HTTP or crypto operations" + ] + }, + "guardrails": { + "required_guard_ids": [ + "GR-TEST-001", + "GR-TEST-002", + "GR-SEC-005", + "GR-LANG-001", + "GR-LANG-002" + ], + "required_quality_gate_ids": [ + "QG-001", + "QG-002", + "QG-006" + ] + }, + "success_criteria": [ + { + "id": "SC-001", + "criterion": "All 4 test files exist with happy-path + error/edge-case coverage for every public method." + }, + { + "id": "SC-002", + "criterion": "AuthCryptoGateway and SettingsCryptoGateway tests verify that Crypto class is used (not raw openssl), per GR-SEC-005." + }, + { + "id": "SC-003", + "criterion": "OidcHttpGateway tests cover: successful token exchange, invalid token response, network timeout, malformed response body." + }, + { + "id": "SC-004", + "criterion": "PHPUnit (QG-001) and PHPStan (QG-002) pass green." + } + ], + "implementation_steps": [ + { + "id": "S1", + "title": "Analyze public API of all 4 gateways", + "description": "Read each gateway, list public methods, identify constructor dependencies, plan test scenarios.", + "guard_refs": ["GR-TEST-001"] + }, + { + "id": "S2", + "title": "Write AuthCryptoGatewayTest", + "description": "Test encrypt/decrypt delegation to Crypto class. Test error handling for invalid keys, empty input, corrupted ciphertext.", + "guard_refs": ["GR-TEST-001", "GR-SEC-005"] + }, + { + "id": "S3", + "title": "Write SettingsCryptoGatewayTest", + "description": "Test settings value encryption/decryption. Test round-trip (encrypt then decrypt returns original). Test error paths.", + "guard_refs": ["GR-TEST-001", "GR-SEC-005"] + }, + { + "id": "S4", + "title": "Write OidcHttpGatewayTest", + "description": "Mock HTTP client. Test token exchange happy path, invalid response codes (401, 500), network errors, malformed JSON response, missing required fields in token response.", + "guard_refs": ["GR-TEST-001", "GR-TEST-002"] + }, + { + "id": "S5", + "title": "Write PermissionGatewayTest", + "description": "Test permission lookup methods. Mock underlying dependency (Service or Repository depending on ARCH-GATEWAY-COMPLIANCE-001 status).", + "guard_refs": ["GR-TEST-001", "GR-TEST-002"] + }, + { + "id": "S6", + "title": "Run quality gates", + "description": "QG-001, QG-002, QG-006.", + "guard_refs": ["GR-LANG-002"] + } + ], + "tests": [ + "tests/Service/Auth/AuthCryptoGatewayTest.php", + "tests/Service/Settings/SettingsCryptoGatewayTest.php", + "tests/Service/Auth/OidcHttpGatewayTest.php", + "tests/Service/Access/PermissionGatewayTest.php" + ], + "acceptance_checks": [ + "SC-001: All 4 test files pass with >= 3 test methods each", + "SC-002: AuthCrypto + SettingsCrypto tests assert Crypto class method calls via mock expectations", + "SC-003: OidcHttpGatewayTest has test methods for success, 401, 500, timeout, malformed JSON", + "SC-004: QG-001 + QG-002 + QG-006 exit 0" + ], + "risks": [ + { + "risk": "OidcHttpGateway may use PHP native curl/file_get_contents instead of injectable HTTP client", + "mitigation": "If not mockable, document as tech debt and test at integration level with a mock HTTP server, or refactor to accept an HTTP client interface." + }, + { + "risk": "PermissionGateway may change in ARCH-GATEWAY-COMPLIANCE-001", + "mitigation": "If ARCH task runs first, write tests against refactored version. If not, write tests against current version and note that tests may need update." + } + ] +} diff --git a/agent-system/runs/TEST-COVERAGE-POLICIES-001/plan.json b/agent-system/runs/TEST-COVERAGE-POLICIES-001/plan.json new file mode 100644 index 0000000..e940c26 --- /dev/null +++ b/agent-system/runs/TEST-COVERAGE-POLICIES-001/plan.json @@ -0,0 +1,81 @@ +{ + "task_id": "TEST-COVERAGE-POLICIES-001", + "summary": "Add PHPUnit test for the single untested authorization policy: OperationsAuthorizationPolicy. Achieves 100% policy test coverage (7/7).", + "assumptions": [ + "OperationsAuthorizationPolicy follows the same pattern as the 6 tested policies (returns AuthorizationDecision).", + "Can use the existing AuthorizationPolicyTestSupport trait for consistent assertions.", + "Policy has constructor-injected dependencies (gateways) that can be mocked." + ], + "scope": { + "in": [ + "New: tests/Service/Access/OperationsAuthorizationPolicyTest.php" + ], + "out": [ + "Other policy files (already tested)", + "Production code changes", + "Service or gateway tests" + ] + }, + "guardrails": { + "required_guard_ids": [ + "GR-TEST-001", + "GR-TEST-002", + "GR-CORE-005", + "GR-LANG-001", + "GR-LANG-002" + ], + "required_quality_gate_ids": [ + "QG-001", + "QG-002", + "QG-006" + ] + }, + "success_criteria": [ + { + "id": "SC-001", + "criterion": "OperationsAuthorizationPolicyTest exists and covers every public method with allowed + denied scenarios." + }, + { + "id": "SC-002", + "criterion": "Test uses AuthorizationPolicyTestSupport trait for consistent assertion patterns." + }, + { + "id": "SC-003", + "criterion": "All 7 policy test files pass green (100% policy coverage)." + } + ], + "implementation_steps": [ + { + "id": "S1", + "title": "Analyze OperationsAuthorizationPolicy", + "description": "Read the policy file, identify public methods, constructor dependencies, and authorization decision logic.", + "guard_refs": ["GR-CORE-005"] + }, + { + "id": "S2", + "title": "Write OperationsAuthorizationPolicyTest", + "description": "Use AuthorizationPolicyTestSupport trait. Test: allowed scenarios (correct permissions), denied scenarios (missing permissions, wrong scope), edge cases (null user, empty permissions).", + "guard_refs": ["GR-TEST-001", "GR-TEST-002"] + }, + { + "id": "S3", + "title": "Run quality gates", + "description": "QG-001, QG-002, QG-006.", + "guard_refs": ["GR-LANG-002"] + } + ], + "tests": [ + "tests/Service/Access/OperationsAuthorizationPolicyTest.php" + ], + "acceptance_checks": [ + "SC-001: Test file has >= 6 test methods covering allowed + denied + edge cases", + "SC-002: Test class uses AuthorizationPolicyTestSupport trait (assertForbiddenDecision, assertDeniedDecision)", + "SC-003: docker compose exec php vendor/bin/phpunit tests/Service/Access/ exits 0 with all 7 policy tests passing" + ], + "risks": [ + { + "risk": "OperationsAuthorizationPolicy may have different constructor signature than other policies", + "mitigation": "Read the file first (S1). Adapt mocking strategy to match actual dependencies." + } + ] +} diff --git a/agent-system/runs/TEST-COVERAGE-SERVICES-001/plan.json b/agent-system/runs/TEST-COVERAGE-SERVICES-001/plan.json new file mode 100644 index 0000000..beb7e2c --- /dev/null +++ b/agent-system/runs/TEST-COVERAGE-SERVICES-001/plan.json @@ -0,0 +1,149 @@ +{ + "task_id": "TEST-COVERAGE-SERVICES-001", + "summary": "Add PHPUnit tests for 7 critical untested services: PermissionService, RoleService, DepartmentService, TenantService, MailService, UserLifecycleService, RateLimiterService.", + "assumptions": [ + "All 7 services use constructor injection and are unit-testable with mocks (GR-TEST-002 compliant).", + "Focus on happy path + at least one failure/edge case per public method (GR-TEST-001).", + "Services that orchestrate repositories will have repositories mocked.", + "MailService test will mock PHPMailer — no actual email sending." + ], + "scope": { + "in": [ + "New: tests/Service/Access/PermissionServiceTest.php — CRUD + permission assignment logic", + "New: tests/Service/Access/RoleServiceTest.php — CRUD + role validation", + "New: tests/Service/Org/DepartmentServiceTest.php — CRUD + hierarchy validation", + "New: tests/Service/Tenant/TenantServiceTest.php — CRUD + tenant lifecycle", + "New: tests/Service/Mail/MailServiceTest.php — composition + send + error handling", + "New: tests/Service/User/UserLifecycleServiceTest.php — state transitions (active/inactive/deleted)", + "New: tests/Service/Security/RateLimiterServiceTest.php — rate limit enforcement + reset" + ], + "out": [ + "Repository tests (separate task)", + "Gateway tests (separate task)", + "Integration/E2E tests", + "Production code changes (test-only task)", + "Medium/low priority untested services (BrandingService, SearchDataService, etc.)" + ] + }, + "guardrails": { + "required_guard_ids": [ + "GR-TEST-001", + "GR-TEST-002", + "GR-LANG-001", + "GR-LANG-002" + ], + "required_quality_gate_ids": [ + "QG-001", + "QG-002", + "QG-006" + ] + }, + "success_criteria": [ + { + "id": "SC-001", + "criterion": "All 7 test files exist and contain at least one happy-path and one failure/edge-case test per public method of the corresponding service." + }, + { + "id": "SC-002", + "criterion": "Each test file uses proper mocking (createMock) for all injected dependencies — no real DB or HTTP calls." + }, + { + "id": "SC-003", + "criterion": "Total new test methods >= 40 across the 7 files (average ~6 per service)." + }, + { + "id": "SC-004", + "criterion": "PHPUnit full suite (QG-001) passes green including all new tests." + }, + { + "id": "SC-005", + "criterion": "PHPStan (QG-002) passes green — test files are type-safe." + } + ], + "implementation_steps": [ + { + "id": "S1", + "title": "Analyze public API of all 7 services", + "description": "Read each service file, list all public methods, identify constructor dependencies, and plan test scenarios (happy path + edge cases).", + "guard_refs": ["GR-TEST-001"] + }, + { + "id": "S2", + "title": "Write PermissionServiceTest", + "description": "Test CRUD operations (createFromAdmin, updateFromAdmin, deleteById), permission assignment, and validation error paths.", + "guard_refs": ["GR-TEST-001", "GR-TEST-002"] + }, + { + "id": "S3", + "title": "Write RoleServiceTest", + "description": "Test CRUD operations, role validation (duplicate name, missing fields), and permission assignment.", + "guard_refs": ["GR-TEST-001", "GR-TEST-002"] + }, + { + "id": "S4", + "title": "Write DepartmentServiceTest", + "description": "Test CRUD operations, hierarchy validation, tenant scope enforcement.", + "guard_refs": ["GR-TEST-001", "GR-TEST-002"] + }, + { + "id": "S5", + "title": "Write TenantServiceTest", + "description": "Test CRUD operations, tenant lifecycle (activate/deactivate), slug generation, validation.", + "guard_refs": ["GR-TEST-001", "GR-TEST-002"] + }, + { + "id": "S6", + "title": "Write MailServiceTest", + "description": "Test email composition, recipient validation, template rendering, and error handling when SMTP fails. Mock PHPMailer.", + "guard_refs": ["GR-TEST-001", "GR-TEST-002"] + }, + { + "id": "S7", + "title": "Write UserLifecycleServiceTest", + "description": "Test state transitions: active->inactive, inactive->active, soft-delete, restore. Test audit trail calls.", + "guard_refs": ["GR-TEST-001", "GR-TEST-002"] + }, + { + "id": "S8", + "title": "Write RateLimiterServiceTest", + "description": "Test rate limit enforcement (under limit, at limit, over limit), reset, and TTL expiration.", + "guard_refs": ["GR-TEST-001", "GR-TEST-002"] + }, + { + "id": "S9", + "title": "Run quality gates", + "description": "QG-001 PHPUnit full, QG-002 PHPStan, QG-006 CS check.", + "guard_refs": ["GR-LANG-002"] + } + ], + "tests": [ + "tests/Service/Access/PermissionServiceTest.php", + "tests/Service/Access/RoleServiceTest.php", + "tests/Service/Org/DepartmentServiceTest.php", + "tests/Service/Tenant/TenantServiceTest.php", + "tests/Service/Mail/MailServiceTest.php", + "tests/Service/User/UserLifecycleServiceTest.php", + "tests/Service/Security/RateLimiterServiceTest.php" + ], + "acceptance_checks": [ + "SC-001: Each test file covers every public method of its service with happy + failure paths", + "SC-002: rg 'createMock' in all 7 test files shows proper dependency mocking", + "SC-003: phpunit --list-tests | grep -c 'Test::test' for new files totals >= 40", + "SC-004: docker compose exec php vendor/bin/phpunit exits 0", + "SC-005: docker compose exec php vendor/bin/phpstan analyse exits 0" + ], + "risks": [ + { + "risk": "Services with complex dependencies (MailService depends on PHPMailer, SMTP config) may be hard to mock", + "mitigation": "Use interface-based mocking. If PHPMailer is used directly, mock at the gateway boundary instead." + }, + { + "risk": "UserLifecycleService may have implicit state dependencies (session, tenant context)", + "mitigation": "Analyze constructor carefully. Mock all injected gateways/services. If static state access exists, document as tech debt for later refactor." + }, + { + "risk": "Scope creep — discovering untestable code that needs refactoring", + "mitigation": "Document untestable patterns as findings but do NOT refactor production code in this task. Create follow-up tasks instead." + } + ] +} diff --git a/config/settings.php b/config/settings.php index 8afe0ee..9b2d7c2 100644 --- a/config/settings.php +++ b/config/settings.php @@ -1,4 +1,5 @@ 'CoreCore', 'app_locale' => 'de', diff --git a/docs/tutorial-02-systemueberblick.md b/docs/tutorial-02-systemueberblick.md index b93f25d..cd77333 100644 --- a/docs/tutorial-02-systemueberblick.md +++ b/docs/tutorial-02-systemueberblick.md @@ -8,7 +8,7 @@ In 10 Minuten verstehen, was die Software tut, wie sie aufgebaut ist und welche ## Was ist CoreCore? -CoreCore ist eine mandantenfähige Admin-Software für Immobilienmakler-Prozesse mit klarer Rollen-/Rechtestruktur. +CoreCore ist eine mandantenfähige Admin-Software mit klarer Rollen-/Rechtestruktur. Kernmodule: