Skip to content

Commit eb0eb3c

Browse files
committed
fix: validate segment continuity before recovery dispatch
1 parent 90982b0 commit eb0eb3c

2 files changed

Lines changed: 30 additions & 1 deletion

File tree

apps/web/__tests__/unit/desktop-segments-recovery.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,28 @@ beforeEach(() => {
120120
});
121121

122122
describe("committed source recovery", () => {
123+
it.each([
124+
{ video_segments: [2, 3] },
125+
{ video_segments: [1, 3] },
126+
{ audio_init_uploaded: true, audio_segments: [] },
127+
{ audio_init_uploaded: false, audio_segments: [1] },
128+
{ audio_init_uploaded: true, audio_segments: [1, 3] },
129+
])(
130+
"does not queue a completed manifest with missing source declarations: %j",
131+
async (invalid) => {
132+
mocks.get.mockReturnValue(
133+
Effect.succeed(
134+
Option.some(JSON.stringify({ ...manifest, ...invalid })),
135+
),
136+
);
137+
expect(
138+
await completeDesktopSegmentsManifestAndQueue({ videoId, userId }),
139+
).toEqual({ status: "source-incomplete" });
140+
expect(mocks.queue).not.toHaveBeenCalled();
141+
expect(mocks.put).not.toHaveBeenCalled();
142+
},
143+
);
144+
123145
it("never marks an inactive but unfinished manifest complete", async () => {
124146
const incomplete = { ...manifest, is_complete: false };
125147
mocks.get.mockReturnValue(

apps/web/lib/desktop-segments-recovery.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
listRecoverableSegmentJobs,
2626
SourceCommitPendingError,
2727
} from "@/lib/desktop-recording-jobs";
28+
import { readCompletedRecordingManifest } from "@/lib/desktop-recording-verification";
2829
import {
2930
type DesktopSegmentsFinalizationStatus,
3031
queueDesktopSegmentsFinalization,
@@ -80,6 +81,7 @@ type LoadedDesktopSegmentsManifest =
8081
status: "loaded";
8182
video: typeof videos.$inferSelect;
8283
manifest: Video.SegmentManifestType;
84+
manifestJson: string;
8385
}
8486
| { status: "already-finalized" }
8587
| { status: "not-found" }
@@ -128,7 +130,7 @@ async function loadDesktopSegmentsManifest({
128130
)
129131
.pipe(Effect.mapError(getErrorMessage))
130132
.pipe(runPromise);
131-
return { status: "loaded", video, manifest };
133+
return { status: "loaded", video, manifest, manifestJson: json };
132134
} catch (error) {
133135
return { status: "invalid-manifest", error: getErrorMessage(error) };
134136
}
@@ -159,6 +161,11 @@ export async function completeDesktopSegmentsManifestAndQueue({
159161
return { status: "manifest-changed" };
160162
}
161163
if (!loaded.manifest.is_complete) return { status: "source-incomplete" };
164+
try {
165+
readCompletedRecordingManifest(loaded.manifestJson);
166+
} catch {
167+
return { status: "source-incomplete" };
168+
}
162169
try {
163170
const status = await queueDesktopSegmentsFinalization({
164171
videoId,

0 commit comments

Comments
 (0)