From 2e8d625ab099b7c25a0a00b9f47818be9cc98f9e Mon Sep 17 00:00:00 2001 From: Matt Young Date: Mon, 7 Jul 2025 21:52:10 -0500 Subject: [PATCH] Add Scoring Guide controller test --- .../Admin/ScoringGuideController.php | 103 ++--- .../Requests/SubscoreDefinitionRequest.php | 57 +++ app/Observers/ScoringGuideObserver.php | 6 +- .../views/components/form/checkbox.blade.php | 1 + .../Schools/AssignUserToSchoolTest.php | 1 + .../Admin/ScoringGuideControllerTest.php | 399 ++++++++++++++++++ 6 files changed, 488 insertions(+), 79 deletions(-) create mode 100644 app/Http/Requests/SubscoreDefinitionRequest.php create mode 100644 tests/Feature/app/Http/Controllers/Admin/ScoringGuideControllerTest.php diff --git a/app/Http/Controllers/Admin/ScoringGuideController.php b/app/Http/Controllers/Admin/ScoringGuideController.php index efbf8aa..657e9ae 100644 --- a/app/Http/Controllers/Admin/ScoringGuideController.php +++ b/app/Http/Controllers/Admin/ScoringGuideController.php @@ -3,14 +3,12 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; +use App\Http\Requests\SubscoreDefinitionRequest; use App\Models\ScoringGuide; use App\Models\SubscoreDefinition; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; -use function abort; -use function auditionSetting; use function request; use function response; @@ -28,26 +26,19 @@ class ScoringGuideController extends Controller public function store() { - if (! Auth::user()->is_admin) { - abort(403); - } - request()->validate([ 'name' => ['required', 'unique:scoring_guides'], ]); - $guide = ScoringGuide::create([ + ScoringGuide::create([ 'name' => request('name'), ]); return redirect(route('admin.scoring.index'))->with('success', 'Scoring guide created'); } - public function edit(Request $request, ScoringGuide $guide, string $tab = 'detail') + public function edit(ScoringGuide $guide, string $tab = 'detail') { - if (! Auth::user()->is_admin) { - abort(403); - } if ($tab == 'tiebreakOrder') { $subscores = SubscoreDefinition::where('scoring_guide_id', $guide->id)->orderBy('tiebreak_order')->get(); } else { @@ -59,9 +50,6 @@ class ScoringGuideController extends Controller public function update(ScoringGuide $guide) { - if (! Auth::user()->is_admin) { - abort(403); - } request()->validate([ 'name' => ['required', 'unique:scoring_guides'], ]); @@ -75,12 +63,9 @@ class ScoringGuideController extends Controller public function destroy(ScoringGuide $guide) { - if (! Auth::user()->is_admin) { - abort(403); - } - if ($guide->auditions()->count() > 0) { - return redirect('/admin/scoring')->with('error', 'Cannot delete scoring guide with auditions'); + return redirect('/admin/scoring')->with('error', + 'Cannot delete scoring guide being used by one or more auditions'); } $guide->delete(); @@ -88,89 +73,57 @@ class ScoringGuideController extends Controller return redirect('/admin/scoring')->with('success', 'Scoring guide deleted'); } - public function subscore_store(Request $request, ScoringGuide $guide) + public function subscore_store(SubscoreDefinitionRequest $request, ScoringGuide $guide) { - if (! $guide->exists()) { - abort(409); - } - $validateData = request()->validate([ - 'name' => ['required'], - 'max_score' => ['required', 'integer'], - 'weight' => ['required', 'integer'], - 'for_seating' => ['nullable', 'boolean'], - 'for_advance' => ['nullable', 'boolean'], - ]); - $for_seating = $request->has('for_seating') ? (bool) $request->input('for_seating') : false; - $for_advance = $request->has('for_advance') ? (bool) $request->input('for_advance') : false; - if (! auditionSetting('advanceTo')) { - $for_seating = true; - } + $validateData = $request->validated(); + // Put the new subscore at the end of the list for both display and tiebreak order $display_order = SubscoreDefinition::where('scoring_guide_id', '=', $guide->id)->max('display_order') + 1; $tiebreak_order = SubscoreDefinition::where('scoring_guide_id', '=', $guide->id)->max('tiebreak_order') + 1; - $subscore = SubscoreDefinition::create([ + SubscoreDefinition::create([ 'scoring_guide_id' => $guide->id, 'name' => $validateData['name'], 'max_score' => $validateData['max_score'], 'weight' => $validateData['weight'], 'display_order' => $display_order, 'tiebreak_order' => $tiebreak_order, - 'for_seating' => $for_seating, - 'for_advance' => $for_advance, + 'for_seating' => $validateData['for_seating'], + 'for_advance' => $validateData['for_advance'], ]); return redirect(route('admin.scoring.edit', $guide))->with('success', 'Subscore added'); } - public function subscore_update(ScoringGuide $guide, SubscoreDefinition $subscore) - { - if (! Auth::user()->is_admin) { - abort(403); - } - if (! $guide->exists() || ! $subscore->exists()) { - abort(409); - } + public function subscore_update( + SubscoreDefinitionRequest $request, + ScoringGuide $guide, + SubscoreDefinition $subscore + ) { if ($subscore->scoring_guide_id !== $guide->id) { // Make sure the subscore were updating belongs to the guide - abort(409); - } - $validateData = request()->validate([ - 'name' => ['required'], - 'max_score' => ['required', 'integer'], - 'weight' => ['required', 'integer'], - 'for_seating' => ['nullable', 'boolean'], - 'for_advance' => ['nullable', 'boolean'], - ]); - - $for_seating = request()->has('for_seating') ? (bool) request()->input('for_seating') : false; - $for_advance = request()->has('for_advance') ? (bool) request()->input('for_advance') : false; - - if (! auditionSetting('advanceTo')) { - $for_seating = true; + return redirect('/admin/scoring/guides/'.$subscore->scoring_guide_id.'/edit')->with('error', + 'Cannot update a subscore for a different scoring guide'); } + $validateData = $validateData = $request->validated(); $subscore->update([ 'name' => $validateData['name'], 'max_score' => $validateData['max_score'], 'weight' => $validateData['weight'], - 'for_seating' => $for_seating, - 'for_advance' => $for_advance, + 'for_seating' => $validateData['for_seating'], + 'for_advance' => $validateData['for_advance'], ]); - return redirect('/admin/scoring/guides/'.$guide->id.'/edit')->with('success', 'Subscore updated'); + return redirect(route('admin.scoring.edit', $guide))->with('success', 'Subscore updated'); } public function subscore_destroy(ScoringGuide $guide, SubscoreDefinition $subscore) { - if (! Auth::user()->is_admin) { - abort(403); - } - if (! $guide->exists() || ! $subscore->exists()) { - abort(409); - } if ($subscore->scoring_guide_id !== $guide->id) { // Make sure the subscore were updating belongs to the guide - abort(409); + + return redirect(route('admin.scoring.edit', $subscore->scoring_guide_id))->with('error', + 'Cannot delete a subscore for a different scoring guide'); } $subscore->delete(); @@ -181,9 +134,6 @@ class ScoringGuideController extends Controller public function reorder_display(Request $request) { - if (! Auth::user()->is_admin) { - abort(403); - } $order = $request->order; foreach ($order as $index => $id) { $subscore = SubscoreDefinition::find($id); @@ -196,9 +146,6 @@ class ScoringGuideController extends Controller public function reorder_tiebreak(Request $request) { - if (! Auth::user()->is_admin) { - abort(403); - } $order = $request->order; foreach ($order as $index => $id) { $subscore = SubscoreDefinition::find($id); diff --git a/app/Http/Requests/SubscoreDefinitionRequest.php b/app/Http/Requests/SubscoreDefinitionRequest.php new file mode 100644 index 0000000..8b188be --- /dev/null +++ b/app/Http/Requests/SubscoreDefinitionRequest.php @@ -0,0 +1,57 @@ +user()->is_admin; // Only allow admins to create/update subscores + + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + $guideId = $this->route('guide')->id; // get the guide ID from route model binding + $definition = $this->route('subscore')->id ?? null; // get the guide ID from route model binding + + return [ + 'name' => [ + 'required', + 'string', + Rule::unique('subscore_definitions') // name must be unique on a guide, allow for the current name + ->where(fn ($query) => $query->where('scoring_guide_id', $guideId)) + ->ignore($definition), + ], + 'max_score' => ['required', 'integer'], + 'weight' => ['required', 'integer'], + 'for_seating' => ['sometimes', 'nullable'], + 'for_advance' => ['sometimes', 'nullable'], + ]; + } + + protected function passedValidation() + { + // Normalize the boolean inputs + $this->merge([ + 'for_seating' => $this->has('for_seating') ? (bool) $this->input('for_seating') : false, + 'for_advance' => $this->has('for_advance') ? (bool) $this->input('for_advance') : false, + ]); + + // Apply your custom logic + if (! auditionSetting('advanceTo')) { + $this->merge(['for_seating' => true]); + } + } +} diff --git a/app/Observers/ScoringGuideObserver.php b/app/Observers/ScoringGuideObserver.php index 7f2c23a..a11649c 100644 --- a/app/Observers/ScoringGuideObserver.php +++ b/app/Observers/ScoringGuideObserver.php @@ -3,6 +3,7 @@ namespace App\Observers; use App\Models\ScoringGuide; +use Illuminate\Support\Facades\Schema; use function auditionLog; @@ -10,7 +11,10 @@ class ScoringGuideObserver { public function created(ScoringGuide $scoringGuide): void { - $message = 'Added scoring guide '.$scoringGuide->name.$scoringGuide->name.'(ID #'.$scoringGuide->id.')'; + if (! Schema::hasTable('audit_log_entries')) { + return; + } + $message = 'Added scoring guide '.$scoringGuide->name.'(ID #'.$scoringGuide->id.')'; $affected = ['scoring_guides' => [$scoringGuide->id]]; auditionLog($message, $affected); } diff --git a/resources/views/components/form/checkbox.blade.php b/resources/views/components/form/checkbox.blade.php index fbba523..9c31e41 100644 --- a/resources/views/components/form/checkbox.blade.php +++ b/resources/views/components/form/checkbox.blade.php @@ -14,6 +14,7 @@ aria-describedby="comments-description" name="{{ $name }}" type="checkbox" + value="1" @if($checked) checked @endif {{ $attributes->merge(['class' => "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600"]) }}> diff --git a/tests/Feature/app/Actions/Schools/AssignUserToSchoolTest.php b/tests/Feature/app/Actions/Schools/AssignUserToSchoolTest.php index b9275e2..d79b9ee 100644 --- a/tests/Feature/app/Actions/Schools/AssignUserToSchoolTest.php +++ b/tests/Feature/app/Actions/Schools/AssignUserToSchoolTest.php @@ -33,6 +33,7 @@ it('assigns a user to a school', function () { }); it('logs the assignment of the user to the school', function () { + $this->user->save(); ($this->assigner)($this->user, $this->school); $logEntry = AuditLogEntry::orderBy('id', 'desc')->first(); expect($logEntry->message)->toEqual('Added '.$this->user->full_name().' to '.$this->school->name); diff --git a/tests/Feature/app/Http/Controllers/Admin/ScoringGuideControllerTest.php b/tests/Feature/app/Http/Controllers/Admin/ScoringGuideControllerTest.php new file mode 100644 index 0000000..1019367 --- /dev/null +++ b/tests/Feature/app/Http/Controllers/Admin/ScoringGuideControllerTest.php @@ -0,0 +1,399 @@ +get(route('admin.scoring.index'))->assertRedirect(route('home')); + actAsNormal(); + $this->get(route('admin.scoring.index'))->assertRedirect(route('dashboard')); + actAsTab(); + $this->get(route('admin.scoring.index'))->assertRedirect(route('dashboard')); + }); + it('ensures that auditions with null for scoring_guide_id are set to zero', function () { + $audition = Audition::factory()->create(); + actAsAdmin(); + $this->get(route('admin.scoring.index')); + $audition->refresh(); + expect($audition->scoring_guide_id)->toEqual(0); + }); + it('shows the scoring guide index page', function () { + actAsAdmin(); + $this->get(route('admin.scoring.index')) + ->assertOk() + ->assertViewHas('guides') + ->assertViewIs('admin.scoring.index'); + }); +}); + +describe('ScoringGuideController::store', function () { + it('denies access to a non-admin user', function () { + $this->post(route('admin.scoring.store'))->assertRedirect(route('home')); + actAsNormal(); + $this->post(route('admin.scoring.store'))->assertRedirect(route('dashboard')); + actAsTab(); + $this->post(route('admin.scoring.store'))->assertRedirect(route('dashboard')); + }); + it('stores a scoring guide', function () { + actAsAdmin(); + $response = $this->post(route('admin.scoring.store'), [ + 'name' => 'Test Guide', + ]); + $response->assertRedirect(route('admin.scoring.index')); + expect(ScoringGuide::count())->toEqual(2) + ->and(ScoringGuide::orderBy('id', 'desc')->first())->toBeInstanceOf(ScoringGuide::class) + ->and(ScoringGuide::orderBy('id', 'desc')->first()->name)->toEqual('Test Guide'); + }); + it('will not store a scoring guide with a blank name', function () { + actAsAdmin(); + $response = $this->post(route('admin.scoring.store'), [ + 'name' => '', + ]); + $response->assertSessionHasErrors('name'); + expect(ScoringGuide::count())->toEqual(1); + }); + it('will not store a scoring guide with a duplicate name', function () { + actAsAdmin(); + $response = $this->post(route('admin.scoring.store'), [ + 'name' => 'Test Guide', + ]); + $response = $this->post(route('admin.scoring.store'), [ + 'name' => 'Test Guide', + ]); + $response->assertSessionHasErrors('name'); + expect(ScoringGuide::count())->toEqual(2); + }); +}); + +describe('ScoringGuideController::edit', function () { + beforeEach(function () { + $this->guide = ScoringGuide::factory()->create(); + }); + it('denies access to a non-admin user', function () { + $this->get(route('admin.scoring.edit', $this->guide))->assertRedirect(route('home')); + actAsNormal(); + $this->get(route('admin.scoring.edit', $this->guide))->assertRedirect(route('dashboard')); + actAsTab(); + $this->get(route('admin.scoring.edit', $this->guide))->assertRedirect(route('dashboard')); + }); + it('shows the scoring guide edit page', function () { + actAsAdmin(); + $this->get(route('admin.scoring.edit', $this->guide)) + ->assertOk() + ->assertViewIs('admin.scoring.edit') + ->assertViewHas('guide') + ->assertViewHas('subscores') + ->assertViewHas('tab'); + }); + it('includes links to other tabs for managing subscore order', function () { + actAsAdmin(); + $this->get(route('admin.scoring.edit', $this->guide)) + ->assertSeeInOrder(['href', route('admin.scoring.edit', $this->guide, ['tab' => 'detail'])]) + ->assertSeeInOrder(['href', route('admin.scoring.edit', $this->guide, ['tab' => 'displayOrder'])]) + ->assertSeeInOrder(['href', route('admin.scoring.edit', $this->guide, ['tab' => 'tiebreakOrder'])]); + }); + it('includes forms to edit each subscore', function () { + actAsAdmin(); + $response = $this->get(route('admin.scoring.edit', $this->guide)); + foreach (SubscoreDefinition::all() as $subscore) { + $response->assertSeeInOrder([ + 'POST', 'action', route('admin.scoring.subscore_update', [$this->guide, 'subscore' => $subscore->id]), + ]); + } + }); + it('gets subscores in the correct order fo the specified tab', function () { + $tbOrderOn = 1; + $displayOrderOn = 10; + SubscoreDefinition::truncate(); + $id = $this->guide->id; + $ss1 = SubscoreDefinition::create([ + 'display_order' => 2, + 'tiebreak_order' => 2, + 'name' => 'ss1', + 'scoring_guide_id' => $id, + 'max_score' => 100, + 'weight' => 1, + 'for_advance' => 1, + 'for_seating' => 1, + ]); + $ss2 = SubscoreDefinition::create([ + 'display_order' => 1, + 'tiebreak_order' => 3, + 'name' => 'ss1', + 'scoring_guide_id' => $id, + 'max_score' => 100, + 'weight' => 1, + 'for_advance' => 1, + 'for_seating' => 1, + ]); + $ss3 = SubscoreDefinition::create([ + 'display_order' => 3, + 'tiebreak_order' => 1, + 'name' => 'ss1', + 'scoring_guide_id' => $id, + 'max_score' => 100, + 'weight' => 1, + 'for_advance' => 1, + 'for_seating' => 1, + ]); + actAsAdmin(); + $response = $this->get(route('admin.scoring.edit', [$this->guide, 'tab' => 'displayOrder'])); + expect($response->viewData('subscores')[0]->id)->toEqual($ss2->id); + $response = $this->get(route('admin.scoring.edit', [$this->guide, 'tab' => 'detail'])); + expect($response->viewData('subscores')[0]->id)->toEqual($ss2->id); + $response = $this->get(route('admin.scoring.edit', [$this->guide, 'tab' => 'tiebreakOrder'])); + expect($response->viewData('subscores')[0]->id)->toEqual($ss3->id); + }); +}); + +describe('ScoringGuideController::update', function () { + beforeEach(function () { + $this->guide = ScoringGuide::factory()->create(); + }); + it('denies access to a non-admin user', function () { + $this->patch(route('admin.scoring.update', $this->guide))->assertRedirect(route('home')); + actAsNormal(); + $this->patch(route('admin.scoring.update', $this->guide))->assertRedirect(route('dashboard')); + actAsTab(); + $this->patch(route('admin.scoring.update', $this->guide))->assertRedirect(route('dashboard')); + }); + it('updates a scoring guide', function () { + actAsAdmin(); + $response = $this->patch(route('admin.scoring.update', $this->guide), [ + 'name' => 'Test Guide', + ]); + $response->assertRedirect(route('admin.scoring.edit', $this->guide)); + expect(ScoringGuide::count())->toEqual(2) // Accounts for guide 0 - no guide assigned + ->and(ScoringGuide::orderBy('id', 'desc')->first())->toBeInstanceOf(ScoringGuide::class) + ->and(ScoringGuide::orderBy('id', 'desc')->first()->name)->toEqual('Test Guide'); + }); + it('will not duplicate a scoring guide name', function () { + actAsAdmin(); + ScoringGuide::create(['name' => 'Dont Copy Me Bro']); + $response = $this->patch(route('admin.scoring.update', $this->guide), [ + 'name' => 'Dont Copy Me Bro', + ]); + $response->assertSessionHasErrors('name'); + expect($this->guide->fresh()->name)->not()->toEqual('Dont Copy Me Bro'); + }); +}); + +describe('ScoringGuideController::destroy', function () { + beforeEach(function () { + $this->guide = ScoringGuide::factory()->create(); + }); + it('denies access to a non-admin user', function () { + $this->delete(route('admin.scoring.destroy', $this->guide))->assertRedirect(route('home')); + actAsNormal(); + $this->delete(route('admin.scoring.destroy', $this->guide))->assertRedirect(route('dashboard')); + actAsTab(); + $this->delete(route('admin.scoring.destroy', $this->guide))->assertRedirect(route('dashboard')); + }); + it('deletes a scoring guide', function () { + actAsAdmin(); + $this->delete(route('admin.scoring.destroy', $this->guide)) + ->assertRedirect(route('admin.scoring.index')); + expect(ScoringGuide::count())->toEqual(1); + expect(ScoringGuide::find($this->guide->id))->toBeNull(); + }); + it('will not delete a scoring guide that is assigned to an audition', function () { + $audition = Audition::factory()->create(['scoring_guide_id' => $this->guide->id]); + actAsAdmin(); + $this->delete(route('admin.scoring.destroy', $this->guide)) + ->assertRedirect(route('admin.scoring.index')) + ->assertSessionHas('error', 'Cannot delete scoring guide being used by one or more auditions'); + expect(ScoringGuide::count())->toEqual(2); + }); +}); + +describe('ScoringGuideController::subscore_store', function () { + beforeEach(function () { + $this->guide = ScoringGuide::factory()->create(); + }); + it('denies access to a non-admin user', function () { + $this->post(route('admin.scoring.subscore_store', + [$this->guide, 'subscore' => $this->guide->id]))->assertRedirect(route('home')); + actAsNormal(); + $this->post(route('admin.scoring.subscore_store', + [$this->guide, 'subscore' => $this->guide->id]))->assertRedirect(route('dashboard')); + actAsTab(); + $this->post(route('admin.scoring.subscore_store', + [$this->guide, 'subscore' => $this->guide->id]))->assertRedirect(route('dashboard')); + }); + it('creates a subscore', function () { + actAsAdmin(); + $response = $this->post(route('admin.scoring.subscore_store', [$this->guide, 'subscore' => $this->guide->id]), [ + 'name' => 'Test Subscore', + 'max_score' => 150, + 'weight' => 3, + 'for_advance' => 'on', + 'for_seating' => 'on', + ]); + $response->assertRedirect(route('admin.scoring.edit', [$this->guide])); + expect(SubscoreDefinition::orderBy('id', 'desc')->first()->name)->toEqual('Test Subscore'); + }); +}); + +describe('ScoringGuideController::subscore_update', function () { + beforeEach(function () { + $this->guide = ScoringGuide::factory()->create(); + $this->testSubscore = SubscoreDefinition::first(); + $this->testSubscore->name = 'Test Subscore'; + $this->testSubscore->save(); + }); + it('denies access to a non-admin user', function () { + $this->patch(route('admin.scoring.subscore_update', + [$this->guide, 'subscore' => $this->testSubscore->id]))->assertRedirect(route('home')); + actAsNormal(); + $this->patch(route('admin.scoring.subscore_update', + [$this->guide, 'subscore' => $this->testSubscore->id]))->assertRedirect(route('dashboard')); + actAsTab(); + $this->patch(route('admin.scoring.subscore_update', + [$this->guide, 'subscore' => $this->testSubscore->id]))->assertRedirect(route('dashboard')); + }); + + it('can update a subscore', function () { + actAsAdmin(); + $response = $this->patch(route('admin.scoring.subscore_update', + ['subscore' => $this->testSubscore->id, $this->guide]), [ + 'name' => 'New Name', + 'max_score' => 150, + 'weight' => 3, + 'for_advance' => 'on', + 'for_seating' => 'on', + ]); + $response->assertRedirect(route('admin.scoring.edit', [$this->guide])); + expect(SubscoreDefinition::find($this->testSubscore->id)->name)->toEqual('New Name'); + }); + + it('can update a subscore without changing name', function () { + actAsAdmin(); + $response = $this->patch(route('admin.scoring.subscore_update', + ['subscore' => $this->testSubscore->id, $this->guide]), [ + 'name' => 'Test Subscore', + 'max_score' => 90, + 'weight' => 3, + 'for_advance' => 'on', + 'for_seating' => 'on', + ]); + $response->assertRedirect(route('admin.scoring.edit', [$this->guide])); + expect(SubscoreDefinition::find($this->testSubscore->id)->name)->toEqual('Test Subscore') + ->and(SubscoreDefinition::find($this->testSubscore->id)->max_score)->toEqual(90); + }); + + it('will not update a subscore for a different guide', function () { + $guide2 = ScoringGuide::factory()->create(); + actAsAdmin(); + $response = $this->patch(route('admin.scoring.subscore_update', + ['subscore' => $this->testSubscore->id, $guide2]), [ + 'name' => 'Test Subscore', + 'max_score' => 90, + 'weight' => 3, + 'for_advance' => 'on', + 'for_seating' => 'on', + ] + ); + $response->assertSessionHas('error', 'Cannot update a subscore for a different scoring guide') + ->assertRedirect(route('admin.scoring.edit', [$this->guide])); + }); +}); + +describe('ScoringGuideController::subscore_destroy', function () { + beforeEach(function () { + $this->guide = ScoringGuide::factory()->create(); + $this->testSubscore = SubscoreDefinition::first(); + $this->testSubscore->name = 'Test Subscore'; + $this->testSubscore->save(); + }); + it('denies access to a non-admin user', function () { + $this->delete(route('admin.scoring.subscore_destroy', + ['subscore' => $this->guide, $this->testSubscore->id]))->assertRedirect(route('home')); + actAsNormal(); + $this->delete(route('admin.scoring.subscore_destroy', + ['subscore' => $this->guide, $this->testSubscore->id]))->assertRedirect(route('dashboard')); + actAsTab(); + $this->delete(route('admin.scoring.subscore_destroy', + ['subscore' => $this->guide, $this->testSubscore->id]))->assertRedirect(route('dashboard')); + }); + it('can delete a subscore', function () { + actAsAdmin(); + $response = $this->delete(route('admin.scoring.subscore_destroy', + ['subscore' => $this->guide, $this->testSubscore->id])); + $response->assertRedirect(route('admin.scoring.edit', [$this->guide])); + expect(SubscoreDefinition::find($this->testSubscore->id))->toBeNull(); + }); + it('will not delete a subscore for a different guide', function () { + $guide2 = ScoringGuide::factory()->create(); + actAsAdmin(); + $response = $this->delete(route('admin.scoring.subscore_destroy', + [$guide2, $this->testSubscore->id])); + expect(SubscoreDefinition::find($this->testSubscore->id))->not()->toBeNull(); + $response->assertSessionHas('error', 'Cannot delete a subscore for a different scoring guide') + ->assertRedirect(route('admin.scoring.edit', $this->guide)); + }); +}); + +describe('ScoringGuideController::reorder_display', function () { + it('denies access to a non-admin user', function () { + $this->post(route('admin.scoring.reorder_display'))->assertRedirect(route('home')); + actAsNormal(); + $this->post(route('admin.scoring.reorder_display'))->assertRedirect(route('dashboard')); + actAsTab(); + $this->post(route('admin.scoring.reorder_display'))->assertRedirect(route('dashboard')); + }); + + it('can reorder display order', function () { + $sg = ScoringGuide::factory()->create(); + SubscoreDefinition::truncate(); + $ss1 = SubscoreDefinition::factory()->create(['scoring_guide_id' => $sg->id, 'display_order' => 1]); + $ss2 = SubscoreDefinition::factory()->create(['scoring_guide_id' => $sg->id, 'display_order' => 2]); + $ss3 = SubscoreDefinition::factory()->create(['scoring_guide_id' => $sg->id, 'display_order' => 3]); + + actAsAdmin(); + $response = $this->post(route('admin.scoring.reorder_display'), [ + 'order' => [$ss2->id, $ss1->id, $ss3->id], + ]); + $response->assertJson(['status' => 'success']); + $ss1->refresh(); + $ss2->refresh(); + $ss3->refresh(); + expect($ss1->display_order)->toEqual(2); + expect($ss2->display_order)->toEqual(1); + expect($ss3->display_order)->toEqual(3); + }); +}); + +describe('ScoringGuideController::reorder_tiebreak', function () { + it('denies access to a non-admin user', function () { + $this->post(route('admin.scoring.reorder_tiebreak'))->assertRedirect(route('home')); + actAsNormal(); + $this->post(route('admin.scoring.reorder_tiebreak'))->assertRedirect(route('dashboard')); + actAsTab(); + $this->post(route('admin.scoring.reorder_tiebreak'))->assertRedirect(route('dashboard')); + }); + + it('can reorder display order', function () { + $sg = ScoringGuide::factory()->create(); + SubscoreDefinition::truncate(); + $ss1 = SubscoreDefinition::factory()->create(['scoring_guide_id' => $sg->id, 'tiebreak_order' => 1]); + $ss2 = SubscoreDefinition::factory()->create(['scoring_guide_id' => $sg->id, 'tiebreak_order' => 2]); + $ss3 = SubscoreDefinition::factory()->create(['scoring_guide_id' => $sg->id, 'tiebreak_order' => 3]); + + actAsAdmin(); + $response = $this->post(route('admin.scoring.reorder_tiebreak'), [ + 'order' => [$ss3->id, $ss2->id, $ss1->id], + ]); + $response->assertJson(['status' => 'success']); + $ss1->refresh(); + $ss2->refresh(); + $ss3->refresh(); + expect($ss1->tiebreak_order)->toEqual(3); + expect($ss2->tiebreak_order)->toEqual(2); + expect($ss3->tiebreak_order)->toEqual(1); + }); +});