Files
vfxreview/lib/render-pipeline/delivery-request-state.test.ts
T
twotalesanimation f30acef101
Deploy / deploy (push) Successful in 5m17s
feat(deliveries): delivery requests and upload/share evidence
A built package used to become DELIVERED on one click with nothing behind
it, and nothing recorded what a client had asked for.

- Marking delivered is now two recorded steps: Record upload (the link on
  the client's portal, contents confirmed) then Share link (who it was sent
  to). The second is refused until the first is on record.
- Delivery requests log a client's ask by episode, pasted shot codes or a
  Shot Status selection. A line closes only when its shot goes out with
  evidence after the request was logged; a request can otherwise only be
  cancelled with a reason.
- Delivered Outside Pipeline takes an upload link and recipient, required
  for shots on an open request.
- The dashboard gains an Awaiting delivery section listing open requests
  and built packages the client does not have yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 14:21:24 +02:00

114 lines
4.5 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
createRequestSchema,
matchShotCodes,
parseShotCodes,
resolveLineStage,
waitingSeverity,
} from "./delivery-request-state";
describe("parseShotCodes", () => {
it("takes codes out of a pasted message whatever separates them", () => {
expect(parseShotCodes("UNG_116_010, UNG_116_020\nUNG_116_030; 116-A-0040")).toEqual([
"UNG_116_010",
"UNG_116_020",
"UNG_116_030",
"116-A-0040",
]);
});
it("drops repeats regardless of case", () => {
expect(parseShotCodes("ung_116_010 UNG_116_010")).toEqual(["ung_116_010"]);
});
});
describe("matchShotCodes", () => {
const shots = [
{ id: "a", shotCode: "UNG_116_010", clientCode: "116_A_0010" },
{ id: "b", shotCode: "UNG_116_020", clientCode: null },
];
it("matches our code or the client's, in any case", () => {
const { matched, unmatched } = matchShotCodes(["116_a_0010", "ung_116_020"], shots);
expect(matched.map((s) => s.id)).toEqual(["a", "b"]);
expect(unmatched).toEqual([]);
});
it("reports what it could not match instead of dropping it", () => {
expect(matchShotCodes(["UNG_116_010", "UNG_116_999"], shots).unmatched).toEqual(["UNG_116_999"]);
});
it("counts a shot once when both of its codes are given", () => {
expect(matchShotCodes(["UNG_116_010", "116_A_0010"], shots).matched).toHaveLength(1);
});
});
describe("resolveLineStage", () => {
const logged = new Date("2026-09-30T14:00:00Z");
const before = new Date("2026-09-20T10:00:00Z");
const after = new Date("2026-10-01T10:00:00Z");
const base = {
requestCreatedAt: logged,
packages: [],
manual: { deliveredAt: null, url: null, sharedWith: null },
exportStatuses: [],
};
it("walks a shot from not ready through to delivered", () => {
expect(resolveLineStage(base)).toBe("NOT_READY");
expect(resolveLineStage({ ...base, exportStatuses: ["READY_FOR_QC"] })).toBe("IN_QC");
expect(resolveLineStage({ ...base, exportStatuses: ["READY_FOR_QC", "READY_FOR_DELIVERY"] })).toBe("READY");
const pkg = { status: "BUILDING", uploadedAt: null, deliveredAt: null };
expect(resolveLineStage({ ...base, packages: [pkg] })).toBe("BUILDING");
expect(resolveLineStage({ ...base, packages: [{ ...pkg, status: "READY" }] })).toBe("BUILT");
expect(resolveLineStage({ ...base, packages: [{ ...pkg, status: "READY", uploadedAt: after }] })).toBe("UPLOADED");
expect(
resolveLineStage({ ...base, packages: [{ status: "DELIVERED", uploadedAt: after, deliveredAt: after }] })
).toBe("DELIVERED");
});
it("does not let a delivery from before the request close it", () => {
expect(
resolveLineStage({ ...base, packages: [{ status: "DELIVERED", uploadedAt: before, deliveredAt: before }] })
).toBe("NOT_READY");
});
it("closes on a manual delivery only with a link and a recipient", () => {
const sameDay = new Date("2026-09-30T12:00:00Z");
const manual = { deliveredAt: sameDay, url: "https://x.example/f", sharedWith: "Sam" };
expect(resolveLineStage({ ...base, manual })).toBe("DELIVERED");
expect(resolveLineStage({ ...base, manual: { ...manual, url: null } })).toBe("NOT_READY");
expect(resolveLineStage({ ...base, manual: { ...manual, sharedWith: null } })).toBe("NOT_READY");
expect(resolveLineStage({ ...base, manual: { ...manual, deliveredAt: before } })).toBe("NOT_READY");
});
});
describe("waitingSeverity", () => {
const now = new Date("2026-09-30T12:00:00Z").getTime();
const hoursAgo = (h: number) => new Date(now - h * 3600_000);
it("escalates with age", () => {
expect(waitingSeverity(hoursAgo(2), null, now)).toBe("normal");
expect(waitingSeverity(hoursAgo(30), null, now)).toBe("warning");
expect(waitingSeverity(hoursAgo(50), null, now)).toBe("critical");
});
it("is critical once past the due date, however new", () => {
expect(waitingSeverity(hoursAgo(2), hoursAgo(1), now)).toBe("critical");
});
});
describe("createRequestSchema", () => {
const base = { projectId: "p", deliverable: "EXR", requestedBy: "Sam" };
it("needs exactly one way of naming the shots", () => {
expect(createRequestSchema.safeParse(base).success).toBe(false);
expect(createRequestSchema.safeParse({ ...base, episode: "116" }).success).toBe(true);
expect(createRequestSchema.safeParse({ ...base, episode: "116", shotCodes: "UNG_116_010" }).success).toBe(false);
});
it("needs to know who asked", () => {
expect(createRequestSchema.safeParse({ ...base, episode: "116", requestedBy: " " }).success).toBe(false);
});
});