Skip to content

Commit

Permalink
Add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
onursimsek committed Feb 3, 2024
1 parent a44756d commit aced827
Show file tree
Hide file tree
Showing 10 changed files with 371 additions and 20 deletions.
17 changes: 9 additions & 8 deletions src/Attributes/Precondition.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,23 @@
#[Attribute(Attribute::TARGET_METHOD)]
class Precondition implements AttributeContract
{
public function __construct(private readonly string $validator)
private PreconditionValidator $validator;

public function __construct(string $validator)
{
$this->validator = new $validator();
}

public function validate(Request $request): bool
{
/** @var PreconditionValidator $validator */
$validator = new $this->validator();
if (empty($validator->parameter($request))) {
$validator->preProcess();
if (empty($this->validator->parameter($request))) {
$this->validator->preProcess();

throw new PreconditionRequiredException($validator->getRequiredMessage());
throw new PreconditionRequiredException($this->validator->getRequiredMessage());
}

if (! $validator($request)) {
throw new PreconditionFailedException($validator->getFailedMessage());
if (! ($this->validator)($request)) {
throw new PreconditionFailedException($this->validator->getFailedMessage());
}

return true;
Expand Down
12 changes: 8 additions & 4 deletions src/Middleware/PreconditionRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Routing\Router;
use OnurSimsek\Precondition\Reflection;
use Symfony\Component\HttpFoundation\Response;

class PreconditionRequest
{
public function handle(Request $request, Closure $next): Response
public function __construct(private readonly Router $router, private readonly Reflection $reflection)
{
$route = Route::getRoutes()->match($request);
}

public function handle(Request $request, Closure $next)
{
$route = $this->router->getRoutes()->match($request);
if (! $route->getControllerClass()) {
return $next($request);
}
Expand All @@ -24,7 +28,7 @@ public function handle(Request $request, Closure $next): Response

private function handleRequestUsingAttribute(Request $request, Closure $next, string $controller, string $action): Response
{
$reflection = new Reflection($controller, $action);
$reflection = $this->reflection->reflect($controller, $action);
if (! $reflection->hasPreconditionAttribute()) {
return $next($request);
}
Expand Down
23 changes: 15 additions & 8 deletions src/Reflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,26 @@
use ReflectionAttribute;
use ReflectionMethod;

class Reflection extends ReflectionMethod
class Reflection
{
/** @var ReflectionAttribute[] */
private array $attributes;
private ReflectionMethod $reflectionMethod;
private ReflectionAttribute $attribute;

public function __construct(string $controller, string $action)
public function reflect(string $controller, string $action): static
{
parent::__construct($controller, $action);
$this->reflectionMethod = new ReflectionMethod($controller, $action);
unset($this->attribute);

return $this;
}

private function getPreconditionAttribute(): array
private function getPreconditionAttribute(): ?ReflectionAttribute
{
return $this->attributes ?? $this->attributes = $this->getAttributes(Precondition::class);
if (!$attributes = $this->reflectionMethod->getAttributes(Precondition::class)) {
return null;
}

return $this->attribute ??= $attributes[0];
}

public function hasPreconditionAttribute(): bool
Expand All @@ -31,6 +38,6 @@ public function hasPreconditionAttribute(): bool

public function getPreconditionInstance(): Attribute
{
return $this->getPreconditionAttribute()[0]->newInstance();
return $this->getPreconditionAttribute()->newInstance();
}
}
19 changes: 19 additions & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace OnurSimsek\Precondition\Tests;

use Illuminate\Foundation\Testing\WithFaker;
use OnurSimsek\Precondition\PreconditionServiceProvider;
use Orchestra\Testbench\TestCase as Orchestra;

abstract class TestCase extends Orchestra
{
use WithFaker;

protected function getPackageProviders($app): array
{
return [PreconditionServiceProvider::class];
}
}
81 changes: 81 additions & 0 deletions tests/Unit/Attributes/PreconditionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

namespace OnurSimsek\Precondition\Tests\Unit\Attributes;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Date;
use OnurSimsek\Precondition\Attributes\Precondition;
use OnurSimsek\Precondition\Exceptions\PreconditionFailedException;
use OnurSimsek\Precondition\Exceptions\PreconditionRequiredException;
use OnurSimsek\Precondition\Tests\TestCase;
use OnurSimsek\Precondition\Tests\Unit\Fixtures\LostUpdateValidator;
use PHPUnit\Framework\Attributes\Test;

class PreconditionTest extends TestCase
{
private Precondition $precondition;
protected function setUp(): void
{
parent::setUp();

$this->precondition = new Precondition(LostUpdateValidator::class);
}

#[Test]
public function throw_precondition_required_exception()
{
$request = $this->getMockBuilder(Request::class)->getMock();
$request->expects($this->once())
->method('header')
->with('If-Unmodified-Since')
->willReturn(null);

self::expectException(PreconditionRequiredException::class);
$this->precondition->validate($request);
}

#[Test]
public function throw_precondition_failed_exception()
{
$updatedAt = Date::create(2024, 1, 15, 10, 00, 00, 'utc');

$article = new \stdClass();
$article->updated_at = $updatedAt;

$request = $this->getMockBuilder(Request::class)->getMock();
$request->expects($this->exactly(2))
->method('header')
->with('If-Unmodified-Since')
->willReturn((clone $updatedAt)->addYear());

$request->expects($this->once())
->method('route')
->with('article')
->willReturn($article);

self::expectException(PreconditionFailedException::class);
$this->precondition->validate($request);
}

#[Test]
public function it_can_be_validate_a_proper_request()
{
$updatedAt = Date::create(2024, 1, 15, 10, 00, 00, 'utc');

$article = new \stdClass();
$article->updated_at = $updatedAt;

$request = $this->getMockBuilder(Request::class)->getMock();
$request->expects($this->exactly(2))
->method('header')
->with('If-Unmodified-Since')
->willReturn($updatedAt);

$request->expects($this->once())
->method('route')
->with('article')
->willReturn($article);

self::assertTrue($this->precondition->validate($request));
}
}
20 changes: 20 additions & 0 deletions tests/Unit/Fixtures/Controller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace OnurSimsek\Precondition\Tests\Unit\Fixtures;

use Illuminate\Http\JsonResponse;
use OnurSimsek\Precondition\Attributes\Precondition;

class Controller
{
#[Precondition(LostUpdateValidator::class)]
public function withPreconditionMethod(): JsonResponse
{
return response()->json();
}

public function withoutPreconditionMethod(): JsonResponse
{
return response()->json();
}
}
19 changes: 19 additions & 0 deletions tests/Unit/Fixtures/LostUpdateValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace OnurSimsek\Precondition\Tests\Unit\Fixtures;

use Illuminate\Http\Request;
use OnurSimsek\Precondition\Validators\PreconditionValidator;

class LostUpdateValidator extends PreconditionValidator
{
public function parameter(Request $request): array|string|null
{
return $request->header('If-Unmodified-Since');
}

public function __invoke(Request $request): bool
{
return $this->parameter($request) == $request->route('article')->updated_at;
}
}
134 changes: 134 additions & 0 deletions tests/Unit/Middleware/PreconditionRequestTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php

declare(strict_types=1);

namespace OnurSimsek\Precondition\Tests\Unit\Middleware;

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Routing\Route;
use Illuminate\Routing\RouteCollectionInterface;
use Illuminate\Routing\Router;
use OnurSimsek\Precondition\Attributes\Precondition;
use OnurSimsek\Precondition\Middleware\PreconditionRequest;
use OnurSimsek\Precondition\Reflection;
use OnurSimsek\Precondition\Tests\TestCase;
use PHPUnit\Framework\Attributes\Test;

class PreconditionRequestTest extends TestCase
{
#[Test]
public function it_should_not_be_touch_closure_route()
{
$route = $this->getMockBuilder(Route::class)->disableOriginalConstructor()->getMock();
$route->expects($this->once())
->method('getControllerClass')
->willReturn(null);

$routeCollection = $this->getMockBuilder(RouteCollectionInterface::class)->getMock();
$routeCollection->expects($this->once())
->method('match')
->willReturn($route);

$router = $this->getMockBuilder(Router::class)->disableOriginalConstructor()->getMock();
$router->expects($this->once())
->method('getRoutes')
->willReturn($routeCollection);

$reflection = $this->getReflectionMock();
$middleware = new PreconditionRequest($router, $reflection);

self::assertInstanceOf(Response::class, $middleware->handle(new Request(), fn (Request $request) => new Response()));
}

#[Test]
public function it_should_not_be_touch_when_the_action_doesnt_have_the_attribute()
{
$route = $this->getMockBuilder(Route::class)->disableOriginalConstructor()->getMock();
$route->expects($this->exactly(2))
->method('getControllerClass')
->willReturn('UserController');

$route->expects($this->once())
->method('getActionMethod')
->willReturn('index');

$routeCollection = $this->getMockBuilder(RouteCollectionInterface::class)->getMock();
$routeCollection->expects($this->once())
->method('match')
->willReturn($route);

$router = $this->getMockBuilder(Router::class)->disableOriginalConstructor()->getMock();
$router->expects($this->once())
->method('getRoutes')
->willReturn($routeCollection);

$reflection = $this->getReflectionMock();
$reflection->expects($this->once())
->method('reflect')
->willReturnSelf();

$reflection->expects($this->once())
->method('hasPreconditionAttribute')
->willReturn(false);

$middleware = new PreconditionRequest($router, $reflection);

self::assertInstanceOf(Response::class, $middleware->handle(new Request(), fn (Request $request) => new Response()));
}

#[Test]
public function it_can_be_validate_precondition_request()
{
$route = $this->getMockBuilder(Route::class)->disableOriginalConstructor()->getMock();
$route->expects($this->exactly(2))
->method('getControllerClass')
->willReturn('UserController');

$route->expects($this->once())
->method('getActionMethod')
->willReturn('index');

$routeCollection = $this->getMockBuilder(RouteCollectionInterface::class)->getMock();
$routeCollection->expects($this->once())
->method('match')
->willReturn($route);

$router = $this->getMockBuilder(Router::class)->disableOriginalConstructor()->getMock();
$router->expects($this->once())
->method('getRoutes')
->willReturn($routeCollection);

$precondition = $this->getPreconditionAttributeMock();
$precondition->expects($this->once())
->method('validate')
->willReturn(true);

$reflection = $this->getReflectionMock();
$reflection->expects($this->once())
->method('reflect')
->willReturnSelf();

$reflection->expects($this->once())
->method('hasPreconditionAttribute')
->willReturn(true);

$reflection->expects($this->once())
->method('getPreconditionInstance')
->willReturn($precondition);

$middleware = new PreconditionRequest($router, $reflection);

self::assertInstanceOf(Response::class, $middleware->handle(new Request(), fn (Request $request) => new Response()));
}

private function getReflectionMock(): \PHPUnit\Framework\MockObject\MockObject|Reflection
{
return $this->getMockBuilder(Reflection::class)->disableOriginalConstructor()->getMock();
}

private function getPreconditionAttributeMock(): \PHPUnit\Framework\MockObject\MockObject
{
return $this->getMockBuilder(Precondition::class)->disableOriginalConstructor()->getMock();
}
}
Loading

0 comments on commit aced827

Please sign in to comment.