Skip to content

Commit 3c4935e

Browse files
authored
fix(tasks): recompute lineup startTimeOffsets after duration reconciliation (#2059)
Fixes #2034
1 parent e8ad9ba commit 3c4935e

2 files changed

Lines changed: 65 additions & 0 deletions

File tree

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import type { Kysely } from 'kysely';
2+
import { describe, expect, test, vi } from 'vitest';
3+
import type { Lineup } from '@/db/derived_types/Lineup.ts';
4+
import type { IChannelDB } from '@/db/interfaces/IChannelDB.ts';
5+
import type { DB } from '@/db/schema/db.ts';
6+
import { ReconcileProgramDurationsTask } from './ReconcileProgramDurationsTask.ts';
7+
8+
describe('ReconcileProgramDurationsTask', () => {
9+
test('recomputes startTimeOffsets when it corrects program durations', async () => {
10+
// Two content items persisted with stale durations and the matching stale
11+
// startTimeOffsets (based on those old durations).
12+
const staleLineup = {
13+
id: 'uid1',
14+
channel_uuid: 'ch1',
15+
items: [
16+
{ type: 'content', id: 'p1', durationMs: 1000 },
17+
{ type: 'content', id: 'p2', durationMs: 500 },
18+
],
19+
startTimeOffsets: [0, 1000, 1500],
20+
version: 0,
21+
} as unknown as Lineup;
22+
23+
const savedLineups: Record<string, unknown>[] = [];
24+
const channelDB = {
25+
getAllChannels: vi.fn().mockResolvedValue([{ uuid: 'ch1' }]),
26+
loadLineup: vi.fn().mockResolvedValue(staleLineup),
27+
saveLineup: vi.fn(async (_channelId: string, newLineup: unknown) => {
28+
savedLineups.push(newLineup as Record<string, unknown>);
29+
return {};
30+
}),
31+
} as unknown as IChannelDB;
32+
33+
// Programs table is the source of truth for durations.
34+
const db = {
35+
selectFrom: vi.fn().mockReturnValue({
36+
select: vi.fn().mockReturnValue({
37+
where: vi.fn().mockReturnValue({
38+
execute: vi.fn().mockResolvedValue([
39+
{ uuid: 'p1', duration: 60000 },
40+
{ uuid: 'p2', duration: 30000 },
41+
]),
42+
}),
43+
}),
44+
}),
45+
} as unknown as Kysely<DB>;
46+
47+
const task = new ReconcileProgramDurationsTask(channelDB, db);
48+
await task.run(undefined);
49+
50+
expect(savedLineups).toHaveLength(1);
51+
const saved = savedLineups[0] as {
52+
items: { type: string; id: string; durationMs: number }[];
53+
startTimeOffsets: number[];
54+
};
55+
expect(saved.items).toEqual([
56+
{ type: 'content', id: 'p1', durationMs: 60000 },
57+
{ type: 'content', id: 'p2', durationMs: 30000 },
58+
]);
59+
// The persisted startTimeOffsets must reflect the corrected durations,
60+
// not be carried over stale from the pre-correction lineup.
61+
expect(saved.startTimeOffsets).toEqual([0, 60000, 90000]);
62+
});
63+
});

server/src/tasks/ReconcileProgramDurationsTask.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import z from 'zod';
2222
import { ChannelOrm } from '../db/schema/Channel.ts';
2323
import { DB } from '../db/schema/db.ts';
24+
import { calculateStartTimeOffsets } from '../db/lineupUtil.ts';
2425
import { Task2, TaskMetadata } from './Task.ts';
2526
import { taskDef } from './TaskRegistry.ts';
2627

@@ -160,6 +161,7 @@ export class ReconcileProgramDurationsTask extends Task2<
160161
await this.channelDB.saveLineup(channel.uuid, {
161162
...lineup,
162163
items: newLineupItems,
164+
startTimeOffsets: calculateStartTimeOffsets(newLineupItems),
163165
});
164166
}
165167
}

0 commit comments

Comments
 (0)