Ensure automatic live projection rebuilds use the algorithmic path so Laya identity batches are not launched during live list playback or background sync. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
158 lines
5.5 KiB
JavaScript
158 lines
5.5 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const Database = require('better-sqlite3');
|
|
const {
|
|
ensureColumns,
|
|
LIVE_PROJECTION_COLUMNS,
|
|
LIVE_PROJECTION_SCHEMA_SQL
|
|
} = require('../server/db/liveProjectionSchema');
|
|
const appDb = require('../server/db');
|
|
const layaModule = require('../server/services/layaService');
|
|
const liveProjection = require('../server/services/liveProjectionService');
|
|
|
|
const liveRouteSource = fs.readFileSync(path.join(__dirname, '..', 'server', 'routes', 'live.js'), 'utf8');
|
|
|
|
function createDb() {
|
|
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);
|
|
return db;
|
|
}
|
|
|
|
function seedChannel(db, sourceId, itemId, name) {
|
|
db.prepare(`
|
|
INSERT INTO playlist_items
|
|
(id, source_id, item_id, type, name, category_id, stream_icon, stream_url, data)
|
|
VALUES (?, ?, ?, 'live', ?, 'sports', '', ?, '{}')
|
|
`).run(`${sourceId}:${itemId}`, sourceId, itemId, name, `https://streams.test/${itemId}.m3u8`);
|
|
}
|
|
|
|
async function simulateLiveListOpen(database) {
|
|
let state = liveProjection.projectionState(database);
|
|
if (state.status !== 'success' && state.status !== 'building') {
|
|
state = await liveProjection.startRebuild(database, {
|
|
mode: 'algo',
|
|
schedule: callback => callback()
|
|
}).promise;
|
|
}
|
|
return liveProjection.getChannelPage({ includeHidden: false, userId: 7 }, database, {
|
|
iptvOrgCatalog: { findMatch: () => null },
|
|
epgMatching: { findBestMatch: () => null }
|
|
});
|
|
}
|
|
|
|
test('opening the live list with a missing projection must not start a global Laya identity batch', async () => {
|
|
assert.match(
|
|
liveRouteSource,
|
|
/liveProjection\.startRebuild\(null, \{ mode: 'algo' \}\)/,
|
|
'the live-list repair path must explicitly request the algorithmic rebuild mode'
|
|
);
|
|
const db = createDb();
|
|
for (let index = 0; index < 12; index++) {
|
|
seedChannel(db, 1, `existing-${index}`, `beIN Sports 1 regional ${index}`);
|
|
}
|
|
|
|
let layaCalls = 0;
|
|
let layaPairCount = 0;
|
|
const originalSettingsGet = appDb.settings.get;
|
|
const OriginalLayaService = layaModule.LayaService;
|
|
appDb.settings.get = async () => ({ ...appDb.getDefaultSettings(), languageDetectionMode: 'laya' });
|
|
layaModule.LayaService = class {
|
|
async identityHintBatch(pairs) {
|
|
layaCalls += 1;
|
|
layaPairCount += pairs.length;
|
|
return pairs.map(pair => ({
|
|
id: pair.id,
|
|
sameIdentityProbability: 0.9,
|
|
confidence: 0.9,
|
|
family: 'bein sports',
|
|
number: '1'
|
|
}));
|
|
}
|
|
};
|
|
|
|
try {
|
|
const page = await simulateLiveListOpen(db);
|
|
assert.ok(page.channels.length > 0);
|
|
assert.equal(layaCalls, 0, `unexpected Laya identity batches while opening live list; batches=${layaCalls}, pairs=${layaPairCount}`);
|
|
} finally {
|
|
appDb.settings.get = originalSettingsGet;
|
|
layaModule.LayaService = OriginalLayaService;
|
|
db.close();
|
|
}
|
|
});
|
|
|
|
test('background live sync rebuilds must not start Laya identity batches in configured Laya mode', async () => {
|
|
const db = createDb();
|
|
for (let index = 0; index < 12; index++) {
|
|
seedChannel(db, 1, `existing-${index}`, `beIN Sports 1 regional ${index}`);
|
|
}
|
|
|
|
let layaCalls = 0;
|
|
let layaPairCount = 0;
|
|
const originalSettingsGet = appDb.settings.get;
|
|
const OriginalLayaService = layaModule.LayaService;
|
|
appDb.settings.get = async () => ({ ...appDb.getDefaultSettings(), languageDetectionMode: 'laya' });
|
|
layaModule.LayaService = class {
|
|
async identityHintBatch(pairs) {
|
|
layaCalls += 1;
|
|
layaPairCount += pairs.length;
|
|
return pairs.map(pair => ({
|
|
id: pair.id,
|
|
sameIdentityProbability: 0.9,
|
|
confidence: 0.9,
|
|
family: 'bein sports',
|
|
number: '1'
|
|
}));
|
|
}
|
|
};
|
|
|
|
try {
|
|
await liveProjection.queueRebuild(db);
|
|
assert.equal(layaCalls, 0, `unexpected Laya identity batches during background sync rebuild; batches=${layaCalls}, pairs=${layaPairCount}`);
|
|
} finally {
|
|
appDb.settings.get = originalSettingsGet;
|
|
layaModule.LayaService = OriginalLayaService;
|
|
db.close();
|
|
}
|
|
});
|