const test = require('node:test'); const assert = require('node:assert/strict'); const { projectCatalog, serializeChannels } = require('../server/services/liveProjectionService'); const { LIVE_PROJECTION_COLUMNS, LIVE_PROJECTION_SCHEMA_SQL } = require('../server/db/liveProjectionSchema'); const item = (id, name, extra = {}) => ({ id: `1:${id}`, source_id: 1, item_id: String(id), name, category_id: 'sports', ...extra }); test('projection migration declares grouping and language columns', () => { assert.deepEqual(Object.keys(LIVE_PROJECTION_COLUMNS), [ 'logical_channel_id', 'grouping_fingerprint', 'grouping_input_hash', 'quality_label', 'quality_rank', 'variant_rank', 'language_code', 'language_confidence', 'language_scores', 'language_evidence', 'language_detector', 'language_input_hash' ]); assert.match(LIVE_PROJECTION_SCHEMA_SQL, /CREATE TABLE IF NOT EXISTS logical_channels/); assert.match(LIVE_PROJECTION_SCHEMA_SQL, /CREATE TABLE IF NOT EXISTS catalog_projection_state/); assert.match(LIVE_PROJECTION_SCHEMA_SQL, /idx_items_logical_channel/); }); test('projects beIN quality variants into one logical channel', () => { const groups = projectCatalog([ item('uhd', '|FR| BEIN SPORTS 1 UHD'), item('4k', '|FR| BEIN SPORTS 1 4K'), item('car', 'FR-CAR| bein sports 1') ]); assert.equal(groups.length, 1); assert.equal(groups[0].fingerprint, 'bein sports 1'); assert.equal(groups[0].displayName, 'beIN Sports 1'); assert.equal(groups[0].variants.length, 3); assert.deepEqual(groups[0].variants.map(variant => variant.variantRank), [0, 1, 2]); }); test('does not merge distinct numbered beIN channels', () => { const groups = projectCatalog([ item('1', 'beIN Sports 1 HD'), item('2', 'beIN Sports 2 HD') ]); assert.deepEqual(groups.map(group => group.fingerprint), ['bein sports 1', 'bein sports 2']); }); test('renaming a stream changes its projection membership and input hash', () => { const before = projectCatalog([item('1', 'beIN Sports 1 HD'), item('2', 'beIN Sports 1 4K')]); const after = projectCatalog([item('1', 'beIN Sports 2 HD'), item('2', 'beIN Sports 1 4K')]); assert.equal(before.length, 1); assert.equal(after.length, 2); const beforeRename = before.flatMap(group => group.variants).find(variant => variant.id === '1:1'); const afterRename = after.flatMap(group => group.variants).find(variant => variant.id === '1:1'); assert.notEqual(beforeRename.groupingInputHash, afterRename.groupingInputHash); }); test('API serialization filters source rows upstream and hidden variants by option', () => { const rows = [ { ...item('1', 'beIN Sports 1 HD'), logical_channel_id: 'logical:1', grouping_fingerprint: 'bein sports 1', display_name: 'beIN Sports 1', preferred_variant_id: '1:1', quality_label: 'HD', quality_rank: 2, variant_rank: 0, is_hidden: 0, category_hidden: 0, category_name: 'Sports Premium', user_favorite: 1, data: '{}' }, { ...item('2', 'beIN Sports 1 4K'), logical_channel_id: 'logical:1', grouping_fingerprint: 'bein sports 1', display_name: 'beIN Sports 1', preferred_variant_id: '1:1', quality_label: '4K', quality_rank: 4, variant_rank: 1, is_hidden: 1, category_hidden: 0, category_name: 'Sports Premium', user_favorite: 0, data: '{}' } ]; const visible = serializeChannels(rows, false); assert.equal(visible[0].variants.length, 1); assert.equal(visible[0].isFavorite, true); assert.equal(visible[0].groupTitle, 'Sports Premium'); assert.equal(visible[0].categoryName, 'Sports Premium'); assert.equal(visible[0].variants[0].groupTitle, 'Sports Premium'); assert.equal(serializeChannels(rows, true)[0].variants.length, 2); }); test('API serialization preserves an M3U groupTitle over the stored category fallback', () => { const rows = [{ ...item('m3u', 'M3U Sports HD'), logical_channel_id: 'logical:m3u', grouping_fingerprint: 'm3u sports', display_name: 'M3U Sports', preferred_variant_id: '1:m3u', quality_label: 'HD', quality_rank: 2, variant_rank: 0, is_hidden: 0, category_hidden: 0, category_name: 'Fallback category', user_favorite: 0, data: JSON.stringify({ groupTitle: 'M3U Sports France' }) }]; const [channel] = serializeChannels(rows, false); assert.equal(channel.groupTitle, 'M3U Sports France'); assert.equal(channel.variants[0].categoryName, 'M3U Sports France'); });