diff --git a/README.md b/README.md index 030a60f..5732c10 100644 --- a/README.md +++ b/README.md @@ -1 +1,52 @@ # ACS stations bundle + +ACS pickup point integration for Symfony. + +## Installation + +* install with Composer + +``` +composer require git@github.com:answear/acs-bundle.git +``` + +`Answear\AcsBundle\AnswearAcsBundle::class => ['all' => true],` +should be added automatically to your `config/bundles.php` file by Symfony Flex. + +## Setup + +* provide required config data: `apiKey`, `companyId`, `companyId`, `userId`, `userPassword` + +```yaml +# config/packages/answear_acs.yaml +answear_gls: + apiKey: + companyId: + companyPassword: + userId: + userPassword: + language: //default GR +``` + +## Usage + +### Get ParcelShops + +```php +/** @var \Answear\AcsBundle\Service\ParcelShopsService $parcelShopService **/ +$parcelShopService->getList(CountryIdEnum $countryId, ?int $kind = null); +``` + +will return `\Answear\AcsBundle\Response\DTO\ParcelShop[]` array. + +Where `countryId` is Greece or Cyprus, and `kind` is shop kind according to ACS documentation, `null` means all kinds + +### Error handling + +- `Answear\AcsBundle\Exception\ServiceUnavailable` for all `GuzzleException` +- `Answear\AcsBundle\Exception\MalformedResponse` for incorrect responses + +Final notes +------------ + +Feel free to open pull requests with new features, improvements or bug fixes. The Answear team will be grateful for any comments. diff --git a/composer.json b/composer.json index dd1bf38..48cd2bc 100755 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ "symfony/http-kernel": "^4.4|^5.1.5", "symfony/property-info": "^4.4|^5.0", "symfony/serializer": "^4.4|^5.0", - "webmozart/assert": "^1.3" + "webmozart/assert": "^1.10" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.4", @@ -26,7 +26,7 @@ }, "autoload": { "psr-4": { - "Answear\\AcsBundle\\\\": "src/" + "Answear\\AcsBundle\\": "src/" } }, "autoload-dev": { diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index dc53720..be5c6f5 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -4,9 +4,55 @@ namespace Answear\AcsBundle; +use Answear\AcsBundle\Request\BaseInputParameters; + class ConfigProvider { - public function __construct() + private const API_URL = 'https://webservices.acscourier.net/ACSRestServices/api/ACSAutoRest'; + + private string $apiKey; + private string $companyId; + private string $companyPassword; + private string $userId; + private string $userPassword; + private string $language; + + public function __construct( + string $apiKey, + string $companyId, + string $companyPassword, + string $userId, + string $userPassword, + string $language + ) { + $this->apiKey = $apiKey; + $this->companyId = $companyId; + $this->companyPassword = $companyPassword; + $this->userId = $userId; + $this->userPassword = $userPassword; + $this->language = $language; + } + + public function getRequestHeaders(): array + { + return [ + 'AcsApiKey' => $this->getApiKey(), + 'Content-Type' => 'application/json', + ]; + } + + public function getBaseInputParameters(): BaseInputParameters + { + return new BaseInputParameters($this->companyId, $this->companyPassword, $this->userId, $this->userPassword, $this->language); + } + + public function getUrl(): string + { + return self::API_URL; + } + + private function getApiKey(): string { + return $this->apiKey; } } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 6e2a32e..06f7477 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -9,6 +9,8 @@ class Configuration implements ConfigurationInterface { + private const DEFAULT_LANGUAGE = 'GR'; + public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('answear_acs'); @@ -20,6 +22,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->scalarNode('companyPassword')->cannotBeEmpty()->end() ->scalarNode('userId')->cannotBeEmpty()->end() ->scalarNode('userPassword')->cannotBeEmpty()->end() + ->scalarNode('language')->defaultValue(self::DEFAULT_LANGUAGE)->end() ->end(); return $treeBuilder; diff --git a/src/Enum/CountryIdEnum.php b/src/Enum/CountryIdEnum.php new file mode 100644 index 0000000..7a91233 --- /dev/null +++ b/src/Enum/CountryIdEnum.php @@ -0,0 +1,23 @@ +response = $response; + } + + public function getResponse() + { + return $this->response; + } +} diff --git a/src/Exception/ServiceUnavailableException.php b/src/Exception/ServiceUnavailable.php similarity index 56% rename from src/Exception/ServiceUnavailableException.php rename to src/Exception/ServiceUnavailable.php index 87fa989..7573118 100644 --- a/src/Exception/ServiceUnavailableException.php +++ b/src/Exception/ServiceUnavailable.php @@ -4,6 +4,6 @@ namespace Answear\AcsBundle\Exception; -class ServiceUnavailableException extends \RuntimeException +class ServiceUnavailable extends \RuntimeException { } diff --git a/src/Request/BaseInputParameters.php b/src/Request/BaseInputParameters.php new file mode 100644 index 0000000..bbbcd56 --- /dev/null +++ b/src/Request/BaseInputParameters.php @@ -0,0 +1,34 @@ +companyId = $companyId; + $this->companyPassword = $companyPassword; + $this->userId = $userId; + $this->userPassword = $userPassword; + $this->language = $language; + } + + public function toArray(): array + { + return [ + 'Company_ID' => $this->companyId, + 'Company_Password' => $this->companyPassword, + 'User_ID' => $this->userId, + 'User_Password' => $this->userPassword, + 'language' => $this->language, + ]; + } +} diff --git a/src/Request/Request.php b/src/Request/Request.php new file mode 100755 index 0000000..e221313 --- /dev/null +++ b/src/Request/Request.php @@ -0,0 +1,13 @@ +baseInputParameters = $baseInputParameters; + $this->countryId = $countryId; + $this->kind = $kind; + } + + public function toJson(): string + { + $parameters = [ + 'ACS_SHOP_COUNTRY_ID' => $this->countryId->getValue(), + ]; + + if (null !== $this->kind) { + $parameters['ACS_SHOP_KIND'] = $this->kind; + } + + return json_encode([ + 'ACSAlias' => self::ALIAS, + 'ACSInputParameters' => array_merge($parameters, $this->baseInputParameters->toArray()), + ], JSON_THROW_ON_ERROR); + } +} diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml new file mode 100755 index 0000000..94f152a --- /dev/null +++ b/src/Resources/config/services.yaml @@ -0,0 +1,9 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + Answear\AcsBundle\ConfigProvider: ~ + Answear\AcsBundle\Service\Client: ~ + Answear\AcsBundle\Service\ParcelShopsService: ~ diff --git a/src/Response/DTO/Address.php b/src/Response/DTO/Address.php new file mode 100644 index 0000000..5ae1885 --- /dev/null +++ b/src/Response/DTO/Address.php @@ -0,0 +1,82 @@ +street = $street; + $this->zipCode = $zipCode; + $this->city = $city; + $this->phones = $phones; + $this->email = $email; + $this->fax = $fax; + } + + public static function fromArray(array $parcelShopArray): self + { + Assert::keyExists($parcelShopArray, 'ACS_SHOP_ADDRESS'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_ZIPCODE'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_CITY'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_PHONES'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_EMAIL'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_FAX'); + + return new self( + $parcelShopArray['ACS_SHOP_ADDRESS'], + $parcelShopArray['ACS_SHOP_ZIPCODE'], + $parcelShopArray['ACS_SHOP_CITY'], + $parcelShopArray['ACS_SHOP_PHONES'], + $parcelShopArray['ACS_SHOP_EMAIL'], + $parcelShopArray['ACS_SHOP_FAX'] + ); + } + + public function getZipCode(): string + { + return $this->zipCode; + } + + public function getCity(): string + { + return $this->city; + } + + public function getStreet(): string + { + return $this->street; + } + + public function getFax(): ?string + { + return $this->fax; + } + + public function getPhones(): ?string + { + return $this->phones; + } + + public function getEmail(): ?string + { + return $this->email; + } +} diff --git a/src/Response/DTO/Coordinates.php b/src/Response/DTO/Coordinates.php new file mode 100644 index 0000000..9d0296f --- /dev/null +++ b/src/Response/DTO/Coordinates.php @@ -0,0 +1,30 @@ +latitude = $latitude; + $this->longitude = $longitude; + } + + public static function fromArray(array $parcelShopArray): self + { + Assert::keyExists($parcelShopArray, 'ACS_SHOP_LAT'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_LONG'); + + return new self((float) $parcelShopArray['ACS_SHOP_LAT'], (float) $parcelShopArray['ACS_SHOP_LONG']); + } +} diff --git a/src/Response/DTO/ParcelShop.php b/src/Response/DTO/ParcelShop.php new file mode 100644 index 0000000..03434ab --- /dev/null +++ b/src/Response/DTO/ParcelShop.php @@ -0,0 +1,250 @@ +countryId = $countryId; + $this->countryDescription = $countryDescription; + $this->areaId = $areaId; + $this->areaDescription = $areaDescription; + $this->stationId = $stationId; + $this->stationIdEn = $stationIdEn; + $this->stationDescription = $stationDescription; + $this->branchId = $branchId; + $this->workingHours = $workingHours; + $this->workingHoursSaturday = $workingHoursSaturday; + $this->truckPickupHours = $truckPickupHours; + $this->truckPickupHoursSaturday = $truckPickupHoursSaturday; + $this->deliveryStartHour = $deliveryStartHour; + $this->coordinatesVerified = $coordinatesVerified; + $this->kind = $kind; + $this->services = $services; + $this->paymentTypes = $paymentTypes; + $this->idCode = $idCode; + $this->smartpointKind = $smartpointKind; + $this->workingHoursJson = $workingHoursJson; + $this->message = $message; + $this->address = $address; + $this->coordinates = $coordinates; + } + + public static function fromArray(array $parcelShopArray): self + { + Assert::keyExists($parcelShopArray, 'ACS_SHOP_COUNTRY_ID'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_COUNTRY_DESCR'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_AREA_ID'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_AREA_DESCR'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_STATION_ID'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_STATION_ID_EN'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_STATION_DESCR'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_BRANCH_ID'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_WORKING_HOURS'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_WORKING_HOURS_SATURDAY'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_TRUCK_PICKUP_HOURS'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_TRUCK_PICKUP_HOURS_SATURDAY'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_DELIVERY_START_HOUR'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_COORDINATES_VERIFIED'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_KIND'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_SERVICES'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_PAYMENT_TYPES'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_ID_CODE'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_SMARTPOINT_KIND'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_WORKING_HOURS_JSON'); + Assert::keyExists($parcelShopArray, 'ACS_SHOP_MESSAGE'); + + return new self( + $parcelShopArray['ACS_SHOP_COUNTRY_ID'], + $parcelShopArray['ACS_SHOP_COUNTRY_DESCR'], + $parcelShopArray['ACS_SHOP_AREA_ID'], + $parcelShopArray['ACS_SHOP_AREA_DESCR'], + $parcelShopArray['ACS_SHOP_STATION_ID'], + $parcelShopArray['ACS_SHOP_STATION_ID_EN'], + $parcelShopArray['ACS_SHOP_STATION_DESCR'], + $parcelShopArray['ACS_SHOP_BRANCH_ID'], + $parcelShopArray['ACS_SHOP_WORKING_HOURS'], + $parcelShopArray['ACS_SHOP_WORKING_HOURS_SATURDAY'], + $parcelShopArray['ACS_SHOP_TRUCK_PICKUP_HOURS'], + $parcelShopArray['ACS_SHOP_TRUCK_PICKUP_HOURS_SATURDAY'], + $parcelShopArray['ACS_SHOP_DELIVERY_START_HOUR'], + $parcelShopArray['ACS_SHOP_COORDINATES_VERIFIED'], + $parcelShopArray['ACS_SHOP_KIND'], + $parcelShopArray['ACS_SHOP_SERVICES'], + $parcelShopArray['ACS_SHOP_PAYMENT_TYPES'], + $parcelShopArray['ACS_SHOP_ID_CODE'], + $parcelShopArray['ACS_SHOP_SMARTPOINT_KIND'], + $parcelShopArray['ACS_SHOP_WORKING_HOURS_JSON'], + $parcelShopArray['ACS_SHOP_MESSAGE'], + Address::fromArray($parcelShopArray), + Coordinates::fromArray($parcelShopArray) + ); + } + + public function getCountryId(): int + { + return $this->countryId; + } + + public function getCountryDescription(): string + { + return $this->countryDescription; + } + + public function getAreaId(): int + { + return $this->areaId; + } + + public function getAreaDescription(): string + { + return $this->areaDescription; + } + + public function getStationId(): string + { + return $this->stationId; + } + + public function getStationIdEn(): string + { + return $this->stationIdEn; + } + + public function getStationDescription(): string + { + return $this->stationDescription; + } + + public function getBranchId(): int + { + return $this->branchId; + } + + public function getWorkingHours(): string + { + return $this->workingHours; + } + + public function getWorkingHoursSaturday(): string + { + return $this->workingHoursSaturday; + } + + public function getTruckPickupHours(): string + { + return $this->truckPickupHours; + } + + public function getTruckPickupHoursSaturday(): string + { + return $this->truckPickupHoursSaturday; + } + + public function getDeliveryStartHour(): string + { + return $this->deliveryStartHour; + } + + public function getCoordinatesVerified(): int + { + return $this->coordinatesVerified; + } + + public function getKind(): int + { + return $this->kind; + } + + public function getServices(): string + { + return $this->services; + } + + public function getPaymentTypes(): string + { + return $this->paymentTypes; + } + + public function getIdCode(): string + { + return $this->idCode; + } + + public function getSmartpointKind(): string + { + return $this->smartpointKind; + } + + public function getWorkingHoursJson(): string + { + return $this->workingHoursJson; + } + + public function getMessage(): string + { + return $this->message; + } + + public function getAddress(): Address + { + return $this->address; + } + + public function getCoordinates(): Coordinates + { + return $this->coordinates; + } +} diff --git a/src/Response/StationsResponse.php b/src/Response/StationsResponse.php new file mode 100644 index 0000000..66c3296 --- /dev/null +++ b/src/Response/StationsResponse.php @@ -0,0 +1,55 @@ +parcelShops = $parcelShops; + } + + public static function fromArray(array $response): StationsResponse + { + try { + Assert::isArray($response); + Assert::keyExists($response, 'ACSTableOutput'); + $output = $response['ACSTableOutput']; + Assert::keyExists($output, 'Table_Data'); + + return new self( + array_map( + static function ($item) { + return ParcelShop::fromArray($item); + }, + $output['Table_Data'], + ) + ); + } catch (\Throwable $e) { + throw new MalformedResponse($e->getMessage(), $response, $e); + } + } + + /** + * @return ParcelShop[] + */ + public function getParcelShops(): array + { + return $this->parcelShops; + } +} diff --git a/src/Service/Client.php b/src/Service/Client.php new file mode 100644 index 0000000..94cc082 --- /dev/null +++ b/src/Service/Client.php @@ -0,0 +1,80 @@ +configProvider = $configProvider; + $this->guzzle = $client ?? new \GuzzleHttp\Client(); + } + + public function request(Request $request): array + { + try { + $response = $this->guzzle->request( + 'POST', + $this->configProvider->getUrl(), + [ + 'headers' => $this->configProvider->getRequestHeaders(), + 'body' => $request->toJson(), + ] + ); + $responseText = $this->getResponseText($response); + } catch (GuzzleException $e) { + throw new ServiceUnavailable($e->getMessage(), $e->getCode(), $e); + } + + return $this->getResult($responseText); + } + + public function getBaseInputParameters(): BaseInputParameters + { + return $this->configProvider->getBaseInputParameters(); + } + + private function getResponseText(ResponseInterface $response): string + { + if ($response->getBody()->isSeekable()) { + $response->getBody()->rewind(); + } + + return $response->getBody()->getContents(); + } + + private function getResult(string $responseText): array + { + $decoded = \json_decode($responseText, true); + + try { + Assert::isArray($decoded); + Assert::keyExists($decoded, 'ACSExecution_HasError'); + Assert::keyExists($decoded, 'ACSExecutionErrorMessage'); + Assert::keyExists($decoded, 'ACSOutputResponce'); + } catch (\InvalidArgumentException $e) { + throw new MalformedResponse($e->getMessage(), $decoded, $e); + } + + if (true === $decoded['ACSExecution_HasError']) { + throw new MalformedResponse($decoded['ACSExecutionErrorMessage'], $decoded); + } + + return $decoded['ACSOutputResponce']; + } +} diff --git a/src/Service/ParcelShopsService.php b/src/Service/ParcelShopsService.php new file mode 100644 index 0000000..4a872ca --- /dev/null +++ b/src/Service/ParcelShopsService.php @@ -0,0 +1,30 @@ +client = $client; + } + + /** + * @return ParcelShop[] + */ + public function getList(CountryIdEnum $countryId, ?int $kind = null): array + { + $response = $this->client->request(new StationsRequest($this->client->getBaseInputParameters(), $countryId, $kind)); + + return StationsResponse::fromArray($response)->getParcelShops(); + } +} diff --git a/tests/Integration/Service/ParcelShopsServiceTest.php b/tests/Integration/Service/ParcelShopsServiceTest.php new file mode 100644 index 0000000..ff6e001 --- /dev/null +++ b/tests/Integration/Service/ParcelShopsServiceTest.php @@ -0,0 +1,74 @@ +configProvider = new ConfigProvider( + 'apiKey', + 'companyId', + 'companyPassword', + 'userId', + 'userPassword', + 'EN' + ); + } + + /** + * @test + */ + public function successfulGetParcelShop(): void + { + $parcelShops = FileTestUtil::decodeJsonFromFile(__DIR__ . '/data/parcelShops.json'); + $this->client = $this->getClient(); + $service = $this->getService(); + + $this->mockGuzzleResponse( + new Response(200, [], FileTestUtil::getFileContents(__DIR__ . '/data/parcelShops.json')) + ); + + $result = $service->getList(CountryIdEnum::cyprus()); + + $this->assertEquals($this->getExpectedParcelShops(), $result); + } + + private function getClient(): Client + { + return new Client($this->configProvider, $this->setupGuzzleClient()); + } + + private function getService(): ParcelShopsService + { + return new ParcelShopsService($this->client); + } + + private function getExpectedParcelShops(): array + { + return [ + $this->getCorrectParcelShop(), + $this->getSecondCorrectParcelShop(), + ]; + } +} diff --git a/tests/Integration/Service/data/parcelShops.json b/tests/Integration/Service/data/parcelShops.json new file mode 100644 index 0000000..9e70991 --- /dev/null +++ b/tests/Integration/Service/data/parcelShops.json @@ -0,0 +1,77 @@ +{ + "ACSExecution_HasError": false, + "ACSExecutionErrorMessage": "", + "ACSOutputResponce": { + "ACSValueOutput": [ + { + "error_message": null + } + ], + "ACSTableOutput": { + "Table_Data": [ + { + "ACS_SHOP_COUNTRY_ID": 2, + "ACS_SHOP_COUNTRY_DESCR": "country desc", + "ACS_SHOP_AREA_ID": 0, + "ACS_SHOP_AREA_DESCR": "", + "ACS_SHOP_STATION_ID": "AB", + "ACS_SHOP_STATION_ID_EN": "ABC", + "ACS_SHOP_STATION_DESCR": "station desc", + "ACS_SHOP_BRANCH_ID": 1, + "ACS_SHOP_ADDRESS": "address", + "ACS_SHOP_ZIPCODE": "1234", + "ACS_SHOP_PHONES": "123123123", + "ACS_SHOP_FAX": "123123124", + "ACS_SHOP_WORKING_HOURS": "07:45-19:00", + "ACS_SHOP_WORKING_HOURS_SATURDAY": "08:45-13:00", + "ACS_SHOP_TRUCK_PICKUP_HOURS": "", + "ACS_SHOP_TRUCK_PICKUP_HOURS_SATURDAY": "", + "ACS_SHOP_LAT": "34.0", + "ACS_SHOP_LONG": "33.0", + "ACS_SHOP_DELIVERY_START_HOUR": "", + "ACS_SHOP_COORDINATES_VERIFIED": 1, + "ACS_SHOP_KIND": 1, + "ACS_SHOP_SERVICES": "services", + "ACS_SHOP_EMAIL": "test@email.com", + "ACS_SHOP_PAYMENT_TYPES": "types", + "ACS_SHOP_ID_CODE": "1234", + "ACS_SHOP_CITY": "city", + "ACS_SHOP_SMARTPOINT_KIND": "", + "ACS_SHOP_WORKING_HOURS_JSON": "", + "ACS_SHOP_MESSAGE": "message" + }, + { + "ACS_SHOP_COUNTRY_ID": 2, + "ACS_SHOP_COUNTRY_DESCR": "country desc", + "ACS_SHOP_AREA_ID": 1, + "ACS_SHOP_AREA_DESCR": "", + "ACS_SHOP_STATION_ID": "BC", + "ACS_SHOP_STATION_ID_EN": "BCD", + "ACS_SHOP_STATION_DESCR": "station desc", + "ACS_SHOP_BRANCH_ID": 1, + "ACS_SHOP_ADDRESS": "address", + "ACS_SHOP_ZIPCODE": "6789", + "ACS_SHOP_PHONES": "654654654", + "ACS_SHOP_FAX": "654654653", + "ACS_SHOP_WORKING_HOURS": "07:45-16:00", + "ACS_SHOP_WORKING_HOURS_SATURDAY": "08:45-12:00", + "ACS_SHOP_TRUCK_PICKUP_HOURS": "", + "ACS_SHOP_TRUCK_PICKUP_HOURS_SATURDAY": "", + "ACS_SHOP_LAT": "38.0", + "ACS_SHOP_LONG": "32.0", + "ACS_SHOP_DELIVERY_START_HOUR": "", + "ACS_SHOP_COORDINATES_VERIFIED": 1, + "ACS_SHOP_KIND": 1, + "ACS_SHOP_SERVICES": "services", + "ACS_SHOP_EMAIL": "second_test@email.com", + "ACS_SHOP_PAYMENT_TYPES": "types", + "ACS_SHOP_ID_CODE": "6789", + "ACS_SHOP_CITY": "city", + "ACS_SHOP_SMARTPOINT_KIND": "", + "ACS_SHOP_WORKING_HOURS_JSON": "", + "ACS_SHOP_MESSAGE": "message" + } + ] + } + } +} diff --git a/tests/Unit/ConfigProviderTest.php b/tests/Unit/ConfigProviderTest.php new file mode 100644 index 0000000..4f96d4d --- /dev/null +++ b/tests/Unit/ConfigProviderTest.php @@ -0,0 +1,57 @@ +configProvider = new ConfigProvider( + 'apiKey', + 'companyId', + 'companyPassword', + 'userId', + 'userPassword', + 'EN' + ); + } + + /** + * @test + */ + public function returnsCorrectRequestHeaders(): void + { + self::assertSame( + [ + 'AcsApiKey' => 'apiKey', + 'Content-Type' => 'application/json', + ], + $this->configProvider->getRequestHeaders() + ); + } + + /** + * @test + */ + public function returnCorrectBaseInputParameters(): void + { + self::assertEquals( + [ + 'Company_ID' => 'companyId', + 'Company_Password' => 'companyPassword', + 'User_ID' => 'userId', + 'User_Password' => 'userPassword', + 'language' => 'EN', + ], + $this->configProvider->getBaseInputParameters()->toArray()); + } +} diff --git a/tests/Unit/Request/BaseInputParametersTest.php b/tests/Unit/Request/BaseInputParametersTest.php new file mode 100644 index 0000000..f75b35b --- /dev/null +++ b/tests/Unit/Request/BaseInputParametersTest.php @@ -0,0 +1,30 @@ + 'companyId', + 'Company_Password' => 'companyPassword', + 'User_ID' => 'userId', + 'User_Password' => 'userPassword', + 'language' => 'EN', + ], + $baseInputParameters->toArray() + ); + } +} diff --git a/tests/Unit/Request/StationsRequestTest.php b/tests/Unit/Request/StationsRequestTest.php new file mode 100644 index 0000000..6d2b0c7 --- /dev/null +++ b/tests/Unit/Request/StationsRequestTest.php @@ -0,0 +1,41 @@ +toJson() + ); + } + + /** + * @test + */ + public function returnCorrectJsonWithoutKind(): void + { + $baseInputParameters = new BaseInputParameters('companyId', 'companyPassword', 'userId', 'userPassword', 'EN'); + $stationsRequest = new StationsRequest($baseInputParameters, CountryIdEnum::greece()); + + self::assertSame( + '{"ACSAlias":"ACS_Stations","ACSInputParameters":{"ACS_SHOP_COUNTRY_ID":"GR","Company_ID":"companyId","Company_Password":"companyPassword","User_ID":"userId","User_Password":"userPassword","language":"EN"}}', + $stationsRequest->toJson() + ); + } +} diff --git a/tests/Unit/Response/DTO/AddressTest.php b/tests/Unit/Response/DTO/AddressTest.php new file mode 100644 index 0000000..c0cf2da --- /dev/null +++ b/tests/Unit/Response/DTO/AddressTest.php @@ -0,0 +1,38 @@ +getCorrectResponseItemArray()); + + self::assertEquals( + $this->getCorrectAddress(), + $address + ); + } + + /** + * @test + */ + public function throwsErrorWhenDataNotComplete(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Expected the key "ACS_SHOP_ADDRESS" to exist.'); + Address::fromArray($this->getResponseItemArrayWithoutAddressAndLatitude()); + } +} diff --git a/tests/Unit/Response/DTO/CoordinatesTest.php b/tests/Unit/Response/DTO/CoordinatesTest.php new file mode 100644 index 0000000..16189bc --- /dev/null +++ b/tests/Unit/Response/DTO/CoordinatesTest.php @@ -0,0 +1,38 @@ +getCorrectResponseItemArray()); + + self::assertEquals( + $this->getCorrectCoordinates(), + $coordinates + ); + } + + /** + * @test + */ + public function throwsErrorWhenDataNotComplete(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Expected the key "ACS_SHOP_LAT'); + Coordinates::fromArray($this->getResponseItemArrayWithoutAddressAndLatitude()); + } +} diff --git a/tests/Unit/Response/DTO/ParcelShopTest.php b/tests/Unit/Response/DTO/ParcelShopTest.php new file mode 100644 index 0000000..772ccae --- /dev/null +++ b/tests/Unit/Response/DTO/ParcelShopTest.php @@ -0,0 +1,39 @@ +getCorrectResponseItemArray()); + + self::assertEquals( + $this->getCorrectParcelShop(), + $parcelShop + ); + } + + /** + * @test + */ + public function throwsErrorWhenDataNotComplete(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Expected the key "ACS_SHOP_ADDRESS" to exist.'); + Address::fromArray($this->getResponseItemArrayWithoutAddressAndLatitude()); + } +} diff --git a/tests/Unit/Response/StationsResponseTest.php b/tests/Unit/Response/StationsResponseTest.php new file mode 100644 index 0000000..cc4be9e --- /dev/null +++ b/tests/Unit/Response/StationsResponseTest.php @@ -0,0 +1,67 @@ +getArrayForStationsResponse()); + + self::assertEquals( + new StationsResponse([$this->getCorrectParcelShop(), $this->getSecondCorrectParcelShop()]), + $stationsResponse + ); + } + + /** + * @test + */ + public function throwsErrorWhenResponseWithoutACSTableOutput(): void + { + $this->expectException(MalformedResponse::class); + $this->expectErrorMessage('Expected the key "ACSTableOutput" to exist.'); + StationsResponse::fromArray([]); + } + + /** + * @test + */ + public function throwsErrorWhenResponseWithoutTableData(): void + { + $this->expectException(MalformedResponse::class); + $this->expectErrorMessage('Expected the key "Table_Data" to exist.'); + StationsResponse::fromArray(['ACSTableOutput' => []]); + } + + /** + * @test + */ + public function throwsErrorWhenResponseWithIncorrectItems(): void + { + $this->expectException(MalformedResponse::class); + $this->expectErrorMessage('Expected the key "ACS_SHOP_COUNTRY_ID'); + StationsResponse::fromArray( + [ + 'ACSTableOutput' => [ + 'Table_Data' => [ + [ + 'a' => 1, + ], + ], + ], + ] + ); + } +} diff --git a/tests/Unit/Response/StationsResponseTrait.php b/tests/Unit/Response/StationsResponseTrait.php new file mode 100644 index 0000000..0a0ac8b --- /dev/null +++ b/tests/Unit/Response/StationsResponseTrait.php @@ -0,0 +1,209 @@ + 2, + 'ACS_SHOP_COUNTRY_DESCR' => 'country desc', + 'ACS_SHOP_AREA_ID' => 0, + 'ACS_SHOP_AREA_DESCR' => '', + 'ACS_SHOP_STATION_ID' => 'AB', + 'ACS_SHOP_STATION_ID_EN' => 'ABC', + 'ACS_SHOP_STATION_DESCR' => 'station desc', + 'ACS_SHOP_BRANCH_ID' => 1, + 'ACS_SHOP_ADDRESS' => 'address', + 'ACS_SHOP_ZIPCODE' => '1234', + 'ACS_SHOP_PHONES' => '123123123', + 'ACS_SHOP_FAX' => '123123124', + 'ACS_SHOP_WORKING_HOURS' => '07:45-19:00', + 'ACS_SHOP_WORKING_HOURS_SATURDAY' => '08:45-13:00', + 'ACS_SHOP_TRUCK_PICKUP_HOURS' => '', + 'ACS_SHOP_TRUCK_PICKUP_HOURS_SATURDAY' => '', + 'ACS_SHOP_LAT' => '34.00000000', + 'ACS_SHOP_LONG' => '33.00000000', + 'ACS_SHOP_DELIVERY_START_HOUR' => '', + 'ACS_SHOP_COORDINATES_VERIFIED' => 1, + 'ACS_SHOP_KIND' => 1, + 'ACS_SHOP_SERVICES' => 'services', + 'ACS_SHOP_EMAIL' => 'test@email.com', + 'ACS_SHOP_PAYMENT_TYPES' => 'types', + 'ACS_SHOP_ID_CODE' => '1234', + 'ACS_SHOP_CITY' => 'city', + 'ACS_SHOP_SMARTPOINT_KIND' => '', + 'ACS_SHOP_WORKING_HOURS_JSON' => '', + 'ACS_SHOP_MESSAGE' => 'message', + ]; + } + + public function getSecondCorrectResponseItemArray(): array + { + return [ + 'ACS_SHOP_COUNTRY_ID' => 2, + 'ACS_SHOP_COUNTRY_DESCR' => 'country desc', + 'ACS_SHOP_AREA_ID' => 1, + 'ACS_SHOP_AREA_DESCR' => '', + 'ACS_SHOP_STATION_ID' => 'BC', + 'ACS_SHOP_STATION_ID_EN' => 'BCD', + 'ACS_SHOP_STATION_DESCR' => 'station desc', + 'ACS_SHOP_BRANCH_ID' => 1, + 'ACS_SHOP_ADDRESS' => 'address', + 'ACS_SHOP_ZIPCODE' => '6789', + 'ACS_SHOP_PHONES' => '654654654', + 'ACS_SHOP_FAX' => '654654653', + 'ACS_SHOP_WORKING_HOURS' => '07:45-16:00', + 'ACS_SHOP_WORKING_HOURS_SATURDAY' => '08:45-12:00', + 'ACS_SHOP_TRUCK_PICKUP_HOURS' => '', + 'ACS_SHOP_TRUCK_PICKUP_HOURS_SATURDAY' => '', + 'ACS_SHOP_LAT' => '38.00000000', + 'ACS_SHOP_LONG' => '32.00000000', + 'ACS_SHOP_DELIVERY_START_HOUR' => '', + 'ACS_SHOP_COORDINATES_VERIFIED' => 1, + 'ACS_SHOP_KIND' => 1, + 'ACS_SHOP_SERVICES' => 'services', + 'ACS_SHOP_EMAIL' => 'second_test@email.com', + 'ACS_SHOP_PAYMENT_TYPES' => 'types', + 'ACS_SHOP_ID_CODE' => '6789', + 'ACS_SHOP_CITY' => 'city', + 'ACS_SHOP_SMARTPOINT_KIND' => '', + 'ACS_SHOP_WORKING_HOURS_JSON' => '', + 'ACS_SHOP_MESSAGE' => 'message', + ]; + } + + public function getArrayForStationsResponse(): array + { + return [ + 'ACSTableOutput' => [ + 'Table_Data' => [ + $this->getCorrectResponseItemArray(), + $this->getSecondCorrectResponseItemArray(), + ], + ], + ]; + } + + public function getResponseItemArrayWithoutAddressAndLatitude(): array + { + return [ + 'ACS_SHOP_COUNTRY_ID' => 2, + 'ACS_SHOP_COUNTRY_DESCR' => 'country desc', + 'ACS_SHOP_AREA_ID' => 0, + 'ACS_SHOP_AREA_DESCR' => '', + 'ACS_SHOP_STATION_ID' => 'AB', + 'ACS_SHOP_STATION_ID_EN' => 'AB', + 'ACS_SHOP_STATION_DESCR' => 'station desc', + 'ACS_SHOP_BRANCH_ID' => 1, + 'ACS_SHOP_ZIPCODE' => '1234', + 'ACS_SHOP_PHONES' => '123123123', + 'ACS_SHOP_FAX' => '123123124', + 'ACS_SHOP_WORKING_HOURS' => '07:45-19:00', + 'ACS_SHOP_WORKING_HOURS_SATURDAY' => '08:45-13:00', + 'ACS_SHOP_TRUCK_PICKUP_HOURS' => '', + 'ACS_SHOP_TRUCK_PICKUP_HOURS_SATURDAY' => '', + 'ACS_SHOP_LONG' => '33.00000000', + 'ACS_SHOP_DELIVERY_START_HOUR' => '', + 'ACS_SHOP_COORDINATES_VERIFIED' => 1, + 'ACS_SHOP_KIND' => 1, + 'ACS_SHOP_SERVICES' => 'services', + 'ACS_SHOP_EMAIL' => 'test@email.com', + 'ACS_SHOP_PAYMENT_TYPES' => 'types', + 'ACS_SHOP_ID_CODE' => '1234', + 'ACS_SHOP_CITY' => 'city', + 'ACS_SHOP_SMARTPOINT_KIND' => '', + 'ACS_SHOP_WORKING_HOURS_JSON' => '', + 'ACS_SHOP_MESSAGE' => 'message', + ]; + } + + public function getCorrectAddress(): Address + { + return new Address( + 'address', + '1234', + 'city', + '123123123', + 'test@email.com', + '123123124' + ); + } + + public function getCorrectCoordinates(): Coordinates + { + return new Coordinates(34.0, 33.0); + } + + public function getCorrectParcelShop(): ParcelShop + { + return new ParcelShop( + 2, + 'country desc', + 0, + '', + 'AB', + 'ABC', + 'station desc', + 1, + '07:45-19:00', + '08:45-13:00', + '', + '', + '', + 1, + 1, + 'services', + 'types', + '1234', + '', + '', + 'message', + $this->getCorrectAddress(), + $this->getCorrectCoordinates() + ); + } + + public function getSecondCorrectParcelShop(): ParcelShop + { + return new ParcelShop( + 2, + 'country desc', + 1, + '', + 'BC', + 'BCD', + 'station desc', + 1, + '07:45-16:00', + '08:45-12:00', + '', + '', + '', + 1, + 1, + 'services', + 'types', + '6789', + '', + '', + 'message', + new Address( + 'address', + '6789', + 'city', + '654654654', + 'second_test@email.com', + '654654653' + ), + new Coordinates(38.0, 32.0) + ); + } +} diff --git a/tests/Unit/Service/ClientTest.php b/tests/Unit/Service/ClientTest.php new file mode 100644 index 0000000..a6b1e0b --- /dev/null +++ b/tests/Unit/Service/ClientTest.php @@ -0,0 +1,137 @@ +client = new Client($configProvider, $this->setupGuzzleClient()); + $this->stationsRequest = new StationsRequest($configProvider->getBaseInputParameters(), CountryIdEnum::cyprus()); + } + + /** + * @test + */ + public function properResponse(): void + { + $this->guzzleHandler->append(new Response(200, [], '{"ACSExecution_HasError":false,"ACSExecutionErrorMessage":"","ACSOutputResponce":{"ACSTableOutput":{}}}')); + + $result = $this->client->request($this->stationsRequest); + + self::assertEquals( + [ + 'ACSTableOutput' => [], + ], + $result + ); + self::assertCount(1, $this->clientHistory); + } + + /** + * @test + */ + public function responseWithExecutionErrors(): void + { + $this->guzzleHandler->append(new Response(200, [], '{"ACSExecution_HasError":true,"ACSExecutionErrorMessage":"error message","ACSOutputResponce":""}')); + + $this->expectException(MalformedResponse::class); + $this->expectExceptionMessage('error message'); + + $this->client->request($this->stationsRequest); + } + + /** + * @test + */ + public function responseWithoutOutputResponce(): void + { + $this->guzzleHandler->append(new Response(200, [], '{"ACSExecution_HasError":false,"ACSExecutionErrorMessage":""}')); + + $this->expectException(MalformedResponse::class); + $this->expectExceptionMessage('Expected the key "ACSOutputResponce"'); + + $this->client->request($this->stationsRequest); + } + + /** + * @test + */ + public function responseWithoutExecutionErrorMessage(): void + { + $this->guzzleHandler->append(new Response(200, [], '{"ACSExecution_HasError":false}')); + + $this->expectException(MalformedResponse::class); + $this->expectExceptionMessage('Expected the key "ACSExecutionErrorMessage"'); + + $this->client->request($this->stationsRequest); + } + + /** + * @test + */ + public function responseWithoutHasError(): void + { + $this->guzzleHandler->append(new Response(200, [], '{}')); + + $this->expectException(MalformedResponse::class); + $this->expectExceptionMessage('Expected the key "ACSExecution_HasError"'); + + $this->client->request($this->stationsRequest); + } + + /** + * @test + */ + public function responseWithoutArray(): void + { + $this->guzzleHandler->append(new Response(200, [], '"result":[]')); + + $this->expectException(MalformedResponse::class); + $this->expectExceptionMessage('Expected an array. Got: NULL'); + + $this->client->request($this->stationsRequest); + } + + /** + * @test + */ + public function serviceUnavailable(): void + { + $this->guzzleHandler->append(new Response(500, [], '{}')); + + $this->expectException(ServiceUnavailable::class); + + $this->client->request($this->stationsRequest); + } +} diff --git a/tests/Unit/Test.php b/tests/Unit/Test.php deleted file mode 100644 index 368498a..0000000 --- a/tests/Unit/Test.php +++ /dev/null @@ -1,18 +0,0 @@ -