Skip to content
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
16 changes: 8 additions & 8 deletions Sources/Actions/Stats.php
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ public function execute(): void
'SELECT id_member, real_name, posts
FROM {db_prefix}members
WHERE posts > {int:no_posts}
ORDER BY posts DESC
ORDER BY posts DESC, id_member
LIMIT 10',
[
'no_posts' => 0,
Expand Down Expand Up @@ -301,7 +301,7 @@ public function execute(): void
WHERE {query_see_board}' . (!empty(Config::$modSettings['recycle_enable']) && Config::$modSettings['recycle_board'] > 0 ? '
AND b.id_board != {int:recycle_board}' : '') . '
AND b.redirect = {string:blank_redirect}
ORDER BY num_posts DESC
ORDER BY num_posts DESC, id_board
LIMIT 10',
[
'recycle_board' => Config::$modSettings['recycle_board'],
Expand Down Expand Up @@ -338,7 +338,7 @@ public function execute(): void
FROM {db_prefix}topics
WHERE num_replies != {int:no_replies}' . (Config::$modSettings['postmod_active'] ? '
AND approved = {int:is_approved}' : '') . '
ORDER BY num_replies DESC
ORDER BY num_replies DESC, id_topic
LIMIT 100',
[
'no_replies' => 0,
Expand All @@ -365,7 +365,7 @@ public function execute(): void
WHERE {query_see_board}' . (!empty($topic_ids) ? '
AND t.id_topic IN ({array_int:topic_list})' : (Config::$modSettings['postmod_active'] ? '
AND t.approved = {int:is_approved}' : '')) . '
ORDER BY t.num_replies DESC
ORDER BY t.num_replies DESC, t.id_topic
LIMIT 10',
[
'topic_list' => $topic_ids,
Expand Down Expand Up @@ -410,7 +410,7 @@ public function execute(): void
'SELECT id_topic
FROM {db_prefix}topics
WHERE num_views != {int:no_views}
ORDER BY num_views DESC
ORDER BY num_views DESC, id_topic
LIMIT 100',
[
'no_views' => 0,
Expand All @@ -436,7 +436,7 @@ public function execute(): void
WHERE {query_see_board}' . (!empty($topic_ids) ? '
AND t.id_topic IN ({array_int:topic_list})' : (Config::$modSettings['postmod_active'] ? '
AND t.approved = {int:is_approved}' : '')) . '
ORDER BY t.num_views DESC
ORDER BY t.num_views DESC, t.id_topic
LIMIT 10',
[
'topic_list' => $topic_ids,
Expand Down Expand Up @@ -482,7 +482,7 @@ public function execute(): void
FROM {db_prefix}topics' . (!empty(Config::$modSettings['recycle_enable']) && Config::$modSettings['recycle_board'] > 0 ? '
WHERE id_board != {int:recycle_board}' : '') . '
GROUP BY id_member_started
ORDER BY hits DESC
ORDER BY hits DESC, id_member_started
LIMIT 20',
[
'recycle_board' => Config::$modSettings['recycle_board'],
Expand Down Expand Up @@ -549,7 +549,7 @@ public function execute(): void
FROM {db_prefix}members
WHERE is_activated = {int:is_activated}' .
(!empty($temp) ? ' AND id_member IN ({array_int:member_list_cached})' : '') . '
ORDER BY total_time_logged_in DESC
ORDER BY total_time_logged_in DESC, id_member
LIMIT 20',
[
'member_list_cached' => $temp,
Expand Down
118 changes: 118 additions & 0 deletions tests/Integration/Http/StatsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

declare(strict_types=1);

namespace SMF\Tests\Integration\Http;

use PHPUnit\Framework\Attributes\CoversNothing;
use SMF\Db\DatabaseApi as Db;

/**
* The statistics centre, as a guest sees it.
*
* The members it ranks are made for the test and removed again afterwards: an
* HTTP test runs on another connection than the forum, so it cannot be rolled
* back.
*/
#[CoversNothing]
class StatsTest extends HttpTestCase
{
/*********************
* Internal properties
*********************/

/**
* @var array The members made for the test.
*/
private array $member_ids = [];

/****************
* Public methods
****************/

/**
* The top tens were ordered by their count alone, so members with the same
* count came in whatever order the database returned them: here, on both
* engines, the later member first. PostgreSQL also returns a row it has
* just rewritten after the others, so there the order changes whenever a
* tied member's row is updated.
*/
public function testMembersWithTheSamePostCountAreRankedInTheOrderTheyJoined(): void
{
$first = $this->makeMember('Stats Tied First', 1000000);
$second = $this->makeMember('Stats Tied Second', 1000000);

// Rewriting the earlier row sends it to the end of the table on PostgreSQL.
Db::$db->query(
'UPDATE {db_prefix}members
SET posts = posts
WHERE id_member = {int:member}',
['member' => $first],
);

$page = $this->fetch('?action=stats');
$text = $page->text();

$this->assertStringContainsString('Stats Tied First', $text, 'the tied members are not in the top ten. The forum said: ' . $page->errorText());
$this->assertLessThan(
strpos($text, 'Stats Tied Second'),
strpos($text, 'Stats Tied First'),
'the member who joined later is ranked first',
);

$this->assertNoErrorsLogged('the stats page logged something.' . "\n");
}

/******************
* Internal methods
******************/

protected function tearDown(): void
{
if ($this->member_ids !== []) {
Db::$db->query(
'DELETE FROM {db_prefix}members
WHERE id_member IN ({array_int:members})',
['members' => $this->member_ids],
);

$this->member_ids = [];
}

parent::tearDown();
}

/**
* Makes a member with a given number of posts.
*
* @param string $name Their name.
* @param int $posts How many posts they have made.
* @return int The member's id.
*/
private function makeMember(string $name, int $posts): int
{
$login = 'stats_' . bin2hex(random_bytes(4));

$id = (int) Db::$db->insert(
'insert',
'{db_prefix}members',
[
'member_name' => 'string',
'real_name' => 'string',
'email_address' => 'string',
'posts' => 'int',
'is_activated' => 'int',
'buddy_list' => 'string',
'signature' => 'string',
'ignore_boards' => 'string',
],
[[$login, $name, $login . '@example.com', $posts, 1, '', '', '']],
['id_member'],
1,
);

$this->member_ids[] = $id;

return $id;
}
}
Loading