mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
d65f58c388
* fix: scope SCD2 built-in data tests to current rows Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: add --partition to pipeline run and fix duckdb s3object upload binding Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: note filesystem storage type is dev-only in storage settings Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: use ISO week for weekly partition default in pipeline run Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
152 lines
5.5 KiB
TypeScript
152 lines
5.5 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import {
|
|
devUploadKey,
|
|
parseArgBinding,
|
|
parseS3Uri,
|
|
parseUploadBinding,
|
|
s3Arg,
|
|
s3ObjectParams,
|
|
} from "../src/commands/pipeline/pipelineUpload.ts";
|
|
|
|
test("parseUploadBinding: bare script + local source infers the param later", () => {
|
|
expect(parseUploadBinding("ingest=./fixtures/events.csv")).toEqual({
|
|
scriptTok: "ingest",
|
|
source: "./fixtures/events.csv",
|
|
});
|
|
});
|
|
|
|
test("parseUploadBinding: explicit :param on the left", () => {
|
|
expect(parseUploadBinding("f/analytics/ingest:file=./events.csv")).toEqual({
|
|
scriptTok: "f/analytics/ingest",
|
|
param: "file",
|
|
source: "./events.csv",
|
|
});
|
|
});
|
|
|
|
test("parseUploadBinding: s3:// source — its `:` is right of the first `=`, so it stays intact", () => {
|
|
expect(parseUploadBinding("ingest=s3://bucket/2026/events.csv")).toEqual({
|
|
scriptTok: "ingest",
|
|
source: "s3://bucket/2026/events.csv",
|
|
});
|
|
expect(parseUploadBinding("ingest:file=s3://bucket/k")).toEqual({
|
|
scriptTok: "ingest",
|
|
param: "file",
|
|
source: "s3://bucket/k",
|
|
});
|
|
});
|
|
|
|
test("parseUploadBinding: malformed specs throw", () => {
|
|
expect(() => parseUploadBinding("ingest")).toThrow(); // no `=`
|
|
expect(() => parseUploadBinding("=./x.csv")).toThrow(); // no script
|
|
expect(() => parseUploadBinding("ingest=")).toThrow(); // no source
|
|
expect(() => parseUploadBinding("ingest:=./x.csv")).toThrow(); // empty param
|
|
expect(() => parseUploadBinding(":file=./x.csv")).toThrow(); // empty script
|
|
});
|
|
|
|
test("parseArgBinding: JSON values parse, non-JSON falls back to the raw string", () => {
|
|
// a bare date is not valid JSON → string
|
|
expect(parseArgBinding("daily_report:partition=2026-07-02")).toEqual({
|
|
scriptTok: "daily_report",
|
|
param: "partition",
|
|
value: "2026-07-02",
|
|
});
|
|
// JSON scalars/objects keep their type
|
|
expect(parseArgBinding("stats:limit=10")).toEqual({
|
|
scriptTok: "stats",
|
|
param: "limit",
|
|
value: 10,
|
|
});
|
|
expect(parseArgBinding("stats:enabled=true")).toEqual({
|
|
scriptTok: "stats",
|
|
param: "enabled",
|
|
value: true,
|
|
});
|
|
expect(parseArgBinding('f/pd/stats:opts={"a":1}')).toEqual({
|
|
scriptTok: "f/pd/stats",
|
|
param: "opts",
|
|
value: { a: 1 },
|
|
});
|
|
// quoting forces a string even for number-looking values
|
|
expect(parseArgBinding('stats:code="42"')).toEqual({
|
|
scriptTok: "stats",
|
|
param: "code",
|
|
value: "42",
|
|
});
|
|
// split on the FIRST `=` — values may contain `=`
|
|
expect(parseArgBinding("stats:expr=a=b")).toEqual({
|
|
scriptTok: "stats",
|
|
param: "expr",
|
|
value: "a=b",
|
|
});
|
|
});
|
|
|
|
test("parseArgBinding: :param is required and malformed specs throw", () => {
|
|
expect(() => parseArgBinding("stats=10")).toThrow(); // no :param
|
|
expect(() => parseArgBinding("stats:limit")).toThrow(); // no `=`
|
|
expect(() => parseArgBinding(":limit=10")).toThrow(); // empty script
|
|
expect(() => parseArgBinding("stats:=10")).toThrow(); // empty param
|
|
});
|
|
|
|
test("s3ObjectParams: only resource-s3_object properties, in declaration order", () => {
|
|
const schema = {
|
|
properties: {
|
|
note: { type: "string" },
|
|
file: { type: "object", format: "resource-s3_object" },
|
|
db: { type: "object", format: "resource-postgresql" },
|
|
backup: { type: "object", format: "resource-s3_object" },
|
|
},
|
|
};
|
|
expect(s3ObjectParams(schema)).toEqual(["file", "backup"]);
|
|
expect(s3ObjectParams({})).toEqual([]);
|
|
expect(s3ObjectParams(undefined)).toEqual([]);
|
|
});
|
|
|
|
test("s3ObjectParams: matches SQL-dialect resource-S3Object casing too", () => {
|
|
// TS/python parsers emit `resource-s3_object`; the SQL dialects (duckdb
|
|
// `-- $file (s3object)`) emit `resource-S3Object` — both must bind.
|
|
const schema = {
|
|
properties: {
|
|
file: { type: "object", format: "resource-S3Object" },
|
|
other: { type: "object", format: "resource-s3" },
|
|
},
|
|
};
|
|
expect(s3ObjectParams(schema)).toEqual(["file"]);
|
|
});
|
|
|
|
test("devUploadKey: key scoped by script path + param so basenames don't clobber", () => {
|
|
expect(devUploadKey("f/analytics/ingest", "file", "./fixtures/events.csv")).toBe(
|
|
"wmilldev/pipeline/f/analytics/ingest/file/events.csv",
|
|
);
|
|
// same basename, different script/param → distinct keys
|
|
expect(devUploadKey("f/analytics/other", "file", "/abs/events.csv")).toBe(
|
|
"wmilldev/pipeline/f/analytics/other/file/events.csv",
|
|
);
|
|
expect(devUploadKey("f/analytics/ingest", "backup", "/abs/events.csv")).toBe(
|
|
"wmilldev/pipeline/f/analytics/ingest/backup/events.csv",
|
|
);
|
|
});
|
|
|
|
test("s3Arg: shapes the S3Object run-arg", () => {
|
|
expect(s3Arg("file", { s3: "wmilldev/pipeline/analytics/events.csv" })).toEqual({
|
|
file: { s3: "wmilldev/pipeline/analytics/events.csv" },
|
|
});
|
|
expect(s3Arg("file", { s3: "k.csv", storage: "secondary" })).toEqual({
|
|
file: { s3: "k.csv", storage: "secondary" },
|
|
});
|
|
});
|
|
|
|
test("parseS3Uri: canonical `s3://<storage>/<key>` (authority is the named storage)", () => {
|
|
// named storage — the authority is the storage, the rest is the key
|
|
expect(parseS3Uri("s3://secondary/k.csv")).toEqual({ s3: "k.csv", storage: "secondary" });
|
|
// empty authority (`s3:///key`) ⇒ default storage, incl. nested keys
|
|
expect(parseS3Uri("s3:///events.csv")).toEqual({ s3: "events.csv" });
|
|
expect(parseS3Uri("s3:///raw/2026/events.csv")).toEqual({ s3: "raw/2026/events.csv" });
|
|
// named storage with a nested key
|
|
expect(parseS3Uri("s3://secondary/raw/2026/events.csv")).toEqual({
|
|
s3: "raw/2026/events.csv",
|
|
storage: "secondary",
|
|
});
|
|
// no `/` after the scheme → no authority; whole rest is the default-storage key
|
|
expect(parseS3Uri("s3://events.csv")).toEqual({ s3: "events.csv" });
|
|
});
|