diff --git a/.config/phpunit.xml.dist b/.config/phpunit.xml.dist index 9beadd4..e9bfed1 100644 --- a/.config/phpunit.xml.dist +++ b/.config/phpunit.xml.dist @@ -1,13 +1,14 @@ - - ../tests/ + + ../tests/unit/ - + ../src/ diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index b871f2c..599a76f 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -75,7 +75,7 @@ jobs: matrix: php: - '8.3' # from 2023-11 to 2025-12 (2027-12) - - '8.4' + - '8.4' # from 2024-11 to 2026-12 (2028-12) steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 @@ -117,7 +117,7 @@ jobs: matrix: php: - '8.3' # from 2023-11 to 2025-12 (2027-12) - - '8.4' + - '8.4' # from 2024-11 to 2026-12 (2028-12) steps: - uses: actions/checkout@v4 - uses: docker://pipelinecomponents/php-codesniffer 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 diff --git a/composer.json b/composer.json index 9bec59d..d982101 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,8 @@ "pdsinterop/flysystem-rdf": "^0.7", "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" }, "require-dev": { "ext-xdebug": "*", diff --git a/tests/unit/ExceptionTest.php b/tests/unit/ExceptionTest.php new file mode 100644 index 0000000..fb7406b --- /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); + } +} diff --git a/tests/unit/ServerTest.php b/tests/unit/ServerTest.php index 9bed1b4..553e50e 100644 --- a/tests/unit/ServerTest.php +++ b/tests/unit/ServerTest.php @@ -30,15 +30,37 @@ class ServerTest extends TestCase ////////////////////////////////// FIXTURES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ const MOCK_BODY = 'php://temp'; - const MOCK_PATH = '/path/to/resource/'; + const MOCK_HTTP_METHOD = 'MOCK'; + const MOCK_PATH = '/mock/path/'; const MOCK_SERVER_PARAMS = []; const MOCK_UPLOADED_FILES = []; const MOCK_URL = 'https://example.com' . self::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(['expectException(ArgumentCountError::class); $this->expectExceptionMessageMatches('/Too few arguments .+ 0 passed/'); @@ -73,7 +95,7 @@ public function testInstatiationWithoutResponse() } /** @testdox Server should be instantiated when constructed without Graph */ - public function testInstatiationWithoutGraph() + public function testServerInstatiationWithoutGraph() { $mockFileSystem = $this->getMockBuilder(FilesystemOperator::class)->getMock(); $mockAdapter = $this->getMockBuilder(FilesystemAdapter::class)->getMock(); @@ -86,7 +108,7 @@ public function testInstatiationWithoutGraph() } /** @testdox Server should be instantiated when constructed with Graph */ - public function testInstatiationWithGraph() + public function testServerInstatiationWithGraph() { $mockFileSystem = $this->getMockBuilder(FilesystemOperator::class)->getMock(); $mockAdapter = $this->getMockBuilder(FilesystemAdapter::class)->getMock(); @@ -104,7 +126,7 @@ public function testInstatiationWithGraph() * * @covers ::respondToRequest */ - public function testRespondToRequestWithoutRequest() + public function testServerRespondToRequestWithoutRequest() { // Arrange $mockFileSystem = $this->getMockBuilder(FilesystemOperator::class)->getMock(); @@ -119,6 +141,7 @@ public function testRespondToRequestWithoutRequest() $this->expectExceptionMessageMatches('/Too few arguments .+ 0 passed/'); // Act + $server = new Server($mockFileSystem, $mockResponse); $server->respondToRequest(); } @@ -143,12 +166,32 @@ public function testRespondToRequestWithUnsupportedHttpMethod($httpMethod) // Assert $this->expectException(Exception::class); - $this->expectExceptionMessage('Unknown or unsupported HTTP METHOD'); + $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 * @@ -229,11 +272,36 @@ public static function provideUnsupportedHttpMethods() return [ 'string:CONNECT' => ['CONNECT'], 'string:TRACE' => ['TRACE'], - 'string:UNKNOWN' => ['UNKNOWN'], + '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(