const test = require('node:test'); const assert = require('node:assert/strict'); const { MODE_WEIGHTS, hardBlock, scoreCandidate } = require('../server/services/liveGroupingScoring'); const { collectIdentityHints, collectOptionalIdentityHints, normalizeChannelName, projectCatalog } = require('../server/services/liveProjectionService'); const channel = (id, name, extra = {}) => ({ id, source_id: 1, item_id: id, name, category_id: 'sports', fingerprint: normalizeChannelName(name), ...extra }); test('weights match the validated architecture and remain bounded', () => { assert.deepEqual(MODE_WEIGHTS.algo, { algorithm: 80, structure: 15, metadata: 5, laya: 0 }); assert.deepEqual(MODE_WEIGHTS.hybrid, { algorithm: 70, structure: 15, metadata: 5, laya: 10 }); assert.deepEqual(MODE_WEIGHTS.laya, { algorithm: 55, structure: 15, metadata: 5, laya: 25 }); }); test('exact algorithm match merges without Laya', () => { const left = channel('a', 'beIN Sports 1 HD'); const right = channel('b', 'beIN Sports 1 4K'); assert.equal(scoreCandidate(left, right, { mode: 'algo' }).matched, true); }); test('exact algorithm match survives a contradictory Laya opinion', () => { const left = channel('a', 'beIN Sports 1 HD'); const right = channel('b', 'beIN Sports 1 4K'); const score = scoreCandidate(left, right, { mode: 'hybrid', layaHint: { sameIdentityProbability: 0, confidence: 1, number: '2' } }); assert.equal(score.matched, true); assert.equal(score.signals.laya, 0); }); test('exact generic fingerprints merge in Laya-only mode without family metadata', () => { const left = channel('a', '1000 x Hora TV', { category_id: 'one' }); const right = channel('b', '|UY| 1000 x Hora TV', { category_id: 'two' }); const score = scoreCandidate(left, right, { mode: 'laya' }); assert.equal(score.total, 55); assert.equal(score.matched, true); const groups = projectCatalog([left, right], { mode: 'laya' }); assert.equal(groups.length, 1); }); test('different channel numbers block even when Laya proposes a merge', () => { const left = channel('a', 'beIN Sports 1'); const right = channel('b', 'beIN Sports 2'); const score = scoreCandidate(left, right, { mode: 'laya', layaHint: { sameIdentityProbability: 1, confidence: 1 } }); assert.equal(score.matched, false); assert.equal(score.blocker, 'number_mismatch'); }); test('protected max token blocks beIN Sports Max 10 from beIN Sports 1', () => { assert.equal(hardBlock(channel('a', 'beIN Sports Max 10'), channel('b', 'beIN Sports 1')), 'protected_token_mismatch'); assert.equal(projectCatalog([ channel('a', 'beIN Sports Max 10'), channel('b', 'beIN Sports 1') ], { mode: 'laya' }).length, 2); }); test('different strong families are blocked before weighted scoring', () => { assert.equal(hardBlock(channel('a', 'beIN Sports 1'), channel('b', 'Sky Sports 1')), 'strong_family_mismatch'); }); test('low-confidence Laya hint contributes zero', () => { const score = scoreCandidate( channel('a', 'beIN Sports 1 Event'), channel('b', 'beIN Sports 1'), { mode: 'hybrid', layaHint: { sameIdentityProbability: 1, confidence: 0.64 } } ); assert.equal(score.signals.laya, 0); assert.equal(score.matched, false); }); test('No Laya mode never calls the identity client', async () => { let calls = 0; const hints = await collectIdentityHints([ channel('a', 'beIN Sports 1 Event'), channel('b', 'beIN Sports 1') ], 'algo', { identityHintBatch: async () => { calls++; return []; } }); assert.equal(calls, 0); assert.equal(hints.size, 0); }); test('Laya only calls Laya but still respects blockers', async () => { let calls = 0; const items = [channel('a', 'beIN Sports 1 Event'), channel('b', 'beIN Sports 1')]; const hints = await collectIdentityHints(items, 'laya', { identityHintBatch: async pairs => { calls++; return pairs.map(pair => ({ id: pair.id, sameIdentityProbability: 1, confidence: 1 })); } }); assert.equal(calls, 1); assert.equal(projectCatalog(items, { mode: 'laya', identityHints: hints }).length, 1); assert.equal(projectCatalog([ channel('1', 'beIN Sports 1'), channel('2', 'beIN Sports 2') ], { mode: 'laya', identityHints: hints }).length, 2); }); test('hybrid mode caps the positive Laya contribution at weight 10', () => { const score = scoreCandidate( channel('a', 'beIN Sports 1 Event'), channel('b', 'beIN Sports 1'), { mode: 'hybrid', layaHint: { sameIdentityProbability: 1, confidence: 5 } } ); assert.equal(score.signals.laya, 1); assert.equal(score.total, 72); assert.equal(score.matched, true); }); test('Laya bridge followed by an exact alias stays in one unique fingerprint group', () => { const items = [ channel('a', 'beIN Sports 1 Event'), channel('b', 'beIN Sports 1'), channel('c', 'beIN Sports 1') ]; const hints = new Map([[ ['a', 'b'].sort().join('\0'), { sameIdentityProbability: 1, confidence: 1 } ]]); const groups = projectCatalog(items, { mode: 'hybrid', identityHints: hints }); assert.equal(groups.length, 1); assert.equal(groups[0].fingerprint, 'bein sports 1'); assert.equal(groups[0].variants.length, 3); }); test('optional Laya failure degrades to an empty signal set', async () => { const hints = await collectOptionalIdentityHints([ channel('a', 'beIN Sports 1 Event'), channel('b', 'beIN Sports 1') ], 'hybrid', { identityHintBatch: async () => { throw new Error('runtime unavailable at https://private.invalid'); } }); assert.equal(hints.size, 0); });