Commit fb2519c6 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix(routing): register the scanner above the wildcard that was swallowing it

/attendance/{session} binds its parameter to a uuid, so /attendance/scan
registered after it never matched: the wildcard took 'scan' first and died
casting it to a uuid — a 500, not a 404, so it did not look like a routing
problem at all.

Found by rendering the staff screens rather than by trusting that they route:
route:list sorts its output, so it showed the scanner sitting above the
wildcard when the file has it below. The list is not the matcher.

Adds the staff-screen smoke test that caught it, which also asserts a member
account is refused the approval queue and the scanner.
Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
parent 1819b943
...@@ -301,6 +301,11 @@ ...@@ -301,6 +301,11 @@
->middleware('permission:attendance.list'); ->middleware('permission:attendance.list');
Route::get('/attendance/quick', \App\Livewire\Attendance\QuickAttendance::class)->name('attendance.quick') Route::get('/attendance/quick', \App\Livewire\Attendance\QuickAttendance::class)->name('attendance.quick')
->middleware('permission:attendance.mark'); ->middleware('permission:attendance.mark');
// Must stay above /attendance/{session}: that route binds its parameter to
// a uuid, so a literal segment registered after it never matches — the
// wildcard takes 'scan' first and dies casting it to a uuid.
Route::get('/attendance/scan', \App\Livewire\Attendance\CheckInScanner::class)->name('attendance.scan')
->middleware('permission:attendance.scan');
Route::get('/attendance/{session}', TakeAttendance::class)->name('attendance.take') Route::get('/attendance/{session}', TakeAttendance::class)->name('attendance.take')
->middleware('permission:attendance.mark'); ->middleware('permission:attendance.mark');
...@@ -615,11 +620,6 @@ ...@@ -615,11 +620,6 @@
->middleware('permission:users.merge') ->middleware('permission:users.merge')
->name('users.duplicates'); ->name('users.duplicates');
// ─── Check-in scanner ───────────────────────────────────────
Route::get('/attendance/scan', \App\Livewire\Attendance\CheckInScanner::class)
->middleware('permission:attendance.scan')
->name('attendance.scan');
// ─── InstaPay transfer proofs ─────────────────────────────── // ─── InstaPay transfer proofs ───────────────────────────────
// The review queue ships before the member-facing upload: a proof that can // The review queue ships before the member-facing upload: a proof that can
// be submitted and never reviewed is a promise nobody is keeping. // be submitted and never reviewed is a promise nobody is keeping.
......
<?php
namespace Tests\Feature;
use App\Models\User;
use Tests\TestCase;
/**
* Renders the staff-facing screens this programme added, against a restored
* copy of a real tenant database.
*
* DB_CONNECTION=pgsql DB_DATABASE=oc_sport_test ./vendor/bin/phpunit --filter AdminScreensSmokeTest
*/
class AdminScreensSmokeTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
if (config('database.default') !== 'pgsql') {
$this->markTestSkipped('Needs a restored Postgres tenant.');
}
}
public function test_the_new_staff_screens_render_for_an_owner(): void
{
$owner = User::withoutGlobalScopes()
->whereHas('primaryRole', fn ($q) => $q->where('slug', 'academy_owner'))
->firstOrFail();
foreach ([
'payment-proofs.index',
'portal-invitations.index',
'users.duplicates',
'attendance.scan',
] as $name) {
$response = $this->actingAs($owner)->get(route($name));
fwrite(STDERR, sprintf(" %-26s %d %6d bytes\n",
$name, $response->getStatusCode(), strlen($response->getContent())));
if ($response->getStatusCode() !== 200) {
fwrite(STDERR, ' ' . substr(strip_tags($response->getContent()), 0, 300) . "\n");
}
$this->assertSame(200, $response->getStatusCode(), "{$name} did not render");
}
}
public function test_a_member_account_cannot_reach_a_staff_screen(): void
{
$resolver = app(\App\Domain\Identity\Services\GuardianResolver::class);
$member = User::withoutGlobalScopes()->whereNotNull('person_id')->limit(200)->get()
->first(fn ($u) => $resolver->participantIdsFor($u) !== [] && ! $u->can('payments.approve_proof'));
if (! $member) {
$this->markTestSkipped('No member account without the approval permission on this tenant.');
}
$this->actingAs($member)->get(route('payment-proofs.index'))->assertForbidden();
$this->actingAs($member)->get(route('attendance.scan'))->assertForbidden();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment