diff --git a/.github/workflows/functionaltests.yml b/.github/workflows/functionaltests.yml index 776610a..9937562 100644 --- a/.github/workflows/functionaltests.yml +++ b/.github/workflows/functionaltests.yml @@ -10,12 +10,10 @@ jobs: strategy: fail-fast: false matrix: - php-version: [ 7.4 ] - flow-version: [ 5.3 ] + php-version: [ 8.1, 8.2 ] + flow-version: [ 8.3 ] mysql-version: [5.7] - exclude: [] - env: APP_ENV: true FLOW_CONTEXT: Testing/Functional diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml index 2bbc35f..ff99b5d 100644 --- a/.github/workflows/unittests.yml +++ b/.github/workflows/unittests.yml @@ -10,10 +10,8 @@ jobs: strategy: fail-fast: false matrix: - php-version: [ 7.4 ] - flow-version: [ 5.3 ] - - exclude: [] + php-version: [ 8.1, 8.2 ] + flow-version: [ 8.3 ] env: FLOW_CONTEXT: Testing/Unit diff --git a/Classes/Domain/Model/ResourceProxy.php b/Classes/Domain/Model/ResourceProxy.php index dcc58ee..e920d7f 100644 --- a/Classes/Domain/Model/ResourceProxy.php +++ b/Classes/Domain/Model/ResourceProxy.php @@ -10,8 +10,8 @@ * source code. */ +use GuzzleHttp\Psr7\Uri; use Neos\Flow\Annotations as Flow; -use Neos\Flow\Http\Uri; use Netlogix\JsonApiOrg\Consumer\Service\ConsumerBackendInterface; class ResourceProxy implements \ArrayAccess diff --git a/Classes/Domain/Model/ResourceProxyIterator.php b/Classes/Domain/Model/ResourceProxyIterator.php index 58a1d8d..0ee3a25 100644 --- a/Classes/Domain/Model/ResourceProxyIterator.php +++ b/Classes/Domain/Model/ResourceProxyIterator.php @@ -5,8 +5,8 @@ use Neos\Cache\Frontend\VariableFrontend; use Neos\Flow\Cache\CacheManager; -use Neos\Flow\Http\Uri; use Neos\Flow\Utility\Now; +use Psr\Http\Message\UriInterface; class ResourceProxyIterator implements \IteratorAggregate, \Countable { @@ -45,7 +45,7 @@ protected function __construct(string $uri = '') $this->uri = $uri; } - public static function fromUri(Uri $uri): self + public static function fromUri(UriInterface $uri): self { return new static((string)$uri); } diff --git a/Classes/Domain/Model/Type.php b/Classes/Domain/Model/Type.php index a8ef1b7..04d0283 100644 --- a/Classes/Domain/Model/Type.php +++ b/Classes/Domain/Model/Type.php @@ -11,8 +11,8 @@ */ use Neos\Flow\Annotations as Flow; -use Neos\Flow\Http\Uri; use Neos\Flow\ObjectManagement\ObjectManagerInterface; +use Psr\Http\Message\UriInterface; class Type { @@ -57,7 +57,7 @@ class Type protected $properties = []; /** - * @var Uri + * @var UriInterface */ protected $uri; @@ -76,14 +76,14 @@ class Type * @param string $typeName * @param string $resourceClassName * @param array $properties - * @param Uri $uri + * @param UriInterface $uri * @param array $defaultIncludes */ public function __construct( $typeName, $resourceClassName = ResourceProxy::class, array $properties = [], - Uri $uri = null, + UriInterface $uri = null, array $defaultIncludes = [] ) { $this->typeName = (string)$typeName; @@ -95,19 +95,12 @@ public function __construct( $this->defaultIncludes = $defaultIncludes; } - /** - * @return Uri - */ - public function getUri() + public function getUri(): UriInterface { return $this->uri; } - /** - * @param Uri $uri - * @return void - */ - public function setUri(Uri $uri) + public function setUri(UriInterface $uri): void { $this->uri = $uri; } diff --git a/Classes/Guzzle/Middleware/EndpointCacheMiddleware.php b/Classes/Guzzle/Middleware/EndpointCacheMiddleware.php index 4b7ade6..9ae7ca0 100644 --- a/Classes/Guzzle/Middleware/EndpointCacheMiddleware.php +++ b/Classes/Guzzle/Middleware/EndpointCacheMiddleware.php @@ -31,7 +31,7 @@ public function __invoke(callable $handler) return $handler($request, $options); } - $requestPath = $request->getUri()->getPath() ?? ''; + $requestPath = $request->getUri()->getPath() ?: ''; if (!self::str_ends_with(rtrim($requestPath, '/'), '/.well-known/endpoint-discovery')) { return $handler($request, $options); } diff --git a/Classes/Service/ConsumerBackend.php b/Classes/Service/ConsumerBackend.php index cb3adfc..c411386 100644 --- a/Classes/Service/ConsumerBackend.php +++ b/Classes/Service/ConsumerBackend.php @@ -11,17 +11,18 @@ */ use GuzzleHttp\Client; +use GuzzleHttp\Psr7\Uri; use Neos\Cache\Exception\InvalidDataException; use Neos\Cache\Frontend\StringFrontend; use Neos\Flow\Annotations as Flow; use Neos\Flow\Http\Helper\UriHelper; -use Neos\Flow\Http\Uri; use Netlogix\JsonApiOrg\Consumer\Domain\Model\Arguments\PageInterface; use Netlogix\JsonApiOrg\Consumer\Domain\Model\Arguments\SortInterface; use Netlogix\JsonApiOrg\Consumer\Domain\Model\ResourceProxy; use Netlogix\JsonApiOrg\Consumer\Domain\Model\ResourceProxyIterator; use Netlogix\JsonApiOrg\Consumer\Domain\Model\Type; use Netlogix\JsonApiOrg\Consumer\Guzzle\ClientProvider; +use Psr\Http\Message\UriInterface; /** * @Flow\Scope("singleton") @@ -83,11 +84,11 @@ public function getType($typeName) } /** - * @param Uri $endpointDiscovery + * @param UriInterface $endpointDiscovery * @throws InvalidDataException * @throws \Neos\Cache\Exception */ - public function registerEndpointsByEndpointDiscovery(Uri $endpointDiscovery) + public function registerEndpointsByEndpointDiscovery(UriInterface $endpointDiscovery) { $result = $this->requestJson($endpointDiscovery); foreach ($result['links'] as $link) { @@ -180,12 +181,12 @@ public function getQueryUriForFindByTypeAndFilter( } /** - * @param Uri $queryUri + * @param UriInterface $queryUri * @return ResourceProxyIterator * @throws InvalidDataException * @throws \Neos\Cache\Exception */ - public function fetchFromUri(Uri $queryUri) + public function fetchFromUri(UriInterface $queryUri) { $resourceProxy = ResourceProxyIterator::fromUri($queryUri); $resourceProxy = $resourceProxy->loadFromCache(); diff --git a/Classes/Service/ConsumerBackendInterface.php b/Classes/Service/ConsumerBackendInterface.php index 98a21e7..57a7c40 100644 --- a/Classes/Service/ConsumerBackendInterface.php +++ b/Classes/Service/ConsumerBackendInterface.php @@ -10,12 +10,12 @@ * source code. */ -use Neos\Flow\Http\Uri; use Netlogix\JsonApiOrg\Consumer\Domain\Model\Arguments\PageInterface; use Netlogix\JsonApiOrg\Consumer\Domain\Model\Arguments\SortInterface; use Netlogix\JsonApiOrg\Consumer\Domain\Model\ResourceProxy; use Netlogix\JsonApiOrg\Consumer\Domain\Model\ResourceProxyIterator; use Netlogix\JsonApiOrg\Consumer\Domain\Model\Type; +use Psr\Http\Message\UriInterface; interface ConsumerBackendInterface { @@ -31,9 +31,9 @@ public function addType(Type $type); public function getType($typeName); /** - * @param Uri $endpointDiscovery + * @param UriInterface $endpointDiscovery */ - public function registerEndpointsByEndpointDiscovery(Uri $endpointDiscovery); + public function registerEndpointsByEndpointDiscovery(UriInterface $endpointDiscovery); /** * @param string $type @@ -46,10 +46,10 @@ public function registerEndpointsByEndpointDiscovery(Uri $endpointDiscovery); public function findByTypeAndFilter($type, $filter = [], $include = [], PageInterface $page = null, SortInterface $sort = null); /** - * @param Uri $queryUri + * @param UriInterface $queryUri * @return ResourceProxyIterator */ - public function fetchFromUri(Uri $queryUri); + public function fetchFromUri(UriInterface $queryUri); /** * @param mixed $type diff --git a/Tests/Common/Guzzle/NoResponseQueued.php b/Tests/Common/Guzzle/NoResponseQueued.php new file mode 100644 index 0000000..3da333f --- /dev/null +++ b/Tests/Common/Guzzle/NoResponseQueued.php @@ -0,0 +1,9 @@ +withQuery('') ->withFragment(''); @@ -72,7 +72,7 @@ public function createClient(): Client ); } - throw new \RuntimeException(sprintf('No Response queued for URI "%s"', $uri), 1624304664); + throw new NoResponseQueued(sprintf('No Response queued for URI "%s"', $uri), 1624304664); }; $handlerStack = HandlerStack::create($requestHandler); diff --git a/Tests/Functional/CachingTest.php b/Tests/Functional/CachingTest.php index 9db67f2..4d31035 100644 --- a/Tests/Functional/CachingTest.php +++ b/Tests/Functional/CachingTest.php @@ -3,10 +3,13 @@ namespace Netlogix\JsonApiOrg\Consumer\Tests\Functional; +use GuzzleHttp\Psr7\Uri; +use Neos\Cache\Backend\FileBackendEntryDto; use Neos\Cache\Backend\WithSetupInterface; use Neos\Cache\Frontend\FrontendInterface; use Neos\Flow\Cache\CacheManager; use Netlogix\JsonApiOrg\Consumer\Domain\Model as JsonApi; +use Netlogix\JsonApiOrg\Consumer\Tests\Common\Guzzle\NoResponseQueued; class CachingTest extends FunctionalTestCase { @@ -22,6 +25,7 @@ public function setUp(): void if ($cache->getBackend() instanceof WithSetupInterface) { $cache->getBackend()->setup(); } + $cache->flush(); } /** @@ -30,7 +34,7 @@ public function setUp(): void */ public function JSON_data_can_be_saved_to_cache(array $jsonApiFixture) { - $uri = $this->asDataUri([]); + $uri = new Uri('https://some-url.example/foo'); JsonApi\ResourceProxyIterator::fromUri($uri) ->withJsonResult($jsonApiFixture) @@ -50,19 +54,30 @@ public function JSON_data_can_be_saved_to_cache(array $jsonApiFixture) */ public function Cached_data_respects_cache_lifetime(array $jsonApiFixture) { - $uri = $this->asDataUri([]); + $uri = new Uri('https://some-url.example/foo'); JsonApi\ResourceProxyIterator::fromUri($uri) ->withJsonResult($jsonApiFixture) ->saveToCache(1); + /** + * @see FileBackendEntryDto::isExpired() + */ + $previousTime = $_SERVER['REQUEST_TIME']; + $_SERVER['REQUEST_TIME'] = time() + 2; sleep(2); - $result = $this->consumerBackend - ->fetchFromUri($uri) - ->getArrayCopy(); + self::expectException(NoResponseQueued::class); - $this->assertCount(0, $result); + try { + $this->consumerBackend + ->fetchFromUri($uri) + ->getArrayCopy(); + } catch (\Throwable $t) { + throw $t; + } finally { + $_SERVER['REQUEST_TIME'] = $previousTime; + } } /** @@ -71,7 +86,7 @@ public function Cached_data_respects_cache_lifetime(array $jsonApiFixture) */ public function Cached_data_will_not_be_written_to_cache_again_with_same_lifetime_and_tags(array $jsonApiFixture) { - $uri = $this->asDataUri([]); + $uri = new Uri('https://some-url.example/foo'); $tags = ['foo', 'bar', 'baz']; JsonApi\ResourceProxyIterator::fromUri($uri) @@ -101,7 +116,7 @@ public function Cached_data_will_not_be_written_to_cache_again_with_same_lifetim */ public function Cached_data_will_not_be_written_to_cache_again_with_same_lifetime_and_differently_ordered_tags(array $jsonApiFixture) { - $uri = $this->asDataUri([]); + $uri = new Uri('https://some-url.example/foo'); $tags = ['foo', 'bar', 'baz']; JsonApi\ResourceProxyIterator::fromUri($uri) @@ -131,7 +146,7 @@ public function Cached_data_will_not_be_written_to_cache_again_with_same_lifetim */ public function Cached_data_will_be_written_to_cache_again_if_lifetime_differs(array $jsonApiFixture) { - $uri = $this->asDataUri([]); + $uri = new Uri('https://some-url.example/foo'); $tags = ['foo', 'bar', 'baz']; JsonApi\ResourceProxyIterator::fromUri($uri) @@ -161,7 +176,7 @@ public function Cached_data_will_be_written_to_cache_again_if_lifetime_differs(a */ public function Cached_data_will_be_written_to_cache_again_if_tags_differs(array $jsonApiFixture) { - $uri = $this->asDataUri([]); + $uri = new Uri('https://some-url.example/foo'); $tags = ['foo', 'bar', 'baz']; JsonApi\ResourceProxyIterator::fromUri($uri) diff --git a/Tests/Functional/FunctionalTestCase.php b/Tests/Functional/FunctionalTestCase.php index 42fe3fc..a586119 100644 --- a/Tests/Functional/FunctionalTestCase.php +++ b/Tests/Functional/FunctionalTestCase.php @@ -3,10 +3,11 @@ namespace Netlogix\JsonApiOrg\Consumer\Tests\Functional; -use Neos\Flow\Http\Uri; +use GuzzleHttp\Psr7\Uri; use Neos\Flow\Tests\FunctionalTestCase as BaseTestCase; use Netlogix\JsonApiOrg\Consumer\Domain\Model as JsonApi; use Netlogix\JsonApiOrg\Consumer\Service\ConsumerBackend; +use Psr\Http\Message\UriInterface; class FunctionalTestCase extends BaseTestCase { @@ -49,13 +50,13 @@ public function createEmptyResource(): JsonApi\ResourceProxy $this->type->__consumerBackend = $this->consumerBackend; } - public function asDataUri(array $fixture): Uri + public function asDataUri(array $fixture): UriInterface { $dataUri = 'data:application/json;base64,' . base64_encode(json_encode($fixture)); return new Uri($dataUri); } - public function provideJsonResponse() + public static function provideJsonResponse() { $entity = [ 'id' => 0, diff --git a/Tests/Unit/Guzzle/Middleware/ClosureLike.php b/Tests/Unit/Guzzle/Middleware/ClosureLike.php new file mode 100644 index 0000000..40740e7 --- /dev/null +++ b/Tests/Unit/Guzzle/Middleware/ClosureLike.php @@ -0,0 +1,12 @@ +createPartialMock(\stdClass::class, ['__invoke']); + $mock = $this->getMockBuilder(ClosureLike::class)->getMock(); $mock->expects($this->once()) ->method('__invoke') ->with($request, $options); @@ -59,7 +60,7 @@ public function Cache_only_endpoint_discovery() $request = new Request('GET', '/uri'); $options = []; - $mock = $this->createPartialMock(\stdClass::class, ['__invoke']); + $mock = $this->getMockBuilder(ClosureLike::class)->getMock(); $mock->expects($this->once()) ->method('__invoke') ->with($request, $options); @@ -73,14 +74,14 @@ public function Cache_only_endpoint_discovery() */ public function Empty_paths_dont_cause_exception() { - $uri = new \Neos\Flow\Http\Uri('https://foo'); + $uri = new Uri('https://foo'); $request = new Request('GET', $uri); - $this->assertNull($uri->getPath()); + $this->assertEmpty($uri->getPath()); $options = []; - $mock = $this->createPartialMock(\stdClass::class, ['__invoke']); + $mock = $this->getMockBuilder(ClosureLike::class)->getMock(); $mock->expects($this->once()) ->method('__invoke') ->with($request, $options); @@ -102,11 +103,11 @@ public function Endpoint_discovery_result_is_written_to_cache() $promise ->expects($this->once()) ->method('then') - ->will($this->returnCallback(function (\Closure $callback) use($response) { - return $callback($response); + ->will($this->returnCallback(function (\Closure $callback) use ($response) { + return new FulfilledPromise($callback($response)); })); - $mock = $this->createPartialMock(\stdClass::class, ['__invoke']); + $mock = $this->getMockBuilder(ClosureLike::class)->getMock(); $mock->expects($this->once()) ->method('__invoke') ->with($request, $options) @@ -131,7 +132,7 @@ public function Endpoint_discovery_result_is_read_from_cache() $response = $this->getResponse(); $options = []; - $mock = $this->createPartialMock(\stdClass::class, ['__invoke']); + $mock = $this->getMockBuilder(ClosureLike::class)->getMock(); $mock->expects($this->never()) ->method('__invoke'); @@ -165,11 +166,9 @@ public function Only_200_status_code_is_cached() $promise ->expects($this->once()) ->method('then') - ->will($this->returnCallback(function (\Closure $callback) use($response) { - return $callback($response); - })); + ->willReturn(new FulfilledPromise($response)); - $mock = $this->createPartialMock(\stdClass::class, ['__invoke']); + $mock = $this->getMockBuilder(ClosureLike::class)->getMock(); $mock->expects($this->once()) ->method('__invoke') ->with($request, $options) diff --git a/composer.json b/composer.json index 525e856..d2228d8 100644 --- a/composer.json +++ b/composer.json @@ -7,11 +7,11 @@ "MIT" ], "require": { - "php": "^7.4 || ^8.0 || ^8.1", + "php": "^8.1", "ext-json": "*", "guzzlehttp/guzzle": "^6.5 || ^7.0", - "guzzlehttp/promises": "^1.4", - "neos/flow": "^5.3 || ^6.3 || ^7.3" + "guzzlehttp/promises": "^1.4 || ^2.0", + "neos/flow": "^8.3" }, "autoload": { "psr-4": { @@ -28,7 +28,6 @@ "neos/composer-plugin": false } }, - "extra": { "neos": { "package-key": "Netlogix.JsonApiOrg.Consumer"