Skip to content

Commit

Permalink
Merge pull request #10696 from bdach/multiplayer-allowed-mod-validati…
Browse files Browse the repository at this point in the history
…on-wrong

Fix multiplayer score mods not being validated properly when playlist item has no allowed mods
  • Loading branch information
nanaya authored Nov 1, 2023
2 parents a262a02 + fc65fcd commit 9dffc09
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 10 deletions.
18 changes: 8 additions & 10 deletions app/Models/Multiplayer/ScoreLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,14 @@ public function complete(array $params): void
}
}

if (!empty($this->playlistItem->allowed_mods)) {
$missingMods = array_diff(
array_column($mods, 'acronym'),
array_column($this->playlistItem->required_mods, 'acronym'),
array_column($this->playlistItem->allowed_mods, 'acronym')
);

if (!empty($missingMods)) {
throw new InvariantException('This play includes mods that are not allowed.');
}
$disallowedMods = array_diff(
array_column($mods, 'acronym'),
array_column($this->playlistItem->required_mods, 'acronym'),
array_column($this->playlistItem->allowed_mods, 'acronym')
);

if (!empty($disallowedMods)) {
throw new InvariantException('This play includes mods that are not allowed.');
}

$this->score()->associate($score);
Expand Down
164 changes: 164 additions & 0 deletions tests/Models/Multiplayer/ScoreLinkTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
<?php

// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the GNU Affero General Public License v3.0.
// See the LICENCE file in the repository root for full licence text.

declare(strict_types=1);

namespace Tests\Models\Multiplayer;

use App\Exceptions\InvariantException;
use App\Models\Multiplayer\PlaylistItem;
use App\Models\Multiplayer\ScoreLink;
use App\Models\User;
use Carbon\Carbon;
use Tests\TestCase;

class ScoreLinkTest extends TestCase
{
public function testRequiredModsMissing()
{
$user = User::factory()->create();
$playlistItem = PlaylistItem::factory()->create([
'required_mods' => [[
'acronym' => 'HD',
]],
]);
$scoreLink = ScoreLink::factory()->make([
'score_id' => null,
'user_id' => $user,
'playlist_item_id' => $playlistItem,
]);

$this->expectException(InvariantException::class);
$this->expectExceptionMessage('This play does not include the mods required.');
$scoreLink->complete([
'beatmap_id' => $playlistItem->beatmap_id,
'ruleset_id' => $playlistItem->ruleset_id,
'user_id' => $user->getKey(),
'ended_at' => json_date(Carbon::now()),
'mods' => [],
'statistics' => [
'great' => 1,
],
]);
}

public function testRequiredModsPresent()
{
$user = User::factory()->create();
$playlistItem = PlaylistItem::factory()->create([
'required_mods' => [[
'acronym' => 'HD',
]],
]);
$scoreLink = ScoreLink::factory()->make([
'score_id' => null,
'user_id' => $user,
'playlist_item_id' => $playlistItem,
]);

$this->expectNotToPerformAssertions();
$scoreLink->complete([
'beatmap_id' => $playlistItem->beatmap_id,
'ruleset_id' => $playlistItem->ruleset_id,
'user_id' => $user->getKey(),
'ended_at' => json_date(Carbon::now()),
'mods' => [['acronym' => 'HD']],
'statistics' => [
'great' => 1,
],
]);
}

public function testExpectedAllowedMod()
{
$user = User::factory()->create();
$playlistItem = PlaylistItem::factory()->create([
'required_mods' => [[
'acronym' => 'DT',
]],
'allowed_mods' => [[
'acronym' => 'HD',
]],
]);
$scoreLink = ScoreLink::factory()->make([
'score_id' => null,
'user_id' => $user,
'playlist_item_id' => $playlistItem,
]);

$this->expectNotToPerformAssertions();
$scoreLink->complete([
'beatmap_id' => $playlistItem->beatmap_id,
'ruleset_id' => $playlistItem->ruleset_id,
'user_id' => $user->getKey(),
'ended_at' => json_date(Carbon::now()),
'mods' => [
['acronym' => 'DT'],
['acronym' => 'HD'],
],
'statistics' => [
'great' => 1,
],
]);
}

public function testUnexpectedAllowedMod()
{
$user = User::factory()->create();
$playlistItem = PlaylistItem::factory()->create([
'required_mods' => [[
'acronym' => 'DT',
]],
'allowed_mods' => [[
'acronym' => 'HR',
]],
]);
$scoreLink = ScoreLink::factory()->make([
'score_id' => null,
'user_id' => $user,
'playlist_item_id' => $playlistItem,
]);

$this->expectException(InvariantException::class);
$this->expectExceptionMessage('This play includes mods that are not allowed.');
$scoreLink->complete([
'beatmap_id' => $playlistItem->beatmap_id,
'ruleset_id' => $playlistItem->ruleset_id,
'user_id' => $user->getKey(),
'ended_at' => json_date(Carbon::now()),
'mods' => [
['acronym' => 'DT'],
['acronym' => 'HD'],
],
'statistics' => [
'great' => 1,
],
]);
}

public function testUnexpectedModWhenNoModsAreAllowed()
{
$user = User::factory()->create();
$playlistItem = PlaylistItem::factory()->create(); // no required or allowed mods.
$scoreLink = ScoreLink::factory()->make([
'score_id' => null,
'user_id' => $user,
'playlist_item_id' => $playlistItem,
]);

$this->expectException(InvariantException::class);
$this->expectExceptionMessage('This play includes mods that are not allowed.');
$scoreLink->complete([
'beatmap_id' => $playlistItem->beatmap_id,
'ruleset_id' => $playlistItem->ruleset_id,
'user_id' => $user->getKey(),
'ended_at' => json_date(Carbon::now()),
'mods' => [['acronym' => 'HD']],
'statistics' => [
'great' => 1,
],
]);
}
}

0 comments on commit 9dffc09

Please sign in to comment.