import { describe, it, expect } from "vitest"; import { blenderManifestSchema, createBlenderJobSchema } from "@/lib/render-pipeline/blender"; /** * The add-on and the server are separate codebases that only meet over JSON, * so the exact payload the add-on builds is pinned here. A field renamed on * either side otherwise surfaces as a 422 in front of an artist. */ describe("Blender submission contract", () => { // Verbatim shape of scene_summary() in BlenderPanel/vfxreview_submit.py const addonManifest = { blendFile: "X:/shows/UNG/cg/UNG_110_030_010_smoke.blend", scene: "Scene", frameStart: 1001, frameEnd: 1110, outputPath: "X:/shows/UNG/cg/renders/smoke/frame_", engine: "CYCLES", blenderVersion: "4.2.1", }; it("accepts what the add-on sends", () => { const parsed = blenderManifestSchema.safeParse(addonManifest); expect(parsed.success).toBe(true); }); it("accepts the full submission body", () => { const parsed = createBlenderJobSchema.safeParse({ projectCode: "UNG_S1", label: "UNG_110_030_010_smoke", manifest: addonManifest, submittedByEmail: "chris@twotalesanimation.com", urgent: false, }); expect(parsed.success).toBe(true); }); it("defaults the stage so the add-on need not send it", () => { const parsed = blenderManifestSchema.parse(addonManifest); expect(parsed.stage).toBe("BLENDER"); }); it("rejects a backwards frame range rather than queueing a no-op render", () => { const parsed = blenderManifestSchema.safeParse({ ...addonManifest, frameEnd: 900 }); expect(parsed.success).toBe(false); }); it("accepts EEVEE, which is what the add-on reports on a non-Cycles scene", () => { expect(blenderManifestSchema.safeParse({ ...addonManifest, engine: "BLENDER_EEVEE_NEXT" }).success).toBe(true); }); });