From aeb2557be3170240db2ba7f0235ccdbca3ac9ef6 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 9 Jul 2025 22:49:09 -0500 Subject: [PATCH] Tests for BonusScoreDefinitionController.php --- .../Admin/BonusScoreDefinitionController.php | 18 +- .../BonusScoreDefinitionObserver.php | 40 +++ .../BonusScoreDefinitionControllerTest.php | 255 ++++++++++++++++++ 3 files changed, 300 insertions(+), 13 deletions(-) create mode 100644 app/Observers/BonusScoreDefinitionObserver.php create mode 100644 tests/Feature/app/Http/Controllers/Admin/BonusScoreDefinitionControllerTest.php diff --git a/app/Http/Controllers/Admin/BonusScoreDefinitionController.php b/app/Http/Controllers/Admin/BonusScoreDefinitionController.php index 73ad346..5e7c9f2 100644 --- a/app/Http/Controllers/Admin/BonusScoreDefinitionController.php +++ b/app/Http/Controllers/Admin/BonusScoreDefinitionController.php @@ -27,7 +27,7 @@ class BonusScoreDefinitionController extends Controller public function store() { $validData = request()->validate([ - 'name' => 'required', + 'name' => 'required|unique:bonus_score_definitions,name', 'max_score' => 'required|numeric', 'weight' => 'required|numeric', ]); @@ -49,6 +49,7 @@ class BonusScoreDefinitionController extends Controller public function assignAuditions(Request $request) { + // TODO: add pivot model to log changes to assignments $validData = $request->validate([ 'bonus_score_id' => 'required|exists:bonus_score_definitions,id', 'audition' => 'required|array', @@ -70,12 +71,8 @@ class BonusScoreDefinitionController extends Controller public function unassignAudition(Audition $audition) { - if (! $audition->exists()) { - return redirect()->route('admin.bonus-scores.index')->with('error', 'Audition not found'); - } - if (! $audition->bonusScore()->count() > 0) { - return redirect()->route('admin.bonus-scores.index')->with('error', 'Audition does not have a bonus score'); - } + // TODO: add pivot model to log changes to assignments + $audition->bonusScore()->detach(); return redirect()->route('admin.bonus-scores.index')->with('success', 'Audition unassigned from bonus score'); @@ -83,6 +80,7 @@ class BonusScoreDefinitionController extends Controller public function judges() { + //TODO Need to show if judge is assigned, and show bonus assignments or normal judging page $bonusScores = BonusScoreDefinition::all(); $users = User::orderBy('last_name')->orderBy('first_name')->get(); @@ -91,9 +89,6 @@ class BonusScoreDefinitionController extends Controller public function assignJudge(BonusScoreDefinition $bonusScore) { - if (! $bonusScore->exists()) { - return redirect()->route('admin.bonus-scores.judges')->with('error', 'Bonus Score not found'); - } $validData = request()->validate([ 'judge' => 'required|exists:users,id', ]); @@ -104,9 +99,6 @@ class BonusScoreDefinitionController extends Controller public function removeJudge(BonusScoreDefinition $bonusScore) { - if (! $bonusScore->exists()) { - return redirect()->route('admin.bonus-scores.judges')->with('error', 'Bonus Score not found'); - } $validData = request()->validate([ 'judge' => 'required|exists:users,id', ]); diff --git a/app/Observers/BonusScoreDefinitionObserver.php b/app/Observers/BonusScoreDefinitionObserver.php new file mode 100644 index 0000000..57b202c --- /dev/null +++ b/app/Observers/BonusScoreDefinitionObserver.php @@ -0,0 +1,40 @@ +id.': '.$bonusScoreDefinition->name; + $message .= '
Max Score: '.$bonusScoreDefinition->max_score; + $message .= '
Weight: '.$bonusScoreDefinition->weight; + $affected = ['bonus_score_definitions' => [$bonusScoreDefinition->id]]; + auditionLog($message, $affected); + } + + public function updated(BonusScoreDefinition $bonusScoreDefinition): void + { + $message = 'Updated bonus score definition #'.$bonusScoreDefinition->id.': '.$bonusScoreDefinition->getOriginal('name'); + if ($bonusScoreDefinition->name !== $bonusScoreDefinition->getOriginal('name')) { + $message .= '
'.$bonusScoreDefinition->getOriginal('name').' -> '.$bonusScoreDefinition->name; + } + if ($bonusScoreDefinition->max_score !== $bonusScoreDefinition->getOriginal('max_score')) { + $message .= '
Max Score: '.$bonusScoreDefinition->getOriginal('max_score').' -> '.$bonusScoreDefinition->max_score; + } + if ($bonusScoreDefinition->weight !== $bonusScoreDefinition->getOriginal('weight')) { + $message .= '
Weight: '.$bonusScoreDefinition->getOriginal('weight').' -> '.$bonusScoreDefinition->weight; + } + $affected = ['bonus_score_definitions' => [$bonusScoreDefinition->id]]; + auditionLog($message, $affected); + } + + public function deleted(BonusScoreDefinition $bonusScoreDefinition): void + { + $message = 'Deleted bonus score definition #'.$bonusScoreDefinition->id.': '.$bonusScoreDefinition->name; + $affected = ['bonus_score_definitions' => [$bonusScoreDefinition->id]]; + auditionLog($message, $affected); + } +} diff --git a/tests/Feature/app/Http/Controllers/Admin/BonusScoreDefinitionControllerTest.php b/tests/Feature/app/Http/Controllers/Admin/BonusScoreDefinitionControllerTest.php new file mode 100644 index 0000000..b0b3f30 --- /dev/null +++ b/tests/Feature/app/Http/Controllers/Admin/BonusScoreDefinitionControllerTest.php @@ -0,0 +1,255 @@ +get(route('admin.bonus-scores.index'))->assertRedirect(route('home')); + actAsNormal(); + $this->get(route('admin.bonus-scores.index'))->assertRedirect(route('dashboard')); + actAsTab(); + $this->get(route('admin.bonus-scores.index'))->assertRedirect(route('dashboard')); + }); + it('show a page to manage bonus score definitions', function () { + actAsAdmin(); + $this->get(route('admin.bonus-scores.index'))->assertOk(); + }); + it('includes all bonus score definitions', function () { + actAsAdmin(); + $bonusScores = BonusScoreDefinition::factory()->count(3)->create(); + $response = $this->get(route('admin.bonus-scores.index')); + $response->assertOk(); + foreach ($bonusScores as $bonusScore) { + $response->assertSee($bonusScore->name); + } + }); + it('provides the view with a collection of auditions that have no bonus score assigned', function () { + actAsAdmin(); + $bonusScore = BonusScoreDefinition::factory()->create(); + $auditionsWithoutBonusScore = \App\Models\Audition::factory()->count(3)->create(); + $auditionsWithBonusScore = \App\Models\Audition::factory()->count(3)->create(); + foreach ($auditionsWithBonusScore as $audition) { + DB::table('bonus_score_audition_assignment')->insert([ + 'bonus_score_definition_id' => $bonusScore->id, + 'audition_id' => $audition->id, + ]); + } + $response = $this->get(route('admin.bonus-scores.index')); + $response->assertOk(); + foreach ($auditionsWithBonusScore as $audition) { + expect($response->viewData('unassignedAuditions')->contains('id', $audition->id))->toBeFalse(); + } + foreach ($auditionsWithoutBonusScore as $audition) { + expect($response->viewData('unassignedAuditions')->contains('id', $audition->id))->toBeTrue(); + } + + }); +}); + +describe('BonusScoreDefinitionController::store', function () { + it('denies access to a non-admin user', function () { + $this->post(route('admin.bonus-scores.store'))->assertRedirect(route('home')); + actAsNormal(); + $this->post(route('admin.bonus-scores.store'))->assertRedirect(route('dashboard')); + actAsTab(); + $this->post(route('admin.bonus-scores.store'))->assertRedirect(route('dashboard')); + }); + it('creates a bonus score definition', function () { + actAsAdmin(); + $this->post(route('admin.bonus-scores.store'), [ + 'name' => 'Test Bonus Score', + 'max_score' => 100, + 'weight' => 1, + ])->assertRedirect(route('admin.bonus-scores.index'))->assertSessionHas('success'); + $this->assertDatabaseHas('bonus_score_definitions', [ + 'name' => 'Test Bonus Score', + ]); + }); + it('will not create a bonus score definition with a duplicate name', function () { + BonusScoreDefinition::create(['name' => 'Test Bonus Score', 'max_score' => 100, 'weight' => 1]); + actAsAdmin(); + $response = $this->post(route('admin.bonus-scores.store'), [ + 'name' => 'Test Bonus Score', + 'max_score' => 100, + 'weight' => 1, + ])->assertSessionHasErrors('name'); + expect(BonusScoreDefinition::count())->toBe(1); + }); +}); + +describe('BonusScoreDefinitionController::destroy', function () { + beforeEach(function () { + $this->bonusScore = BonusScoreDefinition::create(['name' => 'Test Bonus Score', 'max_score' => 100, + 'weight' => 1, + ]); + }); + it('can delete a bonus score definition', function () { + actAsAdmin(); + $this->delete(route('admin.bonus-scores.destroy', + $this->bonusScore))->assertRedirect(route('admin.bonus-scores.index'))->assertSessionHas('success'); + $this->assertDatabaseMissing('bonus_score_definitions', [ + 'id' => $this->bonusScore->id, + ]); + }); + + it('denies access to a non-admin user', function () { + $this->delete(route('admin.bonus-scores.destroy', $this->bonusScore))->assertRedirect(route('home')); + actAsNormal(); + $this->delete(route('admin.bonus-scores.destroy', $this->bonusScore))->assertRedirect(route('dashboard')); + actAsTab(); + $this->delete(route('admin.bonus-scores.destroy', $this->bonusScore))->assertRedirect(route('dashboard')); + }); + + it('will not delete a bonus score definition that has auditions assigned to it', function () { + $audition = Audition::factory()->create(); + DB::table('bonus_score_audition_assignment')->insert([ + 'bonus_score_definition_id' => $this->bonusScore->id, + 'audition_id' => $audition->id, + ]); + actAsAdmin(); + $this->delete(route('admin.bonus-scores.destroy', + $this->bonusScore))->assertRedirect(route('admin.bonus-scores.index')) + ->assertSessionHas('error'); + $this->assertDatabaseCount('bonus_score_definitions', 1); + }); +}); + +describe('BonusScoreDefinitionController::assignAuditions', function () { + it('can assign a bonus score definition to an audition', function () { + $audition = Audition::factory()->create(); + $bonusScoreDefinition = BonusScoreDefinition::factory()->create(); + actAsAdmin(); + $response = $this->post(route('admin.bonus-scores.addAuditions', $bonusScoreDefinition), [ + 'audition' => [$audition->id => 1], + 'bonus_score_id' => $bonusScoreDefinition->id, + ]); + $response->assertRedirect(route('admin.bonus-scores.index'))->assertSessionHas('success'); + $this->assertDatabaseHas('bonus_score_audition_assignment', [ + 'bonus_score_definition_id' => $bonusScoreDefinition->id, + 'audition_id' => $audition->id, + ]); + }); + it('denies access to a non-admin user', function () { + $this->post(route('admin.bonus-scores.addAuditions'))->assertRedirect(route('home')); + actAsNormal(); + $this->post(route('admin.bonus-scores.addAuditions'))->assertRedirect(route('dashboard')); + actAsTab(); + $this->post(route('admin.bonus-scores.addAuditions'))->assertRedirect(route('dashboard')); + }); + it('will note assign a bonus score to the same audition twice', function () { + $bonusScoreDefinition = BonusScoreDefinition::factory()->create(); + $audition = Audition::factory()->create(); + DB::table('bonus_score_audition_assignment')->insert([ + 'bonus_score_definition_id' => $bonusScoreDefinition->id, + 'audition_id' => $audition->id, + ]); + actAsAdmin(); + $response = $this->post(route('admin.bonus-scores.addAuditions', $bonusScoreDefinition), [ + 'audition' => [$audition->id => 1], + 'bonus_score_id' => $bonusScoreDefinition->id, + ]); + $response->assertRedirect(route('admin.bonus-scores.index'))->assertSessionHas('error', + 'Error assigning auditions to bonus score'); + $this->assertDatabaseCount('bonus_score_audition_assignment', 1); + }); +}); + +describe('BonusScoreDefinitionController::unassignAudition', function () { + beforeEach(function () { + $this->bonusScoreDefinition = BonusScoreDefinition::factory()->create(); + $this->audition = Audition::factory()->create(); + DB::table('bonus_score_audition_assignment')->insert([ + 'bonus_score_definition_id' => $this->bonusScoreDefinition->id, + 'audition_id' => $this->audition->id, + ]); + }); + it('denies access to a non-admin user', function () { + $this->delete(route('admin.bonus-scores.unassignAudition', $this->audition->id))->assertRedirect(route('home')); + actAsNormal(); + $this->delete(route('admin.bonus-scores.unassignAudition', + $this->audition->id))->assertRedirect(route('dashboard')); + actAsTab(); + $this->delete(route('admin.bonus-scores.unassignAudition', + $this->audition->id))->assertRedirect(route('dashboard')); + }); + it('can unassign an audition from a bonus score definition', function () { + actAsAdmin(); + $this->delete(route('admin.bonus-scores.unassignAudition', + $this->audition->id))->assertRedirect(route('admin.bonus-scores.index'))->assertSessionHas('success'); + $this->assertDatabaseMissing('bonus_score_audition_assignment', [ + 'bonus_score_definition_id' => $this->bonusScoreDefinition->id, + 'audition_id' => $this->audition->id, + ]); + }); +}); + +describe('BonusScoreDefinitionController::judges', function () { + it('denies access to a non-admin user', function () { + $this->get(route('admin.bonus-scores.judges'))->assertRedirect(route('home')); + actAsNormal(); + $this->get(route('admin.bonus-scores.judges'))->assertRedirect(route('dashboard')); + actAsTab(); + $this->get(route('admin.bonus-scores.judges'))->assertRedirect(route('dashboard')); + }); + it('shows a page to manage subscore judges', function () { + actAsAdmin(); + $this->get(route('admin.bonus-scores.judges'))->assertOk(); + }); +}); + +describe('BonusScoreDefinitionController::assignJudge', function () { + it('denies access to a non-admin user', function () { + $bonusScore = BonusScoreDefinition::factory()->create(); + $this->post(route('admin.bonus-scores.judges.assign', $bonusScore))->assertRedirect(route('home')); + actAsNormal(); + $this->post(route('admin.bonus-scores.judges.assign', $bonusScore))->assertRedirect(route('dashboard')); + actAsTab(); + $this->post(route('admin.bonus-scores.judges.assign', $bonusScore))->assertRedirect(route('dashboard')); + }); + it('can assign a judge to a bonus score', function () { + $judge = User::factory()->create(); + $bonusScore = BonusScoreDefinition::factory()->create(); + actAsAdmin(); + $response = $this->post(route('admin.bonus-scores.judges.assign', $bonusScore), [ + 'judge' => $judge->id, + ]); + $response->assertRedirect(route('admin.bonus-scores.judges'))->assertSessionHas('success'); + $this->assertDatabaseHas('bonus_score_judge_assignment', [ + 'bonus_score_definition_id' => $bonusScore->id, + 'user_id' => $judge->id, + ]); + }); +}); + +describe('BonusScoreDefinitionController::removeJudge', function () { + it('denies access to a non-admin user', function () { + $bonusScore = BonusScoreDefinition::factory()->create(); + $this->delete(route('admin.bonus-scores.judges.remove', $bonusScore))->assertRedirect(route('home')); + actAsNormal(); + $this->delete(route('admin.bonus-scores.judges.remove', $bonusScore))->assertRedirect(route('dashboard')); + actAsTab(); + $this->delete(route('admin.bonus-scores.judges.remove', $bonusScore))->assertRedirect(route('dashboard')); + }); + it('can remove a judge from a bonus score', function () { + $judge = User::factory()->create(); + $bonusScore = BonusScoreDefinition::factory()->create(); + DB::table('bonus_score_judge_assignment')->insert([ + 'bonus_score_definition_id' => $bonusScore->id, + 'user_id' => $judge->id, + ]); + actAsAdmin(); + $response = $this->delete(route('admin.bonus-scores.judges.remove', $bonusScore), [ + 'judge' => $judge->id, + ]); + $response->assertRedirect(route('admin.bonus-scores.judges'))->assertSessionHas('success'); + $this->assertDatabaseMissing('bonus_score_judge_assignment', [ + 'bonus_score_definition_id' => $bonusScore->id, + 'user_id' => $judge->id, + ]); + }); +});