diff --git a/tests/Support/TestDb.php b/tests/Support/TestDb.php new file mode 100644 index 00000000..7a0d2645 --- /dev/null +++ b/tests/Support/TestDb.php @@ -0,0 +1,132 @@ +exec('CREATE TABLE ...; INSERT INTO ...;'); + * SomeRepository::setDb($db); + * + * Caveat: SQLite is NOT MySQL. Code using MySQL-specific SQL — `JSON_CONTAINS`, + * `information_schema`, MySQL-only functions — cannot be exercised here and + * needs a real MySQL (CI service) or a query-level mock instead. + * + * @package XC_VM_Tests_Support + */ +final class TestDb { + + public PDO $pdo; + + /** @var array> Buffered rows from the last SELECT. */ + private array $rows = []; + + private int $lastInsertId = 0; + + public function __construct(?PDO $pdo = null) { + $this->pdo = $pdo ?? new PDO('sqlite::memory:'); + $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } + + /** + * Execute raw schema/seed SQL (one or more `;`-separated statements). + */ + public function exec(string $sql): void { + $this->pdo->exec($sql); + } + + /** + * Run a prepared query. Bind values follow $query (as in Database::query()). + * SELECT/PRAGMA/WITH results are buffered for get_rows()/get_row()/num_rows(). + */ + public function query($query, ...$args): bool { + // Mirror Database: the literal string 'null' and PHP null bind as SQL NULL. + $binds = array(); + foreach ($args as $a) { + $binds[] = (is_string($a) && strtolower($a) === 'null') ? null : $a; + } + + $stmt = $this->pdo->prepare($query); + $stmt->execute($binds); + + if (preg_match('/^\s*(SELECT|PRAGMA|WITH)/i', $query)) { + $this->rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: array(); + } else { + $this->rows = array(); + $id = $this->pdo->lastInsertId(); + if ($id) { + $this->lastInsertId = (int) $id; + } + } + + return true; + } + + /** + * Return buffered rows, optionally keyed by a column (mirrors Database). + */ + public function get_rows($use_id = false, $column_as_id = '', $unique_row = true, $sub_row_id = '') { + if (!$use_id) { + return $this->rows; + } + + $out = array(); + foreach ($this->rows as $row) { + if ($column_as_id !== '' && array_key_exists($column_as_id, $row)) { + if ($unique_row) { + $out[$row[$column_as_id]] = $row; + } elseif (!empty($sub_row_id) && array_key_exists($sub_row_id, $row)) { + $out[$row[$column_as_id]][$row[$sub_row_id]] = $row; + } else { + $out[$row[$column_as_id]][] = $row; + } + } else { + $out[] = $row; + } + } + return $out; + } + + public function get_row() { + return $this->rows[0] ?? array(); + } + + public function get_col() { + $row = $this->rows[0] ?? null; + return $row ? array_values($row)[0] : false; + } + + public function get_column(): array { + $col = array(); + foreach ($this->rows as $row) { + $col[] = array_values($row)[0] ?? null; + } + return $col; + } + + public function num_rows(): int { + return count($this->rows); + } + + public function last_insert_id() { + return $this->lastInsertId; + } + + public function escape($string) { + return $this->pdo->quote((string) $string); + } + + public function close_mysql(): bool { + return true; + } + + public function clean_row($row) { + return $row; + } +} diff --git a/tests/Unit/StreamConfigRepositoryTest.php b/tests/Unit/StreamConfigRepositoryTest.php new file mode 100644 index 00000000..eb7ea528 --- /dev/null +++ b/tests/Unit/StreamConfigRepositoryTest.php @@ -0,0 +1,76 @@ +db = new TestDb(); + $this->db->exec( + 'CREATE TABLE profiles (profile_id INTEGER PRIMARY KEY, profile_name TEXT); + CREATE TABLE streams (id INTEGER PRIMARY KEY, transcode_profile_id INTEGER DEFAULT 0); + CREATE TABLE watch_folders (id INTEGER PRIMARY KEY, transcode_profile_id INTEGER DEFAULT 0); + CREATE TABLE streams_arguments (id INTEGER PRIMARY KEY, argument_key TEXT, argument_cmd TEXT); + + INSERT INTO profiles (profile_id, profile_name) VALUES (1, "CPU"), (2, "GPU"); + INSERT INTO streams (id, transcode_profile_id) VALUES (10, 2), (11, 1); + INSERT INTO watch_folders (id, transcode_profile_id) VALUES (5, 2); + INSERT INTO streams_arguments (id, argument_key, argument_cmd) VALUES + (1, "cookie", "-headers ?"), (2, "useragent", "-user_agent ?");' + ); + + StreamConfigRepository::setDb($this->db); + } + + public function testGetTranscodeProfilesReturnsAll() { + $profiles = StreamConfigRepository::getTranscodeProfiles(); + $this->assertCount(2, $profiles); + $this->assertSame('CPU', $profiles[0]['profile_name']); + } + + public function testGetTranscodeProfileByIdReturnsRow() { + $profile = StreamConfigRepository::getTranscodeProfile(2); + $this->assertIsArray($profile); + $this->assertSame('GPU', $profile['profile_name']); + } + + public function testGetTranscodeProfileReturnsNullWhenMissing() { + $this->assertNull(StreamConfigRepository::getTranscodeProfile(999)); + } + + public function testGetStreamArgumentsKeyedByArgumentKey() { + $args = StreamConfigRepository::getStreamArguments(); + $this->assertArrayHasKey('cookie', $args); + $this->assertArrayHasKey('useragent', $args); + $this->assertSame('-user_agent ?', $args['useragent']['argument_cmd']); + } + + public function testDeleteProfileRemovesRowAndDetachesReferences() { + $this->assertTrue(StreamConfigRepository::deleteProfile(2)); + + // Profile gone. + $this->assertNull(StreamConfigRepository::getTranscodeProfile(2)); + + // References reset to 0; the unrelated stream keeps its profile. + $this->db->query('SELECT transcode_profile_id FROM streams WHERE id = ?;', 10); + $this->assertSame(0, (int) $this->db->get_col()); + + $this->db->query('SELECT transcode_profile_id FROM streams WHERE id = ?;', 11); + $this->assertSame(1, (int) $this->db->get_col()); + + $this->db->query('SELECT transcode_profile_id FROM watch_folders WHERE id = ?;', 5); + $this->assertSame(0, (int) $this->db->get_col()); + } + + public function testDeleteProfileReturnsFalseWhenMissing() { + $this->assertFalse(StreamConfigRepository::deleteProfile(999)); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 9059917b..5d7aca33 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -72,3 +72,6 @@ require_once MAIN_HOME . 'autoload.php'; require_once MAIN_HOME . 'core/Parsing/M3uParser/bootstrap.php'; require_once MAIN_HOME . 'core/Parsing/PhpM3u8/bootstrap.php'; require_once __DIR__ . '/Unit/M3uParser/ExtCustomTag.php'; + +// Test support: in-memory SQLite harness for DB-touching repositories/services. +require_once __DIR__ . '/Support/TestDb.php';