Auditionadmin 20 - Bonus scores are fully functional #25
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
|
||||
namespace App\Actions\Tabulation;
|
||||
|
||||
use App\Exceptions\ScoreEntryException;
|
||||
use App\Models\BonusScore;
|
||||
use App\Models\Entry;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class EnterBonusScore
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function __invoke(User $judge, Entry $entry, int $score): void
|
||||
{
|
||||
$this->basicValidations($judge, $entry);
|
||||
$this->validateJudgeValidity($judge, $entry, $score);
|
||||
$entries = $this->getRelatedEntries($entry);
|
||||
|
||||
// Create the score for each related entry
|
||||
foreach ($entries as $relatedEntry) {
|
||||
BonusScore::create([
|
||||
'entry_id' => $relatedEntry->id,
|
||||
'user_id' => $judge->id,
|
||||
'originally_scored_entry' => $entry->id,
|
||||
'score' => $score,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getRelatedEntries(Entry $entry): Collection
|
||||
{
|
||||
$bonusScore = $entry->audition->bonusScore->first();
|
||||
$relatedAuditions = $bonusScore->auditions;
|
||||
|
||||
// Get all entries that have a student_id equal to that of entry and an audition_id in the related auditions
|
||||
return Entry::where('student_id', $entry->student_id)
|
||||
->whereIn('audition_id', $relatedAuditions->pluck('id'))
|
||||
->get();
|
||||
}
|
||||
|
||||
protected function basicValidations(User $judge, Entry $entry): void
|
||||
{
|
||||
if (! $judge->exists) {
|
||||
throw new ScoreEntryException('Invalid judge provided');
|
||||
}
|
||||
if (! $entry->exists) {
|
||||
throw new ScoreEntryException('Invalid entry provided');
|
||||
}
|
||||
if ($entry->audition->bonusScore->count() === 0) {
|
||||
throw new ScoreEntryException('Entry does not have a bonus score');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function validateJudgeValidity(User $judge, Entry $entry, $score): void
|
||||
{
|
||||
if (BonusScore::where('entry_id', $entry->id)->where('user_id', $judge->id)->exists()) {
|
||||
throw new ScoreEntryException('That judge has already scored that entry');
|
||||
}
|
||||
|
||||
$bonusScore = $entry->audition->bonusScore->first();
|
||||
if (! $bonusScore->judges->contains($judge)) {
|
||||
throw new ScoreEntryException('That judge is not assigned to judge that bonus score');
|
||||
}
|
||||
if ($score > $bonusScore->max_score) {
|
||||
throw new ScoreEntryException('That score exceeds the maximum');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,10 +2,9 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class BonusScore extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
protected $guarded = [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
|||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Collection;
|
||||
use App\Models\ScoreSheet;
|
||||
|
||||
class User extends Authenticatable implements MustVerifyEmail
|
||||
{
|
||||
|
|
@ -118,6 +117,11 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||
return $this->rooms();
|
||||
}
|
||||
|
||||
public function bonusJudgingAssignments(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(BonusScoreDefinition::class, 'bonus_score_judge_assignment');
|
||||
}
|
||||
|
||||
public function advancementVotes(): HasMany
|
||||
{
|
||||
return $this->hasMany(JudgeAdvancementVote::class);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
use App\Actions\Tabulation\EnterBonusScore;
|
||||
use App\Exceptions\ScoreEntryException;
|
||||
use App\Models\Audition;
|
||||
use App\Models\BonusScore;
|
||||
use App\Models\BonusScoreDefinition;
|
||||
use App\Models\Entry;
|
||||
use App\Models\Student;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->enterBonusScore = App::make(EnterBonusScore::class);
|
||||
});
|
||||
|
||||
it('rejects a non existent entry', function () {
|
||||
$judge = User::factory()->create();
|
||||
$entry = Entry::factory()->make();
|
||||
$this->enterBonusScore->__invoke($judge, $entry, 42);
|
||||
})->throws(ScoreEntryException::class, 'Invalid entry provided');
|
||||
it('rejects a non existent judge', function () {
|
||||
$judge = User::factory()->make();
|
||||
$entry = Entry::factory()->create();
|
||||
$this->enterBonusScore->__invoke($judge, $entry, 42);
|
||||
})->throws(ScoreEntryException::class, 'Invalid judge provided');
|
||||
it('rejects a submission if the entries audition does not have a bonus score', function () {
|
||||
$judge = User::factory()->create();
|
||||
$entry = Entry::factory()->create();
|
||||
$this->enterBonusScore->__invoke($judge, $entry, 42);
|
||||
})->throws(ScoreEntryException::class, 'Entry does not have a bonus score');
|
||||
it('rejects a submission if the entry already has a score from the given judge', function () {
|
||||
// Arrange
|
||||
$judge = User::factory()->create();
|
||||
$bonusScore = BonusScoreDefinition::factory()->create();
|
||||
$entry = Entry::factory()->create();
|
||||
$entry->audition->bonusScore()->attach($bonusScore->id);
|
||||
$score = BonusScore::create([
|
||||
'entry_id' => $entry->id,
|
||||
'user_id' => $judge->id,
|
||||
'originally_scored_entry' => $entry->id,
|
||||
'score' => 42,
|
||||
]);
|
||||
// Act & Assert
|
||||
$this->enterBonusScore->__invoke($judge, $entry, 43);
|
||||
})->throws(ScoreEntryException::class, 'That judge has already scored that entry');
|
||||
it('rejects a submission for a judge not assigned to judge that bonus score', function () {
|
||||
// Arrange
|
||||
$judge = User::factory()->create();
|
||||
$bonusScore = BonusScoreDefinition::factory()->create();
|
||||
$entry = Entry::factory()->create();
|
||||
$entry->audition->bonusScore()->attach($bonusScore->id);
|
||||
// Act & Assert
|
||||
$this->enterBonusScore->__invoke($judge, $entry, 43);
|
||||
})->throws(ScoreEntryException::class, 'That judge is not assigned to judge that bonus score');
|
||||
it('rejects a submission for a score that exceeds the maximum', function () {
|
||||
// Arrange
|
||||
$judge = User::factory()->create();
|
||||
$bonusScore = BonusScoreDefinition::factory()->create(['max_score' => 50]);
|
||||
$bonusScore->judges()->attach($judge);
|
||||
$entry = Entry::factory()->create();
|
||||
$entry->audition->bonusScore()->attach($bonusScore->id);
|
||||
// Act & Assert
|
||||
$this->enterBonusScore->__invoke($judge, $entry, 51);
|
||||
})->throws(ScoreEntryException::class, 'That score exceeds the maximum');
|
||||
|
||||
it('records a valid bonus score submission on the submitted entry', function () {
|
||||
// Arrange
|
||||
$judge = User::factory()->create();
|
||||
$bonusScore = BonusScoreDefinition::factory()->create(['max_score' => 100]);
|
||||
$entry = Entry::factory()->create();
|
||||
$entry->audition->bonusScore()->attach($bonusScore->id);
|
||||
$bonusScore->judges()->attach($judge);
|
||||
// Act & Assert
|
||||
$this->enterBonusScore->__invoke($judge, $entry, 42);
|
||||
expect(
|
||||
BonusScore::where('entry_id', $entry->id)
|
||||
->where('user_id', $judge->id)
|
||||
->where('score', 42)->exists())
|
||||
->toBeTrue();
|
||||
});
|
||||
it('records a valid bonus score on all related entries', function () {
|
||||
// Arrange
|
||||
$judge = User::factory()->create();
|
||||
$bonusScore = BonusScoreDefinition::factory()->create(['name' => 'Saxophone Improvisation', 'max_score' => 100]);
|
||||
$bonusScore->judges()->attach($judge);
|
||||
$jazzAltoAudition = Audition::factory()->create(['name' => 'Jazz Alto Saxophone']);
|
||||
$jazzTenorAudition = Audition::factory()->create(['name' => 'Jazz Tenor Saxophone']);
|
||||
$jazzBariAudition = Audition::factory()->create(['name' => 'Jazz Bari Saxophone']);
|
||||
$bonusScore->auditions()->attach($jazzAltoAudition->id);
|
||||
$bonusScore->auditions()->attach($jazzTenorAudition->id);
|
||||
$bonusScore->auditions()->attach($jazzBariAudition->id);
|
||||
$saxStudent = Student::factory()->create();
|
||||
$jazzAltoEntry = Entry::factory()->create([
|
||||
'student_id' => $saxStudent->id, 'audition_id' => $jazzAltoAudition->id,
|
||||
]);
|
||||
$jazzTenorEntry = Entry::factory()->create(['student_id' => $saxStudent->id,
|
||||
'audition_id' => $jazzTenorAudition->id,
|
||||
]);
|
||||
$jazzBariEntry = Entry::factory()->create(['student_id' => $saxStudent->id, 'audition_id' => $jazzBariAudition->id,
|
||||
]);
|
||||
Entry::factory()->count(4)->create(['audition_id' => $jazzAltoAudition->id]);
|
||||
Entry::factory()->count(4)->create(['audition_id' => $jazzTenorAudition->id]);
|
||||
Entry::factory()->count(4)->create(['audition_id' => $jazzBariAudition->id]);
|
||||
// Act
|
||||
$this->enterBonusScore->__invoke($judge, $jazzAltoEntry, 42);
|
||||
// Assert
|
||||
expect(
|
||||
BonusScore::where('entry_id', $jazzAltoEntry->id)
|
||||
->where('user_id', $judge->id)
|
||||
->where('originally_scored_entry', $jazzAltoEntry->id)
|
||||
->where('score', 42)->exists())
|
||||
->toBeTrue()
|
||||
|
||||
->and(BonusScore::count())->toBe(3)
|
||||
|
||||
->and(
|
||||
BonusScore::where('entry_id', $jazzTenorEntry->id)
|
||||
->where('user_id', $judge->id)
|
||||
->where('originally_scored_entry', $jazzAltoEntry->id)
|
||||
->where('score', 42)->exists())
|
||||
->toBeTrue()
|
||||
|
||||
->and(
|
||||
BonusScore::where('entry_id', $jazzBariEntry->id)
|
||||
->where('user_id', $judge->id)
|
||||
->where('originally_scored_entry', $jazzAltoEntry->id)
|
||||
->where('score', 42)->exists())
|
||||
->toBeTrue();
|
||||
|
||||
});
|
||||
Loading…
Reference in New Issue