readProjectFile('pages/api/v1/auth/login().php'); $this->assertStringContainsString('ApiBootstrap::init(false);', $content); } public function testApiLoginUsesUniformCredentialErrorAndTimingEqualization(): void { $content = $this->readProjectFile('pages/api/v1/auth/login().php'); $this->assertStringContainsString('$dummyPasswordHash =', $content); $this->assertStringContainsString('password_verify($password, $userPasswordHash)', $content); $this->assertStringContainsString("ApiResponse::error('invalid_credentials', 401);", $content); $this->assertStringNotContainsString("ApiResponse::error('account_inactive', 401);", $content); } public function testApiBootstrapSupportsOptionalAuthRequirement(): void { $content = $this->readProjectFile('core/Http/ApiBootstrap.php'); $this->assertStringContainsString('public static function init(bool $requireAuth = true): void', $content); $this->assertStringContainsString('if ($requireAuth) {', $content); $this->assertStringContainsString('$authenticated = ApiAuth::authenticate();', $content); $this->assertStringContainsString('ApiResponse::unauthorized();', $content); } public function testApiUsersEndpointsUseCentralUserPolicy(): void { $indexContent = $this->readProjectFile('pages/api/v1/users/index().php'); $this->assertStringContainsString('AuthorizationService::class', $indexContent); $this->assertStringContainsString('UserAuthorizationPolicy::ABILITY_API_USERS_INDEX_GET', $indexContent); $this->assertStringContainsString('UserAuthorizationPolicy::ABILITY_API_USERS_INDEX_POST', $indexContent); $showContent = $this->readProjectFile('pages/api/v1/users/show($id).php'); $this->assertStringContainsString('AuthorizationService::class', $showContent); $this->assertStringContainsString('UserAuthorizationPolicy::ABILITY_API_USERS_SHOW_GET', $showContent); $this->assertStringContainsString('UserAuthorizationPolicy::ABILITY_API_USERS_SHOW_UPDATE', $showContent); $this->assertStringContainsString('UserAuthorizationPolicy::ABILITY_API_USERS_SHOW_DELETE', $showContent); } public function testApiPermissionAndSettingsEndpointsHaveExpectedGuards(): void { $permissionContent = $this->readProjectFile('pages/api/v1/permissions/index().php'); $this->assertStringContainsString('ApiResponse::requireAbility(\\MintyPHP\\Service\\Access\\OperationsAuthorizationPolicy::ABILITY_API_PERMISSIONS_VIEW);', $permissionContent); $this->assertStringContainsString("foreach ((\$result['rows'] ?? []) as \$row)", $permissionContent); $settingsContent = $this->readProjectFile('pages/api/v1/settings/index().php'); $this->assertStringContainsString('ApiResponse::requireAbility(\\MintyPHP\\Service\\Access\\OperationsAuthorizationPolicy::ABILITY_API_SETTINGS_VIEW);', $settingsContent); $this->assertStringContainsString('ApiResponse::requireAbility(\\MintyPHP\\Service\\Access\\OperationsAuthorizationPolicy::ABILITY_API_SETTINGS_UPDATE);', $settingsContent); } public function testApiMeEndpointsUseSelfServicePermissions(): void { $meContent = $this->readProjectFile('pages/api/v1/me/index().php'); $this->assertStringContainsString('$method === \'PUT\' || $method === \'PATCH\'', $meContent); $this->assertStringContainsString('OperationsAuthorizationPolicy::ABILITY_API_ME_SELF_UPDATE', $meContent); $this->assertStringContainsString('ApiResponse::validationFromFormErrors(', $meContent); $passwordContent = $this->readProjectFile('pages/api/v1/me/password().php'); $this->assertStringContainsString("ApiResponse::requireMethod('POST');", $passwordContent); $this->assertStringContainsString('ApiResponse::requireAbility(\\MintyPHP\\Service\\Access\\OperationsAuthorizationPolicy::ABILITY_API_ME_SELF_UPDATE);', $passwordContent); $avatarContent = $this->readProjectFile('pages/api/v1/me/avatar().php'); $this->assertStringContainsString("define('MINTY_ALLOW_OUTPUT', true);", $avatarContent); $this->assertStringContainsString('OperationsAuthorizationPolicy::ABILITY_API_ME_SELF_UPDATE', $avatarContent); } public function testApiAvatarEndpointsUseExpectedAuthorizationModel(): void { $userAvatarContent = $this->readProjectFile('pages/api/v1/users/avatar($id).php'); $this->assertStringContainsString('UserAuthorizationPolicy::ABILITY_API_USERS_SHOW_GET', $userAvatarContent); $tenantAvatarContent = $this->readProjectFile('pages/api/v1/tenants/avatar($id).php'); $this->assertStringContainsString('OperationsAuthorizationPolicy::ABILITY_API_TENANTS_VIEW', $tenantAvatarContent); $this->assertStringContainsString("ApiAuth::requireResourceAccess('tenants', \$tenantId);", $tenantAvatarContent); } }