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;
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', 'existingRequests'));
}
return view('doubler_request.index', compact('events', 'doublers', 'students', '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,
'event_id' => $thisRequest['event_id'],
'student_id' => $thisRequest['student_id'],
'request' => $thisRequest['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');
}
}

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()
{
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

View File

@ -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());
}

View File

@ -13,24 +13,18 @@
</tr>
</thead>
<x-table.body>
@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)
<tr>
<x-table.td>{{ $student->full_name() }}</x-table.td>
<x-table.td>
@foreach($doublers[$event->id][$student->id]['entries'] as $entry)
<p>{{$entry->audition->name}}</p>
@foreach($doubler->entries() as $entry)
<p>{{ $entry->audition->name }}</p>
@endforeach
</x-table.td>
<x-table.td>
<x-form.field
value="{{$value}}"
value="{{ $existingRequests[$event->id][$student->id] ?? '' }}"
name="doubler_requests[{{$event->id}}][{{$student->id}}]"/>
</x-table.td>
</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);
}
});
});