From 8da9b5b0a5f049e2339b94fb91808cffcdc6e9bf Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Mon, 5 May 2025 12:48:32 +0200 Subject: [PATCH 1/9] Change PHPUnit config to use `build/` directory for temporary files. --- .config/phpunit.xml.dist | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/phpunit.xml.dist b/.config/phpunit.xml.dist index 9beadd4..676dd2a 100644 --- a/.config/phpunit.xml.dist +++ b/.config/phpunit.xml.dist @@ -7,7 +7,7 @@ beStrictAboutOutputDuringTests="true" beStrictAboutTodoAnnotatedTests="true" bootstrap="../vendor/autoload.php" - cacheResultFile="../.phpunit.cache/test-results" + cacheResultFile="../build/.phpunit.cache/test-results" convertDeprecationsToExceptions="true" failOnRisky="true" failOnWarning="true" @@ -20,7 +20,7 @@ - + ../src/ From f1ed93314d38d05099f94adcdb40575afd109102 Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Mon, 5 May 2025 12:49:17 +0200 Subject: [PATCH 2/9] Delete dummy test. --- tests/dummyTest.php | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 tests/dummyTest.php diff --git a/tests/dummyTest.php b/tests/dummyTest.php deleted file mode 100644 index 4f94796..0000000 --- a/tests/dummyTest.php +++ /dev/null @@ -1,20 +0,0 @@ -assertTrue(true); - } -} \ No newline at end of file From 49218be41f8ebf9b26056c8da2a18e3cd46548e8 Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Mon, 5 May 2025 12:49:34 +0200 Subject: [PATCH 3/9] Add unit-tests for Exception class. --- tests/Unit/ExceptionTest.php | 105 +++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 tests/Unit/ExceptionTest.php diff --git a/tests/Unit/ExceptionTest.php b/tests/Unit/ExceptionTest.php new file mode 100644 index 0000000..d1a525b --- /dev/null +++ b/tests/Unit/ExceptionTest.php @@ -0,0 +1,105 @@ +expectException(TypeError::class); + $this->expectExceptionMessageMatches('/Too few arguments .+ 0 passed/'); + + Exception::create(); + } + + /** + * @testdox Exception should complain when called without a context + */ + public function testCreateWithoutContext(): void + { + $this->expectException(TypeError::class); + $this->expectExceptionMessageMatches('/Too few arguments .+ 1 passed/'); + + Exception::create(self::MOCK_MESSAGE); + } + + /** + * @testdox Exception should complain when called with invalid message + */ + public function testCreateWithInvalidMessage(): void + { + $this->expectException(TypeError::class); + $this->expectExceptionMessageMatches('/Argument #1 .+ must be of type string/'); + + Exception::create(null, self::MOCK_CONTEXT); + } + + /** + * @testdox Exception should complain when called with invalid context + */ + public function testCreateWithInvalidContext(): void + { + $this->expectException(TypeError::class); + $this->expectExceptionMessageMatches('/Argument #2 .+ must be of type array/'); + + Exception::create(self::MOCK_MESSAGE, null); + } + + /** + * @testdox Exception should complain when given context does not match provided message format + */ + public function testCreateWithIncorrectContext(): void + { + $this->expectException(\ValueError::class); + $this->expectExceptionMessageMatches('/The arguments array must contain 1 items?, 0 given/'); + + Exception::create(self::MOCK_MESSAGE, []); + } + + /** + * @testdox Exception should be created when called with valid message and context + */ + public function testCreateWithMessageAndContext(): void + { + $expected = Exception::class; + $actual = Exception::create(self::MOCK_MESSAGE, self::MOCK_CONTEXT); + + $this->assertInstanceOf($expected, $actual); + } + + /** + * @testdox Created Exception should have the correct message when called with a message and context + */ + public function testCreateFormatsErrorMessage(): void + { + $expected = 'Error: Test'; + $actual = Exception::create(self::MOCK_MESSAGE, self::MOCK_CONTEXT)->getMessage(); + + $this->assertSame($expected, $actual); + } + + /** + * @testdox Exception should be created when called with a message, context and previous exception + */ + public function testCreateSetsPreviousException(): void + { + $expected = new \Exception('Previous exception'); + $actual = Exception::create(self::MOCK_MESSAGE, self::MOCK_CONTEXT, $expected)->getPrevious(); + + $this->assertSame($expected, $actual); + } +} From 06b33759d3d61493c44c2a85008af1f4b5dc6eb6 Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Mon, 5 May 2025 16:10:30 +0200 Subject: [PATCH 4/9] Add first draft unit-tests for Server class. --- tests/Unit/ServerTest.php | 179 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 tests/Unit/ServerTest.php diff --git a/tests/Unit/ServerTest.php b/tests/Unit/ServerTest.php new file mode 100644 index 0000000..1e6c0d8 --- /dev/null +++ b/tests/Unit/ServerTest.php @@ -0,0 +1,179 @@ +expectException(TypeError::class); + $this->expectExceptionMessageMatches('/Too few arguments .+ 0 passed/'); + + new Server(); + } + + /** + * @testdox Server should complain when instantiated without a response + * @covers ::__construct + */ + public function testServerConstructWithoutResponse() + { + $this->expectException(TypeError::class); + $this->expectExceptionMessageMatches('/Too few arguments .+ 1 passed/'); + + $mockFilesystem = $this->createMock(FilesystemInterface::class); + + new Server($mockFilesystem); + } + + /** + * @testdox Server should be instantiated when given a filesystem and a response + * @covers ::__construct + */ + public function testServerConstructWithFilesystemAndResponse() + { + $mockFilesystem = $this->createMock(FilesystemInterface::class); + $mockResponse = $this->createMock(ResponseInterface::class); + + $server = new Server($mockFilesystem, $mockResponse); + + $this->assertInstanceOf(Server::class, $server); + } + + /** + * @testdox Server should complain when asked to RespondToRequest without a request + * @covers ::respondToRequest + */ + public function testServerRespondToRequestWithoutRequest() + { + $this->expectException(TypeError::class); + $this->expectExceptionMessageMatches('/Too few arguments .+ 0 passed/'); + + $mockFilesystem = $this->createMock(FilesystemInterface::class); + $mockResponse = $this->createMock(ResponseInterface::class); + + $server = new Server($mockFilesystem, $mockResponse); + + $server->respondToRequest(); + } + + /** + * @testdox Server should complain when asked to RespondToRequest with a request with an unknown HTTP method + * + * @covers ::respondToRequest + * + * @uses \Pdsinterop\Solid\Resources\Exception + */ + public function testServerRespondToRequestWithUnknownHttpMethod() + { + // Assert + $this->expectException(Exception::class); + $this->expectExceptionMessage(vsprintf(Server::ERROR_UNKNOWN_HTTP_METHOD, [self::MOCK_HTTP_METHOD])); + + // Arrange + $mockRequest = $this->createMockRequest(); + $mockResponse = $this->createMock(ResponseInterface::class); + $mockFilesystem = $this->createMockFilesystem(); + + //Act + $server = new Server($mockFilesystem, $mockResponse); + $server->respondToRequest($mockRequest); + } + + /** + * @testdox Server should return provided response when asked to RespondToRequest with va lid request + * + * @covers ::respondToRequest + */ + public function testServerRespondToRequestWithRequest() + { + // Arrange + $mockFilesystem = $this->createMockFilesystem(); + $mockRequest = $this->createMockRequest('GET'); + $expected = $this->createMockResponse(); + + // Act + $server = new Server($mockFilesystem, $expected); + $actual = $server->respondToRequest($mockRequest); + + // Assert + $this->assertSame($expected, $actual); + } + + ////////////////////////////// MOCKS AND STUBS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\ + + public function createMockFilesystem(): FilesystemInterface|MockObject + { + $mockFilesystem = $this->getMockBuilder(FilesystemInterface::class) + ->onlyMethods([ + 'addPlugin', 'copy', 'createDir', 'delete', 'deleteDir', 'get', 'getMetadata', 'getMimetype', 'getSize', 'getTimestamp', 'getVisibility', 'has', 'listContents', 'put', 'putStream', 'read', 'readAndDelete', 'readStream', 'rename', 'setVisibility', 'update', 'updateStream', 'write', 'writeStream' + ]) + ->addMethods(['asMime']) + ->getMock(); + + $mockAsMime = $this->getMockBuilder(AsMime::class) + // ->onlyMethods(['getMimetype', 'getSize', 'getTimestamp']) + ->addMethods(['has']) + ->disableOriginalConstructor() + ->getMock(); + + $mockFilesystem->method('asMime')->willReturn($mockAsMime); + + return $mockFilesystem; + } + + public function createMockRequest($httpMethod = self::MOCK_HTTP_METHOD): ServerRequestInterface|MockObject + { + $mockRequest = $this->createMock(ServerRequestInterface::class); + + $mockUri = $this->createMock(UriInterface::class); + $mockUri->method('getPath')->willReturn(self::MOCK_PATH); + + $mockBody = $this->createMock(StreamInterface::class); + + $mockRequest->method('getUri')->willReturn($mockUri); + $mockRequest->method('getQueryParams')->willReturn([]); + $mockRequest->method('getMethod')->willReturn($httpMethod); + $mockRequest->method('getBody')->willReturn($mockBody); + // $mockRequest->method('getMethod')->willReturn('GET'); + $mockRequest->method('getHeaderLine')->willReturn(''); + + return $mockRequest; + } + + public function createMockResponse(): ResponseInterface|MockObject + { + $mockResponse = $this->createMock(ResponseInterface::class); + $mockBody = $this->createMock(StreamInterface::class); + + $mockResponse->method('getBody')->willReturn($mockBody); + $mockResponse->method('withStatus')->willReturnSelf(); + + return $mockResponse; + } +} From 839c3738fbc04a3657762ee6238369423d1d293c Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Mon, 5 May 2025 16:11:15 +0200 Subject: [PATCH 5/9] Add `build/` directory to git ignore file. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3af7618..e11542a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # Directories to ignore +/build /vendor # Files to ignore From f004a707e7a9b160e9981d87c6ae6fe787864de7 Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Mon, 5 May 2025 16:16:23 +0200 Subject: [PATCH 6/9] Add PHP 8.4 to GitHub Action (GHA) test matrix. --- .github/workflows/php.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 33c065c..39c4a51 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -78,6 +78,7 @@ jobs: - '8.1' # from 2021-11 to 2023-11 (2025-12) - '8.2' # from 2022-12 to 2024-12 (2026-12) - '8.3' # from 2023-11 to 2025-12 (2027-12) + - '8.4' # from 2024-11 to 2026-12 (2028-12) steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 @@ -122,6 +123,7 @@ jobs: - '8.1' # from 2021-11 to 2023-11 (2025-12) - '8.2' # from 2022-12 to 2024-12 (2026-12) - '8.3' # from 2023-11 to 2025-12 (2027-12) + - '8.4' # from 2024-11 to 2026-12 (2028-12) steps: - uses: actions/checkout@v4 - uses: docker://pipelinecomponents/php-codesniffer From de099b83a4179e1ded6e6cf4f38388af89e6b897 Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Mon, 5 May 2025 16:58:18 +0200 Subject: [PATCH 7/9] Update `laminas-diactoros` from v2 to v3. --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index fb38675..48a1bee 100644 --- a/composer.json +++ b/composer.json @@ -20,13 +20,13 @@ "require": { "php": "^8.0", "ext-mbstring": "*", - "laminas/laminas-diactoros": "^2.14", + "laminas/laminas-diactoros": "^3.0", "league/flysystem": "^1.0", "mjrider/flysystem-factory": "^0.7", "pdsinterop/flysystem-rdf": "^0.5", "pietercolpaert/hardf": "^0.3", "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", + "psr/http-message": "^1.1 || ^2.0", "textalk/websocket": "^1.5" }, "scripts": { From 62783191b75f94c8914dc34119e92e0365ae572a Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Mon, 5 May 2025 17:30:52 +0200 Subject: [PATCH 8/9] Add fix in ServerTest for PHP 8.4 + PHPUnit 9 issue. --- composer.json | 2 +- tests/Unit/ServerTest.php | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 48a1bee..587abd6 100644 --- a/composer.json +++ b/composer.json @@ -39,6 +39,6 @@ }, "type": "library", "require-dev": { - "phpunit/phpunit": "^9" + "phpunit/phpunit": "^9.0 | ^10.0" } } diff --git a/tests/Unit/ServerTest.php b/tests/Unit/ServerTest.php index 1e6c0d8..e5043ab 100644 --- a/tests/Unit/ServerTest.php +++ b/tests/Unit/ServerTest.php @@ -23,6 +23,27 @@ class ServerTest extends TestCase const MOCK_HTTP_METHOD = 'MOCK'; const MOCK_PATH = '/mock/path'; + public static function setUpBeforeClass(): void + { + $phpUnitVersion = \PHPUnit\Runner\Version::id(); + + /* PHP 8.4.0 and PHPUnit 9 triggers a Deprecation Warning, which PHPUnit + * promotes to an Exception, which causes tests to fail.This is fixed in + * PHPUnit v10. As a workaround for v9, instead of loading the real + * interface, a fixed interface is loaded on the fly. + */ + if ( + version_compare(PHP_VERSION, '8.4.0', '>=') + && version_compare($phpUnitVersion, '9.0.0', '>=') + && version_compare($phpUnitVersion, '10.0.0', '<') + ) { + $file = __DIR__ . '/../../vendor/league/flysystem/src/FilesystemInterface.php'; + $contents = file_get_contents($file); + $contents = str_replace([' Date: Sat, 21 Mar 2026 20:09:25 +0100 Subject: [PATCH 9/9] Restore unit-tests. --- tests/Unit/ServerTest.php | 200 --------------------------- tests/unit/ServerTest.php | 277 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 277 insertions(+), 200 deletions(-) delete mode 100644 tests/Unit/ServerTest.php create mode 100644 tests/unit/ServerTest.php diff --git a/tests/Unit/ServerTest.php b/tests/Unit/ServerTest.php deleted file mode 100644 index e5043ab..0000000 --- a/tests/Unit/ServerTest.php +++ /dev/null @@ -1,200 +0,0 @@ -=') - && version_compare($phpUnitVersion, '9.0.0', '>=') - && version_compare($phpUnitVersion, '10.0.0', '<') - ) { - $file = __DIR__ . '/../../vendor/league/flysystem/src/FilesystemInterface.php'; - $contents = file_get_contents($file); - $contents = str_replace(['expectException(TypeError::class); - $this->expectExceptionMessageMatches('/Too few arguments .+ 0 passed/'); - - new Server(); - } - - /** - * @testdox Server should complain when instantiated without a response - * @covers ::__construct - */ - public function testServerConstructWithoutResponse() - { - $this->expectException(TypeError::class); - $this->expectExceptionMessageMatches('/Too few arguments .+ 1 passed/'); - - $mockFilesystem = $this->createMock(FilesystemInterface::class); - - new Server($mockFilesystem); - } - - /** - * @testdox Server should be instantiated when given a filesystem and a response - * @covers ::__construct - */ - public function testServerConstructWithFilesystemAndResponse() - { - $mockFilesystem = $this->createMock(FilesystemInterface::class); - $mockResponse = $this->createMock(ResponseInterface::class); - - $server = new Server($mockFilesystem, $mockResponse); - - $this->assertInstanceOf(Server::class, $server); - } - - /** - * @testdox Server should complain when asked to RespondToRequest without a request - * @covers ::respondToRequest - */ - public function testServerRespondToRequestWithoutRequest() - { - $this->expectException(TypeError::class); - $this->expectExceptionMessageMatches('/Too few arguments .+ 0 passed/'); - - $mockFilesystem = $this->createMock(FilesystemInterface::class); - $mockResponse = $this->createMock(ResponseInterface::class); - - $server = new Server($mockFilesystem, $mockResponse); - - $server->respondToRequest(); - } - - /** - * @testdox Server should complain when asked to RespondToRequest with a request with an unknown HTTP method - * - * @covers ::respondToRequest - * - * @uses \Pdsinterop\Solid\Resources\Exception - */ - public function testServerRespondToRequestWithUnknownHttpMethod() - { - // Assert - $this->expectException(Exception::class); - $this->expectExceptionMessage(vsprintf(Server::ERROR_UNKNOWN_HTTP_METHOD, [self::MOCK_HTTP_METHOD])); - - // Arrange - $mockRequest = $this->createMockRequest(); - $mockResponse = $this->createMock(ResponseInterface::class); - $mockFilesystem = $this->createMockFilesystem(); - - //Act - $server = new Server($mockFilesystem, $mockResponse); - $server->respondToRequest($mockRequest); - } - - /** - * @testdox Server should return provided response when asked to RespondToRequest with va lid request - * - * @covers ::respondToRequest - */ - public function testServerRespondToRequestWithRequest() - { - // Arrange - $mockFilesystem = $this->createMockFilesystem(); - $mockRequest = $this->createMockRequest('GET'); - $expected = $this->createMockResponse(); - - // Act - $server = new Server($mockFilesystem, $expected); - $actual = $server->respondToRequest($mockRequest); - - // Assert - $this->assertSame($expected, $actual); - } - - ////////////////////////////// MOCKS AND STUBS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\ - - public function createMockFilesystem(): FilesystemInterface|MockObject - { - $mockFilesystem = $this->getMockBuilder(FilesystemInterface::class) - ->onlyMethods([ - 'addPlugin', 'copy', 'createDir', 'delete', 'deleteDir', 'get', 'getMetadata', 'getMimetype', 'getSize', 'getTimestamp', 'getVisibility', 'has', 'listContents', 'put', 'putStream', 'read', 'readAndDelete', 'readStream', 'rename', 'setVisibility', 'update', 'updateStream', 'write', 'writeStream' - ]) - ->addMethods(['asMime']) - ->getMock(); - - $mockAsMime = $this->getMockBuilder(AsMime::class) - // ->onlyMethods(['getMimetype', 'getSize', 'getTimestamp']) - ->addMethods(['has']) - ->disableOriginalConstructor() - ->getMock(); - - $mockFilesystem->method('asMime')->willReturn($mockAsMime); - - return $mockFilesystem; - } - - public function createMockRequest($httpMethod = self::MOCK_HTTP_METHOD): ServerRequestInterface|MockObject - { - $mockRequest = $this->createMock(ServerRequestInterface::class); - - $mockUri = $this->createMock(UriInterface::class); - $mockUri->method('getPath')->willReturn(self::MOCK_PATH); - - $mockBody = $this->createMock(StreamInterface::class); - - $mockRequest->method('getUri')->willReturn($mockUri); - $mockRequest->method('getQueryParams')->willReturn([]); - $mockRequest->method('getMethod')->willReturn($httpMethod); - $mockRequest->method('getBody')->willReturn($mockBody); - // $mockRequest->method('getMethod')->willReturn('GET'); - $mockRequest->method('getHeaderLine')->willReturn(''); - - return $mockRequest; - } - - public function createMockResponse(): ResponseInterface|MockObject - { - $mockResponse = $this->createMock(ResponseInterface::class); - $mockBody = $this->createMock(StreamInterface::class); - - $mockResponse->method('getBody')->willReturn($mockBody); - $mockResponse->method('withStatus')->willReturnSelf(); - - return $mockResponse; - } -} diff --git a/tests/unit/ServerTest.php b/tests/unit/ServerTest.php new file mode 100644 index 0000000..7df8351 --- /dev/null +++ b/tests/unit/ServerTest.php @@ -0,0 +1,277 @@ +=') + && version_compare($phpUnitVersion, '9.0.0', '>=') + && version_compare($phpUnitVersion, '10.0.0', '<') + ) { + $file = __DIR__ . '/../../vendor/league/flysystem/src/FilesystemInterface.php'; + $contents = file_get_contents($file); + $contents = str_replace(['expectException(ArgumentCountError::class); + $this->expectExceptionMessageMatches('/Too few arguments .+ 0 passed/'); + + new Server(); + } + + /** @testdox Server should complain when instantiated without Response */ + public function testServerInstatiationWithoutResponse() + { + $this->expectException(ArgumentCountError::class); + $this->expectExceptionMessageMatches('/Too few arguments .+ 1 passed/'); + + $mockFileSystem = $this->getMockBuilder(FilesystemInterface::class)->getMock(); + + new Server($mockFileSystem); + } + + /** @testdox Server should be instantiated when constructed without Graph */ + public function testServerInstatiationWithoutGraph() + { + $mockFileSystem = $this->getMockBuilder(FilesystemInterface::class)->getMock(); + $mockResponse = $this->getMockBuilder(ResponseInterface::class)->getMock(); + + $actual = new Server($mockFileSystem, $mockResponse); + $expected = Server::class; + + $this->assertInstanceOf($expected, $actual); + } + + /** @testdox Server should be instantiated when constructed with Graph */ + public function testServerInstatiationWithGraph() + { + $mockFileSystem = $this->getMockBuilder(FilesystemInterface::class)->getMock(); + $mockResponse = $this->getMockBuilder(ResponseInterface::class)->getMock(); + $mockGraph = $this->getMockBuilder(Graph::class)->getMock(); + + $actual = new Server($mockFileSystem, $mockResponse, $mockGraph); + $expected = Server::class; + + $this->assertInstanceOf($expected, $actual); + } + + /** + * @testdox Server should complain when asked to respond to a request without a Request + * + * @covers ::respondToRequest + */ + public function testServerRespondToRequestWithoutRequest() + { + // Arrange + $mockFileSystem = $this->getMockBuilder(FilesystemInterface::class)->getMock(); + $mockResponse = $this->getMockBuilder(ResponseInterface::class)->getMock(); + + // Assert + $this->expectException(ArgumentCountError::class); + $this->expectExceptionMessageMatches('/Too few arguments .+ 0 passed/'); + + // Act + $server = new Server($mockFileSystem, $mockResponse); + $server->respondToRequest(); + } + + /** + * @testdox Server should complain when asked to respond to a Request with an unsupported HTTP METHOD + * + * @covers ::respondToRequest + * + * @dataProvider provideUnsupportedHttpMethods + */ + public function testRespondToRequestWithUnsupportedHttpMethod($httpMethod) + { + // Arrange + $mockFileSystem = $this->getMockBuilder(FilesystemInterface::class)->getMock(); + $mockResponse = new Response(); + $request = $this->createRequest($httpMethod); + + // Assert + $this->expectException(Exception::class); + $this->expectExceptionMessage(vsprintf(Server::ERROR_UNKNOWN_HTTP_METHOD, [$httpMethod])); + + // Act + $server = new Server($mockFileSystem, $mockResponse); + $server->respondToRequest($request); + } + + /** + * @testdox Server should return response when asked to RespondToRequest with valid request + * + * @covers ::respondToRequest + */ + public function testServerRespondToRequestWithRequest() + { + // Arrange + $mockFileSystem = $this->createMockFileSystem(); + $request = $this->createRequest('GET'); + + // Act + $server = new Server($mockFileSystem, new Response()); + $response = $server->respondToRequest($request); + + // Assert + $this->assertEquals(200, $response->getStatusCode()); + } + + /** + * @testdox Server should create a resource when asked to create a resource with Slug header present + * + * @covers ::respondToRequest + * + * @dataProvider provideSlugs + */ + public function testRespondToPOSTCreateRequest($slug, $mimetype, $expected) + { + // Arrange + $mockFileSystem = $this->getMockBuilder(FilesystemInterface::class)->getMock(); + $mockGraph = $this->getMockBuilder(Graph::class)->getMock(); + $request = $this->createRequest('POST', [ + 'Content-Type' => $mimetype, + 'Link' => '', + 'Slug' => $slug, + ]); + + $mockFileSystem + ->method('has') + ->withAnyParameters() + ->willReturnMap([ + [self::MOCK_PATH, true], + ]); + + $mockFileSystem + ->method('getMimetype') + ->with(self::MOCK_PATH) + ->willReturn(Server::MIME_TYPE_DIRECTORY); + + $mockFileSystem + ->method('write') + ->withAnyParameters() + ->willReturn(true); + + // Act + $server = new Server($mockFileSystem, new Response(), $mockGraph); + $response = $server->respondToRequest($request); + + // Assert + $actual = $response->getHeaderLine('Location'); + + $this->assertEquals(self::MOCK_PATH . $expected, $actual); + } + + /////////////////////////////// DATAPROVIDERS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ + + public static function provideSlugs() + { + return [ + // '' => [$slug, $mimetype, $expectedFilename], + 'Slug with json extension, with ld+json MIME' => ['Mock Slug.json', 'application/ld+json', 'Mock Slug.json'], + 'Slug with jsonld extension, with ld+json MIME)' => ['Mock Slug.jsonld', 'application/ld+json', 'Mock Slug.jsonld.json'], + 'Slug with PNG extension, with PNG MIME' => ['Mock Slug.png', 'image/png', 'Mock Slug.png'], + 'Slug with some other, extension) with Turtle MIME' => ['Mock Slug.other', 'text/turtle', 'Mock Slug.other.ttl'], + 'Slug with Turtle extension, with other MIME' => ['Mock Slug.ttl', 'some/other', 'Mock Slug.ttl'], + 'Slug with Turtle extension, with Turtle MIME' => ['Mock Slug.ttl', 'text/turtle', 'Mock Slug.ttl'], + 'Slug without extension), with some other MIME' => ['Mock Slug', 'some/other', 'Mock Slug'], + 'Slug without extension), with turtle MIME' => ['Mock Slug', 'text/turtle', 'Mock Slug.ttl'], + ]; + } + + public static function provideUnsupportedHttpMethods() + { + return [ + 'string:CONNECT' => ['CONNECT'], + 'string:TRACE' => ['TRACE'], + 'string:UNKNOWN' => [self::MOCK_HTTP_METHOD], + ]; + } + + ////////////////////////////// MOCKS AND STUBS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\ + + public function createMockFileSystem(): FilesystemInterface|MockObject + { + $mockFileSystem = $this->getMockBuilder(FilesystemInterface::class) + ->onlyMethods([ + 'addPlugin', 'copy', 'createDir', 'delete', 'deleteDir', 'get', 'getMetadata', 'getMimetype', 'getSize', 'getTimestamp', 'getVisibility', 'has', 'listContents', 'put', 'putStream', 'read', 'readAndDelete', 'readStream', 'rename', 'setVisibility', 'update', 'updateStream', 'write', 'writeStream' + ]) + ->addMethods(['asMime']) + ->getMock(); + + $mockAsMime = $this->getMockBuilder(AsMime::class) + // ->onlyMethods(['getMimetype', 'getSize', 'getTimestamp']) + ->addMethods(['has', 'getMimetype', 'read']) + ->disableOriginalConstructor() + ->getMock(); + + $mockAsMime->method('getMimetype')->willReturn('text/turtle'); + $mockAsMime->method('has')->willReturn(true); + $mockAsMime->method('read')->willReturn(''); + + $mockFileSystem->method('asMime')->willReturn($mockAsMime); + + return $mockFileSystem; + } + + private function createRequest(string $httpMethod, array $headers = []): ServerRequestInterface + { + return new ServerRequest( + self::MOCK_SERVER_PARAMS, + self::MOCK_UPLOADED_FILES, + self::MOCK_URL, + $httpMethod, + self::MOCK_BODY, + $headers + ); + } +}