Files
windmill/frontend/src/lib/editRights.test.ts
T
8dbdfa1949 feat: workspace roles settings UI and capability-based gating (#11824)
* feat: configurable workspace roles with per-workspace overrides

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: close role review gaps in pickers, declarative sync and removal

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: keep resolve_role_name doc on its function

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: validate overrides on instance role changes and bill admins fully

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: allow pipeline drafts and keep role checks on one connection

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: list data pipelines in operator_can_draft

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: expect a 403 role refusal when an operator resolves jobs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: require a valid license for custom workspace roles

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: freeze workspace roles when the license lapses

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: pin the ee companion for workspace roles

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor: broadcast role override changes from the write path, not a trigger

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: update ee-repo-ref to b5294d38db082bdd104d5358a47695390694c05d

This commit updates the EE repository reference after PR #891 was merged in windmill-ee-private.

Previous ee-repo-ref: 688e00c5982865b14098f530d54280e2311994c0

New ee-repo-ref: b5294d38db082bdd104d5358a47695390694c05d

Automated by sync-ee-ref workflow.

* feat: add workspace roles settings UI and role pickers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: show the server's reason when a settings save fails

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: default role pickers to a defined role and confirm resets of unknown capabilities

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: treat null role capabilities as unset and key role drafts by map

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: gate the frontend on workspace role capabilities

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: gate deploys, bulk actions and chat composition per role kind

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: treat a superadmin as full in deploy permission checks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: leave page visibility out of isRestricted, as the server does

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: let a member discard their own draft without the authoring right

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: show custom roles as inactive without a valid license

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: keep the user store import on the agent page

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: show custom roles as frozen without a valid license

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: reorder instance roles with a drag handle

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: let an admin row that kept the operator flag hold full rights everywhere

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: treat superadmin members as admins and return role names from get user

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: move the instance role label into the expanded panel

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-10-21 16:31:04 +02:00

72 lines
2.8 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import type { RoleCapabilities } from '$lib/gen'
import type { UserExt } from '$lib/stores'
import { FULL_CAPABILITIES, legacyOperatorCapabilities } from '$lib/roleCapabilities'
import { canEditItem, roleCanAuthor, roleCanDraft } from './editRights'
function user(overrides: Partial<UserExt>): UserExt {
return {
email: 'op@windmill.dev',
username: 'op',
name: 'op',
is_admin: false,
is_super_admin: false,
created_at: '',
groups: [],
pgroups: [],
folders: [],
folders_owners: [],
folders_read: [],
operator: true,
...overrides
} as UserExt
}
const role = (overrides: Partial<RoleCapabilities>): RoleCapabilities => ({
...legacyOperatorCapabilities(undefined),
...overrides
})
describe('edit rights', () => {
it('lets a compose-only flow role author flows and nothing else', () => {
const builder = role({ flows: 'compose' })
expect(roleCanAuthor('flow', builder)).toBe(true)
expect(roleCanAuthor('script', builder)).toBe(false)
expect(roleCanAuthor('raw_app', builder)).toBe(false)
expect(roleCanDraft('raw_app', builder)).toBe(false)
expect(roleCanDraft('raw_app', role({ apps: 'compose' }))).toBe(true)
})
// apps.rs `require_full_apps`: a composing role builds full-code apps, never low-code ones.
it('keeps low-code apps for full app authors', () => {
expect(roleCanAuthor('app', role({ apps: 'compose' }))).toBe(false)
expect(roleCanAuthor('raw_app', role({ apps: 'compose' }))).toBe(true)
const scripter = { ...FULL_CAPABILITIES, apps: 'none' as const }
expect(roleCanAuthor('script', scripter)).toBe(true)
expect(roleCanAuthor('app', scripter)).toBe(false)
expect(roleCanAuthor('raw_app', scripter)).toBe(false)
})
// drafts.rs admits a restricted role's draft of a kind it could deploy, and no other.
it('lets a role draft schedules and triggers until the right is withdrawn', () => {
expect(roleCanDraft('schedule', role({}))).toBe(true)
expect(roleCanDraft('trigger', role({}))).toBe(true)
expect(roleCanDraft('schedule', role({ manage_schedules: false }))).toBe(false)
expect(roleCanDraft('trigger', role({ manage_triggers: false }))).toBe(false)
})
it('lets any role draft resources and variables, but scripts only with code', () => {
expect(roleCanDraft('resource', role({}))).toBe(true)
expect(roleCanDraft('variable', role({}))).toBe(true)
expect(roleCanDraft('script', role({}))).toBe(false)
expect(roleCanDraft('script', role({ code: true }))).toBe(true)
})
// The role alone is not enough: a builder still cannot edit a flow in a folder they only read.
it('requires write access to the item on top of the role', () => {
const builder = role({ flows: 'compose' })
expect(canEditItem('flow', 'f/demo/sum', {}, user({}), builder)).toBe(false)
expect(canEditItem('flow', 'u/op/sum', {}, user({}), builder)).toBe(true)
})
})