{ "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." } ] }