From 2ac76c84f706efffd0920445b6f56580f7e6819f Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 15 May 2023 16:23:33 -0500 Subject: [PATCH 1/5] Fixes singleton and api singletons when combined with creatable|destroyable|only|except options --- src/Illuminate/Routing/ResourceRegistrar.php | 10 +- src/Illuminate/Routing/Router.php | 17 +- .../Routing/RouteSingletonTest.php | 214 +++++++++++++++++- 3 files changed, 227 insertions(+), 14 deletions(-) diff --git a/src/Illuminate/Routing/ResourceRegistrar.php b/src/Illuminate/Routing/ResourceRegistrar.php index 39a54188f61b..4c9c1626ba69 100644 --- a/src/Illuminate/Routing/ResourceRegistrar.php +++ b/src/Illuminate/Routing/ResourceRegistrar.php @@ -249,13 +249,17 @@ protected function getResourceMethods($defaults, $options) $methods = array_diff($methods, (array) $options['except']); } + if (isset($options['apiSingleton'])) { + $methods = array_diff($methods, ['create', 'edit']); + } + if (isset($options['creatable'])) { $methods = isset($options['apiSingleton']) ? array_merge(['store', 'destroy'], $methods) : array_merge(['create', 'store', 'destroy'], $methods); return $this->getResourceMethods( - $methods, array_values(Arr::except($options, ['creatable'])) + $methods, Arr::except($options, ['creatable']) ); } @@ -263,11 +267,11 @@ protected function getResourceMethods($defaults, $options) $methods = array_merge(['destroy'], $methods); return $this->getResourceMethods( - $methods, array_values(Arr::except($options, ['destroyable'])) + $methods, Arr::except($options, ['destroyable']) ); } - return $methods; + return array_values($methods); } /** diff --git a/src/Illuminate/Routing/Router.php b/src/Illuminate/Routing/Router.php index 412da8dbd114..47deb15eb16f 100644 --- a/src/Illuminate/Routing/Router.php +++ b/src/Illuminate/Routing/Router.php @@ -427,16 +427,17 @@ public function apiSingletons(array $singletons, array $options = []) */ public function apiSingleton($name, $controller, array $options = []) { - $only = ['show', 'update', 'destroy']; - - if (isset($options['except'])) { - $only = array_diff($only, (array) $options['except']); + if ($this->container && $this->container->bound(ResourceRegistrar::class)) { + $registrar = $this->container->make(ResourceRegistrar::class); + } else { + $registrar = new ResourceRegistrar($this); } - return $this->singleton($name, $controller, array_merge([ - 'only' => $only, - 'apiSingleton' => true, - ], $options)); + return new PendingSingletonResourceRegistration( + $registrar, $name, $controller, array_merge([ + 'apiSingleton' => true, + ], $options), + ); } /** diff --git a/tests/Integration/Routing/RouteSingletonTest.php b/tests/Integration/Routing/RouteSingletonTest.php index 804edad76a99..cf4d4e1732b3 100644 --- a/tests/Integration/Routing/RouteSingletonTest.php +++ b/tests/Integration/Routing/RouteSingletonTest.php @@ -62,9 +62,61 @@ public function testCreatableSingleton() $this->assertSame('singleton destroy', $response->getContent()); } + public function testCreatableSingletonOnly() + { + Route::singleton('avatar', CreatableSingletonTestController::class)->creatable()->only('show'); + + $response = $this->get('/avatar'); + $this->assertEquals(200, $response->getStatusCode()); + + $response = $this->get('/avatar/create'); + $this->assertEquals(404, $response->getStatusCode()); + + $response = $this->post('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->get('/avatar/edit'); + $this->assertEquals(404, $response->getStatusCode()); + + $response = $this->put('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->patch('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->delete('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + } + + public function testCreatableSingletonExcept() + { + Route::singleton('avatar', CreatableSingletonTestController::class)->creatable()->except('show'); + + $response = $this->get('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->get('/avatar/create'); + $this->assertEquals(200, $response->getStatusCode()); + + $response = $this->post('/avatar'); + $this->assertEquals(200, $response->getStatusCode()); + + $response = $this->get('/avatar/edit'); + $this->assertEquals(200, $response->getStatusCode()); + + $response = $this->put('/avatar'); + $this->assertEquals(200, $response->getStatusCode()); + + $response = $this->patch('/avatar'); + $this->assertEquals(200, $response->getStatusCode()); + + $response = $this->delete('/avatar'); + $this->assertEquals(200, $response->getStatusCode()); + } + public function testDestroyableSingleton() { - Route::singleton('avatar', CreatableSingletonTestController::class)->destroyable(); + Route::singleton('avatar', SingletonTestController::class)->destroyable(); $this->assertSame('http://localhost/avatar', route('avatar.show')); $response = $this->get('/avatar'); @@ -87,6 +139,58 @@ public function testDestroyableSingleton() $this->assertSame('singleton destroy', $response->getContent()); } + public function testDestroyableSingletonOnly() + { + Route::singleton('avatar', SingletonTestController::class)->destroyable()->only('destroy'); + + $response = $this->get('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->get('/avatar/create'); + $this->assertEquals(404, $response->getStatusCode()); + + $response = $this->post('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->get('/avatar/edit'); + $this->assertEquals(404, $response->getStatusCode()); + + $response = $this->put('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->patch('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->delete('/avatar'); + $this->assertEquals(200, $response->getStatusCode()); + } + + public function testDestroyableSingletonExcept() + { + Route::singleton('avatar', SingletonTestController::class)->destroyable()->except('destroy'); + + $response = $this->get('/avatar'); + $this->assertEquals(200, $response->getStatusCode()); + + $response = $this->get('/avatar/create'); + $this->assertEquals(404, $response->getStatusCode()); + + $response = $this->post('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->get('/avatar/edit'); + $this->assertEquals(200, $response->getStatusCode()); + + $response = $this->put('/avatar'); + $this->assertEquals(200, $response->getStatusCode()); + + $response = $this->patch('/avatar'); + $this->assertEquals(200, $response->getStatusCode()); + + $response = $this->delete('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + } + public function testApiSingleton() { Route::apiSingleton('avatar', SingletonTestController::class); @@ -94,8 +198,8 @@ public function testApiSingleton() $response = $this->get('/avatar/create'); $this->assertEquals(404, $response->getStatusCode()); - $response = $this->post('/avatar/store'); - $this->assertEquals(404, $response->getStatusCode()); + $response = $this->post('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); $this->assertSame('http://localhost/avatar', route('avatar.update')); $response = $this->put('/avatar'); @@ -121,6 +225,58 @@ public function testCreatableApiSingleton() $this->assertSame('singleton update', $response->getContent()); } + public function testCreatableApiSingletonOnly() + { + Route::apiSingleton('avatar', CreatableSingletonTestController::class)->creatable()->only(['create', 'store']); + + $response = $this->get('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->get('/avatar/create'); + $this->assertEquals(404, $response->getStatusCode()); + + $response = $this->post('/avatar'); + $this->assertEquals(200, $response->getStatusCode()); + + $response = $this->get('/avatar/edit'); + $this->assertEquals(404, $response->getStatusCode()); + + $response = $this->put('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->patch('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->delete('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + } + + public function testCreatableApiSingletonExcept() + { + Route::apiSingleton('avatar', CreatableSingletonTestController::class)->creatable()->except(['create', 'store']); + + $response = $this->get('/avatar'); + $this->assertEquals(200, $response->getStatusCode()); + + $response = $this->get('/avatar/create'); + $this->assertEquals(404, $response->getStatusCode()); + + $response = $this->post('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->get('/avatar/edit'); + $this->assertEquals(404, $response->getStatusCode()); + + $response = $this->put('/avatar'); + $this->assertEquals(200, $response->getStatusCode()); + + $response = $this->patch('/avatar'); + $this->assertEquals(200, $response->getStatusCode()); + + $response = $this->delete('/avatar'); + $this->assertEquals(200, $response->getStatusCode()); + } + public function testDestroyableApiSingleton() { Route::apiSingleton('avatar', CreatableSingletonTestController::class)->destroyable(); @@ -141,6 +297,58 @@ public function testDestroyableApiSingleton() $this->assertSame('singleton destroy', $response->getContent()); } + public function testDestroyableApiSingletonOnly() + { + Route::apiSingleton('avatar', CreatableSingletonTestController::class)->destroyable()->only(['destroy']); + + $response = $this->get('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->get('/avatar/create'); + $this->assertEquals(404, $response->getStatusCode()); + + $response = $this->post('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->get('/avatar/edit'); + $this->assertEquals(404, $response->getStatusCode()); + + $response = $this->put('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->patch('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->delete('/avatar'); + $this->assertEquals(200, $response->getStatusCode()); + } + + public function testDestroyableApiSingletonExcept() + { + Route::apiSingleton('avatar', CreatableSingletonTestController::class)->destroyable()->except(['destroy', 'show']); + + $response = $this->get('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->get('/avatar/create'); + $this->assertEquals(404, $response->getStatusCode()); + + $response = $this->post('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->get('/avatar/edit'); + $this->assertEquals(404, $response->getStatusCode()); + + $response = $this->put('/avatar'); + $this->assertEquals(200, $response->getStatusCode()); + + $response = $this->patch('/avatar'); + $this->assertEquals(200, $response->getStatusCode()); + + $response = $this->delete('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + } + public function testSingletonOnly() { Route::singleton('avatar', SingletonTestController::class)->only('show'); From 659dfa5d86850a2023fa3faf1acef0b626e57fa8 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 15 May 2023 16:27:35 -0500 Subject: [PATCH 2/5] Reverts change --- tests/Integration/Routing/RouteSingletonTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Integration/Routing/RouteSingletonTest.php b/tests/Integration/Routing/RouteSingletonTest.php index cf4d4e1732b3..2d2caad3da4c 100644 --- a/tests/Integration/Routing/RouteSingletonTest.php +++ b/tests/Integration/Routing/RouteSingletonTest.php @@ -116,7 +116,7 @@ public function testCreatableSingletonExcept() public function testDestroyableSingleton() { - Route::singleton('avatar', SingletonTestController::class)->destroyable(); + Route::singleton('avatar', CreatableSingletonTestController::class)->destroyable(); $this->assertSame('http://localhost/avatar', route('avatar.show')); $response = $this->get('/avatar'); From f0c05193f66e91fc362a0ab7b55f7d536dbda45e Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 15 May 2023 16:38:23 -0500 Subject: [PATCH 3/5] More tests --- .../Routing/RouteSingletonTest.php | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/Integration/Routing/RouteSingletonTest.php b/tests/Integration/Routing/RouteSingletonTest.php index 2d2caad3da4c..5a9d28f5c23b 100644 --- a/tests/Integration/Routing/RouteSingletonTest.php +++ b/tests/Integration/Routing/RouteSingletonTest.php @@ -191,6 +191,32 @@ public function testDestroyableSingletonExcept() $this->assertEquals(405, $response->getStatusCode()); } + public function testCreatableDestroyableSingletonOnlyExceptTest() + { + Route::singleton('avatar', SingletonTestController::class)->creatable()->destroyable()->only(['show'])->except(['destroy']); + + $response = $this->get('/avatar'); + $this->assertEquals(200, $response->getStatusCode()); + + $response = $this->get('/avatar/create'); + $this->assertEquals(404, $response->getStatusCode()); + + $response = $this->post('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->get('/avatar/edit'); + $this->assertEquals(404, $response->getStatusCode()); + + $response = $this->put('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->patch('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->delete('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + } + public function testApiSingleton() { Route::apiSingleton('avatar', SingletonTestController::class); @@ -349,6 +375,32 @@ public function testDestroyableApiSingletonExcept() $this->assertEquals(405, $response->getStatusCode()); } + public function testCreatableDestroyableApiSingletonOnlyExceptTest() + { + Route::apiSingleton('avatar', CreatableSingletonTestController::class)->creatable()->destroyable()->only(['show'])->except(['destroy']); + + $response = $this->get('/avatar'); + $this->assertEquals(200, $response->getStatusCode()); + + $response = $this->get('/avatar/create'); + $this->assertEquals(404, $response->getStatusCode()); + + $response = $this->post('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->get('/avatar/edit'); + $this->assertEquals(404, $response->getStatusCode()); + + $response = $this->put('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->patch('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + + $response = $this->delete('/avatar'); + $this->assertEquals(405, $response->getStatusCode()); + } + public function testSingletonOnly() { Route::singleton('avatar', SingletonTestController::class)->only('show'); From e0e443f8f4ae409b7e7e08edf2091d50dfd50061 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 15 May 2023 17:34:35 -0500 Subject: [PATCH 4/5] Adds more tests --- tests/Routing/RouteRegistrarTest.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/Routing/RouteRegistrarTest.php b/tests/Routing/RouteRegistrarTest.php index aba801b36873..5ea1cefac2ba 100644 --- a/tests/Routing/RouteRegistrarTest.php +++ b/tests/Routing/RouteRegistrarTest.php @@ -592,6 +592,19 @@ public function testCanExcludeMethodsOnRegisteredResource() $this->assertTrue($this->router->getRoutes()->hasNamedRoute('users.destroy')); } + public function testCanLimitAndExcludeMethodsOnRegisteredResource() + { + $this->router->resource('users', RouteRegistrarControllerStub::class) + ->only('index', 'show', 'destroy') + ->except('destroy'); + + $this->assertCount(2, $this->router->getRoutes()); + + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('users.index')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('users.show')); + $this->assertFalse($this->router->getRoutes()->hasNamedRoute('users.destroy')); + } + public function testCanSetShallowOptionOnRegisteredResource() { $this->router->resource('users.tasks', RouteRegistrarControllerStub::class)->shallow(); From 5594b544cb989517f3c294b573a3656513a495ac Mon Sep 17 00:00:00 2001 From: Jess Archer Date: Tue, 16 May 2023 12:22:40 +1000 Subject: [PATCH 5/5] Make API singleton 'only' behavior consistent with API resource --- src/Illuminate/Routing/ResourceRegistrar.php | 29 +--- src/Illuminate/Routing/Router.php | 16 +- .../Routing/RouteSingletonTest.php | 2 +- tests/Routing/RouteRegistrarTest.php | 149 ++++++++++++++++++ 4 files changed, 163 insertions(+), 33 deletions(-) diff --git a/src/Illuminate/Routing/ResourceRegistrar.php b/src/Illuminate/Routing/ResourceRegistrar.php index 4c9c1626ba69..f383aedca80b 100644 --- a/src/Illuminate/Routing/ResourceRegistrar.php +++ b/src/Illuminate/Routing/ResourceRegistrar.php @@ -2,7 +2,6 @@ namespace Illuminate\Routing; -use Illuminate\Support\Arr; use Illuminate\Support\Str; class ResourceRegistrar @@ -149,6 +148,12 @@ public function singleton($name, $controller, array $options = []) $defaults = $this->singletonResourceDefaults; + if (isset($options['creatable'])) { + $defaults = array_merge($defaults, ['create', 'store', 'destroy']); + } elseif (isset($options['destroyable'])) { + $defaults = array_merge($defaults, ['destroy']); + } + $collection = new RouteCollection; $resourceMethods = $this->getResourceMethods($defaults, $options); @@ -249,28 +254,6 @@ protected function getResourceMethods($defaults, $options) $methods = array_diff($methods, (array) $options['except']); } - if (isset($options['apiSingleton'])) { - $methods = array_diff($methods, ['create', 'edit']); - } - - if (isset($options['creatable'])) { - $methods = isset($options['apiSingleton']) - ? array_merge(['store', 'destroy'], $methods) - : array_merge(['create', 'store', 'destroy'], $methods); - - return $this->getResourceMethods( - $methods, Arr::except($options, ['creatable']) - ); - } - - if (isset($options['destroyable'])) { - $methods = array_merge(['destroy'], $methods); - - return $this->getResourceMethods( - $methods, Arr::except($options, ['destroyable']) - ); - } - return array_values($methods); } diff --git a/src/Illuminate/Routing/Router.php b/src/Illuminate/Routing/Router.php index 47deb15eb16f..ecb492d1f0d5 100644 --- a/src/Illuminate/Routing/Router.php +++ b/src/Illuminate/Routing/Router.php @@ -427,17 +427,15 @@ public function apiSingletons(array $singletons, array $options = []) */ public function apiSingleton($name, $controller, array $options = []) { - if ($this->container && $this->container->bound(ResourceRegistrar::class)) { - $registrar = $this->container->make(ResourceRegistrar::class); - } else { - $registrar = new ResourceRegistrar($this); + $only = ['store', 'show', 'update', 'destroy']; + + if (isset($options['except'])) { + $only = array_diff($only, (array) $options['except']); } - return new PendingSingletonResourceRegistration( - $registrar, $name, $controller, array_merge([ - 'apiSingleton' => true, - ], $options), - ); + return $this->singleton($name, $controller, array_merge([ + 'only' => $only, + ], $options)); } /** diff --git a/tests/Integration/Routing/RouteSingletonTest.php b/tests/Integration/Routing/RouteSingletonTest.php index 5a9d28f5c23b..c7209e66e2ef 100644 --- a/tests/Integration/Routing/RouteSingletonTest.php +++ b/tests/Integration/Routing/RouteSingletonTest.php @@ -259,7 +259,7 @@ public function testCreatableApiSingletonOnly() $this->assertEquals(405, $response->getStatusCode()); $response = $this->get('/avatar/create'); - $this->assertEquals(404, $response->getStatusCode()); + $this->assertEquals(200, $response->getStatusCode()); $response = $this->post('/avatar'); $this->assertEquals(200, $response->getStatusCode()); diff --git a/tests/Routing/RouteRegistrarTest.php b/tests/Routing/RouteRegistrarTest.php index 5ea1cefac2ba..90c0ab03a31c 100644 --- a/tests/Routing/RouteRegistrarTest.php +++ b/tests/Routing/RouteRegistrarTest.php @@ -1101,6 +1101,155 @@ public function testCanRemoveMiddlewareFromGroupUnregisteredGroup() $this->assertEquals([], $this->router->getMiddlewareGroups()); } + public function testCanRegisterSingleton() + { + $this->router->singleton('user', RouteRegistrarControllerStub::class); + + $this->assertCount(3, $this->router->getRoutes()); + + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.show')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.edit')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.update')); + } + + public function testCanRegisterApiSingleton() + { + $this->router->apiSingleton('user', RouteRegistrarControllerStub::class); + + $this->assertCount(2, $this->router->getRoutes()); + + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.show')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.update')); + } + + public function testCanRegisterCreatableSingleton() + { + $this->router->singleton('user', RouteRegistrarControllerStub::class)->creatable(); + + $this->assertCount(6, $this->router->getRoutes()); + + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.create')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.store')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.show')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.edit')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.update')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.destroy')); + } + + public function testCanRegisterCreatableApiSingleton() + { + $this->router->apiSingleton('user', RouteRegistrarControllerStub::class)->creatable(); + + $this->assertCount(4, $this->router->getRoutes()); + + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.store')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.show')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.update')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.destroy')); + } + + public function testSingletonCreatableNotDestroyable() + { + $this->router->singleton('user', RouteRegistrarControllerStub::class) + ->creatable() + ->except('destroy'); + + $this->assertCount(5, $this->router->getRoutes()); + + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.create')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.store')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.show')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.edit')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.update')); + $this->assertFalse($this->router->getRoutes()->hasNamedRoute('user.destroy')); + } + + public function testApiSingletonCreatableNotDestroyable() + { + $this->router->apiSingleton('user', RouteRegistrarControllerStub::class) + ->creatable() + ->except('destroy'); + + $this->assertCount(3, $this->router->getRoutes()); + + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.store')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.show')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.update')); + $this->assertFalse($this->router->getRoutes()->hasNamedRoute('user.destroy')); + } + + public function testSingletonCanBeDestroyable() + { + $this->router->singleton('user', RouteRegistrarControllerStub::class) + ->destroyable(); + + $this->assertCount(4, $this->router->getRoutes()); + + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.show')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.edit')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.update')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.destroy')); + } + + public function testApiSingletonCanBeDestroyable() + { + $this->router->apiSingleton('user', RouteRegistrarControllerStub::class) + ->destroyable(); + + $this->assertCount(3, $this->router->getRoutes()); + + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.show')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.update')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.destroy')); + } + + public function testSingletonCanBeOnlyCreatable() + { + $this->router->singleton('user', RouteRegistrarControllerStub::class) + ->creatable() + ->only('create', 'store'); + + $this->assertCount(2, $this->router->getRoutes()); + + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.create')); + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.store')); + } + + public function testApiSingletonCanBeOnlyCreatable() + { + $this->router->apiSingleton('user', RouteRegistrarControllerStub::class) + ->creatable() + ->only('store'); + + $this->assertCount(1, $this->router->getRoutes()); + + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.store')); + } + + public function testSingletonDoesntAllowIncludingUnsupportedMethods() + { + $this->router->singleton('post', RouteRegistrarControllerStub::class) + ->only('index', 'store', 'create', 'destroy'); + + $this->assertCount(0, $this->router->getRoutes()); + + $this->router->apiSingleton('user', RouteRegistrarControllerStub::class) + ->only('index', 'store', 'create', 'destroy'); + + $this->assertCount(0, $this->router->getRoutes()); + } + + public function testApiSingletonCanIncludeAnySingletonMethods() + { + // This matches the behavior of the apiResource method. + $this->router->apiSingleton('user', RouteRegistrarControllerStub::class) + ->only('edit'); + + $this->assertCount(1, $this->router->getRoutes()); + + $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.edit')); + } + /** * Get the last route registered with the router. *