From 53529fe0e9aa24be44bff4b4ad5c308172a9d46d Mon Sep 17 00:00:00 2001 From: Matt Young Date: Mon, 8 Jul 2024 15:46:44 -0500 Subject: [PATCH] Tabulator Score Entry checks published status --- app/Http/Controllers/JudgingController.php | 23 +++- .../Tabulation/ScoreController.php | 29 ++++- .../tabulation/entry_score_sheet.blade.php | 2 +- .../Pages/JudgingEntryScoreSheetTest.php | 9 ++ .../Pages/Tabulation/enterScoreSelectTest.php | 41 +++++++ tests/Feature/enterScoreEntryFormTest.php | 103 ++++++++++++++++++ 6 files changed, 200 insertions(+), 7 deletions(-) create mode 100644 tests/Feature/Pages/Tabulation/enterScoreSelectTest.php create mode 100644 tests/Feature/enterScoreEntryFormTest.php diff --git a/app/Http/Controllers/JudgingController.php b/app/Http/Controllers/JudgingController.php index 41c7dab..7eb998a 100644 --- a/app/Http/Controllers/JudgingController.php +++ b/app/Http/Controllers/JudgingController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers; use App\Models\Audition; use App\Models\Entry; use App\Models\JudgeAdvancementVote; +use Exception; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Gate; @@ -39,15 +40,23 @@ class JudgingController extends Controller public function entryScoreSheet(Request $request, Entry $entry) { + // Turn away users not assigned to judge this entry if ($request->user()->cannot('judge', $entry->audition)) { return redirect()->route('judging.index')->with('error', 'You are not assigned to judge this entry'); } + // Turn away users if the audition is published if ($entry->audition->hasFlag('seats_published') || $entry->audition->hasFlag('advancement_published')) { - return redirect()->route('judging.auditionEntryList', $entry->audition)->with('error', 'Scores for entries in published auditions cannot be modified'); + return redirect()->route('judging.auditionEntryList', $entry->audition)->with('error', + 'Scores for entries in published auditions cannot be modified'); + } + // Turn away users if the entry is flagged as a no-show + if ($entry->hasFlag('no_show')) { + return redirect()->route('judging.auditionEntryList', $entry->audition)->with('error', + 'The requested entry is marked as a no-show. Scores cannot be entered.'); } $oldSheet = ScoreSheet::where('user_id', Auth::id())->where('entry_id', $entry->id)->value('subscores') ?? null; $oldVote = JudgeAdvancementVote::where('user_id', Auth::id())->where('entry_id', $entry->id)->first(); - $oldVote = $oldVote ? $oldVote->vote : 'novote'; + $oldVote = $oldVote ? $oldVote->vote : 'noVote'; return view('judging.entry_score_sheet', compact('entry', 'oldSheet', 'oldVote')); } @@ -57,6 +66,7 @@ class JudgingController extends Controller if ($request->user()->cannot('judge', $entry->audition)) { abort(403, 'You are not assigned to judge this entry'); } + // TODO extract to a service class $scoringGuide = $entry->audition->scoringGuide()->with('subscores')->first(); $scoreValidation = $scoringGuide->validateScores($request->input('score')); if ($scoreValidation != 'success') { @@ -79,7 +89,8 @@ class JudgingController extends Controller $this->advancementVote($request, $entry); - return redirect('/judging/audition/'.$entry->audition_id)->with('success', 'Entered scores for '.$entry->audition->name.' '.$entry->draw_number); + return redirect('/judging/audition/'.$entry->audition_id)->with('success', + 'Entered scores for '.$entry->audition->name.' '.$entry->draw_number); } @@ -114,7 +125,8 @@ class JudgingController extends Controller $this->advancementVote($request, $entry); - return redirect('/judging/audition/'.$entry->audition_id)->with('success', 'Updated scores for '.$entry->audition->name.' '.$entry->draw_number); + return redirect('/judging/audition/'.$entry->audition_id)->with('success', + 'Updated scores for '.$entry->audition->name.' '.$entry->draw_number); } protected function advancementVote(Request $request, Entry $entry) @@ -134,9 +146,10 @@ class JudgingController extends Controller 'entry_id' => $entry->id, 'vote' => $request->input('advancement-vote'), ]); - } catch (\Exception $e) { + } catch (Exception) { return redirect(url()->previous())->with('error', 'Error saving advancement vote'); } } + return null; } } diff --git a/app/Http/Controllers/Tabulation/ScoreController.php b/app/Http/Controllers/Tabulation/ScoreController.php index 217801a..fa62c7d 100644 --- a/app/Http/Controllers/Tabulation/ScoreController.php +++ b/app/Http/Controllers/Tabulation/ScoreController.php @@ -10,7 +10,7 @@ use Tests\Feature\Models\ScoreSheet; class ScoreController extends Controller { - public function chooseEntry(Request $request) + public function chooseEntry() { $method = 'GET'; $formRoute = 'scores.entryScoreSheet'; @@ -29,6 +29,12 @@ class ScoreController extends Controller { $existing_sheets = []; $entry = Entry::with(['student', 'audition.room.judges'])->find($request->input('entry_id')); + + $publishedCheck = $this->checkIfPublished($entry); + if ($publishedCheck) { + return $publishedCheck; + } + $judges = $entry->audition->room->judges; foreach ($judges as $judge) { $scoreSheet = ScoreSheet::where('entry_id', $entry->id)->where('user_id', $judge->id)->first(); @@ -49,6 +55,11 @@ class ScoreController extends Controller public function saveEntryScoreSheet(Request $request, Entry $entry) { + $publishedCheck = $this->checkIfPublished($entry); + if ($publishedCheck) { + return $publishedCheck; + } + $judges = $entry->audition->room->judges; $subscores = $entry->audition->scoringGuide->subscores->sortBy('tiebreak_order'); @@ -84,4 +95,20 @@ class ScoreController extends Controller return redirect()->route('scores.chooseEntry')->with('success', count($preparedScoreSheets).' Scores saved'); } + + protected function checkIfPublished($entry) + { + // We're not going to enter scores if seats are published + if ($entry->audition->hasFlag('seats_published')) { + return to_route('scores.chooseEntry')->with('error', + 'Cannot enter scores for entry '.$entry->id.'. '.$entry->audition->name.' seats are published'); + } + // Nope, not if advancement is published either + if ($entry->audition->hasFlag('advancement_published')) { + return to_route('scores.chooseEntry')->with('error', + 'Cannot enter scores for entry '.$entry->id.'. '.$entry->audition->name.' advancement is published'); + } + + return false; + } } diff --git a/resources/views/tabulation/entry_score_sheet.blade.php b/resources/views/tabulation/entry_score_sheet.blade.php index 8aedd64..248b417 100644 --- a/resources/views/tabulation/entry_score_sheet.blade.php +++ b/resources/views/tabulation/entry_score_sheet.blade.php @@ -11,7 +11,7 @@ - + diff --git a/tests/Feature/Pages/JudgingEntryScoreSheetTest.php b/tests/Feature/Pages/JudgingEntryScoreSheetTest.php index e8518b4..7b5a338 100644 --- a/tests/Feature/Pages/JudgingEntryScoreSheetTest.php +++ b/tests/Feature/Pages/JudgingEntryScoreSheetTest.php @@ -141,3 +141,12 @@ it('redirects if advancement is published', function () { ->assertSessionHas('error', 'Scores for entries in published auditions cannot be modified'); }); +it('will not show a score sheet for an entry marked as no_show', function () { + // Arrange + $this->entries->first()->addFlag('no_show'); + $this->actingAs($this->user); + // Act & Assert + $response = $this->get(route('judging.entryScoreSheet', $this->entries->first())); + $response->assertRedirect(route('judging.auditionEntryList', $this->entries[0]->audition)) + ->assertSessionHas('error', 'The requested entry is marked as a no-show. Scores cannot be entered.'); +}); diff --git a/tests/Feature/Pages/Tabulation/enterScoreSelectTest.php b/tests/Feature/Pages/Tabulation/enterScoreSelectTest.php new file mode 100644 index 0000000..1c0eaf4 --- /dev/null +++ b/tests/Feature/Pages/Tabulation/enterScoreSelectTest.php @@ -0,0 +1,41 @@ +assertRedirect(route('home')); + actAsAdmin(); + get(route('scores.chooseEntry')) + ->assertOk(); + actAsTab(); + get(route('scores.chooseEntry')) + ->assertOk(); + actAsNormal(); + get(route('scores.chooseEntry')) + ->assertRedirect(route('dashboard')) + ->assertSessionHas('error', 'You are not authorized to perform this action'); +}); +it('has an input for entry_id', function () { + actAsAdmin(); + get(route('scores.chooseEntry')) + ->assertOk() + ->assertElementExists('#entry_id', function (AssertElement $element) { + $element->is('input'); + }); +}); +it('submits to entry-flags.confirmNoShow', function () { + actAsAdmin(); + get(route('scores.chooseEntry')) + ->assertOk() + ->assertFormExists('#entry-select-form', function (AssertForm $form) { + $form->hasMethod('GET') + ->hasAction(route('scores.entryScoreSheet')); + }); +}); diff --git a/tests/Feature/enterScoreEntryFormTest.php b/tests/Feature/enterScoreEntryFormTest.php new file mode 100644 index 0000000..353c303 --- /dev/null +++ b/tests/Feature/enterScoreEntryFormTest.php @@ -0,0 +1,103 @@ +create(); + $data['room'] = Room::factory()->create(); + $data['room']->addJudge($data['judge']); + $data['event'] = Event::factory()->create(); + $data['scoringGuide'] = ScoringGuide::factory()->create(); + $data['subscores'] = SubscoreDefinition::factory()->count(3)->create([ + 'scoring_guide_id' => $data['scoringGuide']->id, + ]); + $data['audition'] = Audition::factory()->create([ + 'room_id' => $data['room']->id, + 'event_id' => $data['event']->id, + 'scoring_guide_id' => $data['scoringGuide']->id, + ]); + $data['entry'] = Entry::factory()->create([ + 'audition_id' => $data['audition']->id, + ]); + + return $data; +} + +/** @noinspection PhpMissingReturnTypeInspection */ +function validRequest(array $data = []) +{ + if (! $data) { + $data = testData(); + } + actAsTab(); + + return get(route('scores.entryScoreSheet', ['entry_id' => $data['entry']->id])); +} + +it('does not allow guests or normal users to access the admin score form', function () { + get(route('scores.entryScoreSheet'))->assertRedirect(route('home')); + actAsNormal(); + get(route('scores.entryScoreSheet'))->assertRedirect(route('dashboard'))->assertSessionHas('error', + 'You are not authorized to perform this action'); +}); +it('grants an admin or tab user access', function () { + // Arrange + $response = validRequest(); + // Act & Assert + $response->assertOk(); +}); +it('has a form with field for each subscore for each judge', function () { + // Arrange + $data = testData(); + $response = validRequest($data); + // Act & Assert + $response->assertOk(); + $fieldsToCheck = []; + foreach ($data['subscores'] as $subscore) { + $fieldsToCheck[] = 'j'.$data['judge']->id.'ss'.$subscore->id; + } + /** @noinspection PhpExpressionResultUnusedInspection */ + $response->assertFormExists('#scoreForm', function (AssertForm $form) use ($fieldsToCheck) { + $form->hasCSRF(); + $form->hasMethod('POST'); + $form->hasAction(route('scores.saveEntryScoreSheet', ['entry' => 1])); + foreach ($fieldsToCheck as $field) { + $form->contains('#'.$field); + } + }); +}); +it('will not accept scores for an entry in a an audition with published seats', function () { + // Arrange + $data = testData(); + $data['audition']->addFlag('seats_published'); + $response = validRequest($data); + // Act & Assert + $expectedError = 'Cannot enter scores for entry '.$data['entry']->id.'. '.$data['entry']->audition->name.' seats are published'; + $response + ->assertRedirect(route('scores.chooseEntry')) + ->assertSessionHas('error', $expectedError); +}); +it('will not accept scores for an entry in an audition with published advancement', function () { + // Arrange + $data = testData(); + $data['audition']->addFlag('advancement_published'); + $response = validRequest($data); + // Act & Assert + $expectedError = 'Cannot enter scores for entry '.$data['entry']->id.'. '.$data['entry']->audition->name.' advancement is published'; + $response + ->assertRedirect(route('scores.chooseEntry')) + ->assertSessionHas('error', $expectedError); +});