From ffa4a1c96a93d15f840620e999f69692e0b014b2 Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Sun, 14 Jun 2026 02:44:23 +0200 Subject: [PATCH 1/2] :white_check_mark: test: PHPUnit-Unit-Tests + CLI-Compose-Runner + GitHub-Action [*] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unit-Tests (PHPUnit 12) für die öffentlichen Kern-Klassen, plus je Paket ein CLI-only docker-compose-Runner (composer install + vendor/bin/phpunit), ein GitHub-Actions-Workflow und .gitignore. phpunit ^12 in require-dev. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/tests.yml | 24 +++++ .gitignore | 4 + composer.json | 3 + docker-compose.yml | 15 ++++ phpunit.dist.xml | 18 ++++ tests/Security/KeycloakUserProviderTest.php | 99 +++++++++++++++++++++ tests/Security/KeycloakUserTest.php | 70 +++++++++++++++ 7 files changed, 233 insertions(+) create mode 100644 .github/workflows/tests.yml create mode 100644 .gitignore create mode 100644 docker-compose.yml create mode 100644 phpunit.dist.xml create mode 100644 tests/Security/KeycloakUserProviderTest.php create mode 100644 tests/Security/KeycloakUserTest.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..afd9a6f --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,24 @@ +# file generated with AI assistance: Claude Code - 2026-06-13 23:14:54 UTC +name: Tests + +on: + push: + branches: [main, master, "feature/**"] + pull_request: + +jobs: + phpunit: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ["8.2", "8.4"] + name: PHPUnit (PHP ${{ matrix.php }}) + steps: + - uses: actions/checkout@v4 + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + - run: composer install --no-interaction --no-progress + - run: vendor/bin/phpunit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cff2af8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# file generated with AI assistance: Claude Code - 2026-06-13 23:14:54 UTC +/vendor/ +/composer.lock +/.phpunit.cache/ diff --git a/composer.json b/composer.json index 413286d..f5ddf32 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,9 @@ "symfony/security-bundle": "^7.0", "lexik/jwt-authentication-bundle": "^3.0" }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, "autoload": { "psr-4": { "Dmstr\\KeycloakSecurity\\": "src/" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..8086794 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +# file generated with AI assistance: Claude Code - 2026-06-13 23:14:54 UTC +# CLI-only test runner for this bundle — no MySQL/FPM required. +# Usage (on host): +# docker compose run --rm php +# Runs `composer install` then PHPUnit against tests/. +services: + php: + build: + dockerfile_inline: | + FROM php:8.4-cli-alpine + COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + working_dir: /repo + volumes: + - .:/repo + command: sh -c "composer install --no-interaction --no-progress && vendor/bin/phpunit" diff --git a/phpunit.dist.xml b/phpunit.dist.xml new file mode 100644 index 0000000..9dc4142 --- /dev/null +++ b/phpunit.dist.xml @@ -0,0 +1,18 @@ + + + + + + tests + + + + + src + + + diff --git a/tests/Security/KeycloakUserProviderTest.php b/tests/Security/KeycloakUserProviderTest.php new file mode 100644 index 0000000..b6abc9d --- /dev/null +++ b/tests/Security/KeycloakUserProviderTest.php @@ -0,0 +1,99 @@ +provider = new KeycloakUserProvider(); + } + + public function testMapsRealmRolesToUppercasedSymfonyRoles(): void + { + $user = $this->provider->loadUserByIdentifierAndPayload('alice', [ + 'realm_access' => ['roles' => ['admin', 'editor']], + ]); + + $roles = array_values($user->getRoles()); + self::assertContains('ROLE_ADMIN', $roles); + self::assertContains('ROLE_EDITOR', $roles); + self::assertContains('ROLE_USER', $roles); + } + + public function testFiltersKeycloakInternalRoles(): void + { + $user = $this->provider->loadUserByIdentifierAndPayload('alice', [ + 'realm_access' => ['roles' => [ + 'admin', + 'default-roles-myrealm', + 'offline_access', + 'uma_authorization', + ]], + ]); + + // Only ROLE_ADMIN survives the filter; ROLE_USER is added by the user. + self::assertEqualsCanonicalizing(['ROLE_ADMIN', 'ROLE_USER'], array_values($user->getRoles())); + } + + public function testExtractsEmailAndName(): void + { + $user = $this->provider->loadUserByIdentifierAndPayload('alice', [ + 'email' => 'alice@example.com', + 'given_name' => 'Alice', + 'family_name' => 'Anderson', + ]); + + self::assertInstanceOf(KeycloakUser::class, $user); + self::assertSame('alice', $user->getUserIdentifier()); + self::assertSame('alice@example.com', $user->getEmail()); + self::assertSame('Alice', $user->getGivenName()); + self::assertSame('Anderson', $user->getFamilyName()); + } + + public function testMissingClaimsDefaultToEmpty(): void + { + $user = $this->provider->loadUserByIdentifierAndPayload('alice', []); + + self::assertSame('', $user->getEmail()); + self::assertSame('', $user->getGivenName()); + self::assertSame('', $user->getFamilyName()); + self::assertSame(['ROLE_USER'], array_values($user->getRoles())); + } + + public function testLoadUserByIdentifierFallbackHasNoRealmRoles(): void + { + $user = $this->provider->loadUserByIdentifier('alice'); + + self::assertSame('alice', $user->getUserIdentifier()); + self::assertSame(['ROLE_USER'], array_values($user->getRoles())); + } + + public function testRefreshUserReturnsSameInstance(): void + { + $user = new KeycloakUser('alice', ['ROLE_ADMIN']); + self::assertSame($user, $this->provider->refreshUser($user)); + } + + public function testSupportsOnlyKeycloakUserClass(): void + { + self::assertTrue($this->provider->supportsClass(KeycloakUser::class)); + self::assertFalse($this->provider->supportsClass(\stdClass::class)); + } +} diff --git a/tests/Security/KeycloakUserTest.php b/tests/Security/KeycloakUserTest.php new file mode 100644 index 0000000..c934e95 --- /dev/null +++ b/tests/Security/KeycloakUserTest.php @@ -0,0 +1,70 @@ +getRoles()); + self::assertContains('ROLE_ADMIN', $roles); + self::assertContains('ROLE_USER', $roles); + } + + public function testGetRolesDeduplicatesRoleUser(): void + { + $user = new KeycloakUser('alice', ['ROLE_USER', 'ROLE_ADMIN']); + + self::assertSame( + ['ROLE_USER', 'ROLE_ADMIN'], + array_values($user->getRoles()), + 'ROLE_USER must not be duplicated when already present.' + ); + } + + public function testEmptyRolesYieldOnlyRoleUser(): void + { + self::assertSame(['ROLE_USER'], array_values((new KeycloakUser('alice'))->getRoles())); + } + + public function testGettersExposeConstructorValues(): void + { + $user = new KeycloakUser('alice', [], 'alice@example.com', 'Alice', 'Anderson'); + + self::assertSame('alice', $user->getUserIdentifier()); + self::assertSame('alice@example.com', $user->getEmail()); + self::assertSame('Alice', $user->getGivenName()); + self::assertSame('Anderson', $user->getFamilyName()); + } + + public function testGettersDefaultToEmptyStrings(): void + { + $user = new KeycloakUser('alice'); + + self::assertSame('', $user->getEmail()); + self::assertSame('', $user->getGivenName()); + self::assertSame('', $user->getFamilyName()); + } + + public function testEraseCredentialsIsNoop(): void + { + $user = new KeycloakUser('alice', ['ROLE_ADMIN'], 'alice@example.com'); + $user->eraseCredentials(); + + self::assertSame('alice', $user->getUserIdentifier()); + self::assertSame('alice@example.com', $user->getEmail()); + } +} From d43bcff6df4fcd7bed3d30b2aba0ebe360b06309 Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Sun, 14 Jun 2026 03:28:15 +0200 Subject: [PATCH 2/2] =?UTF-8?q?:green=5Fheart:=20ci:=20PHP-Matrix=20auf=20?= =?UTF-8?q?8.3/8.4=20(phpunit=20^12=20ben=C3=B6tigt=20PHP=20>=3D8.3)=20[*]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 8.2 fiel raus: PHPUnit 12 verlangt PHP >=8.3, daher schlug composer install im 8.2-Job fehl. Runtime-Constraint des Pakets (>=8.2) bleibt unverändert. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index afd9a6f..2cd720b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - php: ["8.2", "8.4"] + php: ["8.3", "8.4"] name: PHPUnit (PHP ${{ matrix.php }}) steps: - uses: actions/checkout@v4