Auditionadmin 29 #40

Merged
okorpheus merged 5 commits from auditionadmin-29 into master 2024-07-17 21:22:23 +00:00
10 changed files with 711 additions and 28 deletions

View File

@ -0,0 +1,94 @@
<?php
namespace App\Actions\Entries;
use App\Exceptions\ManageEntryException;
use App\Models\Audition;
use App\Models\Entry;
use App\Models\Student;
class CreateEntry
{
public function __construct()
{
}
/**
* @throws ManageEntryException
*/
public function __invoke(Student|int $student, Audition|int $audition, string|array|null $entry_for = null): void
{
$this->createEntry($student, $audition, $entry_for);
}
/**
* @throws ManageEntryException
*/
public function createEntry(Student|int $student, Audition|int $audition, string|array|null $entry_for = null)
{
if (is_int($student)) {
$student = Student::find($student);
}
if (is_int($audition)) {
$audition = Audition::find($audition);
}
if (! $entry_for) {
$entry_for = ['seating', 'advancement'];
}
$entry_for = collect($entry_for);
$this->verifySubmission($student, $audition);
$entry = Entry::make([
'student_id' => $student->id,
'audition_id' => $audition->id,
'draw_number' => $this->checkDraw($audition),
'for_seating' => $entry_for->contains('seating'),
'for_advancement' => $entry_for->contains('advancement'),
]);
$entry->save();
return $entry;
}
private function checkDraw(Audition $audition)
{
if (! $audition->hasFlag('drawn')) {
return null;
}
// get the maximum value of draw_number from $audition->entries()
$draw_number = $audition->entries()->max('draw_number');
return $draw_number + 1;
}
/** @noinspection PhpUnhandledExceptionInspection */
private function verifySubmission(?Student $student, ?Audition $audition): void
{
// Make sure it's a valid student
if (! $student || ! $student->exists()) {
throw new ManageEntryException('Invalid student provided');
}
// Make sure the audition is valid
if (! $audition || ! $audition->exists()) {
throw new ManageEntryException('Invalid audition provided');
}
// A student can't enter the same audition twice
if (Entry::where('student_id', $student->id)->where('audition_id', $audition->id)->exists()) {
throw new ManageEntryException('That student is already entered in that audition');
}
// Can't enter a published audition
if ($audition->hasFlag('seats_published')) {
throw new ManageEntryException('Cannot add an entry to an audition where seats are published');
}
if ($audition->hasFlag('advancement_published')) {
throw new ManageEntryException('Cannot add an entry to an audition where advancement is published');
}
// Verify the grade of the student is in range for the audition
if ($student->grade > $audition->maximum_grade) {
throw new ManageEntryException('The grade of the student exceeds the maximum for that audition');
}
if ($student->grade < $audition->minimum_grade) {
throw new ManageEntryException('The grade of the student does not meet the minimum for that audition');
}
}
}

View File

@ -0,0 +1,145 @@
<?php
namespace App\Actions\Entries;
use App\Exceptions\ManageEntryException;
use App\Models\Audition;
use App\Models\Entry;
use function array_key_exists;
class UpdateEntry
{
protected Entry $entry;
public function __construct()
{
}
/**
* @throws ManageEntryException
*/
public function __invoke(Entry $entry, array $updateData): void
{
$this->updateEntry($entry, $updateData);
}
/**
* @throws ManageEntryException
*/
public function updateEntry(Entry|int $entry, array $updateData): void
{
if (is_int($entry)) {
$entry = Entry::find($entry);
}
if (! $entry || ! $entry->exists) {
throw new ManageEntryException('Invalid entry provided');
}
$this->entry = $entry;
if (array_key_exists('for_seating', $updateData)) {
$this->updateForSeating($updateData['for_seating']);
}
if (array_key_exists('for_advancement', $updateData)) {
$this->updateForAdvancement($updateData['for_advancement']);
}
if (array_key_exists('audition_id', $updateData)) {
$this->updateAudition($updateData['audition_id']);
}
if (array_key_exists('audition', $updateData)) {
$this->updateAudition($updateData['audition']);
}
$this->entry->save();
}
/**
* @throws ManageEntryException
*/
private function updateAudition(Audition|int $audition): void
{
if (is_int($audition)) {
$audition = Audition::find($audition);
}
if (! $audition || ! $audition->exists) {
throw new ManageEntryException('Invalid audition provided');
}
if ($this->entry->audition->hasFlag('seats_published')) {
throw new ManageEntryException('Cannot change the audition for an entry where seating for that entry\'s current audition is published');
}
if ($this->entry->audition->hasFlag('advancement_published')) {
throw new ManageEntryException('Cannot change the audition for an entry where advancement for that entry\'s current audition is published');
}
if ($audition->hasFlag('seats_published')) {
throw new ManageEntryException('Cannot change the entry to an audition with published seating');
}
if ($audition->hasFlag('advancement_published')) {
throw new ManageEntryException('Cannot change the entry to an audition with published advancement');
}
if ($this->entry->student->grade > $audition->maximum_grade) {
throw new ManageEntryException('The grade of the student exceeds the maximum for that audition');
}
if ($this->entry->student->grade < $audition->minimum_grade) {
throw new ManageEntryException('The grade of the student does not meet the minimum for that audition');
}
if ($this->entry->scoreSheets()->count() > 0) {
throw new ManageEntryException('Cannot change the audition for an entry with scores');
}
if (Entry::where('student_id', $this->entry->student_id)->where('audition_id', $audition->id)->exists()) {
throw new ManageEntryException('That student is already entered in that audition');
}
// OK we're allowed to change the audition
$this->entry->audition_id = $audition->id;
// Deal with our draw number
if ($audition->hasFlag('drawn')) {
$draw_number = $audition->entries()->max('draw_number');
$this->entry->draw_number = $draw_number + 1;
} else {
$this->entry->draw_number = null;
}
}
/**
* @throws ManageEntryException
*/
private function updateForSeating($forSeating): void
{
if ($this->entry->for_seating == $forSeating) {
return;
}
if ($forSeating) {
if ($this->entry->audition->hasFlag('seats_published')) {
throw new ManageEntryException('Cannot add seating to an entry in an audition where seats are published');
}
$this->entry->for_seating = 1;
} else {
if ($this->entry->audition->hasFlag('seats_published')) {
throw new ManageEntryException('Cannot remove seating from an entry in an audition where seats are published');
}
$this->entry->for_seating = 0;
}
}
/**
* @throws ManageEntryException
*/
private function updateForAdvancement($forAdvancement): void
{
if ($this->entry->for_advancement == $forAdvancement) {
return;
}
if ($forAdvancement) {
if ($this->entry->audition->hasFlag('advancement_published')) {
throw new ManageEntryException('Cannot add advancement to an entry in an audition where advancement is published');
}
$this->entry->for_advancement = 1;
} else {
if ($this->entry->audition->hasFlag('advancement_published')) {
throw new ManageEntryException('Cannot remove advancement from an entry in an audition where advancement is published');
}
$this->entry->for_advancement = 0;
}
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Exceptions;
use Exception;
class ManageEntryException extends Exception
{
}

View File

@ -2,7 +2,10 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Actions\Entries\CreateEntry;
use App\Actions\Entries\UpdateEntry;
use App\Actions\Tabulation\CalculateEntryScore; use App\Actions\Tabulation\CalculateEntryScore;
use App\Exceptions\ManageEntryException;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Audition; use App\Models\Audition;
use App\Models\Entry; use App\Models\Entry;
@ -87,7 +90,7 @@ class EntryController extends Controller
return view('admin.entries.create', ['students' => $students, 'auditions' => $auditions]); return view('admin.entries.create', ['students' => $students, 'auditions' => $auditions]);
} }
public function store(Request $request) public function store(Request $request, CreateEntry $creator)
{ {
if (! Auth::user()->is_admin) { if (! Auth::user()->is_admin) {
abort(403); abort(403);
@ -99,15 +102,21 @@ class EntryController extends Controller
$validData['for_seating'] = $request->get('for_seating') ? 1 : 0; $validData['for_seating'] = $request->get('for_seating') ? 1 : 0;
$validData['for_advancement'] = $request->get('for_advancement') ? 1 : 0; $validData['for_advancement'] = $request->get('for_advancement') ? 1 : 0;
$enter_for = [];
if ($validData['for_seating']) {
$enter_for[] = 'seating';
}
if ($validData['for_advancement']) {
$enter_for[] = 'advancement';
}
Entry::create([ try {
'student_id' => $validData['student_id'], $creator($validData['student_id'], $validData['audition_id'], $enter_for);
'audition_id' => $validData['audition_id'], } catch (ManageEntryException $ex) {
'for_seating' => $validData['for_seating'], return redirect()->route('admin.entries.index')->with('error', $ex->getMessage());
'for_advancement' => $validData['for_advancement'], }
]);
return redirect('/admin/entries'); return redirect(route('admin.entries.index'))->with('success', 'The entry has been added.');
} }
public function edit(Entry $entry, CalculateEntryScore $calculator) public function edit(Entry $entry, CalculateEntryScore $calculator)
@ -126,11 +135,12 @@ class EntryController extends Controller
$auditions = Audition::orderBy('score_order')->get(); $auditions = Audition::orderBy('score_order')->get();
$scores = $entry->scoreSheets()->with('audition', 'judge')->get(); $scores = $entry->scoreSheets()->with('audition', 'judge')->get();
$scores->each(fn ($score) => $score->entry = $entry); $scores->each(fn ($score) => $score->entry = $entry);
// return view('admin.entries.edit', ['entry' => $entry, 'students' => $students, 'auditions' => $auditions]); // return view('admin.entries.edit', ['entry' => $entry, 'students' => $students, 'auditions' => $auditions]);
return view('admin.entries.edit', compact('entry', 'students', 'auditions', 'scores')); return view('admin.entries.edit', compact('entry', 'students', 'auditions', 'scores'));
} }
public function update(Request $request, Entry $entry) public function update(Request $request, Entry $entry, UpdateEntry $updater)
{ {
if ($entry->audition->hasFlag('seats_published')) { if ($entry->audition->hasFlag('seats_published')) {
return to_route('admin.entries.index')->with('error', return to_route('admin.entries.index')->with('error',
@ -148,15 +158,21 @@ class EntryController extends Controller
$validData['for_seating'] = $request->get('for_seating') ? 1 : 0; $validData['for_seating'] = $request->get('for_seating') ? 1 : 0;
$validData['for_advancement'] = $request->get('for_advancement') ? 1 : 0; $validData['for_advancement'] = $request->get('for_advancement') ? 1 : 0;
// If the audition is not set to advance to the next round, then the entry must be for seating
if (! auditionSetting('advanceTo')) { if (! auditionSetting('advanceTo')) {
$validData['for_seating'] = 1; $validData['for_seating'] = 1;
} }
try {
$updater($entry, $validData);
} catch (ManageEntryException $e) {
return redirect()->route('admin.entries.index')->with('error', $e->getMessage());
}
$entry->update([ // $entry->update([
'audition_id' => $validData['audition_id'], // 'audition_id' => $validData['audition_id'],
'for_seating' => $validData['for_seating'], // 'for_seating' => $validData['for_seating'],
'for_advancement' => $validData['for_advancement'], // 'for_advancement' => $validData['for_advancement'],
]); // ]);
return to_route('admin.entries.index')->with('success', 'Entry updated successfully'); return to_route('admin.entries.index')->with('success', 'Entry updated successfully');
} }

View File

@ -2,6 +2,8 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Actions\Entries\CreateEntry;
use App\Exceptions\ManageEntryException;
use App\Models\Audition; use App\Models\Audition;
use App\Models\Entry; use App\Models\Entry;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -25,7 +27,7 @@ class EntryController extends Controller
return view('entries.index', ['entries' => $entries, 'students' => $students, 'auditions' => $auditions]); return view('entries.index', ['entries' => $entries, 'students' => $students, 'auditions' => $auditions]);
} }
public function store(Request $request) public function store(Request $request, CreateEntry $creator)
{ {
if ($request->user()->cannot('create', Entry::class)) { if ($request->user()->cannot('create', Entry::class)) {
abort(403); abort(403);
@ -34,18 +36,28 @@ class EntryController extends Controller
'student_id' => ['required', 'exists:students,id'], 'student_id' => ['required', 'exists:students,id'],
'audition_id' => ['required', 'exists:auditions,id'], 'audition_id' => ['required', 'exists:auditions,id'],
]); ]);
$audition = Audition::find($validData['audition_id']);
if ($audition->entry_deadline < now()) {
return redirect()->route('entries.index')->with('error', 'The entry deadline for that audition has passed');
}
$validData['for_seating'] = $request->get('for_seating') ? 1 : 0; $validData['for_seating'] = $request->get('for_seating') ? 1 : 0;
$validData['for_advancement'] = $request->get('for_advancement') ? 1 : 0; $validData['for_advancement'] = $request->get('for_advancement') ? 1 : 0;
$enter_for = [];
if ($validData['for_seating']) {
$enter_for[] = 'seating';
}
if ($validData['for_advancement']) {
$enter_for[] = 'advancement';
}
$entry = Entry::create([ try {
'student_id' => $validData['student_id'], $creator($validData['student_id'], $validData['audition_id'], $enter_for);
'audition_id' => $validData['audition_id'], } catch (ManageEntryException $ex) {
'for_seating' => $validData['for_seating'], return redirect()->route('entries.index')->with('error', $ex->getMessage());
'for_advancement' => $validData['for_advancement'], }
]);
return redirect('/entries'); return redirect()->route('entries.index')->with('success', 'The entry has been added.');
} }
public function destroy(Request $request, Entry $entry) public function destroy(Request $request, Entry $entry)

View File

@ -0,0 +1,127 @@
<?php
use App\Actions\Entries\CreateEntry;
use App\Exceptions\ManageEntryException;
use App\Models\Audition;
use App\Models\Entry;
use App\Models\Student;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\App;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->createEntry = App::make(CreateEntry::class);
});
it('throws an exception if the student does not exist', function () {
$audition = Audition::factory()->create();
$student = Student::factory()->make();
$this->createEntry->__invoke($student, $audition);
})->throws(ManageEntryException::class, 'Invalid student provided');
it('throws an exception if the audition does not exist', function () {
$audition = Audition::factory()->make();
$student = Student::factory()->create();
$this->createEntry->__invoke($student, $audition);
})->throws(ManageEntryException::class, 'Invalid audition provided');
it('throws an exception if the student is already entered in the audition', function () {
// Arrange
$audition = Audition::factory()->create();
$student = Student::factory()->create();
$entry = Entry::create([
'student_id' => $student->id,
'audition_id' => $audition->id,
]);
// Act & Assert
$this->createEntry->createEntry($student, $audition);
})->throws(ManageEntryException::class, 'That student is already entered in that audition');
it('throws an exception if seats are published for the audition', function () {
// Arrange
$audition = Audition::factory()->create();
$student = Student::factory()->create();
$audition->addFlag('seats_published');
// Act & Assert
$this->createEntry->createEntry($student, $audition);
})->throws(ManageEntryException::class, 'Cannot add an entry to an audition where seats are published');
it('throws an exception if advancement is published for the audition', function () {
// Arrange
$audition = Audition::factory()->create();
$student = Student::factory()->create();
$audition->addFlag('advancement_published');
// Act & Assert
$this->createEntry->createEntry($student, $audition);
})->throws(ManageEntryException::class, 'Cannot add an entry to an audition where advancement is published');
it('throws an exception if the grade of the student exceeds the maximum grade for the audition', function () {
// Arrange
$audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]);
$student = Student::factory()->create(['grade' => 11]);
// Act & Assert
$this->createEntry->createEntry($student, $audition);
})->throws(ManageEntryException::class, 'The grade of the student exceeds the maximum for that audition');
it('throws an exception if the grade of the student does not meet the minimum grade for the audition', function () {
// Arrange
$audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]);
$student = Student::factory()->create(['grade' => 7]);
// Act & Assert
$this->createEntry->createEntry($student, $audition);
})->throws(ManageEntryException::class, 'The grade of the student does not meet the minimum for that audition');
it('returns an entry object', function () {
// Arrange
$audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]);
$student = Student::factory()->create(['grade' => 8]);
// Act & Assert
$entry = $this->createEntry->createEntry($student, $audition);
expect($entry instanceof Entry)->toBeTrue();
});
it('creates an entry with a null draw number if the audition is not drawn', function () {
$audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]);
$student = Student::factory()->create(['grade' => 8]);
// Act & Assert
$entry = $this->createEntry->createEntry($student, $audition);
expect($entry->draw_number)->toBeNull();
});
it('assigns the next highest draw_number available if the audition is drawn', function () {
$audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]);
$student = Student::factory()->create(['grade' => 8]);
$audition->addFlag('drawn');
foreach (range(1, 5) as $number) {
Entry::factory()->create([
'audition_id' => $audition->id,
'draw_number' => $number,
]);
}
$entry = $this->createEntry->createEntry($student, $audition);
expect($entry->draw_number)->toBe(6);
});
it('makes the entry for both seating and advancement if nothing is specified', function () {
$audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]);
$student = Student::factory()->create(['grade' => 8]);
// Act & Assert
$entry = $this->createEntry->createEntry($student, $audition);
expect($entry->for_seating)->toBeTrue()
->and($entry->for_advancement)->toBeTrue();
});
it('makes the entry for only seating if only seating is specified', function () {
$audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]);
$student = Student::factory()->create(['grade' => 8]);
// Act & Assert
$entry = $this->createEntry->createEntry($student, $audition, 'seating');
expect($entry->for_seating)->toBeTrue()
->and($entry->for_advancement)->toBeFalse();
});
it('makes the entry for only advancement if only advancement is specified', function () {
$audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]);
$student = Student::factory()->create(['grade' => 8]);
// Act & Assert
$entry = $this->createEntry->createEntry($student, $audition, 'advancement');
expect($entry->for_seating)->toBeFalse()
->and($entry->for_advancement)->toBeTrue();
});
it('makes the entry for both seating and advancement if both are specified', function () {
$audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]);
$student = Student::factory()->create(['grade' => 8]);
// Act & Assert
$entry = $this->createEntry->createEntry($student, $audition, ['seating', 'advancement']);
expect($entry->for_seating)->toBeTrue()
->and($entry->for_advancement)->toBeTrue();
});

