Skip to content

Commit

Permalink
Rename the adapter and add unit tests for everything
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgemudry committed Mar 9, 2023
1 parent 5b7dfa3 commit c8fc2d9
Show file tree
Hide file tree
Showing 12 changed files with 262 additions and 177 deletions.
28 changes: 0 additions & 28 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,3 @@ parameters:
level: 8
paths:
- src
- tests
scanFiles:
# Pest handles loading custom helpers only when running tests
# @see https://pestphp.com/docs/helpers#usage
- tests/Pest.php

# Mockery doesn't autoload its helper functions
- vendor/mockery/mockery/library/helpers.php

ignoreErrors:
-
message: '#Access to an undefined property JorgeMudry\\LaravelRemoteTokenAuth\\ValueObjects\\AuthenticatedUser#'
paths:
- tests/Unit/AuthenticatedUserTest.php

# Pest implicitly binds $this to the current test case
# @see https://pestphp.com/docs/underlying-test-case
-
message: '#^Undefined variable: \$this$#'
paths:
- tests/*

# Pest custom expectations are dynamic and not conducive static analysis
# @see https://pestphp.com/docs/expectations#custom-expectations
-
message: '#Call to an undefined method Pest\\Expectation|Pest\\Support\\Extendable::#'
paths:
- tests/*
13 changes: 6 additions & 7 deletions src/LaravelRemoteTokenAuthAdapter.php → src/Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@
use JorgeMudry\LaravelRemoteTokenAuth\Contracts\AdapterInterface;
use Throwable;

class LaravelRemoteTokenAuthAdapter implements AdapterInterface
class Adapter implements AdapterInterface
{
public function __construct(
protected string $endpoint,
protected string $path,
protected string $user_class,
protected ActionsResolver $actions,
) {
}
Expand All @@ -28,12 +27,12 @@ public function __construct(
*/
public function authorize(Request $request): Authenticatable
{
try {
$token_resolver = $this->actions->getTokenResolver();
$request_maker = $this->actions->getRequestMaker();
$attributes_resolver = $this->actions->getAttributesResolver();
$user_maker = $this->actions->getUserMaker();
$token_resolver = $this->actions->getTokenResolver();
$request_maker = $this->actions->getRequestMaker();
$attributes_resolver = $this->actions->getAttributesResolver();
$user_maker = $this->actions->getUserMaker();

try {
$token = $token_resolver->execute($request);
$response = $request_maker->execute($token, $this->endpoint);
$attributes = $attributes_resolver->execute($response, $this->path);
Expand Down
8 changes: 3 additions & 5 deletions src/Providers/LaravelRemoteTokenAuthServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
use JorgeMudry\LaravelRemoteTokenAuth\Actions\ActionsResolver;
use JorgeMudry\LaravelRemoteTokenAuth\Adapter;
use JorgeMudry\LaravelRemoteTokenAuth\Contracts\AdapterInterface;
use JorgeMudry\LaravelRemoteTokenAuth\LaravelRemoteTokenAuthAdapter;

class LaravelRemoteTokenAuthServiceProvider extends ServiceProvider
{
Expand Down Expand Up @@ -59,15 +59,13 @@ protected function registerAdapter(): void
{
$this->app->bind(
AdapterInterface::class,
function (): LaravelRemoteTokenAuthAdapter {
function (): Adapter {
$endpoint = strval(config('remote-token-auth.endpoint'));
$path = strval(config('remote-token-auth.response.user_path'));
$user_class = strval(config('remote-token-auth.response.user_class'));

return new LaravelRemoteTokenAuthAdapter(
return new Adapter(
$endpoint,
$path,
$user_class,
new ActionsResolver(),
);
}
Expand Down
33 changes: 33 additions & 0 deletions tests/Unit/Actions/ActionsResolverTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

use JorgeMudry\LaravelRemoteTokenAuth\Actions\ActionsResolver;
use JorgeMudry\LaravelRemoteTokenAuth\Actions\CreateUserFromAttributesAction;
use JorgeMudry\LaravelRemoteTokenAuth\Actions\GetAttributesFromResponseAction;
use JorgeMudry\LaravelRemoteTokenAuth\Actions\GetTokenFromRequestAction;
use JorgeMudry\LaravelRemoteTokenAuth\Actions\MakeValidationRequestAction;

it('can resolve a GetTokenFromRequestAction instance', function (): void {
config(['remote-token-auth.actions.token-resolver' => GetTokenFromRequestAction::class]);
$resolver = new ActionsResolver();
expect($resolver->getTokenResolver())->toBeInstanceOf(GetTokenFromRequestAction::class);
});

it('can resolve a MakeValidationRequestAction instance', function (): void {
config(['remote-token-auth.actions.request-maker' => MakeValidationRequestAction::class]);
$resolver = new ActionsResolver();
expect($resolver->getRequestMaker())->toBeInstanceOf(MakeValidationRequestAction::class);
});

it('can resolve a GetAttributesFromResponseAction instance', function (): void {
config(['remote-token-auth.actions.attributes-resolver' => GetAttributesFromResponseAction::class]);
$resolver = new ActionsResolver();
expect($resolver->getAttributesResolver())->toBeInstanceOf(GetAttributesFromResponseAction::class);
});

it('can resolve a CreateUserFromAttributesAction instance', function (): void {
config(['remote-token-auth.actions.user-maker' => CreateUserFromAttributesAction::class]);
$resolver = new ActionsResolver();
expect($resolver->getUserMaker())->toBeInstanceOf(CreateUserFromAttributesAction::class);
});
20 changes: 20 additions & 0 deletions tests/Unit/Actions/CreateUserFromAttributesActionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

use JorgeMudry\LaravelRemoteTokenAuth\Actions\CreateUserFromAttributesAction;
use JorgeMudry\LaravelRemoteTokenAuth\ValueObjects\AuthenticatedUser;

it('can create an Authenticatable object from attributes', function (): void {
$action = new CreateUserFromAttributesAction();
$attributes = [
'name' => 'John Doe',
'email' => 'john.doe@example.com',
'role' => 'admin',
];
$user = $action->execute($attributes);
expect($user)->toBeInstanceOf(AuthenticatedUser::class);
expect($user->name)->toBe($attributes['name']);
expect($user->email)->toBe($attributes['email']);
expect($user->role)->toBe($attributes['role']);
});
45 changes: 45 additions & 0 deletions tests/Unit/Actions/GetAttributesFromResponseActionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

use JorgeMudry\LaravelRemoteTokenAuth\Actions\GetAttributesFromResponseAction;

it('can get attributes from a response without a path', function (): void {
$action = new GetAttributesFromResponseAction();
$response = [
'id' => 123,
'name' => 'John Doe',
'email' => 'john.doe@example.com',
'role' => 'admin',
];
$attributes = $action->execute($response, '');
expect($attributes)->toBe($response);
});

it('can get attributes from a response with a path', function (): void {
$action = new GetAttributesFromResponseAction();
$response = [
'data' => [
'id' => 123,
'name' => 'John Doe',
'email' => 'john.doe@example.com',
'role' => 'admin',
]
];
$attributes = $action->execute($response, 'data');
expect($attributes)->toBe($response['data']);
});

it('returns an empty array if the path does not exist in the response', function (): void {
$action = new GetAttributesFromResponseAction();
$response = [
'data' => [
'id' => 123,
'name' => 'John Doe',
'email' => 'john.doe@example.com',
'role' => 'admin',
]
];
$attributes = $action->execute($response, 'missing.path');
expect($attributes)->toBe([]);
});
21 changes: 21 additions & 0 deletions tests/Unit/Actions/GetTokenFromRequestActionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

use Illuminate\Http\Request;
use JorgeMudry\LaravelRemoteTokenAuth\Actions\GetTokenFromRequestAction;

it('can get the access token from a request', function (): void {
$action = new GetTokenFromRequestAction();
$token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9';
$request = Request::create('/', 'GET');
$request->headers->set('Authorization', "Bearer {$token}");
$accessToken = $action->execute($request);
expect($accessToken->token())->toBe($token);
});

it('throws an exception if no access token is provided', function (): void {
$action = new GetTokenFromRequestAction();
$request = Request::create('/', 'GET');
$action->execute($request);
})->throws(\InvalidArgumentException::class, 'A bearer token is required.');
43 changes: 43 additions & 0 deletions tests/Unit/Actions/MakeValidationRequestActionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

use Illuminate\Support\Facades\Http;
use JorgeMudry\LaravelRemoteTokenAuth\Actions\MakeValidationRequestAction;
use JorgeMudry\LaravelRemoteTokenAuth\Contracts\AccessTokenInterface;

test('execute method should return an array', function (): void {
/** @var \Mockery\MockInterface|\Mockery\LegacyMockInterface $token */
$token = Mockery::mock(AccessTokenInterface::class);
$token->shouldReceive('token')->once()->andReturn('valid_token');

$httpMock = Mockery::mock('alias:' . Http::class);
$httpMock->shouldReceive('asJson')->once()->andReturnSelf();
$httpMock->shouldReceive('withToken')->once()->with('valid_token')->andReturnSelf();
$httpMock->shouldReceive('get')->once()->with('http://example.com/api')->andReturnSelf();
$httpMock->shouldReceive('throw')->once()->andReturnSelf();
$httpMock->shouldReceive('json')->once()->andReturn(['response' => 'success']);

$action = new MakeValidationRequestAction();

$result = $action->execute($token, 'http://example.com/api');

expect($result)->toBeArray();
});

test('execute method should throw an exception if the HTTP response is not successful', function (): void {
$token = Mockery::mock(AccessTokenInterface::class);
$token->shouldReceive('token')->once()->andReturn('valid_token');

$httpMock = Mockery::mock('alias:' . Http::class);
$httpMock->shouldReceive('asJson')->once()->andReturnSelf();
$httpMock->shouldReceive('withToken')->once()->with('valid_token')->andReturnSelf();
$httpMock->shouldReceive('get')->once()->with('http://example.com/api')->andReturnSelf();
$httpMock->shouldReceive('throw')->once()->andThrow(new Exception('Invalid response'));

$action = new MakeValidationRequestAction();

expect(function () use ($action, $token): void {
$action->execute($token, 'http://example.com/api');
})->toThrow(Exception::class, 'Invalid response');
});
58 changes: 58 additions & 0 deletions tests/Unit/AdapterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Http\Client\Response;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use JorgeMudry\LaravelRemoteTokenAuth\Actions\ActionsResolver;
use JorgeMudry\LaravelRemoteTokenAuth\Adapter;
use JorgeMudry\LaravelRemoteTokenAuth\Contracts\AccessTokenInterface;
use JorgeMudry\LaravelRemoteTokenAuth\Contracts\AttributesResolverInterface;
use JorgeMudry\LaravelRemoteTokenAuth\Contracts\RequestMakerInterface;
use JorgeMudry\LaravelRemoteTokenAuth\Contracts\TokenResolverInterface;
use JorgeMudry\LaravelRemoteTokenAuth\Contracts\UserMakerInterface;

it('authorizes the user when given a valid token', function (): void {
$user = new stdClass();
$responseBody = ['id' => 1, 'name' => 'John Doe'];
$response = new Response(200, [], json_encode($responseBody));
Http::fake([$this->endpoint => $response]);

$tokenResolver = Mockery::mock(TokenResolverInterface::class);
$tokenResolver->shouldReceive('execute')->once()->andReturn(Mockery::mock(AccessTokenInterface::class));
$requestMaker = Mockery::mock(RequestMakerInterface::class);
$requestMaker->shouldReceive('execute')->once()->andReturn($responseBody);
$attributesResolver = Mockery::mock(AttributesResolverInterface::class);
$attributesResolver->shouldReceive('execute')->once()->andReturn($responseBody);
$userMaker = Mockery::mock(UserMakerInterface::class);
$userMaker->shouldReceive('execute')->once()->andReturn($user);

$actions = new ActionsResolver($tokenResolver, $requestMaker, $attributesResolver, $userMaker);
$adapter = new Adapter($this->endpoint, $this->path, $actions);
$request = Request::create('/', 'GET', [], [], [], ['HTTP_AUTHORIZATION' => 'Bearer valid-token']);
$result = $adapter->authorize($request);

expect($result)->toBeInstanceOf(Authenticatable::class);
expect($result)->toEqual($user);
})->skip();

it('throws an authentication exception when given an invalid token', function (): void {
$response = new Response(401);
Http::fake([$this->endpoint => $response]);

$tokenResolver = Mockery::mock(TokenResolverInterface::class);
$tokenResolver->shouldReceive('execute')->once()->andThrow(new AuthenticationException());
$actions = new ActionsResolver($tokenResolver);
$adapter = new Adapter($this->endpoint, $this->path, $actions);
$request = Request::create('/', 'GET', [], [], [], ['HTTP_AUTHORIZATION' => 'Bearer invalid-token']);
$this->expectException(AuthenticationException::class);
$adapter->authorize($request);
})->skip();

beforeEach(function (): void {
$this->endpoint = 'http://example.com/validate';
$this->path = 'user';
});
Loading

0 comments on commit c8fc2d9

Please sign in to comment.