tests/Repository/MoodRepositoryTest.php
Rama develop · réplica de lectura del repositorio original.
<?php
declare(strict_types=1);
/**
* vim:set softtabstop=4 shiftwidth=4 expandtab:
*
* LICENSE: GNU Affero General Public License, version 3 (AGPL-3.0-or-later)
* Copyright Ampache.org, 2001-2026
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Ampache\Repository;
use Ampache\Module\Database\DatabaseConnectionInterface;
use Ampache\Repository\Model\Mood;
use PDOStatement;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use SEEC\PhpUnit\Helper\ConsecutiveParams;
class MoodRepositoryTest extends TestCase
{
use ConsecutiveParams;
private DatabaseConnectionInterface&MockObject $connection;
private MoodRepository $subject;
public function testAddMapReturnsTheNewMapId(): void
{
$this->connection->expects(static::once())
->method('query')
->with(
'INSERT IGNORE INTO `mood_map` (`mood_id`, `user`, `object_type`, `object_id`) VALUES (?, ?, ?, ?)',
[666, 0, 'song', 42]
);
$this->connection->expects(static::once())
->method('getLastInsertedId')
->willReturn(7);
self::assertSame(7, $this->subject->addMap(666, 'song', 42, 0));
}
public function testCollectGarbageSweepsEveryMappableTypeAndDropsTheEmptyMoods(): void
{
$statements = [];
$this->connection->method('query')
->willReturnCallback(function (string $sql) use (&$statements): PDOStatement {
$statements[] = $sql;
return $this->createMock(PDOStatement::class);
});
$this->subject->collectGarbage();
// a map is orphaned by whichever object it names, and a type with no sweep keeps the mood alive for ever
foreach (Mood::OBJECT_TYPES as $objectType) {
self::assertNotEmpty(
array_filter(
$statements,
static fn(string $sql): bool => str_starts_with($sql, 'DELETE FROM `mood_map`') && str_contains($sql, sprintf("`mood_map`.`object_type`='%s'", $objectType))
),
sprintf('no orphaned map sweep for %s', $objectType)
);
}
// a mood nothing points at any more goes, whoever created it
self::assertNotEmpty(
array_filter($statements, static fn(string $sql): bool => str_starts_with($sql, 'DELETE FROM `mood` USING `mood`'))
);
// a write for a type the enum does not hold is truncated to the error value, and no other sweep can resolve it
self::assertNotEmpty(
array_filter($statements, static fn(string $sql): bool => str_starts_with($sql, 'DELETE FROM `mood_map`') && str_contains($sql, "`object_type` = ''"))
);
// the owner is part of `unique_mood_map`, so the duplicate sweep must not take the map a user set beside the one from the file
self::assertNotEmpty(
array_filter(
$statements,
static fn(string $sql): bool => str_starts_with($sql, 'DELETE `b` FROM `mood_map`') && str_contains($sql, '`a`.`user` <=> `b`.`user`')
)
);
}
public function testGetTopMoodsBulkDoesNothingForNoObjects(): void
{
$this->connection->expects(static::never())
->method('query');
self::assertSame([], $this->subject->getTopMoodsBulk('song', []));
}
public function testGetTopMoodsBulkKeysTheRowsByObjectAndKeepsTheEmptyOnes(): void
{
$result = $this->createMock(PDOStatement::class);
$this->connection->expects(static::once())
->method('query')
->with(
self::callback(
static fn(string $sql): bool => str_contains($sql, '`mood_map`.`object_id` AS `owner_id`') && str_contains($sql, '`mood_map`.`object_id` IN (?,?)')
),
['song', 42, 43]
)
->willReturn($result);
$result->method('fetch')->willReturnOnConsecutiveCalls(
['owner_id' => '43', 'id' => '7', 'name' => 'calm', 'user' => '0', 'count' => '3'],
false
);
self::assertSame(
[
42 => [],
43 => [['id' => 7, 'name' => 'calm', 'user' => 0, 'count' => 3]],
],
$this->subject->getTopMoodsBulk('song', [42, 43])
);
}
public function testGetTopMoodsGroupsTheMapsOfOneMoodIntoASingleRow(): void
{
$result = $this->createMock(PDOStatement::class);
$this->connection->expects(static::once())
->method('query')
->with(
self::callback(
static fn(string $sql): bool => str_contains($sql, 'MAX(`mood_map`.`user`) AS `user`') && str_contains($sql, 'GROUP BY `mood`.`id`')
),
['song', 42]
)
->willReturn($result);
$result->method('fetch')->willReturn(false);
self::assertSame([], $this->subject->getTopMoods('song', 42, 0));
}
public function testMigrateMapsKeepsWhatTheTargetAlreadyHad(): void
{
// a user who already set this mood on both sides keeps it instead of the destination row being silently dropped
$this->connection->expects(static::exactly(2))
->method('query')
->with(
...self::withConsecutive(
[
'INSERT IGNORE INTO `mood_map` (`mood_id`, `object_id`, `object_type`, `user`) SELECT `old_map`.`mood_id`, ?, `old_map`.`object_type`, `old_map`.`user` FROM `mood_map` AS `old_map` WHERE `old_map`.`object_type` = ? AND `old_map`.`object_id` = ?',
[42, 'song', 666],
],
[
'DELETE FROM `mood_map` WHERE `object_type` = ? AND `object_id` = ?',
['song', 666],
],
)
);
$this->subject->migrateMaps('song', 666, 42);
}
protected function setUp(): void
{
$this->connection = $this->createMock(DatabaseConnectionInterface::class);
$this->subject = new MoodRepository($this->connection);
}
}