From 96b590365ad99466cedc8bacf68fabf4dc5e9af2 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 5 Jul 2025 23:44:16 -0500 Subject: [PATCH] Testing for DoublerRequestController.php --- .../Controllers/DoublerRequestController.php | 102 +++++++----------- .../Requests/DoublerRequestsStoreRequest.php | 76 +++++++++++++ app/Models/Doubler.php | 2 +- app/Providers/AppServiceProvider.php | 11 +- .../views/doubler_request/index.blade.php | 16 +-- .../DoublerRequestControllerTest.php | 92 ++++++++++++++++ 6 files changed, 222 insertions(+), 77 deletions(-) create mode 100644 app/Http/Requests/DoublerRequestsStoreRequest.php create mode 100644 tests/Feature/app/Http/Controllers/DoublerRequestControllerTest.php diff --git a/app/Http/Controllers/DoublerRequestController.php b/app/Http/Controllers/DoublerRequestController.php index a79b7e0..3e55e2b 100644 --- a/app/Http/Controllers/DoublerRequestController.php +++ b/app/Http/Controllers/DoublerRequestController.php @@ -2,88 +2,62 @@ namespace App\Http\Controllers; -use App\Models\AuditLogEntry; +use App\Http\Requests\DoublerRequestsStoreRequest; use App\Models\DoublerRequest; use App\Models\Event; -use App\Models\Student; -use App\Services\DoublerService; -use Barryvdh\Debugbar\Facades\Debugbar; -use Psr\Container\ContainerExceptionInterface; -use Psr\Container\NotFoundExceptionInterface; +use Illuminate\Contracts\Foundation\Application; +use Illuminate\Contracts\View\Factory; +use Illuminate\Contracts\View\View; use function auth; use function compact; -use function request; use function to_route; 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(); - $students = auth()->user()->school->students; - $studentIds = $students->pluck('id'); - $existingRequests = DoublerRequest::whereIn('student_id', $studentIds)->get(); - $doublers = []; - foreach ($events as $event) { - $event_doublers = $doublerService->doublersForEvent($event); - $doublers[$event->id] = $event_doublers; - } + $existingRequests = auth()->user()->school->doublerRequests + ->groupBy('event_id') + ->map(function ($requestsForEvent) { + return $requestsForEvent->keyBy('student_id'); + }); + $doublers = auth()->user()->school->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')); } - /** - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - */ - public function makeRequest() + public function makeRequest(DoublerRequestsStoreRequest $request) { - foreach (request()->get('doubler_requests') as $event_id => $requests) { - 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(); - } + foreach ($request->getDoublerRequests() as $thisRequest) { - continue; - } - DoublerRequest::upsert([ - 'event_id' => $event_id, - 'student_id' => $student_id, - 'request' => $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.'
Request: '.$request, - 'affected' => ['students' => [$student_id]], - ]); - } + DoublerRequest::upsert([ + 'event_id' => $thisRequest['event_id'], + 'student_id' => $thisRequest['student_id'], + 'request' => $thisRequest['request'], + ], + uniqueBy: ['event_id', 'student_id'], + update: ['request'] + ); } - echo 'hi'; return to_route('doubler_request.index')->with('success', 'Recorded doubler requests'); + } } diff --git a/app/Http/Requests/DoublerRequestsStoreRequest.php b/app/Http/Requests/DoublerRequestsStoreRequest.php new file mode 100644 index 0000000..5dd2105 --- /dev/null +++ b/app/Http/Requests/DoublerRequestsStoreRequest.php @@ -0,0 +1,76 @@ + ['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; + } +} diff --git a/app/Models/Doubler.php b/app/Models/Doubler.php index 20c71a6..79016c2 100644 --- a/app/Models/Doubler.php +++ b/app/Models/Doubler.php @@ -30,7 +30,7 @@ class Doubler extends Model 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 diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 550cb32..8cd550b 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -10,15 +10,21 @@ use App\Actions\Tabulation\TotalEntryScores; use App\Models\BonusScore; use App\Models\Entry; use App\Models\EntryFlag; +use App\Models\Event; +use App\Models\School; use App\Models\SchoolEmailDomain; use App\Models\ScoreSheet; use App\Models\Student; +use App\Models\User; use App\Observers\BonusScoreObserver; use App\Observers\EntryFlagObserver; use App\Observers\EntryObserver; +use App\Observers\EventObserver; use App\Observers\SchoolEmailDomainObserver; +use App\Observers\SchoolObserver; use App\Observers\ScoreSheetObserver; use App\Observers\StudentObserver; +use App\Observers\UserObserver; use App\Services\AuditionService; use App\Services\DoublerService; use App\Services\DrawService; @@ -55,10 +61,13 @@ class AppServiceProvider extends ServiceProvider { BonusScore::observe(BonusScoreObserver::class); Entry::observe(EntryObserver::class); + EntryFlag::observe(EntryFlagObserver::class); + Event::observe(EventObserver::class); + School::observe(SchoolObserver::class); SchoolEmailDomain::observe(SchoolEmailDomainObserver::class); ScoreSheet::observe(ScoreSheetObserver::class); Student::observe(StudentObserver::class); - EntryFlag::observe(EntryFlagObserver::class); + User::observe(UserObserver::class); // Model::preventLazyLoading(! app()->isProduction()); } diff --git a/resources/views/doubler_request/index.blade.php b/resources/views/doubler_request/index.blade.php index 7d2d6fa..31c30a3 100644 --- a/resources/views/doubler_request/index.blade.php +++ b/resources/views/doubler_request/index.blade.php @@ -13,24 +13,18 @@ - @foreach($students as $student) - @continue(! array_key_exists($student->id, $doublers[$event->id])) - @php - $existingRequest = $existingRequests - ->where('student_id',$student->id) - ->where('event_id',$event->id); - $value = $existingRequest?->first()?->request ?? ''; - @endphp + @foreach($doublers[$event->id] ?? [] as $doubler) + @php($student = $doubler->student) {{ $student->full_name() }} - @foreach($doublers[$event->id][$student->id]['entries'] as $entry) -

{{$entry->audition->name}}

+ @foreach($doubler->entries() as $entry) +

{{ $entry->audition->name }}

@endforeach
diff --git a/tests/Feature/app/Http/Controllers/DoublerRequestControllerTest.php b/tests/Feature/app/Http/Controllers/DoublerRequestControllerTest.php new file mode 100644 index 0000000..14aa964 --- /dev/null +++ b/tests/Feature/app/Http/Controllers/DoublerRequestControllerTest.php @@ -0,0 +1,92 @@ +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); + } + }); +});