diff --git a/.docker/php/Dockerfile b/.docker/php/Dockerfile new file mode 100644 index 0000000..9856727 --- /dev/null +++ b/.docker/php/Dockerfile @@ -0,0 +1,23 @@ +ARG PHP_IMAGE="php:8.3" +FROM $PHP_IMAGE +ARG PHP_IMAGE + +RUN apt-get update + +RUN apt autoremove -y +RUN apt-get install libicu-dev zip -y --no-install-recommends +RUN apt-get install libmcrypt-dev -y --no-install-recommends +RUN apt-get install git unzip -y +RUN docker-php-ext-install mysqli pdo_mysql bcmath + +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer + +RUN apt-get install zlib1g-dev libzip-dev -y --no-install-recommends +RUN docker-php-ext-install zip + +RUN pecl install xdebug +RUN docker-php-ext-enable xdebug + +ENV XDEBUG_MODE=coverage + +RUN rm -rf /var/lib/apt/lists/* diff --git a/.github/workflows/.github-actions.yml b/.github/workflows/.github-actions.yml new file mode 100644 index 0000000..48a1d58 --- /dev/null +++ b/.github/workflows/.github-actions.yml @@ -0,0 +1,80 @@ +name: CI + +on: + push: + branches: + - master + - dev + tags: + - 1.* + - 2.* + pull_request: + branches: [ master ] + + workflow_dispatch: + +jobs: + phpcs: + strategy: + matrix: + version: ['8.1', '8.2', '8.3'] + runs-on: ubuntu-latest + + steps: + - name: Checkout the repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Setup PHP with composer v2 + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.version }} + tools: composer:v2 + + - name: Install composer packages + run: | + php -v + composer install --prefer-dist --no-ansi --no-interaction --no-progress --no-scripts + + - name: Execute PHP_CodeSniffer + run: | + php -v + composer phpcs + + phpunit: + strategy: + matrix: + version: ['8.1', '8.2', '8.3'] + runs-on: ubuntu-latest + + steps: + - name: Checkout the repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.version }} + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, mysql, mysqli, pdo_mysql, bcmath, intl, exif, iconv + coverage: xdebug + + - name: Install composer packages + run: | + php -v + composer install --prefer-dist --no-ansi --no-interaction --no-progress --no-scripts + + - name: Execute tests + run: | + php -v + ./vendor/phpunit/phpunit/phpunit --version + ./vendor/phpunit/phpunit/phpunit --coverage-clover=coverage.xml + export CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} + bash <(curl -s https://codecov.io/bash) || echo 'Codecov failed to upload' + + - name: Upload code coverage + run: | + export CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} + bash <(curl -s https://codecov.io/bash) || echo 'Codecov failed to upload' diff --git a/.gitignore b/.gitignore index 9c0015d..be11935 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,6 @@ bootstrap/cache/ !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json -!.vscode/extensions.json \ No newline at end of file +!.vscode/extensions.json + +composer.lock diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..520ea20 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,130 @@ +stages: + - docker + - build + - lint + - test + +# ========= Anchors (Partials) ========= +.php_base: &php_base + image: $CI_REGISTRY_IMAGE/php:8.3 + tags: + - docker + +.test_base: &test_base + stage: test + before_script: + - rm -rf vendor + - php -d memory_limit=-1 /usr/bin/composer require --dev phpunit/phpunit:^$PHPUNIT_VERSION + - php -d memory_limit=-1 /usr/bin/composer install + script: + - composer du + - composer test:coverage + artifacts: + when: on_failure + expire_in: 4 hours + paths: + - storage/logs/ + reports: + junit: coverage/phpunit.junit.xml + coverage_report: + coverage_format: cobertura + path: coverage/cobertura.xml + coverage: /^\s*Lines:\s*\d+.\d+\%/ + tags: + - docker + +.docker_base: &docker_base + stage: docker + image: docker:20.10.8-alpine3.14 + services: + - docker:20.10.8-dind-alpine3.14 + before_script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + script: + - docker pull $CONTAINER_IMAGE:$PHP_VERSION || true + - docker build --cache-from $CONTAINER_IMAGE:$PHP_VERSION --build-arg PHP_IMAGE=php:$PHP_VERSION -t $CONTAINER_IMAGE:$PHP_VERSION ./.docker/$SERVICE_NAME + - docker push $CONTAINER_IMAGE:$PHP_VERSION + when: manual + tags: + - docker + +.docker_variables: &docker_variables + CONTAINER_IMAGE: $CI_REGISTRY_IMAGE/$SERVICE_NAME + DOCKER_DRIVER: devicemapper + DOCKER_TLS_CERTDIR: '' + GIT_STRATEGY: fetch + GIT_DEPTH: "1" + +# ====================== +# Docker jobs +# ====================== +docker:php81: + <<: *docker_base + variables: + SERVICE_NAME: php + PHP_VERSION: "8.1" + <<: *docker_variables + +docker:php82: + <<: *docker_base + variables: + SERVICE_NAME: php + PHP_VERSION: "8.2" + <<: *docker_variables + +docker:php83: + <<: *docker_base + variables: + SERVICE_NAME: php + PHP_VERSION: "8.3" + <<: *docker_variables + +# ====================== +# Build jobs +# ====================== +build:php: + <<: *php_base + stage: build + script: + - composer install + artifacts: + paths: + - vendor/ + expire_in: 2 hours + cache: + paths: + - vendor + key: php + +# ====================== +# Lint jobs +# ====================== +lint:php: + <<: *php_base + stage: lint + script: + - composer phpcs + dependencies: + - build:php + allow_failure: true + +# ====================== +# Test jobs +# ====================== +test:php81: + <<: *test_base + image: $CI_REGISTRY_IMAGE/php:8.1 + variables: + PHPUNIT_VERSION: "9.5" + +test:php82: + <<: *test_base + image: $CI_REGISTRY_IMAGE/php:8.2 + variables: + PHPUNIT_VERSION: "9.5" + +test:php83: + <<: *test_base + image: $CI_REGISTRY_IMAGE/php:8.3 + variables: + PHPUNIT_VERSION: "9.5" \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c164dd6..0000000 --- a/.travis.yml +++ /dev/null @@ -1,38 +0,0 @@ -language: php - -php: - - 7.3 - - 7.4 - -env: - global: - - CC_TEST_REPORTER_ID=$CODECLIMATE_REPO_TOKEN - matrix: - - LARAVEL_VERSION=5.8.* - - LARAVEL_VERSION=6.0.* - - LARAVEL_VERSION=7.0.* - - LARAVEL_VERSION=8.0.* - -cache: - directories: - - $HOME/.composer/cache - -matrix: - fast_finish: true - -sudo: false - -install: - - if [ "$LARAVEL_VERSION" != "" ]; then composer require --dev "laravel/framework:${LARAVEL_VERSION}" --no-update; fi; - - travis_retry composer install --no-interaction --prefer-source --no-suggest - -before_script: - - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - - chmod +x ./cc-test-reporter - - ./cc-test-reporter before-build - -script: - - $TRAVIS_BUILD_DIR/vendor/bin/phpunit --coverage-clover build/logs/clover.xml - -after_script: - - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT; diff --git a/CHANGELOG.md b/CHANGELOG.md index 26c5cab..ab19f02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,3 +15,19 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html ## [1.0.2] - 14-02-2022 - Laravel 9 Support + +## [1.0.3] - 06-03-2023 +- Laravel 10 Support + +## [2.0.0] - 22-02-2024 +### Added +- Autocomplete and get endpoint support. +- Complete test coverage. + +### Changed +- Refactored codebase, make use of `Http` facade to assist testing. +- Moved core HTTP logic into `Laralabs\GetAddress\Http\Client` which provides `get()` and `post()`. Can be used if wanting to experiment with new endpoints or endpoints not supported by the package. + +### Removed +- Unused classes. + diff --git a/README.md b/README.md index 3d7bc0f..cf1295b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@

Stable Build -Build Status -StyleCI - - +CI Status +

# Laravel getAddress.io Package @@ -35,22 +33,33 @@ php artisan migrate ### Usage A helper function and facade is available, choose your preferred method. -Perform a lookup: +The facade is located at `Laralabs\GetAddress\Facades\GetAddress` + +The endpoints currently supported are (support for other endpoints coming soon): +- find +- autocomplete +- get + +Perform a lookup using the `find` endpoint: ```php $results = get_address()->find($postcode, $property); ``` -Perform an expanded lookup: +The `$property` argument is optional, just searching for a postcode will return all results for that postcode and also cache the data if the cache has been enabled. + +Perform a looking using the `autocomplete` endpoint: ```php -$results = get_address()->expand()->find($postcode, $property); +$results = get_address()->autocomplete($searchTerm, ['top' => '20']); ``` -The `$property` argument is optional, just searching for a postcode will return all results for that postcode and also cache the data if the cache has been enabled. +The second argument supports an array of parameters to send with the POST request, the example above shows setting the returned results to the maximum allowed of 20. -## :pushpin: Todo +Perform a lookup using the `get` endpoint: +```php +$results = get_address()->get($addressId); +``` -- Admin Features. -- Unit Tests. +This is used with the `autocomplete` endpoint to return the full address information, the `$addressId` argument must be the ID returned from the autocomplete response. ## :speech_balloon: Support diff --git a/composer.json b/composer.json index 228dd6a..4c90d45 100644 --- a/composer.json +++ b/composer.json @@ -10,23 +10,38 @@ ], "minimum-stability": "dev", "require": { - "php": "^7.3|^8.0", - "illuminate/support": "^5.5|^6.0|^7.0|^8.0|^9.0|^10.0" + "php": "^8.1", + "guzzlehttp/guzzle": "^7.2", + "laravel/framework": "^10.0" }, "require-dev": { + "roave/security-advisories": "dev-latest", "mockery/mockery": "^1.0", - "orchestra/testbench": "^3.4|^4.0|^5.0|^6.0|^7.0", - "phpunit/phpunit": ">6.0" + "orchestra/testbench": "^8.0", + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "^3.7", + "clntdev/coding-standards": "^1.1.0", + "spatie/phpunit-snapshot-assertions": "^4.0" }, "autoload": { "psr-4": { "Laralabs\\GetAddress\\": "src/", + "Laralabs\\GetAddress\\Factories\\": "database/factories", "Laralabs\\GetAddress\\Tests\\": "tests/" }, "files": [ "src/Helpers/functions.php" ] }, + "scripts": { + "test": "./vendor/bin/phpunit", + "test:coverage": [ + "@putenv XDEBUG_MODE=coverage", + "vendor/bin/phpunit --log-junit=coverage/phpunit.junit.xml --coverage-cobertura=coverage/cobertura.xml --coverage-text" + ], + "phpcs": "vendor/bin/phpcs ./src ./tests --runtime-set ignore_warnings_on_exit true --standard=./vendor/clntdev/coding-standards/phpcs.xml", + "cbf": "vendor/bin/phpcbf ./src ./tests --standard=./vendor/clntdev/coding-standards/phpcs.xml" + }, "extra": { "laravel": { "providers": [ @@ -36,5 +51,10 @@ "GetAddress": "Laralabs\\GetAddress\\Facades\\GetAddress" } } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } } - } +} diff --git a/database/factories/CachedAddressFactory.php b/database/factories/CachedAddressFactory.php new file mode 100644 index 0000000..3eabdb1 --- /dev/null +++ b/database/factories/CachedAddressFactory.php @@ -0,0 +1,25 @@ + '123 Example Street', + 'line_2' => '', + 'line_3' => '', + 'line_4' => '', + 'locality' => 'Moseley', + 'town_or_city' => 'Birmingham', + 'county' => 'West Midlands', + 'postcode' => 'B13 9SZ', + ]; + } +} \ No newline at end of file diff --git a/database/migrations/2019_03_09_000000_create_getaddress_cache_table.php b/database/migrations/2019_03_09_000000_create_getaddress_cache_table.php index 1ec0a00..3ad39bd 100644 --- a/database/migrations/2019_03_09_000000_create_getaddress_cache_table.php +++ b/database/migrations/2019_03_09_000000_create_getaddress_cache_table.php @@ -4,16 +4,11 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -class CreateGetAddressCacheTable extends Migration +return new class extends Migration { - /** - * Run the migrations. - * - * @return void - */ - public function up() + public function up(): void { - Schema::create('getaddress_cache', function (Blueprint $table) { + Schema::create('getaddress_cache', static function (Blueprint $table): void { $table->bigIncrements('id'); $table->string('line_1')->index(); $table->string('line_2')->nullable(); @@ -37,13 +32,8 @@ public function up() }); } - /** - * Reverse the migrations. - * - * @return void - */ - public function down() + public function down(): void { Schema::dropIfExists('getaddress_cache'); } -} +}; diff --git a/phpunit.xml b/phpunit.xml index 7ece024..6e05b21 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -29,6 +29,10 @@ + + + + diff --git a/src/Cache/Manager.php b/src/Cache/Manager.php index 68b3f65..03c40f6 100644 --- a/src/Cache/Manager.php +++ b/src/Cache/Manager.php @@ -2,6 +2,7 @@ namespace Laralabs\GetAddress\Cache; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Collection; use Laralabs\GetAddress\Models\CachedAddress; use Laralabs\GetAddress\Responses\Address; @@ -10,15 +11,9 @@ class Manager { - /** - * @var int - */ - protected $expiry; + protected int $expiry = 30; - /** - * @var bool - */ - protected $expand; + protected bool $expand = false; public function __construct() { @@ -26,20 +21,16 @@ public function __construct() $this->expand = config('getaddress.expanded_results'); } - /** - * @param $postcode - * @param $property - * - * @return array|null - */ - public function checkCache($postcode, $property): ?array + public function checkCache(string $postcode, null|string|int $property): ?array { - $params = ['postcode' => $postcode, 'property' => $property]; - - $results = CachedAddress::where(function ($query) use ($params) { - $params['property'] !== null ? $query->where('postcode', '=', $params['postcode'])->where('line_1', 'LIKE', '%'.$params['property'].'%') - : $query->where('postcode', '=', $params['postcode']); - })->get(); + $results = CachedAddress::query()->when( + filled($property), + static fn (Builder $query): Builder => $query->where( + 'line_1', + 'LIKE', + '%'.$property.'%' + ) + )->where('postcode', $postcode)->get(); if (count($results) >= 1) { return $this->checkExpiry($results); @@ -48,13 +39,6 @@ public function checkCache($postcode, $property): ?array return null; } - /** - * Store response in cache. - * - * @param AddressCollectionResponse $response - * - * @return AddressCollectionResponse - */ public function responseToCache(AddressCollectionResponse $response): AddressCollectionResponse { foreach ($response->getAddresses() as $address) { @@ -67,24 +51,28 @@ public function responseToCache(AddressCollectionResponse $response): AddressCol ])); } - if ($address instanceof Address) { - CachedAddress::create(array_merge($address->toArray(), [ - 'longitude' => $response->getLongitude(), - 'latitude' => $response->getLatitude(), - 'postcode' => $response->getPostcode(), - 'expanded_result' => false, - ])); + if ($address instanceof Address === false) { + continue; } + + CachedAddress::create(array_merge($address->toArray(), [ + 'longitude' => $response->getLongitude(), + 'latitude' => $response->getLatitude(), + 'postcode' => $response->getPostcode(), + 'expanded_result' => false, + ])); } return $response; } - /** - * @param Collection $results - * - * @return array|null - */ + public function expand(bool $expand = true): self + { + $this->expand = $expand; + + return $this; + } + protected function checkExpiry(Collection $results): ?array { $address = $results->first(); @@ -98,25 +86,21 @@ protected function checkExpiry(Collection $results): ?array return $this->formatCachedAddresses($results); } - /** - * @param Collection $results - * - * @return array - */ protected function formatCachedAddresses(Collection $results): array { return [ + 'postcode' => $results->first()->postcode, 'longitude' => (float) $results->first()->longitude, 'latitude' => (float) $results->first()->latitude, - 'addresses' => $results->map(function ($address) { - if ($this->expand) { - return array_merge([ - 'formatted_string' => $address->formatted_string, - 'formatted_address' => array_values($address->only(CachedAddress::$fields)), - ], $address->only(CachedAddress::$expandedFields)); + 'addresses' => $results->transform(function (CachedAddress $address): string|array { + if ($this->expand === false) { + return $address->formatted_string; } - return $address->formatted_string; + return array_merge([ + 'formatted_string' => $address->formatted_string, + 'formatted_address' => array_values($address->only(CachedAddress::$fields)), + ], $address->only(CachedAddress::$expandedFields)); })->toArray(), ]; } diff --git a/src/Exceptions/ForbiddenException.php b/src/Exceptions/Forbidden.php similarity index 52% rename from src/Exceptions/ForbiddenException.php rename to src/Exceptions/Forbidden.php index d146021..4f44691 100644 --- a/src/Exceptions/ForbiddenException.php +++ b/src/Exceptions/Forbidden.php @@ -2,12 +2,9 @@ namespace Laralabs\GetAddress\Exceptions; -class ForbiddenException extends \Exception +use Exception; + +class Forbidden extends Exception { - /** - * Exception Message. - * - * @var string - */ protected $message = 'Your API key is not valid for this request.'; } diff --git a/src/Exceptions/Handler.php b/src/Exceptions/Handler.php new file mode 100644 index 0000000..8637693 --- /dev/null +++ b/src/Exceptions/Handler.php @@ -0,0 +1,26 @@ + new InvalidPostcode(), + 401 => new Forbidden(), + 404 => new PostcodeNotFound(), + 429 => new TooManyRequests(), + 500 => new ServerError(), + default => new Unknown($statusCode), + }; + } +} diff --git a/src/Exceptions/InvalidPostcodeException.php b/src/Exceptions/InvalidPostcode.php similarity index 52% rename from src/Exceptions/InvalidPostcodeException.php rename to src/Exceptions/InvalidPostcode.php index 8b736da..5bcbeb1 100644 --- a/src/Exceptions/InvalidPostcodeException.php +++ b/src/Exceptions/InvalidPostcode.php @@ -2,12 +2,9 @@ namespace Laralabs\GetAddress\Exceptions; -class InvalidPostcodeException extends \Exception +use Exception; + +class InvalidPostcode extends Exception { - /** - * Exception Message. - * - * @var string - */ protected $message = 'The postcode you provided was not a valid format.'; } diff --git a/src/Exceptions/PostcodeNotFoundException.php b/src/Exceptions/PostcodeNotFound.php similarity index 51% rename from src/Exceptions/PostcodeNotFoundException.php rename to src/Exceptions/PostcodeNotFound.php index 326ba69..d8c7e85 100644 --- a/src/Exceptions/PostcodeNotFoundException.php +++ b/src/Exceptions/PostcodeNotFound.php @@ -2,12 +2,9 @@ namespace Laralabs\GetAddress\Exceptions; -class PostcodeNotFoundException extends \Exception +use Exception; + +class PostcodeNotFound extends Exception { - /** - * Exception Message. - * - * @var string - */ protected $message = 'The postcode you provided could not be found.'; } diff --git a/src/Exceptions/ServerException.php b/src/Exceptions/ServerError.php similarity index 53% rename from src/Exceptions/ServerException.php rename to src/Exceptions/ServerError.php index c62837c..7b03a32 100644 --- a/src/Exceptions/ServerException.php +++ b/src/Exceptions/ServerError.php @@ -2,12 +2,9 @@ namespace Laralabs\GetAddress\Exceptions; -class ServerException extends \Exception +use Exception; + +class ServerError extends Exception { - /** - * Exception Message. - * - * @var string - */ protected $message = 'getAddress.io is currently having issues.'; } diff --git a/src/Exceptions/TooManyRequestsException.php b/src/Exceptions/TooManyRequests.php similarity index 52% rename from src/Exceptions/TooManyRequestsException.php rename to src/Exceptions/TooManyRequests.php index 95b4312..dd5dd33 100644 --- a/src/Exceptions/TooManyRequestsException.php +++ b/src/Exceptions/TooManyRequests.php @@ -2,12 +2,9 @@ namespace Laralabs\GetAddress\Exceptions; -class TooManyRequestsException extends \Exception +use Exception; + +class TooManyRequests extends Exception { - /** - * Exception Message. - * - * @var string - */ protected $message = 'You have made too many requests for this key.'; } diff --git a/src/Exceptions/UnknownException.php b/src/Exceptions/Unknown.php similarity index 57% rename from src/Exceptions/UnknownException.php rename to src/Exceptions/Unknown.php index 93a39b9..09477a9 100644 --- a/src/Exceptions/UnknownException.php +++ b/src/Exceptions/Unknown.php @@ -2,19 +2,13 @@ namespace Laralabs\GetAddress\Exceptions; -class UnknownException extends \Exception +use Exception; + +class Unknown extends Exception { - /** - * Message. - * - * @var string - */ protected $message = 'getAddress responded with a %d http status'; - /** - * {@inheritdoc} - */ - public function __construct($status) + public function __construct(int $status) { $this->message = sprintf($this->message, $status); diff --git a/src/Facades/GetAddress.php b/src/Facades/GetAddress.php index cd49756..b8f29d0 100644 --- a/src/Facades/GetAddress.php +++ b/src/Facades/GetAddress.php @@ -4,6 +4,12 @@ use Illuminate\Support\Facades\Facade; +/** + * @method static find(string $postcode, null|int|string $propertyNumber = null, bool $sortNumerically = true) + * @method static autocomplete(string $term, array $parameters = []) + * @method static get(string $id) + * @method static expand() + */ class GetAddress extends Facade { public static function getFacadeRoot(): \Laralabs\GetAddress\GetAddress diff --git a/src/Facades/GetAddressAdmin.php b/src/Facades/GetAddressAdmin.php deleted file mode 100644 index 6d857e1..0000000 --- a/src/Facades/GetAddressAdmin.php +++ /dev/null @@ -1,13 +0,0 @@ - null]); - } -} diff --git a/src/GetAddress.php b/src/GetAddress.php index 0747ea5..4ec6c86 100644 --- a/src/GetAddress.php +++ b/src/GetAddress.php @@ -3,70 +3,58 @@ namespace Laralabs\GetAddress; use Laralabs\GetAddress\Cache\Manager; +use Laralabs\GetAddress\Http\Client; use Laralabs\GetAddress\Responses\Address; use Laralabs\GetAddress\Responses\AddressCollectionResponse; +use Laralabs\GetAddress\Responses\AutocompleteCollectionResponse; use Laralabs\GetAddress\Responses\ExpandedAddress; +use Laralabs\GetAddress\Responses\SingleAddressCollectionResponse; -class GetAddress extends GetAddressBase +class GetAddress { - /** - * @var bool - */ - protected $cache; + protected Client $http; - /** - * @var \Laralabs\GetAddress\Cache\Manager|null - */ - protected $manager; + protected bool $cache = false; - /** - * GetAddress constructor. - * - * @param string|null $apiKey - */ - public function __construct($apiKey = null) - { - parent::__construct($apiKey); + protected ?Manager $manager; + protected bool $expand = false; + + public function __construct(Client $client) + { + $this->http = $client; $this->cache = config('getaddress.enable_cache'); $this->manager = $this->cache ? new Manager() : null; + $this->expand = config('getaddress.expanded_results'); } /** * Find an address or range of addresses by a postcode, and optional number/string. * - * @param string $postcode Postcode to search for - * @param int|string $propertyNumber Property number or name - * @param bool $sortNumerically Sorts addresses numerically - * - * @throws Exceptions\ForbiddenException - * @throws Exceptions\InvalidPostcodeException - * @throws Exceptions\PostcodeNotFoundException - * @throws Exceptions\ServerException - * @throws Exceptions\TooManyRequestsException - * @throws Exceptions\UnknownException - * - * @return \Laralabs\GetAddress\Responses\AddressCollectionResponse - * @return AddressCollectionResponse + * @param string $postcode Postcode to search for + * @param null|int|string $propertyNumber Property number or name + * @param bool $sortNumerically Sorts addresses numerically */ - public function find($postcode, $propertyNumber = null, $sortNumerically = true): AddressCollectionResponse - { + public function find( + string $postcode, + null|int|string $propertyNumber = null, + bool $sortNumerically = true + ): AddressCollectionResponse { if ($this->cache) { - $cached = $this->manager->checkCache($postcode, $propertyNumber); + $cached = $this->manager->expand($this->expand)->checkCache($postcode, $propertyNumber); if ($cached !== null) { return $this->createAddressCollectionResponse($postcode, $cached); } } - $this->queryString['sort'] = (int) $sortNumerically; - - $url = sprintf('find/%s', $postcode); - if ($propertyNumber !== null) { - $url .= sprintf('/%s', $propertyNumber); - } - - $response = $this->createAddressCollectionResponse($postcode, $this->call('GET', $url)); + $response = $this->createAddressCollectionResponse( + $postcode, + $this->http->get('find', [$postcode, $propertyNumber], [ + 'sort' => (int) $sortNumerically, + 'expand' => $this->expand, + ]) + ); if ($this->cache && $propertyNumber === null) { $this->manager->responseToCache($response); @@ -76,10 +64,30 @@ public function find($postcode, $propertyNumber = null, $sortNumerically = true) } /** - * Override expanded results. + * Get an autocomplete response for the given search term. + * Use 'id' returned to get full address information using: @see get() * - * @return self + * @param string $term Search term + * @param array $parameters Additional parameters */ + public function autocomplete(string $term, array $parameters = []): AutocompleteCollectionResponse + { + return new AutocompleteCollectionResponse( + $this->http->post('autocomplete', $term, $parameters)['suggestions'] ?? null + ); + } + + /** + * Get the full address information for the given ID. + * This method should be used when using: @see autocomplete() + * + * @param string $id Address unique ID. + */ + public function get(string $id): SingleAddressCollectionResponse + { + return new SingleAddressCollectionResponse($this->http->get('get', $id), $this->expand); + } + public function expand(): self { $this->expand = true; @@ -87,20 +95,14 @@ public function expand(): self return $this; } - /** - * @param string $postcode - * @param array $response - * - * @return AddressCollectionResponse - */ protected function createAddressCollectionResponse(string $postcode, array $response): AddressCollectionResponse { return new AddressCollectionResponse( $postcode, $response['latitude'], $response['longitude'], - array_map(function ($address) { - return $this->expand ? new ExpandedAddress($address) : new Address($address); + array_map(function (string|array $address): ExpandedAddress|Address { + return $this->expand && is_array($address) ? new ExpandedAddress($address) : new Address($address); }, $response['addresses']) ); } diff --git a/src/GetAddressAdmin.php b/src/GetAddressAdmin.php deleted file mode 100644 index 1f54a22..0000000 --- a/src/GetAddressAdmin.php +++ /dev/null @@ -1,11 +0,0 @@ -apiKey = $apiKey ?? config('getaddress.api_key'); - $this->adminKey = $adminKey ?? config('getaddress.admin_key'); - $this->delay = config('getaddress.limit_delay'); - $this->url = config('getaddress.url'); - $this->expand = config('getaddress.expanded_results'); - $this->http = new Client(); - } - - /** - * Call an external resource. - * - * @param $method - * @param $url - * @param array $parameters - * - * @throws ForbiddenException - * @throws InvalidPostcodeException - * @throws PostcodeNotFoundException - * @throws ServerException - * @throws TooManyRequestsException - * @throws UnknownException - * - * @return array - */ - public function call($method, $url, array $parameters = []): array - { - $this->queryString['api-key'] = $this->requiresAdminKey ? $this->adminKey : $this->apiKey; - - $method = strtolower($method); - $url = sprintf('%s/%s?%s', $this->url, $url, http_build_query($this->queryString)); - - if ($this->expand) { - $url .= '&expand=true'; - } - - $response = $this->http->{$method}($url, $parameters); - - if (floor($response->getStatusCode() / 100) > 2) { - if ($response->getStatusCode() !== 429) { - $this->throwException($response->getStatusCode()); - } - - sleep($this->delay + .25); - - $response = $this->http->{$method}($url, $parameters); - - if ($response->getStatusCode() !== 200) { - $this->throwException($response->getStatusCode()); - } - } - - return json_decode($response->getBody(), true); - } - - /** - * Throw exception. - * - * @param $statusCode - * - * @throws ForbiddenException - * @throws InvalidPostcodeException - * @throws PostcodeNotFoundException - * @throws ServerException - * @throws TooManyRequestsException - * @throws UnknownException - */ - protected function throwException($statusCode): void - { - switch ($statusCode) { - case 400: - throw new InvalidPostcodeException(); - case 401: - throw new ForbiddenException(); - case 404: - throw new PostcodeNotFoundException(); - case 429: - throw new TooManyRequestsException(); - case 500: - throw new ServerException(); - default: - throw new UnknownException($statusCode); - } - } -} diff --git a/src/GetAddressServiceProvider.php b/src/GetAddressServiceProvider.php index 10d3615..0a90b9f 100644 --- a/src/GetAddressServiceProvider.php +++ b/src/GetAddressServiceProvider.php @@ -2,15 +2,12 @@ namespace Laralabs\GetAddress; +use Illuminate\Foundation\Application; use Illuminate\Support\ServiceProvider; +use Laralabs\GetAddress\Http\Client; class GetAddressServiceProvider extends ServiceProvider { - /** - * Register the service provider. - * - * @return void - */ public function register(): void { $this->mergeConfigFrom( @@ -18,20 +15,15 @@ public function register(): void 'getaddress' ); - $this->app->bind('getaddress', function ($app, $parameters) { - return new GetAddress($parameters['apiKey']); + $this->app->singleton(Client::class, function (Application $app, array $parameters): Client { //phpcs:ignore + return new Client($parameters['apiKey'] ?? null, $parameters['adminKey'] ?? null); }); - $this->app->bind('getaddress-admin', function ($app, $parameters) { - return new GetAddressAdmin($parameters['adminKey']); + $this->app->bind('getaddress', function (Application $app, array $parameters): GetAddress { //phpcs:ignore + return new GetAddress(app(Client::class, ['apiKey' => $parameters['apiKey'] ?? null])); }); } - /** - * Bootstrap the application events. - * - * @return void - */ public function boot(): void { $this->publishes([ diff --git a/src/Helpers/functions.php b/src/Helpers/functions.php index 74f5327..5d54b13 100644 --- a/src/Helpers/functions.php +++ b/src/Helpers/functions.php @@ -1,19 +1,10 @@ $apiKey]); +use Laralabs\GetAddress\GetAddress; - return $getAddress; - } -} - -if (!function_exists('get_address_admin')) { - function get_address_admin($adminKey = null) +if (!function_exists('get_address')) { + function get_address(?string $apiKey = null): GetAddress { - $getAddressAdmin = app('getaddress-admin', ['adminKey' => $adminKey]); - - return $getAddressAdmin; + return app('getaddress', ['apiKey' => $apiKey]); } } diff --git a/src/Http/Client.php b/src/Http/Client.php new file mode 100644 index 0000000..be05050 --- /dev/null +++ b/src/Http/Client.php @@ -0,0 +1,123 @@ +apiKey = $apiKey ?? config('getaddress.api_key'); + $this->adminKey = $adminKey ?? config('getaddress.admin_key'); + $this->delay = config('getaddress.limit_delay'); + $this->url = config('getaddress.url'); + $this->expand = config('getaddress.expanded_results'); + } + + public function get(string $endpoint, string|array $term, array $queryParameters = []): ?array + { + $url = $this->buildUrl($endpoint, $term); + $queryParameters['api-key'] = $this->getApiKey(); + + if ($this->expand) { + $queryParameters['expand'] = true; + } + + $response = $this->call($url, 'get', $queryParameters); + + if ($response === null && $this->attempts === 0) { + $this->attempts = 1; + + return $this->call($url, 'get', $queryParameters); + } + + return $response; + } + + public function post(string $endpoint, string|array $term, array $parameters = []): ?array + { + $url = $this->buildUrl($endpoint, $term); + $queryParameters = ['api-key' => $this->getApiKey()]; + + $response = $this->call($url, 'post', $queryParameters, $parameters); + + if ($response === null && $this->attempts === 0) { + $this->attempts = 1; + + return $this->call($url, 'post', $queryParameters, $parameters); + } + + return $response; + } + + private function call( + string $url, + string $method, + array $queryParameters = [], + array $parameters = [] + ): mixed { + return $this->handleResponse(Http::withQueryParameters($queryParameters)->$method($url, $parameters ?? null)); + } + + private function handleResponse(PromiseInterface|Response $response): mixed + { + if ($response->failed() && $response->status() !== 429) { + Handler::throwException($response->status()); + } + + if ($response->status() === 429) { + sleep($this->delay + .25); + return null; + } + + return $response->json(); + } + + private function getApiKey(): ?string + { + return $this->isAdminMode() ? $this->adminKey : $this->apiKey; + } + + private function buildUrl(string $endpoint, string|array $term): string + { + if (is_array($term)) { + return Str::of($this->url) + ->append("/{$endpoint}") + ->append("/") + ->append(implode("/", array_filter($term))); + } + + return Str::of($this->url)->append("/{$endpoint}")->append("/{$term}"); + } + + public function admin(): self + { + $this->adminMode = true; + + return $this; + } + + public function isAdminMode(): bool + { + return $this->adminMode; + } +} diff --git a/src/Models/CachedAddress.php b/src/Models/CachedAddress.php index 89e6da9..3bfb5f1 100644 --- a/src/Models/CachedAddress.php +++ b/src/Models/CachedAddress.php @@ -2,18 +2,18 @@ namespace Laralabs\GetAddress\Models; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Laralabs\GetAddress\Factories\CachedAddressFactory; class CachedAddress extends Model { - /** - * @var string - */ + use HasFactory; + + /** @var string */ protected $table = 'getaddress_cache'; - /** - * @var array - */ + /** @var array */ protected $fillable = [ 'line_1', 'line_2', @@ -35,10 +35,7 @@ class CachedAddress extends Model 'expanded_result', ]; - /** - * @var array - */ - public static $expandedFields = [ + public static array $expandedFields = [ 'thoroughfare', 'building_name', 'sub_building_name', @@ -55,10 +52,7 @@ class CachedAddress extends Model 'country', ]; - /** - * @var array - */ - public static $fields = [ + public static array $fields = [ 'line_1', 'line_2', 'line_3', @@ -68,9 +62,6 @@ class CachedAddress extends Model 'county', ]; - /** - * @return string - */ public function getFormattedStringAttribute(): string { return $this->toString(true); @@ -80,15 +71,18 @@ public function getFormattedStringAttribute(): string * Returns a string based on the address. * * @param bool $removeEmptyElements Prevents strings having conjoining commas - * - * @return string */ - public function toString($removeEmptyElements = false): string + public function toString(bool $removeEmptyElements = false): string { - if (!$removeEmptyElements) { + if ($removeEmptyElements === false) { return implode(',', $this->only(self::$fields)); } return implode(', ', array_filter($this->only(self::$fields))); } + + protected static function newFactory(): CachedAddressFactory + { + return new CachedAddressFactory; + } } diff --git a/src/Responses/AbstractWhitelist.php b/src/Responses/AbstractWhitelist.php deleted file mode 100755 index 6a55539..0000000 --- a/src/Responses/AbstractWhitelist.php +++ /dev/null @@ -1,44 +0,0 @@ -id = $id; - $this->name = $name; - } - - /** - * Get Object ID. - * - * @return string - */ - public function getObjectId(): string - { - return $this->id; - } -} diff --git a/src/Responses/Address.php b/src/Responses/Address.php index 2e1790a..9c96864 100755 --- a/src/Responses/Address.php +++ b/src/Responses/Address.php @@ -4,139 +4,81 @@ class Address { - /** - * Sort returned addresses numerically. - * - * @var bool - */ - const SORT_NUMERICALLY = true; - - /** - * Dont perform any specific sort on the returned addresses. - * - * @var bool - */ - const NO_SORT = false; - - /** - * Address string. - * - * @var array - */ - protected $address = []; + protected array $address = []; - /** - * Constructor. - * - * @param string $address - * - * @return void - */ - public function __construct($address) + public function __construct(string|array $address) { - $this->address = explode(',', $address); + $this->address = is_string($address) ? explode(',', $address) : $address; } - /** - * Get Line 1. - * - * @return string - */ public function getLine1(): string { - return $this->address[0]; + return $this->address[0] ?? $this->address['line_1']; } - /** - * Get Line 2. - * - * @return string - */ public function getLine2(): string { - return $this->address[1]; + return $this->address[1] ?? $this->address['line_2']; } - /** - * Get Line 3. - * - * @return string - */ public function getLine3(): string { - return $this->address[2]; + return $this->address[2] ?? $this->address['line_3']; } - /** - * Get Line 4. - * - * @return string - */ public function getLine4(): string { - return $this->address[3]; + return $this->address[3] ?? $this->address['line_4']; } - /** - * Get Line. - * - * @param int $line - * - * @return string - */ - public function getLine($line): string + public function getLine(int $line): ?string { - return $this->address[$line - 1]; + if ($line > 4) { + return null; + } + + return $this->address[$line - 1] ?? null; } - /** - * Get Locality. - * - * @return string - */ public function getLocality(): string { - return $this->address[4]; + return $this->address[4] ?? $this->address['locality']; } - /** - * Get Town. - * - * @return string - */ public function getTown(): string { - return $this->address[5]; + return $this->address[5] ?? $this->address['town_or_city']; } - /** - * Get City. - * - * @return string - * - * @see Address:getTown() - */ public function getCity(): string { - return $this->address[5]; + return $this->address[5] ?? $this->address['town_or_city']; } - /** - * County. - * - * @return string - */ public function getCounty(): string { - return $this->address[6]; + return $this->address[6] ?? $this->address['county']; + } + + public function getDistrict(): ?string + { + return $this->address[7] ?? $this->address['district'] ?? null; + } + + public function getCountry(): ?string + { + return $this->address[8] ?? $this->address['country'] ?? null; + } + + public function isResidential(): bool + { + return $this->address[9] ?? $this->address['residential']; } /** * Return a formatted array for the address. * * @param array $keys Override default key names - * - * @return array */ public function toArray(array $keys = []): array { @@ -152,37 +94,24 @@ public function toArray(array $keys = []): array * Returns a string based on the address. * * @param bool $removeEmptyElements Prevents strings having conjoining commas - * - * @return string */ - public function toString($removeEmptyElements = false): string + public function toString(bool $removeEmptyElements = false): string { - if (!$removeEmptyElements) { + if ($removeEmptyElements === false) { return implode(',', $this->address); } - return implode(',', array_filter($this->address, function ($value) { - return trim($value) !== ''; - })); + return implode(',', array_filter( + $this->address, + static fn (?string $value): bool => filled($value) + )); } - /** - * Compare two addresses to see if they are equal. - * - * @param \Laralabs\GetAddress\Responses\Address $address Address to compare - * - * @return bool - */ public function sameAs(self $address): bool { return !array_diff($this->address, $address->toArray()); } - /** - * Convert the address to a comma separated string. - * - * @return string - */ public function __toString(): string { return $this->toString(); diff --git a/src/Responses/AddressCollectionResponse.php b/src/Responses/AddressCollectionResponse.php index 2169ad7..6afec27 100755 --- a/src/Responses/AddressCollectionResponse.php +++ b/src/Responses/AddressCollectionResponse.php @@ -6,117 +6,50 @@ class AddressCollectionResponse { - /** - * @var string - */ - protected $postcode; - - /** - * Latitude. - * - * @var float - */ - protected $latitude; - - /** - * Longitude. - * - * @var float - */ - protected $longitude; - - /** - * Addresses. - * - * @var array - */ - protected $addresses = []; - - /** - * Constructor. - * - * @param string $postcode - * @param float $latitude - * @param float $longitude - * @param array $addresses - */ - public function __construct($postcode, $latitude, $longitude, array $addresses = []) - { - $this->postcode = $postcode; - $this->latitude = $latitude; - $this->longitude = $longitude; - $this->addresses = $addresses; + public function __construct( + protected ?string $postcode, + protected ?float $latitude, + protected ?float $longitude, + protected array $addresses = [] + ) { } - /** - * Get Postcode. - * - * @return string - */ - public function getPostcode(): string + public function getPostcode(): ?string { return $this->postcode; } - /** - * Get Latitude. - * - * @return float - */ - public function getLatitude(): float + public function getLatitude(): ?float { return $this->latitude; } - /** - * Get Longitude. - * - * @return float - */ - public function getLongitude(): float + public function getLongitude(): ?float { return $this->longitude; } - /** - * Get Addresses. - * - * @return array - */ public function getAddresses(): array { return $this->addresses; } - /** - * Return response as array. - * - * @return array - */ public function toArray(): array { return [ 'postcode' => $this->postcode, 'latitude' => $this->latitude, 'longitude' => $this->longitude, - 'addresses' => array_map(function ($address) { + 'addresses' => array_map(static function ($address): string|array { if ($address instanceof Address) { return $address->toString(true); } - if ($address instanceof ExpandedAddress) { - return $address->toArray(); - } - return $address; + return (array) $address; }, $this->addresses), ]; } - /** - * Return a json response. - * - * @return JsonResponse - */ public function respond(): JsonResponse { return response()->json($this->toArray()); diff --git a/src/Responses/AutocompleteCollectionResponse.php b/src/Responses/AutocompleteCollectionResponse.php new file mode 100755 index 0000000..7a2543b --- /dev/null +++ b/src/Responses/AutocompleteCollectionResponse.php @@ -0,0 +1,32 @@ +suggestions = $suggestions; + } + + public function all(): ?array + { + return $this->suggestions; + } + + public function toArray(): array + { + return [ + 'suggestions' => $this->suggestions, + ]; + } + + public function respond(): JsonResponse + { + return response()->json($this->toArray()); + } +} diff --git a/src/Responses/Domain.php b/src/Responses/Domain.php deleted file mode 100755 index 3edd5c5..0000000 --- a/src/Responses/Domain.php +++ /dev/null @@ -1,16 +0,0 @@ -name; - } -} diff --git a/src/Responses/ExpandedAddress.php b/src/Responses/ExpandedAddress.php index ed3c8b2..0fa5ada 100755 --- a/src/Responses/ExpandedAddress.php +++ b/src/Responses/ExpandedAddress.php @@ -2,186 +2,93 @@ namespace Laralabs\GetAddress\Responses; -class ExpandedAddress -{ - /** - * Address string. - * - * @var array - */ - protected $address = []; +use Illuminate\Contracts\Support\Arrayable; - /** - * Constructor. - * - * @param string $address - * - * @return void - */ - public function __construct($address) +class ExpandedAddress implements Arrayable +{ + public function __construct(protected array $address = []) { - $this->address = $address; } - /** - * Get Thoroughfare. - * - * @return string - */ public function getThoroughfare(): string { return $this->address['thoroughfare']; } - /** - * Get Building Name. - * - * @return string - */ public function getBuildingName(): string { return $this->address['building_name']; } - /** - * Get Sub Building Name. - * - * @return string - */ public function getSubBuildingName(): string { return $this->address['sub_building_name']; } - /** - * Get Building Number. - * - * @return string - */ public function getBuildingNumber(): string { return $this->address['building_number']; } - /** - * Get Sub Building Number. - * - * @return string - */ public function getSubBuildingNumber(): string { return $this->address['sub_building_number']; } - /** - * Get Line 1. - * - * @return string - */ public function getLine1(): string { return $this->address['line_1']; } - /** - * Get Line 2. - * - * @return string - */ public function getLine2(): string { return $this->address['line_2']; } - /** - * Get Line 3. - * - * @return string - */ public function getLine3(): string { return $this->address['line_3']; } - /** - * Get Line 4. - * - * @return string - */ public function getLine4(): string { return $this->address['line_4']; } - /** - * Get Line. - * - * @param int $line - * - * @return string - */ - public function getLine($line): string + public function getLine(int $line): ?string { - return $this->address['line_'.$line]; + if ($line > 4) { + return null; + } + + return $this->address['line_' . $line]; } - /** - * Get Locality. - * - * @return string - */ public function getLocality(): string { return $this->address['locality']; } - /** - * Get Town. - * - * @return string - */ public function getTown(): string { return $this->address['town_or_city']; } - /** - * Get City. - * - * @return string - * - * @see ExpandedAddress:getTown() - */ public function getCity(): string { return $this->address['town_or_city']; } - /** - * Get County. - * - * @return string - */ public function getCounty(): string { return $this->address['county']; } - /** - * Get District. - * - * @return string - */ public function getDistrict(): string { return $this->address['district']; } - /** - * Get Country. - * - * @return string - */ public function getCountry(): string { return $this->address['country']; @@ -189,51 +96,28 @@ public function getCountry(): string /** * Return a formatted array for the address. - * - * @param array $keys Override default key names - * - * @return array */ - public function toArray(array $keys = []): array + public function toArray(): array { return array_merge([ 'formatted_string' => $this->toString(true), ], $this->address); } - /** - * Returns a string based on the address. - * - * @param bool $removeEmptyElements Prevents strings having conjoining commas - * - * @return string - */ - public function toString($removeEmptyElements = false): string + public function toString(bool $removeEmptyElements = false): string { - if (!$removeEmptyElements) { - return implode(',', $this->address['formatted_address']); - } + $separator = isset($this->address['formatted_address']) ? ', ' : ','; - return implode(', ', array_filter($this->address['formatted_address'])); - } + if ($removeEmptyElements === false) { + return implode($separator, $this->address['formatted_address']); + } - /** - * Compare two addresses to see if they are equal. - * - * @param \Laralabs\GetAddress\Responses\Address $address Address to compare - * - * @return bool - */ - public function sameAs(Address $address): bool - { - return !array_diff($this->address, $address->toArray()); + return implode($separator, array_filter( + $this->address['formatted_address'] ?? $this->address, + static fn (?string $value): bool => filled($value) + )); } - /** - * Convert the address to a comma separated string. - * - * @return string - */ public function __toString(): string { return $this->toString(); diff --git a/src/Responses/Ip.php b/src/Responses/Ip.php deleted file mode 100755 index 27adde0..0000000 --- a/src/Responses/Ip.php +++ /dev/null @@ -1,16 +0,0 @@ -name; - } -} diff --git a/src/Responses/PrivateAddress.php b/src/Responses/PrivateAddress.php deleted file mode 100755 index 9558d02..0000000 --- a/src/Responses/PrivateAddress.php +++ /dev/null @@ -1,75 +0,0 @@ -privateAddressId = $addressId; - } - - /** - * Create a new private address object ready to submit to the API. - * - * @param string $line1 - * @param string $line2 - * @param string $line3 - * @param string $line4 - * @param string $locality - * @param string $townOrCity - * @param string $county - * - * @return \Laralabs\GetAddress\Responses\PrivateAddress - */ - public static function create( - $line1 = '', - $line2 = '', - $line3 = '', - $line4 = '', - $locality = '', - $townOrCity = '', - $county = '' - ) { - $address = implode(',', func_get_args()); - - return new static(null, $address); - } - - /** - * Get Address ID. - * - * @return string - */ - public function getAddressId(): string - { - return $this->privateAddressId; - } - - /** - * Is Saved. - * - * @return bool - */ - public function isSaved(): bool - { - return $this->privateAddressId !== null; - } -} diff --git a/src/Responses/PrivateAddressResponse.php b/src/Responses/PrivateAddressResponse.php deleted file mode 100755 index c3410e5..0000000 --- a/src/Responses/PrivateAddressResponse.php +++ /dev/null @@ -1,37 +0,0 @@ -message = $message; - } - - /** - * Get Message. - * - * @return string - */ - public function getMessage(): string - { - return $this->message; - } -} diff --git a/src/Responses/SingleAddressCollectionResponse.php b/src/Responses/SingleAddressCollectionResponse.php new file mode 100755 index 0000000..67366c7 --- /dev/null +++ b/src/Responses/SingleAddressCollectionResponse.php @@ -0,0 +1,42 @@ +postcode = $address['postcode'] ?? null; + $this->latitude = $address['latitude'] ?? null; + $this->longitude = $address['longitude'] ?? null; + $this->address = $address; + $this->expand = $expand; + + parent::__construct($this->postcode, $this->latitude, $this->longitude, [$this->address]); + } + + public function getAddress(): ExpandedAddress|Address + { + return $this->expand ? new ExpandedAddress($this->address) : new Address($this->address); + } + + public function toArray(): array + { + return [ + 'postcode' => $this->postcode, + 'latitude' => $this->latitude, + 'longitude' => $this->longitude, + 'address' => $this->address, + ]; + } +} diff --git a/src/Responses/Usage.php b/src/Responses/Usage.php deleted file mode 100755 index 4618378..0000000 --- a/src/Responses/Usage.php +++ /dev/null @@ -1,135 +0,0 @@ -count = (int) $count; - $this->limits = [ - (int) $limit1, - (int) $limit2, - ]; - } - - /** - * Get Count. - * - * @return int - */ - public function getCount(): int - { - return $this->count; - } - - /** - * Get Limit 1. - * - * @return int - */ - public function getLimit1(): int - { - return $this->limits[0]; - } - - /** - * Get Limit 2. - * - * @return int - */ - public function getLimit2(): int - { - return $this->limits[1]; - } - - /** - * Get Limit. - * - * @param int $limitNumber - * - * @return int - */ - public function getLimit($limitNumber): int - { - return $this->limits[$limitNumber - 1]; - } - - /** - * Get Limits. - * - * @return array - */ - public function getLimits(): array - { - return $this->limits; - } - - /** - * Requests Remaining. - * - * @param bool $untilSlowed Will return requests remaining until calls are slowed by getAddress - * - * @return int - */ - public function requestsRemaining($untilSlowed = false): int - { - $limit = $untilSlowed ? $this->limits[0] : $this->limits[1]; - - return $limit - $this->count; - } - - /** - * Requests Remaining Until Slowed. - * - * @return int - */ - public function requestsRemainingUntilSlowed(): int - { - return $this->requestsRemaining(true); - } - - /** - * Has Exceeded Limit. - * - * @return bool - */ - public function hasExceededLimit(): bool - { - return $this->count > $this->limits[1]; - } - - /** - * Returns whether the initial limit has been reached and whether subsequent - * requests have been slowed down by getAddress. - * - * @return bool - */ - public function isRestricted(): bool - { - return $this->count >= $this->limits[0]; - } -} diff --git a/src/Responses/Webhook.php b/src/Responses/Webhook.php deleted file mode 100755 index 061f63b..0000000 --- a/src/Responses/Webhook.php +++ /dev/null @@ -1,54 +0,0 @@ -id = $id; - $this->url = $url; - } - - /** - * Get Webhook ID. - * - * @return string - */ - public function getWebhookId(): string - { - return $this->id; - } - - /** - * Get Webhook Url. - * - * @return string - */ - public function getWebhookUrl(): string - { - return $this->url; - } -} diff --git a/src/Responses/WebhookResponse.php b/src/Responses/WebhookResponse.php deleted file mode 100755 index 552a6b1..0000000 --- a/src/Responses/WebhookResponse.php +++ /dev/null @@ -1,52 +0,0 @@ -message = $message; - $this->hooks = $hooks; - } - - /** - * Get Message. - * - * @return string - */ - public function getMessage(): string - { - return $this->message; - } - - /** - * Get Hooks. - * - * @return array - */ - public function getHooks(): array - { - return $this->hooks; - } -} diff --git a/src/Responses/WhitelistResponse.php b/src/Responses/WhitelistResponse.php deleted file mode 100755 index f3d125a..0000000 --- a/src/Responses/WhitelistResponse.php +++ /dev/null @@ -1,52 +0,0 @@ -message = $message; - $this->items = $items; - } - - /** - * Get Message. - * - * @return string - */ - public function getMessage(): string - { - return $this->message; - } - - /** - * Get Items. - * - * @return array - */ - public function getItems(): array - { - return $this->items; - } -} diff --git a/tests/Feature/GetAddressTest.php b/tests/Feature/GetAddressTest.php new file mode 100644 index 0000000..985805e --- /dev/null +++ b/tests/Feature/GetAddressTest.php @@ -0,0 +1,197 @@ +getHttpFake(); + + $results = GetAddress::find('B13 9SZ'); + + $this->assertCount(14, $results->getAddresses()); + $this->assertEquals('B13 9SZ', $results->getPostcode()); + } + + /** @test */ + public function it_can_do_a_postcode_lookup_with_property_number_using_find(): void + { + ResponseFactory::make('singleFindResponse.json')->getHttpFake(); + + $results = get_address()->find('B13 9SZ', 32); + + $this->assertCount(1, $results->getAddresses()); + $this->assertEquals('B13 9SZ', $results->getPostcode()); + } + + /** @test */ + public function it_can_do_a_postcode_lookup_with_property_number_using_find_and_results_from_the_cache(): void + { + config()->set('getaddress.enable_cache', true); + + $manager = new Manager(); + $manager->responseToCache( + ResponseFactory::make('successfulFindResponse.json')->makeAddressCollectionResponse() + ); + + $this->assertEquals(14, CachedAddress::count()); + + $results = GetAddress::find('B13 9SZ'); + + $this->assertCount(14, $results->getAddresses()); + $this->assertEquals('B13 9SZ', $results->getPostcode()); + } + + /** @test */ + public function it_can_do_a_postcode_lookup_using_find_and_results_from_the_cache(): void + { + config()->set('getaddress.enable_cache', true); + + $manager = new Manager(); + $manager->responseToCache( + ResponseFactory::make('successfulFindResponse.json')->makeAddressCollectionResponse() + ); + + $this->assertEquals(14, CachedAddress::count()); + + $results = get_address()->find('B13 9SZ', 32); + + $this->assertCount(2, $results->getAddresses()); + $this->assertEquals('B13 9SZ', $results->getPostcode()); + } + + /** @test */ + public function it_can_do_a_postcode_lookup_using_find_then_store_and_return_results_from_the_cache(): void + { + Carbon::setTestNow('2024-02-14 12:00:00'); + + config()->set('getaddress.enable_cache', true); + + ResponseFactory::make('successfulFindResponse.json')->getHttpFake(); + + CachedAddress::factory()->create(['line_1' => '1 Example Street', 'postcode' => 'B13 9SZ']); + + $this->assertEquals(1, CachedAddress::count()); + + Carbon::setTestNow('2024-03-15 12:00:00'); + + $results = get_address()->expand()->find('B13 9SZ'); + + $this->assertEquals(14, CachedAddress::count()); + + $this->assertCount(14, $results->getAddresses()); + $this->assertEquals('B13 9SZ', $results->getPostcode()); + } + + /** @test */ + public function it_can_do_a_postcode_lookup_using_find_and_store_response_in_cache(): void + { + config()->set('getaddress.enable_cache', true); + + ResponseFactory::make('successfulFindResponse.json')->getHttpFake(); + + $this->assertEquals(0, CachedAddress::count()); + + $results = get_address()->find('B13 9SZ'); + + $this->assertEquals(14, CachedAddress::count()); + + $this->assertCount(14, $results->getAddresses()); + $this->assertEquals('B13 9SZ', $results->getPostcode()); + } + + /** @test */ + public function it_can_perform_an_autocomplete_request(): void + { + ResponseFactory::make('successfulAutocompleteResponse.json')->getHttpFake(); + + $results = GetAddress::autocomplete('32 Clarence'); + + $this->assertCount(6, $results->all()); + } + + /** @test */ + public function it_can_perform_an_autocomplete_request_and_respond_with_json_response(): void + { + ResponseFactory::make('successfulAutocompleteResponse.json')->getHttpFake(); + + $results = GetAddress::autocomplete('32 Clarence'); + + $this->assertCount(6, $results->respond()->getOriginalContent()['suggestions']); + } + + /** @test */ + public function it_can_perform_a_get_request_for_an_autocomplete_result_item(): void + { + config()->set('getaddress.expanded_results', true); + + ResponseFactory::make('successfulGetResponse.json')->getHttpFake(); + + $results = GetAddress::get('NmNhMTg3ZjBkZmQ1OTg0IDEwMzgwMzg1IDdjZmFmNTA5OTI3YjkzZQ=='); + + $this->assertEquals('SK10 5GR', $results->getPostcode()); + $this->assertEquals(53.2998, $results->getLatitude()); + $this->assertEquals(-2.102, $results->getLongitude()); + $this->assertCount(1, $results->getAddresses()); + $this->assertMatchesJsonSnapshot($results->toArray()); + } + + /** @test */ + public function it_can_handle_a_429_error_and_reattempt_on_find(): void + { + ResponseFactory::make('successfulFindResponse.json')->getHttpErrorFake(429); + + $results = get_address()->find('B13 9SZ'); + + $this->assertCount(14, $results->getAddresses()); + } + + /** @test */ + public function it_can_handle_a_429_error_and_reattempt_on_autocomplete(): void + { + ResponseFactory::make('successfulAutocompleteResponse.json')->getHttpErrorFake(429); + + $results = GetAddress::autocomplete('32 Clarence'); + + $this->assertCount(6, $results->all()); + } + + /** @test */ + public function it_can_handle_a_429_error_and_reattempt_on_get(): void + { + ResponseFactory::make('successfulGetResponse.json')->getHttpErrorFake(429); + + $results = GetAddress::get('NmNhMTg3ZjBkZmQ1OTg0IDEwMzgwMzg1IDdjZmFmNTA5OTI3YjkzZQ=='); + + $this->assertCount(1, $results->getAddresses()); + } + + /** @test */ + public function it_can_handle_a_server_error_and_throw_an_exception(): void + { + ResponseFactory::make('successfulGetResponse.json')->getHttpErrorFake(500); + + try { + GetAddress::get('NmNhMTg3ZjBkZmQ1OTg0IDEwMzgwMzg1IDdjZmFmNTA5OTI3YjkzZQ=='); + } catch (ServerError $exception) { + $this->assertEquals('getAddress.io is currently having issues.', $exception->getMessage()); + return; + } + + $this->fail('ServerError exception not thrown'); + } +} diff --git a/tests/Feature/__snapshots__/GetAddressTest__it_can_perform_a_get_request_for_an_autocomplete_result_item__1.json b/tests/Feature/__snapshots__/GetAddressTest__it_can_perform_a_get_request_for_an_autocomplete_result_item__1.json new file mode 100644 index 0000000..325d01f --- /dev/null +++ b/tests/Feature/__snapshots__/GetAddressTest__it_can_perform_a_get_request_for_an_autocomplete_result_item__1.json @@ -0,0 +1,32 @@ +{ + "postcode": "SK10 5GR", + "latitude": 53.2998, + "longitude": -2.102, + "address": { + "postcode": "SK10 5GR", + "latitude": 53.2998, + "longitude": -2.102, + "formatted_address": [ + "Apartment 32", + "Clarence Mill", + "Clarence Road", + "Bollington, Macclesfield", + "Cheshire" + ], + "thoroughfare": "Clarence Road", + "building_name": "Clarence Mill", + "sub_building_name": "", + "sub_building_number": "32", + "building_number": "", + "line_1": "Apartment 32", + "line_2": "Clarence Mill", + "line_3": "Clarence Road", + "line_4": "", + "locality": "Bollington", + "town_or_city": "Macclesfield", + "county": "Cheshire", + "district": "Cheshire East", + "country": "England", + "residential": true + } +} diff --git a/tests/Support/Responses/ResponseFactory.php b/tests/Support/Responses/ResponseFactory.php new file mode 100644 index 0000000..d62759b --- /dev/null +++ b/tests/Support/Responses/ResponseFactory.php @@ -0,0 +1,94 @@ +fileName = $fileName; + $this->response = file_get_contents(__DIR__ . '/examples/' . $fileName); + $this->decodedResponse = json_decode($this->response, true); + $this->expand = $expand; + } + + public static function make(string $fileName, bool $expand = false): self + { + return new self($fileName, $expand); + } + + public function getHttpFake(?array $attributes = null): Factory + { + return Http::fake($attributes ?? [ + '*' => Http::response(ResponseFactory::make($this->fileName)->getResponse()), + ]); + } + + public function getHttpErrorFake(int $code, mixed $content = []): Factory + { + return Http::fake([ + '*' => function () use ($code, $content): PromiseInterface { + static $callCount = 0; + + $callCount += 1; + + if ($callCount === 1) { + return Http::response($content, $code); + } + + return Http::response(ResponseFactory::make($this->fileName)->getResponse()); + }, + ]); + } + + public function getResponse(): string + { + return $this->response; + } + + public function getDecodedResponse(): array + { + return $this->decodedResponse; + } + + public function makeAddressCollectionResponse(): AddressCollectionResponse + { + return new AddressCollectionResponse( + Arr::get($this->decodedResponse, 'postcode'), + Arr::get($this->decodedResponse, 'latitude'), + Arr::get($this->decodedResponse, 'longitude'), + $this->hydrateAddresses() + ); + } + + public function makeSingleAddressCollectionResponse(): SingleAddressCollectionResponse + { + return new SingleAddressCollectionResponse($this->decodedResponse, $this->expand); + } + + private function hydrateAddresses(): array + { + return collect($this->decodedResponse['addresses'])->transform( + fn (array $address): ExpandedAddress|Address => $this->expand ? new ExpandedAddress( + $address + ) : new Address(array_values($address)) + )->toArray(); + } +} diff --git a/tests/Support/Responses/examples/singleFindResponse.json b/tests/Support/Responses/examples/singleFindResponse.json new file mode 100644 index 0000000..586db68 --- /dev/null +++ b/tests/Support/Responses/examples/singleFindResponse.json @@ -0,0 +1 @@ +{"postcode":"B13 9SZ","latitude":52.437353914285715,"longitude":-1.8828399142857144,"addresses":[{"line_1":"32 Clarence Road","line_2":" ","line_3":" ","line_4":" ","locality":" Moseley","town_or_city":" Birmingham","county":" West Midlands"}]} \ No newline at end of file diff --git a/tests/Support/Responses/examples/successfulAutocompleteResponse.json b/tests/Support/Responses/examples/successfulAutocompleteResponse.json new file mode 100644 index 0000000..1882001 --- /dev/null +++ b/tests/Support/Responses/examples/successfulAutocompleteResponse.json @@ -0,0 +1 @@ +{"suggestions":[{"address":"Apartment 32, Clarence Mill, Clarence Road, Bollington, Macclesfield","url":"\/get\/NmNhMTg3ZjBkZmQ1OTg0IDEwMzgwMzg1IDdjZmFmNTA5OTI3YjkzZQ==","id":"NmNhMTg3ZjBkZmQ1OTg0IDEwMzgwMzg1IDdjZmFmNTA5OTI3YjkzZQ=="},{"address":"Revive Digital Solutions Ltd, Clarence Street Chambers, 32 Clarence Street, Southend-on-Sea","url":"\/get\/MmY4NmNmODQ3ODE1NWE2IDIzNDEyMDgwNTcgN2NmYWY1MDk5MjdiOTNl","id":"MmY4NmNmODQ3ODE1NWE2IDIzNDEyMDgwNTcgN2NmYWY1MDk5MjdiOTNl"},{"address":"Looking Good Consultants Ltd, Clarence Street Chambers, 32 Clarence Street, Southend-on-Sea","url":"\/get\/NDM3YjJiYzk2NGE0M2RjIDIzNDA3Nzc3NjggN2NmYWY1MDk5MjdiOTNl","id":"NDM3YjJiYzk2NGE0M2RjIDIzNDA3Nzc3NjggN2NmYWY1MDk5MjdiOTNl"},{"address":"Maven Fm Services Ltd, Clarence Street Chambers, 32 Clarence Street, Southend-on-Sea","url":"\/get\/YmVkYjY1MDU4ODYwY2I5IDIzNDA4MTM1NjAgN2NmYWY1MDk5MjdiOTNl","id":"YmVkYjY1MDU4ODYwY2I5IDIzNDA4MTM1NjAgN2NmYWY1MDk5MjdiOTNl"},{"address":"Avamore Finance 1 Ltd, Clarence Street Chambers, 32 Clarence Street, Southend-on-Sea","url":"\/get\/NGE1ZmY1YTMwYTBlM2JhIDIzNDA4NTc2NjcgN2NmYWY1MDk5MjdiOTNl","id":"NGE1ZmY1YTMwYTBlM2JhIDIzNDA4NTc2NjcgN2NmYWY1MDk5MjdiOTNl"},{"address":"Hyde Built Homes Ltd, Clarence Street Chambers, 32 Clarence Street, Southend-on-Sea","url":"\/get\/ZDZlZDA0YzA5MjNiM2RjIDIzNDEwNzE2MzMgN2NmYWY1MDk5MjdiOTNl","id":"ZDZlZDA0YzA5MjNiM2RjIDIzNDEwNzE2MzMgN2NmYWY1MDk5MjdiOTNl"}]} \ No newline at end of file diff --git a/tests/Support/Responses/examples/successfulFindResponse.json b/tests/Support/Responses/examples/successfulFindResponse.json new file mode 100644 index 0000000..19e2c3b --- /dev/null +++ b/tests/Support/Responses/examples/successfulFindResponse.json @@ -0,0 +1 @@ +{"postcode":"B13 9SZ","latitude":52.437353914285715,"longitude":-1.8828399142857144,"addresses":[{"line_1":"32 Clarence Road","line_2":" ","line_3":" ","line_4":" ","locality":" Moseley","town_or_city":" Birmingham","county":" West Midlands"},{"line_1":"32a Clarence Road","line_2":" ","line_3":" ","line_4":" ","locality":" Moseley","town_or_city":" Birmingham","county":" West Midlands"},{"line_1":"34 Clarence Road","line_2":" ","line_3":" ","line_4":" ","locality":" Moseley","town_or_city":" Birmingham","county":" West Midlands"},{"line_1":"36 Clarence Road","line_2":" ","line_3":" ","line_4":" ","locality":" Moseley","town_or_city":" Birmingham","county":" West Midlands"},{"line_1":"38 Clarence Road","line_2":" ","line_3":" ","line_4":" ","locality":" Moseley","town_or_city":" Birmingham","county":" West Midlands"},{"line_1":"40 Clarence Road","line_2":" ","line_3":" ","line_4":" ","locality":" Moseley","town_or_city":" Birmingham","county":" West Midlands"},{"line_1":"41 Clarence Road","line_2":" ","line_3":" ","line_4":" ","locality":" Moseley","town_or_city":" Birmingham","county":" West Midlands"},{"line_1":"43 Clarence Road","line_2":" ","line_3":" ","line_4":" ","locality":" Moseley","town_or_city":" Birmingham","county":" West Midlands"},{"line_1":"45 Clarence Road","line_2":" ","line_3":" ","line_4":" ","locality":" Moseley","town_or_city":" Birmingham","county":" West Midlands"},{"line_1":"47 Clarence Road","line_2":" ","line_3":" ","line_4":" ","locality":" Moseley","town_or_city":" Birmingham","county":" West Midlands"},{"line_1":"49 Clarence Road","line_2":" ","line_3":" ","line_4":" ","locality":" Moseley","town_or_city":" Birmingham","county":" West Midlands"},{"line_1":"51 Clarence Road","line_2":" ","line_3":" ","line_4":" ","locality":" Moseley","town_or_city":" Birmingham","county":" West Midlands"},{"line_1":"Flat 1","line_2":" 36 Clarence Road","line_3":" ","line_4":" ","locality":" Moseley","town_or_city":" Birmingham","county":" West Midlands"},{"line_1":"Flat 2","line_2":" 36 Clarence Road","line_3":" ","line_4":" ","locality":" Moseley","town_or_city":" Birmingham","county":" West Midlands"}]} \ No newline at end of file diff --git a/tests/Support/Responses/examples/successfulGetResponse.json b/tests/Support/Responses/examples/successfulGetResponse.json new file mode 100644 index 0000000..9367886 --- /dev/null +++ b/tests/Support/Responses/examples/successfulGetResponse.json @@ -0,0 +1 @@ +{"postcode":"SK10 5GR","latitude":53.2998,"longitude":-2.102,"formatted_address":["Apartment 32","Clarence Mill","Clarence Road","Bollington, Macclesfield","Cheshire"],"thoroughfare":"Clarence Road","building_name":"Clarence Mill","sub_building_name":"","sub_building_number":"32","building_number":"","line_1":"Apartment 32","line_2":"Clarence Mill","line_3":"Clarence Road","line_4":"","locality":"Bollington","town_or_city":"Macclesfield","county":"Cheshire","district":"Cheshire East","country":"England","residential":true} \ No newline at end of file diff --git a/tests/TestCase.php b/tests/TestCase.php index f171d8f..87fb822 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -6,12 +6,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase { - public function setUp() - { - parent::setUp(); - } - - protected function getPackageProviders($app) + protected function getPackageProviders($app): array //phpcs:ignore { return [ GetAddressServiceProvider::class, diff --git a/tests/Unit/Cache/ManagerTest.php b/tests/Unit/Cache/ManagerTest.php new file mode 100644 index 0000000..bdcf478 --- /dev/null +++ b/tests/Unit/Cache/ManagerTest.php @@ -0,0 +1,87 @@ +manager = new Manager(); + } + + /** @test */ + public function it_can_check_for_cached_addresses_with_postcode(): void + { + CachedAddress::factory()->create(['line_1' => '1 Example Street', 'postcode' => 'ABC 123']); + CachedAddress::factory()->create(['line_1' => '2 Example Street', 'postcode' => 'ABC 123']); + CachedAddress::factory()->create(['line_1' => '3 Example Street', 'postcode' => 'ABC 321']); + + $result = $this->manager->checkCache('ABC 123', null); + + $this->assertCount(2, $result['addresses'] ?? 0); + } + + /** @test */ + public function it_can_check_for_expanded_cached_addresses_with_postcode(): void + { + CachedAddress::factory()->create(['line_1' => '1 Example Street', 'postcode' => 'ABC 123']); + CachedAddress::factory()->create(['line_1' => '2 Example Street', 'postcode' => 'ABC 123']); + CachedAddress::factory()->create(['line_1' => '3 Example Street', 'postcode' => 'ABC 321']); + + $result = $this->manager->expand()->checkCache('ABC 123', null); + + $this->assertCount(2, $result['addresses'] ?? 0); + } + + /** @test */ + public function it_can_check_for_cached_addresses_with_postcode_and_property_number(): void + { + CachedAddress::factory()->create(['line_1' => '1 Example Street', 'postcode' => 'ABC 123']); + CachedAddress::factory()->create(['line_1' => '2 Example Street', 'postcode' => 'ABC 123']); + CachedAddress::factory()->create(['line_1' => '3 Example Street', 'postcode' => 'ABC 321']); + + $result = $this->manager->checkCache('ABC 123', 1); + + $this->assertCount(1, $result['addresses'] ?? 0); + + $this->assertEquals('1 Example Street, Moseley, Birmingham, West Midlands', $result['addresses'][0]); + $this->assertEquals('ABC 123', $result['postcode']); + } + + /** @test */ + public function it_can_store_the_address_collection_response_to_cache(): void + { + $response = ResponseFactory::make('successfulFindResponse.json') + ->makeAddressCollectionResponse(); + $expectedAddress = $response->getAddresses()[0]; + + $this->assertEquals(0, CachedAddress::count()); + + $this->manager->responseToCache($response); + + $this->assertEquals(14, CachedAddress::count()); + $this->assertEquals($expectedAddress->getLine1(), CachedAddress::first()->line_1); + $this->assertEquals($expectedAddress->getLine2(), CachedAddress::first()->line_2); + $this->assertEquals($expectedAddress->getLine3(), CachedAddress::first()->line_3); + $this->assertEquals($expectedAddress->getLine4(), CachedAddress::first()->line_4); + $this->assertEquals($expectedAddress->getLocality(), CachedAddress::first()->locality); + $this->assertEquals($expectedAddress->getTown(), CachedAddress::first()->town_or_city); + $this->assertEquals($expectedAddress->getCity(), CachedAddress::first()->town_or_city); + $this->assertEquals($expectedAddress->getCounty(), CachedAddress::first()->county); + $this->assertEquals($response->getPostcode(), CachedAddress::first()->postcode); + $this->assertIsFloat(CachedAddress::first()->longitude); + $this->assertIsFloat(CachedAddress::first()->latitude); + } +} diff --git a/tests/Unit/Exceptions/HandlerTest.php b/tests/Unit/Exceptions/HandlerTest.php new file mode 100644 index 0000000..dd07e4f --- /dev/null +++ b/tests/Unit/Exceptions/HandlerTest.php @@ -0,0 +1,99 @@ +assertEquals('getAddress responded with a 418 http status', $exception->getMessage()); + + return; + } + + $this->fail('Unknown exception not thrown'); + } + + /** @test */ + public function it_throws_an_invalid_postcode_exception(): void + { + try { + Handler::throwException(400); + } catch (InvalidPostcode $exception) { + $this->assertEquals('The postcode you provided was not a valid format.', $exception->getMessage()); + + return; + } + + $this->fail('InvalidPostcode exception not thrown'); + } + + /** @test */ + public function it_throws_a_forbidden_exception(): void + { + try { + Handler::throwException(401); + } catch (Forbidden $exception) { + $this->assertEquals('Your API key is not valid for this request.', $exception->getMessage()); + + return; + } + + $this->fail('Forbidden exception not thrown'); + } + + /** @test */ + public function it_throws_a_postcode_not_found_exception(): void + { + try { + Handler::throwException(404); + } catch (PostcodeNotFound $exception) { + $this->assertEquals('The postcode you provided could not be found.', $exception->getMessage()); + + return; + } + + $this->fail('PostcodeNotFound exception not thrown'); + } + + /** @test */ + public function it_throws_a_too_many_requests_exception(): void + { + try { + Handler::throwException(429); + } catch (TooManyRequests $exception) { + $this->assertEquals('You have made too many requests for this key.', $exception->getMessage()); + + return; + } + + $this->fail('TooManyRequests exception not thrown'); + } + + /** @test */ + public function it_throws_a_server_exception(): void + { + try { + Handler::throwException(500); + } catch (ServerError $exception) { + $this->assertEquals('getAddress.io is currently having issues.', $exception->getMessage()); + + return; + } + + $this->fail('ServerError exception not thrown'); + } +} diff --git a/tests/Unit/Http/ClientTest.php b/tests/Unit/Http/ClientTest.php new file mode 100644 index 0000000..002cee7 --- /dev/null +++ b/tests/Unit/Http/ClientTest.php @@ -0,0 +1,21 @@ +assertFalse($client->isAdminMode()); + + $client->admin(); + + $this->assertTrue($client->isAdminMode()); + } +} diff --git a/tests/Unit/Models/CachedAddressTest.php b/tests/Unit/Models/CachedAddressTest.php new file mode 100644 index 0000000..db98e14 --- /dev/null +++ b/tests/Unit/Models/CachedAddressTest.php @@ -0,0 +1,34 @@ +create(); + + $this->assertEquals( + '123 Example Street, Moseley, Birmingham, West Midlands', + $cachedAddress->getFormattedStringAttribute() + ); + } + + /** @test */ + public function it_can_convert_the_cached_address_to_string_without_stripping_empty_elements(): void + { + $cachedAddress = CachedAddress::factory()->create(); + + $this->assertEquals( + '123 Example Street,,,,Moseley,Birmingham,West Midlands', + $cachedAddress->toString() + ); + } +} diff --git a/tests/Unit/Responses/AddressCollectionResponseTest.php b/tests/Unit/Responses/AddressCollectionResponseTest.php new file mode 100644 index 0000000..77e39d5 --- /dev/null +++ b/tests/Unit/Responses/AddressCollectionResponseTest.php @@ -0,0 +1,89 @@ +response = ResponseFactory::make('successfulFindResponse.json') + ->makeAddressCollectionResponse(); + } + + /** @test */ + public function it_can_get_the_postcode(): void + { + $this->assertEquals('B13 9SZ', $this->response->getPostcode()); + } + + /** @test */ + public function it_can_get_the_latitude(): void + { + $this->assertEquals(52.437353914285715, $this->response->getLatitude()); + } + + /** @test */ + public function it_can_get_the_longitude(): void + { + $this->assertEquals(-1.8828399142857144, $this->response->getLongitude()); + } + + /** @test */ + public function it_can_get_the_addresses(): void + { + $addresses = collect($this->response->getAddresses()); + + $this->assertCount(14, $addresses); + $this->assertInstanceOf(Address::class, $addresses->first()); + } + + /** @test */ + public function it_can_transform_to_the_expected_array(): void + { + $this->assertMatchesJsonSnapshot($this->response->toArray()); + } + + /** @test */ + public function it_can_transform_to_the_expected_array_with_expanded_addresses(): void + { + $response = ResponseFactory::make('successfulFindResponse.json', true) + ->makeAddressCollectionResponse(); + + $this->assertMatchesJsonSnapshot($response->toArray()); + } + + /** @test */ + public function it_can_get_a_json_response(): void + { + $this->assertInstanceOf(JsonResponse::class, $this->response->respond()); + } + + /** @test */ + public function it_can_instantiate_a_new_address_collection_response(): void + { + $response = new AddressCollectionResponse( + 'B13 9SZ', + 52.437353914285715, + -1.8828399142857144, + [] + ); + + $this->assertEquals('B13 9SZ', $response->getPostcode()); + $this->assertEquals(52.437353914285715, $response->getLatitude()); + $this->assertEquals(-1.8828399142857144, $response->getLongitude()); + $this->assertCount(0, $response->getAddresses()); + } +} diff --git a/tests/Unit/Responses/AddressTest.php b/tests/Unit/Responses/AddressTest.php new file mode 100644 index 0000000..d17de1e --- /dev/null +++ b/tests/Unit/Responses/AddressTest.php @@ -0,0 +1,123 @@ +address = ResponseFactory::make('singleFindResponse.json') + ->makeAddressCollectionResponse()->getAddresses()[0]; + } + + /** @test */ + public function it_can_get_line_one(): void + { + $this->assertEquals('32 Clarence Road', $this->address->getLine1()); + } + + /** @test */ + public function it_can_get_line_two(): void + { + $this->assertEquals(' ', $this->address->getLine2()); + } + + /** @test */ + public function it_can_get_line_three(): void + { + $this->assertEquals(' ', $this->address->getLine3()); + } + + /** @test */ + public function it_can_get_line_four(): void + { + $this->assertEquals(' ', $this->address->getLine4()); + } + + /** @test */ + public function it_can_get_a_line_by_the_given_number(): void + { + $this->assertEquals('32 Clarence Road', $this->address->getLine(1)); + $this->assertNull($this->address->getLine(5)); + } + + /** @test */ + public function it_can_get_the_locality(): void + { + $this->assertEquals(' Moseley', $this->address->getLocality()); + } + + /** @test */ + public function it_can_get_the_town(): void + { + $this->assertEquals(' Birmingham', $this->address->getTown()); + } + + /** @test */ + public function it_can_get_the_city(): void + { + $this->assertEquals(' Birmingham', $this->address->getCity()); + } + + /** @test */ + public function it_can_get_the_county(): void + { + $this->assertEquals(' West Midlands', $this->address->getCounty()); + } + + /** @test */ + public function it_can_get_the_district(): void + { + $this->assertNull($this->address->getDistrict()); + } + + /** @test */ + public function it_can_get_the_country(): void + { + $this->assertNull($this->address->getCountry()); + } + + /** @test */ + public function it_can_transform_address_to_string_and_remove_empty_elements(): void + { + $this->assertEquals( + '32 Clarence Road, Moseley, Birmingham, West Midlands', + $this->address->toString(true) + ); + } + + /** @test */ + public function it_can_transform_address_to_string(): void + { + $this->assertEquals( + '32 Clarence Road, , , , Moseley, Birmingham, West Midlands', + $this->address->toString() + ); + } + + /** @test */ + public function it_can_cast_the_address_to_a_string(): void + { + $this->assertEquals( + '32 Clarence Road, , , , Moseley, Birmingham, West Midlands', + (string) $this->address + ); + } + + /** @test */ + public function it_can_can_check_if_two_address_responses_are_the_same(): void + { + $address = ResponseFactory::make('singleFindResponse.json') + ->makeAddressCollectionResponse()->getAddresses()[0]; + + $this->assertTrue($this->address->sameAs($address)); + } +} diff --git a/tests/Unit/Responses/ExpandedAddressTest.php b/tests/Unit/Responses/ExpandedAddressTest.php new file mode 100644 index 0000000..25630de --- /dev/null +++ b/tests/Unit/Responses/ExpandedAddressTest.php @@ -0,0 +1,154 @@ +address = ResponseFactory::make('successfulGetResponse.json', true) + ->makeSingleAddressCollectionResponse()->getAddress(); + } + + /** @test */ + public function it_can_get_the_thoroughfare(): void + { + $this->assertEquals('Clarence Road', $this->address->getThoroughfare()); + } + + /** @test */ + public function it_can_get_the_building_name(): void + { + $this->assertEquals('Clarence Mill', $this->address->getBuildingName()); + } + + /** @test */ + public function it_can_get_the_sub_building_name(): void + { + $this->assertEquals('', $this->address->getSubBuildingName()); + } + + /** @test */ + public function it_can_get_the_building_number(): void + { + $this->assertEquals('', $this->address->getBuildingNumber()); + } + + /** @test */ + public function it_can_get_the_sub_building_number(): void + { + $this->assertEquals('32', $this->address->getSubBuildingNumber()); + } + + /** @test */ + public function it_can_get_line_one(): void + { + $this->assertEquals('Apartment 32', $this->address->getLine1()); + } + + /** @test */ + public function it_can_get_line_two(): void + { + $this->assertEquals('Clarence Mill', $this->address->getLine2()); + } + + /** @test */ + public function it_can_get_line_three(): void + { + $this->assertEquals('Clarence Road', $this->address->getLine3()); + } + + /** @test */ + public function it_can_get_line_four(): void + { + $this->assertEquals('', $this->address->getLine4()); + } + + /** @test */ + public function it_can_get_a_line_by_the_given_number(): void + { + $this->assertEquals('Clarence Road', $this->address->getLine(3)); + $this->assertNull($this->address->getLine(5)); + } + + /** @test */ + public function it_can_get_the_locality(): void + { + $this->assertEquals('Bollington', $this->address->getLocality()); + } + + /** @test */ + public function it_can_get_the_town(): void + { + $this->assertEquals('Macclesfield', $this->address->getTown()); + } + + /** @test */ + public function it_can_get_the_city(): void + { + $this->assertEquals('Macclesfield', $this->address->getCity()); + } + + /** @test */ + public function it_can_get_the_county(): void + { + $this->assertEquals('Cheshire', $this->address->getCounty()); + } + + /** @test */ + public function it_can_get_the_district(): void + { + $this->assertEquals('Cheshire East', $this->address->getDistrict()); + } + + /** @test */ + public function it_can_get_the_country(): void + { + $this->assertEquals('England', $this->address->getCountry()); + } + + /** @test */ + public function it_can_transform_expanded_address_to_array(): void + { + $response = ResponseFactory::make('successfulGetResponse.json', true)->getDecodedResponse(); + $this->assertEquals( + array_merge($response, ['formatted_string' => $this->address->toString(true)]), + $this->address->toArray() + ); + } + + /** @test */ + public function it_can_transform_expanded_address_to_string_and_remove_empty_elements(): void + { + $this->assertEquals( + 'Apartment 32, Clarence Mill, Clarence Road, Bollington, Macclesfield, Cheshire', + $this->address->toString(true) + ); + } + + /** @test */ + public function it_can_transform_expanded_address_to_string(): void + { + $this->assertEquals( + 'Apartment 32, Clarence Mill, Clarence Road, Bollington, Macclesfield, Cheshire', + $this->address->toString() + ); + } + + /** @test */ + public function it_can_cast_the_expanded_address_to_a_string(): void + { + $this->assertEquals( + 'Apartment 32, Clarence Mill, Clarence Road, Bollington, Macclesfield, Cheshire', + (string) $this->address + ); + } +} diff --git a/tests/Unit/Responses/SingleAddressCollectionResponseTest.php b/tests/Unit/Responses/SingleAddressCollectionResponseTest.php new file mode 100644 index 0000000..c298c5c --- /dev/null +++ b/tests/Unit/Responses/SingleAddressCollectionResponseTest.php @@ -0,0 +1,28 @@ +makeSingleAddressCollectionResponse(); + + $address = $response->getAddress(); + $this->assertEquals('Apartment 32', $address->getLine1()); + $this->assertEquals('Clarence Mill', $address->getLine2()); + $this->assertEquals('Clarence Road', $address->getLine3()); + $this->assertEquals('', $address->getLine4()); + $this->assertEquals('Bollington', $address->getLocality()); + $this->assertEquals('Macclesfield', $address->getTown()); + $this->assertEquals('Macclesfield', $address->getCity()); + $this->assertEquals('Cheshire', $address->getCounty()); + $this->assertEquals('Cheshire East', $address->getDistrict()); + $this->assertEquals('England', $address->getCountry()); + $this->assertTrue($address->isResidential()); + } +} diff --git a/tests/Unit/Responses/__snapshots__/AddressCollectionResponseTest__it_can_transform_to_the_expected_array__1.json b/tests/Unit/Responses/__snapshots__/AddressCollectionResponseTest__it_can_transform_to_the_expected_array__1.json new file mode 100644 index 0000000..02d65d8 --- /dev/null +++ b/tests/Unit/Responses/__snapshots__/AddressCollectionResponseTest__it_can_transform_to_the_expected_array__1.json @@ -0,0 +1,21 @@ +{ + "postcode": "B13 9SZ", + "latitude": 52.437353914285715, + "longitude": -1.8828399142857144, + "addresses": [ + "32 Clarence Road, Moseley, Birmingham, West Midlands", + "32a Clarence Road, Moseley, Birmingham, West Midlands", + "34 Clarence Road, Moseley, Birmingham, West Midlands", + "36 Clarence Road, Moseley, Birmingham, West Midlands", + "38 Clarence Road, Moseley, Birmingham, West Midlands", + "40 Clarence Road, Moseley, Birmingham, West Midlands", + "41 Clarence Road, Moseley, Birmingham, West Midlands", + "43 Clarence Road, Moseley, Birmingham, West Midlands", + "45 Clarence Road, Moseley, Birmingham, West Midlands", + "47 Clarence Road, Moseley, Birmingham, West Midlands", + "49 Clarence Road, Moseley, Birmingham, West Midlands", + "51 Clarence Road, Moseley, Birmingham, West Midlands", + "Flat 1, 36 Clarence Road, Moseley, Birmingham, West Midlands", + "Flat 2, 36 Clarence Road, Moseley, Birmingham, West Midlands" + ] +} diff --git a/tests/Unit/Responses/__snapshots__/AddressCollectionResponseTest__it_can_transform_to_the_expected_array_with_expanded_addresses__1.json b/tests/Unit/Responses/__snapshots__/AddressCollectionResponseTest__it_can_transform_to_the_expected_array_with_expanded_addresses__1.json new file mode 100644 index 0000000..d98e9ac --- /dev/null +++ b/tests/Unit/Responses/__snapshots__/AddressCollectionResponseTest__it_can_transform_to_the_expected_array_with_expanded_addresses__1.json @@ -0,0 +1,147 @@ +{ + "postcode": "B13 9SZ", + "latitude": 52.437353914285715, + "longitude": -1.8828399142857144, + "addresses": [ + { + "formatted_string": "32 Clarence Road, Moseley, Birmingham, West Midlands", + "line_1": "32 Clarence Road", + "line_2": " ", + "line_3": " ", + "line_4": " ", + "locality": " Moseley", + "town_or_city": " Birmingham", + "county": " West Midlands" + }, + { + "formatted_string": "32a Clarence Road, Moseley, Birmingham, West Midlands", + "line_1": "32a Clarence Road", + "line_2": " ", + "line_3": " ", + "line_4": " ", + "locality": " Moseley", + "town_or_city": " Birmingham", + "county": " West Midlands" + }, + { + "formatted_string": "34 Clarence Road, Moseley, Birmingham, West Midlands", + "line_1": "34 Clarence Road", + "line_2": " ", + "line_3": " ", + "line_4": " ", + "locality": " Moseley", + "town_or_city": " Birmingham", + "county": " West Midlands" + }, + { + "formatted_string": "36 Clarence Road, Moseley, Birmingham, West Midlands", + "line_1": "36 Clarence Road", + "line_2": " ", + "line_3": " ", + "line_4": " ", + "locality": " Moseley", + "town_or_city": " Birmingham", + "county": " West Midlands" + }, + { + "formatted_string": "38 Clarence Road, Moseley, Birmingham, West Midlands", + "line_1": "38 Clarence Road", + "line_2": " ", + "line_3": " ", + "line_4": " ", + "locality": " Moseley", + "town_or_city": " Birmingham", + "county": " West Midlands" + }, + { + "formatted_string": "40 Clarence Road, Moseley, Birmingham, West Midlands", + "line_1": "40 Clarence Road", + "line_2": " ", + "line_3": " ", + "line_4": " ", + "locality": " Moseley", + "town_or_city": " Birmingham", + "county": " West Midlands" + }, + { + "formatted_string": "41 Clarence Road, Moseley, Birmingham, West Midlands", + "line_1": "41 Clarence Road", + "line_2": " ", + "line_3": " ", + "line_4": " ", + "locality": " Moseley", + "town_or_city": " Birmingham", + "county": " West Midlands" + }, + { + "formatted_string": "43 Clarence Road, Moseley, Birmingham, West Midlands", + "line_1": "43 Clarence Road", + "line_2": " ", + "line_3": " ", + "line_4": " ", + "locality": " Moseley", + "town_or_city": " Birmingham", + "county": " West Midlands" + }, + { + "formatted_string": "45 Clarence Road, Moseley, Birmingham, West Midlands", + "line_1": "45 Clarence Road", + "line_2": " ", + "line_3": " ", + "line_4": " ", + "locality": " Moseley", + "town_or_city": " Birmingham", + "county": " West Midlands" + }, + { + "formatted_string": "47 Clarence Road, Moseley, Birmingham, West Midlands", + "line_1": "47 Clarence Road", + "line_2": " ", + "line_3": " ", + "line_4": " ", + "locality": " Moseley", + "town_or_city": " Birmingham", + "county": " West Midlands" + }, + { + "formatted_string": "49 Clarence Road, Moseley, Birmingham, West Midlands", + "line_1": "49 Clarence Road", + "line_2": " ", + "line_3": " ", + "line_4": " ", + "locality": " Moseley", + "town_or_city": " Birmingham", + "county": " West Midlands" + }, + { + "formatted_string": "51 Clarence Road, Moseley, Birmingham, West Midlands", + "line_1": "51 Clarence Road", + "line_2": " ", + "line_3": " ", + "line_4": " ", + "locality": " Moseley", + "town_or_city": " Birmingham", + "county": " West Midlands" + }, + { + "formatted_string": "Flat 1, 36 Clarence Road, Moseley, Birmingham, West Midlands", + "line_1": "Flat 1", + "line_2": " 36 Clarence Road", + "line_3": " ", + "line_4": " ", + "locality": " Moseley", + "town_or_city": " Birmingham", + "county": " West Midlands" + }, + { + "formatted_string": "Flat 2, 36 Clarence Road, Moseley, Birmingham, West Midlands", + "line_1": "Flat 2", + "line_2": " 36 Clarence Road", + "line_3": " ", + "line_4": " ", + "locality": " Moseley", + "town_or_city": " Birmingham", + "county": " West Midlands" + } + ] +}