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/ 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 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 fb38675..587abd6 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": { @@ -39,6 +39,6 @@ }, "type": "library", "require-dev": { - "phpunit/phpunit": "^9" + "phpunit/phpunit": "^9.0 | ^10.0" } } 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); + } +} diff --git a/tests/Unit/ServerTest.php b/tests/Unit/ServerTest.php new file mode 100644 index 0000000..e5043ab --- /dev/null +++ b/tests/Unit/ServerTest.php @@ -0,0 +1,200 @@ +=') + && 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/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