mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-15 16:03:02 +00:00
* feat: add the workspace settings document, applied server-side * feat: add wmill workspace get-settings and apply-settings * fix: warn on unknown settings keys and refuse ee-only ones in the dry run Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: stub the settings document endpoint in the push settings unit tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: refuse mistyped settings and keep 4xx status on a partial settings apply Reject non-boolean switches and non-string Slack credentials instead of coercing them into destructive writes, preserve the failing endpoint's status when wrapping a partial-apply error, keep the stored Slack secret when an older CLI pushes one encrypted under another workspace's key, print the plan's warnings in apply-settings, and state the admin check load_settings_document leaves to its caller. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: compare handler arguments verbatim and type-check scalar settings A handler's extra_args is stored as sent and its nulls reach the job as arguments, so an added or removed null argument is now a change. Scalar settings are type-checked in the plan so a dry run never reports a change the endpoint then refuses, and an error handler's omitted mute flags compare as the false the endpoint stores. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: require a 32-bit integer rate limit in the settings plan Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: data table permissions and protection rules in the settings document The settings document gains datatable_permissions, each data table's roles named by their Postgres role name and written by catalog id, and protection_rules, keyed by rule name. Both are managed only when stated and stated as a whole. The dev workspace lock stays with the dev workspace setting. wmill sync carries datatable_permissions only with includeDatatablePermissions (or --include-datatable-permissions), since tenants and roles are specific to a workspace and an instance. protection_rules stay out of settings.yaml: protection-rules.yaml syncs them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: validate the default data table role and plan permissions against a replaced datatable Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
87 lines
3.0 KiB
TypeScript
87 lines
3.0 KiB
TypeScript
/**
|
|
* Regression guard: `sync push` (pushWorkspaceSettings) applies the domain invite and
|
|
* the instance groups of `auto_invite` through their own endpoints, and never clears
|
|
* instance groups that settings.yaml does not declare.
|
|
*/
|
|
|
|
import { expect, test, describe, beforeEach } from "bun:test";
|
|
import { mockServices } from "./mock_services.ts";
|
|
|
|
let editAutoInviteCalls: unknown[] = [];
|
|
let editInstanceGroupsCalls: unknown[] = [];
|
|
const remoteAutoInvite = {
|
|
enabled: true,
|
|
domain: "*",
|
|
operator: false,
|
|
mode: "invite",
|
|
instance_groups: ["eng"],
|
|
instance_groups_roles: { eng: "developer" },
|
|
};
|
|
|
|
// Every wmill.* call reachable from pushWorkspaceSettings is stubbed so the
|
|
// function runs without a backend.
|
|
mockServices({
|
|
// A server without the settings document, so the per-setting endpoints run.
|
|
applyWorkspaceSettingsDocument: async () => {
|
|
throw { status: 404 };
|
|
},
|
|
getSettings: async () => ({ auto_invite: remoteAutoInvite }),
|
|
getWorkspaceName: async () => "phoenix",
|
|
changeWorkspaceName: async () => {},
|
|
changeWorkspaceColor: async () => {},
|
|
editWebhook: async () => {},
|
|
editAutoInvite: async (a: unknown) => {
|
|
editAutoInviteCalls.push(a);
|
|
},
|
|
editInstanceGroups: async (a: unknown) => {
|
|
editInstanceGroupsCalls.push(a);
|
|
},
|
|
editErrorHandler: async () => {},
|
|
editSuccessHandler: async () => {},
|
|
editCopilotConfig: async () => {},
|
|
editLargeFileStorageConfig: async () => {},
|
|
editWorkspaceGitSyncConfig: async () => {},
|
|
editWorkspaceDefaultApp: async () => {},
|
|
editDefaultScripts: async () => {},
|
|
workspaceMuteCriticalAlertsUi: async () => {},
|
|
updateOperatorSettings: async () => {},
|
|
editDataTableConfig: async () => {},
|
|
editSlackCommand: async () => {},
|
|
setWorkspaceSlackOauthConfig: async () => {},
|
|
deleteWorkspaceSlackOauthConfig: async () => {},
|
|
});
|
|
|
|
const { pushWorkspaceSettings } = await import("../src/core/settings.ts");
|
|
|
|
describe("pushWorkspaceSettings auto_invite", () => {
|
|
beforeEach(() => {
|
|
editAutoInviteCalls = [];
|
|
editInstanceGroupsCalls = [];
|
|
});
|
|
|
|
test("an instance-group-only change updates the groups and leaves the domain invite", async () => {
|
|
await pushWorkspaceSettings("phoenix", "settings", undefined, {
|
|
name: "phoenix",
|
|
auto_invite: { ...remoteAutoInvite, instance_groups_roles: { eng: "admin" } },
|
|
});
|
|
expect(editAutoInviteCalls.length).toBe(0);
|
|
expect(editInstanceGroupsCalls).toEqual([
|
|
{
|
|
workspace: "phoenix",
|
|
requestBody: { groups: ["eng"], roles: { eng: "admin" } },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("a settings.yaml without instance_groups does not clear them", async () => {
|
|
const { instance_groups: _g, instance_groups_roles: _r, ...domainInvite } =
|
|
remoteAutoInvite;
|
|
await pushWorkspaceSettings("phoenix", "settings", undefined, {
|
|
name: "phoenix",
|
|
auto_invite: { ...domainInvite, operator: true },
|
|
});
|
|
expect(editAutoInviteCalls.length).toBe(1);
|
|
expect(editInstanceGroupsCalls.length).toBe(0);
|
|
});
|
|
});
|