evOo-local/apps/mobile/lib/media-project.test.ts
Claude 4c30af9ea3
feat(media): mobile library index, resumable upload, and project variants
Index photos and videos from the device library into a client-pushed
ios_library source, upload originals in resumable chunks, and review,
approve, and share project variants with expo-video and the native share
sheet. Pure logic for chunking, resume, indexing, and variants is unit
tested.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NLSCTnhge4umMnemGXWr1w
2026-10-03 19:20:42 +00:00

300 lines
9.5 KiB
TypeScript

import type { MediaEditPlan, MediaRender } from "@rakazo/contracts";
import { describe, expect, it } from "vitest";
import {
assetThumbUrl,
blockers,
canApprove,
canShare,
formatDuration,
isRenderActive,
libraryAccess,
mediaNotificationRoute,
projectReadiness,
projectVariants,
renderFileUrl,
shareFileName,
shouldPollProject,
uploadsFromDevice,
variantState,
} from "./media-project";
function plan(
id: string,
profileId: string,
version: number,
createdAt = "2026-01-01T00:00:00.000Z",
) {
return { id, profileId, version, createdAt } as MediaEditPlan;
}
function render(id: string, planId: string, overrides: Partial<MediaRender> = {}): MediaRender {
return {
id,
planId,
projectId: "p1",
profileId: "example-short-vertical",
quality: "draft",
status: "done",
progress: 1,
outputs: [{ kind: "video", artifactId: null, fileName: "video.mp4", sizeBytes: 10 }],
gateReport: null,
ffprobe: null,
error: null,
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
...overrides,
};
}
const profiles = [{ id: "short", name: "Short" }];
function variantFor(renders: MediaRender[]) {
const [variant] = projectVariants({
profileIds: ["short"],
plans: [plan("plan1", "short", 1)],
renders,
profiles,
});
if (!variant) throw new Error("no variant");
return variant;
}
describe("projectVariants", () => {
it("uses the newest plan version per profile and its renders only", () => {
const variants = projectVariants({
profileIds: ["short", "wide", "missing"],
plans: [plan("a1", "short", 1), plan("a2", "short", 2), plan("b1", "wide", 1)],
renders: [
render("r-old", "a1", { createdAt: "2026-01-02T00:00:00.000Z" }),
render("r-new", "a2"),
],
profiles,
});
expect(
variants.map((variant) => [variant.profileId, variant.profileName, variant.plan.id]),
).toEqual([
["short", "Short", "a2"],
["wide", "wide", "b1"],
]);
expect(variants[0]?.latest?.id).toBe("r-new");
expect(variants[1]?.latest).toBeNull();
});
it("previews the final render once it is done, else the draft", () => {
const draft = render("d", "plan1", { createdAt: "2026-01-01T00:00:00.000Z" });
const finalRunning = render("f", "plan1", {
quality: "final",
status: "rendering",
outputs: [],
createdAt: "2026-01-02T00:00:00.000Z",
});
expect(variantFor([draft, finalRunning]).preview?.id).toBe("d");
const finalDone = { ...finalRunning, status: "done" as const, outputs: draft.outputs };
expect(variantFor([draft, finalDone]).preview?.id).toBe("f");
});
});
describe("variant state and actions", () => {
it("is planned without renders and can be approved", () => {
const variant = variantFor([]);
expect(variantState(variant)).toBe("planned");
expect(canApprove(variant)).toBe(true);
expect(canShare(variant)).toBe(false);
});
it("is rendering while a draft runs and cannot be approved", () => {
const variant = variantFor([render("d", "plan1", { status: "gating", progress: 0.9 })]);
expect(variantState(variant)).toBe("rendering");
expect(canApprove(variant)).toBe(false);
});
it("approves a finished draft", () => {
const variant = variantFor([render("d", "plan1")]);
expect(variantState(variant)).toBe("draft_ready");
expect(canApprove(variant)).toBe(true);
expect(canShare(variant)).toBe(false);
});
it("shares only a finished final render", () => {
const running = variantFor([render("f", "plan1", { quality: "final", status: "queued" })]);
expect(variantState(running)).toBe("approved_rendering");
expect(canShare(running)).toBe(false);
const done = variantFor([render("f", "plan1", { quality: "final" })]);
expect(variantState(done)).toBe("ready");
expect(canShare(done)).toBe(true);
expect(canApprove(done)).toBe(false);
});
it("never shares a blocked render and lists its blockers", () => {
const blocked = render("f", "plan1", {
quality: "final",
status: "blocked",
gateReport: {
profileId: "short",
passed: false,
checkedAt: "2026-01-01T00:00:00.000Z",
sampledFrames: 10,
autoFixRound: 3,
issues: [
{
check: "consent",
severity: "block",
code: "consent_missing",
message: "Consent missing",
},
{ check: "text", severity: "warn", code: "long", message: "Long caption" },
],
},
});
const variant = variantFor([blocked]);
expect(variantState(variant)).toBe("blocked");
expect(canShare(variant)).toBe(false);
expect(canApprove(variant)).toBe(false);
expect(blockers(variant.latest).map((issue) => issue.code)).toEqual(["consent_missing"]);
});
it("allows another try after a failure", () => {
const variant = variantFor([render("d", "plan1", { status: "failed", outputs: [] })]);
expect(variantState(variant)).toBe("failed");
expect(canApprove(variant)).toBe(true);
});
});
describe("polling", () => {
it("polls only while a render is queued, rendering, or gating", () => {
expect(isRenderActive(null)).toBe(false);
for (const status of ["queued", "rendering", "gating"] as const) {
expect(shouldPollProject([render("r", "p", { status })])).toBe(true);
}
for (const status of ["done", "blocked", "failed", "canceled"] as const) {
expect(shouldPollProject([render("r", "p", { status })])).toBe(false);
}
});
});
describe("projectReadiness", () => {
const device = "src-phone";
it("waits for this device's uploads first", () => {
expect(
projectReadiness(
[
{ sourceId: device, storage: "remote", ingestState: "awaiting_upload" },
{ sourceId: device, storage: "uploaded", ingestState: "analyzed" },
],
true,
device,
),
).toBe("uploading");
});
it("treats items another device must upload as processing", () => {
const assets = [
{ sourceId: "src-mac", storage: "remote" as const, ingestState: "awaiting_upload" as const },
];
expect(projectReadiness(assets, true, device)).toBe("processing");
expect(uploadsFromDevice(assets, device)).toEqual([]);
expect(uploadsFromDevice(assets, null)).toEqual([]);
});
it("waits for analysis when it is available", () => {
const assets = [
{ sourceId: device, storage: "uploaded" as const, ingestState: "proxied" as const },
];
expect(projectReadiness(assets, true, device)).toBe("processing");
expect(projectReadiness(assets, false, device)).toBe("ready");
});
it("ignores failed items unless all failed", () => {
expect(
projectReadiness(
[
{ sourceId: device, storage: "uploaded", ingestState: "failed" },
{ sourceId: device, storage: "uploaded", ingestState: "analyzed" },
],
true,
device,
),
).toBe("ready");
expect(
projectReadiness(
[{ sourceId: device, storage: "uploaded", ingestState: "failed" }],
true,
device,
),
).toBe("failed");
expect(projectReadiness([], true, device)).toBe("processing");
});
});
describe("urls and names", () => {
it("builds render and thumbnail routes", () => {
expect(renderFileUrl("https://api.example.test/", "r1", "video.mp4")).toBe(
"https://api.example.test/api/media/renders/r1/files/video.mp4",
);
expect(renderFileUrl("https://api.example.test", "r1", "video.mp4", true)).toBe(
"https://api.example.test/api/media/renders/r1/files/video.mp4?download=1",
);
expect(assetThumbUrl("https://api.example.test", "a 1")).toBe(
"https://api.example.test/api/media/assets/a%201/thumb",
);
});
it("names shared files from the render id", () => {
expect(shareFileName("r1", "video.MP4")).toBe("render-r1.mp4");
expect(shareFileName("../r1", "video")).toBe("render-r1.mp4");
});
it("formats durations", () => {
expect(formatDuration(0)).toBe("0:00");
expect(formatDuration(65_400)).toBe("1:05");
expect(formatDuration(3_725_000)).toBe("1:02:05");
});
});
describe("mediaNotificationRoute", () => {
it("opens the project of a render push", () => {
expect(
mediaNotificationRoute({
kind: "media.render",
projectId: "p1",
renderId: "r1",
status: "done",
}),
).toEqual({ pathname: "/media-project", params: { projectId: "p1" } });
});
it("ignores other pushes and malformed ids", () => {
expect(mediaNotificationRoute(null)).toBeNull();
expect(mediaNotificationRoute({ kind: "completion", threadId: "t1" })).toBeNull();
expect(mediaNotificationRoute({ kind: "media.render", projectId: "../x" })).toBeNull();
expect(mediaNotificationRoute({ kind: "media.render" })).toBeNull();
});
});
describe("libraryAccess", () => {
it("distinguishes full, limited, undecided, denied, and blocked access", () => {
expect(
libraryAccess({
granted: true,
canAskAgain: true,
status: "granted",
accessPrivileges: "all",
}),
).toBe("all");
expect(
libraryAccess({
granted: true,
canAskAgain: true,
status: "granted",
accessPrivileges: "limited",
}),
).toBe("limited");
expect(libraryAccess({ granted: false, canAskAgain: true, status: "undetermined" })).toBe(
"undetermined",
);
expect(libraryAccess({ granted: false, canAskAgain: true, status: "denied" })).toBe("denied");
expect(libraryAccess({ granted: false, canAskAgain: false, status: "denied" })).toBe("blocked");
});
});