View File

@ -0,0 +1,219 @@
<?php
/** @noinspection PhpUnhandledExceptionInspection */
use App\Actions\Entries\UpdateEntry;
use App\Exceptions\ManageEntryException;
use App\Models\Audition;
use App\Models\Entry;
use App\Models\ScoreSheet;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\App;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->updater = App::make(UpdateEntry::class);
});
it('throws an error if an invalid entry is provided', function () {
$this->updater->updateEntry(2, []);
})->throws(ManageEntryException::class, 'Invalid entry provided');
it('throws an error if we try to remove for_seating while seating is published', function () {
// Arrange
$entry = Entry::factory()->create();
$entry->audition->addFlag('seats_published');
$data = ['for_seating' => 0];
// Act & Assert
$this->updater->updateEntry($entry, $data);
})->throws('Cannot remove seating from an entry in an audition where seats are published');
it('throws an error if we try to add for_seating while seating is published', function () {
// Arrange
$entry = Entry::factory()->advanceOnly()->create();
$entry->audition->addFlag('seats_published');
$data = ['for_seating' => 1];
// Act & Assert
$this->updater->updateEntry($entry, $data);
})->throws('Cannot add seating to an entry in an audition where seats are published');
it('allows us to remove for_seating if seating is not published', function () {
// Arrange
$entry = Entry::factory()->create();
$data = ['for_seating' => 0];
// Act
$this->updater->updateEntry($entry, $data);
// Assert
$this->assertDatabaseHas('entries', ['id' => $entry->id, 'for_seating' => 0]);
});
it('allows us to add for_seating if seating is not published', function () {
// Arrange
$entry = Entry::factory()->advanceOnly()->create();
$data = ['for_seating' => 1];
// Act
$this->updater->updateEntry($entry, $data);
// Assert
$this->assertDatabaseHas('entries', ['id' => $entry->id, 'for_seating' => 1]);
});
it('throws an error if we try to remove for_advancement while seating is published', function () {
// Arrange
$entry = Entry::factory()->create();
$entry->audition->addFlag('advancement_published');
$data = ['for_advancement' => 0];
// Act & Assert
$this->updater->updateEntry($entry, $data);
})->throws('Cannot remove advancement from an entry in an audition where advancement is published');
it('throws an error if we try to add for_advancement while advancement is published', function () {
// Arrange
$entry = Entry::factory()->seatingOnly()->create();
$entry->audition->addFlag('advancement_published');
$data = ['for_advancement' => 1];
// Act & Assert
$this->updater->updateEntry($entry, $data);
})->throws('Cannot add advancement to an entry in an audition where advancement is published');
it('allows us to remove for_advancement if advancement is not published', function () {
// Arrange
$entry = Entry::factory()->create();
$data = ['for_advancement' => 0];
// Act
$this->updater->updateEntry($entry, $data);
// Assert
$this->assertDatabaseHas('entries', ['id' => $entry->id, 'for_advancement' => 0]);
});
it('allows us to add for_advancement if advancement is not published', function () {
// Arrange
$entry = Entry::factory()->seatingOnly()->create();
$data = ['for_advancement' => 1];
// Act
$this->updater->updateEntry($entry, $data);
// Assert
$this->assertDatabaseHas('entries', ['id' => $entry->id, 'for_advancement' => 1]);
});
it('throws an exception if an attempt to change to an invalid audition is made', function () {
$entry = Entry::factory()->create();
$data = ['audition' => 2];
$this->updater->updateEntry($entry, $data);
})->throws(ManageEntryException::class, 'Invalid audition provided');
it('cannot change auditions if our current audition advancement is published', function () {
$entry = Entry::factory()->create();
$entry->audition->addFlag('advancement_published');
$otherAudition = Audition::factory()->create();
$data = ['audition' => $otherAudition];
// Act
$this->updater->updateEntry($entry, $data);
})->throws(ManageEntryException::class,
'Cannot change the audition for an entry where advancement for that entry\'s current audition is published');
it('cannot change auditions if our current audition seating is published', function () {
$entry = Entry::factory()->create();
$entry->audition->addFlag('seats_published');
$otherAudition = Audition::factory()->create();
$data = ['audition' => $otherAudition];
// Act
$this->updater->updateEntry($entry, $data);
})->throws(ManageEntryException::class,
'Cannot change the audition for an entry where seating for that entry\'s current audition is published');
it('cannot change auditions if our proposed audition advancement is published', function () {
$entry = Entry::factory()->create();
$otherAudition = Audition::factory()->create();
$otherAudition->addFlag('advancement_published');
$data = ['audition' => $otherAudition];
// Act
$this->updater->updateEntry($entry, $data);
})->throws(ManageEntryException::class, 'Cannot change the entry to an audition with published advancement');
it('cannot change auditions if our proposed audition seating is published', function () {
$entry = Entry::factory()->create();
$otherAudition = Audition::factory()->create();
$otherAudition->addFlag('seats_published');
$data = ['audition' => $otherAudition];
// Act
$this->updater->updateEntry($entry, $data);
})->throws(ManageEntryException::class, 'Cannot change the entry to an audition with published seating');
it('will not let us switch to an audition that is too old for the student', function () {
$entry = Entry::factory()->create();
$otherAudition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]);
$entry->student->update(['grade' => 7]);
$data = ['audition' => $otherAudition];
$this->updater->updateEntry($entry, $data);
})->throws(ManageEntryException::class, 'The grade of the student does not meet the minimum for that audition');
it('will not let us switch to an audition that is too young for the student', function () {
$entry = Entry::factory()->create();
$otherAudition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]);
$entry->student->update(['grade' => 11]);
$data = ['audition' => $otherAudition];
$this->updater->updateEntry($entry, $data);
})->throws(ManageEntryException::class, 'The grade of the student exceeds the maximum for that audition');
it('will not let us change auditions for an entry with scores', function () {
$entry = Entry::factory()->create();
$judge = User::factory()->create();
ScoreSheet::create([
'entry_id' => $entry->id,
'user_id' => $judge->id,
'subscores' => 100,
]);
$otherAudition = Audition::factory()->create([
'minimum_grade' => $entry->student->grade, 'maximum_grade' => $entry->student->grade,
]);
$data = ['audition' => $otherAudition];
$this->updater->updateEntry($entry, $data);
})->throws(ManageEntryException::class, 'Cannot change the audition for an entry with scores');
it('will not let us change to an audition that the student is already entered in', function () {
$entry = Entry::factory()->create();
$otherAudition = Audition::factory()->create([
'minimum_grade' => $entry->student->grade, 'maximum_grade' => $entry->student->grade,
]);
Entry::create([
'student_id' => $entry->student->id,
'audition_id' => $otherAudition->id,
]);
$data = ['audition' => $otherAudition];
$this->updater->updateEntry($entry, $data);
})->throws(ManageEntryException::class, 'That student is already entered in that audition');
it('allows us to switch auditions', function () {
// Arrange
$entry = Entry::factory()->create();
$originalAudition = $entry->audition;
$otherAudition = Audition::factory()->create([
'minimum_grade' => $entry->student->grade, 'maximum_grade' => $entry->student->grade,
]);
$data = ['audition' => $otherAudition];
// Act & Assert
$this->updater->updateEntry($entry, $data);
$this->assertDatabaseHas('entries', ['id' => $entry->id, 'audition_id' => $otherAudition->id]);
$this->assertDatabaseMissing('entries',
['student_id' => $entry->student->id, 'audition_id' => $originalAudition->id]);
});
it('sets the draw number null if the new audition is not drawn', function () {
// Arrange
$entry = Entry::factory()->create(['draw_number' => 3]);
$newAudition = Audition::factory()->create([
'minimum_grade' => $entry->student->grade, 'maximum_grade' => $entry->student->grade,
]);
$data = ['audition' => $newAudition];
// Act
$this->updater->updateEntry($entry, $data);
// Assert
$this->assertDatabaseHas('entries', ['id' => $entry->id, 'draw_number' => null]);
});
it('sets the draw number last if the new audition is not drawn', function () {
// Arrange
$entry = Entry::factory()->create(['draw_number' => 3]);
$newAudition = Audition::factory()->create([
'minimum_grade' => $entry->student->grade, 'maximum_grade' => $entry->student->grade,
]);
$newAudition->addFlag('drawn');
foreach (range(1, 6) as $dn) {
Entry::factory()->create(['audition_id' => $newAudition->id, 'draw_number' => $dn]);
}
$data = ['audition' => $newAudition];
// Act
$this->updater->updateEntry($entry, $data);
// Assert
$this->assertDatabaseHas('entries', ['id' => $entry->id, 'draw_number' => 7]);
});

