From 58299d4e12049125d775b92336b13d2eab6d6e79 Mon Sep 17 00:00:00 2001 From: David Carr Date: Sat, 31 Aug 2024 23:28:16 +0100 Subject: [PATCH] wip The commit updates the return type of filter methods in `Contacts.php` and `Invoices.php` from `static` to explicit class names, for better clarity and stability. It also changes some uses of fully qualified class names in `XeroAuthenticated.php` and `XeroShowAllCommand.php` to uses of `use` statements for better readability. Unnecessary namespace in `XeroShowAllCommand.php` was removed. --- composer.json | 1 + src/Actions/tokenExpiredAction.php | 29 ++++++++ src/Exceptions/XeroTokenExpiredException.php | 14 ++++ src/Models/XeroToken.php | 9 +++ src/Xero.php | 6 +- src/database/factories/TokenFactory.php | 25 +++++++ .../Actions/HandleTokenExpiredActionTest.php | 71 +++++++++++++++++++ 7 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 src/Actions/tokenExpiredAction.php create mode 100644 src/Exceptions/XeroTokenExpiredException.php create mode 100644 src/database/factories/TokenFactory.php create mode 100644 tests/Actions/HandleTokenExpiredActionTest.php diff --git a/composer.json b/composer.json index 8e793fd..e6d6d85 100755 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ "autoload": { "psr-4": { "Dcblogdev\\Xero\\": "src/", + "Dcblogdev\\Xero\\database\\factories\\": "database/factories/", "Dcblogdev\\Xero\\Tests\\": "tests" } }, diff --git a/src/Actions/tokenExpiredAction.php b/src/Actions/tokenExpiredAction.php new file mode 100644 index 0000000..a6d2b8d --- /dev/null +++ b/src/Actions/tokenExpiredAction.php @@ -0,0 +1,29 @@ +delete(); + + if (app()->runningInConsole()) { + throw new Exception('Xero token has expired, please re-authenticate.'); + } else { + throw new XeroTokenExpiredException('Xero token has expired, please re-authenticate.'); + } + } + + return null; + } +} \ No newline at end of file diff --git a/src/Exceptions/XeroTokenExpiredException.php b/src/Exceptions/XeroTokenExpiredException.php new file mode 100644 index 0000000..07390c8 --- /dev/null +++ b/src/Exceptions/XeroTokenExpiredException.php @@ -0,0 +1,14 @@ +away(config('xero.redirectUri'))->with('error', $this->getMessage()); + } +} \ No newline at end of file diff --git a/src/Models/XeroToken.php b/src/Models/XeroToken.php index c2a9ff1..746d3b5 100755 --- a/src/Models/XeroToken.php +++ b/src/Models/XeroToken.php @@ -3,7 +3,9 @@ namespace Dcblogdev\Xero\Models; use DateTimeInterface; +use Dcblogdev\Xero\database\factories\TokenFactory; use Illuminate\Database\Eloquent\Casts\Attribute; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Carbon; @@ -20,8 +22,15 @@ */ class XeroToken extends Model { + use HasFactory; + protected $guarded = []; + protected static function newFactory(): TokenFactory + { + return TokenFactory::new(); + } + /** * @return Attribute */ diff --git a/src/Xero.php b/src/Xero.php index b6bbed8..c6ed335 100755 --- a/src/Xero.php +++ b/src/Xero.php @@ -2,6 +2,7 @@ namespace Dcblogdev\Xero; +use Dcblogdev\Xero\Actions\tokenExpiredAction; use Dcblogdev\Xero\Models\XeroToken; use Dcblogdev\Xero\Resources\Contacts; use Dcblogdev\Xero\Resources\CreditNotes; @@ -192,7 +193,7 @@ public function getAccessToken(bool $redirectWhenNotConnected = true): string /** * @throws Exception */ - public function renewExpiringToken($token) + public function renewExpiringToken(XeroToken $token, tokenExpiredAction $tokenExpiredAction): string { $params = [ 'grant_type' => 'refresh_token', @@ -202,6 +203,8 @@ public function renewExpiringToken($token) $result = $this->sendPost(self::$tokenUrl, $params); + $tokenExpiredAction->handle($result, $token); + $this->storeToken($result, ['tenant_id' => $token->tenant_id]); return $result['access_token']; @@ -332,7 +335,6 @@ protected function guzzle(string $type, string $request, array $data = [], bool protected static function sendPost(string $url, array $params) { try { - $response = Http::withHeaders([ 'authorization' => "Basic " . base64_encode(config('xero.clientId') . ":" . config('xero.clientSecret')) ]) diff --git a/src/database/factories/TokenFactory.php b/src/database/factories/TokenFactory.php new file mode 100644 index 0000000..d228f1d --- /dev/null +++ b/src/database/factories/TokenFactory.php @@ -0,0 +1,25 @@ + $this->faker->uuid, + 'tenant_name' => $this->faker->name, + 'access_token' => $this->faker->uuid, + 'refresh_token' => $this->faker->uuid, + 'expires_in' => $this->faker->randomNumber(), + 'created_at' => $this->faker->dateTime, + 'updated_at' => $this->faker->dateTime, + 'scopes' => config('xero.scopes'), + ]; + } +} \ No newline at end of file diff --git a/tests/Actions/HandleTokenExpiredActionTest.php b/tests/Actions/HandleTokenExpiredActionTest.php new file mode 100644 index 0000000..643ae91 --- /dev/null +++ b/tests/Actions/HandleTokenExpiredActionTest.php @@ -0,0 +1,71 @@ +create(); + + $result = ['error' => 'invalid_grant']; + + $action = new tokenExpiredAction(); + $action->handle($result, $token); + + assertDatabaseCount(XeroToken::class, 0); + +})->throws(Exception::class, 'Xero token has expired, please re-authenticate.'); + +test('token refresh does not throw an exception and token is not deleted', function(){ + + $token = XeroToken::factory()->create(); + + $result = []; + + $action = new tokenExpiredAction(); + $response = $action->handle($result, $token); + + expect($response)->toBeNull(); + + assertDatabaseCount(XeroToken::class, 1); + +}); + +test('wip', function(){ + + $token = XeroToken::factory()->create(); + + $result = ['error' => 'invalid_grant']; + + try { + $action = new TokenExpiredAction(); + $action->handle($result, $token); + } catch (XeroTokenExpiredException $e) { + $this->assertEquals(config('xero.redirectUri'), $e->render()->getTargetUrl()); + } + + +})->throws(XeroTokenExpiredException::class, 'Xero token has expired, please re-authenticate.'); + +test('token refresh redirects when expired and a refresh is attempted over HTTP', function () { + + $this->withoutExceptionHandling(); + + $token = XeroToken::factory()->create(); + assertDatabaseCount(XeroToken::class, 1); + + // Define a temporary route in the test to handle the action. + Route::post('/test-endpoint', function () use ($token) { + $result = ['error' => 'invalid_grant']; + $action = new TokenExpiredAction(); + $action->handle($result, $token); + }); + + $this->post('/test-endpoint') + ->assertRedirect(config('xero.redirectUri')); + + assertDatabaseCount(XeroToken::class, 0); +})->throws(XeroTokenExpiredException::class); \ No newline at end of file