Testing for DoublerRequestController.php

This commit is contained in:
Matt Young 2025-07-05 23:44:16 -05:00
parent ba55d75172
commit 96b590365a
6 changed files with 222 additions and 77 deletions

View File

@ -2,88 +2,62 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\AuditLogEntry; use App\Http\Requests\DoublerRequestsStoreRequest;
use App\Models\DoublerRequest; use App\Models\DoublerRequest;
use App\Models\Event; use App\Models\Event;
use App\Models\Student; use Illuminate\Contracts\Foundation\Application;
use App\Services\DoublerService; use Illuminate\Contracts\View\Factory;
use Barryvdh\Debugbar\Facades\Debugbar; use Illuminate\Contracts\View\View;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use function auth; use function auth;
use function compact; use function compact;
use function request;
use function to_route; use function to_route;
class DoublerRequestController extends Controller class DoublerRequestController extends Controller
{ {
public function index(DoublerService $doublerService) /**
* Display a listing of the resource.
*
* Data sent to view:
* - events - all existing events
* - existingRequests - previously made requests for each event, keyed by student id
* existingRequest[eventId][student id]-> Request
* - doublers - existing doublers, grouped by event. Keyed by event_id and student_id
*
* @return Application|Factory|View|\Illuminate\Foundation\Application|\Illuminate\View\View
*/
public function index()
{ {
$events = Event::all(); $events = Event::all();
$students = auth()->user()->school->students; $existingRequests = auth()->user()->school->doublerRequests
$studentIds = $students->pluck('id'); ->groupBy('event_id')
$existingRequests = DoublerRequest::whereIn('student_id', $studentIds)->get(); ->map(function ($requestsForEvent) {
$doublers = []; return $requestsForEvent->keyBy('student_id');
foreach ($events as $event) { });
$event_doublers = $doublerService->doublersForEvent($event); $doublers = auth()->user()->school->doublers()
$doublers[$event->id] = $event_doublers; ->with('student')
} ->with('event')
->get()
->groupBy('event_id');
return view('doubler_request.index', compact('events', 'doublers', 'students', 'existingRequests')); return view('doubler_request.index', compact('events', 'doublers', 'existingRequests'));
} }
/** public function makeRequest(DoublerRequestsStoreRequest $request)
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function makeRequest()
{ {
foreach (request()->get('doubler_requests') as $event_id => $requests) { foreach ($request->getDoublerRequests() as $thisRequest) {
if (! Event::find($event_id)->exists()) {
return to_route('doubler_request.index')->with('error', 'Invalid event id specified');
}
$thisEvent = Event::find($event_id);
foreach ($requests as $student_id => $request) {
if (! Student::find($student_id)->exists()) {
return to_route('doubler_request.index')->with('error', 'Invalid student id specified');
}
$thisStudent = Student::find($student_id);
if (! $request) {
$oldRequest = DoublerRequest::where('student_id', $student_id)
->where('event_id', $event_id)
->first();
if ($oldRequest) {
Debugbar::info('hit');
AuditLogEntry::create([
'user' => auth()->user()->email,
'ip_address' => request()->ip(),
'message' => 'Removed doubler request for '.$thisStudent->full_name().' in '.$thisEvent->name,
'affected' => ['students' => [$student_id]],
]);
$oldRequest->delete();
}
continue; DoublerRequest::upsert([
} 'event_id' => $thisRequest['event_id'],
DoublerRequest::upsert([ 'student_id' => $thisRequest['student_id'],
'event_id' => $event_id, 'request' => $thisRequest['request'],
'student_id' => $student_id, ],
'request' => $request, uniqueBy: ['event_id', 'student_id'],
], update: ['request']
uniqueBy: ['event_id', 'student_id'], );
update: ['request']
);
AuditLogEntry::create([
'user' => auth()->user()->email,
'ip_address' => request()->ip(),
'message' => 'Made doubler request for '.$thisStudent->full_name().' in '.$thisEvent->name.'<br>Request: '.$request,
'affected' => ['students' => [$student_id]],
]);
}
} }
echo 'hi';
return to_route('doubler_request.index')->with('success', 'Recorded doubler requests'); return to_route('doubler_request.index')->with('success', 'Recorded doubler requests');
} }
} }

View File

@ -0,0 +1,76 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class DoublerRequestsStoreRequest extends FormRequest
{
public function rules(): array
{
return [
'doubler_requests' => ['required', 'array'],
// Validate event IDs (first keys)
'doubler_requests.*' => ['required', 'array'],
// Validate student IDs (second keys) and their values
'doubler_requests.*.*' => [
'required',
'string',
'max:50',
// Custom validation rule to check if the student ID exists in DB
function ($attribute, $value, $fail) {
// $attribute will be like "doubler_requests.1.107"
// Extract event_id and student_id from attribute
preg_match('/doubler_requests\.(\d+)\.(\d+)/', $attribute, $matches);
if (! $matches) {
$fail('Invalid attribute format.');
return;
}
$eventId = $matches[1];
$studentId = $matches[2];
// Check event ID exists
if (! \App\Models\Event::where('id', $eventId)->exists()) {
$fail("Event ID {$eventId} does not exist.");
return;
}
// Check student ID exists
if (! \App\Models\Student::where('id', $studentId)->exists()) {
$fail("Student ID {$studentId} does not exist.");
return;
}
},
],
];
}
public function getDoublerRequests(): array
{
$validated = $this->validated()['doubler_requests'] ?? [];
$result = [];
foreach ($validated as $eventId => $students) {
foreach ($students as $studentId => $request) {
$result[] = [
'event_id' => (int) $eventId,
'student_id' => (int) $studentId,
'request' => $request,
];
}
}
return $result;
}
public function authorize(): bool
{
return true;
}
}

View File

@ -30,7 +30,7 @@ class Doubler extends Model
public function entries() public function entries()
{ {
return Entry::whereIn('id', $this->entries)->get(); return Entry::whereIn('id', $this->entries)->with('student')->with('audition')->get();
} }
// Find a doubler based on both keys // Find a doubler based on both keys

View File

@ -10,15 +10,21 @@ use App\Actions\Tabulation\TotalEntryScores;
use App\Models\BonusScore; use App\Models\BonusScore;
use App\Models\Entry; use App\Models\Entry;
use App\Models\EntryFlag; use App\Models\EntryFlag;
use App\Models\Event;
use App\Models\School;
use App\Models\SchoolEmailDomain; use App\Models\SchoolEmailDomain;
use App\Models\ScoreSheet; use App\Models\ScoreSheet;
use App\Models\Student; use App\Models\Student;
use App\Models\User;
use App\Observers\BonusScoreObserver; use App\Observers\BonusScoreObserver;
use App\Observers\EntryFlagObserver; use App\Observers\EntryFlagObserver;
use App\Observers\EntryObserver; use App\Observers\EntryObserver;
use App\Observers\EventObserver;
use App\Observers\SchoolEmailDomainObserver; use App\Observers\SchoolEmailDomainObserver;
use App\Observers\SchoolObserver;
use App\Observers\ScoreSheetObserver; use App\Observers\ScoreSheetObserver;
use App\Observers\StudentObserver; use App\Observers\StudentObserver;
use App\Observers\UserObserver;
use App\Services\AuditionService; use App\Services\AuditionService;
use App\Services\DoublerService; use App\Services\DoublerService;
use App\Services\DrawService; use App\Services\DrawService;
@ -55,10 +61,13 @@ class AppServiceProvider extends ServiceProvider
{ {
BonusScore::observe(BonusScoreObserver::class); BonusScore::observe(BonusScoreObserver::class);
Entry::observe(EntryObserver::class); Entry::observe(EntryObserver::class);
EntryFlag::observe(EntryFlagObserver::class);
Event::observe(EventObserver::class);
School::observe(SchoolObserver::class);
SchoolEmailDomain::observe(SchoolEmailDomainObserver::class); SchoolEmailDomain::observe(SchoolEmailDomainObserver::class);
ScoreSheet::observe(ScoreSheetObserver::class); ScoreSheet::observe(ScoreSheetObserver::class);
Student::observe(StudentObserver::class); Student::observe(StudentObserver::class);
EntryFlag::observe(EntryFlagObserver::class); User::observe(UserObserver::class);
// Model::preventLazyLoading(! app()->isProduction()); // Model::preventLazyLoading(! app()->isProduction());
} }

View File

@ -13,24 +13,18 @@
</tr> </tr>
</thead> </thead>
<x-table.body> <x-table.body>
@foreach($students as $student) @foreach($doublers[$event->id] ?? [] as $doubler)
@continue(! array_key_exists($student->id, $doublers[$event->id])) @php($student = $doubler->student)
@php
$existingRequest = $existingRequests
->where('student_id',$student->id)
->where('event_id',$event->id);
$value = $existingRequest?->first()?->request ?? '';
@endphp
<tr> <tr>
<x-table.td>{{ $student->full_name() }}</x-table.td> <x-table.td>{{ $student->full_name() }}</x-table.td>
<x-table.td> <x-table.td>
@foreach($doublers[$event->id][$student->id]['entries'] as $entry) @foreach($doubler->entries() as $entry)
<p>{{$entry->audition->name}}</p> <p>{{ $entry->audition->name }}</p>
@endforeach @endforeach
</x-table.td> </x-table.td>
<x-table.td> <x-table.td>
<x-form.field <x-form.field
value="{{$value}}" value="{{ $existingRequests[$event->id][$student->id] ?? '' }}"
name="doubler_requests[{{$event->id}}][{{$student->id}}]"/> name="doubler_requests[{{$event->id}}][{{$student->id}}]"/>
</x-table.td> </x-table.td>
</tr> </tr>

View File

@ -0,0 +1,92 @@
<?php
/** @noinspection PhpUnhandledExceptionInspection */
use App\Actions\Entries\CreateEntry;
use App\Models\Audition;
use App\Models\AuditLogEntry;
use App\Models\DoublerRequest;
use App\Models\Event;
use App\Models\School;
use App\Models\Student;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use function Pest\Laravel\actingAs;
uses(RefreshDatabase::class);
describe('index', function () {
it('returns a form to enter doubler requests', function () {
$school = School::factory()->create();
$user = User::factory()->forSchool($school)->create();
actingAs($user);
$event = Event::factory()->create(['name' => 'Concert']);
$event2 = Event::factory()->create(['name' => 'Jazz']);
$audition11 = Audition::factory()->create([
'minimum_grade' => 1, 'maximum_grade' => 15, 'event_id' => $event->id,
]);
$audition12 = Audition::factory()->create([
'minimum_grade' => 1, 'maximum_grade' => 15, 'event_id' => $event->id,
]);
$audition21 = Audition::factory()->create([
'minimum_grade' => 1, 'maximum_grade' => 15, 'event_id' => $event2->id,
]);
$audition22 = Audition::factory()->create([
'minimum_grade' => 1, 'maximum_grade' => 15, 'event_id' => $event2->id,
]);
$student = Student::factory()->forSchool($school)->create();
$entryDesk = app(CreateEntry::class);
$entryDesk($student, $audition11);
$entryDesk($student, $audition12);
$entryDesk($student, $audition21);
$entryDesk($student, $audition22);
DoublerRequest::create([
'event_id' => $event2->id,
'student_id' => $student->id,
'request' => 'request body',
]);
$response = $this->get(route('doubler_request.index'));
$response->assertOk();
$viewEvents = $response->viewData('events');
$viewDoublers = $response->viewData('doublers');
$viewExistingRequests = $response->viewData('existingRequests');
expect($viewEvents)->toHaveCount(2)
->and($viewDoublers)->toHaveCount(2)
->and($viewDoublers[$event->id]->count())->toBe(1)
->and($viewExistingRequests[$event2->id][$student->id]->request)->toBe('request body');
});
});
describe('makeRequest', function () {
it('submits a request', function () {
$school = School::factory()->create();
$user = User::factory()->forSchool($school)->create();
$event1 = Event::factory()->create();
$event2 = Event::factory()->create();
$student = Student::factory()->forSchool($school)->create();
$student2 = Student::factory()->forSchool($school)->create();
actingAs($user);
$response = $this->post(route('doubler_request.make_request'), [
'doubler_requests' => [
$event1->id => [
$student->id => 'student 1 request in event 1',
$student2->id => 'student 2 request in event 1',
],
$event2->id => [
$student->id => 'student 1 request in event 2',
$student2->id => 'student 2 request in event 2',
],
],
]);
$response->assertRedirect(route('doubler_request.index'));
expect(DoublerRequest::count())->toBe(4)
->and(DoublerRequest::where('event_id', $event2->id)->where('student_id',
$student->id)->first()->request)->toBe('student 1 request in event 2');
foreach (AuditLogEntry::all() as $logEntry) {
dump($logEntry->message);
}
});
});