Files
XC_VM/tests/Unit/VodItemImporterRunTest.php
T
Divarion_D b7ec6c08d9 fix(vod): match provider series named in two languages, without accents or on TMDb
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.
2026-10-20 20:55:34 +03:00

416 lines
24 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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());
}
}