From f771aa8645dac1e630b399e42c7621cff9ac838d Mon Sep 17 00:00:00 2001 From: lukinovec Date: Mon, 2 Jun 2025 19:05:17 +0200 Subject: [PATCH] [4.x] Test that route model binding works correctly with path identification (#1360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Test that route model binding works with path identification (closure-based routes) * Correct test name * Update tests/PathIdentificationTest.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * make assertions more clear --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Samuel Ć tancl --- tests/PathIdentificationTest.php | 49 ++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/PathIdentificationTest.php b/tests/PathIdentificationTest.php index 79cd3816..fd602a9f 100644 --- a/tests/PathIdentificationTest.php +++ b/tests/PathIdentificationTest.php @@ -3,15 +3,29 @@ declare(strict_types=1); use Illuminate\Contracts\Http\Kernel; +use Illuminate\Database\QueryException; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Routing\Middleware\SubstituteBindings; use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Schema; +use Illuminate\Support\Facades\Event; use Stancl\Tenancy\Exceptions\RouteIsMissingTenantParameterException; use Stancl\Tenancy\Exceptions\TenantColumnNotWhitelistedException; use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByPathException; use Stancl\Tenancy\Middleware\InitializeTenancyByPath; use Stancl\Tenancy\Resolvers\PathTenantResolver; use Stancl\Tenancy\Tests\Etc\Tenant; +use Stancl\Tenancy\Events\TenantCreated; +use Stancl\Tenancy\Jobs\CreateDatabase; +use Stancl\Tenancy\Jobs\MigrateDatabase; +use Stancl\JobPipeline\JobPipeline; +use Stancl\Tenancy\Events\TenancyInitialized; +use Stancl\Tenancy\Listeners\BootstrapTenancy; +use Stancl\Tenancy\Listeners\RevertToCentralContext; +use Stancl\Tenancy\Tests\Etc\User; +use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper; +use Stancl\Tenancy\Events\TenancyEnded; + use function Stancl\Tenancy\Tests\pest; beforeEach(function () { @@ -246,3 +260,38 @@ test('any extra model column needs to be whitelisted', function () { config(['tenancy.identification.resolvers.' . PathTenantResolver::class . '.allowed_extra_model_columns' => ['slug']]); pest()->get('/acme/foo')->assertSee($tenant->getTenantKey()); }); + +test('route model binding works with path identification', function() { + config(['tenancy.bootstrappers' => [DatabaseTenancyBootstrapper::class]]); + + Event::listen(TenancyInitialized::class, BootstrapTenancy::class); + Event::listen(TenantCreated::class, JobPipeline::make([ + CreateDatabase::class, MigrateDatabase::class, + ])->send(fn (TenantCreated $event) => $event->tenant)->toListener()); + Event::listen(TenancyEnded::class, RevertToCentralContext::class); + + $tenant = Tenant::create(); + + $this->withoutExceptionHandling(); + + // Importantly, the route must have the 'web' middleware group, or SubstituteBindings directly + Route::get('/{tenant}/foo/{user}', fn (User $user) => $user->name)->middleware([InitializeTenancyByPath::class, 'web']); + Route::get('/{tenant}/bar/{user}', fn (User $user) => $user->name)->middleware([InitializeTenancyByPath::class, SubstituteBindings::class]); + + $user = $tenant->run(fn () => User::create(['name' => 'John Doe', 'email' => 'john@doe.com', 'password' => 'foobar'])); + + pest()->get("/{$tenant->id}/foo/{$user->id}")->assertSee("John Doe"); + tenancy()->end(); + pest()->get("/{$tenant->id}/bar/{$user->id}")->assertSee("John Doe"); + tenancy()->end(); + + // If SubstituteBindings comes BEFORE tenancy middleware and middleware priority is not set, route model binding is NOT expected to work correctly + // Since SubstituteBindings runs first, it tries to query the central database instead of the tenant database (which fails with a QueryException in this case) + Route::get('/{tenant}/baz/{user}', fn (User $user) => $user->name ?: 'No user')->middleware([SubstituteBindings::class, InitializeTenancyByPath::class]); + expect(fn () => pest()->get("/{$tenant->id}/baz/{$user->id}"))->toThrow(QueryException::class); + tenancy()->end(); + + // If SubstituteBindings is NOT USED AT ALL, we simply get an empty User instance + Route::get('/{tenant}/xyz/{user}', fn (User $user) => $user->name ?: 'No user')->middleware([InitializeTenancyByPath::class]); + pest()->get("/{$tenant->id}/xyz/{$user->id}")->assertSee('No user'); +});