Skip to content

Commit efc0e11

Browse files
committed
Ignore routes that are already considered tenant routes from cloning, update test accordingly
1 parent 410165e commit efc0e11

File tree

2 files changed

+74
-69
lines changed

2 files changed

+74
-69
lines changed

src/Actions/CloneRoutesAsTenant.php

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ public function cloneRoute(Route|string $route): static
111111

112112
protected function shouldBeCloned(Route $route): bool
113113
{
114+
// Don't clone routes that already have tenant parameter or prefix
115+
if ($this->routeIsTenant($route)) {
116+
return false;
117+
}
118+
114119
if ($this->shouldClone) {
115120
return ($this->shouldClone)($route);
116121
}
@@ -166,45 +171,26 @@ protected function copyMiscRouteProperties(Route $originalRoute, Route $newRoute
166171
->setDefaults($originalRoute->defaults);
167172
}
168173

169-
/**
170-
* Process middleware for cloning, handling middleware groups properly.
171-
* This extracts middleware from groups (up to 3 levels deep), filters out
172-
* cloneRoutesWithMiddleware, and adds the 'tenant' middleware.
173-
*
174-
* Uses approach similar to getRouteMiddleware() in DealsWithRouteContexts for consistency.
175-
*/
174+
/** Removes top-level cloneRoutesWithMiddleware and adds 'tenant' middleware. */
176175
protected function processMiddlewareForCloning(array $middlewares): array
177176
{
178-
$middlewareGroups = $this->router->getMiddlewareGroups();
179-
180-
$unpackGroupMiddleware = function (array $middleware) use ($middlewareGroups) {
181-
$innerMiddleware = [];
182-
183-
foreach ($middleware as $inner) {
184-
if (isset($middlewareGroups[$inner])) {
185-
$innerMiddleware = array_merge($innerMiddleware, $middlewareGroups[$inner]);
186-
} else {
187-
// Actual middleware, not a group
188-
$innerMiddleware[] = $inner;
189-
}
190-
}
191-
192-
return $innerMiddleware;
193-
};
194-
195-
// Extract all middleware from groups (up to 3 levels deep)
196-
$firstLevelUnpacked = $unpackGroupMiddleware($middlewares);
197-
$secondLevelUnpacked = $unpackGroupMiddleware($firstLevelUnpacked);
198-
$thirdLevelUnpacked = $unpackGroupMiddleware($secondLevelUnpacked);
199-
200-
// Filter out MW in cloneRoutesWithMiddleware and add the 'tenant' flag
177+
// Filter out top-level cloneRoutesWithMiddleware and add the 'tenant' flag
201178
$processedMiddleware = array_filter(
202-
$thirdLevelUnpacked,
179+
$middlewares,
203180
fn ($mw) => ! in_array($mw, $this->cloneRoutesWithMiddleware)
204181
);
205182

206183
$processedMiddleware[] = 'tenant';
207184

208185
return array_unique($processedMiddleware);
209186
}
187+
188+
/** Check if route already has tenant parameter or name prefix. */
189+
protected function routeIsTenant(Route $route): bool
190+
{
191+
$routeHasTenantParameter = in_array(PathTenantResolver::tenantParameterName(), $route->parameterNames());
192+
$routeHasTenantPrefix = $route->getName() && str_starts_with($route->getName(), PathTenantResolver::tenantRouteNamePrefix());
193+
194+
return $routeHasTenantParameter || $routeHasTenantPrefix;
195+
}
210196
}

tests/CloneActionTest.php

Lines changed: 57 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -215,43 +215,62 @@
215215
->toBe("http://localhost/prefix/{$tenant->getTenantKey()}/home");
216216
});
217217

