Skip to content

Commit

Permalink
Add AccessToken provider and make it more readable
Browse files Browse the repository at this point in the history
  • Loading branch information
vody105 authored and f3l1x committed May 15, 2020
1 parent b0196da commit 00f3251
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 35 deletions.
37 changes: 14 additions & 23 deletions src/Auth/AccessTokenCacheProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class AccessTokenCacheProvider extends AccessTokenClient

private const CACHE_NAMESPACE = 'Contributte/Gosms';

/** @var AccessToken */
/** @var AccessToken|null */
protected $accessToken;

/** @var Cache */
Expand All @@ -28,24 +28,22 @@ public function __construct(IHttpClient $client, IStorage $storage)

public function getAccessToken(Config $config): AccessToken
{
$token = $this->accessToken;

// If we have it in cache we retrieve it
if ($this->accessToken === null) {
$token = $this->loadAccessToken($config);
// We have token
if ($this->accessToken !== null && !$this->accessToken->isExpired()) {
return $this->accessToken;
}

if ($token instanceof AccessToken) {
$this->accessToken = $token;
// Load token from cache
$token = $this->loadAccessToken($config);
if ($token !== null && !$token->isExpired()) {
return $this->accessToken = $token;
}

$this->accessToken = parent::getAccessToken($config);

if ($token === null || $token->getAccessToken() !== $this->accessToken->getAccessToken()) {
$this->saveAccessToken($config, $this->accessToken);
}
// We need to request token
$token = parent::getAccessToken($config);
$this->saveAccessToken($config, $token);

return $this->accessToken;
return $this->accessToken = $token;
}

private function loadAccessToken(Config $config): ?AccessToken
Expand All @@ -55,16 +53,9 @@ private function loadAccessToken(Config $config): ?AccessToken
return null;
}

/** @var DateTimeImmutable $expiresAt */
$expiresAt = DateTimeImmutable::createFromFormat(DateTimeImmutable::ATOM, $token['expiresAt']);
$token['expires_at'] = DateTimeImmutable::createFromFormat(DateTimeImmutable::ATOM, $token['expires_at']);

return new AccessToken(
$token['accessToken'],
$token['expiresIn'],
$token['tokenType'],
$token['scope'],
$expiresAt
);
return AccessToken::fromArray($token);
}

private function saveAccessToken(Config $config, AccessToken $token): void
Expand Down
11 changes: 3 additions & 8 deletions src/Auth/AccessTokenClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class AccessTokenClient implements IAccessTokenProvider

protected const URL = 'https://app.gosms.cz/oauth/v2/token';

/** @var AccessToken */
/** @var AccessToken|null */
protected $accessToken;

/** @var IHttpClient */
Expand Down Expand Up @@ -47,14 +47,9 @@ protected function generateAccessToken(Config $config): AccessToken
throw new ClientException($response->getBody()->getContents(), $response->getStatusCode());
}

$data = Json::decode($response->getBody()->getContents());
$data = Json::decode($response->getBody()->getContents(), Json::FORCE_ARRAY);

return new AccessToken(
$data->access_token,
$data->expires_in,
$data->token_type,
$data->scope
);
return AccessToken::fromArray($data);
}

}
22 changes: 18 additions & 4 deletions src/Entity/AccessToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,31 @@ public function getExpiresAt(): DateTimeImmutable
return $this->expiresAt;
}

/**
* @param mixed[] $data
*/
public static function fromArray(array $data): self
{
return new self(
$data['access_token'],
$data['expires_in'],
$data['token_type'],
$data['scope'],
$data['expires_at'] ?? null
);
}

/**
* @return mixed[]
*/
public function toArray(): array
{
return [
'accessToken' => $this->accessToken,
'expiresIn' => $this->expiresIn,
'tokenType' => $this->tokenType,
'access_token' => $this->accessToken,
'expires_in' => $this->expiresIn,
'token_type' => $this->tokenType,
'scope' => $this->scope,
'expiresAt' => $this->expiresAt->format(DateTimeImmutable::ATOM),
'expires_at' => $this->expiresAt->format(DateTimeImmutable::ATOM),
];
}

Expand Down
101 changes: 101 additions & 0 deletions tests/cases/Auth/AccessTokenCacheProvider.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php declare(strict_types = 1);

use Contributte\Gosms\Auth\AccessTokenCacheProvider;
use Contributte\Gosms\Config;
use Contributte\Gosms\Entity\AccessToken;
use Contributte\Gosms\Http\IHttpClient;
use GuzzleHttp\Psr7\Response;
use Nette\Caching\IStorage;
use Nette\Caching\Storages\MemoryStorage;
use Tester\Assert;
use Tester\Environment;

require_once __DIR__ . '/../../bootstrap.php';

Environment::bypassFinals();

