Finish DoublerDecision tests

This commit is contained in:
Matt Young 2025-07-01 00:18:52 -05:00
parent db22918ff8
commit 5ff3785f9f
6 changed files with 240 additions and 15 deletions

View File

@ -0,0 +1,28 @@
<?php
namespace App\Actions\Development;
use App\Actions\Tabulation\EnterScore;
use App\Models\Entry;
class FakeScoresForEntry
{
public function __construct()
{
}
public function __invoke(Entry $entry): void
{
$scoreScribe = app(EnterScore::class);
$scoringGuide = $entry->audition->scoringGuide;
$subscores = $scoringGuide->subscores;
$judges = $entry->audition->judges;
foreach ($judges as $judge) {
$scoringArray = [];
foreach ($subscores as $subscore) {
$scoringArray[$subscore->id] = mt_rand(0, $subscore->max_score);
}
$scoreScribe($judge, $entry, $scoringArray);
}
}
}

View File

@ -28,7 +28,7 @@ class CreateEntry
/**
* @throws ManageEntryException
*/
public function createEntry(Student|int $student, Audition|int $audition, string|array|null $entry_for = null)
public function createEntry(Student|int $student, Audition|int $audition, string|array|null $entry_for = null): Entry
{
if (is_int($student)) {
$student = Student::find($student);

View File

@ -27,11 +27,6 @@ class DoublerDecision
'decline' => $this->decline($entry),
default => throw new AuditionAdminException('Invalid decision specified')
};
if ($decision != 'accept' && $decision != 'decline') {
throw new AuditionAdminException('Invalid decision specified');
}
}
/**
@ -56,9 +51,6 @@ class DoublerDecision
if ($entry->audition->hasFlag('seats_published')) {
throw new AuditionAdminException('Cannot accept an entry in an audition where seats are published');
}
if ($entry->audition->hasFlag('advancement_published')) {
throw new AuditionAdminException('Cannot accept an entry in an audition where advancement is published');
}
Cache::forget('rank_seating_'.$entry->audition_id);
// Process student entries
@ -87,6 +79,13 @@ class DoublerDecision
if ($entry->hasFlag('declined')) {
throw new AuditionAdminException('Entry '.$entry->id.' is already declined');
}
if (! $entry->totalScore) {
throw new AuditionAdminException('Cannot decline an unscored entry');
}
if ($entry->audition->hasFlag('seats_published')) {
throw new AuditionAdminException('Cannot decline an entry in an audition where seats are published');
}
// Flag this entry
$entry->addFlag('declined');

View File

@ -2,6 +2,7 @@
namespace Database\Factories;
use App\Models\SubscoreDefinition;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
@ -20,4 +21,18 @@ class ScoringGuideFactory extends Factory
'name' => $this->faker->sentence(3),
];
}
/**
* Configure the model factory.
*
* @return $this
*/
public function configure()
{
return $this->afterCreating(function ($scoringGuide) {
SubscoreDefinition::factory()
->count(5)
->create(['scoring_guide_id' => $scoringGuide->id]);
});
}
}

View File

@ -17,10 +17,13 @@ class SubscoreDefinitionFactory extends Factory
*/
public function definition(): array
{
$sg = ScoringGuide::factory()->create();
return [
'scoring_guide_id' => $sg->id,
'scoring_guide_id' => function (array $attributes) {
return array_key_exists('scoring_guide_id', $attributes)
? $attributes['scoring_guide_id']
: ScoringGuide::factory()->create()->id;
},
'name' => $this->faker->word,
'max_score' => 100,
'weight' => $this->faker->numberBetween(1, 4),
@ -30,6 +33,7 @@ class SubscoreDefinitionFactory extends Factory
'for_advance' => 1,
];
}
public function seatingOnly(): self
{
return $this->state(
@ -57,5 +61,4 @@ class SubscoreDefinitionFactory extends Factory
fn (array $attributes) => ['tiebreak_order' => 0]
);
}
}

