evOo-local/packages/db/src/media.test.ts
Claude 1aab5ab3a8
feat(media): expose asset externalId, filter by ingest state, push device deletions
The desktop Photos sync needs to map awaiting assets back to Photos items,
query only awaiting uploads, and report deletions without a full relisting.

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

274 lines
7.4 KiB
TypeScript

import type { ClientMediaItem } from "@rakazo/contracts";
import { describe, expect, it } from "vitest";
import type {
MediaAsset as MediaAssetRow,
MediaMark as MediaMarkRow,
MediaRender as MediaRenderRow,
MediaUpload as MediaUploadRow,
} from "./client.js";
import {
clientMediaItemToSourceItem,
decodeKeysetCursor,
encodeKeysetCursor,
InvalidMediaInputError,
likeContains,
mapMediaAsset,
mapMediaMark,
mapMediaRender,
mapMediaSegment,
mapMediaUpload,
pageLimit,
toPage,
} from "./media.js";
const at = new Date("2026-01-01T08:00:00.000Z");
describe("media keyset cursors", () => {
it("round-trips dated and undated positions as opaque base64url", () => {
for (const cursor of [
{ at, id: "asset-1" },
{ at: null, id: "asset-2" },
]) {
const token = encodeKeysetCursor(cursor);
expect(token).toMatch(/^[A-Za-z0-9_-]+$/);
expect(decodeKeysetCursor(token)).toEqual(cursor);
}
});
it("rejects tampered or malformed cursors", () => {
const bad = [
"not json",
Buffer.from("{}").toString("base64url"),
Buffer.from(JSON.stringify(["2026-01-01T00:00:00Z"])).toString("base64url"),
Buffer.from(JSON.stringify(["yesterday", "id"])).toString("base64url"),
Buffer.from(JSON.stringify([null, ""])).toString("base64url"),
Buffer.from(JSON.stringify([42, "id"])).toString("base64url"),
];
for (const token of bad) {
expect(() => decodeKeysetCursor(token), token).toThrow(InvalidMediaInputError);
}
});
it("slices a limit+1 result into a page with the last row as cursor", () => {
const rows = [
{ id: "c", at },
{ id: "b", at: null },
{ id: "a", at: null },
];
const page = toPage(
rows,
2,
(row) => ({ at: row.at, id: row.id }),
(row) => row.id,
);
expect(page.items).toEqual(["c", "b"]);
expect(decodeKeysetCursor(page.nextCursor as string)).toEqual({ at: null, id: "b" });
expect(
toPage(
rows,
3,
(row) => ({ at: row.at, id: row.id }),
(row) => row.id,
).nextCursor,
).toBe(null);
});
it("clamps page sizes and escapes LIKE wildcards", () => {
expect(pageLimit(undefined)).toBe(100);
expect(pageLimit(0)).toBe(1);
expect(pageLimit(10_000)).toBe(500);
expect(likeContains("50%_off\\")).toBe("%50\\%\\_off\\\\%");
});
});
describe("media DTO mappers", () => {
const asset: MediaAssetRow = {
id: "asset-1",
spaceId: "space-1",
sourceId: "source-1",
externalId: "a.mp4",
revision: "r1",
contentHash: "hash",
kind: "video",
storage: "referenced",
storagePath: "originals/a.mp4",
fileName: "a.mp4",
sizeBytes: 10n,
capturedAt: at,
durationMs: 1000,
width: 1920,
height: 1080,
fps: 30,
codec: "h264",
orientation: 0,
gpsLat: 48.21,
gpsLon: 16.37,
deviceModel: "Camera",
albumNames: ["Trip"],
isFavorite: true,
proxyPath: "proxy.mp4",
thumbPath: null,
spritePath: null,
qualityScore: 0.5,
analysisVersion: 1,
ingestState: "proxied",
ingestError: null,
createdAt: at,
updatedAt: at,
};
it("maps assets without leaking paths or hashes", () => {
const dto = mapMediaAsset(asset);
expect(dto).toEqual({
id: "asset-1",
sourceId: "source-1",
externalId: asset.externalId,
kind: "video",
storage: "referenced",
capturedAt: at.toISOString(),
durationMs: 1000,
width: 1920,
height: 1080,
fps: 30,
gpsLat: 48.21,
gpsLon: 16.37,
deviceModel: "Camera",
albumNames: ["Trip"],
isFavorite: true,
ingestState: "proxied",
hasProxy: true,
hasThumbnail: false,
qualityScore: 0.5,
createdAt: at.toISOString(),
});
expect(JSON.stringify(dto)).not.toContain("proxy.mp4");
expect(JSON.stringify(dto)).not.toContain("originals");
});
it("keeps only numeric segment scores", () => {
const segment = mapMediaSegment({
id: "seg-1",
spaceId: "space-1",
assetId: "asset-1",
kind: "shot",
startMs: 0,
endMs: 100,
scores: { motion: 0.4, faces: "many", sharpness: null },
teaserScore: null,
transcriptExcerpt: null,
labels: [],
paidOnly: true,
analysisVersion: 1,
createdAt: at,
});
expect(segment.scores).toEqual({ motion: 0.4 });
expect(segment.paidOnly).toBe(true);
});
it("drops internal render output paths and invalid reports", () => {
const row: MediaRenderRow = {
id: "render-1",
spaceId: "space-1",
projectId: "project-1",
planId: "plan-1",
profileId: "example-short-vertical",
quality: "draft",
status: "done",
progress: 1.5,
outputs: [
{ kind: "video", artifactId: null, fileName: "a.mp4", sizeBytes: 1, path: "/abs/a.mp4" },
{ kind: "nonsense" },
],
gateReport: { passed: "maybe" },
ffprobe: null,
autoFixRound: 0,
error: null,
createdBy: "user-1",
createdAt: at,
updatedAt: at,
};
const dto = mapMediaRender(row);
expect(dto.progress).toBe(1);
expect(dto.outputs).toEqual([
{ kind: "video", artifactId: null, fileName: "a.mp4", sizeBytes: 1 },
]);
expect(dto.gateReport).toBeNull();
});
it("sorts upload chunks and converts sizes", () => {
const row: MediaUploadRow = {
id: "upload-1",
spaceId: "space-1",
assetId: "asset-1",
userId: "user-1",
sizeBytes: 21n * 1024n * 1024n * 1024n,
chunkBytes: 8,
sha256: "a".repeat(64),
fileName: "a.mov",
receivedChunks: [3, 0, 2],
status: "open",
createdAt: at,
updatedAt: at,
};
expect(mapMediaUpload(row)).toEqual({
id: "upload-1",
assetId: "asset-1",
sizeBytes: 21 * 1024 * 1024 * 1024,
chunkBytes: 8,
receivedChunks: [0, 2, 3],
status: "open",
});
});
it("maps mark prompts and drops malformed ones", () => {
const row: MediaMarkRow = {
id: "mark-1",
spaceId: "space-1",
kind: "tattoo",
label: null,
personId: null,
assetId: "asset-1",
prompt: { tMs: 10, point: { x: 0.5, y: 0.5 } },
treatment: "blur",
maskDir: "masks/mark-1",
qualityScore: null,
suggestedFallback: null,
createdAt: at,
updatedAt: at,
};
expect(mapMediaMark(row).prompt).toEqual({ tMs: 10, point: { x: 0.5, y: 0.5 } });
expect(mapMediaMark({ ...row, prompt: { tMs: -1 } }).prompt).toBeNull();
expect(mapMediaMark(row)).not.toHaveProperty("maskDir");
});
});
describe("client media items", () => {
const item: ClientMediaItem = {
externalId: "local-1",
kind: "video",
capturedAt: at.toISOString(),
durationMs: 1000,
width: 1080,
height: 1920,
fps: 30,
gpsLat: 48.2085,
gpsLon: 16.3721,
deviceModel: "Phone",
albumNames: ["Trip"],
isFavorite: false,
fileName: "IMG_0001.MOV",
};
it("derives a revision that changes with any metadata change", () => {
const base = clientMediaItemToSourceItem(item);
expect(base).toMatchObject({ externalId: "local-1", capturedAt: at, sizeBytes: null });
expect(clientMediaItemToSourceItem({ ...item }).revision).toBe(base.revision);
expect(clientMediaItemToSourceItem({ ...item, isFavorite: true }).revision).not.toBe(
base.revision,
);
expect(clientMediaItemToSourceItem({ ...item, albumNames: [] }).revision).not.toBe(
base.revision,
);
});
});