diff --git a/src/CallbackRegistrar.php b/src/CallbackRegistrar.php index dca70db..35c0964 100644 --- a/src/CallbackRegistrar.php +++ b/src/CallbackRegistrar.php @@ -4,6 +4,7 @@ use Exception; use Illuminate\Support\Str; +use InvalidArgumentException; use ReflectionClass; use ReflectionMethod; @@ -16,6 +17,13 @@ class CallbackRegistrar */ protected $proxies; + /** + * The default callback and its arguments. + * + * @var array + */ + protected $default = [[Callbacks::class, 'unauthorized'], []]; + /** * Create a new CallbacksRegistrar instance. */ @@ -141,4 +149,49 @@ public function hasProxy(string $proxy) { return array_key_exists($proxy, $this->proxies); } + + /** + * Set the default callback and its arguments. + * + * @param string|callable $callback + * @param mixed ...$arguments + * + * @return void + */ + public function setDefault($callback, ...$arguments) + { + if (is_callable($callback)) { + return $this->default = [$callback, $arguments]; + } + + if (is_string($callback)) { + return $this->default = [$this->callback($callback), $arguments]; + } + + throw new InvalidArgumentException(sprintf( + "%s expects parameter 1 to be string or callable %s given", + __FUNCTION__, + gettype($callback) + )); + } + + /** + * Get the default callback callable and its arguments. + * + * @return array + */ + public function getDefault() + { + return $this->default; + } + + /** + * Invoke the default callback. + * + * @return mixed + */ + public function invokeDefault() + { + return call_user_func_array(...$this->default); + } } diff --git a/src/Callbacks.php b/src/Callbacks.php index bde8076..9aec3a1 100644 --- a/src/Callbacks.php +++ b/src/Callbacks.php @@ -6,6 +6,16 @@ class Callbacks { + /** + * Return a HTTP 401 Unauthorized response. + * + * @throws \Symfony\Component\HttpKernel\Exception\HttpException + */ + public static function unauthorized() + { + throw new HttpException(401); + } + /** * Return a HTTP 404 Not Found response. * diff --git a/src/Http/Middleware/GeoMiddleware.php b/src/Http/Middleware/GeoMiddleware.php index 64cf5bc..df49a23 100644 --- a/src/Http/Middleware/GeoMiddleware.php +++ b/src/Http/Middleware/GeoMiddleware.php @@ -5,6 +5,7 @@ use Closure; use Illuminate\Http\Request; use LaraCrafts\GeoRoutes\DeterminesGeoAccess; +use LaraCrafts\GeoRoutes\Support\Facades\CallbackRegistrar; class GeoMiddleware { @@ -24,7 +25,7 @@ public function handle(Request $request, Closure $next) $strategy = config()->get('geo-routes.global.strategy'); if (!$this->shouldHaveAccess($countries, $strategy)) { - abort(401); + return CallbackRegistrar::invokeDefault(); } return $next($request); diff --git a/src/Http/Middleware/GeoRoutesMiddleware.php b/src/Http/Middleware/GeoRoutesMiddleware.php index 1e14d43..d199307 100644 --- a/src/Http/Middleware/GeoRoutesMiddleware.php +++ b/src/Http/Middleware/GeoRoutesMiddleware.php @@ -7,6 +7,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; use LaraCrafts\GeoRoutes\DeterminesGeoAccess; +use LaraCrafts\GeoRoutes\Support\Facades\CallbackRegistrar; class GeoRoutesMiddleware { @@ -25,8 +26,7 @@ public function handle(Request $request, Closure $next) $route = $request->route(); if (!$route) { - #TODO: Invoke the default callback. - return abort(401); + return CallbackRegistrar::invokeDefault(); } $constraint = $route->getAction('geo') ?? []; @@ -49,6 +49,6 @@ public function handle(Request $request, Closure $next) return call_user_func_array($callback[0], $callback[1]); } - return abort(401); + return CallbackRegistrar::invokeDefault(); } } diff --git a/src/Support/Facades/CallbackRegistrar.php b/src/Support/Facades/CallbackRegistrar.php index bee83f8..810bf6d 100644 --- a/src/Support/Facades/CallbackRegistrar.php +++ b/src/Support/Facades/CallbackRegistrar.php @@ -12,6 +12,9 @@ * @method static mixed callback(string $name, callable $callable = null) * @method static boolean hasCallback(string $name) * @method static boolean hasProxy(string $proxy) + * @method static void setDefault(string|callable $callback, ...$arguments) + * @method static array getDefault() + * @method static mixed invokeDefault() * * @see \LaraCrafts\GeoRoutes\CallbackRegistrar */ diff --git a/tests/Unit/CallbackRegistrarTest.php b/tests/Unit/CallbackRegistrarTest.php index f1385c7..6650f79 100644 --- a/tests/Unit/CallbackRegistrarTest.php +++ b/tests/Unit/CallbackRegistrarTest.php @@ -3,6 +3,7 @@ namespace LaraCrafts\GeoRoutes\Tests\Unit; use Closure; +use InvalidArgumentException; use LaraCrafts\GeoRoutes\CallbackRegistrar; use LaraCrafts\GeoRoutes\Tests\TestCase; use Mockery; @@ -22,9 +23,14 @@ class CallbackRegistrarTest extends TestCase /** @var \Illuminate\Routing\Router */ protected $router; + /** @var \LaraCrafts\GeoRoutes\CallbackRegistrar|\Mockery\MockInterface */ + protected $registrar; + public function setUp(): void { parent::setUp(); + + $this->registrar = new CallbackRegistrar; } public function tearDown(): void @@ -39,10 +45,9 @@ public function testIfCallbacksFetchesTheProxiesList() 'bar' => '\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::bar', ]; - $registrar = new CallbackRegistrar; - $registrar->loadCallbacks($callbacks); + $this->registrar->loadCallbacks($callbacks); - $proxies = $registrar->callbacks(); + $proxies = $this->registrar->callbacks(); foreach ($callbacks as $key => $callback) { $this->assertArrayHasKey('or' . ucfirst($key), $proxies); @@ -57,11 +62,11 @@ public function testIfCallbacksInvokesLoadCallbacksWhenArrayArgIsPresent() 'bar' => '\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::bar', ]; - $registrar = Mockery::mock(CallbackRegistrar::class)->makePartial(); + $this->registrar = Mockery::mock(CallbackRegistrar::class)->makePartial(); - $registrar->shouldReceive('loadCallbacks')->with($callbacks)->once(); + $this->registrar->shouldReceive('loadCallbacks')->with($callbacks)->once(); - $registrar->callbacks($callbacks); + $this->registrar->callbacks($callbacks); } public function testIfParseCallbacksLoadsCallbacksFromClass() @@ -71,11 +76,9 @@ public function testIfParseCallbacksLoadsCallbacksFromClass() 'orBar' => '\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::bar', ]; - $registrar = new CallbackRegistrar; - - $registrar->parseCallbacks(\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::class); + $this->registrar->parseCallbacks(\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::class); - $proxies = $registrar->callbacks(); + $proxies = $this->registrar->callbacks(); foreach ($expected as $proxy => $callable) { $this->assertArrayHasKey($proxy, $proxies); @@ -85,41 +88,78 @@ public function testIfParseCallbacksLoadsCallbacksFromClass() public function testIfCallbackReturnsCallable() { - $registrar = new CallbackRegistrar; - $registrar->loadCallbacks(['foo' => '\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::foo']); + $this->registrar->loadCallbacks(['foo' => '\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::foo']); - $this->assertEquals('\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::foo', $registrar->callback('foo')); - $this->assertEquals('\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::foo', $registrar->callback('orFoo')); + $this->assertEquals('\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::foo', $this->registrar->callback('foo')); + $this->assertEquals('\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::foo', $this->registrar->callback('orFoo')); } public function testIfHasCallbackReturnsTrueIfCallbackExists() { - $registrar = new CallbackRegistrar; - $registrar->loadCallbacks(['foo' => '\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::foo']); + $this->registrar->loadCallbacks(['foo' => '\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::foo']); - $this->assertTrue($registrar->hasCallback('foo')); + $this->assertTrue($this->registrar->hasCallback('foo')); } public function testIfHasCallbackReturnsFalseIfCallbackDoesNotExist() { - $registrar = new CallbackRegistrar; - - $this->assertFalse($registrar->hasCallback('foo')); + $this->assertFalse($this->registrar->hasCallback('foo')); } public function testIfHasProxyReturnsTrueIfProxyExists() { - $registrar = new CallbackRegistrar; - $registrar->loadCallbacks(['foo' => '\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::foo']); + $this->registrar->loadCallbacks(['foo' => '\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::foo']); - $this->assertTrue($registrar->hasProxy('orFoo')); + $this->assertTrue($this->registrar->hasProxy('orFoo')); } public function testIfHasProxyReturnsFalseIfProxyDoesNotExist() { - $registrar = new CallbackRegistrar; + $this->assertFalse($this->registrar->hasProxy('orFoo')); + } + + public function testIfSetDefaultInvokesCallbackIfArgIsString() + { + $this->registrar = Mockery::mock(CallbackRegistrar::class)->makePartial(); + $this->registrar->loadCallbacks(['foo' => '\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::foo']); + + $this->registrar->shouldReceive('callback')->with('foo')->once(); + + $this->registrar->setDefault('foo'); + } + + public function testIfSetDefaultSetsDefaultPropertyValueIfArgIsCallable() + { + $this->registrar->setDefault('\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::foo', 'arg1'); + + $default = $this->getProperty($this->registrar, 'default')->getValue($this->registrar); + + $this->assertEquals(['\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::foo', ['arg1']], $default); + } + + public function testIfSetDefaultThrowsExceptionIfArgIsInvalid() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('setDefault expects parameter 1 to be string or callable integer given'); + + $this->registrar->setDefault(321314); + } + + public function testIfGetDefaultReturnsDefaultCallbackArray() + { + $this->registrar->setDefault('\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::foo', 'arg1'); + + $this->assertEquals( + ['\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::foo', ['arg1']], + $this->registrar->getDefault() + ); + } + + public function testIfInvokeDefaultExecutesDefault() + { + $this->registrar->setDefault('\LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks::foo'); - $this->assertFalse($registrar->hasProxy('orFoo')); + $this->assertEquals('foo', $this->registrar->invokeDefault()); } /** diff --git a/tests/Unit/GeoMiddlewareTest.php b/tests/Unit/GeoMiddlewareTest.php index 8c9f6f8..3f5a17e 100644 --- a/tests/Unit/GeoMiddlewareTest.php +++ b/tests/Unit/GeoMiddlewareTest.php @@ -4,6 +4,8 @@ use Illuminate\Contracts\Http\Kernel; use Illuminate\Http\Request; +use LaraCrafts\GeoRoutes\Support\Facades\CallbackRegistrar; +use LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks; use LaraCrafts\GeoRoutes\Tests\TestCase; use Mockery; @@ -58,4 +60,20 @@ public function testIfAllowedCountryIsLetThrough() $response = $this->kernel->handle($this->request); $this->assertEquals(200, $response->getStatusCode()); } + + public function testIfMiddlewareExecutesDefaultCallback() + { + $callbacks = Mockery::mock(Callbacks::class); + $callbacks->shouldReceive('foo')->once()->with('bar')->andReturn('foo'); + + CallbackRegistrar::setDefault([$callbacks, 'foo'], 'bar'); + + $this->location->shouldReceive('get') + ->once() + ->andReturn((object)['countryCode' => 'ch']); + + $response = $this->kernel->handle($this->request); + + $this->assertEquals('foo', $response); + } } diff --git a/tests/Unit/GeoRoutesMiddlewareTest.php b/tests/Unit/GeoRoutesMiddlewareTest.php index 4586fc5..6a33c21 100644 --- a/tests/Unit/GeoRoutesMiddlewareTest.php +++ b/tests/Unit/GeoRoutesMiddlewareTest.php @@ -5,6 +5,8 @@ use Illuminate\Http\Request; use Illuminate\Routing\Route; use LaraCrafts\GeoRoutes\Http\Middleware\GeoRoutesMiddleware; +use LaraCrafts\GeoRoutes\Support\Facades\CallbackRegistrar; +use LaraCrafts\GeoRoutes\Tests\Mocks\Callbacks; use LaraCrafts\GeoRoutes\Tests\TestCase; use Mockery; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; @@ -78,23 +80,23 @@ public function middlewareAllowsAccess() /** @test */ public function middlewareExecutesCallback() { - $mockClass = Mockery::mock('alias:mockClass'); - $mockClass->shouldReceive('callback') + $callbacks = Mockery::mock(Callbacks::class); + $callbacks->shouldReceive('foo') ->once() ->with('arg') - ->andReturn('MockCallback'); + ->andReturn('foo'); $this->location->shouldReceive('get') ->once() ->andReturn((object)['countryCode' => 'ca']); - $callback = ['mockClass::callback', ['arg']]; + $callback = [[$callbacks, 'foo'], ['arg']]; $this->setGeoConstraint('allow', 'us', $callback); $output = $this->middleware->handle($this->request, $this->next); - $this->assertEquals('MockCallback', $output); + $this->assertEquals('foo', $output); } /** @test */ @@ -112,7 +114,24 @@ public function middlewareThrowsExceptionIfTheGeoAttributeIsInvalid() ->once() ->andReturn([]); - $output = $this->middleware->handle($this->request, $this->next); + $this->middleware->handle($this->request, $this->next); + } + + /** @test */ + public function middlewareExecutesDefaultCallbackIfNoCallbackIsFound() + { + $callbacks = Mockery::mock(Callbacks::class); + $callbacks->shouldReceive('foo')->once()->with('bar'); + + CallbackRegistrar::setDefault([$callbacks, 'foo'], 'bar'); + + $this->location->shouldReceive('get') + ->once() + ->andReturn((object)['countryCode' => 'ca']); + + $this->setGeoConstraint('deny', 'ca'); + + $this->middleware->handle($this->request, $this->next); } /**