diff --git a/app/Http/Controllers/Admin/DrawController.php b/app/Http/Controllers/Admin/DrawController.php index b89ed75..6578e94 100644 --- a/app/Http/Controllers/Admin/DrawController.php +++ b/app/Http/Controllers/Admin/DrawController.php @@ -3,12 +3,14 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; +use App\Http\Requests\ClearDrawRequest; use App\Http\Requests\RunDrawRequest; use App\Models\Audition; use App\Models\Event; use App\Services\DrawService; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Log; use function array_keys; use function to_route; @@ -51,6 +53,16 @@ class DrawController extends Controller $drawnAuditions = Audition::whereHas('flags', function ($query) { $query->where('flag_name', 'drawn'); })->get(); + return view('admin.draw.edit', compact('drawnAuditions')); } + + public function destroy(ClearDrawRequest $request) + { + $auditions = Audition::with('flags')->findMany(array_keys($request->input('audition', []))); + $this->drawService->clearDrawsOnCollection($auditions); + + return to_route('admin.draw.index')->with('status', 'Draw completed successfully'); + + } } diff --git a/app/Http/Requests/ClearDrawRequest.php b/app/Http/Requests/ClearDrawRequest.php new file mode 100644 index 0000000..d4b12ee --- /dev/null +++ b/app/Http/Requests/ClearDrawRequest.php @@ -0,0 +1,53 @@ + ['required', 'array'], + ]; + } + + public function messages(): array + { + return [ + 'audition.required' => 'No auditions were selected', + 'audition.array' => 'Invalid request format', + ]; + } + + public function withValidator($validator): void + { + $validator->after(function ($validator) { + foreach ($this->input('audition', []) as $auditionId => $value) { + if (! is_numeric($auditionId) || ! Audition::where('id', $auditionId)->exists()) { + $validator->errors()->add('audition', 'One or more invalid auditions were selected'); + } + } + }); + + } + + protected function failedValidation(Validator $validator) + { + $msg = $validator->errors()->get('audition')[0]; + + return to_route('admin.draw.index')->with('error', $msg); + } +} diff --git a/app/Services/DrawService.php b/app/Services/DrawService.php index 5ee96c6..53d162c 100644 --- a/app/Services/DrawService.php +++ b/app/Services/DrawService.php @@ -40,4 +40,15 @@ class DrawService return $auditions->contains(fn ($audition) => $audition->hasFlag('drawn')); } + + public function clearDrawForAudition(Audition $audition): void + { + $audition->removeFlag('drawn'); + DB::table('entries')->where('audition_id', $audition->id)->update(['draw_number' => null]); + } + + public function clearDrawsOnCollection($auditions): void + { + $auditions->each(fn ($audition) => $this->clearDrawForAudition($audition)); + } } diff --git a/tests/Feature/Pages/Setup/DrawEditTest.php b/tests/Feature/Pages/Setup/DrawEditTest.php new file mode 100644 index 0000000..1bd3dae --- /dev/null +++ b/tests/Feature/Pages/Setup/DrawEditTest.php @@ -0,0 +1,77 @@ +get(route('admin.draw.edit')) + ->assertRedirect(route('home')); + actAsNormal(); + $this->get(route('admin.draw.edit')) + ->assertSessionHas('error', 'You are not authorized to perform this action') + ->assertRedirect(route('dashboard')); + actAsAdmin(); + $this->get(route('admin.draw.edit')) + ->assertOk() + ->assertViewIs('admin.draw.edit'); + +}); +it('lists auditions that have been drawn', function () { + $audition = Audition::factory()->create(); + $drawnAudition = Audition::factory()->create(); + $drawnAudition->addFlag('drawn'); + actAsAdmin(); + $response = $this->get(route('admin.draw.edit')); + $response->assertOk() + ->assertSee($drawnAudition->name) + ->assertDontSee($audition->name); +}); +it('has a warning message', function () { + actAsAdmin(); + $response = $this->get(route('admin.draw.edit')); + $response->assertOk() + ->assertSee('Caution'); +}); + +it('submits to the admin.draw.destroy route', function () { + actAsAdmin(); + $this->get(route('admin.draw.edit')) + ->assertOk() + ->assertSee(route('admin.draw.destroy')); +}); +it('does not allow a non-admin user to clear a draw', function () { + $this->delete(route('admin.draw.destroy')) + ->assertRedirect(route('home')); + actAsNormal(); + $this->delete(route('admin.draw.destroy')) + ->assertSessionHas('error', 'You are not authorized to perform this action') + ->assertRedirect(route('dashboard')); +}); +it('allows an administrator to clear a draw', function () { + $audition = Audition::factory()->create(); + $audition->addFlag('drawn'); + $entries = Entry::factory()->count(3)->create(['audition_id' => $audition->id]); + $n = 1; + $entries->each(function ($entry) use (&$n) { + $entry->draw_number = $n; + $entry->save(); + $n++; + }); + actAsAdmin(); + /** @noinspection PhpUnhandledExceptionInspection */ + $this->delete(route('admin.draw.destroy'), ['audition' => [$audition->id => 'on']]) + ->assertSessionHasNoErrors() + ->assertRedirect(route('admin.draw.index')); + + $entries->each(function ($entry) { + $entry->refresh(); + expect($entry->draw_number)->toBeNull(); + }); + + $checkAudition = Audition::find($audition->id); + expect($checkAudition->hasFlag('drawn'))->toBeFalse(); +}); diff --git a/tests/Feature/Pages/Setup/DrawStoreTest.php b/tests/Feature/Pages/Setup/DrawStoreTest.php index ea291e4..ad3e63f 100644 --- a/tests/Feature/Pages/Setup/DrawStoreTest.php +++ b/tests/Feature/Pages/Setup/DrawStoreTest.php @@ -2,6 +2,7 @@ use App\Models\Audition; use App\Models\Entry; +use App\Services\DrawService; use Illuminate\Foundation\Testing\RefreshDatabase; uses(RefreshDatabase::class); @@ -48,7 +49,7 @@ it('sets a drawn flag on the audition once a draw is run', function () { $audition = Audition::factory()->create(); actAsAdmin(); // Act & Assert - $response = $this->post(route('admin.draw.store', ['audition['.$audition->id.']' => 'on'])); + $this->post(route('admin.draw.store', ['audition['.$audition->id.']' => 'on'])); expect($audition->hasFlag('drawn'))->toBeTrue(); }); it('refuses to draw an audition that has an existing draw', function () { @@ -72,4 +73,21 @@ it('randomizes the order of the entries in the audition', function () { expect($audition->entries->sortBy('draw_number')->pluck('id')) ->not()->toBe($entries->pluck('id')); }); -// sets the draw_number column on each entry in the audition based on the randomized entry order +it('sets the draw_number column on each entry in the audition based on the randomized entry order', function () { + // Arrange + $audition = Audition::factory()->hasEntries(10)->create(); + Entry::all()->each(fn ($entry) => expect($entry->draw_number)->toBeNull()); + $drawService = new DrawService(); + $drawService->runOneDraw($audition); + // Act & Assert + Entry::all()->each(fn ($entry) => expect($entry->draw_number)->not()->toBeNull()); +}); +it('only sets draw numbers in the specified audition', function () { + // Arrange + $audition = Audition::factory()->hasEntries(10)->create(); + $bonusEntry = Entry::factory()->create(); + $drawService = new DrawService(); + $drawService->runOneDraw($audition); + // Act & Assert + expect($bonusEntry->draw_number)->toBeNull(); +});