View File

@ -4,6 +4,7 @@ use App\Models\Audition;
use App\Models\Student; use App\Models\Student;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use function Pest\Laravel\assertDatabaseHas;
use function Pest\Laravel\get; use function Pest\Laravel\get;
use function PHPUnit\Framework\assertEquals; use function PHPUnit\Framework\assertEquals;
@ -19,7 +20,7 @@ it('does not respond to a guest', function () {
get(route('admin.entries.create')) get(route('admin.entries.create'))
->assertRedirect(route('home')); ->assertRedirect(route('home'));
}); });
it('passes a collection of all students with thier schools to the view', function () { it('passes a collection of all students with their schools to the view', function () {
// Arrange // Arrange
Student::factory()->count(8)->create(); Student::factory()->count(8)->create();
$students = Student::with('school')->orderBy('last_name')->orderBy('first_name')->get(); $students = Student::with('school')->orderBy('last_name')->orderBy('first_name')->get();
@ -47,3 +48,42 @@ it('passes a collection of available auditions to the view', function () {
$response->assertOk(); $response->assertOk();
assertEquals(array_values($auditions), array_values($viewAuditions)); assertEquals(array_values($auditions), array_values($viewAuditions));
}); });
it('can create an entry', function () {
$audition = Audition::factory()->create(['maximum_grade' => 12, 'minimum_grade' => 7]);
$student = Student::factory()->create(['grade' => 9]);
actAsAdmin();
$response = $this->post(route('admin.entries.store'), [
'student_id' => $student->id,
'audition_id' => $audition->id,
'for_seating' => 'on',
]);
$response->assertRedirect(route('admin.entries.index'))
->assertSessionDoesntHaveErrors()
->assertSessionHas('success', 'The entry has been added.');
assertDatabaseHas('entries', [
'student_id' => $student->id,
'audition_id' => $audition->id,
'for_seating' => 1,
'for_advancement' => 0,
]);
});
it('can create a late entry', function () {
$audition = Audition::factory()->closed()->create(['maximum_grade' => 12, 'minimum_grade' => 7]);
$student = Student::factory()->create(['grade' => 9]);
actAsAdmin();
$response = $this->post(route('admin.entries.store'), [
'student_id' => $student->id,
'audition_id' => $audition->id,
'for_seating' => 'on',
]);
$response->assertRedirect(route('admin.entries.index'))
->assertSessionDoesntHaveErrors()
->assertSessionMissing('error')
->assertSessionHas('success', 'The entry has been added.');
assertDatabaseHas('entries', [
'student_id' => $student->id,
'audition_id' => $audition->id,
'for_seating' => 1,
'for_advancement' => 0,
]);
});

View File

@ -137,12 +137,13 @@ it('does not let a normal user update an entry', function () {
}); });
it('allows an admin to update an entry', function () { it('allows an admin to update an entry', function () {
// Arrange // Arrange
$newAudition = Audition::factory()->create(); $newAudition = Audition::factory()->create(['minimum_grade' => 1, 'maximum_grade' => 20]);
actAsAdmin(); actAsAdmin();
// Act & Assert // Act & Assert
/** @noinspection PhpUnhandledExceptionInspection */ /** @noinspection PhpUnhandledExceptionInspection */
patch(route('admin.entries.update', $this->entry), ['audition_id' => $newAudition->id]) patch(route('admin.entries.update', $this->entry), ['audition_id' => $newAudition->id])
->assertSessionHasNoErrors() ->assertSessionHasNoErrors()
->assertSessionMissing('error')
->assertSessionHas('success', 'Entry updated successfully') ->assertSessionHas('success', 'Entry updated successfully')
->assertRedirect(route('admin.entries.index')); ->assertRedirect(route('admin.entries.index'));
$this->entry->refresh(); $this->entry->refresh();
@ -221,7 +222,7 @@ it('displays scores', function () {
$response->assertSee($subscore->name); $response->assertSee($subscore->name);
} }
}); });
it('has a link to delete scores', function() { it('has a link to delete scores', function () {
// Arrange // Arrange
$sg = ScoringGuide::factory()->create(); $sg = ScoringGuide::factory()->create();
SubscoreDefinition::factory()->count(5)->create(['scoring_guide_id' => $sg->id]); SubscoreDefinition::factory()->count(5)->create(['scoring_guide_id' => $sg->id]);
@ -236,7 +237,7 @@ it('has a link to delete scores', function() {
$scoreSheet = ScoreSheet::where('entry_id', $entry->id)->first(); $scoreSheet = ScoreSheet::where('entry_id', $entry->id)->first();
actAsAdmin(); actAsAdmin();
$response = get(route('admin.entries.edit', $entry)) $response = get(route('admin.entries.edit', $entry))
->assertSee(route('scores.destroy', ['score'=>$scoreSheet])); ->assertSee(route('scores.destroy', ['score' => $scoreSheet]));
}); });
// Delete tests // Delete tests

View File

@ -9,6 +9,7 @@ use App\Settings;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use function Pest\Laravel\actingAs; use function Pest\Laravel\actingAs;
use function Pest\Laravel\assertDatabaseMissing;
use function Pest\Laravel\delete; use function Pest\Laravel\delete;
use function Pest\Laravel\get; use function Pest\Laravel\get;
use function Pest\Laravel\post; use function Pest\Laravel\post;
@ -133,8 +134,8 @@ it('shows appropriate flags for entry types when advancement is enabled', functi
it('accepts a valid entry', function () { it('accepts a valid entry', function () {
// Arrange // Arrange
$student = Student::factory()->create(['school_id' => $this->school->id]); $student = Student::factory()->create(['school_id' => $this->school->id, 'grade' => 8]);
$audition = Audition::factory()->create(); $audition = Audition::factory()->create(['maximum_grade' => 9, 'minimum_grade' => 7]);
// Act & Assert // Act & Assert
actingAs($this->user); actingAs($this->user);
$response = post(route('entries.store'), [ $response = post(route('entries.store'), [
@ -144,6 +145,7 @@ it('accepts a valid entry', function () {
/** @noinspection PhpUnhandledExceptionInspection */ /** @noinspection PhpUnhandledExceptionInspection */
$response->assertSessionHasNoErrors(); $response->assertSessionHasNoErrors();
$response->assertRedirect(route('entries.index')); $response->assertRedirect(route('entries.index'));
$response->assertSessionHas('success', 'The entry has been added.');
$this->assertDatabaseHas('entries', [ $this->assertDatabaseHas('entries', [
'student_id' => $student->id, 'student_id' => $student->id,
'audition_id' => $audition->id, 'audition_id' => $audition->id,
@ -179,3 +181,21 @@ it('shows entry type checkboxes only when advancement is enabled', function () {
get(route('entries.index')) get(route('entries.index'))
->assertDontSee('Enter for'); ->assertDontSee('Enter for');
}); });
it('denies an entry that is late', function () {
// Arrnge
$student = Student::factory()->create(['school_id' => $this->school->id, 'grade' => 8]);
$audition = Audition::factory()->closed()->create(['maximum_grade' => 9, 'minimum_grade' => 7]);
$user = User::factory()->create(['school_id' => $student->school_id]);
actingAs($user);
// Act & Assert
$response = post(route('entries.store'), [
'student_id' => $student->id,
'audition_id' => $audition->id,
]);
assertDatabaseMissing('entries', [
'student_id' => $student->id,
'audition_id' => $audition->id,
]);
$response->assertRedirect(route('entries.index'))
->assertSessionHas('error', 'The entry deadline for that audition has passed');
});