Ctrl K
ComunidadGuías…

tests/Repository/RatingRepositoryTest.php

Rama develop · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
<?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 PDO;
use PDOStatement;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use SEEC\PhpUnit\Helper\ConsecutiveParams;

class RatingRepositoryTest extends TestCase
{
    use ConsecutiveParams;

    private DatabaseConnectionInterface&MockObject $connection;
    private LoggerInterface&MockObject $logger;
    private RatingRepository $subject;

    public function testAdjustWeightFloorsTheDecrementAtZero(): void
    {
        // an unbalanced sequence of removals must never push the weight below zero
        $this->connection->expects(static::once())
            ->method('query')
            ->with(
                'UPDATE `song` SET `weight` = GREATEST(`weight` - 1, 0) WHERE `id` = ?;',
                [666]
            );

        $this->subject->adjustWeight('song', 666, -1);
    }

    public function testAdjustWeightIgnoresATypeWithoutTheColumn(): void
    {
        $this->connection->expects(static::never())
            ->method('query');

        $this->subject->adjustWeight('playlist', 666, 1);
    }

    public function testAdjustWeightRaisesTheCounterOnTheRatedTable(): void
    {
        $this->connection->expects(static::once())
            ->method('query')
            ->with(
                'UPDATE `song` SET `weight` = `weight` + 1 WHERE `id` = ?;',
                [666]
            );

        $this->subject->adjustWeight('song', 666, 1);
    }

    public function testCollectGarbageRefusesAnUnsupportedType(): void
    {
        $this->connection->expects(static::never())
            ->method('query');

        $this->logger->expects(static::once())
            ->method('critical');

        $this->subject->collectGarbage('some-type', 666);
    }

    public function testCollectGarbageSweepsOneObjectAndTheEmptyRatings(): void
    {
        $calls = [];

        $this->connection->expects(static::exactly(2))
            ->method('query')
            ->willReturnCallback(function (string $sql) use (&$calls): PDOStatement {
                $calls[] = $sql;

                return $this->createMock(PDOStatement::class);
            });

        $this->subject->collectGarbage('song', 666);

        self::assertSame(
            [
                'DELETE FROM `rating` WHERE `object_type` = ? AND `object_id` = ?',
                'DELETE FROM `rating` WHERE `rating`.`rating` = 0;',
            ],
            $calls
        );
    }

    public function testGetAverageRatingCountsItsVotersFromTheSameQuery(): void
    {
        // the HAVING already counts them, so the number of voters costs nothing to read back
        $this->connection->expects(static::once())
            ->method('fetchRow')
            ->willReturn(['rating' => '4.14', 'voters' => '7']);

        self::assertSame([4.14, 7], $this->subject->getAverageRating(666, 'song'));
    }

    public function testGetAverageRatingReturnsNullWhileOnlyOneUserHasRated(): void
    {
        $this->connection->expects(static::once())
            ->method('fetchRow')
            ->with(
                'SELECT ROUND(AVG(`rating`), 2) AS `rating`, COUNT(`object_id`) AS `voters` FROM `rating` WHERE `object_id` = ? AND `object_type` = ? HAVING COUNT(`object_id`) > 1',
                [666, 'song']
            )
            ->willReturn(false);

        self::assertNull($this->subject->getAverageRating(666, 'song'));
    }

    public function testGetAverageRatingsNarrowsTheSameWayTheSingleObjectQueryDoes(): void
    {
        $result = $this->createMock(PDOStatement::class);

        // without the HAVING clause the batch that warms the cache disagrees with getAverageRating(),
        // so a browse showed a rounded average where the object's own page showed none at all
        $this->connection->expects(static::once())
            ->method('query')
            ->with(
                'SELECT ROUND(AVG(`rating`), 2) AS `rating`, COUNT(`object_id`) AS `voters`, `object_id` FROM `rating` WHERE `object_id` IN (1,2) AND `object_type` = ? GROUP BY `object_id` HAVING COUNT(`object_id`) > 1',
                ['song']
            )
            ->willReturn($result);

        $result->expects(static::exactly(2))
            ->method('fetch')
            ->willReturn(['object_id' => '1', 'rating' => '3.67', 'voters' => '3'], false);

        self::assertSame([1 => [3.67, 3]], $this->subject->getAverageRatings('song', [1, 2]));
    }

    public function testGetUserRatingReturnsTheStoredValue(): void
    {
        $this->connection->expects(static::once())
            ->method('fetchOne')
            ->with(
                'SELECT `rating` FROM `rating` WHERE `user` = ? AND `object_id` = ? AND `object_type` = ? AND `rating` > 0;',
                [42, 666, 'song']
            )
            ->willReturn('4');

        self::assertSame(4, $this->subject->getUserRating(666, 'song', 42));
    }

    public function testGetUserRatingsKeysTheRowsByObjectAndCastsTheIdList(): void
    {
        $result = $this->createMock(PDOStatement::class);

        $this->connection->expects(static::once())
            ->method('query')
            ->with(
                'SELECT `rating`, `object_id` FROM `rating` WHERE `user` = ? AND `object_id` IN (1,2) AND `object_type` = ?',
                [42, 'song']
            )
            ->willReturn($result);

        $result->expects(static::exactly(2))
            ->method('fetch')
            ->with(PDO::FETCH_ASSOC)
            ->willReturn(['object_id' => '2', 'rating' => '5'], false);

        self::assertSame(
            [2 => 5],
            $this->subject->getUserRatings('song', [1, '2'], 42)
        );
    }

    public function testGetUserRatingsSkipsTheQueryForAnEmptyList(): void
    {
        $this->connection->expects(static::never())
            ->method('query');

        self::assertSame([], $this->subject->getUserRatings('song', [], 42));
    }

    public function testMigrateMovesTheRatingsKeepingWhatTheTargetHad(): void
    {
        // a user who rated both sides keeps the higher rating instead of the destination row being silently dropped
        $this->connection->expects(static::exactly(2))
            ->method('query')
            ->with(
                ...self::withConsecutive(
                    [
                        'INSERT INTO `rating` (`object_id`, `object_type`, `rating`, `user`, `date`) SELECT ?, `old_rating`.`object_type`, `old_rating`.`rating`, `old_rating`.`user`, `old_rating`.`date` FROM `rating` AS `old_rating` WHERE `old_rating`.`object_type` = ? AND `old_rating`.`object_id` = ? ON DUPLICATE KEY UPDATE `rating` = GREATEST(`rating`.`rating`, VALUES(`rating`)), `date` = GREATEST(`rating`.`date`, VALUES(`date`))',
                        [42, 'song', 666],
                    ],
                    [
                        'DELETE FROM `rating` WHERE `object_type` = ? AND `object_id` = ?',
                        ['song', 666],
                    ],
                )
            );

        $this->subject->migrate('song', 666, 42);
    }

    public function testSetRatingReplacesThePreviousOne(): void
    {
        $this->connection->expects(static::once())
            ->method('query')
            ->with(
                'REPLACE INTO `rating` (`object_id`, `object_type`, `rating`, `user`, `date`) VALUES (?, ?, ?, ?, ?)',
                [666, 'song', 4, 42, 123456]
            );

        $this->subject->setRating(666, 'song', 4, 42, 123456);
    }

    protected function setUp(): void
    {
        $this->connection = $this->createMock(DatabaseConnectionInterface::class);
        $this->logger     = $this->createMock(LoggerInterface::class);

        $this->subject = new RatingRepository(
            $this->connection,
            $this->logger
        );
    }
}