Files
windmill/cli/test/push_workspace_key_unit.test.ts
T
Ruben FiszelandClaude Opus 5.5 30bb62cd25 fix(cli): stub the API client over its real exports in tests (#11363)
* fix(cli): stub the API client over its real exports in tests

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): mock the API client once and dispatch to per-suite stubs

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 17:43:08 +00:00

95 lines
3.3 KiB
TypeScript

/**
* Unit tests for pushWorkspaceKey in settings.ts.
*
* Covers WIN-2005: changing the encryption key in encryption_key.yaml and
* pushing it must (by default) re-encrypt the remote secrets with the new key.
*
* Verifies that:
* - an unchanged key is a no-op (no setWorkspaceEncryptionKey call)
* - a changed key in non-interactive mode re-encrypts by default
* (skip_reencrypt = false), so secret plaintext values are preserved
* - the --skip-reencrypt-on-key-change flag keeps the remote ciphertexts
* untouched (skip_reencrypt = true)
* - WMILL_NO_REENCRYPT_ON_KEY_CHANGE=true does the same via env var
*/
import { expect, test, describe, beforeEach, afterEach } from "bun:test";
import { mockServices } from "./mock_services.ts";
// Track calls to mocked wmill functions
let remoteKey = "";
let setEncryptionKeyCalls: {
workspace: string;
requestBody: { new_key: string; skip_reencrypt?: boolean };
}[] = [];
// Mock the wmill module before importing settings.ts
mockServices({
getWorkspaceEncryptionKey: async (_args: { workspace: string }) => ({
key: remoteKey,
}),
setWorkspaceEncryptionKey: async (args: {
workspace: string;
requestBody: { new_key: string; skip_reencrypt?: boolean };
}) => {
setEncryptionKeyCalls.push(args);
},
});
import { pushWorkspaceKey } from "../src/core/settings.ts";
describe("pushWorkspaceKey", () => {
const ws = "test-workspace";
beforeEach(() => {
remoteKey = "";
setEncryptionKeyCalls = [];
delete process.env.WMILL_NO_REENCRYPT_ON_KEY_CHANGE;
});
afterEach(() => {
delete process.env.WMILL_NO_REENCRYPT_ON_KEY_CHANGE;
});
test("no-op when local key matches the remote key", async () => {
remoteKey = "samekey";
await pushWorkspaceKey(ws, "encryption_key", undefined, "samekey", {
noninteractive: true,
});
expect(setEncryptionKeyCalls.length).toBe(0);
});
test("changed key re-encrypts by default in non-interactive mode", async () => {
remoteKey = "oldkey";
await pushWorkspaceKey(ws, "encryption_key", undefined, "newkey", {
noninteractive: true,
});
expect(setEncryptionKeyCalls.length).toBe(1);
expect(setEncryptionKeyCalls[0].requestBody.new_key).toBe("newkey");
// skip_reencrypt false => backend re-encrypts existing secrets with new key
expect(setEncryptionKeyCalls[0].requestBody.skip_reencrypt).toBe(false);
});
test("--skip-reencrypt-on-key-change skips re-encryption", async () => {
remoteKey = "oldkey";
await pushWorkspaceKey(ws, "encryption_key", undefined, "newkey", {
noninteractive: true,
skipReencrypt: true,
});
expect(setEncryptionKeyCalls.length).toBe(1);
expect(setEncryptionKeyCalls[0].requestBody.new_key).toBe("newkey");
expect(setEncryptionKeyCalls[0].requestBody.skip_reencrypt).toBe(true);
});
test("WMILL_NO_REENCRYPT_ON_KEY_CHANGE=true skips re-encryption non-interactively", async () => {
remoteKey = "oldkey";
process.env.WMILL_NO_REENCRYPT_ON_KEY_CHANGE = "true";
await pushWorkspaceKey(ws, "encryption_key", undefined, "newkey", {
noninteractive: true,
});
expect(setEncryptionKeyCalls.length).toBe(1);
expect(setEncryptionKeyCalls[0].requestBody.new_key).toBe("newkey");
expect(setEncryptionKeyCalls[0].requestBody.skip_reencrypt).toBe(true);
});
});