├── phpstan-baseline.neon ├── phpstan.neon.dist ├── src ├── Expectations │ ├── Plugin.php │ ├── Response.php │ ├── RateLimit.php │ ├── Cache.php │ ├── Authentication.php │ ├── Trait.php │ ├── Connector.php │ ├── Pagination.php │ ├── Properties.php │ └── Request.php ├── Plugin.php └── Autoload.php ├── rector.php ├── LICENSE └── composer.json /phpstan-baseline.neon: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - phpstan-baseline.neon 3 | 4 | parameters: 5 | level: 5 6 | paths: 7 | - src 8 | -------------------------------------------------------------------------------- /src/Expectations/Plugin.php: -------------------------------------------------------------------------------- 1 | extend( 8 | 'toBeSaloonPlugin', 9 | fn (): ArchExpectation => // @phpstan-ignore-next-line 10 | $this->toBeTrait() 11 | ); 12 | -------------------------------------------------------------------------------- /src/Plugin.php: -------------------------------------------------------------------------------- 1 | extend( 8 | 'toBeSaloonResponse', 9 | fn (): ArchExpectation => // @phpstan-ignore-next-line 10 | $this->toExtend('Saloon\Http\Response') 11 | ); 12 | -------------------------------------------------------------------------------- /src/Expectations/RateLimit.php: -------------------------------------------------------------------------------- 1 | extend( 8 | 'toHaveRateLimits', 9 | fn (): ArchExpectation => // @phpstan-ignore-next-line 10 | $this->toUse('Saloon\RateLimitPlugin\Traits\HasRateLimits') 11 | ); 12 | -------------------------------------------------------------------------------- /src/Expectations/Cache.php: -------------------------------------------------------------------------------- 1 | extend( 8 | 'toHaveCaching', 9 | fn (): SingleArchExpectation => // @phpstan-ignore-next-line 10 | $this->toUse('Saloon\CachePlugin\Traits\HasCaching') 11 | ->toImplement('Saloon\CachePlugin\Contracts\Cacheable') 12 | ); 13 | -------------------------------------------------------------------------------- /src/Autoload.php: -------------------------------------------------------------------------------- 1 | paths([ 12 | __DIR__.'/src', 13 | ]); 14 | 15 | $rectorConfig->rules([ 16 | InlineConstructorDefaultToPropertyRector::class, 17 | ]); 18 | 19 | $rectorConfig->sets([ 20 | LevelSetList::UP_TO_PHP_81, 21 | SetList::CODE_QUALITY, 22 | SetList::DEAD_CODE, 23 | SetList::EARLY_RETURN, 24 | SetList::TYPE_DECLARATION, 25 | SetList::PRIVATIZATION, 26 | ]); 27 | }; 28 | -------------------------------------------------------------------------------- /src/Expectations/Authentication.php: -------------------------------------------------------------------------------- 1 | extend( 8 | 'toUseTokenAuthentication', 9 | fn (): ArchExpectation => // @phpstan-ignore-next-line 10 | $this->toUse('Saloon\Http\Auth\TokenAuthenticator') 11 | ); 12 | 13 | expect()->extend( 14 | 'toUseBasicAuthentication', 15 | fn (): ArchExpectation => // @phpstan-ignore-next-line 16 | $this->toUse('Saloon\Http\Auth\BasicAuthenticator') 17 | ); 18 | 19 | expect()->extend( 20 | 'toUseCertificateAuthentication', 21 | fn (): ArchExpectation => // @phpstan-ignore-next-line 22 | $this->toUse('Saloon\Http\Auth\CertificateAuthenticator') 23 | ); 24 | 25 | expect()->extend( 26 | 'toUseHeaderAuthentication', 27 | fn (): ArchExpectation => // @phpstan-ignore-next-line 28 | $this->toUse('Saloon\Http\Auth\HeaderAuthenticator') 29 | ); 30 | 31 | expect()->extend( 32 | 'toUseQueryAuthentication', 33 | fn (): ArchExpectation => // @phpstan-ignore-next-line 34 | $this->toUse('Saloon\Http\Auth\QueryAuthenticator') 35 | ); 36 | -------------------------------------------------------------------------------- /src/Expectations/Trait.php: -------------------------------------------------------------------------------- 1 | extend( 8 | 'toUseAcceptsJsonTrait', 9 | fn (): ArchExpectation => // @phpstan-ignore-next-line 10 | $this->toUse('Saloon\Traits\Plugins\AcceptsJson') 11 | ); 12 | 13 | expect()->extend( 14 | 'toUseAlwaysThrowOnErrorsTrait', 15 | fn (): ArchExpectation => // @phpstan-ignore-next-line 16 | $this->toUse('Saloon\Traits\Plugins\AlwaysThrowOnErrors') 17 | ); 18 | 19 | expect()->extend( 20 | 'toUseTimeoutTrait', 21 | fn (): ArchExpectation => // @phpstan-ignore-next-line 22 | $this->toUse('Saloon\Traits\Plugins\HasTimeout') 23 | ); 24 | 25 | expect()->extend( 26 | 'toUseAuthorisationCodeGrantTrait', 27 | fn (): ArchExpectation => // @phpstan-ignore-next-line 28 | $this->toUse('Saloon\Traits\OAuth2\AuthorizationCodeGrant') 29 | ); 30 | 31 | expect()->extend( 32 | 'toUseClientCredentialsGrantTrait', 33 | fn (): ArchExpectation => // @phpstan-ignore-next-line 34 | $this->toUse('Saloon\Traits\OAuth2\ClientCredentialsGrant') 35 | ); 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 jp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Expectations/Connector.php: -------------------------------------------------------------------------------- 1 | extend( 8 | 'toBeSaloonConnector', 9 | fn (): ArchExpectation => // @phpstan-ignore-next-line 10 | $this->toExtend('Saloon\Http\Connector') 11 | ); 12 | 13 | expect()->extend( 14 | 'toHaveDefaultHeaders', 15 | fn (): ArchExpectation => // @phpstan-ignore-next-line 16 | $this->toHaveMethod('defaultHeaders') 17 | ); 18 | 19 | expect()->extend( 20 | 'toHaveDefaultConfig', 21 | fn (): ArchExpectation => // @phpstan-ignore-next-line 22 | $this->toHaveMethod('defaultConfig') 23 | ); 24 | 25 | expect()->extend( 26 | 'toHaveBaseUrl', 27 | fn (): ArchExpectation => // @phpstan-ignore-next-line 28 | $this->toHaveMethod('resolveBaseUrl') 29 | ); 30 | 31 | expect()->extend( 32 | 'toUseCustomResponse', 33 | fn (): ArchExpectation => // @phpstan-ignore-next-line 34 | $this->toHaveMethod('resolveResponseClass') 35 | ); 36 | 37 | expect()->extend( 38 | 'toHaveCustomFailureDetection', 39 | fn (): ArchExpectation => // @phpstan-ignore-next-line 40 | $this->toHaveMethod('hasRequestFailed') 41 | ); 42 | 43 | expect()->extend( 44 | 'toHaveCustomException', 45 | fn (): ArchExpectation => // @phpstan-ignore-next-line 46 | $this->toHaveMethod('getRequestException') 47 | ); 48 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jonpurvis/lawman", 3 | "description": "A PestPHP Plugin to help with architecture testing SaloonPHP integrations", 4 | "keywords": [ 5 | "php", 6 | "framework", 7 | "pest", 8 | "unit", 9 | "test", 10 | "testing", 11 | "plugin", 12 | "saloon" 13 | ], 14 | "license": "MIT", 15 | "require": { 16 | "php": "^8.2", 17 | "pestphp/pest": "^3.0|^4.0", 18 | "pestphp/pest-plugin": "^3.0|^4.0" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "JonPurvis\\Lawman\\": "src/", 23 | "Tests\\Fixtures\\Arch\\": "tests/Fixtures/Arch" 24 | }, 25 | "files": [ 26 | "src/Autoload.php" 27 | ] 28 | }, 29 | "require-dev": { 30 | "larastan/larastan": "^2.9", 31 | "pestphp/pest-dev-tools": "^2.9", 32 | "saloonphp/cache-plugin": "^3.0", 33 | "saloonphp/pagination-plugin": "^2.0", 34 | "saloonphp/rate-limit-plugin": "^2.0", 35 | "saloonphp/saloon": "^3.6" 36 | }, 37 | "minimum-stability": "dev", 38 | "prefer-stable": true, 39 | "config": { 40 | "sort-packages": true, 41 | "preferred-install": "dist", 42 | "allow-plugins": { 43 | "pestphp/pest-plugin": true 44 | } 45 | }, 46 | "scripts": { 47 | "refacto": "rector", 48 | "lint": "pint", 49 | "test:refacto": "rector --dry-run", 50 | "test:lint": "pint --test", 51 | "test:types": "phpstan analyse --ansi", 52 | "test:unit": "pest --colors=always", 53 | "test": [ 54 | "@test:refacto", 55 | "@test:lint", 56 | "@test:types", 57 | "@test:unit" 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Expectations/Pagination.php: -------------------------------------------------------------------------------- 1 | extend( 8 | 'toUsePagedPagination', 9 | fn (): Expectation => // @phpstan-ignore-next-line 10 | $this->toImplement('Saloon\PaginationPlugin\Contracts\HasPagination') 11 | ->and((new ReflectionClass($this->value)) // @phpstan-ignore-line 12 | ->getMethod('paginate') 13 | ->getReturnType()->getName()) 14 | ->toEqual('Saloon\PaginationPlugin\PagedPaginator') 15 | ); 16 | 17 | expect()->extend( 18 | 'toUseOffsetPagination', 19 | fn (): Expectation => // @phpstan-ignore-next-line 20 | $this->toImplement('Saloon\PaginationPlugin\Contracts\HasPagination') 21 | ->and((new ReflectionClass($this->value)) // @phpstan-ignore-line 22 | ->getMethod('paginate') 23 | ->getReturnType()->getName()) 24 | ->toEqual('Saloon\PaginationPlugin\OffsetPaginator') 25 | ); 26 | 27 | expect()->extend( 28 | 'toUseCursorPagination', 29 | fn (): Expectation => // @phpstan-ignore-next-line 30 | $this->toImplement('Saloon\PaginationPlugin\Contracts\HasPagination') 31 | ->and((new ReflectionClass($this->value)) // @phpstan-ignore-line 32 | ->getMethod('paginate') 33 | ->getReturnType()->getName()) 34 | ->toEqual('Saloon\PaginationPlugin\CursorPaginator') 35 | ); 36 | 37 | expect()->extend( 38 | 'toUseCustomPagination', 39 | fn (): Expectation => // @phpstan-ignore-next-line 40 | $this->toImplement('Saloon\PaginationPlugin\Contracts\HasPagination') 41 | ->and((new ReflectionClass($this->value)) // @phpstan-ignore-line 42 | ->getMethod('paginate') 43 | ->getReturnType()->getName()) 44 | ->toEqual('Saloon\PaginationPlugin\Paginator') 45 | ); 46 | 47 | expect()->extend( 48 | 'toUseRequestPagination', 49 | fn (): Expectation => // @phpstan-ignore-next-line 50 | $this->toImplement('Saloon\PaginationPlugin\Contracts\HasRequestPagination') 51 | ->and((new ReflectionClass($this->value)) // @phpstan-ignore-line 52 | ->getMethod('paginate') 53 | ->getReturnType()->getName()) 54 | ->toEqual('Saloon\PaginationPlugin\Paginator') 55 | ); 56 | -------------------------------------------------------------------------------- /src/Expectations/Properties.php: -------------------------------------------------------------------------------- 1 | extend( 8 | 'toSetConnectTimeout', 9 | fn (int $connectTimeout = 10): Expectation => // @phpstan-ignore-next-line 10 | expect(property_exists($this->value, 'connectTimeout'))->toBeTrue() 11 | ->and((new ReflectionClass($this->value)) // @phpstan-ignore-line 12 | ->getProperty('connectTimeout') 13 | ->getValue((new ReflectionClass($this->value))->newInstanceWithoutConstructor())) // @phpstan-ignore-line 14 | ->toEqual($connectTimeout) 15 | ); 16 | 17 | expect()->extend( 18 | 'toSetRequestTimeout', 19 | fn (int $requestTimeout = 30): Expectation => // @phpstan-ignore-next-line 20 | expect(property_exists($this->value, 'requestTimeout'))->toBeTrue() 21 | ->and((new ReflectionClass($this->value)) // @phpstan-ignore-line 22 | ->getProperty('requestTimeout') 23 | ->getValue((new ReflectionClass($this->value))->newInstanceWithoutConstructor())) // @phpstan-ignore-line 24 | ->toEqual($requestTimeout) 25 | ); 26 | 27 | expect()->extend( 28 | 'toBeTriedAgainOnFailure', 29 | fn (int $tries = 3): Expectation => // @phpstan-ignore-next-line 30 | expect(property_exists($this->value, 'tries'))->toBeTrue() 31 | ->and((new ReflectionClass($this->value)) // @phpstan-ignore-line 32 | ->getProperty('tries') 33 | ->getValue((new ReflectionClass($this->value))->newInstanceWithoutConstructor())) // @phpstan-ignore-line 34 | ->toEqual($tries) 35 | ); 36 | 37 | expect()->extend( 38 | 'toHaveRetryInterval', 39 | fn (int $retryInterval = 500): Expectation => // @phpstan-ignore-next-line 40 | expect(property_exists($this->value, 'retryInterval'))->toBeTrue() 41 | ->and((new ReflectionClass($this->value)) // @phpstan-ignore-line 42 | ->getProperty('retryInterval') 43 | ->getValue((new ReflectionClass($this->value))->newInstanceWithoutConstructor())) // @phpstan-ignore-line 44 | ->toEqual($retryInterval) 45 | ); 46 | 47 | expect()->extend( 48 | 'toUseExponentialBackoff', 49 | fn (): Expectation => // @phpstan-ignore-next-line 50 | expect(property_exists($this->value, 'useExponentialBackoff'))->toBeTrue() 51 | ->and((new ReflectionClass($this->value)) // @phpstan-ignore-line 52 | ->getProperty('useExponentialBackoff') 53 | ->getValue((new ReflectionClass($this->value))->newInstanceWithoutConstructor())) // @phpstan-ignore-line 54 | ->toBeTrue() 55 | ); 56 | 57 | expect()->extend( 58 | 'toThrowOnMaxTries', 59 | fn (): Expectation => // @phpstan-ignore-next-line 60 | expect(property_exists($this->value, 'throwOnMaxTries'))->toBeTrue() 61 | ->and((new ReflectionClass($this->value)) // @phpstan-ignore-line 62 | ->getProperty('throwOnMaxTries') 63 | ->getValue((new ReflectionClass($this->value))->newInstanceWithoutConstructor())) // @phpstan-ignore-line 64 | ->toBeTrue() 65 | ); 66 | -------------------------------------------------------------------------------- /src/Expectations/Request.php: -------------------------------------------------------------------------------- 1 | newInstanceWithoutConstructor(); 13 | $methodProperty = $class->getProperty('method'); 14 | 15 | return $methodProperty->getValue($newInstance)->name; 16 | } 17 | 18 | expect()->extend( 19 | 'toBeSaloonRequest', 20 | fn (): ArchExpectation => // @phpstan-ignore-next-line 21 | $this->toExtend('Saloon\Http\Request') 22 | ); 23 | 24 | expect()->extend( 25 | 'toHaveRequestMethod', 26 | fn (): Expectation => // @phpstan-ignore-next-line 27 | expect(property_exists($this->value, 'method'))->toBeTrue() 28 | ); 29 | 30 | expect()->extend( 31 | 'toSendGetRequest', 32 | fn (): Expectation => // @phpstan-ignore-next-line 33 | expect(getRequestType($this->value)) 34 | ->toEqual(Method::GET->name) 35 | ); 36 | 37 | expect()->extend( 38 | 'toSendPostRequest', 39 | fn (): Expectation => // @phpstan-ignore-next-line 40 | expect(getRequestType($this->value)) 41 | ->toEqual(Method::POST->name) 42 | ); 43 | 44 | expect()->extend( 45 | 'toSendHeadRequest', 46 | fn (): Expectation => // @phpstan-ignore-next-line 47 | expect(getRequestType($this->value)) 48 | ->toEqual(Method::HEAD->name) 49 | ); 50 | 51 | expect()->extend( 52 | 'toSendPutRequest', 53 | fn (): Expectation => // @phpstan-ignore-next-line 54 | expect(getRequestType($this->value)) 55 | ->toEqual(Method::PUT->name) 56 | ); 57 | 58 | expect()->extend( 59 | 'toSendPatchRequest', 60 | fn (): Expectation => // @phpstan-ignore-next-line 61 | expect(getRequestType($this->value)) 62 | ->toEqual(Method::PATCH->name) 63 | ); 64 | 65 | expect()->extend( 66 | 'toSendDeleteRequest', 67 | fn (): Expectation => // @phpstan-ignore-next-line 68 | expect(getRequestType($this->value)) 69 | ->toEqual(Method::DELETE->name) 70 | ); 71 | 72 | expect()->extend( 73 | 'toSendOptionsRequest', 74 | fn (): Expectation => // @phpstan-ignore-next-line 75 | expect(getRequestType($this->value)) 76 | ->toEqual(Method::OPTIONS->name) 77 | ); 78 | 79 | expect()->extend( 80 | 'toSendConnectRequest', 81 | fn (): Expectation => // @phpstan-ignore-next-line 82 | expect(getRequestType($this->value)) 83 | ->toEqual(Method::CONNECT->name) 84 | ); 85 | 86 | expect()->extend( 87 | 'toSendTraceRequest', 88 | fn (): Expectation => // @phpstan-ignore-next-line 89 | expect(getRequestType($this->value)) 90 | ->toEqual(Method::TRACE->name) 91 | ); 92 | 93 | expect()->extend( 94 | 'toHaveJsonBody', 95 | fn (): ArchExpectation => // @phpstan-ignore-next-line 96 | $this->toImplement('Saloon\Contracts\Body\HasBody') 97 | ->toUse('Saloon\Traits\Body\HasJsonBody') 98 | ); 99 | 100 | expect()->extend( 101 | 'toHaveMultipartBody', 102 | fn (): ArchExpectation => // @phpstan-ignore-next-line 103 | $this->toImplement('Saloon\Contracts\Body\HasBody') 104 | ->toUse('Saloon\Traits\Body\HasMultipartBody') 105 | ); 106 | 107 | expect()->extend( 108 | 'toHaveXmlBody', 109 | fn (): ArchExpectation => // @phpstan-ignore-next-line 110 | $this->toImplement('Saloon\Contracts\Body\HasBody') 111 | ->toUse('Saloon\Traits\Body\HasXmlBody') 112 | ); 113 | 114 | expect()->extend( 115 | 'toHaveFormBody', 116 | fn (): ArchExpectation => // @phpstan-ignore-next-line 117 | $this->toImplement('Saloon\Contracts\Body\HasBody') 118 | ->toUse('Saloon\Traits\Body\HasFormBody') 119 | ); 120 | 121 | expect()->extend( 122 | 'toHaveStringBody', 123 | fn (): ArchExpectation => // @phpstan-ignore-next-line 124 | $this->toImplement('Saloon\Contracts\Body\HasBody') 125 | ->toUse('Saloon\Traits\Body\HasStringBody') 126 | ); 127 | 128 | expect()->extend( 129 | 'toHaveStreamBody', 130 | fn (): ArchExpectation => // @phpstan-ignore-next-line 131 | $this->toImplement('Saloon\Contracts\Body\HasBody') 132 | ->toUse('Saloon\Traits\Body\HasStreamBody') 133 | ); 134 | 135 | expect()->extend( 136 | 'toHaveDefaultQuery', 137 | fn (): ArchExpectation => // @phpstan-ignore-next-line 138 | $this->toHaveMethod('defaultQuery') 139 | ); 140 | 141 | expect()->extend( 142 | 'toHaveDefaultBody', 143 | fn (): ArchExpectation => // @phpstan-ignore-next-line 144 | $this->toHaveMethod('defaultBody') 145 | ); 146 | --------------------------------------------------------------------------------