import type { AccessPolicyWithBindings, ResourceUserAccessSetting } from '@/models/access-control' import type { Member } from '@/models/common' import { fireEvent, render, screen, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import AccessRulesEditor from '../index' const mockMembers = vi.hoisted(() => ({ accounts: [] as Member[] | null, isLoading: false, })) vi.mock('@/service/use-common', () => ({ useMembers: vi.fn(() => ({ data: { accounts: mockMembers.accounts }, isLoading: mockMembers.isLoading, })), })) const createRule = (resourceType: 'app' | 'dataset'): AccessPolicyWithBindings => ({ policy: { id: `${resourceType}-policy-id`, tenant_id: 'tenant-id', resource_type: resourceType, policy_key: `${resourceType}-policy-key`, name: `${resourceType} policy`, description: `${resourceType} policy description`, permission_keys: [], is_builtin: false, category: 'global_custom', created_at: '2026-05-22T00:00:00Z', updated_at: '2026-05-22T00:00:00Z', }, roles: [], accounts: [], }) const createUserAccessSetting = (): ResourceUserAccessSetting => ({ account: { account_id: 'account-1', account_name: 'Evan', email: 'evan@example.com', }, roles: [ { id: 'role-1', type: 'app', category: 'global_custom', name: 'Maintainer', is_builtin: false, permission_keys: [], }, ], access_policies: [ { id: 'app-policy-id', tenant_id: 'tenant-id', resource_type: 'app', policy_key: 'app-policy-key', name: 'Manage', description: 'Can manage this app', permission_keys: [], is_builtin: false, category: 'global_custom', }, ], }) const createDefaultUserAccessSetting = (): ResourceUserAccessSetting => ({ ...createUserAccessSetting(), access_policies: [], }) const createMember = (overrides: Partial = {}): Member => ({ id: 'account-2', name: 'Mia', email: 'mia@example.com', avatar: '', avatar_url: '', status: 'active', role: 'normal', roles: [], ...overrides, }) as Member describe('AccessRulesEditor', () => { beforeEach(() => { vi.clearAllMocks() mockMembers.accounts = [] mockMembers.isLoading = false }) it('should render loading state before empty or row content', () => { render( , ) expect(screen.getByRole('status', { name: 'appApi.loading' })).toBeInTheDocument() expect(screen.queryByText('permission.accessRule.noUserAccessSettings')).not.toBeInTheDocument() }) it('should render empty state when there are no user access settings', () => { render( , ) expect(screen.getByText('permission.accessRule.noUserAccessSettings')).toBeInTheDocument() }) it('should disable resource access controls before open scope is available', () => { render( , ) expect(screen.getByText('permission.accessRule.resourceOpenScope')).toBeInTheDocument() expect( screen.getByRole('button', { name: 'permission.accessRule.resourceOpenScopeDescription' }), ).toBeInTheDocument() const allMembersButton = screen.getByRole('button', { name: /permission\.accessRule\.allPermittedMembers/, }) const onlyMeButton = screen.getByRole('button', { name: /permission\.accessRule\.onlyMe/ }) const specificMembersButton = screen.getByRole('button', { name: /permission\.accessRule\.specificMembersOnly/, }) expect(allMembersButton).toBeDisabled() expect(onlyMeButton).toBeDisabled() expect(specificMembersButton).toBeDisabled() expect(allMembersButton).toHaveAttribute('aria-pressed', 'false') expect(onlyMeButton).toHaveAttribute('aria-pressed', 'false') expect(specificMembersButton).toHaveAttribute('aria-pressed', 'false') }) it('should render resource access controls and update account exceptions', () => { const onOpenScopeChange = vi.fn() const onUserAccessPoliciesChange = vi.fn() const onRemoveAccessPolicyMemberBinding = vi.fn() render( , ) expect(screen.getByText('permission.accessRule.resourceOpenScope')).toBeInTheDocument() expect( screen.getByRole('button', { name: /permission\.accessRule\.specificMembersOnly/ }), ).toHaveAttribute('aria-pressed', 'true') expect( screen.getByText('permission.accessRule.individualPermissionSettings'), ).toBeInTheDocument() expect(screen.getByText('Evan')).toBeInTheDocument() expect(screen.getByText('evan@example.com')).toBeInTheDocument() expect(screen.queryByText('Maintainer')).not.toBeInTheDocument() expect(screen.getAllByText('Manage').length).toBeGreaterThan(0) fireEvent.click( screen.getByRole('button', { name: /permission\.accessRule\.allPermittedMembers/ }), ) expect(onOpenScopeChange).not.toHaveBeenCalled() expect(screen.getByText('permission.accessRule.changeOpenScopeTitle')).toBeInTheDocument() expect(screen.getByText('permission.accessRule.changeOpenScopeDescription')).toBeInTheDocument() fireEvent.click(screen.getByRole('button', { name: 'common.operation.change' })) expect(onOpenScopeChange).toHaveBeenCalledWith('all') fireEvent.click(screen.getByRole('button', { name: 'common.operation.remove' })) expect(onRemoveAccessPolicyMemberBinding).toHaveBeenCalledWith('account-1', 'app-policy-id') }) it('should render and update the only-me resource access scope', () => { const onOpenScopeChange = vi.fn() render( , ) expect(screen.getByRole('button', { name: /permission\.accessRule\.onlyMe/ })).toHaveAttribute( 'aria-pressed', 'true', ) fireEvent.click( screen.getByRole('button', { name: /permission\.accessRule\.specificMembersOnly/ }), ) fireEvent.click(screen.getByRole('button', { name: 'common.operation.change' })) expect(onOpenScopeChange).toHaveBeenCalledWith('specific') }) it('should render the fixed default option when an account has no exception policy', () => { render( , ) expect(screen.getByText('permission.accessRule.defaultPermission')).toBeInTheDocument() }) it('should mark maintainer rows and prevent editing them', () => { const onUserAccessPoliciesChange = vi.fn() const onRemoveAccessPolicyMemberBinding = vi.fn() render( , ) expect(screen.getByText('permission.accessRule.maintainer')).toBeInTheDocument() expect(screen.getByLabelText(/permission\.accessRule\.exceptionPermissionFor/)).toBeDisabled() const removeButton = screen.getByRole('button', { name: 'common.operation.remove' }) expect(removeButton).toBeDisabled() fireEvent.click(removeButton) expect(onUserAccessPoliciesChange).not.toHaveBeenCalled() expect(onRemoveAccessPolicyMemberBinding).not.toHaveBeenCalled() }) it('should keep open scope unchanged when the confirmation is cancelled', () => { const onOpenScopeChange = vi.fn() render( , ) fireEvent.click( screen.getByRole('button', { name: /permission\.accessRule\.allPermittedMembers/ }), ) fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' })) expect(onOpenScopeChange).not.toHaveBeenCalled() }) it('should add unassigned members with default permission', async () => { const user = userEvent.setup() const onAddAccessSubject = vi.fn() mockMembers.accounts = [ createMember({ id: 'account-1', name: 'Evan', email: 'evan@example.com', }), createMember(), ] render( , ) await user.click(screen.getByRole('button', { name: 'common.operation.add' })) const dialog = screen.getByRole('dialog', { name: 'permission.accessRule.addMembersTitle' }) expect(within(dialog).getByText('Evan')).toBeInTheDocument() expect(within(dialog).getByRole('button', { name: 'common.operation.added' })).toBeDisabled() expect( within(dialog).queryByRole('button', { name: 'permission.accessRule.addMemberAria:{"name":"Evan"}', }), ).not.toBeInTheDocument() expect(within(dialog).getByText('Mia')).toBeInTheDocument() expect(within(dialog).queryByRole('tablist')).not.toBeInTheDocument() await user.click( within(dialog).getByRole('button', { name: 'permission.accessRule.addMemberAria:{"name":"Mia"}', }), ) expect(onAddAccessSubject).toHaveBeenCalledWith('account-2', ['default']) }) })