mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-10-31 11:03:22 +01:00
A provider playlist ("|TR| Baskalarinin Hayati (2026) حياة الاخرين - S01E01")
imported none of its 356 episodes: every one ended in No match.
- parseTitle() compares letters without their accents (mb_strtolower plus a
Latin fold table), so "Alti Ustu Istanbul" equals "Altı Üstü İstanbul"
instead of scoring 70% under the 80% threshold.
- splitYear() takes a bracketed year from inside a name as well as from its
end, for every parserelease() caller.
- findTmdbMatch() searches a name's candidates in turn: the name, then each
run of one script ("Sumud" / "صمود"), then, with a year, each " - " part.
Any candidate but the name is searched only with the year when there is
one; without a year a one-word run is skipped, as "أمي" matched a Saudi
show for the Turkish "Anne".
- An episode TMDb has no show for goes into the series created by hand in
the panel (no TMDb id) whose normalized name and year match it; one that
series already has under its number is a duplicate.
Imported through Series -> Import on the test panel: the playlist went from
0 to 341 of 356 episodes, and the remaining 15 joined a hand-made series.
416 lines
24 KiB
PHP
416 lines
24 KiB
PHP
<?php
|
||
|
||
use XcVm\Core\Config\SettingsManager;
|
||
use XcVm\Core\Events\EventDispatcher;
|
||
use XcVm\Core\Events\Vod\VodImportResultEvent;
|
||
use XcVm\Domain\Vod\VodItemImporter;
|
||
use PHPUnit\Framework\TestCase;
|
||
|
||
/**
|
||
* VodItemImporter::run() end to end for the Series → Import (M3U) path: the
|
||
* thread data SeriesService::import() hands to `vod_import_item`, a fake TMDb
|
||
* client, and an existing series row (a brand-new series would fetch its
|
||
* trailer over HTTP).
|
||
*/
|
||
final class VodItemImporterRunTest extends TestCase {
|
||
|
||
private const STREAM_COLUMNS = array(
|
||
'type' => 'int', 'stream_source' => 'mediumtext', 'target_container' => 'varchar', 'read_native' => 'tinyint',
|
||
'movie_symlink' => 'tinyint', 'remove_subtitles' => 'tinyint', 'transcode_profile_id' => 'int',
|
||
'direct_source' => 'tinyint', 'direct_proxy' => 'tinyint', 'order' => 'int', 'stream_display_name' => 'mediumtext',
|
||
'movie_properties' => 'mediumtext', 'movie_subtitles' => 'mediumtext', 'series_no' => 'int', 'added' => 'int',
|
||
'tmdb_language' => 'varchar', 'notes' => 'mediumtext',
|
||
);
|
||
|
||
private TestDb $db;
|
||
private array $settingsBefore;
|
||
private $globalDbBefore;
|
||
/** @var VodImportResultEvent[] */
|
||
private array $results = array();
|
||
|
||
protected function setUp(): void {
|
||
if (!defined('SERVER_ID')) {
|
||
define('SERVER_ID', 1);
|
||
}
|
||
\XcVm\Infrastructure\Tmdb\TmdbApiService::requireLibrary();
|
||
|
||
$this->db = new TestDb();
|
||
$rColumns = array();
|
||
foreach (self::STREAM_COLUMNS as $rName => $rType) {
|
||
$rColumns[] = '`' . $rName . '` ' . ($rType === 'varchar' ? 'varchar(255)' : $rType) . ' NULL';
|
||
}
|
||
$this->db->exec('CREATE TABLE streams (id INTEGER PRIMARY KEY AUTO_INCREMENT, ' . implode(', ', $rColumns) . ');');
|
||
$this->db->exec('CREATE TABLE streams_servers (stream_id INTEGER, server_id INTEGER, parent_id INTEGER);');
|
||
$this->db->exec('CREATE TABLE streams_episodes (season_num INTEGER, series_id INTEGER, stream_id INTEGER, episode_num INTEGER);');
|
||
$this->db->exec('CREATE TABLE streams_series (id INTEGER PRIMARY KEY AUTO_INCREMENT, tmdb_id INTEGER, title TEXT, seasons TEXT, year INTEGER);');
|
||
$this->db->exec("INSERT INTO streams_series (id, tmdb_id, title, seasons) VALUES (5, 1399, 'Les Psys', '[]');");
|
||
|
||
|
||
VodItemImporter::setDb($this->db);
|
||
$this->globalDbBefore = $GLOBALS['db'] ?? null;
|
||
$GLOBALS['db'] = $this->db;
|
||
$this->settingsBefore = SettingsManager::getAll();
|
||
SettingsManager::set(array('tmdb_api_key' => '', 'tmdb_language' => '', 'parse_type' => 'guessit', 'fallback_parser' => 0, 'percentage_match' => 80, 'download_images' => 0));
|
||
EventDispatcher::listen(VodImportResultEvent::class, function (VodImportResultEvent $rEvent): void {
|
||
$this->results[] = $rEvent;
|
||
});
|
||
}
|
||
|
||
protected function tearDown(): void {
|
||
EventDispatcher::unlisten(VodImportResultEvent::class);
|
||
SettingsManager::set($this->settingsBefore);
|
||
$GLOBALS['db'] = $this->globalDbBefore;
|
||
@unlink(WATCH_TMP_PATH . 'lock_1399');
|
||
@unlink(WATCH_TMP_PATH . 'series_1399');
|
||
array_map('unlink', glob(WATCH_TMP_PATH . 'season_1399_*.cache') ?: array());
|
||
\XcVm\Core\Process\ProcessRunner::useCapturer(null);
|
||
}
|
||
|
||
/** The payload SeriesService::import() builds for one M3U entry. */
|
||
private function threadData(): array {
|
||
return array(
|
||
'import' => true, 'type' => 'series', 'title' => 'Les Psys (2026) - S01E02 - Épisode 2',
|
||
'file' => 'http://provider.example/series/u/p/377778.mkv', 'subtitles' => array(), 'servers' => array(SERVER_ID),
|
||
'fb_category_id' => array(7), 'fb_bouquets' => array(), 'disable_tmdb' => false, 'ignore_no_match' => false,
|
||
'bouquets' => array(), 'category_id' => array(), 'language' => '', 'watch_categories' => array(),
|
||
'read_native' => 0, 'movie_symlink' => 0, 'remove_subtitles' => 0, 'direct_source' => 0, 'direct_proxy' => 0,
|
||
'auto_encode' => false, 'auto_upgrade' => false, 'fallback_title' => false, 'ffprobe_input' => false,
|
||
'transcode_profile_id' => null, 'target_container' => 'mkv', 'max_genres' => 0, 'duplicate_tmdb' => true,
|
||
);
|
||
}
|
||
|
||
private function tmdb(array $rSearchResults): FakeTmdbClient {
|
||
return new FakeTmdbClient(array(
|
||
'searchTVShow' => fn() => $rSearchResults,
|
||
'getTVShow' => fn() => new TVShow(array(
|
||
'id' => 1399, 'name' => 'Les Psys', 'seasons' => array(array('poster_path' => '/s1.jpg')),
|
||
'genres' => array(), 'credits' => array('cast' => array(), 'crew' => array()), 'episode_run_time' => array(45),
|
||
)),
|
||
'getSeason' => fn() => new Season(array('episodes' => array(
|
||
array('episode_number' => 2, 'name' => 'Épisode 2', 'id' => 222, 'air_date' => '2026-01-08', 'overview' => '', 'vote_average' => 7.1, 'still_path' => null),
|
||
))),
|
||
));
|
||
}
|
||
|
||
public function testImportsAnM3uEpisodeIntoTheExistingSeries(): void {
|
||
$this->expectOutputRegex('/Success!/');
|
||
|
||
VodItemImporter::run($this->threadData(), 60, $this->tmdb(array(new TVShow(array('id' => 1399, 'name' => 'Les Psys', 'first_air_date' => '2026-01-01')))));
|
||
|
||
$this->assertCount(1, $this->results);
|
||
$this->assertSame(VodImportResultEvent::STATUS_IMPORTED, $this->results[0]->status);
|
||
$this->assertSame(2, $this->results[0]->type);
|
||
$rStreamID = $this->results[0]->streamId;
|
||
|
||
$this->db->query('SELECT `stream_display_name`, `series_no`, `stream_source` FROM `streams` WHERE `id` = ?;', $rStreamID);
|
||
$rRow = $this->db->get_row();
|
||
$this->assertSame('Les Psys - S01E02 - Épisode 2', $rRow['stream_display_name']);
|
||
$this->assertSame(5, (int) $rRow['series_no']);
|
||
$this->assertSame(array('http://provider.example/series/u/p/377778.mkv'), json_decode($rRow['stream_source'], true));
|
||
|
||
$this->db->query('SELECT `season_num`, `series_id`, `episode_num` FROM `streams_episodes` WHERE `stream_id` = ?;', $rStreamID);
|
||
$this->assertSame(array(1, 5, 2), array_map('intval', array_values($this->db->get_row())));
|
||
$this->db->query('SELECT `server_id` FROM `streams_servers` WHERE `stream_id` = ?;', $rStreamID);
|
||
$this->assertSame((string) SERVER_ID, (string) $this->db->get_col());
|
||
}
|
||
|
||
/**
|
||
* A watch folder's file on another server is imported like an M3U entry,
|
||
* named by its title (the file's name). With the folder's "fallback title"
|
||
* the folder's name is tried next, as for a file scanned where it lies:
|
||
* the title used to stand in for that try too, which then never matched.
|
||
*/
|
||
public function testAnImportFallsBackToItsFoldersName(): void {
|
||
$rData = array('title' => 'lpsy.S01E02', 'file' => 's:2:/mnt/series/Les Psys/lpsy.S01E02.mkv', 'servers' => array(2), 'fallback_title' => true) + $this->threadData();
|
||
$rAsked = array();
|
||
$rTmdb = new FakeTmdbClient(array(
|
||
'searchTVShow' => function ($rTitle) use (&$rAsked) {
|
||
$rAsked[] = $rTitle;
|
||
return $rTitle === 'Les Psys' ? array(new TVShow(array('id' => 1399, 'name' => 'Les Psys', 'first_air_date' => '2026-01-01'))) : array();
|
||
},
|
||
'getTVShow' => fn() => new TVShow(array('id' => 1399, 'name' => 'Les Psys', 'seasons' => array(array('poster_path' => '/s1.jpg')), 'genres' => array(), 'credits' => array('cast' => array(), 'crew' => array()), 'episode_run_time' => array(45))),
|
||
'getSeason' => fn() => new Season(array('episodes' => array(array('episode_number' => 2, 'name' => 'Épisode 2', 'id' => 222, 'air_date' => '2026-01-08', 'overview' => '', 'vote_average' => 7.1, 'still_path' => null)))),
|
||
));
|
||
$this->expectOutputRegex('/Success!/');
|
||
|
||
VodItemImporter::run($rData, 60, $rTmdb);
|
||
|
||
$this->assertSame(array('lpsy', 'Les Psys'), $rAsked, 'the title first, then the folder');
|
||
$this->assertSame(VodImportResultEvent::STATUS_IMPORTED, $this->results[0]->status);
|
||
$this->db->query('SELECT `server_id` FROM `streams_servers` WHERE `stream_id` = ?;', $this->results[0]->streamId);
|
||
$this->assertSame('2', (string) $this->db->get_col());
|
||
}
|
||
|
||
/**
|
||
* On a fallback match the episode and whether it is a range are both read
|
||
* from the name the episode came from, the title: the folder's name, parsed
|
||
* last, used to decide the range, so a double episode lost its second half
|
||
* and a single one could take a range from its folder's name.
|
||
*
|
||
* @dataProvider fallbackEpisodes
|
||
*/
|
||
public function testAFallbackMatchKeepsTheTitlesEpisodes(array $rParsed, string $rName): void {
|
||
\XcVm\Core\Process\ProcessRunner::useCapturer(static fn(array $rArgv): array => array(0, json_encode($rParsed[basename((string) end($rArgv), '.mkv')] ?? array())));
|
||
$rData = array('title' => 'lpsy 1x02', 'file' => 's:2:/mnt/series/Les Psys/lpsy 1x02.mkv', 'servers' => array(2), 'fallback_title' => true) + $this->threadData();
|
||
$rTmdb = new FakeTmdbClient(array(
|
||
'searchTVShow' => fn($rTitle) => $rTitle === 'Les Psys' ? array(new TVShow(array('id' => 1399, 'name' => 'Les Psys', 'first_air_date' => '2026-01-01'))) : array(),
|
||
'getTVShow' => fn() => new TVShow(array('id' => 1399, 'name' => 'Les Psys', 'seasons' => array(array('poster_path' => '/s1.jpg')), 'genres' => array(), 'credits' => array('cast' => array(), 'crew' => array()), 'episode_run_time' => array(45))),
|
||
'getSeason' => fn() => new Season(array('episodes' => array())),
|
||
));
|
||
$this->expectOutputRegex('/Success!/');
|
||
try {
|
||
VodItemImporter::run($rData, 60, $rTmdb);
|
||
} finally {
|
||
\XcVm\Core\Process\ProcessRunner::useCapturer(null);
|
||
}
|
||
|
||
$this->db->query('SELECT `stream_display_name` FROM `streams` WHERE `id` = ?;', $this->results[0]->streamId);
|
||
$this->assertSame($rName, $this->db->get_col());
|
||
}
|
||
|
||
public static function fallbackEpisodes(): array {
|
||
return array(
|
||
'a double episode under a plain folder' => array(array('lpsy 1x02' => array('title' => 'lpsy', 'season' => 1, 'episode' => array(2, 3)), 'Les Psys' => array('title' => 'Les Psys')), 'Les Psys - S01E02-03'),
|
||
'a single episode under a folder named with a range' => array(array('lpsy 1x02' => array('title' => 'lpsy', 'season' => 1, 'episode' => 2), 'Les Psys' => array('title' => 'Les Psys', 'season' => 1, 'episode' => array(1, 10))), 'Les Psys - S01E02'),
|
||
);
|
||
}
|
||
|
||
public function testRunPayloadRefusesAnythingButBase64Json(): void {
|
||
$this->expectOutputString("vod_import_item: payload must be base64-encoded JSON object\nvod_import_item: payload must be base64-encoded JSON object\nvod_import_item: unsupported type\n");
|
||
|
||
VodItemImporter::runPayload('not base64!');
|
||
VodItemImporter::runPayload(base64_encode('"a string"'));
|
||
VodItemImporter::runPayload(base64_encode(json_encode(array('file' => 'x', 'import' => true, 'type' => 'radio'))));
|
||
|
||
$this->assertSame(array(), $this->results);
|
||
}
|
||
|
||
public function testQueueBatchWritesOneLinePerItemAndStartsOneProcess(): void {
|
||
$rStarted = array();
|
||
$rItems = array(array('type' => 'series', 'title' => 'A - S01E01'), array('type' => 'series', 'title' => "B's - S01E02"));
|
||
|
||
$this->assertTrue(VodItemImporter::queueBatch($rItems, function (string $rFile) use (&$rStarted) {
|
||
$rStarted[] = $rFile;
|
||
}));
|
||
$this->assertFalse(VodItemImporter::queueBatch(array(), function () {
|
||
$this->fail('an empty import starts nothing');
|
||
}));
|
||
|
||
$this->assertCount(1, $rStarted);
|
||
$rFile = $rStarted[0];
|
||
$this->assertSame($rFile, \XcVm\Cli\Commands\VodImportBatchCommand::batchFile($rFile), 'the batch command accepts it');
|
||
$rCommands = iterator_to_array(VodItemImporter::batchCommands($rFile, 300), false);
|
||
unlink($rFile);
|
||
|
||
$this->assertCount(2, $rCommands);
|
||
$this->assertStringStartsWith('/usr/bin/timeout 300 ', $rCommands[0]);
|
||
preg_match("/vod_import_item '([^']+)'$/", $rCommands[1], $rMatch);
|
||
$this->assertSame($rItems[1], json_decode(base64_decode($rMatch[1]), true));
|
||
}
|
||
|
||
public function testTheBatchCommandRefusesFilesItDidNotQueue(): void {
|
||
$rOther = WATCH_TMP_PATH . 'notes.jsonl';
|
||
file_put_contents($rOther, '{}');
|
||
|
||
$this->assertNull(\XcVm\Cli\Commands\VodImportBatchCommand::batchFile($rOther));
|
||
$this->assertNull(\XcVm\Cli\Commands\VodImportBatchCommand::batchFile('/etc/passwd'));
|
||
$this->assertNull(\XcVm\Cli\Commands\VodImportBatchCommand::batchFile(WATCH_TMP_PATH . '../watch/import_0123456789abcdef.jsonl'));
|
||
unlink($rOther);
|
||
$this->assertSame(array(), iterator_to_array(VodItemImporter::batchCommands($rOther, 300)), 'a missing file yields nothing');
|
||
}
|
||
|
||
public function testImportThreadsFallsBackToFour(): void {
|
||
SettingsManager::set(array('thread_count' => 0));
|
||
$this->assertSame(4, VodItemImporter::importThreads());
|
||
SettingsManager::set(array('thread_count' => '12'));
|
||
$this->assertSame(12, VodItemImporter::importThreads());
|
||
}
|
||
|
||
public function testTheCommandRunsOnlyAsXcVm(): void {
|
||
if (posix_getpwuid(posix_geteuid())['name'] === 'xc_vm') {
|
||
$this->markTestSkipped('Running as xc_vm.');
|
||
}
|
||
$this->expectOutputString("Please run as XC_VM!\n");
|
||
|
||
$this->assertSame(1, (new \XcVm\Cli\Commands\VodImportItemCommand())->execute(array(base64_encode('{}'))));
|
||
}
|
||
|
||
public function testTheBatchCommandRunsOnlyAsXcVm(): void {
|
||
if (posix_getpwuid(posix_geteuid())['name'] === 'xc_vm') {
|
||
$this->markTestSkipped('Running as xc_vm.');
|
||
}
|
||
$this->expectOutputString("Please run as XC_VM!\n");
|
||
|
||
$this->assertSame(1, (new \XcVm\Cli\Commands\VodImportBatchCommand())->execute(array('/etc/passwd')));
|
||
}
|
||
|
||
/**
|
||
* A name with its season and no episode number ("Les Psys S01 Le Divan"),
|
||
* as guessit reads it, and a TMDb client that counts what it is asked.
|
||
*
|
||
* @param array<int, string> $rTitles Episode number => title of the TMDb season.
|
||
* @param array<string, int> $rAsked Calls per method.
|
||
*/
|
||
private function titledEpisode(array $rTitles, ?array &$rAsked, int $rSeason = 1): array {
|
||
\XcVm\Core\Process\ProcessRunner::useCapturer(static fn(): array => array(0, json_encode(array('title' => 'Les Psys', 'season' => $rSeason, 'episode_title' => 'Le Divan'))));
|
||
$rAsked = array('searchTVShow' => 0, 'getSeason' => 0);
|
||
$rEpisodes = array();
|
||
foreach ($rTitles as $rNumber => $rTitle) {
|
||
$rEpisodes[] = array('episode_number' => $rNumber, 'name' => $rTitle, 'id' => 200 + $rNumber, 'air_date' => '2026-01-08', 'overview' => '', 'vote_average' => 7.1, 'still_path' => null);
|
||
}
|
||
$rTmdb = new FakeTmdbClient(array(
|
||
'searchTVShow' => function () use (&$rAsked) {
|
||
$rAsked['searchTVShow']++;
|
||
return array(new TVShow(array('id' => 1399, 'name' => 'Les Psys', 'first_air_date' => '2026-01-01')));
|
||
},
|
||
'getTVShow' => fn() => new TVShow(array('id' => 1399, 'name' => 'Les Psys', 'seasons' => array(array('poster_path' => '/s1.jpg')), 'genres' => array(), 'credits' => array('cast' => array(), 'crew' => array()), 'episode_run_time' => array(45))),
|
||
'getSeason' => function () use (&$rAsked, $rEpisodes) {
|
||
$rAsked['getSeason']++;
|
||
return new Season(array('episodes' => $rEpisodes));
|
||
},
|
||
));
|
||
return array(array('title' => 'Les Psys S0' . $rSeason . ' Le Divan') + $this->threadData(), $rTmdb);
|
||
}
|
||
|
||
/** Off, as installed: a file with no episode number is No match, and TMDb is not asked. */
|
||
public function testAnEpisodeWithNoNumberIsNoMatchWhileTheTitleMatchIsOff(): void {
|
||
list($rData, $rTmdb) = $this->titledEpisode(array(1 => 'Pilote', 2 => 'Le Divan'), $rAsked);
|
||
$this->expectOutputString("Scanning: Les Psys S01 Le Divan\n");
|
||
|
||
VodItemImporter::run($rData, 60, $rTmdb);
|
||
|
||
$this->assertCount(1, $this->results);
|
||
$this->assertSame(VodImportResultEvent::STATUS_NO_MATCH, $this->results[0]->status);
|
||
$this->assertSame(array('searchTVShow' => 0, 'getSeason' => 0), $rAsked);
|
||
$this->db->query('SELECT COUNT(*) AS `count` FROM `streams`;');
|
||
$this->assertSame(0, (int) $this->db->get_col());
|
||
}
|
||
|
||
/** On: the episode is the one of that title, said so in its notes, and the season is fetched once. */
|
||
public function testAnEpisodeWithNoNumberIsFoundByItsTitle(): void {
|
||
SettingsManager::set(array('tmdb_episode_by_title' => 1) + SettingsManager::getAll());
|
||
list($rData, $rTmdb) = $this->titledEpisode(array(1 => 'Pilote', 2 => 'Le Divan', 3 => 'Le Transfert'), $rAsked);
|
||
$this->expectOutputRegex('~Matched by title: http://provider\.example/series/u/p/377778\.mkv => S01E02 "Le Divan" \(100%\)\n.*Success!~s');
|
||
|
||
VodItemImporter::run($rData, 60, $rTmdb);
|
||
|
||
$this->assertCount(1, $this->results);
|
||
$this->assertSame(VodImportResultEvent::STATUS_IMPORTED, $this->results[0]->status);
|
||
$this->db->query('SELECT `stream_display_name`, `series_no`, `notes` FROM `streams` WHERE `id` = ?;', $this->results[0]->streamId);
|
||
$rRow = $this->db->get_row();
|
||
$this->assertSame(array('Les Psys - S01E02 - Le Divan', 5, 'Episode number matched by TMDb title: "Le Divan" (100%)'), array($rRow['stream_display_name'], (int) $rRow['series_no'], $rRow['notes']));
|
||
$this->db->query('SELECT `season_num`, `series_id`, `episode_num` FROM `streams_episodes` WHERE `stream_id` = ?;', $this->results[0]->streamId);
|
||
$this->assertSame(array(1, 5, 2), array_map('intval', array_values($this->db->get_row())));
|
||
$this->assertSame(array('searchTVShow' => 1, 'getSeason' => 1), $rAsked, "the episode's title and still come from the season the match fetched");
|
||
}
|
||
|
||
/** A number found by title never replaces the file of an episode already imported, auto-upgrade or not. */
|
||
public function testAnEpisodeFoundByItsTitleNeverUpgradesTheOneAlreadyImported(): void {
|
||
SettingsManager::set(array('tmdb_episode_by_title' => 1) + SettingsManager::getAll());
|
||
list($rData, $rTmdb) = $this->titledEpisode(array(1 => 'Pilote', 2 => 'Le Divan'), $rAsked);
|
||
file_put_contents(WATCH_TMP_PATH . 'series_1399.cache', json_encode(array('1_2' => array('id' => 77, 'source' => 's:1:/vod/old.mkv'))));
|
||
$this->expectOutputRegex('/Upgrade disabled/');
|
||
|
||
try {
|
||
VodItemImporter::run(array('auto_upgrade' => true, 'duplicate_tmdb' => false) + $rData, 60, $rTmdb);
|
||
} finally {
|
||
@unlink(WATCH_TMP_PATH . 'series_1399.cache');
|
||
}
|
||
|
||
$this->assertSame(array(), $this->results);
|
||
$this->db->query('SELECT COUNT(*) AS `count` FROM `streams`;');
|
||
$this->assertSame(0, (int) $this->db->get_col());
|
||
}
|
||
|
||
/**
|
||
* On, and still No match: a title too close to another one, and the
|
||
* specials (season 0), which are never matched nor looked up.
|
||
*
|
||
* @dataProvider refusedTitles
|
||
*/
|
||
public function testATitleMatchThatIsNotClearStaysNoMatch(array $rTitles, int $rSeason, array $rExpectedCalls): void {
|
||
SettingsManager::set(array('tmdb_episode_by_title' => 1) + SettingsManager::getAll());
|
||
list($rData, $rTmdb) = $this->titledEpisode($rTitles, $rAsked, $rSeason);
|
||
$this->expectOutputRegex('/^Scanning: /');
|
||
|
||
VodItemImporter::run($rData, 60, $rTmdb);
|
||
|
||
$this->assertCount(1, $this->results);
|
||
$this->assertSame(VodImportResultEvent::STATUS_NO_MATCH, $this->results[0]->status);
|
||
$this->assertSame($rExpectedCalls, $rAsked);
|
||
$this->db->query('SELECT COUNT(*) AS `count` FROM `streams`;');
|
||
$this->assertSame(0, (int) $this->db->get_col());
|
||
}
|
||
|
||
public static function refusedTitles(): array {
|
||
return array(
|
||
'two parts of one title' => array(array(1 => 'Le Divan (1)', 2 => 'Le Divan (2)'), 1, array('searchTVShow' => 1, 'getSeason' => 1)),
|
||
'no title near it' => array(array(1 => 'Pilote', 2 => 'Le Transfert'), 1, array('searchTVShow' => 1, 'getSeason' => 1)),
|
||
'a special' => array(array(1 => 'Le Divan'), 0, array('searchTVShow' => 0, 'getSeason' => 0)),
|
||
);
|
||
}
|
||
|
||
public function testReportsNoMatchAndInsertsNothingWhenTmdbFindsNothing(): void {
|
||
$this->expectOutputRegex('/No match!/');
|
||
|
||
VodItemImporter::run($this->threadData(), 60, $this->tmdb(array()));
|
||
|
||
$this->assertCount(1, $this->results);
|
||
$this->assertSame(VodImportResultEvent::STATUS_NO_MATCH, $this->results[0]->status);
|
||
$this->db->query('SELECT COUNT(*) AS `count` FROM `streams`;');
|
||
$this->assertSame(0, (int) $this->db->get_col());
|
||
}
|
||
|
||
/** A provider's entry: tag, title in Turkish without its letters, year, the same title in Arabic. */
|
||
private function providerEpisode(string $rName): array {
|
||
return array('title' => '|TR| ' . $rName . ' - S01E02 - |TR| ' . $rName . ' - S01E02 - Épisode 2', 'duplicate_tmdb' => false) + $this->threadData();
|
||
}
|
||
|
||
public function testATwoLanguageProviderNameMatchesTheShowByItsLatinHalf(): void {
|
||
$rAsked = array();
|
||
$rTmdb = new FakeTmdbClient(array(
|
||
'searchTVShow' => function ($rTitle, $rYear) use (&$rAsked) {
|
||
$rAsked[] = $rTitle;
|
||
return $rTitle === 'Alti Ustu Istanbul' ? array(new TVShow(array('id' => 1399, 'name' => 'Altı Üstü İstanbul', 'first_air_date' => '2026-01-01'))) : array();
|
||
},
|
||
'getTVShow' => fn() => new TVShow(array('id' => 1399, 'name' => 'Altı Üstü İstanbul', 'seasons' => array(array('poster_path' => '/s1.jpg')), 'genres' => array(), 'credits' => array('cast' => array(), 'crew' => array()), 'episode_run_time' => array(45))),
|
||
'getSeason' => fn() => new Season(array('episodes' => array())),
|
||
));
|
||
$this->expectOutputRegex('/Success!/');
|
||
|
||
VodItemImporter::run($this->providerEpisode('Alti Ustu Istanbul (2026) اسطنبول راسا على عقب'), 60, $rTmdb);
|
||
|
||
$this->assertSame(array('Alti Ustu Istanbul اسطنبول راسا على عقب', 'Alti Ustu Istanbul اسطنبول راسا على عقب', 'Alti Ustu Istanbul'), $rAsked);
|
||
$this->assertSame(VodImportResultEvent::STATUS_IMPORTED, $this->results[0]->status);
|
||
$this->db->query('SELECT `series_no`, `stream_display_name` FROM `streams` WHERE `id` = ?;', $this->results[0]->streamId);
|
||
$this->assertSame(array('series_no' => '5', 'stream_display_name' => 'Altı Üstü İstanbul - S01E02'), array_map('strval', $this->db->get_row()));
|
||
}
|
||
|
||
public function testAnEpisodeTmdbDoesNotKnowGoesIntoTheSeriesMadeInThePanel(): void {
|
||
$this->db->exec("INSERT INTO streams_series (id, title, year, seasons) VALUES (6, 'Başkalarının Hayatı', 2026, '[]');");
|
||
$this->expectOutputRegex('/Series in the panel: Başkalarının Hayatı.*Success!/s');
|
||
|
||
VodItemImporter::run($this->providerEpisode('Baskalarinin Hayati (2026) حياة الاخرين'), 60, $this->tmdb(array()));
|
||
|
||
$this->assertSame(VodImportResultEvent::STATUS_IMPORTED, $this->results[0]->status);
|
||
$rStreamID = $this->results[0]->streamId;
|
||
$this->db->query('SELECT `series_no`, `stream_display_name` FROM `streams` WHERE `id` = ?;', $rStreamID);
|
||
$this->assertSame(array('series_no' => '6', 'stream_display_name' => 'Başkalarının Hayatı - S01E02'), array_map('strval', $this->db->get_row()));
|
||
$this->db->query('SELECT `season_num`, `series_id`, `episode_num` FROM `streams_episodes` WHERE `stream_id` = ?;', $rStreamID);
|
||
$this->assertSame(array(1, 6, 2), array_map('intval', array_values($this->db->get_row())));
|
||
}
|
||
|
||
public function testAnEpisodeTheHandMadeSeriesHasIsADuplicate(): void {
|
||
$this->db->exec("INSERT INTO streams_series (id, title, year, seasons) VALUES (6, 'Başkalarının Hayatı', 2026, '[]');");
|
||
$this->db->exec('INSERT INTO streams_episodes (season_num, series_id, stream_id, episode_num) VALUES (1, 6, 77, 2);');
|
||
$this->expectOutputRegex('/Episode already in the series/');
|
||
|
||
VodItemImporter::run($this->providerEpisode('Baskalarinin Hayati (2026) حياة الاخرين'), 60, $this->tmdb(array()));
|
||
|
||
$this->assertSame(VodImportResultEvent::STATUS_DUPLICATE, $this->results[0]->status);
|
||
$this->assertSame(77, $this->results[0]->streamId);
|
||
$this->db->query('SELECT COUNT(*) AS `count` FROM `streams`;');
|
||
$this->assertSame(0, (int) $this->db->get_col());
|
||
}
|
||
}
|