Files
windmill/cli/test/fileset_sync_unit.test.ts
hugocasa 85134578b4 fix(cli): sync push crashed on edited fileset children; reject non-canonical fileset dirs (#10572)
* fix(cli): route fileset children to their parent resource on sync push

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(cli): scope fileset pointer validation to sync pushes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(cli): error on script push of file/fileset resource content files

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(cli): enforce server-canonical fileset pointers and fail fast before apply

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(cli): resolve ws-specific fileset metadata and validate pointers before dry-run

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(cli): prefer workspace-specific fileset metadata over base file

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(cli): make fileset metadata lookup assertions platform-separator safe

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(cli): stop dropping fileset children whose names look like typed metadata

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

* fix(cli): exempt fileset children from the current-workspace classifier too

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 13:16:45 +00:00

156 lines
5.6 KiB
TypeScript

/**
* Unit tests for fileset resource sync routing and pointer validation.
* These tests require no backend — they test standalone logic.
*/
import { expect, test, describe } from "bun:test";
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, sep as SEP } from "node:path";
import { handleFile } from "../src/commands/script/script.ts";
import { validateFilesetPointer } from "../src/commands/resource/resource.ts";
import { findFilesetResourceFile } from "../src/commands/sync/sync.ts";
import {
isCurrentWorkspaceFile,
isWorkspaceSpecificFile,
} from "../src/core/specific_items.ts";
describe("handleFile routing", () => {
test("returns false for fileset children with script extensions", async () => {
// A fileset child belongs to its parent resource's value, never to a
// standalone script: bare `.sql` has no script language (only `.pg.sql`
// etc. do), so routing it to the script pusher aborts the whole push.
for (const p of [
"f/resources/data.fileset/energy/queries/report.sql",
"f/resources/data.fileset/scripts/main.ts",
"f/resources/data.fileset/scripts/main.py",
]) {
expect(
await handleFile(p, {} as any, [], undefined, undefined, {}, []),
).toBe(false);
}
});
test("returns false for single-file resource content files", async () => {
expect(
await handleFile(
"f/resources/query.resource.file.sql",
{} as any,
[],
undefined,
undefined,
{},
[],
),
).toBe(false);
});
});
describe("workspace-specific classification", () => {
test("fileset children are never workspace-specific", () => {
// The workspace-name segment of the pattern spans `/`, so a child whose
// own name looks like a typed metadata file would otherwise be read as
// belonging to another workspace and dropped from every diff — i.e.
// silently never deployed.
for (const p of [
"f/resources/data.fileset/edge/q.resource.file.sql",
"f/resources/data.fileset/edge/inner.resource.yaml",
"f/resources/data.fileset/queries/report.sql",
]) {
expect(isWorkspaceSpecificFile(p)).toBe(false);
}
// A child carrying the active workspace's own suffix must not be remapped
// onto the unsuffixed sibling key, which would deploy over that sibling.
expect(
isCurrentWorkspaceFile(
"f/resources/data.fileset/edge/inner.ws_main.resource.yaml",
"ws_main",
),
).toBe(false);
// The parent's own metadata still carries the suffix.
expect(isWorkspaceSpecificFile("f/resources/data.ws_main.resource.yaml")).toBe(
true,
);
expect(
isCurrentWorkspaceFile("f/resources/data.ws_main.resource.yaml", "ws_main"),
).toBe(true);
});
});
describe("validateFilesetPointer", () => {
test("accepts the server-canonical pointer", () => {
expect(() =>
validateFilesetPointer("f/resources/data.fileset", "f/resources/data"),
).not.toThrow();
});
test("normalizes trailing slashes", () => {
expect(() =>
validateFilesetPointer("f/resources/data.fileset/", "f/resources/data"),
).not.toThrow();
});
test("rejects a custom pointer with an actionable message", () => {
expect(() =>
validateFilesetPointer(
"f/queries.fileset",
"f/resources/analytics_data",
),
).toThrow(/must live next to its resource file, at 'f\/resources\/analytics_data\.fileset'/);
});
test("resolves workspace-specific metadata for canonical children", async () => {
// The metadata file carries the workspace suffix while children stay at
// the server-canonical `<base>.fileset/` directory.
const dir = mkdtempSync(join(tmpdir(), "fileset-ws-"));
const cwd = process.cwd();
try {
mkdirSync(join(dir, "f/res"), { recursive: true });
writeFileSync(
join(dir, "f/res/data.ws_main.resource.yaml"),
"resource_type: c_files\nvalue: '!inline_fileset f/res/data.fileset'\n",
);
process.chdir(dir);
// findFilesetResourceFile derives the metadata path from the child
// path, so both use the platform separator.
const childPath = ["f", "res", "data.fileset", "q.sql"].join(SEP);
const wsMetadataPath = ["f", "res", "data.ws_main.resource.yaml"].join(
SEP,
);
const baseMetadataPath = ["f", "res", "data.resource.yaml"].join(SEP);
expect(await findFilesetResourceFile(childPath, "ws_main")).toBe(
wsMetadataPath,
);
await expect(findFilesetResourceFile(childPath, null)).rejects.toThrow(
/No resource metadata file found/,
);
// The suffixed file is the workspace's authoritative metadata: it wins
// even when a base metadata file coexists with it.
writeFileSync(
join(dir, "f/res/data.resource.yaml"),
"resource_type: c_files\nvalue: '!inline_fileset f/res/data.fileset'\n",
);
expect(await findFilesetResourceFile(childPath, "ws_main")).toBe(
wsMetadataPath,
);
expect(await findFilesetResourceFile(childPath, null)).toBe(
baseMetadataPath,
);
} finally {
process.chdir(cwd);
rmSync(dir, { recursive: true, force: true });
}
});
test("rejects a branch-suffixed directory for a workspace-specific resource", () => {
// The remote exporter always renders children at the server-canonical
// location, so a `.ws_<branch>.fileset` directory would never round-trip.
expect(() =>
validateFilesetPointer(
"f/resources/data.ws_main.fileset",
"f/resources/data",
),
).toThrow(/at 'f\/resources\/data\.fileset'/);
});
});