fix(release): gate the /release route on the flag (spec gating triple)
Register the /release route only when config('clusev.release_controls') is
true — the first of the spec's three gating layers (route + component mount +
sidebar). The route file is re-included per request when uncached, so the flag
is honoured; mount() abort_unless(...) still covers the cached-route case.
The gating test re-evaluates routes/web.php after toggling the flag (the file
is read once at boot, so a runtime config()->set() alone cannot register the
route), mirroring a fresh request.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat/v1-foundation
parent
4b68b8dc04
commit
76c1d46d12
|
|
@ -135,11 +135,13 @@ Route::middleware('auth')->group(function () {
|
||||||
Route::get('/help', Help\Index::class)->name('help');
|
Route::get('/help', Help\Index::class)->name('help');
|
||||||
Route::get('/wireguard', Wireguard\Index::class)->name('wireguard');
|
Route::get('/wireguard', Wireguard\Index::class)->name('wireguard');
|
||||||
|
|
||||||
// Dev-only Release page. The route is always registered (routes/web.php is evaluated once at
|
// Dev-only Release page — first of the gating triple (spec): the route is registered ONLY when
|
||||||
// boot, so a boot-time config flag could not be toggled per-request), but the component's
|
// the flag is on, so with it off /release does not exist (uncached route file is re-included
|
||||||
// mount() abort_unless(config('clusev.release_controls')) is the real gate: with the flag off
|
// per request, so the flag is honoured). Release\Index::mount() abort_unless(...) is the second
|
||||||
// every hit 404s, exactly as if the route did not exist. The sidebar item is flag-gated too.
|
// guard (covers a cached route file); the sidebar item is flag-gated too (third).
|
||||||
Route::get('/release', Release\Index::class)->name('release');
|
if (config('clusev.release_controls')) {
|
||||||
|
Route::get('/release', Release\Index::class)->name('release');
|
||||||
|
}
|
||||||
|
|
||||||
Route::get('/wg-status.json', fn (\App\Services\WgStatus $wg) => response()->json($wg->read()))->name('wg.status');
|
Route::get('/wg-status.json', fn (\App\Services\WgStatus $wg) => response()->json($wg->read()))->name('wg.status');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Routing\RouteCollection;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
|
||||||
class ReleaseGatingTest extends TestCase
|
class ReleaseGatingTest extends TestCase
|
||||||
|
|
@ -16,27 +18,41 @@ class ReleaseGatingTest extends TestCase
|
||||||
$this->actingAs(User::factory()->create(['must_change_password' => false]));
|
$this->actingAs(User::factory()->create(['must_change_password' => false]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the release flag and re-evaluate routes/web.php so the conditional `/release`
|
||||||
|
* registration reflects it. The route file is read once at boot (flag default off), so a
|
||||||
|
* runtime config()->set() alone cannot register the route — re-loading the file mirrors a
|
||||||
|
* fresh request that re-includes routes/web.php with the flag now set.
|
||||||
|
*/
|
||||||
|
private function withReleaseFlag(bool $on): void
|
||||||
|
{
|
||||||
|
config()->set('clusev.release_controls', $on);
|
||||||
|
Route::setRoutes(new RouteCollection);
|
||||||
|
require base_path('routes/web.php');
|
||||||
|
$this->app['router']->getRoutes()->refreshNameLookups();
|
||||||
|
}
|
||||||
|
|
||||||
public function test_release_route_is_404_when_the_flag_is_off(): void
|
public function test_release_route_is_404_when_the_flag_is_off(): void
|
||||||
{
|
{
|
||||||
config()->set('clusev.release_controls', false);
|
$this->withReleaseFlag(false);
|
||||||
$this->get('/release')->assertNotFound();
|
$this->get('/release')->assertNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_release_route_loads_when_the_flag_is_on(): void
|
public function test_release_route_loads_when_the_flag_is_on(): void
|
||||||
{
|
{
|
||||||
config()->set('clusev.release_controls', true);
|
$this->withReleaseFlag(true);
|
||||||
$this->get('/release')->assertOk();
|
$this->get('/release')->assertOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_sidebar_hides_the_release_item_when_the_flag_is_off(): void
|
public function test_sidebar_hides_the_release_item_when_the_flag_is_off(): void
|
||||||
{
|
{
|
||||||
config()->set('clusev.release_controls', false);
|
$this->withReleaseFlag(false);
|
||||||
$this->get('/audit')->assertOk()->assertDontSee(__('release.nav'));
|
$this->get('/audit')->assertOk()->assertDontSee(__('release.nav'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_sidebar_shows_the_release_item_when_the_flag_is_on(): void
|
public function test_sidebar_shows_the_release_item_when_the_flag_is_on(): void
|
||||||
{
|
{
|
||||||
config()->set('clusev.release_controls', true);
|
$this->withReleaseFlag(true);
|
||||||
$this->get('/audit')->assertOk()->assertSee(__('release.nav'));
|
$this->get('/audit')->assertOk()->assertSee(__('release.nav'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue