From 72a22181162630ab4344f4cee6918ffe81f35742 Mon Sep 17 00:00:00 2001 From: Art4 Date: Thu, 7 Mar 2024 08:37:44 +0100 Subject: [PATCH 1/4] Add Client::listOwnNotices() --- src/Client.php | 19 ++ tests/Unit/Client/ListOwnNoticesTest.php | 273 +++++++++++++++++++++++ 2 files changed, 292 insertions(+) create mode 100644 tests/Unit/Client/ListOwnNoticesTest.php diff --git a/src/Client.php b/src/Client.php index 47e8ff5..97cc720 100644 --- a/src/Client.php +++ b/src/Client.php @@ -90,6 +90,25 @@ public function listCharges(): array return $this->parseJsonResponseToArray($response); } + /** + * List all notices for the authorized user using the endpoint `GET /api/notices` + * + * @link https://www.weg.li/api-docs/index.html#operations-notice-get_notices + * + * @throws \Psr\Http\Client\ClientExceptionInterface If an error happens while processing the request. + * @throws UnexpectedResponseException If an error happens while processing the response. + * + * @return mixed[] + */ + public function listOwnNotices(): array + { + $response = $this->sendJsonRequest('GET', '/api/notices'); + + $this->ensureJsonResponse($response, 200); + + return $this->parseJsonResponseToArray($response); + } + /** * @throws \Psr\Http\Client\ClientExceptionInterface If an error happens while processing the request. */ diff --git a/tests/Unit/Client/ListOwnNoticesTest.php b/tests/Unit/Client/ListOwnNoticesTest.php new file mode 100644 index 0000000..88f5e83 --- /dev/null +++ b/tests/Unit/Client/ListOwnNoticesTest.php @@ -0,0 +1,273 @@ + '7a473465c3cbbc6c90781b5e798966ce', + 'status' => 'shared', + 'street' => 'Musterstraße 123', + 'city' => 'Berlin', + 'zip' => '12305', + 'latitude' => 52.5170365, + 'longitude' => 13.3888599, + 'registration' => 'EX AM 713', + 'color' => 'white', + 'brand' => 'Car brand', + 'charge' => [ + 'tbnr' => '141312', + 'description' => 'Sie parkten im absoluten Haltverbot (Zeichen 283).', + 'fine' => '25.0', + 'bkat' => '§ 41 Abs. 1 iVm Anlage 2, § 49 StVO; § 24 Abs. 1, 3 Nr. 5 StVG; 52 BKat', + 'penalty' => null, + 'fap' => null, + 'points' => 0, + 'valid_from' => '2021-11-09T00:00:00.000+01:00', + 'valid_to' => null, + 'implementation' => null, + 'classification' => 5, + 'variant_table_id' => 741017, + 'rule_id' => 39, + 'table_id' => null, + 'required_refinements' => '00000000000000000000000000000000', + 'number_required_refinements' => 0, + 'max_fine' => '0.0', + 'created_at' => '2023-09-18T15:30:43.312+02:00', + 'updated_at' => '2023-09-18T15:30:43.312+02:00', + ], + 'tbnr' => '141312', + 'start_date' => '2023-11-12T11:31:00.000+01:00', + 'end_date' => '2023-11-12T11:36:00.000+01:00', + 'note' => 'Some user notes', + 'photos' => [ + [ + 'filename' => 'IMG_20231124_113156.jpg', + 'url' => 'https://example.com/storage/IMG_20231124_113156.jpg', + ], + ], + 'created_at' => '2023-11-12T11:33:29.423+01:00', + 'updated_at' => '2023-11-12T11:49:24.383+01:00', + 'sent_at' => '2023-11-12T11:49:24.378+01:00', + 'vehicle_empty' => true, + 'hazard_lights' => false, + 'expired_tuv' => false, + 'expired_eco' => false, + 'over_2_8_tons' => false, + ], + ]; + + $request = $this->createMock(RequestInterface::class); + $request->expects($this->exactly(1))->method('withHeader')->willReturn($request); + + $requestFactory = $this->createMock(RequestFactoryInterface::class); + $requestFactory->expects($this->exactly(1))->method('createRequest')->with('GET', 'https://www.weg.li/api/notices')->willReturn($request); + + $stream = $this->createConfiguredMock( + StreamInterface::class, + [ + '__toString' => json_encode($expected), + ], + ); + + $response = $this->createConfiguredMock( + ResponseInterface::class, + [ + 'getStatusCode' => 200, + 'getHeaderLine' => 'application/json', + 'getBody' => $stream, + ] + ); + + $httpClient = $this->createMock(ClientInterface::class); + $httpClient->expects($this->exactly(1))->method('sendRequest')->willReturn($response); + + $client = Client::create( + $httpClient, + $requestFactory, + ); + + $response = $client->listOwnNotices(); + + $this->assertSame( + $expected, + $response, + ); + } + + public function testListOwnNoticesThrowsClientException(): void + { + $request = $this->createMock(RequestInterface::class); + $request->expects($this->exactly(1))->method('withHeader')->willReturn($request); + + $requestFactory = $this->createMock(RequestFactoryInterface::class); + $requestFactory->expects($this->exactly(1))->method('createRequest')->with('GET', 'https://www.weg.li/api/notices')->willReturn($request); + + $httpClient = $this->createMock(ClientInterface::class); + $httpClient->expects($this->exactly(1))->method('sendRequest')->willThrowException( + $this->createMock(ClientExceptionInterface::class), + ); + + $client = Client::create( + $httpClient, + $requestFactory, + ); + + $this->expectException(ClientExceptionInterface::class); + $this->expectExceptionMessage(''); + + $client->listOwnNotices(); + } + + public function testListOwnNoticesThrowsUnexpectedResponseExceptionOnWrongStatusCode(): void + { + $request = $this->createMock(RequestInterface::class); + $request->expects($this->exactly(1))->method('withHeader')->willReturn($request); + + $requestFactory = $this->createMock(RequestFactoryInterface::class); + $requestFactory->expects($this->exactly(1))->method('createRequest')->with('GET', 'https://www.weg.li/api/notices')->willReturn($request); + + $response = $this->createConfiguredMock( + ResponseInterface::class, + [ + 'getStatusCode' => 500, + ] + ); + + $httpClient = $this->createMock(ClientInterface::class); + $httpClient->expects($this->exactly(1))->method('sendRequest')->willReturn($response); + + $client = Client::create( + $httpClient, + $requestFactory, + ); + + $this->expectException(UnexpectedResponseException::class); + $this->expectExceptionMessage('Server replied with status code 500'); + + $client->listOwnNotices(); + } + + public function testListOwnNoticesThrowsUnexpectedResponseExceptionOnWrongContentTypeHeader(): void + { + $request = $this->createMock(RequestInterface::class); + $request->expects($this->exactly(1))->method('withHeader')->willReturn($request); + + $requestFactory = $this->createMock(RequestFactoryInterface::class); + $requestFactory->expects($this->exactly(1))->method('createRequest')->with('GET', 'https://www.weg.li/api/notices')->willReturn($request); + + $response = $this->createConfiguredMock( + ResponseInterface::class, + [ + 'getStatusCode' => 200, + 'getHeaderLine' => 'text/html', + ] + ); + + $httpClient = $this->createMock(ClientInterface::class); + $httpClient->expects($this->exactly(1))->method('sendRequest')->willReturn($response); + + $client = Client::create( + $httpClient, + $requestFactory, + ); + + $this->expectException(UnexpectedResponseException::class); + $this->expectExceptionMessage('Server replied not with JSON content.'); + + $client->listOwnNotices(); + } + + public function testListOwnNoticesThrowsUnexpectedResponseExceptionOnInvalidJsonBody(): void + { + $request = $this->createMock(RequestInterface::class); + $request->expects($this->exactly(1))->method('withHeader')->willReturn($request); + + $requestFactory = $this->createMock(RequestFactoryInterface::class); + $requestFactory->expects($this->exactly(1))->method('createRequest')->with('GET', 'https://www.weg.li/api/notices')->willReturn($request); + + $stream = $this->createConfiguredMock( + StreamInterface::class, + [ + '__toString' => 'invalid json', + ], + ); + + $response = $this->createConfiguredMock( + ResponseInterface::class, + [ + 'getStatusCode' => 200, + 'getHeaderLine' => 'application/json', + 'getBody' => $stream, + ] + ); + + $httpClient = $this->createMock(ClientInterface::class); + $httpClient->expects($this->exactly(1))->method('sendRequest')->willReturn($response); + + $client = Client::create( + $httpClient, + $requestFactory, + ); + + $this->expectException(UnexpectedResponseException::class); + $this->expectExceptionMessage('Response body contains no valid JSON: invalid json'); + + $client->listOwnNotices(); + } + + public function testListOwnNoticesThrowsUnexpectedResponseExceptionOnJsonBodyWithoutArray(): void + { + $request = $this->createMock(RequestInterface::class); + $request->expects($this->exactly(1))->method('withHeader')->willReturn($request); + + $requestFactory = $this->createMock(RequestFactoryInterface::class); + $requestFactory->expects($this->exactly(1))->method('createRequest')->with('GET', 'https://www.weg.li/api/notices')->willReturn($request); + + $stream = $this->createConfiguredMock( + StreamInterface::class, + [ + '__toString' => '"this is not an array"', + ], + ); + + $response = $this->createConfiguredMock( + ResponseInterface::class, + [ + 'getStatusCode' => 200, + 'getHeaderLine' => 'application/json', + 'getBody' => $stream, + ] + ); + + $httpClient = $this->createMock(ClientInterface::class); + $httpClient->expects($this->exactly(1))->method('sendRequest')->willReturn($response); + + $client = Client::create( + $httpClient, + $requestFactory, + ); + + $this->expectException(UnexpectedResponseException::class); + $this->expectExceptionMessage('Response JSON does not contain an array: "this is not an array"'); + + $client->listOwnNotices(); + } +} From c001abd8c0bca6f57f90a21550ca3e7c0d4e9f33 Mon Sep 17 00:00:00 2001 From: Art4 Date: Thu, 7 Mar 2024 08:48:09 +0100 Subject: [PATCH 2/4] Refactor Client --- src/Client.php | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/src/Client.php b/src/Client.php index 97cc720..c0b8a5c 100644 --- a/src/Client.php +++ b/src/Client.php @@ -47,9 +47,7 @@ public function listDistricts(): array { $response = $this->sendJsonRequest('GET', '/districts.json'); - $this->ensureJsonResponse($response, 200); - - return $this->parseJsonResponseToArray($response); + return $this->parseJsonResponseToArray($response, 200); } /** @@ -66,9 +64,7 @@ public function getDistrictByZip(string $zip): array { $response = $this->sendJsonRequest('GET', '/districts/' . $zip . '.json'); - $this->ensureJsonResponse($response, 200); - - return $this->parseJsonResponseToArray($response); + return $this->parseJsonResponseToArray($response, 200); } /** @@ -85,9 +81,7 @@ public function listCharges(): array { $response = $this->sendJsonRequest('GET', '/charges.json'); - $this->ensureJsonResponse($response, 200); - - return $this->parseJsonResponseToArray($response); + return $this->parseJsonResponseToArray($response, 200); } /** @@ -104,9 +98,7 @@ public function listOwnNotices(): array { $response = $this->sendJsonRequest('GET', '/api/notices'); - $this->ensureJsonResponse($response, 200); - - return $this->parseJsonResponseToArray($response); + return $this->parseJsonResponseToArray($response, 200); } /** @@ -127,12 +119,14 @@ private function sendJsonRequest( } /** - * @throws UnexpectedResponseException If the response has the wrong status code or content type header. + * @throws UnexpectedResponseException If the response has the wrong status code. + * @throws UnexpectedResponseException If the response has the wrong content type header. + * @throws UnexpectedResponseException If an error happens while parsing the JSON response. + * + * @return mixed[] */ - private function ensureJsonResponse( - ResponseInterface $response, - int $expectedStatusCode, - ): void { + private function parseJsonResponseToArray(ResponseInterface $response, int $expectedStatusCode): array + { if ($response->getStatusCode() !== $expectedStatusCode) { throw UnexpectedResponseException::create('Server replied with status code ' . $response->getStatusCode(), $response); } @@ -140,15 +134,7 @@ private function ensureJsonResponse( if (! str_starts_with($response->getHeaderLine('content-type'), 'application/json')) { throw UnexpectedResponseException::create('Server replied not with JSON content.', $response); } - } - /** - * @throws UnexpectedResponseException If an error happens while parsing the JSON response. - * - * @return mixed[] - */ - private function parseJsonResponseToArray(ResponseInterface $response): array - { $responseBody = $response->getBody()->__toString(); try { From 8c902d2dfbd6f11b05a26267f0d354f893f6e72e Mon Sep 17 00:00:00 2001 From: Art4 Date: Thu, 7 Mar 2024 08:58:17 +0100 Subject: [PATCH 3/4] Improve error message on wrong status code --- src/Client.php | 9 ++++++++- tests/Unit/Client/GetDistrictByZipTest.php | 2 +- tests/Unit/Client/ListChargesTest.php | 2 +- tests/Unit/Client/ListDistrictsTest.php | 2 +- tests/Unit/Client/ListOwnNoticesTest.php | 2 +- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Client.php b/src/Client.php index c0b8a5c..3980390 100644 --- a/src/Client.php +++ b/src/Client.php @@ -128,7 +128,14 @@ private function sendJsonRequest( private function parseJsonResponseToArray(ResponseInterface $response, int $expectedStatusCode): array { if ($response->getStatusCode() !== $expectedStatusCode) { - throw UnexpectedResponseException::create('Server replied with status code ' . $response->getStatusCode(), $response); + throw UnexpectedResponseException::create( + sprintf( + 'Server replied with the status code %d, but %d was expected.', + $response->getStatusCode(), + $expectedStatusCode, + ), + $response, + ); } if (! str_starts_with($response->getHeaderLine('content-type'), 'application/json')) { diff --git a/tests/Unit/Client/GetDistrictByZipTest.php b/tests/Unit/Client/GetDistrictByZipTest.php index a24e314..150767d 100644 --- a/tests/Unit/Client/GetDistrictByZipTest.php +++ b/tests/Unit/Client/GetDistrictByZipTest.php @@ -121,7 +121,7 @@ public function testGetDistrictByZipThrowsUnexpectedResponseExceptionOnWrongStat ); $this->expectException(UnexpectedResponseException::class); - $this->expectExceptionMessage('Server replied with status code 500'); + $this->expectExceptionMessage('Server replied with the status code 500, but 200 was expected.'); $client->getDistrictByZip('00000'); } diff --git a/tests/Unit/Client/ListChargesTest.php b/tests/Unit/Client/ListChargesTest.php index 84325c7..8247f53 100644 --- a/tests/Unit/Client/ListChargesTest.php +++ b/tests/Unit/Client/ListChargesTest.php @@ -193,7 +193,7 @@ public function testListChargesThrowsUnexpectedResponseExceptionOnWrongStatusCod ); $this->expectException(UnexpectedResponseException::class); - $this->expectExceptionMessage('Server replied with status code 500'); + $this->expectExceptionMessage('Server replied with the status code 500, but 200 was expected.'); $client->listCharges(); } diff --git a/tests/Unit/Client/ListDistrictsTest.php b/tests/Unit/Client/ListDistrictsTest.php index dbdfac3..66a774f 100644 --- a/tests/Unit/Client/ListDistrictsTest.php +++ b/tests/Unit/Client/ListDistrictsTest.php @@ -151,7 +151,7 @@ public function testListDistrictsThrowsUnexpectedResponseExceptionOnWrongStatusC ); $this->expectException(UnexpectedResponseException::class); - $this->expectExceptionMessage('Server replied with status code 500'); + $this->expectExceptionMessage('Server replied with the status code 500, but 200 was expected.'); $client->listDistricts(); } diff --git a/tests/Unit/Client/ListOwnNoticesTest.php b/tests/Unit/Client/ListOwnNoticesTest.php index 88f5e83..7ca53c1 100644 --- a/tests/Unit/Client/ListOwnNoticesTest.php +++ b/tests/Unit/Client/ListOwnNoticesTest.php @@ -160,7 +160,7 @@ public function testListOwnNoticesThrowsUnexpectedResponseExceptionOnWrongStatus ); $this->expectException(UnexpectedResponseException::class); - $this->expectExceptionMessage('Server replied with status code 500'); + $this->expectExceptionMessage('Server replied with the status code 500, but 200 was expected.'); $client->listOwnNotices(); } From a9ea96e1b0a2b98c56c7ab6dd48dbdd7408db73a Mon Sep 17 00:00:00 2001 From: Art4 Date: Thu, 7 Mar 2024 09:09:32 +0100 Subject: [PATCH 4/4] Update README.md and CHANGELOG.md --- CHANGELOG.md | 1 + README.md | 63 ++++++++++++++++++++++++ tests/Unit/Client/ListOwnNoticesTest.php | 6 +-- 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5efbe54..953212f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - New method `Art4\Wegliphant\Client::authenticate()` to set your API key for authorized API requests. +- New method `Art4\Wegliphant\Client::listOwnNotices()` to list all notices for the authorized user. - New class `Art4\Wegliphant\Exception\UnexpectedResponseException` that will be thrown if an error happens while processing the response. ### Changed diff --git a/README.md b/README.md index 909bb1c..7f94be2 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,69 @@ You can find your API key [here](https://www.weg.li/user/edit). $client->authenticate($apiKey); ``` +### List all own notices + +```php +$notices = $client->listOwnNotices(); + +// $notices contains: +[ + [...], + [ + 'token' => '8843d7f92416211de9ebb963ff4ce281', + 'status' => 'shared', + 'street' => 'Musterstraße 123', + 'city' => 'Berlin', + 'zip' => '12305', + 'latitude' => 52.5170365, + 'longitude' => 13.3888599, + 'registration' => 'EX AM 713', + 'color' => 'white', + 'brand' => 'Car brand', + 'charge' => [ + 'tbnr' => '141312', + 'description' => 'Sie parkten im absoluten Haltverbot (Zeichen 283).', + 'fine' => '25.0', + 'bkat' => '§ 41 Abs. 1 iVm Anlage 2, § 49 StVO; § 24 Abs. 1, 3 Nr. 5 StVG; 52 BKat', + 'penalty' => null, + 'fap' => null, + 'points' => 0, + 'valid_from' => '2021-11-09T00:00:00.000+01:00', + 'valid_to' => null, + 'implementation' => null, + 'classification' => 5, + 'variant_table_id' => 741017, + 'rule_id' => 39, + 'table_id' => null, + 'required_refinements' => '00000000000000000000000000000000', + 'number_required_refinements' => 0, + 'max_fine' => '0.0', + 'created_at' => '2023-09-18T15:30:43.312+02:00', + 'updated_at' => '2023-09-18T15:30:43.312+02:00', + ], + 'tbnr' => '141312', + 'start_date' => '2023-11-12T11:31:00.000+01:00', + 'end_date' => '2023-11-12T11:36:00.000+01:00', + 'note' => 'Some user notes', + 'photos' => [ + [ + 'filename' => 'IMG_20231124_113156.jpg', + 'url' => 'https://example.com/storage/IMG_20231124_113156.jpg', + ], + ], + 'created_at' => '2023-11-12T11:33:29.423+01:00', + 'updated_at' => '2023-11-12T11:49:24.383+01:00', + 'sent_at' => '2023-11-12T11:49:24.378+01:00', + 'vehicle_empty' => true, + 'hazard_lights' => false, + 'expired_tuv' => false, + 'expired_eco' => false, + 'over_2_8_tons' => false, + ], + [...], +], +``` + ### List all districts ```php diff --git a/tests/Unit/Client/ListOwnNoticesTest.php b/tests/Unit/Client/ListOwnNoticesTest.php index 7ca53c1..03580ed 100644 --- a/tests/Unit/Client/ListOwnNoticesTest.php +++ b/tests/Unit/Client/ListOwnNoticesTest.php @@ -22,7 +22,7 @@ public function testListOwnNoticesReturnsArray(): void { $expected = [ [ - 'token' => '7a473465c3cbbc6c90781b5e798966ce', + 'token' => '8843d7f92416211de9ebb963ff4ce281', 'status' => 'shared', 'street' => 'Musterstraße 123', 'city' => 'Berlin', @@ -59,8 +59,8 @@ public function testListOwnNoticesReturnsArray(): void 'note' => 'Some user notes', 'photos' => [ [ - 'filename' => 'IMG_20231124_113156.jpg', - 'url' => 'https://example.com/storage/IMG_20231124_113156.jpg', + 'filename' => 'IMG_20231112_113156.jpg', + 'url' => 'https://example.com/storage/IMG_20231112_113156.jpg', ], ], 'created_at' => '2023-11-12T11:33:29.423+01:00',