diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..90f3600 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,72 @@ +name: CI + +on: + pull_request: + push: + branches: [ main, develop ] + +jobs: + run: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: + - '8.3' + coverage: ['none'] + symfony-versions: + - '6.4.*' + - '7.0.*' + + name: Test with PHP ${{ matrix.php }} Symfony ${{ matrix.symfony-versions }} ${{ matrix.description }} + steps: + - name: Checkout + uses: actions/checkout@v2 + + - uses: actions/cache@v2 + with: + path: ~/.composer/cache/files + key: ${{ matrix.php }}-${{ matrix.symfony-versions }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: xdebug + extensions: xdebug + + - name: Add PHPUnit matcher + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Set composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer + uses: actions/cache@v2.1.2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.symfony-versions }}-composer-${{ hashFiles('composer.json') }} + restore-keys: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.symfony-versions }}-composer + + - name: Update Symfony version + if: matrix.symfony-versions != '' + run: | + composer require symfony/config:${{ matrix.symfony-versions }} --no-update --no-scripts + composer require symfony/dependency-injection:${{ matrix.symfony-versions }} --no-update --no-scripts + composer require symfony/http-kernel:${{ matrix.symfony-versions }} --no-update --no-scripts + composer require symfony/cache:${{ matrix.symfony-versions }} --no-update --no-scripts + composer require --dev symfony/yaml:${{ matrix.symfony-versions }} --no-update --no-scripts + composer require --dev symfony/phpunit-bridge:${{ matrix.symfony-versions }} --no-update --no-scripts + composer require --dev symfony/framework-bundle:${{ matrix.symfony-versions }} --no-update --no-scripts + + - name: Install dependencies + run: composer install + + - name: Run PHPUnit tests + run: XDEBUG_MODE=coverage vendor/bin/phpunit + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v4.0.1 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/security.yaml b/.github/workflows/security.yaml new file mode 100644 index 0000000..248d5d0 --- /dev/null +++ b/.github/workflows/security.yaml @@ -0,0 +1,24 @@ +on: + pull_request: + push: + branches: [ main, develop ] + +jobs: + security-checker: + name: Security checker + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + + - name: Install dependencies + run: composer install --no-progress --no-interaction --prefer-dist + + - name: Download local-php-security-checker + run: curl -s -L -o local-php-security-checker https://github.com/fabpot/local-php-security-checker/releases/download/v1.0.0/local-php-security-checker_1.0.0_linux_amd64 + + - name: Run local-php-security-checker + run: chmod +x local-php-security-checker && ./local-php-security-checker diff --git a/.gitignore b/.gitignore index 1473148..416d30e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,8 @@ /phpunit.xml .phpunit.result.cache ###< phpunit/phpunit ### + +/reports +/coverage +/tests/App/var +/var diff --git a/composer.json b/composer.json index d81020f..ee9c7d4 100644 --- a/composer.json +++ b/composer.json @@ -7,18 +7,27 @@ "Macpaw\\DoctrineAwsIamRdsAuthBundle\\": "src/" } }, + "autoload-dev": { + "psr-4": { + "Macpaw\\DoctrineAwsIamRdsAuthBundle\\Tests\\": "tests/" + } + }, "require": { "php": ">=8.3", "doctrine/orm": "^2.17", - "symfony/dependency-injection": "^6.4", - "symfony/config": "^6.4", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", "aws/aws-sdk-php": "^3.303", - "symfony/cache": "^6.4", - "symfony/http-kernel": "^6.4" + "symfony/cache": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" }, "require-dev": { "phpstan/phpstan": "^1.10", - "squizlabs/php_codesniffer": "^3.9" + "squizlabs/php_codesniffer": "^3.9", + "phpunit/phpunit": "^11.1", + "symfony/phpunit-bridge": "^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" }, "config": { "allow-plugins": { diff --git a/config/services.yaml b/config/services.yaml index e75d7c9..4c6de91 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -4,9 +4,16 @@ services: autoconfigure: true public: false + Macpaw\DoctrineAwsIamRdsAuthBundle\Factory\AuthTokenGeneratorFactory: ~ + + rds.authTokenGenerator: + class: Aws\Rds\AuthTokenGenerator + factory: '@Macpaw\DoctrineAwsIamRdsAuthBundle\Factory\AuthTokenGeneratorFactory' + Macpaw\DoctrineAwsIamRdsAuthBundle\Aws\Token\RdsTokenProvider: arguments: $lifetime: '%env(RDS_TOKEN_LIFETIME_MINUTES)%' + $generator: '@rds.authTokenGenerator' Macpaw\DoctrineAwsIamRdsAuthBundle\Aws\Token\RdsTokenProviderCacheDecorator: decorates: Macpaw\DoctrineAwsIamRdsAuthBundle\Aws\Token\RdsTokenProvider diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..1118df7 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + tests/Unit + + + tests/Functional + + + + + ./src + + + + + + + + + + + diff --git a/src/Aws/Token/RdsTokenProvider.php b/src/Aws/Token/RdsTokenProvider.php index d6b2b9e..18b9a13 100644 --- a/src/Aws/Token/RdsTokenProvider.php +++ b/src/Aws/Token/RdsTokenProvider.php @@ -7,15 +7,17 @@ use Aws\Credentials\CredentialProvider; use Aws\Rds\AuthTokenGenerator; -class RdsTokenProvider implements TokenProviderInterface +final readonly class RdsTokenProvider implements TokenProviderInterface { - private readonly int $lifetime; - private readonly AuthTokenGenerator $generator; + private int $lifetime; + private AuthTokenGenerator $generator; - public function __construct(int $lifetime) - { + public function __construct( + AuthTokenGenerator $generator, + int $lifetime, + ) { $this->lifetime = $lifetime; - $this->generator = new AuthTokenGenerator(CredentialProvider::defaultProvider()); + $this->generator = $generator; } public function getToken(string $endpoint, string $region, string $username, bool $refresh = false): string diff --git a/src/Aws/Token/RdsTokenProviderCacheDecorator.php b/src/Aws/Token/RdsTokenProviderCacheDecorator.php index fe8294c..7f5fe68 100644 --- a/src/Aws/Token/RdsTokenProviderCacheDecorator.php +++ b/src/Aws/Token/RdsTokenProviderCacheDecorator.php @@ -6,14 +6,14 @@ use Macpaw\DoctrineAwsIamRdsAuthBundle\Cache\CacheStorageInterface; -class RdsTokenProviderCacheDecorator implements TokenProviderInterface +final readonly class RdsTokenProviderCacheDecorator implements TokenProviderInterface { private const string CACHE_KEY_PREFIX = 'rds_token_'; public function __construct( - private readonly TokenProviderInterface $tokenProvider, - private readonly CacheStorageInterface $cacheStorage, - private readonly int $ttl + private TokenProviderInterface $tokenProvider, + private CacheStorageInterface $cacheStorage, + private int $ttl ) { } diff --git a/src/Cache/CacheStorage.php b/src/Cache/CacheStorage.php index 9ee1e52..4d50a76 100644 --- a/src/Cache/CacheStorage.php +++ b/src/Cache/CacheStorage.php @@ -7,7 +7,7 @@ use Psr\Cache\InvalidArgumentException; use Symfony\Component\Cache\Adapter\AdapterInterface; -class CacheStorage implements CacheStorageInterface +final readonly class CacheStorage implements CacheStorageInterface { public function __construct(private AdapterInterface $cacheAdapter) { diff --git a/src/DependencyInjection/DoctrineAwsIamRdsAuthExtension.php b/src/DependencyInjection/DoctrineAwsIamRdsAuthExtension.php index 7b00267..66c9837 100644 --- a/src/DependencyInjection/DoctrineAwsIamRdsAuthExtension.php +++ b/src/DependencyInjection/DoctrineAwsIamRdsAuthExtension.php @@ -9,7 +9,7 @@ use Symfony\Component\DependencyInjection\Extension\Extension; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; -class DoctrineAwsIamRdsAuthExtension extends Extension +final class DoctrineAwsIamRdsAuthExtension extends Extension { public function load(array $configs, ContainerBuilder $container) { diff --git a/src/Doctrine/Driver/IamDecorator.php b/src/Doctrine/Driver/IamDecorator.php index ffb4e19..c3ebfad 100644 --- a/src/Doctrine/Driver/IamDecorator.php +++ b/src/Doctrine/Driver/IamDecorator.php @@ -14,12 +14,12 @@ use Doctrine\DBAL\Schema\AbstractSchemaManager; use Macpaw\DoctrineAwsIamRdsAuthBundle\Aws\Token\TokenProviderInterface; -class IamDecorator implements Driver +readonly class IamDecorator implements Driver { public function __construct( - private readonly Driver $subject, - private readonly TokenProviderInterface $tokenProvider, - private readonly string $region, + private Driver $subject, + private TokenProviderInterface $tokenProvider, + private string $region, ) { } diff --git a/src/Doctrine/Driver/IamMiddleware.php b/src/Doctrine/Driver/IamMiddleware.php index d072206..852e5cf 100644 --- a/src/Doctrine/Driver/IamMiddleware.php +++ b/src/Doctrine/Driver/IamMiddleware.php @@ -8,12 +8,12 @@ use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\Middleware; -class IamMiddleware implements Middleware +readonly class IamMiddleware implements Middleware { public function __construct( - private readonly TokenProviderInterface $tokenProvider, - private readonly string $region, - private readonly bool $useIam, + private TokenProviderInterface $tokenProvider, + private string $region, + private bool $useIam, ) { } diff --git a/src/DoctrineAwsIamRdsAuthBundle.php b/src/DoctrineAwsIamRdsAuthBundle.php index 0a3bc0a..c6c0c28 100644 --- a/src/DoctrineAwsIamRdsAuthBundle.php +++ b/src/DoctrineAwsIamRdsAuthBundle.php @@ -6,6 +6,6 @@ use Symfony\Component\HttpKernel\Bundle\Bundle; -class DoctrineAwsIamRdsAuthBundle extends Bundle +final class DoctrineAwsIamRdsAuthBundle extends Bundle { } diff --git a/src/Factory/AuthTokenGeneratorFactory.php b/src/Factory/AuthTokenGeneratorFactory.php new file mode 100644 index 0000000..15b6af7 --- /dev/null +++ b/src/Factory/AuthTokenGeneratorFactory.php @@ -0,0 +1,18 @@ +addCompilerPass(new PublicCompilerPass()); + + return $builder; + } +} diff --git a/tests/App/config/bundles.php b/tests/App/config/bundles.php new file mode 100644 index 0000000..4f18211 --- /dev/null +++ b/tests/App/config/bundles.php @@ -0,0 +1,11 @@ + ['all' => true], + DoctrineAwsIamRdsAuthBundle::class => ['all' => true], +]; diff --git a/tests/App/config/package/cache.yaml b/tests/App/config/package/cache.yaml new file mode 100644 index 0000000..79d6416 --- /dev/null +++ b/tests/App/config/package/cache.yaml @@ -0,0 +1,4 @@ +framework: + cache: + app: cache.adapter.filesystem + system: cache.adapter.filesystem diff --git a/tests/App/config/package/doctrine_aws_iam_rds.yaml b/tests/App/config/package/doctrine_aws_iam_rds.yaml new file mode 100644 index 0000000..c67dd69 --- /dev/null +++ b/tests/App/config/package/doctrine_aws_iam_rds.yaml @@ -0,0 +1 @@ +doctrine_aws_iam_rds: diff --git a/tests/App/config/package/framework.yaml b/tests/App/config/package/framework.yaml new file mode 100644 index 0000000..6455b36 --- /dev/null +++ b/tests/App/config/package/framework.yaml @@ -0,0 +1 @@ +framework: diff --git a/tests/App/config/package/test/doctrine_aws_iam_rds.yaml b/tests/App/config/package/test/doctrine_aws_iam_rds.yaml new file mode 100644 index 0000000..e34f93a --- /dev/null +++ b/tests/App/config/package/test/doctrine_aws_iam_rds.yaml @@ -0,0 +1,7 @@ +doctrine_aws_iam_rds: + +services: + Macpaw\DoctrineAwsIamRdsAuthBundle\Aws\Token\RdsTokenProvider: + public: true + arguments: + [ '@rds.authTokenGenerator', '@?' ] diff --git a/tests/App/config/package/test/framework.yaml b/tests/App/config/package/test/framework.yaml new file mode 100644 index 0000000..2ee7eb4 --- /dev/null +++ b/tests/App/config/package/test/framework.yaml @@ -0,0 +1,2 @@ +framework: + test: true diff --git a/tests/App/config/services.yaml b/tests/App/config/services.yaml new file mode 100644 index 0000000..47e192a --- /dev/null +++ b/tests/App/config/services.yaml @@ -0,0 +1,16 @@ +services: + _defaults: + public: true + autowire: true + autoconfigure: true + + test.service_container: + alias: 'service_container' + + Macpaw\DoctrineAwsIamRdsAuthBundle\Cache\CacheStorageInterface: + class: Macpaw\DoctrineAwsIamRdsAuthBundle\Cache\CacheStorage + arguments: + $cacheAdapter: '@cache.app' + +parameters: + 'kernel.secret': '%env(APP_SECRET)%' diff --git a/tests/App/dependencyInjection/PublicCompilerPass.php b/tests/App/dependencyInjection/PublicCompilerPass.php new file mode 100644 index 0000000..4c78097 --- /dev/null +++ b/tests/App/dependencyInjection/PublicCompilerPass.php @@ -0,0 +1,28 @@ +getDefinitions(); + + foreach ($definitions as $name => $definition) { + $class = $definition->getClass(); + + if (!str_starts_with(strtolower($class ?? $name), 'macpaw\\')) { + continue; + } + + $definition->setPublic(true); + } + } +} diff --git a/tests/App/var/.gitignore b/tests/App/var/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/tests/App/var/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/Functional/AbstractFunctional.php b/tests/Functional/AbstractFunctional.php new file mode 100644 index 0000000..0a0f7eb --- /dev/null +++ b/tests/Functional/AbstractFunctional.php @@ -0,0 +1,17 @@ +get(RdsTokenProvider::class); + + self::assertInstanceOf( + RdsTokenProviderCacheDecorator::class, + $provider, + ); + + $reflection = new ReflectionClass($provider); + $prop = $reflection->getProperty('tokenProvider'); + $prop->setAccessible(true); + + $provider = $prop->getValue($provider); + self::assertInstanceOf(RdsTokenProvider::class, $provider); + + $reflection = new ReflectionClass($provider); + $prop = $reflection->getProperty('lifetime'); + $prop->setAccessible(true); + + self::assertEquals( + 10, + $prop->getValue($provider), + ); + + $reflection = new ReflectionClass($provider); + $prop = $reflection->getProperty('generator'); + $prop->setAccessible(true); + + self::assertInstanceOf( + AuthTokenGenerator::class, + $prop->getValue($provider), + ); + } + + public function testIamDecorator(): void + { + $tokenProvider = self::getContainer()->get(RdsTokenProviderCacheDecorator::class); + $iamDecorator = self::getContainer()->get(IamMiddleware::class); + + $reflection = new ReflectionClass($iamDecorator); + $prop = $reflection->getProperty('tokenProvider'); + $prop->setAccessible(true); + + self::assertEquals( + $tokenProvider, + $prop->getValue($iamDecorator), + ); + } + + protected function setUp(): void + { + parent::setUp(); + + $cacheDecorator = self::getContainer()->get(CacheStorageInterface::class); + $reflection = new ReflectionClass($cacheDecorator); + $prop = $reflection->getProperty('cacheAdapter'); + /** @var FilesystemAdapter $cacheAdapter */ + $cacheAdapter = $prop->getValue($cacheDecorator); + $cacheAdapter->clear(); + + putenv('APP_ENV=test'); + } +} diff --git a/tests/Unit/CacheRdsTokenProviderTest.php b/tests/Unit/CacheRdsTokenProviderTest.php new file mode 100644 index 0000000..eb585ff --- /dev/null +++ b/tests/Unit/CacheRdsTokenProviderTest.php @@ -0,0 +1,99 @@ +createMock(AuthTokenGenerator::class); + $mock->expects(self::once()) + ->method('createToken') + ->with( + self::ENDPOINT, + self::REGION, + self::USERNAME, + self::TTL, + ) + ->willReturn('token'); + + $provider = $this->getCacheProvider($mock); + + $token = $provider->getToken( + self::ENDPOINT, + self::REGION, + self::USERNAME, + ); + + self::assertEquals(self::TOKEN, $token); + + $token = $provider->getToken( + self::ENDPOINT, + self::REGION, + self::USERNAME, + ); + + self::assertEquals(self::TOKEN, $token); + } + + public function testInvalidTTL(): void + { + $mock = $this->createMock(AuthTokenGenerator::class); + $mock->expects(self::once()) + ->method('createToken') + ->with( + self::ENDPOINT, + self::REGION, + self::USERNAME, + -1, + ) + ->willThrowException(new InvalidArgumentException()); + + $provider = $this->getProvider($mock, -1); + + self::expectException(InvalidArgumentException::class); + $provider->getToken( + self::ENDPOINT, + self::REGION, + self::USERNAME, + ); + } + + private function getProvider( + AuthTokenGenerator $generator, + int $ttl = self::TTL, + ): RdsTokenProvider { + return new RdsTokenProvider($generator, $ttl); + } + + private function getCacheProvider( + AuthTokenGenerator $generator, + int $ttl = self::TTL, + ): RdsTokenProviderCacheDecorator + { + $adapter = new FilesystemAdapter(''); + $adapter->clear(); + + return new RdsTokenProviderCacheDecorator( + $this->getProvider($generator, $ttl), + new CacheStorage($adapter), + $ttl, + ); + } +} diff --git a/tests/Unit/IamDecoratorTest.php b/tests/Unit/IamDecoratorTest.php new file mode 100644 index 0000000..a6cf19e --- /dev/null +++ b/tests/Unit/IamDecoratorTest.php @@ -0,0 +1,281 @@ + self::ENDPOINT, + 'port' => 5432, + 'user' => self::USERNAME, + ]; + $driverMock = $this->createMock(Driver::class); + $connectionMock = $this->createMock(Connection::class); + + $driverMock->expects(self::once()) + ->method('connect') + ->with( + array_merge( + $params, + ['password' => self::TOKEN], + ), + )->willReturn($connectionMock); + + $cacheStorageMock = $this->createMock( + CacheStorageInterface::class, + ); + $cacheStorageMock->expects(self::once()) + ->method('get') + ->willReturn(null); + + $authMethodMock = $this->createMock(AuthTokenGenerator::class); + $authMethodMock->expects(self::once()) + ->method('createToken') + ->with( + sprintf("%s:%d", self::ENDPOINT, 5432), + self::REGION, + self::USERNAME, + self::TTL, + ) + ->willReturn(self::TOKEN); + + $tokenProvider = new RdsTokenProvider($authMethodMock, self::TTL); + + $tokenProvider = new RdsTokenProviderCacheDecorator( + $tokenProvider, + $cacheStorageMock, + self::TTL, + ); + + $decorator = new IamDecorator( + $driverMock, + $tokenProvider, + self::REGION, + ); + + $decorator->connect($params); + + return [ + 'driver' => $driverMock, + 'iam' => $decorator, + ]; + } + + public function testSuccessfullyReConnected(): void + { + $params = [ + 'host' => self::ENDPOINT, + 'port' => 5432, + 'user' => self::USERNAME, + ]; + $driverMock = $this->createMock(Driver::class); + $connectionMock = $this->createMock(Connection::class); + + $count = 0; + $driverMock->expects(self::exactly(2)) + ->method('connect') + ->with( + array_merge( + $params, + ['password' => self::TOKEN], + ), + )->willReturnCallback(function () use ($connectionMock) : Connection { + $this->count++; + + if (1 === $this->count) { + throw new DriverException( + new ConnectionException( + new Driver\Exception\UnknownParameterType('test'), + null, + ), + new Query('', [], []), + ); + } + + return $connectionMock; + }); + $exceptionConverter = $this->createMock(ExceptionConverter::class); + $exceptionConverter->expects(self::once()) + ->method('convert') + ->willReturn( new ConnectionException( + new Driver\Exception\UnknownParameterType('test'), + null, + )); + $driverMock->expects(self::once()) + ->method('getExceptionConverter') + ->willReturn($exceptionConverter); + + $cacheStorageMock = $this->createMock( + CacheStorageInterface::class, + ); + $cacheStorageMock->expects(self::exactly(2)) + ->method('get') + ->willReturn(null); + + $authMethodMock = $this->createMock( + AuthTokenGenerator::class + ); + $authMethodMock->expects(self::exactly(2)) + ->method('createToken') + ->with( + sprintf("%s:%d", self::ENDPOINT, 5432), + self::REGION, + self::USERNAME, + self::TTL, + ) + ->willReturn(self::TOKEN); + + $tokenProvider = new RdsTokenProvider($authMethodMock, self::TTL); + + $tokenProvider = new RdsTokenProviderCacheDecorator( + $tokenProvider, + $cacheStorageMock, + self::TTL, + ); + + $decorator = new IamDecorator( + $driverMock, + $tokenProvider, + self::REGION, + ); + + $decorator->connect($params); + } + + public function testErrorException(): void + { + $params = [ + 'host' => self::ENDPOINT, + 'port' => 5432, + 'user' => self::USERNAME, + ]; + $driverMock = $this->createMock(Driver::class); + + $driverMock->expects(self::once()) + ->method('connect') + ->with( + array_merge( + $params, + ['password' => self::TOKEN], + ), + )->willReturnCallback(function (): Connection { + throw new InvalidArgumentException(); + }); + + $cacheStorageMock = $this->createMock( + CacheStorageInterface::class, + ); + $cacheStorageMock->expects(self::once()) + ->method('get') + ->willReturn(null); + + $authMethodMock = $this->createMock( + AuthTokenGenerator::class + ); + $authMethodMock->expects(self::once()) + ->method('createToken') + ->with( + sprintf("%s:%d", self::ENDPOINT, 5432), + self::REGION, + self::USERNAME, + self::TTL, + ) + ->willReturn(self::TOKEN); + + $tokenProvider = new RdsTokenProvider($authMethodMock, self::TTL); + + $tokenProvider = new RdsTokenProviderCacheDecorator( + $tokenProvider, + $cacheStorageMock, + self::TTL, + ); + + $decorator = new IamDecorator( + $driverMock, + $tokenProvider, + self::REGION, + ); + + $this->expectException(InvalidArgumentException::class); + $decorator->connect($params); + } + + /** + * @param array{ + * driver: Driver&MockObject, + * iam: IamDecorator&MockObject, + * } $data + */ + #[Depends(methodName: 'testSuccessfullyConnected')] + public function testGetDatabasePlatform(array $data): void + { + /** @var Driver&MockObject $driver */ + $driver = $data['driver']; + /** @var IamDecorator&MockObject $iam */ + $iam = $data['iam']; + $platform = $this->createMock(AbstractPlatform::class); + $driver->expects(self::once()) + ->method('getDatabasePlatform') + ->willReturn($platform); + + self::assertEquals($platform, $iam->getDatabasePlatform()); + } + + /** + * @param array{ + * driver: Driver&MockObject, + * iam: IamDecorator&MockObject, + * } $data + */ + #[Depends(methodName: 'testSuccessfullyConnected')] + public function testGetExceptionConverter(array $data): void + { + /** @var Driver&MockObject $driver */ + $driver = $data['driver']; + /** @var IamDecorator&MockObject $iam */ + $iam = $data['iam']; + $exceptionConverter = $this->createMock(ExceptionConverter::class); + $driver->expects(self::once()) + ->method('getExceptionConverter') + ->willReturn($exceptionConverter); + + self::assertEquals($exceptionConverter, $iam->getExceptionConverter()); + } +} diff --git a/tests/Unit/RdsTokenProviderTest.php b/tests/Unit/RdsTokenProviderTest.php new file mode 100644 index 0000000..cca64df --- /dev/null +++ b/tests/Unit/RdsTokenProviderTest.php @@ -0,0 +1,73 @@ +createMock(AuthTokenGenerator::class); + $mock->expects(self::once()) + ->method('createToken') + ->with( + self::ENDPOINT, + self::REGION, + self::USERNAME, + self::TTL, + ) + ->willReturn(self::TOKEN); + + $provider = $this->getProvider($mock); + + $token = $provider->getToken( + self::ENDPOINT, + self::REGION, + self::USERNAME, + ); + + self::assertEquals(self::TOKEN, $token); + } + + public function testInvalidTTL(): void + { + $mock = $this->createMock(AuthTokenGenerator::class); + $mock->expects(self::once()) + ->method('createToken') + ->with( + self::ENDPOINT, + self::REGION, + self::USERNAME, + -1, + ) + ->willThrowException(new InvalidArgumentException()); + + $provider = $this->getProvider($mock, -1); + + self::expectException(InvalidArgumentException::class); + $provider->getToken( + self::ENDPOINT, + self::REGION, + self::USERNAME, + ); + } + + private function getProvider( + AuthTokenGenerator $generator, + int $ttl = self::TTL, + ): RdsTokenProvider { + return new RdsTokenProvider($generator, $ttl); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..2409459 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,7 @@ +