From 7ca833b53c5788811b8827ae6c3a39755a2c4146 Mon Sep 17 00:00:00 2001 From: 3mdistal <86723305+3mdistal@users.noreply.github.com> Date: Fri, 23 Oct 2026 02:44:09 -0400 Subject: [PATCH 1/2] Respect Clip availability during video playback --- ...deo-playback-respects-clip-availability.md | 6 ++ .../api/video/[recordingId].get.test.ts | 60 +++++++++++++++++++ .../routes/api/video/[recordingId].get.ts | 5 +- 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 templates/clips/changelog/2026-10-23-video-playback-respects-clip-availability.md diff --git a/templates/clips/changelog/2026-10-23-video-playback-respects-clip-availability.md b/templates/clips/changelog/2026-10-23-video-playback-respects-clip-availability.md new file mode 100644 index 00000000000..dcf14fd0dce --- /dev/null +++ b/templates/clips/changelog/2026-10-23-video-playback-respects-clip-availability.md @@ -0,0 +1,6 @@ +--- +type: fixed +date: 2026-10-23 +--- + +Video playback respects Clip availability. diff --git a/templates/clips/server/routes/api/video/[recordingId].get.test.ts b/templates/clips/server/routes/api/video/[recordingId].get.test.ts index b24f0153735..4c1c83a4ff2 100644 --- a/templates/clips/server/routes/api/video/[recordingId].get.test.ts +++ b/templates/clips/server/routes/api/video/[recordingId].get.test.ts @@ -907,6 +907,66 @@ describe("/api/video/:recordingId route", () => { expect(fetch).not.toHaveBeenCalled(); }); + it.each([null, "viewer", "editor", "admin", "owner"])( + "does not serve footage from a taken-down Clip to %s", + async (role) => { + const clip = { + visibility: "public", + password: null, + failureCode: "taken_down", + }; + mockGetDb.mockReturnValue( + createDbWithSelectSequence( + [{ recordingId: "clip-1" }], + ...(role ? [] : [[clip]]), + [footageRow], + ), + ); + mockResolveAccess.mockResolvedValue( + role ? { role, resource: clip } : null, + ); + vi.mocked(fetch).mockResolvedValue( + new Response("media", { + headers: { "content-type": "video/mp4" }, + }), + ); + + const event = makeEvent(); + const result = await handler(event as any); + + expect(event.status).toBe(404); + expect(result).toEqual({ error: "Not found" }); + expect(mockReadAppState).not.toHaveBeenCalled(); + expect(mockFetchS3ObjectByUrl).not.toHaveBeenCalled(); + expect(fetch).not.toHaveBeenCalled(); + }, + ); + + it.each(["viewer", "owner"])( + "does not serve taken-down footage under a healthy Clip to a %s", + async (role) => { + mockGetDb.mockReturnValue( + createDbWithSelectSequence( + [{ recordingId: "clip-1" }], + [{ ...footageRow, failureCode: "taken_down" }], + ), + ); + mockResolveAccess.mockResolvedValue({ + role, + resource: { visibility: "public", password: null }, + }); + + const event = makeEvent(); + const result = await handler(event as any); + + expect(event.status).toBe(404); + expect(result).toEqual({ error: "Not found" }); + expect(mockReadAppState).not.toHaveBeenCalled(); + expect(mockFetchS3ObjectByUrl).not.toHaveBeenCalled(); + expect(fetch).not.toHaveBeenCalled(); + }, + ); + it("requires the clip's password, not the footage's", async () => { mockGetDb.mockReturnValue( createDbWithSelectSequence([{ recordingId: "clip-1" }], [footageRow]), diff --git a/templates/clips/server/routes/api/video/[recordingId].get.ts b/templates/clips/server/routes/api/video/[recordingId].get.ts index 94718d344ea..6162aed5dd1 100644 --- a/templates/clips/server/routes/api/video/[recordingId].get.ts +++ b/templates/clips/server/routes/api/video/[recordingId].get.ts @@ -407,7 +407,10 @@ export default defineEventHandler(async (event: H3Event) => { rec = footage as RecordingRow; } - if (rec.failureCode === "taken_down") { + if ( + gate.failureCode === "taken_down" || + rec.failureCode === "taken_down" + ) { setResponseStatus(event, 404); return { error: "Not found" }; } From 081e6d8e5bb73f0953af67f6872e3a2d19f142fc Mon Sep 17 00:00:00 2001 From: 3mdistal <86723305+3mdistal@users.noreply.github.com> Date: Tue, 27 Oct 2026 08:59:55 -0400 Subject: [PATCH 2/2] Use typed video route test events --- .../server/routes/api/video/[recordingId].get.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/templates/clips/server/routes/api/video/[recordingId].get.test.ts b/templates/clips/server/routes/api/video/[recordingId].get.test.ts index 4c1c83a4ff2..50af1cab581 100644 --- a/templates/clips/server/routes/api/video/[recordingId].get.test.ts +++ b/templates/clips/server/routes/api/video/[recordingId].get.test.ts @@ -932,7 +932,9 @@ describe("/api/video/:recordingId route", () => { ); const event = makeEvent(); - const result = await handler(event as any); + const result = await handler( + event as unknown as Parameters[0], + ); expect(event.status).toBe(404); expect(result).toEqual({ error: "Not found" }); @@ -957,7 +959,9 @@ describe("/api/video/:recordingId route", () => { }); const event = makeEvent(); - const result = await handler(event as any); + const result = await handler( + event as unknown as Parameters[0], + ); expect(event.status).toBe(404); expect(result).toEqual({ error: "Not found" });