// Import mocks for assertions import type { AppContextStateMockState } from '@/__tests__/utils/mock-app-context-state' import type { LangGeniusVersionInfo } from '@/context/app-context-types' import { toast } from '@langgenius/dify-ui/toast' import { waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { renderHookWithSystemFeatures as renderHook } from '@/__tests__/utils/mock-system-features' import { useInvalidateReferenceSettings, useMutationPluginPermissionSettings, useMutationReferenceSettings, usePluginAutoUpgradeSettings, usePluginPermissionSettings } from '@/service/use-plugins' import { PermissionType, PluginCategoryEnum } from '../../types' import useReferenceSetting, { useCanInstallPluginFromMarketplace } from '../use-reference-setting' const defaultLangGeniusVersionInfo: LangGeniusVersionInfo = { current_env: '', current_version: '1.0.0', latest_version: '', release_date: '', release_notes: '', version: '', features: { can_replace_logo: false, model_load_balancing_enabled: false, }, can_auto_update: false, } type MockAppContextState = Omit & { langGeniusVersionInfo?: Partial } let mockAppContextState: AppContextStateMockState = {} const setAppContextState = (state: MockAppContextState) => { mockAppContextState = { ...state, langGeniusVersionInfo: { ...defaultLangGeniusVersionInfo, ...state.langGeniusVersionInfo, }, } } vi.mock('@/context/account-state', async (importOriginal) => { const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) }) vi.mock('@/context/workspace-state', async (importOriginal) => { const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) }) vi.mock('@/context/permission-state', async (importOriginal) => { const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) }) vi.mock('@/context/version-state', async (importOriginal) => { const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) }) vi.mock('@/context/system-features-state', async (importOriginal) => { const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') return createAppContextStateAtomMock(importOriginal, () => mockAppContextState) }) vi.mock('jotai', async (importOriginal) => { const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state') return createAppContextStateJotaiMock(importOriginal) }) vi.mock('@/service/use-plugins', () => ({ usePluginAutoUpgradeSettings: vi.fn(), usePluginPermissionSettings: vi.fn(), useMutationPluginPermissionSettings: vi.fn(), useMutationReferenceSettings: vi.fn(), useInvalidateReferenceSettings: vi.fn(), })) const toastSuccessSpy = vi.spyOn(toast, 'success').mockReturnValue('toast-success') describe('useReferenceSetting Hook', () => { beforeEach(() => { vi.clearAllMocks() toastSuccessSpy.mockClear() // Default mocks setAppContextState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, workspacePermissionKeys: [] as string[], }) vi.mocked(usePluginAutoUpgradeSettings).mockReturnValue({ data: { category: PluginCategoryEnum.tool, auto_upgrade: { strategy_setting: 'fix_only', upgrade_time_of_day: 0, upgrade_mode: 'all', exclude_plugins: [], include_plugins: [], }, }, } as unknown as ReturnType) vi.mocked(usePluginPermissionSettings).mockReturnValue({ data: { install_permission: PermissionType.everyone, debug_permission: PermissionType.everyone, }, } as ReturnType) vi.mocked(useMutationReferenceSettings).mockReturnValue({ mutate: vi.fn(), isPending: false, } as unknown as ReturnType) vi.mocked(useMutationPluginPermissionSettings).mockReturnValue({ mutate: vi.fn(), isPending: false, } as unknown as ReturnType) vi.mocked(useInvalidateReferenceSettings).mockReturnValue(vi.fn()) }) describe('permission key access', () => { it('should not expose installed plugin list viewing as a permission capability', () => { const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool)) expect('canViewInstalledPlugins' in result.current).toBe(false) }) it('should return false without plugin permission keys', () => { vi.mocked(usePluginPermissionSettings).mockReturnValue({ data: { install_permission: undefined, debug_permission: undefined, }, } as unknown as ReturnType) const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool)) expect(result.current.canManagement).toBe(false) expect(result.current.canDebugger).toBe(false) }) it('should ignore legacy noOne permission when plugin keys are missing', () => { vi.mocked(usePluginPermissionSettings).mockReturnValue({ data: { install_permission: PermissionType.noOne, debug_permission: PermissionType.noOne, }, } as ReturnType) const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool)) expect(result.current.canManagement).toBe(false) expect(result.current.canDebugger).toBe(false) }) it('should allow install and debug when plugin permission keys are present', () => { setAppContextState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, workspacePermissionKeys: ['plugin.install', 'plugin.debug'], }) vi.mocked(usePluginPermissionSettings).mockReturnValue({ data: { install_permission: PermissionType.everyone, debug_permission: PermissionType.everyone, }, } as ReturnType) const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool)) expect(result.current.canManagement).toBe(true) expect(result.current.canDebugger).toBe(true) }) it('should allow debug for managers with legacy admin permission when RBAC is disabled', () => { setAppContextState({ isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, workspacePermissionKeys: [] as string[], }) vi.mocked(usePluginPermissionSettings).mockReturnValue({ data: { install_permission: PermissionType.admin, debug_permission: PermissionType.admin, }, } as ReturnType) const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool)) expect(result.current.canManagement).toBe(false) expect(result.current.canDebugger).toBe(true) }) it('should allow debug for owners with legacy admin permission when RBAC is disabled', () => { setAppContextState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: true, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, workspacePermissionKeys: [] as string[], }) vi.mocked(usePluginPermissionSettings).mockReturnValue({ data: { install_permission: PermissionType.admin, debug_permission: PermissionType.admin, }, } as ReturnType) const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool)) expect(result.current.canManagement).toBe(false) expect(result.current.canDebugger).toBe(true) }) it('should allow debug for normal users when legacy debug permission is everyone and RBAC is disabled', () => { setAppContextState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, workspacePermissionKeys: ['plugin.install'], }) vi.mocked(usePluginPermissionSettings).mockReturnValue({ data: { install_permission: PermissionType.everyone, debug_permission: PermissionType.everyone, }, } as ReturnType) const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool), { systemFeatures: { rbac_enabled: false }, }) expect(result.current.canDebugPlugin).toBe(true) expect(result.current.canDebugger).toBe(true) }) it('should use plugin keys even when legacy admin permission is configured and RBAC is enabled', () => { setAppContextState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, workspacePermissionKeys: ['plugin.install', 'plugin.debug'], }) vi.mocked(usePluginPermissionSettings).mockReturnValue({ data: { install_permission: PermissionType.admin, debug_permission: PermissionType.admin, }, } as ReturnType) const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool), { systemFeatures: { rbac_enabled: true }, }) expect(result.current.canManagement).toBe(true) expect(result.current.canDebugger).toBe(true) }) it('should apply legacy noOne plugin permissions when RBAC is disabled', () => { setAppContextState({ isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, workspacePermissionKeys: ['plugin.install', 'plugin.delete', 'plugin.debug'], }) vi.mocked(usePluginPermissionSettings).mockReturnValue({ data: { install_permission: PermissionType.noOne, debug_permission: PermissionType.noOne, }, } as ReturnType) const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool), { systemFeatures: { rbac_enabled: false }, }) expect(result.current.canInstallPlugin).toBe(false) expect(result.current.canManagement).toBe(false) expect(result.current.canUpdatePlugin).toBe(false) expect(result.current.canDeletePlugin).toBe(false) expect(result.current.canDebugPlugin).toBe(false) expect(result.current.canDebugger).toBe(false) }) }) describe('canSetPermissions', () => { it('should be true with plugin preferences permission when RBAC is disabled', () => { setAppContextState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, workspacePermissionKeys: ['plugin.plugin_preferences'], }) const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool)) expect(result.current.canSetPermissions).toBe(true) }) it('should be false when RBAC is enabled even with plugin preferences permission', () => { setAppContextState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: true, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, workspacePermissionKeys: ['plugin.plugin_preferences'], }) const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool), { systemFeatures: { rbac_enabled: true }, }) expect(result.current.canSetPermissions).toBe(false) expect(result.current.canSetPluginPreferences).toBe(true) }) it('should be false without plugin preferences permission', () => { setAppContextState({ isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, workspacePermissionKeys: [] as string[], }) const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool)) expect(result.current.canSetPermissions).toBe(false) }) }) describe('setReferenceSettings callback', () => { it('should call invalidateReferenceSettings and show toast on success', async () => { const mockInvalidate = vi.fn() vi.mocked(useInvalidateReferenceSettings).mockReturnValue(mockInvalidate) let onSuccessCallback: (() => void) | undefined vi.mocked(useMutationReferenceSettings).mockImplementation((options) => { onSuccessCallback = options?.onSuccess as () => void return { mutate: vi.fn(), isPending: false, } as unknown as ReturnType }) renderHook(() => useReferenceSetting(PluginCategoryEnum.tool)) // Trigger the onSuccess callback if (onSuccessCallback) onSuccessCallback() await waitFor(() => { expect(mockInvalidate).toHaveBeenCalled() expect(toastSuccessSpy).toHaveBeenCalledWith('common.api.actionSuccess') }) }) }) describe('returned values', () => { it('should return referenceSetting data', () => { const mockData = { permission: { install_permission: PermissionType.everyone, debug_permission: PermissionType.everyone, }, auto_upgrade: { strategy_setting: 'fix_only', upgrade_time_of_day: 0, upgrade_mode: 'all', exclude_plugins: [], include_plugins: [], }, } vi.mocked(usePluginAutoUpgradeSettings).mockReturnValue({ data: { category: PluginCategoryEnum.tool, auto_upgrade: mockData.auto_upgrade, }, } as unknown as ReturnType) const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool)) expect(result.current.referenceSetting).toEqual(mockData) }) it('should return isUpdatePending from mutation', () => { vi.mocked(useMutationReferenceSettings).mockReturnValue({ mutate: vi.fn(), isPending: true, } as unknown as ReturnType) const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool)) expect(result.current.isUpdatePending).toBe(true) }) it('should keep permission key access available when reference setting data is still loading', () => { setAppContextState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, workspacePermissionKeys: ['plugin.install', 'plugin.debug'], }) vi.mocked(usePluginAutoUpgradeSettings).mockReturnValue({ data: undefined, } as unknown as ReturnType) const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool)) expect(result.current.referenceSetting).toBeUndefined() expect(result.current.canManagement).toBe(true) expect(result.current.canDebugger).toBe(true) }) it('should keep permission state loading while workspace permission keys are loading', () => { setAppContextState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, isLoadingWorkspacePermissionKeys: true, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, workspacePermissionKeys: [] as string[], }) const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool)) expect(result.current.isPermissionLoading).toBe(true) expect(result.current.canInstallPlugin).toBe(false) }) it('should keep permission state loading while current workspace is loading', () => { setAppContextState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, isLoadingCurrentWorkspace: true, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, workspacePermissionKeys: ['plugin.install'], }) const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool)) expect(result.current.isPermissionLoading).toBe(true) }) }) describe('RBAC permissions', () => { it('should use workspace permission keys when RBAC is enabled', () => { setAppContextState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, workspacePermissionKeys: [ 'plugin.install', 'plugin.delete', 'plugin.debug', 'plugin.plugin_preferences', ], }) vi.mocked(usePluginPermissionSettings).mockReturnValue({ data: { install_permission: PermissionType.noOne, debug_permission: PermissionType.noOne, }, } as ReturnType) const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool), { systemFeatures: { rbac_enabled: true }, }) expect(result.current.canInstallPlugin).toBe(true) expect(result.current.canManagement).toBe(true) expect(result.current.canUpdatePlugin).toBe(true) expect(result.current.canDeletePlugin).toBe(true) expect(result.current.canDebugPlugin).toBe(true) expect(result.current.canDebugger).toBe(true) expect(result.current.canSetPermissions).toBe(false) expect(result.current.canSetPluginPreferences).toBe(true) }) it('should ignore legacy plugin permission settings when RBAC is enabled', () => { setAppContextState({ isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, workspacePermissionKeys: [] as string[], }) const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool), { systemFeatures: { rbac_enabled: true }, }) expect(result.current.canInstallPlugin).toBe(false) expect(result.current.canManagement).toBe(false) expect(result.current.canUpdatePlugin).toBe(false) expect(result.current.canDeletePlugin).toBe(false) expect(result.current.canDebugPlugin).toBe(false) expect(result.current.canDebugger).toBe(false) expect(result.current.canSetPermissions).toBe(false) expect(result.current.canSetPluginPreferences).toBe(false) }) }) }) describe('useCanInstallPluginFromMarketplace Hook', () => { beforeEach(() => { vi.clearAllMocks() setAppContextState({ isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, workspacePermissionKeys: ['plugin.install'], }) vi.mocked(usePluginPermissionSettings).mockReturnValue({ data: { install_permission: PermissionType.everyone, debug_permission: PermissionType.everyone, }, } as ReturnType) vi.mocked(useMutationReferenceSettings).mockReturnValue({ mutate: vi.fn(), isPending: false, } as unknown as ReturnType) vi.mocked(useInvalidateReferenceSettings).mockReturnValue(vi.fn()) }) it('should return true when marketplace is enabled and plugin.install is available', () => { const { result } = renderHook(() => useCanInstallPluginFromMarketplace(), { systemFeatures: { enable_marketplace: true }, }) expect(result.current.canInstallPluginFromMarketplace).toBe(true) }) it('should return false when marketplace is disabled', () => { const { result } = renderHook(() => useCanInstallPluginFromMarketplace(), { systemFeatures: { enable_marketplace: false }, }) expect(result.current.canInstallPluginFromMarketplace).toBe(false) }) it('should return false without plugin.install', () => { setAppContextState({ isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, workspacePermissionKeys: [] as string[], }) const { result } = renderHook(() => useCanInstallPluginFromMarketplace(), { systemFeatures: { enable_marketplace: true }, }) expect(result.current.canInstallPluginFromMarketplace).toBe(false) }) it('should return false when both marketplace is disabled and plugin.install is missing', () => { setAppContextState({ isCurrentWorkspaceManager: true, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, workspacePermissionKeys: [] as string[], }) const { result } = renderHook(() => useCanInstallPluginFromMarketplace(), { systemFeatures: { enable_marketplace: false }, }) expect(result.current.canInstallPluginFromMarketplace).toBe(false) }) it('should fetch legacy plugin permissions but not category auto-upgrade settings', () => { renderHook(() => useCanInstallPluginFromMarketplace(), { systemFeatures: { enable_marketplace: true }, }) expect(usePluginPermissionSettings).toHaveBeenCalled() expect(usePluginAutoUpgradeSettings).not.toHaveBeenCalled() }) it('should return false when legacy install permission is noOne and RBAC is disabled', () => { vi.mocked(usePluginPermissionSettings).mockReturnValue({ data: { install_permission: PermissionType.noOne, debug_permission: PermissionType.everyone, }, } as ReturnType) const { result } = renderHook(() => useCanInstallPluginFromMarketplace(), { systemFeatures: { enable_marketplace: true, rbac_enabled: false, }, }) expect(result.current.canInstallPluginFromMarketplace).toBe(false) }) it('should use plugin.install when marketplace and RBAC are enabled', () => { setAppContextState({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' }, workspacePermissionKeys: ['plugin.install'], }) vi.mocked(usePluginPermissionSettings).mockReturnValue({ data: { install_permission: PermissionType.noOne, debug_permission: PermissionType.noOne, }, } as ReturnType) const { result } = renderHook(() => useCanInstallPluginFromMarketplace(), { systemFeatures: { enable_marketplace: true, rbac_enabled: true, }, }) expect(result.current.canInstallPluginFromMarketplace).toBe(true) }) })