// New token
test(function (): void {
$http = Mockery::mock(IHttpClient::class);
$http->shouldReceive('sendRequest')
->andReturn(new Response(200, [], '{"access_token":"token","expires_in":123,"token_type":"type","scope":"scope"}'));

$client = new AccessTokenCacheProvider($http, new MemoryStorage());
Closure::fromCallable(function (): void {
$this->accessToken = Mockery::mock(AccessToken::class);
$this->accessToken->shouldReceive('isExpired')
->andReturn(true);
})->call($client);

$token = $client->getAccessToken(new Config('foo', 'bar'));

Assert::same('token', $token->getAccessToken());
Assert::same(123, $token->getExpiresIn());
Assert::same('type', $token->getTokenType());
Assert::same('scope', $token->getScope());
});

// Cached token
test(function (): void {
$http = Mockery::mock(IHttpClient::class);
$http->shouldReceive('sendRequest')
->andReturn(new Response(200, [], '{"access_token":"token","expires_in":123,"token_type":"type","scope":"scope"}'));

$storage = Mockery::mock(IStorage::class);
$storage->shouldReceive('read')
->andReturn(['access_token' => 'cached', 'expires_in' => 999, 'token_type' => 'cached', 'scope' => 'cached', 'expires_at' => (new DateTimeImmutable('+1 year'))->format(DateTimeImmutable::ATOM)]);

$client = new AccessTokenCacheProvider($http, $storage);
$token = $client->getAccessToken(new Config('foo', 'bar'));

Assert::same('cached', $token->getAccessToken());
Assert::same(999, $token->getExpiresIn());
Assert::same('cached', $token->getTokenType());
Assert::same('cached', $token->getScope());
});

// Cached token is expired
test(function (): void {
$http = Mockery::mock(IHttpClient::class);
$http->shouldReceive('sendRequest')
->andReturn(new Response(200, [], '{"access_token":"token","expires_in":123,"token_type":"type","scope":"scope"}'));

$storage = Mockery::mock(IStorage::class);
$storage->shouldReceive('read')
->andReturn(['access_token' => 'cached', 'expires_in' => 999, 'token_type' => 'cached', 'scope' => 'cached', 'expires_at' => (new DateTimeImmutable('-5 minutes'))->format(DateTimeImmutable::ATOM)]);
$storage->shouldReceive('write')
->once();

$client = new AccessTokenCacheProvider($http, $storage);
$token = $client->getAccessToken(new Config('foo', 'bar'));

Assert::same('token', $token->getAccessToken());
Assert::same(123, $token->getExpiresIn());
Assert::same('type', $token->getTokenType());
Assert::same('scope', $token->getScope());
});

// Cached token is expired so we request new one and save new one to cache
test(function (): void {
$http = Mockery::mock(IHttpClient::class);
$http->shouldReceive('sendRequest')
->andReturn(new Response(200, [], '{"access_token":"token","expires_in":123,"token_type":"type","scope":"scope"}'));

$storage = Mockery::mock(IStorage::class);
$storage->shouldReceive('read')
->andReturn(['access_token' => 'cached', 'expires_in' => 999, 'token_type' => 'cached', 'scope' => 'cached', 'expires_at' => (new DateTimeImmutable('-5 minutes'))->format(DateTimeImmutable::ATOM)]);
$storage->shouldReceive('write')
->withArgs(function (string $key, array $data): bool {
Assert::same('token', $data['access_token']);

return true;
})->once();

$client = new AccessTokenCacheProvider($http, $storage);
$token = $client->getAccessToken(new Config('foo', 'bar'));

Assert::same('token', $token->getAccessToken());
Assert::same(123, $token->getExpiresIn());
Assert::same('type', $token->getTokenType());
Assert::same('scope', $token->getScope());
});
34 changes: 34 additions & 0 deletions tests/cases/Auth/AccessTokenClient.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php declare(strict_types = 1);

use Contributte\Gosms\Auth\AccessTokenClient;
use Contributte\Gosms\Config;
use Contributte\Gosms\Entity\AccessToken;
use Contributte\Gosms\Http\IHttpClient;
use GuzzleHttp\Psr7\Response;
use Tester\Assert;
use Tester\Environment;

require_once __DIR__ . '/../../bootstrap.php';

Environment::bypassFinals();

// Check client creates token and requests a new one when saved is expired
test(function (): void {
$http = Mockery::mock(IHttpClient::class);
$http->shouldReceive('sendRequest')
->andReturn(new Response(200, [], '{"access_token":"token","expires_in":123,"token_type":"type","scope":"scope"}'));

$client = new AccessTokenClient($http);
Closure::fromCallable(function (): void {
$this->accessToken = Mockery::mock(AccessToken::class);
$this->accessToken->shouldReceive('isExpired')
->andReturn(true);
})->call($client);

$token = $client->getAccessToken(new Config('foo', 'bar'));

Assert::same('token', $token->getAccessToken());
Assert::same(123, $token->getExpiresIn());
Assert::same('type', $token->getTokenType());
Assert::same('scope', $token->getScope());
});

0 comments on commit 00f3251

Please sign in to comment.