const test = require('node:test'); const assert = require('node:assert/strict'); const Database = require('better-sqlite3'); const liveProjection = require('../server/services/liveProjectionService'); const { ensureColumns, LIVE_PROJECTION_COLUMNS, LIVE_PROJECTION_SCHEMA_SQL } = require('../server/db/liveProjectionSchema'); function createProjectionDb() { const db = new Database(':memory:'); db.exec(` CREATE TABLE categories ( id TEXT PRIMARY KEY, source_id INTEGER NOT NULL, category_id TEXT NOT NULL, type TEXT NOT NULL, name TEXT NOT NULL, parent_id TEXT, is_hidden INTEGER DEFAULT 0, data JSON ); CREATE TABLE playlist_items ( id TEXT PRIMARY KEY, source_id INTEGER NOT NULL, item_id TEXT NOT NULL, type TEXT NOT NULL, name TEXT NOT NULL, category_id TEXT, parent_id TEXT, stream_icon TEXT, stream_url TEXT, container_extension TEXT, rating REAL, year TEXT, added_at TEXT, is_hidden INTEGER DEFAULT 0, is_favorite INTEGER DEFAULT 0, data JSON ); CREATE TABLE favorites ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, source_id INTEGER NOT NULL, item_id TEXT NOT NULL, item_type TEXT NOT NULL ); `); ensureColumns(db, 'playlist_items', LIVE_PROJECTION_COLUMNS); db.exec(LIVE_PROJECTION_SCHEMA_SQL); db.prepare(` INSERT INTO categories (id, source_id, category_id, type, name) VALUES ('sports', 2, 'sports', 'live', 'Sports') `).run(); return db; } function seedProjectedVariant(db, { itemId, name, icon, rank }) { db.prepare(` INSERT INTO playlist_items (id, source_id, item_id, type, name, category_id, stream_icon, stream_url, data, logical_channel_id, grouping_fingerprint, grouping_input_hash, quality_label, quality_rank, variant_rank, language_code, language_confidence, language_detector, language_input_hash) VALUES (?, 2, ?, 'live', ?, 'sports', ?, ?, ?, 'logical:bein-sports-1', 'bein sports 1', 'group-hash', 'HD', 2, ?, 'fr', 0.97, 'laya', ?) `).run( `2:${itemId}`, itemId, name, icon, `https://streams.test/${itemId}.m3u8`, JSON.stringify({ groupTitle: 'Sports', tvgId: `bein1.${itemId}` }), rank, `language-${itemId}` ); } test('projected live channel exposes a row logo from the selected variant stream_icon', () => { const db = createProjectionDb(); const preferredLogo = 'http://57.128.182.77:8080/0tv_logo/119.png'; db.prepare(` INSERT INTO logical_channels (id, fingerprint, display_name, preferred_variant_id, variant_count, created_at, updated_at) VALUES ('logical:bein-sports-1', 'bein sports 1', 'beIN Sports 1', '2:146116', 2, 1, 1) `).run(); seedProjectedVariant(db, { itemId: '146116', name: 'beIN Sports 1 HD FR', icon: preferredLogo, rank: 0 }); seedProjectedVariant(db, { itemId: '146117', name: 'beIN Sports 1 Backup FR', icon: 'http://57.128.182.77:8080/0tv_logo/backup.png', rank: 1 }); try { const page = liveProjection.getChannelPage({ q: 'bein sports 1', languages: ['fr'], includeHidden: false, page: 1, limit: 10 }, db); assert.equal(page.channels.length, 1); const [channel] = page.channels; assert.equal(channel.variantCount, 2); assert.equal(channel.selectedVariantId, '2:146116'); assert.equal(channel.preferredVariant.streamIcon, preferredLogo); assert.equal( channel.tvgLogo || channel.streamIcon, preferredLogo, 'ChannelList renders channel.tvgLogo, so the service response needs a row-level logo derived from the preferred variant stream_icon' ); } finally { db.close(); } });