diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 73785e7..fd614b6 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -6,22 +6,28 @@ on: - '**/*.php' - '.github/workflows/php.yml' - 'composer.json' + - 'phpcs.xml.dist' + - 'phpstan.neon.dist' + - 'phpunit.xml.dist' push: paths: - '**/*.php' - '.github/workflows/php.yml' - 'composer.json' + - 'phpcs.xml.dist' + - 'phpstan.neon.dist' + - 'phpunit.xml.dist' jobs: cs: uses: bedita/github-workflows/.github/workflows/php-cs.yml@v2 with: - php_versions: '["8.3"]' + php_versions: '["8.4"]' stan: uses: bedita/github-workflows/.github/workflows/php-stan.yml@v2 with: - php_versions: '["8.3"]' + php_versions: '["8.4"]' unit: name: 'Run unit tests' @@ -33,6 +39,7 @@ jobs: matrix: php: - '8.3' + - '8.4' env: PHP_VERSION: '${{ matrix.php }}' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7a95e94..70bb2ad 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,5 +17,5 @@ jobs: uses: bedita/github-workflows/.github/workflows/release.yml@v2 with: main_branch: 'master' - dist_branches: '["master"]' + dist_branches: '["4.x", "master"]' version_bump: ${{ inputs.releaseType }} diff --git a/.gitignore b/.gitignore index bf832c4..574a1a5 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ /plugins /tmp .phpunit.result.cache +.phpunit.cache phinx.yml diff --git a/composer.json b/composer.json index 47fe43d..fc14e02 100644 --- a/composer.json +++ b/composer.json @@ -6,23 +6,24 @@ "php": ">= 8.3", "ext-openssl": "*", "aws/aws-sdk-php": "^3.222", - "bedita/core": "^5.36", - "cakephp/cakephp": "^4.5", + "bedita/core": "^6.0", + "cakephp/cakephp": "^5", "lcobucci/jwt": "^4.2.1", - "league/flysystem": "^2.4.3", - "league/flysystem-aws-s3-v3": "^2.4.3", + "league/flysystem": "^3.30.2", + "league/flysystem-aws-s3-v3": "^3.30.1", "guzzlehttp/guzzle": "^7.4" }, "require-dev": { + "cakephp/authentication": "^3.3", "cakephp/cakephp-codesniffer": "~4.7.0", - "phpunit/phpunit": "^9.6", - "phpstan/phpstan": "~1.10", - "cakephp/authentication": "^2.9", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-phpunit": "^1.1" + "phpstan/phpstan": "^1.10", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^11.5" }, "suggest": { - "cakephp/authentication": "^2.9" + "cakephp/authentication": "^3.3" }, "autoload": { "psr-4": { @@ -35,10 +36,11 @@ } }, "scripts": { - "cs-check": "vendor/bin/phpcs", - "cs-fix": "vendor/bin/phpcbf", + "cs-check": "vendor/bin/phpcs --colors -p --standard=vendor/cakephp/cakephp-codesniffer/CakePHP ./src ./tests", + "cs-fix": "vendor/bin/phpcbf --colors --standard=vendor/cakephp/cakephp-codesniffer/CakePHP ./src ./tests", "stan": "vendor/bin/phpstan analyse", - "test": "vendor/bin/phpunit --colors=always" + "test": "vendor/bin/phpunit --colors=always", + "coverage": "vendor/bin/phpunit --colors=always --coverage-html coverage" }, "config": { "allow-plugins": { diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 181ca55..8169527 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -4,6 +4,8 @@ parameters: paths: - src - tests - phpVersion: 70400 level: 8 - checkMissingIterableValueType: false + ignoreErrors: + - '#type has no value type specified in iterable type array#' + - '#has parameter .* with no value type specified in iterable type array#' + - '#has parameter .* with generic interface .* but does not specify its types#' diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 96ac958..633780b 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -10,24 +10,28 @@ ~ ~ See LICENSE.LGPL or for more details. --> - - - - src/ - - - - - - - - - - tests/TestCase/ - - - - - - + + + + ./src/ + + + + + + + + + + ./tests/TestCase/ + + diff --git a/src/Authenticator/AlbAuthenticator.php b/src/Authenticator/AlbAuthenticator.php index 0358609..e6c5451 100644 --- a/src/Authenticator/AlbAuthenticator.php +++ b/src/Authenticator/AlbAuthenticator.php @@ -19,9 +19,9 @@ use Authentication\Authenticator\Result; use Authentication\Authenticator\ResultInterface; use Authentication\Authenticator\TokenAuthenticator; -use Authentication\Identifier\IdentifierInterface; +use Authentication\Identifier\JwtSubjectIdentifier; use Cake\Cache\Cache; -use Cake\I18n\FrozenTime; +use Cake\I18n\DateTime; use Exception; use GuzzleHttp\Client; use GuzzleHttp\RequestOptions; @@ -29,6 +29,7 @@ use Lcobucci\Clock\FrozenClock; use Lcobucci\JWT\Decoder; use Lcobucci\JWT\Encoding\CannotDecodeContent; +use Lcobucci\JWT\Signer\Ecdsa\MultibyteStringConverter; use Lcobucci\JWT\Signer\Ecdsa\Sha256; use Lcobucci\JWT\Signer\Key; use Lcobucci\JWT\SodiumBase64Polyfill; @@ -60,11 +61,11 @@ class AlbAuthenticator extends TokenAuthenticator * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'header' => 'x-amzn-oidc-data', 'returnPayload' => true, 'fields' => [ - IdentifierInterface::CREDENTIAL_JWT_SUBJECT => IdentifierInterface::CREDENTIAL_JWT_SUBJECT, + JwtSubjectIdentifier::CREDENTIAL_JWT_SUBJECT => JwtSubjectIdentifier::CREDENTIAL_JWT_SUBJECT, ], 'publicKeyEndpoint' => null, 'region' => null, @@ -114,7 +115,7 @@ public function authenticate(ServerRequestInterface $request): ResultInterface return new Result(null, ResultInterface::FAILURE_CREDENTIALS_INVALID); } - if (empty($result[IdentifierInterface::CREDENTIAL_JWT_SUBJECT])) { + if (empty($result[JwtSubjectIdentifier::CREDENTIAL_JWT_SUBJECT])) { return new Result(null, ResultInterface::FAILURE_CREDENTIALS_MISSING); } @@ -189,7 +190,7 @@ function () use ($keyId): string { protected function decodeToken(string $token): ?array { $jwt = (new TokenParser(new class implements Decoder { - /** @inheritdoc */ + /** @inheritDoc */ public function jsonDecode(string $json) { try { @@ -199,7 +200,7 @@ public function jsonDecode(string $json) } } - /** @inheritdoc */ + /** @inheritDoc */ public function base64UrlDecode(string $data): string { return SodiumBase64Polyfill::base642bin( @@ -212,11 +213,11 @@ public function base64UrlDecode(string $data): string if (empty($kid) || !is_string($kid) || !$jwt instanceof UnencryptedToken) { return null; } - + $ecdsa = new Sha256(new MultibyteStringConverter()); (new Validator())->assert( $jwt, - new SignedWith(Sha256::create(), $this->getKey($kid)), - new LooseValidAt(new FrozenClock(FrozenTime::now())), + new SignedWith($ecdsa, $this->getKey($kid)), + new LooseValidAt(new FrozenClock(DateTime::now())), ); return $jwt->claims()->all(); diff --git a/src/Filesystem/Adapter/AwsS3CloudFrontAdapter.php b/src/Filesystem/Adapter/AwsS3CloudFrontAdapter.php index 6c5fe68..2941f20 100644 --- a/src/Filesystem/Adapter/AwsS3CloudFrontAdapter.php +++ b/src/Filesystem/Adapter/AwsS3CloudFrontAdapter.php @@ -19,6 +19,7 @@ use Aws\CloudFront\Exception\CloudFrontException; use Aws\S3\S3ClientInterface; use DomainException; +use Exception; use League\Flysystem\AwsS3V3\AwsS3V3Adapter; use League\Flysystem\Config; @@ -58,8 +59,14 @@ class AwsS3CloudFrontAdapter extends AwsS3V3Adapter * @param bool $streamReads Whether reads should be streamed. * @param \Aws\CloudFront\CloudFrontClient|null $cloudfrontClient CloudFront client instance, or `null`. */ - public function __construct(S3ClientInterface $client, string $bucket, string $prefix = '', array $options = [], $streamReads = true, ?CloudFrontClient $cloudfrontClient = null) - { + public function __construct( + S3ClientInterface $client, + string $bucket, + string $prefix = '', + array $options = [], + $streamReads = true, + ?CloudFrontClient $cloudfrontClient = null + ) { parent::__construct($client, $bucket, $prefix, null, null, $options, $streamReads); if (!empty($options['distributionId']) && $cloudfrontClient === null) { @@ -100,12 +107,30 @@ public function hasCloudFrontConfig(): bool return $this->cloudfrontClient !== null; } + /** + * Check whether a file exists. + * + * @param string $path The path to check. + * @return bool + */ + protected function exists(string $path): bool + { + $result = false; + try { + $result = $this->hasCloudFrontConfig() && $this->fileExists($path); + } catch (Exception $e) { + // Ignore exceptions + } + + return $result; + } + /** * @inheritDoc */ public function copy(string $source, string $destination, Config $config): void { - $existed = $this->hasCloudFrontConfig() && $this->fileExists($destination); + $existed = $this->exists($destination); parent::copy($source, $destination, $config); if ($existed) { $this->createCloudFrontInvalidation($destination); @@ -117,7 +142,7 @@ public function copy(string $source, string $destination, Config $config): void */ public function delete(string $path): void { - $existed = $this->hasCloudFrontConfig() && $this->fileExists($path); + $existed = $this->exists($path); parent::delete($path); if ($existed) { $this->createCloudFrontInvalidation($path); @@ -138,7 +163,7 @@ public function deleteDirectory(string $path): void */ public function write(string $path, string $contents, Config $config): void { - $existed = $this->hasCloudFrontConfig() && $this->fileExists($path); + $existed = $this->exists($path); parent::write($path, $contents, $config); if ($existed) { $this->createCloudFrontInvalidation($path); diff --git a/src/Filesystem/Adapter/S3Adapter.php b/src/Filesystem/Adapter/S3Adapter.php index 359683d..b018394 100644 --- a/src/Filesystem/Adapter/S3Adapter.php +++ b/src/Filesystem/Adapter/S3Adapter.php @@ -34,7 +34,7 @@ class S3Adapter extends FilesystemAdapter /** * @inheritDoc */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'region' => null, 'bucket' => null, 'version' => 'latest', diff --git a/src/Mailer/Transport/SesTransport.php b/src/Mailer/Transport/SesTransport.php index ce372a1..709e7f8 100644 --- a/src/Mailer/Transport/SesTransport.php +++ b/src/Mailer/Transport/SesTransport.php @@ -39,7 +39,7 @@ class SesTransport extends AbstractTransport /** * @inheritDoc */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'region' => null, 'version' => 'latest', ]; diff --git a/src/Mailer/Transport/SnsTransport.php b/src/Mailer/Transport/SnsTransport.php index 337cc83..e410ae1 100644 --- a/src/Mailer/Transport/SnsTransport.php +++ b/src/Mailer/Transport/SnsTransport.php @@ -32,7 +32,7 @@ class SnsTransport extends AbstractTransport /** * @inheritDoc */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'region' => null, 'version' => 'latest', 'smsType' => null, diff --git a/tests/TestCase/Authenticator/AlbAuthenticatorTest.php b/tests/TestCase/Authenticator/AlbAuthenticatorTest.php index 3154e58..2eb22c1 100644 --- a/tests/TestCase/Authenticator/AlbAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/AlbAuthenticatorTest.php @@ -19,10 +19,10 @@ use ArrayObject; use Authentication\Authenticator\ResultInterface; use Authentication\Identifier\CallbackIdentifier; -use Authentication\Identifier\IdentifierInterface; +use Authentication\Identifier\JwtSubjectIdentifier; use BEdita\AWS\Authenticator\AlbAuthenticator; use Cake\Http\ServerRequest; -use Cake\I18n\FrozenTime; +use Cake\I18n\DateTime; use Cake\Utility\Text; use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Handler\MockHandler; @@ -32,18 +32,19 @@ use GuzzleHttp\Psr7\Response; use Lcobucci\JWT\Encoding\ChainedFormatter; use Lcobucci\JWT\Encoding\JoseEncoder; +use Lcobucci\JWT\Signer; +use Lcobucci\JWT\Signer\Ecdsa\MultibyteStringConverter; use Lcobucci\JWT\Signer\Ecdsa\Sha256; use Lcobucci\JWT\Signer\Key; use Lcobucci\JWT\Signer\Key\InMemory; -use Lcobucci\JWT\Signer\None; use Lcobucci\JWT\Token\Builder; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** * Test {@see \BEdita\AWS\Authenticator\AlbAuthenticator}. - * - * @covers \BEdita\AWS\Authenticator\AlbAuthenticator */ +#[CoversClass(AlbAuthenticator::class)] class AlbAuthenticatorTest extends TestCase { /** @@ -147,13 +148,14 @@ public function testAuthenticate(): void ['region' => 'eu-south-1', 'guzzleClient' => ['handler' => $this->handler]] ); + $ecdsa = new Sha256(new MultibyteStringConverter()); $token = (new Builder(new JoseEncoder(), ChainedFormatter::default())) - ->issuedAt(FrozenTime::now()) - ->canOnlyBeUsedAfter(FrozenTime::now()) - ->expiresAt(FrozenTime::now()->addMinute()) + ->issuedAt(DateTime::now()) + ->canOnlyBeUsedAfter(DateTime::now()) + ->expiresAt(DateTime::now()->addMinutes(1)) ->withHeader('kid', $this->keyId) ->relatedTo('gustavo@example.com') - ->getToken(Sha256::create(), $this->privateKey) + ->getToken($ecdsa, $this->privateKey) ->toString(); $result = $authenticator->authenticate( @@ -256,7 +258,8 @@ public function testAuthenticateMalformedJsonInToken(): void $encoder->base64UrlEncode($encoder->jsonEncode(['typ' => 'JWT', 'alg' => 'ES256', 'kid' => $this->keyId])), $encoder->base64UrlEncode('NOT A JSON'), ); - $token .= sprintf('.%s', $encoder->base64UrlEncode(Sha256::create()->sign($token, $this->privateKey))); + $ecdsa = new Sha256(new MultibyteStringConverter()); + $token .= sprintf('.%s', $encoder->base64UrlEncode($ecdsa->sign($token, $this->privateKey))); $result = $authenticator->authenticate( new ServerRequest(['environment' => ['HTTP_X_AMZN_OIDC_DATA' => $token]]) @@ -287,12 +290,13 @@ public function testAuthenticateMissingKid(): void ['region' => 'eu-south-1', 'guzzleClient' => ['handler' => $this->handler]] ); + $ecdsa = new Sha256(new MultibyteStringConverter()); $token = (new Builder(new JoseEncoder(), ChainedFormatter::default())) - ->issuedAt(FrozenTime::now()->subDay()) - ->canOnlyBeUsedAfter(FrozenTime::now()->subDay()) - ->expiresAt(FrozenTime::now()->subDay()->addMinute()) + ->issuedAt(DateTime::now()->subDays(1)) + ->canOnlyBeUsedAfter(DateTime::now()->subDays(1)) + ->expiresAt(DateTime::now()->subDays(1)->addMinutes(1)) ->relatedTo('gustavo@example.com') - ->getToken(Sha256::create(), $this->privateKey) + ->getToken($ecdsa, $this->privateKey) ->toString(); $result = $authenticator->authenticate( @@ -328,13 +332,14 @@ public function testAuthenticateKeyNotFound(): void ['region' => 'eu-south-1', 'guzzleClient' => ['handler' => $handler]] ); + $ecdsa = new Sha256(new MultibyteStringConverter()); $token = (new Builder(new JoseEncoder(), ChainedFormatter::default())) - ->issuedAt(FrozenTime::now()->subDay()) - ->canOnlyBeUsedAfter(FrozenTime::now()->subDay()) - ->expiresAt(FrozenTime::now()->subDay()->addMinute()) + ->issuedAt(DateTime::now()->subDays(1)) + ->canOnlyBeUsedAfter(DateTime::now()->subDays(1)) + ->expiresAt(DateTime::now()->subDays(1)->addMinutes(1)) ->withHeader('kid', $this->keyId) ->relatedTo('gustavo@example.com') - ->getToken(Sha256::create(), $this->privateKey) + ->getToken($ecdsa, $this->privateKey) ->toString(); $result = $authenticator->authenticate( @@ -371,13 +376,14 @@ public function testAuthenticateExpiredToken(): void ['region' => 'eu-south-1', 'guzzleClient' => ['handler' => $this->handler]] ); + $ecdsa = new Sha256(new MultibyteStringConverter()); $token = (new Builder(new JoseEncoder(), ChainedFormatter::default())) - ->issuedAt(FrozenTime::now()->subDay()) - ->canOnlyBeUsedAfter(FrozenTime::now()->subDay()) - ->expiresAt(FrozenTime::now()->subDay()->addMinute()) + ->issuedAt(DateTime::now()->subDays(1)) + ->canOnlyBeUsedAfter(DateTime::now()->subDays(1)) + ->expiresAt(DateTime::now()->subDays(1)->addMinutes(1)) ->withHeader('kid', $this->keyId) ->relatedTo('gustavo@example.com') - ->getToken(Sha256::create(), $this->privateKey) + ->getToken($ecdsa, $this->privateKey) ->toString(); $result = $authenticator->authenticate( @@ -417,13 +423,14 @@ public function testAuthenticateInvalidSignature(): void assert($privateKey !== ''); $privateKey = InMemory::plainText($privateKey); + $ecdsa = new Sha256(new MultibyteStringConverter()); $token = (new Builder(new JoseEncoder(), ChainedFormatter::default())) - ->issuedAt(FrozenTime::now()) - ->canOnlyBeUsedAfter(FrozenTime::now()) - ->expiresAt(FrozenTime::now()->addMinute()) + ->issuedAt(DateTime::now()) + ->canOnlyBeUsedAfter(DateTime::now()) + ->expiresAt(DateTime::now()->addMinutes(1)) ->withHeader('kid', $this->keyId) ->relatedTo('gustavo@example.com') - ->getToken(Sha256::create(), $privateKey) + ->getToken($ecdsa, $privateKey) ->toString(); $result = $authenticator->authenticate( @@ -458,13 +465,30 @@ public function testAuthenticateInvalidAlgorithm(): void ['region' => 'eu-south-1', 'guzzleClient' => ['handler' => $this->handler]] ); + $none = new class () implements Signer + { + public function algorithmId(): string + { + return 'none'; + } + + public function sign(string $payload, Key $key): string + { + return ''; + } + + public function verify(string $expected, string $payload, Key $key): bool + { + return $expected === ''; + } + }; $token = (new Builder(new JoseEncoder(), ChainedFormatter::default())) - ->issuedAt(FrozenTime::now()) - ->canOnlyBeUsedAfter(FrozenTime::now()) - ->expiresAt(FrozenTime::now()->addMinute()) + ->issuedAt(DateTime::now()) + ->canOnlyBeUsedAfter(DateTime::now()) + ->expiresAt(DateTime::now()->addMinutes(1)) ->withHeader('kid', $this->keyId) ->relatedTo('gustavo@example.com') - ->getToken(new None(), InMemory::plainText('key')) + ->getToken($none, InMemory::plainText('key')) ->toString(); $result = $authenticator->authenticate( @@ -499,12 +523,13 @@ public function testAuthenticateMissingSub(): void ['region' => 'eu-south-1', 'guzzleClient' => ['handler' => $this->handler]] ); + $ecdsa = new Sha256(new MultibyteStringConverter()); $token = (new Builder(new JoseEncoder(), ChainedFormatter::default())) - ->issuedAt(FrozenTime::now()) - ->canOnlyBeUsedAfter(FrozenTime::now()) - ->expiresAt(FrozenTime::now()->addMinute()) + ->issuedAt(DateTime::now()) + ->canOnlyBeUsedAfter(DateTime::now()) + ->expiresAt(DateTime::now()->addMinutes(1)) ->withHeader('kid', $this->keyId) - ->getToken(Sha256::create(), $this->privateKey) + ->getToken($ecdsa, $this->privateKey) ->toString(); $result = $authenticator->authenticate( @@ -540,20 +565,21 @@ public function testAuthenticateIdentify(): void [ 'returnPayload' => false, 'fields' => [ - 'email' => IdentifierInterface::CREDENTIAL_JWT_SUBJECT, + 'email' => JwtSubjectIdentifier::CREDENTIAL_JWT_SUBJECT, ], 'region' => 'eu-south-1', 'guzzleClient' => ['handler' => $this->handler], ] ); + $ecdsa = new Sha256(new MultibyteStringConverter()); $token = (new Builder(new JoseEncoder(), ChainedFormatter::default())) - ->issuedAt(FrozenTime::now()) - ->canOnlyBeUsedAfter(FrozenTime::now()) - ->expiresAt(FrozenTime::now()->addMinute()) + ->issuedAt(DateTime::now()) + ->canOnlyBeUsedAfter(DateTime::now()) + ->expiresAt(DateTime::now()->addMinutes(1)) ->withHeader('kid', $this->keyId) ->relatedTo('gustavo@example.com') - ->getToken(Sha256::create(), $this->privateKey) + ->getToken($ecdsa, $this->privateKey) ->toString(); $result = $authenticator->authenticate( @@ -596,20 +622,21 @@ public function testAuthenticateIdentifyFailure(): void [ 'returnPayload' => false, 'fields' => [ - 'email' => IdentifierInterface::CREDENTIAL_JWT_SUBJECT, + 'email' => JwtSubjectIdentifier::CREDENTIAL_JWT_SUBJECT, ], 'region' => 'eu-south-1', 'guzzleClient' => ['handler' => $this->handler], ] ); + $ecdsa = new Sha256(new MultibyteStringConverter()); $token = (new Builder(new JoseEncoder(), ChainedFormatter::default())) - ->issuedAt(FrozenTime::now()) - ->canOnlyBeUsedAfter(FrozenTime::now()) - ->expiresAt(FrozenTime::now()->addMinute()) + ->issuedAt(DateTime::now()) + ->canOnlyBeUsedAfter(DateTime::now()) + ->expiresAt(DateTime::now()->addMinutes(1)) ->withHeader('kid', $this->keyId) ->relatedTo('gustavo@example.com') - ->getToken(Sha256::create(), $this->privateKey) + ->getToken($ecdsa, $this->privateKey) ->toString(); $result = $authenticator->authenticate( diff --git a/tests/TestCase/AwsConfigTraitTest.php b/tests/TestCase/AwsConfigTraitTest.php index 4f51f49..04fb5fa 100644 --- a/tests/TestCase/AwsConfigTraitTest.php +++ b/tests/TestCase/AwsConfigTraitTest.php @@ -16,13 +16,16 @@ namespace BEdita\AWS\Test\TestCase; use BEdita\AWS\AwsConfigTrait; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; /** * Test {@see \BEdita\AWS\AwsConfigTrait}. - * - * @coversDefaultClass \BEdita\AWS\AwsConfigTrait */ +#[CoversClass(AwsConfigTrait::class)] +#[CoversMethod(AwsConfigTrait::class, 'reformatCredentials')] class AwsConfigTraitTest extends TestCase { /** @@ -30,7 +33,7 @@ class AwsConfigTraitTest extends TestCase * * @return array[] */ - public function reformatCredentialsProvider(): array + public static function reformatCredentialsProvider(): array { return [ 'no username, no password' => [ @@ -116,9 +119,8 @@ public function reformatCredentialsProvider(): array * @param array $expected Expected result. * @param array $config Input configuration. * @return void - * @dataProvider reformatCredentialsProvider() - * @covers ::reformatCredentials() */ + #[DataProvider('reformatCredentialsProvider')] public function testReformatCredentials(array $expected, array $config): void { $subject = new class { diff --git a/tests/TestCase/Filesystem/Adapter/AwsS3CloudFrontAdapterTest.php b/tests/TestCase/Filesystem/Adapter/AwsS3CloudFrontAdapterTest.php index 46e24c8..4a83f75 100644 --- a/tests/TestCase/Filesystem/Adapter/AwsS3CloudFrontAdapterTest.php +++ b/tests/TestCase/Filesystem/Adapter/AwsS3CloudFrontAdapterTest.php @@ -24,13 +24,25 @@ use DomainException; use InvalidArgumentException; use League\Flysystem\Config; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\TestCase; /** * Test {@see \BEdita\AWS\Filesystem\Adapter\AwsS3CloudFrontAdapter}. - * - * @coversDefaultClass \BEdita\AWS\Filesystem\Adapter\AwsS3CloudFrontAdapter */ +#[CoversClass(AwsS3CloudFrontAdapter::class)] +#[CoversMethod(AwsS3CloudFrontAdapter::class, '__construct')] +#[CoversMethod(AwsS3CloudFrontAdapter::class, 'applyCloudFrontPathPrefix')] +#[CoversMethod(AwsS3CloudFrontAdapter::class, 'copy')] +#[CoversMethod(AwsS3CloudFrontAdapter::class, 'createCloudFrontInvalidation')] +#[CoversMethod(AwsS3CloudFrontAdapter::class, 'delete')] +#[CoversMethod(AwsS3CloudFrontAdapter::class, 'deleteDirectory')] +#[CoversMethod(AwsS3CloudFrontAdapter::class, 'getCloudFrontClient')] +#[CoversMethod(AwsS3CloudFrontAdapter::class, 'getDistributionId')] +#[CoversMethod(AwsS3CloudFrontAdapter::class, 'hasCloudFrontConfig')] +#[CoversMethod(AwsS3CloudFrontAdapter::class, 'write')] + class AwsS3CloudFrontAdapterTest extends TestCase { /** @@ -77,10 +89,6 @@ protected static function cloudFrontClientFactory(?callable $handler = null): Cl * methods. * * @return void - * @covers ::__construct() - * @covers ::getCloudFrontClient() - * @covers ::hasCloudFrontConfig() - * @covers ::getDistributionId() */ public function testConstruct(): void { @@ -96,7 +104,6 @@ public function testConstruct(): void * Test {@see AwsS3CloudFrontAdapter} constructor when CloudFront client is missing. * * @return void - * @covers ::__construct() */ public function testConstructMissingCloudFrontClient(): void { @@ -112,10 +119,6 @@ public function testConstructMissingCloudFrontClient(): void * methods with a distribution ID. * * @return void - * @covers ::__construct() - * @covers ::getCloudFrontClient() - * @covers ::hasCloudFrontConfig() - * @covers ::getDistributionId() */ public function testConstructWithDistribution(): void { @@ -133,7 +136,6 @@ public function testConstructWithDistribution(): void * Test {@see AwsS3CloudFrontAdapter::copy()} method. * * @return void - * @covers ::copy() */ public function testCopy(): void { @@ -173,7 +175,6 @@ public function testCopy(): void * Test {@see AwsS3CloudFrontAdapter::copy()} method with CloudFront config set to a new destination. * * @return void - * @covers ::copy() */ public function testCopyCloudFrontNotExistingObject(): void { @@ -224,9 +225,6 @@ public function testCopyCloudFrontNotExistingObject(): void * Test {@see AwsS3CloudFrontAdapter::copy()} method with CloudFront config set to an existing destination. * * @return void - * @covers ::copy() - * @covers ::applyCloudFrontPathPrefix() - * @covers ::createCloudFrontInvalidation() */ public function testCopyCloudFrontExistingObject(): void { @@ -285,7 +283,6 @@ public function testCopyCloudFrontExistingObject(): void * Test {@see AwsS3CloudFrontAdapter::delete()} method. * * @return void - * @covers ::delete() */ public function testDelete(): void { @@ -313,7 +310,6 @@ public function testDelete(): void * Test {@see AwsS3CloudFrontAdapter::delete()} method with CloudFront config set to a new destination. * * @return void - * @covers ::delete() */ public function testDeleteCloudFrontNotExistingObject(): void { @@ -353,9 +349,6 @@ public function testDeleteCloudFrontNotExistingObject(): void * Test {@see AwsS3CloudFrontAdapter::delete()} method with CloudFront config set to an existing destination. * * @return void - * @covers ::delete() - * @covers ::applyCloudFrontPathPrefix() - * @covers ::createCloudFrontInvalidation() */ public function testDeleteCloudFrontExistingObject(): void { @@ -412,7 +405,6 @@ public function testDeleteCloudFrontExistingObject(): void * Test {@see AwsS3CloudFrontAdapter::deleteDirectory()} method. * * @return void - * @covers ::deleteDirectory() */ public function testDeleteDir(): void { @@ -446,9 +438,6 @@ public function testDeleteDir(): void * Test {@see AwsS3CloudFrontAdapter::deleteDirectory()} method with CloudFront config set. * * @return void - * @covers ::deleteDirectory() - * @covers ::applyCloudFrontPathPrefix() - * @covers ::createCloudFrontInvalidation() */ public function testDeleteDirCloudFront(): void { @@ -497,7 +486,6 @@ public function testDeleteDirCloudFront(): void * Test {@see AwsS3CloudFrontAdapter::write()} method. * * @return void - * @covers ::write() */ public function testWrite(): void { @@ -526,7 +514,6 @@ public function testWrite(): void * Test {@see AwsS3CloudFrontAdapter::write()} method with CloudFront config set to a new destination. * * @return void - * @covers ::write() */ public function testWriteCloudFrontNotExistingObject(): void { @@ -567,9 +554,6 @@ public function testWriteCloudFrontNotExistingObject(): void * Test {@see AwsS3CloudFrontAdapter::write()} method with CloudFront config set to an existing destination. * * @return void - * @covers ::write() - * @covers ::applyCloudFrontPathPrefix() - * @covers ::createCloudFrontInvalidation() */ public function testWriteCloudFrontExistingObject(): void { diff --git a/tests/TestCase/Filesystem/Adapter/S3AdapterTest.php b/tests/TestCase/Filesystem/Adapter/S3AdapterTest.php index 2f338b3..4462e2e 100644 --- a/tests/TestCase/Filesystem/Adapter/S3AdapterTest.php +++ b/tests/TestCase/Filesystem/Adapter/S3AdapterTest.php @@ -21,13 +21,21 @@ use BEdita\AWS\Filesystem\Adapter\S3Adapter; use Exception; use InvalidArgumentException; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; /** * Test {@see \BEdita\AWS\Filesystem\Adapter\S3Adapter} - * - * @coversDefaultClass \BEdita\AWS\Filesystem\Adapter\S3Adapter */ +#[CoversClass(S3Adapter::class)] +#[CoversMethod(S3Adapter::class, 'buildAdapter')] +#[CoversMethod(S3Adapter::class, 'getClient')] +#[CoversMethod(S3Adapter::class, 'getCloudFrontClient')] +#[CoversMethod(S3Adapter::class, 'getPublicUrl')] +#[CoversMethod(S3Adapter::class, 'initialize')] +#[CoversMethod(S3Adapter::class, 'reformatConfig')] class S3AdapterTest extends TestCase { /** @@ -35,7 +43,7 @@ class S3AdapterTest extends TestCase * * @return array */ - public function initializeProvider(): array + public static function initializeProvider(): array { return [ 'empty bucket' => [ @@ -174,10 +182,8 @@ public function initializeProvider(): array * @param array|\Exception $expected Expected outcome. * @param array $config Adapter configuration. * @return void - * @dataProvider initializeProvider() - * @covers ::initialize() - * @covers ::reformatConfig() */ + #[DataProvider('initializeProvider')] public function testInitialize($expected, array $config): void { if ($expected instanceof Exception) { @@ -196,8 +202,6 @@ public function testInitialize($expected, array $config): void * Test {@see S3Adapter::getClient()} and {@see S3Adapter::getCloudFrontClient()} methods. * * @return void - * @covers ::getClient() - * @covers ::getCloudFrontClient() */ public function testGetClient(): void { @@ -248,7 +252,6 @@ public function getCloudFrontClient(): CloudFrontClient * Test {@see S3Adapter::buildAdapter()} method. * * @return void - * @covers ::buildAdapter() */ public function testBuildAdapter(): void { @@ -276,7 +279,6 @@ public function testBuildAdapter(): void * Test {@see S3Adapter::buildAdapter()} method with a CloudFront distribution. * * @return void - * @covers ::buildAdapter() */ public function testBuildAdapterCloudFront(): void { @@ -304,7 +306,6 @@ public function testBuildAdapterCloudFront(): void * Test {@see S3Adapter::getPublicUrl()} method. * * @return void - * @covers ::getPublicUrl() */ public function testGetPublicUrl(): void { @@ -331,7 +332,6 @@ public function testGetPublicUrl(): void * Test {@see S3Adapter::getPublicUrl()} method falling back to default AWS S3 URL.. * * @return void - * @covers ::getPublicUrl() */ public function testGetPublicUrlDefaultS3Url(): void { diff --git a/tests/TestCase/Mailer/Transport/SesTransportTest.php b/tests/TestCase/Mailer/Transport/SesTransportTest.php index b6587b4..56fb86a 100644 --- a/tests/TestCase/Mailer/Transport/SesTransportTest.php +++ b/tests/TestCase/Mailer/Transport/SesTransportTest.php @@ -19,24 +19,27 @@ use Aws\Result; use Aws\Ses\SesClient; use BEdita\AWS\Mailer\Transport\SesTransport; -use Cake\I18n\FrozenTime; +use Cake\I18n\DateTime; use Cake\Mailer\Message; use Cake\Utility\Text; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; /** * Test {@see \BEdita\AWS\Mailer\Transport\SesTransport}. - * - * @coversDefaultClass \BEdita\AWS\Mailer\Transport\SesTransport */ +#[CoversClass(SesTransport::class)] +#[CoversMethod(SesTransport::class, '__construct')] +#[CoversMethod(SesTransport::class, 'getClient')] +#[CoversMethod(SesTransport::class, 'send')] class SesTransportTest extends TestCase { /** * Test {@see SesTransport} constructor and {@see SesTransport::getClient()} methods. * * @return void - * @covers ::__construct() - * @covers ::getClient() */ public function testConstruct(): void { @@ -81,10 +84,10 @@ public function getClient(): SesClient * * @return array */ - public function sendProvider(): array + public static function sendProvider(): array { $messageId = sprintf('<%s@example.com>', Text::uuid()); - $now = FrozenTime::now(); + $now = DateTime::now(); return [ 'simple' => [ @@ -122,9 +125,8 @@ public function sendProvider(): array * @param array $config Client configuration. * @param \Cake\Mailer\Message $email Email message to send. * @return void - * @dataProvider sendProvider() - * @covers ::send() */ + #[DataProvider('sendProvider')] public function testSend(string $expectedHeaders, string $expectedMessage, array $config, Message $email): void { $invocations = 0; diff --git a/tests/TestCase/Mailer/Transport/SnsTransportTest.php b/tests/TestCase/Mailer/Transport/SnsTransportTest.php index c8ebca7..281635f 100644 --- a/tests/TestCase/Mailer/Transport/SnsTransportTest.php +++ b/tests/TestCase/Mailer/Transport/SnsTransportTest.php @@ -20,21 +20,24 @@ use Aws\Sns\SnsClient; use BEdita\AWS\Mailer\Transport\SnsTransport; use Cake\Mailer\Message; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; /** * Test {@see \BEdita\AWS\Mailer\Transport\SnsTransport}. - * - * @coversDefaultClass \BEdita\AWS\Mailer\Transport\SnsTransport */ +#[CoversClass(SnsTransport::class)] +#[CoversMethod(SnsTransport::class, '__construct')] +#[CoversMethod(SnsTransport::class, 'getClient')] +#[CoversMethod(SnsTransport::class, 'send')] class SnsTransportTest extends TestCase { /** * Test {@see SnsTransport} constructor and {@see SnsTransport::getClient()} methods. * * @return void - * @covers ::__construct() - * @covers ::getClient() */ public function testConstruct(): void { @@ -80,7 +83,7 @@ public function getClient(): SnsClient * * @return array */ - public function sendProvider(): array + public static function sendProvider(): array { return [ 'simple' => [ @@ -157,9 +160,8 @@ public function sendProvider(): array * @param array $config Client configuration. * @param \Cake\Mailer\Message $email Email to send. * @return void - * @dataProvider sendProvider() - * @covers ::send() */ + #[DataProvider('sendProvider')] public function testSend(array $expected, array $expectedPayload, array $config, Message $email): void { $invocations = 0; diff --git a/tests/TestCase/PluginTest.php b/tests/TestCase/PluginTest.php index 9e53114..32214f5 100644 --- a/tests/TestCase/PluginTest.php +++ b/tests/TestCase/PluginTest.php @@ -17,16 +17,18 @@ use BEdita\AWS\Plugin; use BEdita\Core\Filesystem\FilesystemRegistry; -use BEdita\Core\Mailer\Email; use Cake\Http\BaseApplication; use Cake\Http\MiddlewareQueue; +use Cake\Mailer\Mailer; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\TestCase; /** * Test {@see \BEdita\AWS\Plugin}. - * - * @coversDefaultClass \BEdita\AWS\Plugin */ +#[CoversClass(Plugin::class)] +#[CoversMethod(Plugin::class, 'bootstrap')] class PluginTest extends TestCase { /** @@ -50,7 +52,6 @@ protected function setUp(): void * Test {@see Plugin::bootstrap()} method. * * @return void - * @covers ::bootstrap() */ public function testBootstrap(): void { @@ -61,7 +62,7 @@ public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue } }; - $mailers = Email::getDsnClassMap(); + $mailers = Mailer::getDsnClassMap(); static::assertArrayNotHasKey('ses', $mailers); static::assertArrayNotHasKey('sns', $mailers); @@ -70,7 +71,7 @@ public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue $this->plugin->bootstrap($app); - $mailers = Email::getDsnClassMap(); + $mailers = Mailer::getDsnClassMap(); static::assertArrayHasKey('ses', $mailers); static::assertArrayHasKey('sns', $mailers); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 38fa40d..858c2a8 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -26,7 +26,6 @@ use Cake\Cache\Engine\NullEngine; use Cake\Core\Configure; use Cake\Datasource\ConnectionManager; -use Cake\I18n\FrozenTime; use Cake\Log\Engine\ConsoleLog; use Cake\Log\Log; use Cake\ORM\TableRegistry; @@ -47,16 +46,21 @@ unset($findRoot); chdir($root); -require_once 'vendor/cakephp/cakephp/src/basics.php'; -require_once 'vendor/autoload.php'; +require dirname(__DIR__) . '/vendor/autoload.php'; + +define('ROOT', dirname(__DIR__)); +define('CAKE_CORE_INCLUDE_PATH', ROOT . DS . 'vendor' . DS . 'cakephp' . DS . 'cakephp'); +define('CORE_PATH', CAKE_CORE_INCLUDE_PATH . DS); +define('CAKE', CORE_PATH . 'src' . DS); + +require CORE_PATH . 'config' . DS . 'bootstrap.php'; +require CAKE . 'functions.php'; -define('ROOT', $root . DS . 'tests' . DS); define('APP', ROOT . 'TestApp' . DS); define('TMP', sys_get_temp_dir() . DS); define('LOGS', ROOT . DS . 'logs' . DS); define('CONFIG', ROOT . DS . 'config' . DS); define('CACHE', TMP . 'cache' . DS); -define('CORE_PATH', $root . DS . 'vendor' . DS . 'cakephp' . DS . 'cakephp' . DS); Configure::write('debug', true); Configure::write('App', [ @@ -99,7 +103,6 @@ Router::reload(); Security::setSalt('BEDITA'); -FrozenTime::setTestNow('2022-01-01T00:00:00+01:00'); // clear all before running tests TableRegistry::getTableLocator()->clear();