Files
jhonny-editor/bm/premiere-pro-mcp-main/tests/uxp/protocol.test.ts
T
João Henrique b541f502ba feat: initial commit - Jhonny Editor
- Adicionado estrutura completa do projeto
- Configurado MCP server para Premiere Pro
- Adicionado documentação e skills
- Configurado Gitignore para o projeto
2026-09-08 09:59:31 -04:00

162 lines
7.4 KiB
TypeScript
Executable File

import { createRequire } from "node:module";
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
const require = createRequire(import.meta.url);
const protocol = require("../../uxp-plugin/protocol.cjs");
describe("UXP bridge protocol", () => {
it("builds versioned envelopes", () => {
const result = protocol.envelope("event", { name: "changed" }, "r1");
expect(result).toMatchObject({ protocolVersion: 2, type: "event", requestId: "r1", payload: { name: "changed" } });
expect(Date.parse(result.sentAt)).not.toBeNaN();
});
it("parses commands with safe defaults", () => {
expect(protocol.parseCommand('{"type":"command","command":"state.get"}')).toEqual({ requestId: null, command: "state.get", args: {} });
});
it.each([
"projectSelection.views",
"bins.createSmart",
"sequenceSettings.update",
"parameters.keyframeAdd",
"timeline.cloneSelection",
"sequences.createFromMedia",
"encoder.projectItem",
])("accepts registered lower-camel command segments: %s", (command) => {
expect(protocol.parseCommand({ type: "command", command })).toMatchObject({ command });
});
it("rejects malformed commands", () => expect(() => protocol.parseCommand({ type: "event" })).toThrow("Invalid UXP bridge command"));
it("rejects invalid protocol versions and argument shapes", () => {
expect(() => protocol.parseCommand({ protocolVersion: 1, type: "command", command: "state.get" })).toThrow("Unsupported UXP protocol version");
expect(() => protocol.parseCommand({ type: "command", command: "state.get", args: [] })).toThrow("args must be an object");
expect(() => protocol.parseCommand({ type: "command", command: "../state" })).toThrow("Invalid UXP bridge command");
expect(() => protocol.parseCommand({ type: "command", command: "ProjectSelection.views" })).toThrow("Invalid UXP bridge command");
expect(() => protocol.parseCommand({ type: "command", command: "project_selection.views" })).toThrow("Invalid UXP bridge command");
});
it("bounds request identifiers and command size", () => {
expect(() => protocol.parseCommand({ type: "command", requestId: "", command: "state.get" })).toThrow("requestId");
const oversized = JSON.stringify({ type: "command", command: "state.get", padding: "x".repeat(protocol.MAX_COMMAND_BYTES) });
expect(() => protocol.parseCommand(oversized)).toThrow("64 KiB");
});
it("bounds complete result envelopes by UTF-8 bytes", () => {
expect(protocol.utf8ByteLength("aé😀")).toBe(7);
expect(() => protocol.assertResultSize({
projectMetadata: "😀".repeat(170_000),
xmpMetadata: "😀".repeat(170_000),
})).toThrow("1 MiB");
expect(protocol.serializeEnvelope(protocol.envelope("result", { ok: true, result: { value: "small" } }, "r1")))
.toContain('"type":"result"');
});
it("pre-serializes a success result before publishing its completed event", () => {
const panel = readFileSync(new URL("../../uxp-plugin/index.cjs", import.meta.url), "utf8");
const dispatchStart = panel.indexOf("async function dispatch(raw)");
const validation = panel.indexOf("Protocol.serializeEnvelope(response);", dispatchStart);
const completion = panel.indexOf('publishOperation("completed"', dispatchStart);
expect(validation).toBeGreaterThan(dispatchStart);
expect(completion).toBeGreaterThan(validation);
});
it("routes transcript imports through the replay-aware command registry", () => {
const panel = readFileSync(new URL("../../uxp-plugin/index.cjs", import.meta.url), "utf8");
expect(panel).toContain("transcriptImportHandler: transcriptImportRuntime && transcriptImportRuntime.importTranscript");
expect(panel).not.toContain('cmd.command === "transcript.import") result = await importTranscript');
});
it("registers the bounded root operation-boundary event definitions with host subscriptions", () => {
const panel = readFileSync(new URL("../../uxp-plugin/index.cjs", import.meta.url), "utf8");
expect(panel).toContain("EventSupport.createOperationBoundaryEventDefinitions(ppro.OperationCompleteEvent)");
expect(panel).toContain("].concat(snapEvents, operationBoundaryEvents)");
});
it("prevents filename path traversal", () => {
expect(protocol.safeFilename("shot-01.png")).toBe("shot-01.png");
expect(() => protocol.safeFilename("../shot.png")).toThrow();
expect(() => protocol.safeFilename("shot.jpg")).toThrow();
});
it("passes a bare frame stem to Premiere while retaining the public PNG filename", () => {
expect(protocol.exporterFrameName("shot-01.png")).toBe("shot-01");
expect(protocol.exporterFrameName("Frame.PNG")).toBe("Frame");
});
it("joins Windows and POSIX-style output paths", () => {
expect(protocol.joinPath("C:/temp", "a.png")).toBe("C:/temp/a.png");
expect(protocol.joinPath("C:/temp/", "a.png")).toBe("C:/temp/a.png");
});
it("keeps the one-extension normalization in the panel's direct frame-export path", () => {
const panel = readFileSync(new URL("../../uxp-plugin/index.cjs", import.meta.url), "utf8");
expect(panel).toContain("const exporterFilename = Protocol.exporterFrameName(filename);");
expect(panel).toContain("exportSequenceFrame(sequence, position, exporterFilename, outputDirectory, width, height)");
});
it("describes verification, undo, transaction, and cancellation boundaries", () => {
expect(protocol.operationSemantics({
mutatesProject: true,
verificationStatus: "verified",
verificationBoundary: "post_action_snapshot",
verificationEvidence: [{ type: "sequence", id: "s1" }],
undoSupported: true,
undoLabel: "Rename track",
transactionActionGroup: true,
cancellationSupported: false,
})).toEqual({
mutatesProject: true,
verification: {
status: "verified",
boundary: "post_action_snapshot",
evidence: [{ type: "sequence", id: "s1" }],
},
undo: {
supported: true,
boundary: "premiere_undo_history",
label: "Rename track",
},
transaction: {
actionGroup: true,
boundary: "project_executeTransaction",
atomicRollback: false,
},
cancellation: {
supported: false,
boundary: "before_non_cancellable_host_call",
},
});
});
it("emits correlated operation lifecycle events", () => {
expect(protocol.operationEvent(
"progress",
{ requestId: "op-1", command: "frame.export" },
{ phase: "verification", progress: 0.8 },
)).toMatchObject({
protocolVersion: 2,
type: "event",
requestId: "op-1",
payload: {
name: "premiere.operation.progress",
operation: {
requestId: "op-1",
command: "frame.export",
phase: "verification",
progress: 0.8,
},
},
});
});
it("only accepts cancellation before a non-cancellable host call", () => {
const tracker = protocol.createOperationTracker();
const operation = tracker.begin("op-1", "frame.export");
expect(tracker.requestCancel("op-1")).toEqual({
accepted: true,
reason: "cancellation_requested",
});
tracker.finish(operation);
expect(tracker.requestCancel("op-1")).toEqual({
accepted: false,
reason: "operation_not_active",
});
const inHost = tracker.begin("op-2", "frame.export");
inHost.phase = "host_call";
expect(tracker.requestCancel("op-2")).toEqual({
accepted: false,
reason: "host_call_not_cancellable",
});
});
});