Release 2.6.2-rc2
This commit is contained in:
@@ -288,6 +288,11 @@ describe("up API pipeline", () => {
|
||||
expect(
|
||||
new Headers(build.init?.headers).get("x-kuber-trust-project"),
|
||||
).toBe("shop");
|
||||
const plans = calls.filter(({ path }) => path === "/workspaces/shop/resources/plan");
|
||||
expect(plans).toHaveLength(1);
|
||||
expect(plans[0]?.options).toEqual({ timeoutMs: 120_000 });
|
||||
expect(plans[0]?.init?.signal).toBeInstanceOf(AbortSignal);
|
||||
expect(new Headers(plans[0]?.init?.headers).get("x-kuber-trust-project")).toBe("shop");
|
||||
} finally {
|
||||
process.chdir(previousCwd);
|
||||
if (previousConfigHome === undefined) delete process.env.XDG_CONFIG_HOME;
|
||||
@@ -714,6 +719,69 @@ describe("up API pipeline", () => {
|
||||
expect(order).toEqual(["plan", "apply", "hook", "wait", "delete"]);
|
||||
});
|
||||
|
||||
test("allows a slow plan beyond the default deadline before applying once", async () => {
|
||||
const calls: string[] = [];
|
||||
const controller = new AbortController();
|
||||
const request: ApiRequester = async <T>(
|
||||
path: string,
|
||||
init?: ApiRequestInit,
|
||||
options?: ApiRequestOptions,
|
||||
) => {
|
||||
calls.push(path.split("/").at(-1)!);
|
||||
if (path.endsWith("/plan")) {
|
||||
expect(init?.method).toBe("POST");
|
||||
expect(init?.signal).toBe(controller.signal);
|
||||
// Scale 1 simulated second to 1ms: the plan completes at 35s.
|
||||
const deadline = options?.timeoutMs ?? 30_000;
|
||||
if (deadline < 35_000) throw new DOMException("Timed out", "TimeoutError");
|
||||
await Bun.sleep(35);
|
||||
return { desired: [], stale: [] } as T;
|
||||
}
|
||||
if (path.endsWith("/apply")) return {} as T;
|
||||
throw new Error(`Unexpected request: ${path}`);
|
||||
};
|
||||
|
||||
await reconcileResources("shop", [], 1, undefined, request, {
|
||||
signal: controller.signal,
|
||||
});
|
||||
expect(calls).toEqual(["plan", "apply"]);
|
||||
});
|
||||
|
||||
test("cancels a pending plan without starting a mutation", async () => {
|
||||
const controller = new AbortController();
|
||||
const cancelled = new DOMException("Cancelled", "AbortError");
|
||||
const calls: string[] = [];
|
||||
let planStarted!: () => void;
|
||||
const started = new Promise<void>((resolve) => { planStarted = resolve; });
|
||||
const request: ApiRequester = async <T>(path: string, init?: ApiRequestInit) => {
|
||||
calls.push(path);
|
||||
if (!path.endsWith("/plan")) throw new Error(`Unexpected request: ${path}`);
|
||||
expect(init?.signal).toBe(controller.signal);
|
||||
return new Promise<T>((_resolve, reject) => {
|
||||
init?.signal?.addEventListener("abort", () => reject(init.signal?.reason), { once: true });
|
||||
planStarted();
|
||||
});
|
||||
};
|
||||
const run = reconcileResources("shop", [], 1, undefined, request, {
|
||||
signal: controller.signal,
|
||||
});
|
||||
await started;
|
||||
controller.abort(cancelled);
|
||||
await expect(run).rejects.toBe(cancelled);
|
||||
expect(calls).toEqual(["/workspaces/shop/resources/plan"]);
|
||||
});
|
||||
|
||||
test("does not retry an HTTP 500 plan or start an apply", async () => {
|
||||
const calls: string[] = [];
|
||||
const failure = new KuberApiError("plan failed", 500);
|
||||
const request: ApiRequester = async <T>(path: string) => {
|
||||
calls.push(path);
|
||||
throw failure;
|
||||
};
|
||||
await expect(reconcileResources("shop", [], 1, undefined, request)).rejects.toBe(failure);
|
||||
expect(calls).toEqual(["/workspaces/shop/resources/plan"]);
|
||||
});
|
||||
|
||||
test("resumes an interrupted reconcile operation by its persisted ID", async () => {
|
||||
const calls: string[] = [];
|
||||
const applyIdempotencyKeys: Array<string | null> = [];
|
||||
|
||||
Reference in New Issue
Block a user