View File

@ -1,11 +1,191 @@
<?php
/** @noinspection PhpUnhandledExceptionInspection */
use App\Actions\Development\FakeScoresForEntry;
use App\Actions\Entries\CreateEntry;
use App\Actions\Entries\DoublerDecision;
use App\Exceptions\AuditionAdminException;
use App\Models\Audition;
use App\Models\Doubler;
use App\Models\Room;
use App\Models\ScoringGuide;
use App\Models\Student;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
test('', function () {
$response = $this->get('/');
beforeEach(function () {
$this->decider = app(DoublerDecision::class);
$this->entryScribe = app(CreateEntry::class);
$this->scoreFaker = app(FakeScoresForEntry::class);
$response->assertStatus(200);
// Setup doubler
$this->scoringGuide = ScoringGuide::factory()->create();
$this->judge1 = User::factory()->create();
$this->judge2 = User::factory()->create();
$this->room = Room::factory()->create();
$this->room->addJudge($this->judge1);
$this->room->addJudge($this->judge2);
$this->audition1 = Audition::factory()->create(
[
'minimum_grade' => 9, 'maximum_grade' => 12,
'scoring_guide_id' => $this->scoringGuide->id,
'room_id' => $this->room->id,
'order_in_room' => 1,
'name' => 'Flute',
]);
$this->audition2 = Audition::factory()->create(
[
'minimum_grade' => 9, 'maximum_grade' => 12,
'scoring_guide_id' => $this->scoringGuide->id,
'room_id' => $this->room->id,
'order_in_room' => 2,
'event_id' => $this->audition1->event_id,
'name' => 'Trumpet',
]);
$this->audition3 = Audition::factory()->create(
[
'minimum_grade' => 9, 'maximum_grade' => 12,
'scoring_guide_id' => $this->scoringGuide->id,
'room_id' => $this->room->id,
'order_in_room' => 3,
'event_id' => $this->audition1->event_id,
'name' => 'Trombone',
]);
$this->otherEventAudition = Audition::factory()->create([
'minimum_grade' => 9, 'maximum_grade' => 12,
'scoring_guide_id' => $this->scoringGuide->id,
'room_id' => $this->room->id,
'order_in_room' => 4,
'name' => 'Jazz Trumpet',
]);
$this->student = Student::factory()->create([
'grade' => 9, 'first_name' => 'Percy',
'last_name' => 'Grainger',
]);
$this->entry1 = $this->entryScribe->createEntry($this->student, $this->audition1);
$this->entry2 = $this->entryScribe->createEntry($this->student, $this->audition2);
$this->entry3 = $this->entryScribe->createEntry($this->student, $this->audition3);
$this->otherEventEntry = $this->entryScribe->createEntry($this->student, $this->otherEventAudition);
});
it('is invokable', function () {
($this->scoreFaker)($this->entry1);
($this->scoreFaker)($this->entry2);
($this->scoreFaker)($this->entry3);
($this->decider)($this->entry2, 'decline');
expect($this->entry2->hasFlag('declined'))->toBeTruthy();
});
it('cannot be called with an invalid decision', function () {
($this->scoreFaker)($this->entry1);
($this->scoreFaker)($this->entry2);
($this->scoreFaker)($this->entry3);
($this->decider)($this->entry2, 'idunno');
})->throws(AuditionAdminException::class, 'Invalid decision specified');
it('can decline an entry', function () {
($this->scoreFaker)($this->entry1);
($this->scoreFaker)($this->entry2);
($this->scoreFaker)($this->entry3);
$this->decider->decline($this->entry2);
expect($this->entry2->hasFlag('declined'))->toBeTruthy();
});
it('will not decline an entry with no scores', function () {
$this->decider->decline($this->entry2);
})->throws(AuditionAdminException::class, 'Cannot decline an unscored entry');
it('will not decline an entry that is already declined', function () {
($this->scoreFaker)($this->entry2);
$this->entry2->addFlag('declined');
$this->decider->decline($this->entry2);
})->throws(AuditionAdminException::class, 'Entry 2 is already declined');
it('will not decline an entry in a published event', function () {
($this->scoreFaker)($this->entry2);
$this->audition2->addFlag('seats_published');
$this->entry2->refresh();
$this->decider->decline($this->entry2);
})->throws(AuditionAdminException::class, 'Cannot decline an entry in an audition where seats are published');
it('accepts an entry and declines others in the same event', function () {
($this->scoreFaker)($this->entry1);
($this->scoreFaker)($this->entry2);
($this->scoreFaker)($this->entry3);
$this->decider->accept($this->entry2);
$this->entry1->refresh();
$this->entry2->refresh();
$this->entry3->refresh();
expect($this->entry1->hasFlag('declined'))->toBeTruthy()
->and($this->entry3->hasFlag('declined'))->toBeTruthy();
$doubler = Doubler::findDoubler($this->entry2->student_id, $this->audition2->event_id);
expect($doubler->accepted_entry)->toBe($this->entry2->id);
});
it('will not accept an entry into an event with seats published', function () {
($this->scoreFaker)($this->entry1);
($this->scoreFaker)($this->entry2);
($this->scoreFaker)($this->entry3);
$this->audition2->addFlag('seats_published');
$this->audition2->refresh();
$this->entry2->refresh();
$this->decider->accept($this->entry2);
$this->entry1->refresh();
$this->entry2->refresh();
$this->entry3->refresh();
expect($this->entry1->hasFlag('declined'))->toBeTruthy()
->and($this->entry3->hasFlag('declined'))->toBeTruthy();
$doubler = Doubler::findDoubler($this->entry2->student_id, $this->audition2->event_id);
expect($doubler->accepted_entry)->toBe($this->entry2->id);
})->throws(AuditionAdminException::class, 'Cannot accept an entry in an audition where seats are published');
it('will not accept an entry that has already been declined', function () {
($this->scoreFaker)($this->entry1);
($this->scoreFaker)($this->entry2);
($this->scoreFaker)($this->entry3);
$this->entry2->addFlag('declined');
$this->decider->accept($this->entry2);
$this->entry1->refresh();
$this->entry2->refresh();
$this->entry3->refresh();
expect($this->entry1->hasFlag('declined'))->toBeTruthy()
->and($this->entry3->hasFlag('declined'))->toBeTruthy();
$doubler = Doubler::findDoubler($this->entry2->student_id, $this->audition2->event_id);
expect($doubler->accepted_entry)->toBe($this->entry2->id);
})->throws(AuditionAdminException::class, 'Entry 2 is already declined');
it('when accepting a seat, does not decline no_show or failed_prelim entries', function () {
($this->scoreFaker)($this->entry2);
$this->entry1->addFlag('no_show');
$this->entry3->addFlag('failed_prelim');
$this->decider->accept($this->entry2);
$this->entry1->refresh();
$this->entry2->refresh();
$this->entry3->refresh();
expect($this->entry1->hasFlag('declined'))->toBeFalsy()
->and($this->entry3->hasFlag('declined'))->toBeFalsy();
$doubler = Doubler::findDoubler($this->entry2->student_id, $this->audition2->event_id);
expect($doubler->accepted_entry)->toBe($this->entry2->id);
});
it('when accepting an entry, does not decline seats in other events', function () {
($this->scoreFaker)($this->entry1);
($this->scoreFaker)($this->entry2);
($this->scoreFaker)($this->entry3);
$this->decider->accept($this->entry2);
$this->entry1->refresh();
$this->entry2->refresh();
$this->entry3->refresh();
expect($this->otherEventEntry->hasFlag('declined'))->toBeFalsy();
});
it('will not accept an entry if the student has unscored entries', function () {
$this->decider->accept($this->entry2);
})->throws(AuditionAdminException::class,
'Cannot accept seating for Percy Grainger because student has unscored entries');