diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..45c634a --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +AIRTABLE_KEY= +AIRTABLE_BASE= +AIRTABLE_TABLE= diff --git a/.github/run-tests-L7.yml b/.github/run-tests-L7.yml new file mode 100644 index 0000000..7bcdea9 --- /dev/null +++ b/.github/run-tests-L7.yml @@ -0,0 +1,46 @@ +name: "Run Tests - Older" + +on: [push, pull_request] + +jobs: + test: + + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: [8.0, 7.4, 7.3, 7.2] + laravel: [7.*, 6.*] + dependency-version: [prefer-lowest, prefer-stable] + include: + - laravel: 7.* + testbench: 5.* + - laravel: 6.* + testbench: 4.* + + name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ~/.composer/cache/files + key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: curl, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, iconv + coverage: none + + - name: Install dependencies + run: | + composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "symfony/console:>=4.3.4" "mockery/mockery:^1.3.2" --no-interaction --no-update + composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction + cp .env.example .env + - name: Execute tests + run: vendor/bin/phpunit diff --git a/.github/workflows/run-tests-l8.yml b/.github/workflows/run-tests-l8.yml new file mode 100644 index 0000000..fb8d931 --- /dev/null +++ b/.github/workflows/run-tests-l8.yml @@ -0,0 +1,44 @@ +name: "Run Tests - Current" + +on: [push, pull_request] + +jobs: + test: + + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: [8.0, 7.4, 7.3] + laravel: [8.*] + dependency-version: [prefer-lowest, prefer-stable] + include: + - laravel: 8.* + testbench: 6.* + + name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ~/.composer/cache/files + key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: curl, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, iconv + coverage: none + + - name: Install dependencies + run: | + composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "symfony/console:>=4.3.4" "mockery/mockery:^1.3.2" --no-interaction --no-update + composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction + cp .env.example .env + - name: Execute tests + run: vendor/bin/phpunit diff --git a/.gitignore b/.gitignore index 6dd7ae6..32cc79a 100755 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ vendor coverage .env .ac-php-conf.json +.phpunit.result.cache +.DS_Store diff --git a/.travis.yml b/.travis.yml deleted file mode 100755 index 24c2d6c..0000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -language: php - -php: - - 7.1 - - 7.2 - - 7.3 - -env: - matrix: - - COMPOSER_FLAGS="--prefer-lowest" - - COMPOSER_FLAGS="" - -before_script: - - travis_retry composer self-update - - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-source - -script: - - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover - -after_script: - - php vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover diff --git a/composer.json b/composer.json index 166706c..ce12319 100755 --- a/composer.json +++ b/composer.json @@ -16,15 +16,15 @@ } ], "require": { - "php": "^7.1 || ^8.0", + "php": "^7.3 || ^8.0", "guzzlehttp/guzzle": "~6.0 || ~7.0", "illuminate/support": "5.7.* || 5.8.* ||^6.0 || ^7.0 || ^8.0", "symfony/dotenv": "^4.2 || ^5.1" }, "require-dev": { - "mockery/mockery": "^1.0", - "orchestra/testbench": "3.7.*", - "phpunit/phpunit": "^7.0" + "mockery/mockery": "^1.4", + "orchestra/testbench": "^5.0 || ^6.0", + "phpunit/phpunit": "^8.4 || ^9.3.3" }, "autoload": { "psr-4": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9676f7b..5d3b72b 100755 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,32 +1,33 @@ - + + + + src/ + + + + + + + - + - - tests + + ./tests/Feature - - - src/ - - - - - - - - - diff --git a/src/Airtable.php b/src/Airtable.php index 25427d8..8f06995 100755 --- a/src/Airtable.php +++ b/src/Airtable.php @@ -24,7 +24,7 @@ public function create($data) } /** - * @param dynamic $args (string) $id, $data | (array) $data + * @param dynamic $args (string) $id, $data | (array) $data * @return mixed * * @throws \InvalidArgumentException @@ -45,7 +45,7 @@ public function update(...$args) } /** - * @param dynamic $args (string) $id, $data | (array) $data + * @param dynamic $args (string) $id, $data | (array) $data * @return mixed * * @throws \InvalidArgumentException @@ -123,7 +123,7 @@ public function firstOrCreate(array $idData, array $createData = []) // first if ($results->isNotEmpty()) { - return $results->first(); + return $results; } // create diff --git a/src/AirtableManager.php b/src/AirtableManager.php index 379d520..d8d8b97 100755 --- a/src/AirtableManager.php +++ b/src/AirtableManager.php @@ -142,7 +142,7 @@ protected function getConfig($name) * Dynamically pass methods to the default connection. * * @param string $method - * @param array $parameters + * @param array $parameters * @return mixed */ public function __call($method, $parameters) diff --git a/src/Api/AirtableApiClient.php b/src/Api/AirtableApiClient.php index a24d2cb..22ab55a 100644 --- a/src/Api/AirtableApiClient.php +++ b/src/Api/AirtableApiClient.php @@ -2,7 +2,7 @@ namespace Tapp\Airtable\Api; -use GuzzleHttp\Client; +use Illuminate\Support\Facades\Http; use Illuminate\Support\Str; class AirtableApiClient implements ApiClient @@ -21,36 +21,24 @@ class AirtableApiClient implements ApiClient private $pageSize = 100; private $maxRecords = 100; - public function __construct($base, $table, $access_token, $httpLogFormat = null, Client $client = null, $typecast = false, $delayBetweenRequests = 200000) + public function __construct($base, $table, $access_token, $httpLogFormat = null, Http $client = null, $typecast = false, $delayBetweenRequests = 200000) { $this->base = $base; $this->table = $table; $this->typecast = $typecast; $this->delay = $delayBetweenRequests; - $stack = \GuzzleHttp\HandlerStack::create(); - - if ($httpLogFormat) { - $stack->push( - \GuzzleHttp\Middleware::log( - new \Monolog\Logger('Logger'), - new \GuzzleHttp\MessageFormatter($httpLogFormat) - ) - ); - } - - $this->client = $client ?? $this->buildClient($access_token, $stack); + $this->client = $client ?? $this->buildClient($access_token); } - private function buildClient($access_token, $stack) + private function buildClient($access_token) { - return new Client([ + return Http::withOptions([ 'base_uri' => 'https://api.airtable.com', 'headers' => [ 'Authorization' => "Bearer {$access_token}", 'content-type' => 'application/json', ], - 'handler' => $stack, ]); } @@ -191,7 +179,7 @@ public function decodeResponse($response) return []; } - return json_decode($body, true); + return collect(json_decode($body, true)); } public function setFields(?array $fields) diff --git a/tests/ClientTest.php b/tests/ClientTest.php deleted file mode 100644 index 880b705..0000000 --- a/tests/ClientTest.php +++ /dev/null @@ -1,193 +0,0 @@ -assertInstanceOf(Client::class, $client); - } - - /** @test */ - public function it_can_post() - { - $expectedResponse = [ - 'id' => 'randomlygenerated', - 'fields' => ['Company Name' => 'Tapp Network'], - 'createdTime' => 'timestamp', - ]; - - $postData = ['Company Name' => 'Tapp Network']; - - $mockGuzzle = $this->mock_guzzle_request( - json_encode($expectedResponse), - '/v0/test_base/companies', - [ - 'json' => [ - 'fields' => (object) $postData, - ], - ] - ); - - $client = $this->build_client($mockGuzzle); - - $actualResponse = $client->setTable('companies') - ->post($postData); - - $this->assertEquals($expectedResponse['fields'], $actualResponse['fields']); - } - - /** @test */ - public function it_can_search() - { - $expectedResponse = [ - 'id' => 'randomlygenerated', - 'fields' => ['Company Name' => 'Tapp Network'], - 'createdTime' => 'timestamp', - ]; - - $mockGuzzle = $this->mock_guzzle_request( - json_encode($expectedResponse), - '/v0/test_base/companies', - [ - 'json' => [ - 'filterByFormula' => '{Company Name}="Tapp Network"', - ], - ] - ); - - $client = $this->build_client($mockGuzzle); - - $actualResponse = $client->setTable('companies') - ->addFilter('Company Name', '=', 'Tapp Network') - ->get(); - - $first = $actualResponse['records'][0]; - - $this->assertEquals($expectedResponse['fields'], $first['fields']); - } - - /** @test */ - public function it_can_sort() - { - //Ascending sort - $expectedResponseAsc = [ - [ - 'id' => 0, - 'fields' => ['Company Name' => 'A Network'], - 'createdTime' => 'timestamp', - ], - [ - 'id' => 1, - 'fields' => ['Company Name' => 'B Network'], - 'createdTime' => 'timestamp', - ], - ]; - - $mockGuzzle = $this->mock_guzzle_request( - json_encode($expectedResponseAsc), - '/v0/test_base/companies', - [ - 'json' => [ - 'sort' => '[{field:"Company Name",direction:"asc"}]', - ], - ] - ); - - $client = $this->build_client($mockGuzzle); - - $actualResponse = $client->setTable('companies') - ->addSort('Company Name', 'asc') - ->get(); - - $first = $actualResponse['records'][0]; - - $this->assertEquals($expectedResponseAsc['fields'], $first['fields']); - - //Descending sort - $expectedResponseDesc = [ - [ - 'id' => 1, - 'fields' => ['Company Name' => 'B Network'], - 'createdTime' => 'timestamp', - ], - [ - 'id' => 0, - 'fields' => ['Company Name' => 'A Network'], - 'createdTime' => 'timestamp', - ], - ]; - - $mockGuzzle = $this->mock_guzzle_request( - json_encode($expectedResponseDesc), - '/v0/test_base/companies', - [ - 'json' => [ - 'sort' => '[{field:"Company Name",direction:"desc"}]', - ], - ] - ); - - $client = $this->build_client($mockGuzzle); - - $actualResponse = $client->setTable('companies') - ->addSort('Company Name', 'desc') - ->get(); - - $first = $actualResponse['records'][0]; - - $this->assertEquals($expectedResponseDesc['fields'], $first['fields']); - } - - private function build_client($mockGuzzle = null) - { - if (env('LOG_HTTP')) { - $httpLogFormat = env('LOG_HTTP_FORMAT', '{request} >>> {res_body}'); - } else { - $httpLogFormat = null; - } - - return new Client( - $mockGuzzle ? 'test_base' : env('AIRTABLE_BASE', 'test_base'), - $mockGuzzle ? 'test_table' : env('AIRTABLE_TABLE', 'test_table'), - $mockGuzzle ? 'test_key' : env('AIRTABLE_KEY', 'test_key'), - $httpLogFormat, - $mockGuzzle - ); - } - - private function mock_guzzle_request($expectedResponse, $expectedEndpoint, $expectedParams = []) - { - if (env('TEST_AIRTABLE_API')) { - return; - } - - $mockResponse = $this->getMockBuilder(ResponseInterface::class) - ->getMock(); - - if ($expectedResponse) { - $mockResponse->expects($this->once()) - ->method('getBody') - ->willReturn($expectedResponse); - } - - $mockGuzzle = $this->getMockBuilder(GuzzleClient::class) - ->setMethods(['post']) - ->getMock(); - - $mockGuzzle->expects($this->once()) - ->method('post') - ->with($expectedEndpoint, $expectedParams) - ->willReturn($mockResponse); - - return $mockGuzzle; - } -} diff --git a/tests/Feature/ClientTest.php b/tests/Feature/ClientTest.php new file mode 100644 index 0000000..a047179 --- /dev/null +++ b/tests/Feature/ClientTest.php @@ -0,0 +1,119 @@ +assertInstanceOf(Client::class, $client); + } + + /** @test */ + public function it_can_post() + { + $expectedResponse = [ + 'id' => 'randomlygenerated', + 'fields' => ['Company Name' => 'Tapp Network'], + 'createdTime' => 'timestamp', + ]; + + $postData = ['Company Name' => 'Tapp Network']; + + Http::fake([ + 'api.airtable.com/*' => Http::response($expectedResponse, 200), + ]); + + $actualResponse = Airtable::table('companies') + ->firstOrCreate($postData); + + $this->assertEquals($expectedResponse['fields'], $actualResponse['fields']); + } + + /** @test */ + public function it_can_search() + { + $expectedResponse = [ + 'id' => 'randomlygenerated', + 'fields' => ['Company Name' => 'Tapp Network'], + 'createdTime' => 'timestamp', + ]; + + Http::fake([ + 'api.airtable.com/*' => Http::response($expectedResponse, 200), + ]); + + $actualResponse = Airtable::table('companies') + ->where('Company Name', '=', 'Tapp Network') + ->get(); + + $this->assertEquals($expectedResponse['fields'], $actualResponse['fields']); + } + + /** @test */ + public function it_can_sort_asc() + { + //Ascending sort + $expectedResponseAsc = [ + [ + 'id' => 0, + 'fields' => ['Company Name' => 'A Network'], + 'createdTime' => 'timestamp', + ], + [ + 'id' => 1, + 'fields' => ['Company Name' => 'B Network'], + 'createdTime' => 'timestamp', + ], + ]; + + Http::fake([ + 'api.airtable.com/*' => Http::response($expectedResponseAsc, 200), + ]); + + $actualResponse = Airtable::table('companies') + ->orderBy('Company Name', 'asc') + ->get(); + + $first = $actualResponse[0]; + + $this->assertEquals($expectedResponseAsc[0]['fields'], $first['fields']); + } + + /** @test */ + public function it_can_sort_desc() + { + //Descending sort + $expectedResponseDesc = [ + [ + 'id' => 1, + 'fields' => ['Company Name' => 'B Network'], + 'createdTime' => 'timestamp', + ], + [ + 'id' => 0, + 'fields' => ['Company Name' => 'A Network'], + 'createdTime' => 'timestamp', + ], + ]; + + Http::fake([ + 'api.airtable.com/*' => Http::response($expectedResponseDesc, 200), + ]); + + $actualResponse = Airtable::table('companies') + ->orderBy('Company Name', 'desc') + ->get(); + + $first = $actualResponse[0]; + + $this->assertEquals($expectedResponseDesc[0]['fields'], $first['fields']); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..9e78f9b --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,31 @@ + 'Companies', + ]); + } + + protected function getPackageProviders($app) + { + return [ + AirtableServiceProvider::class, + ]; + } + + protected function getEnvironmentSetUp($app) + { + // perform environment setup + } +}