218-
test('clone middleware within middleware groups is properly handled during cloning', function () {
219-
// Simple MW group with 'clone' flag
220-
RouteFacade::middlewareGroup('simple-group', ['auth', 'clone']);
221-
222-
// Define nested middleware groups 3 levels deep
223-
RouteFacade::middlewareGroup('level-3-group', ['clone']);
224-
RouteFacade::middlewareGroup('level-2-group', ['auth', 'level-3-group']);
225-
RouteFacade::middlewareGroup('nested-group', ['web', 'level-2-group']);
226-
227-
// Create routes using both simple and nested middleware groups
228-
RouteFacade::get('/simple', fn () => true)
229-
->middleware('simple-group')
230-
->name('simple');
231-
232-
RouteFacade::get('/nested', fn () => true)
233-
->middleware('nested-group')
234-
->name('nested');
235-
236-
app(CloneRoutesAsTenant::class)->handle();
237-
238-
// Test simple middleware group handling
239-
$clonedSimpleRoute = RouteFacade::getRoutes()->getByName('tenant.simple');
240-
expect($clonedSimpleRoute)->not()->toBeNull();
241-
242-
$simpleRouteMiddleware = tenancy()->getRouteMiddleware($clonedSimpleRoute);
243-
expect($simpleRouteMiddleware)
244-
->toContain('auth', 'tenant')
245-
->not()->toContain('clone', 'simple-group');
246-
247-
// Test nested middleware group handling (3 levels deep)
248-
$clonedNestedRoute = RouteFacade::getRoutes()->getByName('tenant.nested');
249-
expect($clonedNestedRoute)->not()->toBeNull();
250-
251-
$nestedRouteMiddleware = tenancy()->getRouteMiddleware($clonedNestedRoute);
218+
test('tenant routes are ignored from cloning and clone middleware in groups causes no issues', function () {
219+
// Should NOT be cloned, already has tenant parameter
220+
RouteFacade::get("/{tenant}/route-with-tenant-parameter", fn () => true)
221+
->middleware(['clone'])
222+
->name("tenant.route-with-tenant-parameter");
223+
224+
// Should NOT be cloned
225+
// The route already has tenant name prefix
226+
RouteFacade::get("/route-with-tenant-name-prefix", fn () => true)
227+
->middleware(['clone'])
228+
->name("tenant.route-with-tenant-name-prefix");
229+
230+
// Should NOT be cloned
231+
// The route already has a tenant parameter + 'clone' middleware in group
232+
// 'clone' MW in groups won't be removed, this also doesn't cause any issues
233+
RouteFacade::middlewareGroup('group', ['auth', 'clone']);
234+
RouteFacade::get("/{tenant}/route-with-clone-in-mw-group", fn () => true)
235+
->middleware('group')
236+
->name("tenant.route-with-clone-in-mw-group");
237+
238+
// SHOULD be cloned (with clone middleware)
239+
RouteFacade::get('/foo', fn () => true)
240+
->middleware(['clone'])
241+
->name('foo');
242+
243+
// SHOULD be cloned (with nested clone middleware)
244+
RouteFacade::get('/bar', fn () => true)
245+
->middleware(['group'])
246+
->name('bar');
247+
248+
$cloneAction = app(CloneRoutesAsTenant::class);
249+
$initialRouteCount = count(RouteFacade::getRoutes()->get());
250+
251+
// Run clone action multiple times
252+
$cloneAction->handle();
253+
$firstRunCount = count(RouteFacade::getRoutes()->get());
254+
255+
$cloneAction->handle();
256+
$secondRunCount = count(RouteFacade::getRoutes()->get());
257+
258+
$cloneAction->handle();
259+
$thirdRunCount = count(RouteFacade::getRoutes()->get());
260+
261+
// Two route should have been cloned, and only once
262+
expect($firstRunCount)->toBe($initialRouteCount + 2);
263+
// No new routes on subsequent runs
264+
expect($secondRunCount)->toBe($firstRunCount);
265+
expect($thirdRunCount)->toBe($firstRunCount);
266+
267+
// Verify the correct routes were cloned
268+
expect(RouteFacade::getRoutes()->getByName('tenant.foo'))->not()->toBeNull();
269+
expect(RouteFacade::getRoutes()->getByName('tenant.bar'))->not()->toBeNull();
252270

253-
expect($nestedRouteMiddleware)
254-
->toContain('web', 'auth', 'tenant')
255-
// Shouldn't contain 'clone' or any nested group names
256-
->not()->toContain('clone', 'nested-group', 'level-2-group', 'level-3-group');
271+
// Tenant routes were not duplicated
272+
$allRouteNames = collect(RouteFacade::getRoutes()->get())->map->getName()->filter();
273+
expect($allRouteNames->filter(fn($name) => str_contains($name, 'route-with-tenant-parameter'))->count())->toBe(1);
274+
expect($allRouteNames->filter(fn($name) => str_contains($name, 'route-with-tenant-name-prefix'))->count())->toBe(1);
275+
expect($allRouteNames->filter(fn($name) => str_contains($name, 'route-with-clone-in-mw-group'))->count())->toBe(1);
257276
});

0 commit comments

Comments
 (0)