Claude a6598f09d9
test(media): cover clustering, EDL, compiler, teaser, gate and marketing
Includes compiler snapshots, injection-proof escaping checks, and an
ffmpeg integration test on synthetic testsrc/sine inputs that is skipped
when ffmpeg is not installed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0138YqDj3ttfayRwuMKKrqVj
2026-10-03 18:06:02 +00:00

338 lines
10 KiB
TypeScript

import type { EdlError } from "@rakazo/contracts";
import { EdlErrorSchema } from "@rakazo/contracts";
import { describe, expect, it } from "vitest";
import type { EdlContext } from "./edl.js";
import { edlDurationMs, locateOutputTime, locateOutputTimeAll, validateEdl } from "./edl.js";
import { testContext, testEdl, testProfile } from "./test-fixtures.js";
/** Raw plan (pre-defaults) so cases can break it in any way. */
function raw(patch: (plan: Record<string, any>) => void): unknown {
const plan = JSON.parse(JSON.stringify(testEdl())) as Record<string, any>;
patch(plan);
return plan;
}
function codes(input: unknown, context: EdlContext = testContext()): EdlError["code"][] {
const result = validateEdl(input, context);
return result.ok ? [] : result.errors.map((e) => e.code);
}
describe("validateEdl", () => {
it("accepts a valid plan and returns the parsed EDL", () => {
const result = validateEdl(testEdl(), testContext());
expect(result.ok).toBe(true);
if (result.ok) expect(result.edl.tracks.video).toHaveLength(2);
});
const cases: Array<{
code: EdlError["code"];
input: unknown;
context?: Partial<EdlContext>;
path?: string;
}> = [
{ code: "schema", input: raw((p) => (p.edlVersion = 2)), path: "edlVersion" },
{
code: "schema",
input: raw((p) => (p.tracks.video[1].id = "c1")),
path: "tracks.video[1].id",
},
{
code: "schema",
input: raw((p) => (p.tracks.video[0].id = "Bad Id;[x]")),
path: "tracks.video[0].id",
},
{
code: "unknown_asset",
input: raw((p) => (p.tracks.video[0].assetId = "missing")),
path: "tracks.video[0].assetId",
},
{
code: "unknown_clip",
input: raw((p) => {
p.transitions = [{ fromClipId: "c1", toClipId: "c9", kind: "cut", durationMs: 0 }];
}),
path: "transitions[0].toClipId",
},
{
code: "unknown_clip",
input: raw((p) => {
p.redactions.regions = [
{
id: "r1",
clipId: "zz",
keyframes: [{ tMs: 0, box: { x: 0, y: 0, w: 0.1, h: 0.1 } }],
treatment: "blur",
},
];
}),
},
{
code: "unknown_artifact",
input: raw((p) => {
p.tracks.audio = [
{ id: "m", kind: "sfx", artifactId: "nope", inMs: 0, outMs: 1000, atMs: 0 },
];
}),
},
{
code: "unknown_artifact",
input: raw((p) => (p.overlays = [{ id: "w", kind: "watermark", atMs: 0, durationMs: 1000 }])),
context: { brandKit: null },
},
{
code: "unknown_mark",
input: raw((p) => (p.redactions.marks = [{ markId: "mark-x", treatment: "blur" }])),
},
{
code: "unknown_person",
input: raw((p) => {
p.redactions.faces = [{ id: "f", target: { personId: "person-x" }, mode: "blur" }];
}),
},
{
code: "unknown_track",
input: raw((p) => {
p.redactions.faces = [{ id: "f", target: { trackId: "track-x" }, mode: "pixelate" }];
}),
},
{
code: "unknown_template",
input: raw((p) => (p.intro = { templateId: "intro-none", durationMs: 1000 })),
path: "intro.templateId",
},
{
code: "unknown_template",
input: raw((p) => (p.captions = { mode: "burned", presetId: "missing" })),
path: "captions.presetId",
},
{
code: "range_out_of_asset",
input: raw((p) => (p.tracks.video[1].outMs = 12_000)),
path: "tracks.video[1].outMs",
},
{
code: "range_out_of_asset",
input: raw((p) => (p.tracks.video[0].reframe = [{ tMs: 9000, cx: 0.5, cy: 0.5, scale: 1 }])),
},
{
code: "range_empty",
input: raw((p) => (p.tracks.video[0].outMs = 0)),
},
{
code: "range_empty",
input: raw(
(p) => (p.overlays = [{ id: "t", kind: "text", atMs: 0, durationMs: 0, text: "x" }]),
),
},
{
code: "overlap",
input: raw((p) => (p.tracks.video[1].atMs = 1500)),
path: "tracks.video[1].atMs",
},
{
code: "overlap",
// A crossfade needs exactly its duration of overlap.
input: raw((p) => {
p.transitions = [{ fromClipId: "c1", toClipId: "c2", kind: "crossfade", durationMs: 500 }];
}),
},
{
code: "overlap",
input: raw((p) => {
p.tracks.audio = [
{ id: "m1", kind: "music", artifactId: "music-1", inMs: 0, outMs: 3000, atMs: 0 },
{ id: "m2", kind: "music", artifactId: "music-1", inMs: 0, outMs: 3000, atMs: 2000 },
];
}),
},
{
code: "duration_below_min",
input: raw((p) => (p.output.durationHardMinMs = 5000)),
},
{
code: "duration_below_min",
input: testEdl(),
context: { profile: testProfile({ duration: { hardMinMs: 6000, hardMaxMs: 60_000 } }) },
},
{
code: "duration_above_max",
input: raw((p) => (p.output.durationHardMaxMs = 3000)),
},
{
code: "duration_above_max",
input: testEdl(),
context: { profile: testProfile({ duration: { hardMinMs: 0, hardMaxMs: 3000 } }) },
},
{
code: "overlay_outside_safe_zone",
input: raw((p) => {
p.overlays = [
{
id: "t",
kind: "text",
atMs: 0,
durationMs: 1000,
text: "Hi",
box: { x: 0.1, y: 0.85, w: 0.5, h: 0.1 },
},
];
}),
path: "overlays[0].box",
},
{
code: "paid_only_segment",
input: testEdl(),
context: { paidOnly: [{ assetId: "asset-a", startMs: 6500, endMs: 9000 }] },
path: "tracks.video[1]",
},
{
code: "paid_only_segment",
input: testEdl({ output: { ...testEdl().output, profileId: "example-paid-video" } }),
context: {
profile: testProfile({ id: "example-paid-video", kind: "paid_video" }),
paidOnly: [{ assetId: "asset-a", startMs: 0, endMs: 100 }],
goal: "teaser",
},
},
{
code: "profile_mismatch",
input: raw((p) => (p.output.profileId = "other-profile")),
},
{
code: "profile_mismatch",
input: raw((p) => {
p.output.width = 1920;
p.output.height = 1080;
}),
},
{
code: "music_license",
input: raw((p) => {
p.tracks.audio = [
{ id: "m", kind: "music", artifactId: "music-2", inMs: 0, outMs: 1000, atMs: 0 },
];
}),
},
{
code: "music_license",
input: raw((p) => {
p.tracks.audio = [
{ id: "m", kind: "music", artifactId: "vo-1", inMs: 0, outMs: 1000, atMs: 0 },
];
}),
},
{
code: "music_license",
input: raw((p) => {
p.tracks.audio = [
{ id: "m", kind: "music", artifactId: "music-1", inMs: 0, outMs: 1000, atMs: 0 },
];
}),
context: { profile: testProfile({ audio: { music: "platform_library_only" } }) },
},
];
it.each(cases)("rejects $code ($path)", ({ code, input, context, path }) => {
const result = validateEdl(input, testContext(context));
expect(result.ok).toBe(false);
if (result.ok) return;
for (const error of result.errors) expect(EdlErrorSchema.safeParse(error).success).toBe(true);
const match = result.errors.find((e) => e.code === code);
expect(match, JSON.stringify(result.errors)).toBeDefined();
if (path) expect(match!.path).toBe(path);
});
it("covers every error code", () => {
const covered = new Set(cases.map((c) => c.code));
expect([...covered].sort()).toEqual([...EdlErrorSchema.shape.code.options].sort());
});
it("allows paid-only material in a full release for a paid profile and any music where allowed", () => {
const paid = testProfile({
id: "example-paid-video",
kind: "paid_video",
audio: { music: "any" },
});
const plan = raw((p) => {
p.output.profileId = "example-paid-video";
p.tracks.audio = [
{ id: "m", kind: "music", artifactId: "music-2", inMs: 0, outMs: 1000, atMs: 0 },
];
});
expect(
codes(
plan,
testContext({ profile: paid, paidOnly: [{ assetId: "asset-a", startMs: 0, endMs: 9000 }] }),
),
).toEqual([]);
});
it("accepts photos without a duration and a crossfade with the right overlap", () => {
const plan = raw((p) => {
p.tracks.video[0].assetId = "photo-1";
p.tracks.video[1].atMs = 1500;
p.transitions = [{ fromClipId: "c1", toClipId: "c2", kind: "crossfade", durationMs: 500 }];
});
expect(codes(plan)).toEqual([]);
});
it("reports every failure at once", () => {
const plan = raw((p) => {
p.tracks.video[0].assetId = "missing";
p.tracks.video[1].outMs = 4000;
p.redactions.marks = [{ markId: "mark-x", treatment: "cover" }];
});
expect(codes(plan)).toEqual(["unknown_asset", "range_empty", "unknown_mark"]);
});
});
describe("timeline", () => {
const edl = testEdl({
intro: { templateId: "intro-minimal", durationMs: 1000 },
outro: { templateId: "outro-simple", durationMs: 500 },
tracks: {
video: [
{ id: "c1", assetId: "asset-a", inMs: 0, outMs: 2000, atMs: 0, reframe: "none" },
{
id: "c2",
assetId: "asset-b",
inMs: 1000,
outMs: 5000,
atMs: 1500,
speed: 2,
reframe: "none",
},
],
},
transitions: [{ fromClipId: "c1", toClipId: "c2", kind: "dip", durationMs: 500 }],
});
it("measures intro, speed, transition overlap and outro", () => {
expect(edlDurationMs(edl)).toBe(1000 + 1500 + 2000 + 500);
expect(edlDurationMs(testEdl())).toBe(4000);
});
it("maps output time back to clip and source time", () => {
expect(locateOutputTime(edl, 500)).toBeNull();
expect(locateOutputTime(edl, 1000)).toEqual({ clipId: "c1", assetId: "asset-a", sourceMs: 0 });
expect(locateOutputTime(edl, 2200)).toEqual({
clipId: "c1",
assetId: "asset-a",
sourceMs: 1200,
});
// Inside the transition both clips are visible; the incoming one wins.
expect(locateOutputTimeAll(edl, 2700).map((h) => h.clipId)).toEqual(["c1", "c2"]);
expect(locateOutputTime(edl, 2700)).toEqual({
clipId: "c2",
assetId: "asset-b",
sourceMs: 1400,
});
expect(locateOutputTime(edl, 4400)).toEqual({
clipId: "c2",
assetId: "asset-b",
sourceMs: 4800,
});
expect(locateOutputTime(edl, 4600)).toBeNull();
});
});