mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 16:02:36 +00:00
Claude-Session: https://claude.ai/code/session_01PENfHNJceAbrcUsgfFCjXN Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
167 lines
6.0 KiB
TypeScript
167 lines
6.0 KiB
TypeScript
import { expect, test, describe } from "bun:test";
|
|
import { mkdtemp, rm, mkdir, writeFile } from "node:fs/promises";
|
|
import os from "node:os";
|
|
import * as path from "node:path";
|
|
import { runLint } from "../src/commands/lint/lint.ts";
|
|
|
|
const WMILL_YAML = "defaultTs: bun\nincludes:\n - f/**\nexcludes: []\n";
|
|
const METADATA = "summary: test\nlock: ''\nschema:\n properties: {}\n";
|
|
|
|
async function write(dir: string, rel: string, content: string) {
|
|
const full = path.join(dir, rel);
|
|
await mkdir(path.dirname(full), { recursive: true });
|
|
await writeFile(full, content, "utf-8");
|
|
}
|
|
|
|
/**
|
|
* Runs `fn` with a sync root at `<temp>/<rootName>`, from which lint resolves
|
|
* every walked path. The name is a parameter because it is load-bearing: the
|
|
* folder suffixes lint classifies by (`.app`, `__mod`, …) are matched anywhere
|
|
* in a path, so a root carrying one must not change what lint reports.
|
|
*/
|
|
async function withSyncRoot(
|
|
rootName: string,
|
|
fn: (syncRoot: string) => Promise<void>,
|
|
opts: { runFromParent?: boolean } = {},
|
|
): Promise<void> {
|
|
const tempDir = await mkdtemp(path.join(os.tmpdir(), "wmill_lint_orphan_"));
|
|
const syncRoot = path.join(tempDir, rootName);
|
|
const originalCwd = process.cwd();
|
|
try {
|
|
await write(syncRoot, "wmill.yaml", WMILL_YAML);
|
|
process.chdir(opts.runFromParent ? tempDir : syncRoot);
|
|
await fn(syncRoot);
|
|
} finally {
|
|
process.chdir(originalCwd);
|
|
await rm(tempDir, { recursive: true });
|
|
}
|
|
}
|
|
|
|
describe("orphan script metadata", () => {
|
|
test("reports metadata with no content file, with locks not required", async () => {
|
|
await withSyncRoot("repo", async (syncRoot) => {
|
|
await write(syncRoot, "f/paired.py", "def main():\n pass\n");
|
|
await write(syncRoot, "f/paired.script.yaml", METADATA);
|
|
await write(syncRoot, "f/orphan.script.yaml", METADATA);
|
|
await write(syncRoot, "f/orphan_json.script.json", "{}\n");
|
|
await write(syncRoot, "f/orphan_lock.script.lock", "some-dep==1.0.0\n");
|
|
|
|
const report = await runLint({} as any, syncRoot);
|
|
|
|
expect(report.exitCode).toBe(1);
|
|
expect(report.issues.map((i) => i.path).sort()).toEqual([
|
|
"f/orphan.script.yaml",
|
|
"f/orphan_json.script.json",
|
|
"f/orphan_lock.script.lock",
|
|
]);
|
|
expect(report.issues[0].target).toBe("script");
|
|
expect(report.issues[0].errors[0]).toContain("No script file found next to");
|
|
});
|
|
});
|
|
|
|
test("reports a module folder's own metadata with no content file", async () => {
|
|
await withSyncRoot("repo", async (syncRoot) => {
|
|
await write(syncRoot, "f/orphan__mod/script.yaml", METADATA);
|
|
|
|
const report = await runLint({} as any, syncRoot);
|
|
|
|
expect(report.issues.map((i) => i.path)).toEqual([
|
|
"f/orphan__mod/script.yaml",
|
|
]);
|
|
|
|
// Linting the module folder itself: the walked paths no longer carry the
|
|
// `__mod/` boundary that says this is a module's metadata.
|
|
const inFolder = await runLint(
|
|
{} as any,
|
|
path.join(syncRoot, "f/orphan__mod"),
|
|
);
|
|
|
|
expect(inFolder.issues.map((i) => i.path)).toEqual(["script.yaml"]);
|
|
});
|
|
});
|
|
|
|
test("reports orphans under a sync root named like a resource folder", async () => {
|
|
await withSyncRoot("acme.app", async (syncRoot) => {
|
|
await write(syncRoot, "f/orphan.script.yaml", METADATA);
|
|
|
|
const report = await runLint({} as any, syncRoot);
|
|
|
|
expect(report.issues.map((i) => i.path)).toEqual(["f/orphan.script.yaml"]);
|
|
});
|
|
});
|
|
|
|
test("does not report a paired module under a sync root named like a module folder", async () => {
|
|
// Run from OUTSIDE the checkout, the one invocation whose paths carry the
|
|
// root's own name: nothing above the sync root may be classified.
|
|
await withSyncRoot(
|
|
"repo__mod",
|
|
async (syncRoot) => {
|
|
await write(syncRoot, "f/example__mod/script.yaml", METADATA);
|
|
await write(
|
|
syncRoot,
|
|
"f/example__mod/script.ts",
|
|
"export function main() {}\n",
|
|
);
|
|
|
|
const report = await runLint({} as any, syncRoot);
|
|
|
|
expect(report.issues).toEqual([]);
|
|
},
|
|
{ runFromParent: true },
|
|
);
|
|
});
|
|
|
|
test("does not report a non-dotted folder resource's child", async () => {
|
|
// The dotted/non-dotted setting is read from the invocation directory, so
|
|
// an explicit target configured the other way must still be recognized.
|
|
await withSyncRoot(
|
|
"repo",
|
|
async (syncRoot) => {
|
|
await write(syncRoot, "f/a__raw_app/raw_app.yaml", "value: {}\n");
|
|
await write(syncRoot, "f/a__raw_app/backend/config.script.lock", "x\n");
|
|
|
|
const report = await runLint({} as any, syncRoot);
|
|
|
|
expect(report.issues).toEqual([]);
|
|
},
|
|
{ runFromParent: true },
|
|
);
|
|
});
|
|
|
|
test("keeps the reported path whole when the lint target is a path segment", async () => {
|
|
await withSyncRoot("repo", async (syncRoot) => {
|
|
await write(syncRoot, "f/conf/orphan.script.yaml", METADATA);
|
|
|
|
const report = await runLint({} as any, path.join(syncRoot, "f"));
|
|
|
|
expect(report.issues.map((i) => i.path)).toEqual([
|
|
"conf/orphan.script.yaml",
|
|
]);
|
|
expect(report.issues[0].errors[0]).toContain("conf/orphan.script.yaml");
|
|
});
|
|
});
|
|
|
|
test("does not report a fileset child spelled like script metadata", async () => {
|
|
await withSyncRoot("repo", async (syncRoot) => {
|
|
await write(syncRoot, "f/data.resource.yaml", "value: {}\n");
|
|
await write(syncRoot, "f/data.fileset/config.script.yaml", "a: 1\n");
|
|
|
|
const report = await runLint({} as any, syncRoot);
|
|
|
|
expect(report.issues).toEqual([]);
|
|
});
|
|
});
|
|
|
|
test("does not report a dbt project whose optional descriptor is absent", async () => {
|
|
await withSyncRoot("repo", async (syncRoot) => {
|
|
await write(syncRoot, "f/proj.script.yaml", METADATA);
|
|
await write(syncRoot, "f/proj__dbt/dbt_project.yml", "name: proj\n");
|
|
await write(syncRoot, "f/proj__dbt/models/a.sql", "select 1\n");
|
|
|
|
const report = await runLint({} as any, syncRoot);
|
|
|
|
expect(report.issues).toEqual([]);
|
|
});
|
|
});
|
|
});
|