Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add tests for achievementwondata API #1784

Merged
merged 2 commits into from
Sep 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
],
],
]);
}
}
Loading