diff --git a/app/Actions/Entries/CreateEntry.php b/app/Actions/Entries/CreateEntry.php new file mode 100644 index 0000000..daa6825 --- /dev/null +++ b/app/Actions/Entries/CreateEntry.php @@ -0,0 +1,94 @@ +createEntry($student, $audition, $entry_for); + } + + /** + * @throws ManageEntryException + */ + public function createEntry(Student|int $student, Audition|int $audition, string|array|null $entry_for = null) + { + if (is_int($student)) { + $student = Student::find($student); + } + if (is_int($audition)) { + $audition = Audition::find($audition); + } + + if (! $entry_for) { + $entry_for = ['seating', 'advancement']; + } + $entry_for = collect($entry_for); + $this->verifySubmission($student, $audition); + $entry = Entry::make([ + 'student_id' => $student->id, + 'audition_id' => $audition->id, + 'draw_number' => $this->checkDraw($audition), + 'for_seating' => $entry_for->contains('seating'), + 'for_advancement' => $entry_for->contains('advancement'), + ]); + $entry->save(); + + return $entry; + } + + private function checkDraw(Audition $audition) + { + if (! $audition->hasFlag('drawn')) { + return null; + } + // get the maximum value of draw_number from $audition->entries() + $draw_number = $audition->entries()->max('draw_number'); + + return $draw_number + 1; + } + + /** @noinspection PhpUnhandledExceptionInspection */ + private function verifySubmission(?Student $student, ?Audition $audition): void + { + // Make sure it's a valid student + if (! $student || ! $student->exists()) { + throw new ManageEntryException('Invalid student provided'); + } + // Make sure the audition is valid + if (! $audition || ! $audition->exists()) { + throw new ManageEntryException('Invalid audition provided'); + } + // A student can't enter the same audition twice + if (Entry::where('student_id', $student->id)->where('audition_id', $audition->id)->exists()) { + throw new ManageEntryException('That student is already entered in that audition'); + } + // Can't enter a published audition + if ($audition->hasFlag('seats_published')) { + throw new ManageEntryException('Cannot add an entry to an audition where seats are published'); + } + if ($audition->hasFlag('advancement_published')) { + throw new ManageEntryException('Cannot add an entry to an audition where advancement is published'); + } + // Verify the grade of the student is in range for the audition + if ($student->grade > $audition->maximum_grade) { + throw new ManageEntryException('The grade of the student exceeds the maximum for that audition'); + } + if ($student->grade < $audition->minimum_grade) { + throw new ManageEntryException('The grade of the student does not meet the minimum for that audition'); + } + } +} diff --git a/app/Actions/Entries/UpdateEntry.php b/app/Actions/Entries/UpdateEntry.php new file mode 100644 index 0000000..eeb8d90 --- /dev/null +++ b/app/Actions/Entries/UpdateEntry.php @@ -0,0 +1,145 @@ +updateEntry($entry, $updateData); + } + + /** + * @throws ManageEntryException + */ + public function updateEntry(Entry|int $entry, array $updateData): void + { + if (is_int($entry)) { + $entry = Entry::find($entry); + } + if (! $entry || ! $entry->exists) { + throw new ManageEntryException('Invalid entry provided'); + } + $this->entry = $entry; + if (array_key_exists('for_seating', $updateData)) { + $this->updateForSeating($updateData['for_seating']); + } + if (array_key_exists('for_advancement', $updateData)) { + $this->updateForAdvancement($updateData['for_advancement']); + } + if (array_key_exists('audition_id', $updateData)) { + $this->updateAudition($updateData['audition_id']); + } + if (array_key_exists('audition', $updateData)) { + $this->updateAudition($updateData['audition']); + } + + $this->entry->save(); + } + + /** + * @throws ManageEntryException + */ + private function updateAudition(Audition|int $audition): void + { + + if (is_int($audition)) { + $audition = Audition::find($audition); + } + if (! $audition || ! $audition->exists) { + throw new ManageEntryException('Invalid audition provided'); + } + + if ($this->entry->audition->hasFlag('seats_published')) { + throw new ManageEntryException('Cannot change the audition for an entry where seating for that entry\'s current audition is published'); + } + if ($this->entry->audition->hasFlag('advancement_published')) { + throw new ManageEntryException('Cannot change the audition for an entry where advancement for that entry\'s current audition is published'); + } + if ($audition->hasFlag('seats_published')) { + throw new ManageEntryException('Cannot change the entry to an audition with published seating'); + } + if ($audition->hasFlag('advancement_published')) { + throw new ManageEntryException('Cannot change the entry to an audition with published advancement'); + } + if ($this->entry->student->grade > $audition->maximum_grade) { + throw new ManageEntryException('The grade of the student exceeds the maximum for that audition'); + } + if ($this->entry->student->grade < $audition->minimum_grade) { + throw new ManageEntryException('The grade of the student does not meet the minimum for that audition'); + } + if ($this->entry->scoreSheets()->count() > 0) { + throw new ManageEntryException('Cannot change the audition for an entry with scores'); + } + if (Entry::where('student_id', $this->entry->student_id)->where('audition_id', $audition->id)->exists()) { + throw new ManageEntryException('That student is already entered in that audition'); + } + // OK we're allowed to change the audition + $this->entry->audition_id = $audition->id; + // Deal with our draw number + if ($audition->hasFlag('drawn')) { + $draw_number = $audition->entries()->max('draw_number'); + $this->entry->draw_number = $draw_number + 1; + } else { + $this->entry->draw_number = null; + } + } + + /** + * @throws ManageEntryException + */ + private function updateForSeating($forSeating): void + { + if ($this->entry->for_seating == $forSeating) { + return; + } + if ($forSeating) { + if ($this->entry->audition->hasFlag('seats_published')) { + throw new ManageEntryException('Cannot add seating to an entry in an audition where seats are published'); + } + $this->entry->for_seating = 1; + } else { + if ($this->entry->audition->hasFlag('seats_published')) { + throw new ManageEntryException('Cannot remove seating from an entry in an audition where seats are published'); + } + $this->entry->for_seating = 0; + } + } + + /** + * @throws ManageEntryException + */ + private function updateForAdvancement($forAdvancement): void + { + if ($this->entry->for_advancement == $forAdvancement) { + return; + } + if ($forAdvancement) { + if ($this->entry->audition->hasFlag('advancement_published')) { + throw new ManageEntryException('Cannot add advancement to an entry in an audition where advancement is published'); + } + $this->entry->for_advancement = 1; + } else { + if ($this->entry->audition->hasFlag('advancement_published')) { + throw new ManageEntryException('Cannot remove advancement from an entry in an audition where advancement is published'); + } + $this->entry->for_advancement = 0; + } + + } +} diff --git a/app/Exceptions/ManageEntryException.php b/app/Exceptions/ManageEntryException.php new file mode 100644 index 0000000..6d5c3cf --- /dev/null +++ b/app/Exceptions/ManageEntryException.php @@ -0,0 +1,9 @@ + $students, 'auditions' => $auditions]); } - public function store(Request $request) + public function store(Request $request, CreateEntry $creator) { if (! Auth::user()->is_admin) { abort(403); @@ -99,15 +102,21 @@ class EntryController extends Controller $validData['for_seating'] = $request->get('for_seating') ? 1 : 0; $validData['for_advancement'] = $request->get('for_advancement') ? 1 : 0; + $enter_for = []; + if ($validData['for_seating']) { + $enter_for[] = 'seating'; + } + if ($validData['for_advancement']) { + $enter_for[] = 'advancement'; + } - Entry::create([ - 'student_id' => $validData['student_id'], - 'audition_id' => $validData['audition_id'], - 'for_seating' => $validData['for_seating'], - 'for_advancement' => $validData['for_advancement'], - ]); + try { + $creator($validData['student_id'], $validData['audition_id'], $enter_for); + } catch (ManageEntryException $ex) { + return redirect()->route('admin.entries.index')->with('error', $ex->getMessage()); + } - return redirect('/admin/entries'); + return redirect(route('admin.entries.index'))->with('success', 'The entry has been added.'); } public function edit(Entry $entry, CalculateEntryScore $calculator) @@ -126,11 +135,12 @@ class EntryController extends Controller $auditions = Audition::orderBy('score_order')->get(); $scores = $entry->scoreSheets()->with('audition', 'judge')->get(); $scores->each(fn ($score) => $score->entry = $entry); + // return view('admin.entries.edit', ['entry' => $entry, 'students' => $students, 'auditions' => $auditions]); return view('admin.entries.edit', compact('entry', 'students', 'auditions', 'scores')); } - public function update(Request $request, Entry $entry) + public function update(Request $request, Entry $entry, UpdateEntry $updater) { if ($entry->audition->hasFlag('seats_published')) { return to_route('admin.entries.index')->with('error', @@ -148,15 +158,21 @@ class EntryController extends Controller $validData['for_seating'] = $request->get('for_seating') ? 1 : 0; $validData['for_advancement'] = $request->get('for_advancement') ? 1 : 0; + // If the audition is not set to advance to the next round, then the entry must be for seating if (! auditionSetting('advanceTo')) { $validData['for_seating'] = 1; } + try { + $updater($entry, $validData); + } catch (ManageEntryException $e) { + return redirect()->route('admin.entries.index')->with('error', $e->getMessage()); + } - $entry->update([ - 'audition_id' => $validData['audition_id'], - 'for_seating' => $validData['for_seating'], - 'for_advancement' => $validData['for_advancement'], - ]); + // $entry->update([ + // 'audition_id' => $validData['audition_id'], + // 'for_seating' => $validData['for_seating'], + // 'for_advancement' => $validData['for_advancement'], + // ]); return to_route('admin.entries.index')->with('success', 'Entry updated successfully'); } diff --git a/app/Http/Controllers/EntryController.php b/app/Http/Controllers/EntryController.php index c102318..cdb992e 100644 --- a/app/Http/Controllers/EntryController.php +++ b/app/Http/Controllers/EntryController.php @@ -2,6 +2,8 @@ namespace App\Http\Controllers; +use App\Actions\Entries\CreateEntry; +use App\Exceptions\ManageEntryException; use App\Models\Audition; use App\Models\Entry; use Illuminate\Http\Request; @@ -25,7 +27,7 @@ class EntryController extends Controller return view('entries.index', ['entries' => $entries, 'students' => $students, 'auditions' => $auditions]); } - public function store(Request $request) + public function store(Request $request, CreateEntry $creator) { if ($request->user()->cannot('create', Entry::class)) { abort(403); @@ -34,18 +36,28 @@ class EntryController extends Controller 'student_id' => ['required', 'exists:students,id'], 'audition_id' => ['required', 'exists:auditions,id'], ]); + $audition = Audition::find($validData['audition_id']); + if ($audition->entry_deadline < now()) { + return redirect()->route('entries.index')->with('error', 'The entry deadline for that audition has passed'); + } $validData['for_seating'] = $request->get('for_seating') ? 1 : 0; $validData['for_advancement'] = $request->get('for_advancement') ? 1 : 0; + $enter_for = []; + if ($validData['for_seating']) { + $enter_for[] = 'seating'; + } + if ($validData['for_advancement']) { + $enter_for[] = 'advancement'; + } - $entry = Entry::create([ - 'student_id' => $validData['student_id'], - 'audition_id' => $validData['audition_id'], - 'for_seating' => $validData['for_seating'], - 'for_advancement' => $validData['for_advancement'], - ]); + try { + $creator($validData['student_id'], $validData['audition_id'], $enter_for); + } catch (ManageEntryException $ex) { + return redirect()->route('entries.index')->with('error', $ex->getMessage()); + } - return redirect('/entries'); + return redirect()->route('entries.index')->with('success', 'The entry has been added.'); } public function destroy(Request $request, Entry $entry) diff --git a/tests/Feature/Actions/CreateEntryTest.php b/tests/Feature/Actions/CreateEntryTest.php new file mode 100644 index 0000000..cf1601a --- /dev/null +++ b/tests/Feature/Actions/CreateEntryTest.php @@ -0,0 +1,127 @@ +createEntry = App::make(CreateEntry::class); +}); + +it('throws an exception if the student does not exist', function () { + $audition = Audition::factory()->create(); + $student = Student::factory()->make(); + $this->createEntry->__invoke($student, $audition); +})->throws(ManageEntryException::class, 'Invalid student provided'); +it('throws an exception if the audition does not exist', function () { + $audition = Audition::factory()->make(); + $student = Student::factory()->create(); + $this->createEntry->__invoke($student, $audition); +})->throws(ManageEntryException::class, 'Invalid audition provided'); +it('throws an exception if the student is already entered in the audition', function () { + // Arrange + $audition = Audition::factory()->create(); + $student = Student::factory()->create(); + $entry = Entry::create([ + 'student_id' => $student->id, + 'audition_id' => $audition->id, + ]); + // Act & Assert + $this->createEntry->createEntry($student, $audition); +})->throws(ManageEntryException::class, 'That student is already entered in that audition'); +it('throws an exception if seats are published for the audition', function () { + // Arrange + $audition = Audition::factory()->create(); + $student = Student::factory()->create(); + $audition->addFlag('seats_published'); + // Act & Assert + $this->createEntry->createEntry($student, $audition); +})->throws(ManageEntryException::class, 'Cannot add an entry to an audition where seats are published'); +it('throws an exception if advancement is published for the audition', function () { + // Arrange + $audition = Audition::factory()->create(); + $student = Student::factory()->create(); + $audition->addFlag('advancement_published'); + // Act & Assert + $this->createEntry->createEntry($student, $audition); +})->throws(ManageEntryException::class, 'Cannot add an entry to an audition where advancement is published'); +it('throws an exception if the grade of the student exceeds the maximum grade for the audition', function () { + // Arrange + $audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]); + $student = Student::factory()->create(['grade' => 11]); + // Act & Assert + $this->createEntry->createEntry($student, $audition); +})->throws(ManageEntryException::class, 'The grade of the student exceeds the maximum for that audition'); +it('throws an exception if the grade of the student does not meet the minimum grade for the audition', function () { + // Arrange + $audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]); + $student = Student::factory()->create(['grade' => 7]); + // Act & Assert + $this->createEntry->createEntry($student, $audition); +})->throws(ManageEntryException::class, 'The grade of the student does not meet the minimum for that audition'); +it('returns an entry object', function () { + // Arrange + $audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]); + $student = Student::factory()->create(['grade' => 8]); + // Act & Assert + $entry = $this->createEntry->createEntry($student, $audition); + expect($entry instanceof Entry)->toBeTrue(); +}); +it('creates an entry with a null draw number if the audition is not drawn', function () { + $audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]); + $student = Student::factory()->create(['grade' => 8]); + // Act & Assert + $entry = $this->createEntry->createEntry($student, $audition); + expect($entry->draw_number)->toBeNull(); +}); +it('assigns the next highest draw_number available if the audition is drawn', function () { + $audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]); + $student = Student::factory()->create(['grade' => 8]); + $audition->addFlag('drawn'); + foreach (range(1, 5) as $number) { + Entry::factory()->create([ + 'audition_id' => $audition->id, + 'draw_number' => $number, + ]); + } + $entry = $this->createEntry->createEntry($student, $audition); + expect($entry->draw_number)->toBe(6); +}); +it('makes the entry for both seating and advancement if nothing is specified', function () { + $audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]); + $student = Student::factory()->create(['grade' => 8]); + // Act & Assert + $entry = $this->createEntry->createEntry($student, $audition); + expect($entry->for_seating)->toBeTrue() + ->and($entry->for_advancement)->toBeTrue(); +}); +it('makes the entry for only seating if only seating is specified', function () { + $audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]); + $student = Student::factory()->create(['grade' => 8]); + // Act & Assert + $entry = $this->createEntry->createEntry($student, $audition, 'seating'); + expect($entry->for_seating)->toBeTrue() + ->and($entry->for_advancement)->toBeFalse(); +}); +it('makes the entry for only advancement if only advancement is specified', function () { + $audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]); + $student = Student::factory()->create(['grade' => 8]); + // Act & Assert + $entry = $this->createEntry->createEntry($student, $audition, 'advancement'); + expect($entry->for_seating)->toBeFalse() + ->and($entry->for_advancement)->toBeTrue(); +}); +it('makes the entry for both seating and advancement if both are specified', function () { + $audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]); + $student = Student::factory()->create(['grade' => 8]); + // Act & Assert + $entry = $this->createEntry->createEntry($student, $audition, ['seating', 'advancement']); + expect($entry->for_seating)->toBeTrue() + ->and($entry->for_advancement)->toBeTrue(); +}); diff --git a/tests/Feature/Actions/UpdateEntryTest.php b/tests/Feature/Actions/UpdateEntryTest.php new file mode 100644 index 0000000..c9350e2 --- /dev/null +++ b/tests/Feature/Actions/UpdateEntryTest.php @@ -0,0 +1,219 @@ +updater = App::make(UpdateEntry::class); +}); + +it('throws an error if an invalid entry is provided', function () { + $this->updater->updateEntry(2, []); +})->throws(ManageEntryException::class, 'Invalid entry provided'); + +it('throws an error if we try to remove for_seating while seating is published', function () { + // Arrange + $entry = Entry::factory()->create(); + $entry->audition->addFlag('seats_published'); + $data = ['for_seating' => 0]; + // Act & Assert + $this->updater->updateEntry($entry, $data); +})->throws('Cannot remove seating from an entry in an audition where seats are published'); +it('throws an error if we try to add for_seating while seating is published', function () { + // Arrange + $entry = Entry::factory()->advanceOnly()->create(); + $entry->audition->addFlag('seats_published'); + $data = ['for_seating' => 1]; + // Act & Assert + $this->updater->updateEntry($entry, $data); +})->throws('Cannot add seating to an entry in an audition where seats are published'); +it('allows us to remove for_seating if seating is not published', function () { + // Arrange + $entry = Entry::factory()->create(); + $data = ['for_seating' => 0]; + // Act + $this->updater->updateEntry($entry, $data); + // Assert + $this->assertDatabaseHas('entries', ['id' => $entry->id, 'for_seating' => 0]); +}); +it('allows us to add for_seating if seating is not published', function () { + // Arrange + $entry = Entry::factory()->advanceOnly()->create(); + $data = ['for_seating' => 1]; + // Act + $this->updater->updateEntry($entry, $data); + // Assert + $this->assertDatabaseHas('entries', ['id' => $entry->id, 'for_seating' => 1]); +}); + +it('throws an error if we try to remove for_advancement while seating is published', function () { + // Arrange + $entry = Entry::factory()->create(); + $entry->audition->addFlag('advancement_published'); + $data = ['for_advancement' => 0]; + // Act & Assert + $this->updater->updateEntry($entry, $data); +})->throws('Cannot remove advancement from an entry in an audition where advancement is published'); +it('throws an error if we try to add for_advancement while advancement is published', function () { + // Arrange + $entry = Entry::factory()->seatingOnly()->create(); + $entry->audition->addFlag('advancement_published'); + $data = ['for_advancement' => 1]; + // Act & Assert + $this->updater->updateEntry($entry, $data); +})->throws('Cannot add advancement to an entry in an audition where advancement is published'); +it('allows us to remove for_advancement if advancement is not published', function () { + // Arrange + $entry = Entry::factory()->create(); + $data = ['for_advancement' => 0]; + // Act + $this->updater->updateEntry($entry, $data); + // Assert + $this->assertDatabaseHas('entries', ['id' => $entry->id, 'for_advancement' => 0]); +}); +it('allows us to add for_advancement if advancement is not published', function () { + // Arrange + $entry = Entry::factory()->seatingOnly()->create(); + $data = ['for_advancement' => 1]; + // Act + $this->updater->updateEntry($entry, $data); + // Assert + $this->assertDatabaseHas('entries', ['id' => $entry->id, 'for_advancement' => 1]); +}); + +it('throws an exception if an attempt to change to an invalid audition is made', function () { + $entry = Entry::factory()->create(); + $data = ['audition' => 2]; + $this->updater->updateEntry($entry, $data); +})->throws(ManageEntryException::class, 'Invalid audition provided'); + +it('cannot change auditions if our current audition advancement is published', function () { + $entry = Entry::factory()->create(); + $entry->audition->addFlag('advancement_published'); + $otherAudition = Audition::factory()->create(); + $data = ['audition' => $otherAudition]; + // Act + $this->updater->updateEntry($entry, $data); +})->throws(ManageEntryException::class, + 'Cannot change the audition for an entry where advancement for that entry\'s current audition is published'); +it('cannot change auditions if our current audition seating is published', function () { + $entry = Entry::factory()->create(); + $entry->audition->addFlag('seats_published'); + $otherAudition = Audition::factory()->create(); + $data = ['audition' => $otherAudition]; + // Act + $this->updater->updateEntry($entry, $data); +})->throws(ManageEntryException::class, + 'Cannot change the audition for an entry where seating for that entry\'s current audition is published'); + +it('cannot change auditions if our proposed audition advancement is published', function () { + $entry = Entry::factory()->create(); + $otherAudition = Audition::factory()->create(); + $otherAudition->addFlag('advancement_published'); + $data = ['audition' => $otherAudition]; + // Act + $this->updater->updateEntry($entry, $data); +})->throws(ManageEntryException::class, 'Cannot change the entry to an audition with published advancement'); +it('cannot change auditions if our proposed audition seating is published', function () { + $entry = Entry::factory()->create(); + $otherAudition = Audition::factory()->create(); + $otherAudition->addFlag('seats_published'); + $data = ['audition' => $otherAudition]; + // Act + $this->updater->updateEntry($entry, $data); +})->throws(ManageEntryException::class, 'Cannot change the entry to an audition with published seating'); + +it('will not let us switch to an audition that is too old for the student', function () { + $entry = Entry::factory()->create(); + $otherAudition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]); + $entry->student->update(['grade' => 7]); + $data = ['audition' => $otherAudition]; + $this->updater->updateEntry($entry, $data); +})->throws(ManageEntryException::class, 'The grade of the student does not meet the minimum for that audition'); +it('will not let us switch to an audition that is too young for the student', function () { + $entry = Entry::factory()->create(); + $otherAudition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]); + $entry->student->update(['grade' => 11]); + $data = ['audition' => $otherAudition]; + $this->updater->updateEntry($entry, $data); +})->throws(ManageEntryException::class, 'The grade of the student exceeds the maximum for that audition'); +it('will not let us change auditions for an entry with scores', function () { + $entry = Entry::factory()->create(); + $judge = User::factory()->create(); + ScoreSheet::create([ + 'entry_id' => $entry->id, + 'user_id' => $judge->id, + 'subscores' => 100, + ]); + $otherAudition = Audition::factory()->create([ + 'minimum_grade' => $entry->student->grade, 'maximum_grade' => $entry->student->grade, + ]); + $data = ['audition' => $otherAudition]; + $this->updater->updateEntry($entry, $data); +})->throws(ManageEntryException::class, 'Cannot change the audition for an entry with scores'); +it('will not let us change to an audition that the student is already entered in', function () { + $entry = Entry::factory()->create(); + + $otherAudition = Audition::factory()->create([ + 'minimum_grade' => $entry->student->grade, 'maximum_grade' => $entry->student->grade, + ]); + Entry::create([ + 'student_id' => $entry->student->id, + 'audition_id' => $otherAudition->id, + ]); + $data = ['audition' => $otherAudition]; + $this->updater->updateEntry($entry, $data); +})->throws(ManageEntryException::class, 'That student is already entered in that audition'); +it('allows us to switch auditions', function () { + // Arrange + $entry = Entry::factory()->create(); + $originalAudition = $entry->audition; + $otherAudition = Audition::factory()->create([ + 'minimum_grade' => $entry->student->grade, 'maximum_grade' => $entry->student->grade, + ]); + $data = ['audition' => $otherAudition]; + // Act & Assert + $this->updater->updateEntry($entry, $data); + $this->assertDatabaseHas('entries', ['id' => $entry->id, 'audition_id' => $otherAudition->id]); + $this->assertDatabaseMissing('entries', + ['student_id' => $entry->student->id, 'audition_id' => $originalAudition->id]); +}); +it('sets the draw number null if the new audition is not drawn', function () { + // Arrange + $entry = Entry::factory()->create(['draw_number' => 3]); + $newAudition = Audition::factory()->create([ + 'minimum_grade' => $entry->student->grade, 'maximum_grade' => $entry->student->grade, + ]); + $data = ['audition' => $newAudition]; + // Act + $this->updater->updateEntry($entry, $data); + // Assert + $this->assertDatabaseHas('entries', ['id' => $entry->id, 'draw_number' => null]); +}); +it('sets the draw number last if the new audition is not drawn', function () { + // Arrange + $entry = Entry::factory()->create(['draw_number' => 3]); + $newAudition = Audition::factory()->create([ + 'minimum_grade' => $entry->student->grade, 'maximum_grade' => $entry->student->grade, + ]); + $newAudition->addFlag('drawn'); + foreach (range(1, 6) as $dn) { + Entry::factory()->create(['audition_id' => $newAudition->id, 'draw_number' => $dn]); + } + $data = ['audition' => $newAudition]; + // Act + $this->updater->updateEntry($entry, $data); + + // Assert + $this->assertDatabaseHas('entries', ['id' => $entry->id, 'draw_number' => 7]); +}); diff --git a/tests/Feature/Pages/Admin/EntiesCreateTest.php b/tests/Feature/Pages/Admin/EntiesCreateTest.php index dc71571..3142c0f 100644 --- a/tests/Feature/Pages/Admin/EntiesCreateTest.php +++ b/tests/Feature/Pages/Admin/EntiesCreateTest.php @@ -4,6 +4,7 @@ use App\Models\Audition; use App\Models\Student; use Illuminate\Foundation\Testing\RefreshDatabase; +use function Pest\Laravel\assertDatabaseHas; use function Pest\Laravel\get; use function PHPUnit\Framework\assertEquals; @@ -19,7 +20,7 @@ it('does not respond to a guest', function () { get(route('admin.entries.create')) ->assertRedirect(route('home')); }); -it('passes a collection of all students with thier schools to the view', function () { +it('passes a collection of all students with their schools to the view', function () { // Arrange Student::factory()->count(8)->create(); $students = Student::with('school')->orderBy('last_name')->orderBy('first_name')->get(); @@ -47,3 +48,42 @@ it('passes a collection of available auditions to the view', function () { $response->assertOk(); assertEquals(array_values($auditions), array_values($viewAuditions)); }); +it('can create an entry', function () { + $audition = Audition::factory()->create(['maximum_grade' => 12, 'minimum_grade' => 7]); + $student = Student::factory()->create(['grade' => 9]); + actAsAdmin(); + $response = $this->post(route('admin.entries.store'), [ + 'student_id' => $student->id, + 'audition_id' => $audition->id, + 'for_seating' => 'on', + ]); + $response->assertRedirect(route('admin.entries.index')) + ->assertSessionDoesntHaveErrors() + ->assertSessionHas('success', 'The entry has been added.'); + assertDatabaseHas('entries', [ + 'student_id' => $student->id, + 'audition_id' => $audition->id, + 'for_seating' => 1, + 'for_advancement' => 0, + ]); +}); +it('can create a late entry', function () { + $audition = Audition::factory()->closed()->create(['maximum_grade' => 12, 'minimum_grade' => 7]); + $student = Student::factory()->create(['grade' => 9]); + actAsAdmin(); + $response = $this->post(route('admin.entries.store'), [ + 'student_id' => $student->id, + 'audition_id' => $audition->id, + 'for_seating' => 'on', + ]); + $response->assertRedirect(route('admin.entries.index')) + ->assertSessionDoesntHaveErrors() + ->assertSessionMissing('error') + ->assertSessionHas('success', 'The entry has been added.'); + assertDatabaseHas('entries', [ + 'student_id' => $student->id, + 'audition_id' => $audition->id, + 'for_seating' => 1, + 'for_advancement' => 0, + ]); +}); diff --git a/tests/Feature/Pages/Admin/EntriesEditTest.php b/tests/Feature/Pages/Admin/EntriesEditTest.php index e9b3fcb..105e245 100644 --- a/tests/Feature/Pages/Admin/EntriesEditTest.php +++ b/tests/Feature/Pages/Admin/EntriesEditTest.php @@ -137,12 +137,13 @@ it('does not let a normal user update an entry', function () { }); it('allows an admin to update an entry', function () { // Arrange - $newAudition = Audition::factory()->create(); + $newAudition = Audition::factory()->create(['minimum_grade' => 1, 'maximum_grade' => 20]); actAsAdmin(); // Act & Assert /** @noinspection PhpUnhandledExceptionInspection */ patch(route('admin.entries.update', $this->entry), ['audition_id' => $newAudition->id]) ->assertSessionHasNoErrors() + ->assertSessionMissing('error') ->assertSessionHas('success', 'Entry updated successfully') ->assertRedirect(route('admin.entries.index')); $this->entry->refresh(); @@ -221,7 +222,7 @@ it('displays scores', function () { $response->assertSee($subscore->name); } }); -it('has a link to delete scores', function() { +it('has a link to delete scores', function () { // Arrange $sg = ScoringGuide::factory()->create(); SubscoreDefinition::factory()->count(5)->create(['scoring_guide_id' => $sg->id]); @@ -236,7 +237,7 @@ it('has a link to delete scores', function() { $scoreSheet = ScoreSheet::where('entry_id', $entry->id)->first(); actAsAdmin(); $response = get(route('admin.entries.edit', $entry)) - ->assertSee(route('scores.destroy', ['score'=>$scoreSheet])); + ->assertSee(route('scores.destroy', ['score' => $scoreSheet])); }); // Delete tests diff --git a/tests/Feature/Pages/EntriesIndexTest.php b/tests/Feature/Pages/EntriesIndexTest.php index fd47344..2df03aa 100644 --- a/tests/Feature/Pages/EntriesIndexTest.php +++ b/tests/Feature/Pages/EntriesIndexTest.php @@ -9,6 +9,7 @@ use App\Settings; use Illuminate\Foundation\Testing\RefreshDatabase; use function Pest\Laravel\actingAs; +use function Pest\Laravel\assertDatabaseMissing; use function Pest\Laravel\delete; use function Pest\Laravel\get; use function Pest\Laravel\post; @@ -133,8 +134,8 @@ it('shows appropriate flags for entry types when advancement is enabled', functi it('accepts a valid entry', function () { // Arrange - $student = Student::factory()->create(['school_id' => $this->school->id]); - $audition = Audition::factory()->create(); + $student = Student::factory()->create(['school_id' => $this->school->id, 'grade' => 8]); + $audition = Audition::factory()->create(['maximum_grade' => 9, 'minimum_grade' => 7]); // Act & Assert actingAs($this->user); $response = post(route('entries.store'), [ @@ -144,6 +145,7 @@ it('accepts a valid entry', function () { /** @noinspection PhpUnhandledExceptionInspection */ $response->assertSessionHasNoErrors(); $response->assertRedirect(route('entries.index')); + $response->assertSessionHas('success', 'The entry has been added.'); $this->assertDatabaseHas('entries', [ 'student_id' => $student->id, 'audition_id' => $audition->id, @@ -179,3 +181,21 @@ it('shows entry type checkboxes only when advancement is enabled', function () { get(route('entries.index')) ->assertDontSee('Enter for'); }); +it('denies an entry that is late', function () { + // Arrnge + $student = Student::factory()->create(['school_id' => $this->school->id, 'grade' => 8]); + $audition = Audition::factory()->closed()->create(['maximum_grade' => 9, 'minimum_grade' => 7]); + $user = User::factory()->create(['school_id' => $student->school_id]); + actingAs($user); + // Act & Assert + $response = post(route('entries.store'), [ + 'student_id' => $student->id, + 'audition_id' => $audition->id, + ]); + assertDatabaseMissing('entries', [ + 'student_id' => $student->id, + 'audition_id' => $audition->id, + ]); + $response->assertRedirect(route('entries.index')) + ->assertSessionHas('error', 'The entry deadline for that audition has passed'); +});