Skip to content

Commit

Permalink
add tests for achievementwondata API (#1784)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jamiras authored Sep 2, 2023
1 parent a73d395 commit e6458c0
Show file tree
Hide file tree
Showing 3 changed files with 212 additions and 39 deletions.
56 changes: 23 additions & 33 deletions app/Helpers/database/player-achievement.php
Original file line number Diff line number Diff line change
Expand Up @@ -277,24 +277,33 @@ function getRecentUnlocksPlayersData(
?string $user = null,
bool $friendsOnly = false
): array {
sanitize_sql_inputs($user);

$retVal = [];
$retVal = [
'NumEarned' => 0,
'GameID' => 0,
'TotalPlayers' => 0,
'RecentWinner' => [],
];

// Fetch the number of times this has been earned whatsoever (excluding hardcore)
$query = "SELECT COUNT(*) AS NumEarned, ach.GameID
FROM Awarded AS aw
LEFT JOIN Achievements AS ach ON ach.ID = aw.AchievementID
WHERE AchievementID=$achID AND aw.HardcoreMode = " . UnlockMode::Softcore;
$achievement = Achievement::find($achID);
if (!$achievement) {
return $retVal;
}

$dbResult = s_mysql_query($query);
$data = mysqli_fetch_assoc($dbResult);
$game = Game::find($achievement->GameID);
if (!$game) {
return $retVal;
}
$retVal['GameID'] = $game->ID;

// Fetch the number of times this has been earned whatsoever (excluding hardcore)
$query = "SELECT COUNT(*) AS NumEarned FROM Awarded
WHERE AchievementID=$achID AND HardcoreMode = " . UnlockMode::Softcore;
$data = legacyDbFetch($query);
$retVal['NumEarned'] = (int) $data['NumEarned'];
$retVal['GameID'] = (int) $data['GameID'];

// Fetch the total number of players for this game:
$retVal['TotalPlayers'] = getUniquePlayersByUnlocks($retVal['GameID']);
$parentGameID = getParentGameIdFromGameTitle($game->Title, $game->ConsoleID);
$retVal['TotalPlayers'] = getTotalUniquePlayers($game->ID, $parentGameID, achievementFlag: AchievementFlag::OfficialCore);

$extraWhere = "";
if ($friendsOnly && isset($user) && $user) {
Expand All @@ -303,15 +312,14 @@ function getRecentUnlocksPlayersData(
}

// Get recent winners, and their most recent activity:
$query = "SELECT aw.User, ua.RAPoints, UNIX_TIMESTAMP(aw.Date) AS DateAwarded
$query = "SELECT aw.User, ua.RAPoints, " . unixTimestampStatement('aw.Date', 'DateAwarded') . "
FROM Awarded AS aw
LEFT JOIN UserAccounts AS ua ON ua.User = aw.User
WHERE AchievementID=$achID AND aw.HardcoreMode = " . UnlockMode::Softcore . " $extraWhere
ORDER BY aw.Date DESC
LIMIT $offset, $count";

$dbResult = s_mysql_query($query);
while ($db_entry = mysqli_fetch_assoc($dbResult)) {
foreach (legacyDbFetchAll($query) as $db_entry) {
$db_entry['RAPoints'] = (int) $db_entry['RAPoints'];
$db_entry['DateAwarded'] = (int) $db_entry['DateAwarded'];
$retVal['RecentWinner'][] = $db_entry;
Expand All @@ -320,24 +328,6 @@ function getRecentUnlocksPlayersData(
return $retVal;
}

function getUniquePlayersByUnlocks(int $gameID): int
{
$query = "SELECT MAX( Inner1.MaxAwarded ) AS TotalPlayers FROM
(
SELECT ach.ID, COUNT(*) AS MaxAwarded
FROM Awarded AS aw
LEFT JOIN Achievements AS ach ON ach.ID = aw.AchievementID
LEFT JOIN GameData AS gd ON gd.ID = ach.GameID
WHERE gd.ID = $gameID AND aw.HardcoreMode = " . UnlockMode::Softcore . "
GROUP BY ach.ID
) AS Inner1";

$dbResult = s_mysql_query($query);
$data = mysqli_fetch_assoc($dbResult);

return (int) $data['TotalPlayers'];
}

/**
* Gets the number of softcore and hardcore awards for an achievement since a given time.
*/
Expand Down
9 changes: 3 additions & 6 deletions app/Helpers/database/user-relationship.php
Original file line number Diff line number Diff line change
Expand Up @@ -186,19 +186,16 @@ function GetFriendList(string $user): array
function GetFriendsSubquery(string $user, bool $includeUser = true): string
{
$friendsSubquery = "SELECT ua.User FROM UserAccounts ua
JOIN (SELECT Friend AS User FROM Friends WHERE User='$user' AND Friendship=" . UserRelationship::Following . ") as Friends1 ON Friends1.User=ua.User
JOIN (SELECT Friend AS User FROM Friends WHERE User=:user AND Friendship=" . UserRelationship::Following . ") as Friends1 ON Friends1.User=ua.User
WHERE ua.Deleted IS NULL AND ua.Permissions >= " . Permissions::Unregistered;

// TODO: why is it so much faster to run this query and build the IN list
// than to use it as a subquery? i.e. "AND aw.User IN ($friendsSubquery)"
// local testing took over 2 seconds with the subquery and < 0.01 seconds
// total for two separate queries
$friends = [];
$dbResult = s_mysql_query($friendsSubquery);
if ($dbResult !== false) {
while ($db_entry = mysqli_fetch_assoc($dbResult)) {
$friends[] = "'" . $db_entry['User'] . "'";
}
foreach (legacyDbFetchAll($friendsSubquery, ['user' => $user]) as $db_entry) {
$friends[] = "'" . $db_entry['User'] . "'";
}

if ($includeUser) {
Expand Down
186 changes: 186 additions & 0 deletions tests/Feature/Connect/AchievementWonDataTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<?php

declare(strict_types=1);

namespace Tests\Feature\Connect;

use App\Platform\Models\Achievement;
use App\Platform\Models\Game;
use App\Platform\Models\System;
use App\Site\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Carbon;
use Tests\Feature\Platform\TestsPlayerAchievements;
use Tests\TestCase;

class AchievementWonDataTest extends TestCase
{
use BootstrapsConnect;
use RefreshDatabase;
use TestsPlayerAchievements;

public function testRecentWinners(): void
{
$userCount = 20;
User::factory()->count($userCount)->create();

/** @var System $system */
$system = System::factory()->create();
/** @var Game $game */
$game = Game::factory()->create(['ConsoleID' => $system->ID]);
/** @var Achievement $achievement1 */
$achievement1 = Achievement::factory()->published()->create(['GameID' => $game->ID]);
/** @var Achievement $achievement2 */
$achievement2 = Achievement::factory()->published()->create(['GameID' => $game->ID]);
/** @var Achievement $achievement3 */
$achievement3 = Achievement::factory()->published()->create(['GameID' => $game->ID]);

$users = [];
$unlocks = [];
$now = Carbon::now()->clone()->subDays(2);

for ($count = 1; $count < $userCount; $count++) {
$user = User::findOrFail($count);
$users[$count] = $user;

$now = $now->addMinutes(5);
$unlocks[$count] = $now->timestamp;

// $count/4 users will NOT have unlocked achievement.
// $count/4 users will only unlock in softcore
if ($count % 4 == 2) { // 2,6,10,14,18
$this->addSoftcoreUnlock($user, $achievement1, $now);
} elseif ($count % 4 != 1) { // 3,4,7,8,11,12,15,16,19
$this->addHardcoreUnlock($user, $achievement1, $now);
}

// $count/3 users will have unlocked achievement2
if ($count % 3 == 1) { // 1,4,7,10,13,16,19
$this->addSoftcoreUnlock($user, $achievement2, $now);
}

// 1 and 13 will have only unlocked achievement2
// 5,9,17 will not have unlocked either
}

// first achievement - 5 most recent
$this->get($this->apiUrl('achievementwondata', ['a' => $achievement1->ID, 'c' => 5]))
->assertExactJson([
'Success' => true,
'Offset' => 0,
'Count' => 5,
'FriendsOnly' => false,
'AchievementID' => $achievement1->ID,
'Response' => [
'NumEarned' => 14,
'GameID' => $game->ID,
'TotalPlayers' => 16,
'RecentWinner' => [
['User' => $users[19]->User, 'RAPoints' => $users[19]->RAPoints, 'DateAwarded' => $unlocks[19]],
['User' => $users[18]->User, 'RAPoints' => $users[18]->RAPoints, 'DateAwarded' => $unlocks[18]],
['User' => $users[16]->User, 'RAPoints' => $users[16]->RAPoints, 'DateAwarded' => $unlocks[16]],
['User' => $users[15]->User, 'RAPoints' => $users[15]->RAPoints, 'DateAwarded' => $unlocks[15]],
['User' => $users[14]->User, 'RAPoints' => $users[14]->RAPoints, 'DateAwarded' => $unlocks[14]],
],
],
]);

// first achievement - offset and ask for more than available
$this->get($this->apiUrl('achievementwondata', ['a' => $achievement1->ID, 'o' => 12, 'c' => 6]))
->assertExactJson([
'Success' => true,
'Offset' => 12,
'Count' => 6,
'FriendsOnly' => false,
'AchievementID' => $achievement1->ID,
'Response' => [
'NumEarned' => 14,
'GameID' => $game->ID,
'TotalPlayers' => 16,
'RecentWinner' => [
['User' => $users[3]->User, 'RAPoints' => $users[3]->RAPoints, 'DateAwarded' => $unlocks[3]],
['User' => $users[2]->User, 'RAPoints' => $users[2]->RAPoints, 'DateAwarded' => $unlocks[2]],
],
],
]);

// other achievement - different earn rate/winners
$this->get($this->apiUrl('achievementwondata', ['a' => $achievement2->ID, 'o' => 3, 'c' => 4]))
->assertExactJson([
'Success' => true,
'Offset' => 3,
'Count' => 4,
'FriendsOnly' => false,
'AchievementID' => $achievement2->ID,
'Response' => [
'NumEarned' => 7,
'GameID' => $game->ID,
'TotalPlayers' => 16,
'RecentWinner' => [
['User' => $users[10]->User, 'RAPoints' => $users[10]->RAPoints, 'DateAwarded' => $unlocks[10]],
['User' => $users[7]->User, 'RAPoints' => $users[7]->RAPoints, 'DateAwarded' => $unlocks[7]],
['User' => $users[4]->User, 'RAPoints' => $users[4]->RAPoints, 'DateAwarded' => $unlocks[4]],
['User' => $users[1]->User, 'RAPoints' => $users[1]->RAPoints, 'DateAwarded' => $unlocks[1]],
],
],
]);

// third achievement - no unlocks
$this->get($this->apiUrl('achievementwondata', ['a' => $achievement3->ID]))
->assertExactJson([
'Success' => true,
'Offset' => 0,
'Count' => 10,
'FriendsOnly' => false,
'AchievementID' => $achievement3->ID,
'Response' => [
'NumEarned' => 0,
'GameID' => $game->ID,
'TotalPlayers' => 16,
'RecentWinner' => [],
],
]);

// non-existant achievement
$this->get($this->apiUrl('achievementwondata', ['a' => 999999]))
->assertExactJson([
'Success' => true,
'Offset' => 0,
'Count' => 10,
'FriendsOnly' => false,
'AchievementID' => 999999,
'Response' => [
'NumEarned' => 0,
'GameID' => 0,
'TotalPlayers' => 0,
'RecentWinner' => [],
],
]);

// second achievement - friends only
$this->assertEquals($this->user->ID, $users[1]->ID); /* logic assumes that first user is making API call */
// TODO: Use UserRelation model
legacyDbStatement("INSERT INTO Friends (User, Friend, Friendship) VALUES (:user, :friend, 1)",
['user' => $this->user->User, 'friend' => $users[10]->User]);
legacyDbStatement("INSERT INTO Friends (User, Friend, Friendship) VALUES (:user, :friend, 1)",
['user' => $this->user->User, 'friend' => $users[4]->User]);
$this->get($this->apiUrl('achievementwondata', ['a' => $achievement2->ID, 'f' => 1]))
->assertExactJson([
'Success' => true,
'Offset' => 0,
'Count' => 10,
'FriendsOnly' => true,
'AchievementID' => $achievement2->ID,
'Response' => [
'NumEarned' => 7,
'GameID' => $game->ID,
'TotalPlayers' => 16,
'RecentWinner' => [
['User' => $users[10]->User, 'RAPoints' => $users[10]->RAPoints, 'DateAwarded' => $unlocks[10]],
['User' => $users[4]->User, 'RAPoints' => $users[4]->RAPoints, 'DateAwarded' => $unlocks[4]],
// $users[1] is not their own friend, so won't be in the list
],
],
]);
}
}

0 comments on commit e6458c0

Please sign in to comment.