Finish DoublerDecision tests
This commit is contained in:
parent
db22918ff8
commit
5ff3785f9f
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -28,7 +28,7 @@ class CreateEntry
|
||||||
/**
|
/**
|
||||||
* @throws ManageEntryException
|
* @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)) {
|
if (is_int($student)) {
|
||||||
$student = Student::find($student);
|
$student = Student::find($student);
|
||||||
|
|
|
||||||
|
|
@ -27,11 +27,6 @@ class DoublerDecision
|
||||||
'decline' => $this->decline($entry),
|
'decline' => $this->decline($entry),
|
||||||
default => throw new AuditionAdminException('Invalid decision specified')
|
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')) {
|
if ($entry->audition->hasFlag('seats_published')) {
|
||||||
throw new AuditionAdminException('Cannot accept an entry in an audition where seats are 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);
|
Cache::forget('rank_seating_'.$entry->audition_id);
|
||||||
|
|
||||||
// Process student entries
|
// Process student entries
|
||||||
|
|
@ -87,6 +79,13 @@ class DoublerDecision
|
||||||
if ($entry->hasFlag('declined')) {
|
if ($entry->hasFlag('declined')) {
|
||||||
throw new AuditionAdminException('Entry '.$entry->id.' is already 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
|
// Flag this entry
|
||||||
$entry->addFlag('declined');
|
$entry->addFlag('declined');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace Database\Factories;
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\SubscoreDefinition;
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -20,4 +21,18 @@ class ScoringGuideFactory extends Factory
|
||||||
'name' => $this->faker->sentence(3),
|
'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]);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,13 @@ class SubscoreDefinitionFactory extends Factory
|
||||||
*/
|
*/
|
||||||
public function definition(): array
|
public function definition(): array
|
||||||
{
|
{
|
||||||
$sg = ScoringGuide::factory()->create();
|
|
||||||
|
|
||||||
return [
|
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,
|
'name' => $this->faker->word,
|
||||||
'max_score' => 100,
|
'max_score' => 100,
|
||||||
'weight' => $this->faker->numberBetween(1, 4),
|
'weight' => $this->faker->numberBetween(1, 4),
|
||||||
|
|
@ -30,6 +33,7 @@ class SubscoreDefinitionFactory extends Factory
|
||||||
'for_advance' => 1,
|
'for_advance' => 1,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function seatingOnly(): self
|
public function seatingOnly(): self
|
||||||
{
|
{
|
||||||
return $this->state(
|
return $this->state(
|
||||||
|
|
@ -57,5 +61,4 @@ class SubscoreDefinitionFactory extends Factory
|
||||||
fn (array $attributes) => ['tiebreak_order' => 0]
|
fn (array $attributes) => ['tiebreak_order' => 0]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,191 @@
|
||||||
<?php
|
<?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;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
|
||||||
uses(RefreshDatabase::class);
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
test('', function () {
|
beforeEach(function () {
|
||||||
$response = $this->get('/');
|
$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');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue