Rewrite draw #12

Merged
okorpheus merged 3 commits from rewrite-draw into master 2024-07-07 21:00:20 +00:00
5 changed files with 173 additions and 2 deletions
Showing only changes of commit 817eb574bd - Show all commits

View File

@ -3,12 +3,14 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\ClearDrawRequest;
use App\Http\Requests\RunDrawRequest; use App\Http\Requests\RunDrawRequest;
use App\Models\Audition; use App\Models\Audition;
use App\Models\Event; use App\Models\Event;
use App\Services\DrawService; use App\Services\DrawService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use function array_keys; use function array_keys;
use function to_route; use function to_route;
@ -51,6 +53,16 @@ class DrawController extends Controller
$drawnAuditions = Audition::whereHas('flags', function ($query) { $drawnAuditions = Audition::whereHas('flags', function ($query) {
$query->where('flag_name', 'drawn'); $query->where('flag_name', 'drawn');
})->get(); })->get();
return view('admin.draw.edit', compact('drawnAuditions')); 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');
}
} }

View File

@ -0,0 +1,53 @@
<?php
namespace App\Http\Requests;
use App\Models\Audition;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use function to_route;
class ClearDrawRequest extends FormRequest
{
public function authorize(): true
{
// Return true if the user is authorized to make this request.
// You might want to check if the user is an admin, for example.
return true;
}
public function rules()
{
return [
'audition' => ['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);
}
}

View File

@ -40,4 +40,15 @@ class DrawService
return $auditions->contains(fn ($audition) => $audition->hasFlag('drawn')); 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));
}
} }

View File

@ -0,0 +1,77 @@
<?php
use App\Models\Audition;
use App\Models\Entry;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Log;
uses(RefreshDatabase::class);
it('only allows admin users to manage the draw', function () {
$this->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();
});

View File

@ -2,6 +2,7 @@
use App\Models\Audition; use App\Models\Audition;
use App\Models\Entry; use App\Models\Entry;
use App\Services\DrawService;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class); 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(); $audition = Audition::factory()->create();
actAsAdmin(); actAsAdmin();
// Act & Assert // 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(); expect($audition->hasFlag('drawn'))->toBeTrue();
}); });
it('refuses to draw an audition that has an existing draw', function () { 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')) expect($audition->entries->sortBy('draw_number')->pluck('id'))
->not()->toBe($entries->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();
});