mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-13 08:02:47 +00:00
`wmill sync` carries `value_expires_at` on variables in both directions, and pushes the workspace handler alongside the error and success handlers in the grouped settings format. Dropping the key from a variable's yaml clears the remote expiry, so the file stays canonical rather than accumulating fields that can only ever be added. The handler follows the same rule, with the caveat recorded where the type is declared: an omitted handler only reaches the clear when some other setting differs, because the push short-circuits on a comparison that walks the local object's keys and so never sees a missing one. That gate is shared with the two existing handlers and is left as it is here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
65 lines
2.0 KiB
TypeScript
65 lines
2.0 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import { deployItem } from "../windmill-utils-internal/src/deploy.ts";
|
|
|
|
// Workspace deploy builds the variable body field by field, so a field the variable
|
|
// carries but the body forgets is dropped on promotion — silently, since the deploy
|
|
// still reports success. The clear half matters as much as the carry: the server reads
|
|
// an absent `value_expires_at` as "leave the stored date alone", so a source with no
|
|
// expiry has to say so explicitly or the target keeps a date the source no longer has.
|
|
function recordingProvider(
|
|
captured: [string, any][],
|
|
exists: boolean,
|
|
value_expires_at: string | null,
|
|
) {
|
|
return {
|
|
existsVariable: async () => exists,
|
|
getVariable: async () => ({
|
|
path: "f/x/v",
|
|
value: "secret",
|
|
is_secret: true,
|
|
description: "d",
|
|
value_expires_at,
|
|
}),
|
|
createVariable: async (p: any) =>
|
|
void captured.push(["createVariable", p.requestBody]),
|
|
updateVariable: async (p: any) =>
|
|
void captured.push(["updateVariable", p.requestBody]),
|
|
} as any;
|
|
}
|
|
|
|
test("deployItem: carries value_expires_at, and clears it explicitly", async () => {
|
|
const captured: [string, any][] = [];
|
|
|
|
// The field is written out once per branch, so exercise both: a variable absent from
|
|
// the target (create) and one already there (update).
|
|
for (const exists of [false, true]) {
|
|
await deployItem(
|
|
recordingProvider(captured, exists, "2027-03-15T08:00:00Z"),
|
|
"variable" as any,
|
|
"f/x/v",
|
|
"src",
|
|
"dst",
|
|
);
|
|
await deployItem(
|
|
recordingProvider(captured, exists, null),
|
|
"variable" as any,
|
|
"f/x/v",
|
|
"src",
|
|
"dst",
|
|
);
|
|
}
|
|
|
|
expect(captured.map(([fn]) => fn)).toEqual([
|
|
"createVariable",
|
|
"createVariable",
|
|
"updateVariable",
|
|
"updateVariable",
|
|
]);
|
|
expect(captured.map(([, body]) => body.value_expires_at)).toEqual([
|
|
"2027-03-15T08:00:00Z",
|
|
null,
|
|
"2027-03-15T08:00:00Z",
|
|
null,
|
|
]);
|
|
});
|