get(route($route))->assertRedirect(route('admin.login')); })->with($adminRoutes); it('sends a guest to the right login for whichever side they hit, in exclusive mode too', function () { // The other of the two modes redirectGuestsTo() has to get right — see // the shared-mode version of this test above. AdminArea::isConsole() // reads admin_access.* LIVE, per request (unlike route domain-binding, // which this suite's routes do not carry: they are compiled once at // boot under the default non-exclusive ADMIN_HOSTS="", so both // route('admin.login') and route('login') below resolve against // whichever host the current request came in on, exactly as production // would resolve them for a request truly received on that host). config()->set('admin_access.hosts', ['admin.example.test']); config()->set('admin_access.exclusive', true); $consoleLocation = $this->get('http://admin.example.test/admin') ->assertRedirect()->headers->get('Location'); expect(parse_url($consoleLocation, PHP_URL_HOST))->toBe('admin.example.test') ->and(parse_url($consoleLocation, PHP_URL_PATH))->toBe('/admin/login'); // A different, non-console host in the same exclusive setup still gets // the PORTAL's sign-in page — this fix is not a blanket change to every // guest redirect, only to the ones landing on the console side. $portalLocation = $this->get('http://app.example.test/dashboard') ->assertRedirect()->headers->get('Location'); expect(parse_url($portalLocation, PHP_URL_HOST))->toBe('app.example.test') ->and(parse_url($portalLocation, PHP_URL_PATH))->toBe('/login'); }); it('forbids non-admin users from the admin console', function (string $route) { $user = User::factory()->create(); $this->actingAs($user)->get(route($route))->assertForbidden(); })->with($adminRoutes); it('renders the admin console for admins', function (string $route) { $admin = Operator::factory()->role('Owner')->create(); // Asserted on the console badge and the sign-out control rather than on the // wordmark: the wordmark is split across an element so its second half can // carry the accent, so "CluPilot" is not a contiguous string in the markup. // These two say what the assertion actually meant — the console shell, with // its navigation, rendered for this route. $this->actingAs($admin, 'operator')->get(route($route)) ->assertOk() ->assertSee(__('admin.badge')) ->assertSee(__('admin.nav.overview')) ->assertSee(__('dashboard.logout')); })->with($adminRoutes);