diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9515500..d96f759 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,25 +16,38 @@ jobs: strategy: matrix: php: - - '7.2' - '7.3' - '7.4' - - 'rc' + - '8.0' symfony: - '4.4.*' - '5.1.*' dependency: - '' - - 'lowest' + - '--prefer-lowest' include: - php: '7.4' dependency: '' symfony: '5.1.*' coverage: true bootable: true + - php: '8.0' + dependency: '--ignore-platform-req=php' + symfony: '4.4.*' + - php: '8.0' + dependency: '--ignore-platform-req=php' + symfony: '5.1.*' + - php: '8.0' + dependency: '--prefer-lowest --ignore-platform-req=php' + symfony: '4.4.*' + - php: '8.0' + dependency: '--prefer-lowest --ignore-platform-req=php' + symfony: '5.1.*' exclude: - - symfony: '4.4.*' - dependency: 'lowest' + - php: '8.0' + dependency: '' + - php: '8.0' + dependency: '--prefer-lowest' fail-fast: false steps: - name: Checkout @@ -63,15 +76,10 @@ jobs: run: composer config extra.symfony.require "${{ matrix.symfony }}" - name: Update project dependencies - if: matrix.dependency == '' - run: composer update --no-progress --ansi --prefer-stable ${{ matrix.composer }} - - - name: Update project dependencies lowest - if: matrix.dependency == 'lowest' - run: composer update --no-progress --ansi --prefer-lowest --prefer-stable + run: composer update --no-progress --ansi --prefer-stable ${{ matrix.dependency }} - name: Disable deprecations notices for lowest dependencies - if: matrix.dependency == 'lowest' + if: matrix.dependency != '' run: echo "SYMFONY_DEPRECATIONS_HELPER=weak" >> $GITHUB_ENV - name: Bundle is bootable @@ -82,10 +90,10 @@ jobs: composer create-project "symfony/skeleton:${SKELETON_VERSION}" flex cd flex composer config extra.symfony.allow-contrib true - composer req --ignore-platform-reqs gheb/docusign-bundle + composer req gheb/docusign-bundle - name: Run php-cs-fixer tests - if: matrix.php != 'rc' + if: matrix.php != '8.0' run: php-cs-fixer fix --diff --dry-run - name: Install chromium @@ -94,11 +102,11 @@ jobs: sudo apt-get install -y --no-install-recommends chromium-browser - name: Run phpstan tests - if: matrix.dependency == '' + if: matrix.dependency == '' && matrix.php != '7.3' && matrix.php != '8.0' run: vendor/bin/phpstan analyze - name: Run phpstan tests lowest - if: matrix.dependency == 'lowest' + if: matrix.dependency != '' && matrix.php != '8.0' run: vendor/bin/phpstan analyze -c phpstan.neon.lowest.dist - name: Prepare PHPUnit tests diff --git a/composer.json b/composer.json index d70aab2..8891da0 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "php": ">=7.2", "ext-SimpleXML": "*", "docusign/esign-client": "^3.0", - "lcobucci/jwt": "^3.3.1 || ^4.0-dev", + "lcobucci/jwt": "^3.3.1 || ^4.0", "league/flysystem": "^1.0.8", "psr/log": "^1.1", "symfony/config": "^4.4 || ^5.0", @@ -46,6 +46,7 @@ "doctrine/annotations": "^1.11", "league/flysystem-bundle": "^1.2", "nyholm/symfony-bundle-test": "dev-master", + "phpspec/prophecy": "^1.12", "phpstan/phpstan": "^0.12.18", "psr/event-dispatcher": "^1.0", "symfony/console": "^4.4 || ^5.0", @@ -54,7 +55,7 @@ "symfony/dotenv": "^4.4 || ^5.0", "symfony/monolog-bundle": "^3.5", "symfony/panther": "^0.6.1", - "symfony/phpunit-bridge": "^4.4 || ^5.0", + "symfony/phpunit-bridge": "^5.1", "symfony/polyfill-php72": "^1.9", "symfony/process": "^4.4 || ^5.0", "symfony/profiler-pack": "^1.0", @@ -73,8 +74,7 @@ "autoload-dev": { "psr-4": { "DocusignBundle\\Tests\\": "tests/", - "DocusignBundle\\E2e\\": "features/src/", - "PHPUnit\\": "vendor/bin/.phpunit/phpunit/src" + "DocusignBundle\\E2e\\": "features/src/" } }, "scripts": { diff --git a/features/bootstrap.php b/features/bootstrap.php index 805fc1f..6f9aeac 100644 --- a/features/bootstrap.php +++ b/features/bootstrap.php @@ -15,6 +15,16 @@ date_default_timezone_set('UTC'); +// PHPUnit's autoloader +if (!file_exists($phpUnitAutoloaderPath = __DIR__.'/../vendor/bin/.phpunit/phpunit/vendor/autoload.php')) { + die('PHPUnit is not installed. Please run vendor/bin/simple-phpunit --version to install it'); +} + +$phpunitLoader = require $phpUnitAutoloaderPath; +// Don't register the PHPUnit autoloader before the normal autoloader to prevent weird issues +$phpunitLoader->unregister(); +$phpunitLoader->register(); + require __DIR__.'/../vendor/autoload.php'; // Load cached env vars if the .env.local.php file exists diff --git a/phpstan.neon.dist b/phpstan.neon.dist index ff22639..50d7b40 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -7,5 +7,8 @@ parameters: - '#Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition::useAttributeAsKey\(\)\.#' # known bug of phpstan - - "#Instantiated class Lcobucci\\\\JWT\\\\Token\\\\Builder not found\\.#" - - "#Instantiated class Lcobucci\\\\Jose\\\\Parsing\\\\Parser not found\\.#" + - "#Cannot cast Lcobucci\\\\JWT\\\\Token\\\\Plain to string\\.#" + - "#Parameter \\#1 \\$issuedAt of method Lcobucci\\\\JWT\\\\Builder::issuedAt\\(\\) expects DateTimeImmutable, int given\\.#" + - "#Parameter \\#1 \\$expiration of method Lcobucci\\\\JWT\\\\Builder::expiresAt\\(\\) expects DateTimeImmutable, int given\\.#" + - "#Cannot instantiate interface Lcobucci\\\\JWT\\\\Builder\\.#" + - "#Cannot instantiate interface Lcobucci\\\\JWT\\\\Signer\\\\Key\\.#" diff --git a/phpstan.neon.lowest.dist b/phpstan.neon.lowest.dist index 9fe4818..cc0d0db 100644 --- a/phpstan.neon.lowest.dist +++ b/phpstan.neon.lowest.dist @@ -7,6 +7,5 @@ parameters: - '#Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition::useAttributeAsKey\(\)\.#' # known bug of phpstan - - "#Class Lcobucci\\\\JWT\\\\Token\\\\Builder not found\\.#" - - "#Instantiated class Lcobucci\\\\JWT\\\\Token\\\\Builder not found\\.#" - - "#Instantiated class Lcobucci\\\\Jose\\\\Parsing\\\\Parser not found\\.#" + - "#Call to static method file\\(\\) on an unknown class Lcobucci\\\\JWT\\\\Signer\\\\Key\\\\LocalFileReference\\.#" + - "#Call to static method forSymmetricSigner\\(\\) on an unknown class Lcobucci\\\\JWT\\\\Configuration\\.#" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 84bdfb6..d079da4 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,14 +1,14 @@ - + - + diff --git a/src/Grant/JwtGrant.php b/src/Grant/JwtGrant.php index c05cc29..1e6d3be 100644 --- a/src/Grant/JwtGrant.php +++ b/src/Grant/JwtGrant.php @@ -13,9 +13,10 @@ namespace DocusignBundle\Grant; -use Lcobucci\Jose\Parsing\Parser; use Lcobucci\JWT\Builder; +use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Signer\Key; +use Lcobucci\JWT\Signer\Key\LocalFileReference; use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\Token\Builder as TokenBuilder; use Symfony\Component\HttpClient\HttpClient; @@ -55,23 +56,9 @@ public function __construct( public function __invoke(): string { - // Ensure compatibility with lcobucci/jwt v3 and v4 - $v4 = class_exists(TokenBuilder::class); - $time = $v4 ? new \DateTimeImmutable() : time(); - /** @var Builder $builder */ - $builder = $v4 ? new TokenBuilder(new Parser()) : new Builder(); - $token = $builder->issuedBy($this->integrationKey) // iss - ->relatedTo($this->userGuid) // sub - ->issuedAt($time) // iat - ->expiresAt($v4 ? $time->modify("$this->ttl sec") : $time + $this->ttl) // exp - ->permittedFor(parse_url($this->accountApiUri, PHP_URL_HOST)) // aud - ->withClaim('scope', 'signature impersonation') // scope - ->getToken(new Sha256(), new Key("file://$this->privateKey")); - $token = method_exists($token, 'toString') ? $token->toString() : (string) $token; - try { $response = $this->client->request('POST', $this->accountApiUri, [ - 'body' => "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=$token", + 'body' => sprintf('grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=%s', $this->createToken()), ]); return $response->toArray()['access_token'] ?? ''; @@ -79,4 +66,43 @@ public function __invoke(): string return ''; } } + + /** + * Creates a valid JWT for DocuSign. + * + * @see https://developers.docusign.com/platform/auth/jwt/jwt-get-token/ + */ + private function createToken(): string + { + // Ensure compatibility with lcobucci/jwt v3 and v4 + if (class_exists(TokenBuilder::class)) { + // lcobucci/jwt v4 + $time = new \DateTimeImmutable(); + // Need for force seconds to 0, otherwise DocuSign will consider this token as invalid + $time = $time->setTime((int) $time->format('H'), (int) $time->format('i'), 0, 0); + $config = Configuration::forSymmetricSigner(new Sha256(), LocalFileReference::file("file://$this->privateKey")); + + return $config + ->builder() + ->issuedBy($this->integrationKey) // iss + ->relatedTo($this->userGuid) // sub + ->issuedAt($time) // iat + ->expiresAt($time->modify("$this->ttl sec")) // exp + ->permittedFor(parse_url($this->accountApiUri, PHP_URL_HOST)) // aud + ->withClaim('scope', 'signature impersonation') // scope + ->getToken($config->signer(), $config->signingKey()) + ->toString(); + } + + $time = time(); + + return (string) (new Builder()) + ->issuedBy($this->integrationKey) // iss + ->relatedTo($this->userGuid) // sub + ->issuedAt($time) // iat + ->expiresAt($time + $this->ttl) // exp + ->permittedFor(parse_url($this->accountApiUri, PHP_URL_HOST)) // aud + ->withClaim('scope', 'signature impersonation') // scope + ->getToken(new Sha256(), new Key("file://$this->privateKey")); + } } diff --git a/tests/Controller/AuthorizationCodeTest.php b/tests/Controller/AuthorizationCodeTest.php index 53cd302..30f100c 100644 --- a/tests/Controller/AuthorizationCodeTest.php +++ b/tests/Controller/AuthorizationCodeTest.php @@ -17,6 +17,7 @@ use DocusignBundle\EnvelopeBuilderInterface; use DocusignBundle\Events\AuthorizationCodeEvent; use DocusignBundle\Exception\MissingMandatoryParameterHttpException; +use DocusignBundle\Tests\ProphecyTrait; use DocusignBundle\TokenEncoder\TokenEncoderInterface; use PHPUnit\Framework\TestCase; use Prophecy\Argument; @@ -28,6 +29,8 @@ class AuthorizationCodeTest extends TestCase { + use ProphecyTrait; + public function testTheAuthorizationCodeControllerIsValid(): void { $tokenEncoderProphecy = $this->prophesize(TokenEncoderInterface::class); diff --git a/tests/Controller/CallbackTest.php b/tests/Controller/CallbackTest.php index d326344..0dbdf16 100644 --- a/tests/Controller/CallbackTest.php +++ b/tests/Controller/CallbackTest.php @@ -16,6 +16,7 @@ use DocusignBundle\Controller\Callback; use DocusignBundle\DocusignBundle; use DocusignBundle\Events\DocumentSignatureCompletedEvent; +use DocusignBundle\Tests\ProphecyTrait; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -25,6 +26,8 @@ class CallbackTest extends TestCase { + use ProphecyTrait; + public function testTheCallbackControllerIsValid(): void { $requestProphecy = $this->prophesize(Request::class); diff --git a/tests/Controller/ConsentTest.php b/tests/Controller/ConsentTest.php index 290265d..bb70e45 100644 --- a/tests/Controller/ConsentTest.php +++ b/tests/Controller/ConsentTest.php @@ -14,6 +14,7 @@ namespace DocusignBundle\Tests\Controller; use DocusignBundle\Controller\Consent; +use DocusignBundle\Tests\ProphecyTrait; use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\ObjectProphecy; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -24,6 +25,8 @@ */ final class ConsentTest extends TestCase { + use ProphecyTrait; + public function testItRedirectsToValidUri(): void { /** @var Request|ObjectProphecy $requestMock */ diff --git a/tests/Controller/SignTest.php b/tests/Controller/SignTest.php index 16602b6..3f26775 100644 --- a/tests/Controller/SignTest.php +++ b/tests/Controller/SignTest.php @@ -17,6 +17,7 @@ use DocusignBundle\EnvelopeBuilderInterface; use DocusignBundle\Events\PreSignEvent; use DocusignBundle\Exception\MissingMandatoryParameterHttpException; +use DocusignBundle\Tests\ProphecyTrait; use League\Flysystem\FileNotFoundException; use PHPUnit\Framework\TestCase; use Prophecy\Argument; @@ -29,6 +30,8 @@ class SignTest extends TestCase { + use ProphecyTrait; + public function testTheSignControllerRedirectsTheUserToDocusign(): void { $eventDispatcherProphecy = $this->prophesize(EventDispatcherInterface::class); diff --git a/tests/Controller/WebhookTest.php b/tests/Controller/WebhookTest.php index 8154918..e34be0d 100644 --- a/tests/Controller/WebhookTest.php +++ b/tests/Controller/WebhookTest.php @@ -15,6 +15,7 @@ use DocusignBundle\Controller\Webhook; use DocusignBundle\Events\CompletedEvent; +use DocusignBundle\Tests\ProphecyTrait; use DocusignBundle\TokenEncoder\TokenEncoderInterface; use PHPUnit\Framework\TestCase; use Prophecy\Argument; @@ -27,6 +28,8 @@ class WebhookTest extends TestCase { + use ProphecyTrait; + private $requestProphecy; private $queryProphecy; private $eventDispatcherProphecy; diff --git a/tests/DependencyInjection/DocusignExtensionTest.php b/tests/DependencyInjection/DocusignExtensionTest.php index 1bac861..0930500 100644 --- a/tests/DependencyInjection/DocusignExtensionTest.php +++ b/tests/DependencyInjection/DocusignExtensionTest.php @@ -27,6 +27,7 @@ use DocusignBundle\Grant\GrantInterface; use DocusignBundle\Grant\JwtGrant; use DocusignBundle\Routing\DocusignLoader; +use DocusignBundle\Tests\ProphecyTrait; use DocusignBundle\TokenEncoder\TokenEncoder; use DocusignBundle\TokenEncoder\TokenEncoderInterface; use DocusignBundle\Translator\TranslatorAwareInterface; @@ -46,6 +47,8 @@ class DocusignExtensionTest extends TestCase { + use ProphecyTrait; + public const DEFAULT_CONFIG = ['docusign' => [ 'demo' => false, 'enable_profiler' => false, diff --git a/tests/EnvelopeBuilderTest.php b/tests/EnvelopeBuilderTest.php index 453a02a..5d6be2b 100644 --- a/tests/EnvelopeBuilderTest.php +++ b/tests/EnvelopeBuilderTest.php @@ -26,6 +26,8 @@ class EnvelopeBuilderTest extends TestCase { + use ProphecyTrait; + private $loggerProphecyMock; private $routerProphecyMock; private $fileSystemProphecyMock; diff --git a/tests/EnvelopeCreator/CreateDocumentTest.php b/tests/EnvelopeCreator/CreateDocumentTest.php index bbf8674..ffece6b 100644 --- a/tests/EnvelopeCreator/CreateDocumentTest.php +++ b/tests/EnvelopeCreator/CreateDocumentTest.php @@ -16,6 +16,7 @@ use DocuSign\eSign\Model\Document; use DocusignBundle\EnvelopeBuilderInterface; use DocusignBundle\EnvelopeCreator\CreateDocument; +use DocusignBundle\Tests\ProphecyTrait; use League\Flysystem\FileNotFoundException; use League\Flysystem\FilesystemInterface; use PHPUnit\Framework\TestCase; @@ -23,6 +24,8 @@ class CreateDocumentTest extends TestCase { + use ProphecyTrait; + private $envelopeBuilderProphecyMock; private $fileSystemProphecyMock; diff --git a/tests/EnvelopeCreator/CreateSignatureTest.php b/tests/EnvelopeCreator/CreateSignatureTest.php index ffb8804..d34afa7 100644 --- a/tests/EnvelopeCreator/CreateSignatureTest.php +++ b/tests/EnvelopeCreator/CreateSignatureTest.php @@ -15,11 +15,14 @@ use DocusignBundle\EnvelopeBuilderInterface; use DocusignBundle\EnvelopeCreator\CreateSignature; +use DocusignBundle\Tests\ProphecyTrait; use DocusignBundle\Utils\SignatureExtractor; use PHPUnit\Framework\TestCase; class CreateSignatureTest extends TestCase { + use ProphecyTrait; + private $envelopeBuilderProphecyMock; private $signatureExtractorProphecyMock; diff --git a/tests/EnvelopeCreator/DefineEnvelopeTest.php b/tests/EnvelopeCreator/DefineEnvelopeTest.php index aec6228..ed0fda2 100644 --- a/tests/EnvelopeCreator/DefineEnvelopeTest.php +++ b/tests/EnvelopeCreator/DefineEnvelopeTest.php @@ -17,6 +17,7 @@ use DocusignBundle\DocusignBundle; use DocusignBundle\EnvelopeBuilderInterface; use DocusignBundle\EnvelopeCreator\DefineEnvelope; +use DocusignBundle\Tests\ProphecyTrait; use DocusignBundle\TokenEncoder\TokenEncoderInterface; use PHPUnit\Framework\TestCase; use Prophecy\Argument; @@ -26,6 +27,8 @@ class DefineEnvelopeTest extends TestCase { + use ProphecyTrait; + private $envelopeBuilderProphecyMock; private $routerProphecyMock; private $translatorProphecyMock; diff --git a/tests/EnvelopeCreator/EnvelopeCreatorTest.php b/tests/EnvelopeCreator/EnvelopeCreatorTest.php index eb35bf9..6416bc0 100644 --- a/tests/EnvelopeCreator/EnvelopeCreatorTest.php +++ b/tests/EnvelopeCreator/EnvelopeCreatorTest.php @@ -17,6 +17,7 @@ use DocusignBundle\EnvelopeBuilderInterface; use DocusignBundle\EnvelopeCreator\EnvelopeBuilderCallableInterface; use DocusignBundle\EnvelopeCreator\EnvelopeCreator; +use DocusignBundle\Tests\ProphecyTrait; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Psr\Log\LoggerInterface; @@ -24,6 +25,8 @@ class EnvelopeCreatorTest extends TestCase { + use ProphecyTrait; + private $envelopeBuilderProphecyMock; private $routerProphecyMock; private $loggerProphecyMock; diff --git a/tests/EnvelopeCreator/GetViewUrlTest.php b/tests/EnvelopeCreator/GetViewUrlTest.php index 68db463..41ba40a 100644 --- a/tests/EnvelopeCreator/GetViewUrlTest.php +++ b/tests/EnvelopeCreator/GetViewUrlTest.php @@ -18,6 +18,7 @@ use DocusignBundle\EnvelopeBuilder; use DocusignBundle\EnvelopeBuilderInterface; use DocusignBundle\EnvelopeCreator\GetViewUrl; +use DocusignBundle\Tests\ProphecyTrait; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Symfony\Component\Routing\Router; @@ -25,6 +26,8 @@ class GetViewUrlTest extends TestCase { + use ProphecyTrait; + private $envelopeBuilderProphecyMock; private $routerProphecyMock; diff --git a/tests/EnvelopeCreator/SendEnvelopeTest.php b/tests/EnvelopeCreator/SendEnvelopeTest.php index 047f83e..814f327 100644 --- a/tests/EnvelopeCreator/SendEnvelopeTest.php +++ b/tests/EnvelopeCreator/SendEnvelopeTest.php @@ -19,12 +19,15 @@ use DocusignBundle\EnvelopeBuilderInterface; use DocusignBundle\EnvelopeCreator\SendEnvelope; use DocusignBundle\Grant\GrantInterface; +use DocusignBundle\Tests\ProphecyTrait; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Symfony\Component\Routing\RouterInterface; class SendEnvelopeTest extends TestCase { + use ProphecyTrait; + private $envelopeBuilderProphecyMock; private $grantProphecyMock; private $routerProphecyMock; diff --git a/tests/EventSubscriber/AuthorizationCodeEventSubscriberTest.php b/tests/EventSubscriber/AuthorizationCodeEventSubscriberTest.php index 3a9a9a3..354cfaa 100644 --- a/tests/EventSubscriber/AuthorizationCodeEventSubscriberTest.php +++ b/tests/EventSubscriber/AuthorizationCodeEventSubscriberTest.php @@ -19,6 +19,7 @@ use DocusignBundle\Events\AuthorizationCodeEvent; use DocusignBundle\Events\PreSignEvent; use DocusignBundle\EventSubscriber\AuthorizationCodeEventSubscriber; +use DocusignBundle\Tests\ProphecyTrait; use DocusignBundle\TokenEncoder\TokenEncoderInterface; use PHPUnit\Framework\TestCase; use Prophecy\Argument; @@ -35,6 +36,8 @@ */ final class AuthorizationCodeEventSubscriberTest extends TestCase { + use ProphecyTrait; + /** * @var ObjectProphecy|AuthorizationCodeHandlerInterface */ diff --git a/tests/Events/WebhookEventFactoryTest.php b/tests/Events/WebhookEventFactoryTest.php index a4b7c8b..4b5bd6f 100644 --- a/tests/Events/WebhookEventFactoryTest.php +++ b/tests/Events/WebhookEventFactoryTest.php @@ -21,6 +21,7 @@ use DocusignBundle\Events\SentEvent; use DocusignBundle\Events\WebhookEventFactory; use DocusignBundle\Exception\InvalidStatusException; +use DocusignBundle\Tests\ProphecyTrait; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; @@ -29,6 +30,8 @@ */ final class WebhookEventFactoryTest extends TestCase { + use ProphecyTrait; + /** * @dataProvider getEvents */ diff --git a/tests/ProphecyTrait.php b/tests/ProphecyTrait.php new file mode 100644 index 0000000..20c20a8 --- /dev/null +++ b/tests/ProphecyTrait.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace DocusignBundle\Tests; + +use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\TestCase; +use Prophecy\Exception\Doubler\DoubleException; +use Prophecy\Exception\Doubler\InterfaceNotFoundException; +use Prophecy\Exception\Prediction\PredictionException; +use Prophecy\Prophecy\MethodProphecy; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophet; + +/** + * Copied from https://github.com/phpspec/prophecy-phpunit for symfony/phpunit-bridge. + * + * @mixin TestCase + */ +trait ProphecyTrait +{ + /** + * @var Prophet|null + * + * @internal + */ + private $prophet; + + /** + * @var bool + * + * @internal + */ + private $prophecyAssertionsCounted = false; + + /** + * @throws DoubleException + * @throws InterfaceNotFoundException + * + * @psalm-param class-string|null $classOrInterface + */ + protected function prophesize(?string $classOrInterface = null): ObjectProphecy + { + if (\is_string($classOrInterface)) { + \assert($this instanceof TestCase); + $this->recordDoubledType($classOrInterface); + } + + return $this->getProphet()->prophesize($classOrInterface); + } + + /** + * @postCondition + */ + protected function verifyProphecyDoubles(): void + { + if (null === $this->prophet) { + return; + } + + try { + $this->prophet->checkPredictions(); + } catch (PredictionException $e) { + throw new AssertionFailedError($e->getMessage()); + } finally { + $this->countProphecyAssertions(); + } + } + + /** + * @after + */ + protected function tearDownProphecy(): void + { + if (null !== $this->prophet && !$this->prophecyAssertionsCounted) { + // Some Prophecy assertions may have been done in tests themselves even when a failure happened before checking mock objects. + $this->countProphecyAssertions(); + } + + $this->prophet = null; + } + + /** + * @internal + */ + private function countProphecyAssertions(): void + { + \assert($this instanceof TestCase); + $this->prophecyAssertionsCounted = true; + + foreach ($this->prophet->getProphecies() as $objectProphecy) { + foreach ($objectProphecy->getMethodProphecies() as $methodProphecies) { + foreach ($methodProphecies as $methodProphecy) { + \assert($methodProphecy instanceof MethodProphecy); + + $this->addToAssertionCount(\count($methodProphecy->getCheckedPredictions())); + } + } + } + } + + /** + * @internal + */ + private function getProphet(): Prophet + { + if (null === $this->prophet) { + $this->prophet = new Prophet(); + } + + return $this->prophet; + } +} diff --git a/tests/Twig/Extension/ClickwrapExtensionTest.php b/tests/Twig/Extension/ClickwrapExtensionTest.php index 606b9b7..6027fed 100644 --- a/tests/Twig/Extension/ClickwrapExtensionTest.php +++ b/tests/Twig/Extension/ClickwrapExtensionTest.php @@ -13,6 +13,7 @@ namespace DocusignBundle\tests\Twig\Extension; +use DocusignBundle\Tests\ProphecyTrait; use DocusignBundle\Twig\Extension\ClickwrapExtension; use PHPUnit\Framework\TestCase; use Prophecy\Argument; @@ -23,6 +24,8 @@ */ final class ClickwrapExtensionTest extends TestCase { + use ProphecyTrait; + private $extension; private $twigMock; diff --git a/tests/Utils/SignatureExtractorTest.php b/tests/Utils/SignatureExtractorTest.php index 4fd9906..c5e9fb4 100644 --- a/tests/Utils/SignatureExtractorTest.php +++ b/tests/Utils/SignatureExtractorTest.php @@ -14,6 +14,7 @@ namespace DocusignBundle\Tests\DependencyInjection; use DocusignBundle\Exception\AmbiguousDocumentSelectionException; +use DocusignBundle\Tests\ProphecyTrait; use DocusignBundle\Utils\SignatureExtractor; use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\ObjectProphecy; @@ -25,6 +26,8 @@ class SignatureExtractorTest extends TestCase { + use ProphecyTrait; + /** * @var ObjectProphecy|RequestStack */