Files
windmill/cli/test/flow_preview_unit.test.ts
T
Alexander PetricandClaude Opus 5.5 65f3c5ee5c fix(cli): flow preview hub steps and windmill paths, trigger templates (#12075)
* fix(cli): drop deprecated and unused fields from trigger new templates

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cli): flow preview takes the windmill path and runs hub steps with --step

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cli): run a deployed script step with its deployed lock in flow preview --step

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cli): flow preview --remote honors nonDottedPaths when resolving the folder

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 19:22:50 +01:00

138 lines
4.6 KiB
TypeScript

/**
* `wmill flow preview` argument resolution and `--step` script loading.
*/
import { expect, test, describe, beforeEach, afterEach } from "bun:test";
import * as fs from "node:fs";
import * as path from "node:path";
import * as os from "node:os";
import { mockServices } from "./mock_services.ts";
const calls: { fn: string; args: any }[] = [];
mockServices({
getHubScriptByPath: async (args: any) => {
calls.push({ fn: "getHubScriptByPath", args });
return {
content: "export async function main() { return 'hub' }",
language: "deno",
lockfile: "hub-lock",
};
},
getScriptByPath: async (args: any) => {
calls.push({ fn: "getScriptByPath", args });
return { content: "workspace code", language: "bun", lock: "ws-lock" };
},
getScriptByHash: async (args: any) => {
calls.push({ fn: "getScriptByHash", args });
return { content: "hashed code", language: "python3", lock: "hash-lock" };
},
});
const { resolveFlowFolderArg, loadStepScript } = await import(
"../src/commands/flow/flow.ts"
);
const { setNonDottedPaths } = await import("../src/utils/resource_folders.ts");
const SEP = path.sep;
describe("resolveFlowFolderArg", () => {
let root: string;
let previousCwd: string;
beforeEach(() => {
previousCwd = process.cwd();
root = fs.realpathSync(
fs.mkdtempSync(path.join(os.tmpdir(), "wmill_flow_preview_")),
);
fs.mkdirSync(path.join(root, "f", "t", "dotted.flow"), { recursive: true });
fs.mkdirSync(path.join(root, "f", "t", "nondotted__flow"), { recursive: true });
process.chdir(root);
});
afterEach(() => {
process.chdir(previousCwd);
fs.rmSync(root, { recursive: true, force: true });
});
test("a Windmill path resolves to its local .flow folder", () => {
expect(resolveFlowFolderArg(`f${SEP}t${SEP}dotted`)).toBe(
`f${SEP}t${SEP}dotted.flow${SEP}`,
);
});
test("a Windmill path resolves to a __flow folder when that is the one on disk", () => {
expect(resolveFlowFolderArg(`f${SEP}t${SEP}nondotted`)).toBe(
`f${SEP}t${SEP}nondotted__flow${SEP}`,
);
});
test("when both folders exist, the configured suffix wins", () => {
fs.mkdirSync(path.join(root, "f", "t", "both.flow"), { recursive: true });
fs.mkdirSync(path.join(root, "f", "t", "both__flow"), { recursive: true });
try {
setNonDottedPaths(true);
expect(resolveFlowFolderArg(`f${SEP}t${SEP}both`)).toBe(
`f${SEP}t${SEP}both__flow${SEP}`,
);
setNonDottedPaths(false);
expect(resolveFlowFolderArg(`f${SEP}t${SEP}both`)).toBe(
`f${SEP}t${SEP}both.flow${SEP}`,
);
} finally {
setNonDottedPaths(false);
}
});
test("the folder and its flow.yaml keep resolving to the folder", () => {
const folder = `f${SEP}t${SEP}dotted.flow${SEP}`;
expect(resolveFlowFolderArg(`f${SEP}t${SEP}dotted.flow`)).toBe(folder);
expect(resolveFlowFolderArg(folder)).toBe(folder);
expect(resolveFlowFolderArg(`${folder}flow.yaml`)).toBe(folder);
expect(resolveFlowFolderArg("flow.yaml")).toBe(`.${SEP}`);
});
test("a Windmill path with no local flow folder is refused, naming what was looked for", () => {
expect(() => resolveFlowFolderArg(`f${SEP}t${SEP}missing`)).toThrow(
`found none of f${SEP}t${SEP}missing.flow, f${SEP}t${SEP}missing__flow`,
);
});
});
describe("loadStepScript", () => {
beforeEach(() => {
calls.length = 0;
});
test("a hub path is fetched from the hub, with the hub's lockfile", async () => {
const script = await loadStepScript("ws", {
path: "hub/29624/slack/send_message_to_channel",
});
expect(calls).toEqual([
{
fn: "getHubScriptByPath",
args: { path: "hub/29624/slack/send_message_to_channel" },
},
]);
expect(script).toEqual({
content: "export async function main() { return 'hub' }",
language: "deno",
lock: "hub-lock",
});
});
test("a workspace path is fetched from the workspace, with its deployed lock", async () => {
const script = await loadStepScript("ws", { path: "f/t/helper" });
expect(calls).toEqual([
{ fn: "getScriptByPath", args: { workspace: "ws", path: "f/t/helper" } },
]);
expect(script).toEqual({ content: "workspace code", language: "bun", lock: "ws-lock" });
});
test("a pinned hash is fetched by hash", async () => {
const script = await loadStepScript("ws", { path: "f/t/helper", hash: "abc" });
expect(calls).toEqual([
{ fn: "getScriptByHash", args: { workspace: "ws", hash: "abc" } },
]);
expect(script).toEqual({ content: "hashed code", language: "python3", lock: "hash-lock" });
});
});