mirror of
https://github.com/langgenius/dify.git
synced 2026-07-21 18:58:35 +08:00
refactor(web): migrate plugins and tools app context consumers (#38533)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
98f67e9c82
commit
503d80be1d
@ -3457,14 +3457,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/switch-credential-in-load-balancing.spec.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
},
|
||||
"jsx-a11y/no-static-element-interactions": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/header/account-setting/model-provider-page/model-auth/add-custom-model.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 2
|
||||
|
||||
@ -44,7 +44,13 @@ vi.mock('@/context/app-context', () => ({
|
||||
isCurrentWorkspaceManager: false,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: {
|
||||
current_env: 'CLOUD',
|
||||
current_version: '1.0.0',
|
||||
latest_version: '1.0.0',
|
||||
version: '1.0.0',
|
||||
release_date: '',
|
||||
release_notes: '',
|
||||
can_auto_update: false,
|
||||
},
|
||||
workspacePermissionKeys: [
|
||||
'plugin.install',
|
||||
@ -54,6 +60,33 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
isCurrentWorkspaceManager: false,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: {
|
||||
current_env: 'CLOUD',
|
||||
current_version: '1.0.0',
|
||||
latest_version: '1.0.0',
|
||||
version: '1.0.0',
|
||||
release_date: '',
|
||||
release_notes: '',
|
||||
can_auto_update: false,
|
||||
},
|
||||
workspacePermissionKeys: [
|
||||
'plugin.install',
|
||||
'plugin.delete',
|
||||
'plugin.plugin_preferences',
|
||||
],
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/service/use-plugins', () => ({
|
||||
hasPluginPermission: () => true,
|
||||
useReferenceSettings: () => ({
|
||||
|
||||
@ -53,6 +53,19 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
isCurrentWorkspaceManager: true,
|
||||
workspacePermissionKeys: ['tool.manage', 'credential.create', 'credential.manage', 'credential.use'],
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
const mockSetShowModelModal = vi.fn()
|
||||
vi.mock('@/context/modal-context', () => ({
|
||||
useModalContext: () => ({
|
||||
|
||||
@ -14,6 +14,10 @@ export type AppContextStateMockState = {
|
||||
currentWorkspace?: {
|
||||
id?: string
|
||||
} | null
|
||||
isCurrentWorkspaceManager?: boolean
|
||||
isCurrentWorkspaceOwner?: boolean
|
||||
isCurrentWorkspaceEditor?: boolean
|
||||
isCurrentWorkspaceDatasetOperator?: boolean
|
||||
isLoadingCurrentWorkspace?: boolean
|
||||
isLoadingWorkspacePermissionKeys?: boolean
|
||||
workspacePermissionKeys?: string[]
|
||||
@ -25,6 +29,7 @@ type AppContextStateAtomKind
|
||||
| 'userProfileId'
|
||||
| 'currentWorkspace'
|
||||
| 'currentWorkspaceId'
|
||||
| 'workspaceRoleFlags'
|
||||
| 'currentWorkspaceLoading'
|
||||
| 'workspacePermissionKeys'
|
||||
| 'workspacePermissionKeysLoading'
|
||||
@ -98,6 +103,7 @@ export const createAppContextStateAtomMock = async (
|
||||
userProfileIdAtom: createMockAtom('userProfileId'),
|
||||
currentWorkspaceAtom: createMockAtom('currentWorkspace'),
|
||||
currentWorkspaceIdAtom: createMockAtom('currentWorkspaceId'),
|
||||
workspaceRoleFlagsAtom: createMockAtom('workspaceRoleFlags'),
|
||||
currentWorkspaceLoadingAtom: createMockAtom('currentWorkspaceLoading'),
|
||||
workspacePermissionKeysAtom: createMockAtom('workspacePermissionKeys'),
|
||||
workspacePermissionKeysLoadingAtom: createMockAtom('workspacePermissionKeysLoading'),
|
||||
@ -135,6 +141,15 @@ export const createAppContextStateJotaiMock = async (
|
||||
if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'currentWorkspaceId')
|
||||
return currentWorkspace.id
|
||||
|
||||
if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'workspaceRoleFlags') {
|
||||
return {
|
||||
isCurrentWorkspaceManager: state.isCurrentWorkspaceManager ?? false,
|
||||
isCurrentWorkspaceOwner: state.isCurrentWorkspaceOwner ?? false,
|
||||
isCurrentWorkspaceEditor: state.isCurrentWorkspaceEditor ?? false,
|
||||
isCurrentWorkspaceDatasetOperator: state.isCurrentWorkspaceDatasetOperator ?? false,
|
||||
}
|
||||
}
|
||||
|
||||
if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'currentWorkspaceLoading')
|
||||
return state.isLoadingCurrentWorkspace ?? false
|
||||
|
||||
|
||||
@ -116,6 +116,19 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
isCurrentWorkspaceManager: true,
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
toast: {
|
||||
error: (...args: unknown[]) => mockToastError(...args),
|
||||
|
||||
@ -31,6 +31,16 @@ vi.mock('@/context/app-context', async (importOriginal) => {
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current ?? {})
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/next/navigation', () => ({
|
||||
useRouter: vi.fn(() => ({
|
||||
push: vi.fn(),
|
||||
@ -77,10 +87,14 @@ vi.mock('@/service/use-datasource', () => ({
|
||||
useGetDataSourceListAuth: vi.fn(() => ({ data: { result: [] } })),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-common', () => ({
|
||||
useMembers: vi.fn(() => ({ data: { accounts: [] }, refetch: vi.fn() })),
|
||||
useProviderContext: vi.fn(),
|
||||
}))
|
||||
vi.mock('@/service/use-common', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/service/use-common')>()
|
||||
return {
|
||||
...actual,
|
||||
useMembers: vi.fn(() => ({ data: { accounts: [] }, refetch: vi.fn() })),
|
||||
useProviderContext: vi.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/service/client', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/service/client')>()
|
||||
|
||||
@ -19,6 +19,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/app/components/plugins/plugin-auth', () => ({
|
||||
ApiKeyModal: vi.fn(({ onClose, onUpdate, onRemove, disabled, editValues }: { onClose: () => void, onUpdate: () => void, onRemove: () => void, disabled: boolean, editValues: Record<string, unknown> }) => (
|
||||
<div data-testid="mock-api-key-modal" data-disabled={disabled}>
|
||||
|
||||
@ -13,6 +13,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys.value,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/app/components/header/account-setting/model-provider-page/model-auth', () => ({
|
||||
Authorized: ({
|
||||
renderTrigger,
|
||||
|
||||
@ -34,6 +34,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys.value,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
// Mock components
|
||||
vi.mock('../../model-icon', () => ({
|
||||
default: () => <div data-testid="model-icon" />,
|
||||
|
||||
@ -14,6 +14,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys.value,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
// Mock components
|
||||
vi.mock('../authorized', () => ({
|
||||
default: ({
|
||||
@ -37,7 +49,12 @@ vi.mock('../authorized', () => ({
|
||||
data-hide-add-action={String(!!hideAddAction)}
|
||||
data-trigger-only-open-modal={String(!!triggerOnlyOpenModal)}
|
||||
>
|
||||
<div data-testid="trigger-container" onClick={() => onItemClick?.(items[0]!.credentials[0])}>
|
||||
<div
|
||||
role="presentation"
|
||||
data-testid="trigger-container"
|
||||
onClick={() => onItemClick?.(items[0]!.credentials[0])}
|
||||
onKeyDown={() => undefined}
|
||||
>
|
||||
{renderTrigger()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -19,6 +19,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('../../hooks', () => ({
|
||||
useAuth: () => ({
|
||||
openConfirmDelete: mockOpenConfirmDelete,
|
||||
|
||||
@ -80,6 +80,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
selector({ workspacePermissionKeys: mockState.workspacePermissionKeys }),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockState.workspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/hooks/use-i18n', () => ({
|
||||
useRenderI18nObject: () => (value: { en_US: string }) => value.en_US,
|
||||
}))
|
||||
|
||||
@ -94,6 +94,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys.value,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
const makeModelItem = (overrides: Partial<ModelItem> = {}): ModelItem => ({
|
||||
model: 'gpt-4',
|
||||
label: { en_US: 'GPT-4', zh_Hans: 'GPT-4' },
|
||||
|
||||
@ -43,6 +43,19 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager,
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
// Mock internal components to simplify testing of the index file
|
||||
vi.mock('../credential-panel', () => ({
|
||||
default: () => <div data-testid="credential-panel" />,
|
||||
|
||||
@ -9,6 +9,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'],
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
const createCredential = (overrides: Partial<Credential> = {}): Credential => ({
|
||||
credential_id: 'cred-1',
|
||||
credential_name: 'Test API Key',
|
||||
|
||||
@ -41,6 +41,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('../../../model-auth/authorized/credential-item', () => ({
|
||||
default: ({ credential, disabled, disableEdit, disableDelete, onItemClick, onEdit, onDelete }: {
|
||||
credential: { credential_id: string, credential_name: string }
|
||||
|
||||
@ -103,6 +103,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockAppContextState.workspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
let mockCheckedInstalledData: { plugins: { id: string, name: string }[] } | null = null
|
||||
const mockInvalidateInstalledPluginList = vi.fn()
|
||||
vi.mock('@/service/use-plugins', () => ({
|
||||
|
||||
@ -41,11 +41,17 @@ vi.mock('@/utils/format', () => ({
|
||||
formatNumber: (num: number) => num.toLocaleString(),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useSelector: (selector: (value: { currentWorkspace: { id: string } }) => string) => selector({
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
currentWorkspace: { id: 'workspace-123' },
|
||||
}),
|
||||
}))
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/utils/mcp', () => ({
|
||||
shouldUseMcpIcon: (src: unknown) => typeof src === 'object' && src !== null && (src as { content?: string })?.content === '🔗',
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
'use client'
|
||||
import type { Plugin } from '../types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { useSelector } from '@/context/app-context'
|
||||
import { currentWorkspaceIdAtom } from '@/context/app-context-state'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
import {
|
||||
@ -61,7 +62,7 @@ const Card = ({
|
||||
const locale = useGetLanguage()
|
||||
const { t } = useTranslation()
|
||||
const { categoriesMap } = useCategories(true)
|
||||
const currentWorkspaceId = useSelector(s => s.currentWorkspace.id)
|
||||
const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom)
|
||||
const { category, type, name, org, label, brief, icon, icon_dark, verified, from } = payload
|
||||
const badges = payload.badges ?? []
|
||||
const { theme } = useTheme()
|
||||
|
||||
@ -2,15 +2,27 @@ import { renderHook } from '@testing-library/react'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import useGetIcon from '../use-get-icon'
|
||||
|
||||
const mockCurrentWorkspaceIdAtom = vi.hoisted(() => Symbol('currentWorkspaceIdAtom'))
|
||||
|
||||
vi.mock('@/config', () => ({
|
||||
API_PREFIX: 'https://api.example.com',
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useSelector: (selector: (state: { currentWorkspace: { id: string } }) => string | { id: string }) =>
|
||||
selector({ currentWorkspace: { id: 'workspace-123' } }),
|
||||
vi.mock('@/context/app-context-state', () => ({
|
||||
currentWorkspaceIdAtom: mockCurrentWorkspaceIdAtom,
|
||||
}))
|
||||
|
||||
vi.mock('jotai', () => {
|
||||
return {
|
||||
useAtomValue: (atom: unknown) => {
|
||||
if (atom === mockCurrentWorkspaceIdAtom)
|
||||
return 'workspace-123'
|
||||
|
||||
throw new Error('Unexpected atom')
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('useGetIcon', () => {
|
||||
it('builds icon url with current workspace id', () => {
|
||||
const { result } = renderHook(() => useGetIcon())
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback } from 'react'
|
||||
import { API_PREFIX } from '@/config'
|
||||
import { useSelector } from '@/context/app-context'
|
||||
import { currentWorkspaceIdAtom } from '@/context/app-context-state'
|
||||
|
||||
const useGetIcon = () => {
|
||||
const currentWorkspace = useSelector(s => s.currentWorkspace)
|
||||
const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom)
|
||||
const getIconUrl = useCallback((fileName: string) => {
|
||||
return `${API_PREFIX}/workspaces/current/plugin/icon?tenant_id=${currentWorkspace.id}&filename=${fileName}`
|
||||
}, [currentWorkspace.id])
|
||||
return `${API_PREFIX}/workspaces/current/plugin/icon?tenant_id=${currentWorkspaceId}&filename=${fileName}`
|
||||
}, [currentWorkspaceId])
|
||||
|
||||
return {
|
||||
getIconUrl,
|
||||
|
||||
@ -4,12 +4,26 @@ import useWorkspacePluginInstallPermission from '../use-workspace-plugin-install
|
||||
|
||||
let mockWorkspacePermissionKeys: string[] = []
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({
|
||||
langGeniusVersionInfo: { current_version: '1.0.0' },
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
langGeniusVersionInfo: {
|
||||
current_env: '',
|
||||
current_version: '1.0.0',
|
||||
latest_version: '',
|
||||
release_date: '',
|
||||
release_notes: '',
|
||||
version: '',
|
||||
can_auto_update: false,
|
||||
},
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
}),
|
||||
}))
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
describe('useWorkspacePluginInstallPermission', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useMemo } from 'react'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { langGeniusVersionInfoAtom, workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
|
||||
const useWorkspacePluginInstallPermission = () => {
|
||||
const {
|
||||
langGeniusVersionInfo,
|
||||
workspacePermissionKeys,
|
||||
} = useAppContext()
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
|
||||
const canInstallPlugin = useMemo(() => {
|
||||
return hasPermission(workspacePermissionKeys, 'plugin.install')
|
||||
|
||||
@ -57,11 +57,22 @@ vi.mock('../../../base/check-task-status', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
const mockLangGeniusVersionInfo = { current_version: '1.0.0' }
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({
|
||||
langGeniusVersionInfo: mockLangGeniusVersionInfo,
|
||||
}),
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
langGeniusVersionInfoAtom: Symbol('langGeniusVersionInfoAtom'),
|
||||
langGeniusVersionInfo: { current_version: '1.0.0' as string | undefined },
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', () => ({
|
||||
langGeniusVersionInfoAtom: mockAppContextState.langGeniusVersionInfoAtom,
|
||||
}))
|
||||
|
||||
vi.mock('jotai', () => ({
|
||||
useAtomValue: (atom: unknown) => {
|
||||
if (atom === mockAppContextState.langGeniusVersionInfoAtom)
|
||||
return mockAppContextState.langGeniusVersionInfo
|
||||
|
||||
throw new Error('Unexpected atom')
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('../../../../card', () => ({
|
||||
@ -466,7 +477,7 @@ describe('Install', () => {
|
||||
// ================================
|
||||
describe('Dify Version Compatibility', () => {
|
||||
it('should not show warning when dify version is compatible', () => {
|
||||
mockLangGeniusVersionInfo.current_version = '1.0.0'
|
||||
mockAppContextState.langGeniusVersionInfo.current_version = '1.0.0'
|
||||
const payload = createMockManifest({ meta: { version: '1.0.0', minimum_dify_version: '0.8.0' } })
|
||||
|
||||
render(<Install {...defaultProps} payload={payload} />)
|
||||
@ -475,7 +486,7 @@ describe('Install', () => {
|
||||
})
|
||||
|
||||
it('should show warning when dify version is incompatible', () => {
|
||||
mockLangGeniusVersionInfo.current_version = '1.0.0'
|
||||
mockAppContextState.langGeniusVersionInfo.current_version = '1.0.0'
|
||||
const payload = createMockManifest({ meta: { version: '1.0.0', minimum_dify_version: '2.0.0' } })
|
||||
|
||||
render(<Install {...defaultProps} payload={payload} />)
|
||||
@ -484,7 +495,7 @@ describe('Install', () => {
|
||||
})
|
||||
|
||||
it('should be compatible when minimum_dify_version is undefined', () => {
|
||||
mockLangGeniusVersionInfo.current_version = '1.0.0'
|
||||
mockAppContextState.langGeniusVersionInfo.current_version = '1.0.0'
|
||||
const payload = createMockManifest({ meta: { version: '1.0.0' } })
|
||||
|
||||
render(<Install {...defaultProps} payload={payload} />)
|
||||
@ -493,7 +504,7 @@ describe('Install', () => {
|
||||
})
|
||||
|
||||
it('should be compatible when current_version is empty', () => {
|
||||
mockLangGeniusVersionInfo.current_version = ''
|
||||
mockAppContextState.langGeniusVersionInfo.current_version = ''
|
||||
const payload = createMockManifest({ meta: { version: '1.0.0', minimum_dify_version: '2.0.0' } })
|
||||
|
||||
render(<Install {...defaultProps} payload={payload} />)
|
||||
@ -503,7 +514,7 @@ describe('Install', () => {
|
||||
})
|
||||
|
||||
it('should be compatible when current_version is undefined', () => {
|
||||
mockLangGeniusVersionInfo.current_version = undefined as unknown as string
|
||||
mockAppContextState.langGeniusVersionInfo.current_version = undefined as unknown as string
|
||||
const payload = createMockManifest({ meta: { version: '1.0.0', minimum_dify_version: '2.0.0' } })
|
||||
|
||||
render(<Install {...defaultProps} payload={payload} />)
|
||||
|
||||
@ -3,11 +3,12 @@ import type { FC } from 'react'
|
||||
import type { PluginDeclaration } from '../../../types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { RiLoader2Line } from '@remixicon/react'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { langGeniusVersionInfoAtom } from '@/context/app-context-state'
|
||||
import { uninstallPlugin } from '@/service/plugins'
|
||||
import { useInstallPackageFromLocal, usePluginTaskList } from '@/service/use-plugins'
|
||||
import { isEqualOrLaterThanVersion } from '@/utils/semver'
|
||||
@ -108,7 +109,7 @@ const Installed: FC<Props> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const { langGeniusVersionInfo } = useAppContext()
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const isDifyVersionCompatible = useMemo(() => {
|
||||
if (!langGeniusVersionInfo.current_version)
|
||||
return true
|
||||
|
||||
@ -60,7 +60,10 @@ const mockStopTaskStatus = vi.fn()
|
||||
const mockHandleInstallTaskStart = vi.fn()
|
||||
let mockPluginDeclaration: { manifest: { meta: { minimum_dify_version: string } } } | undefined
|
||||
let mockCanInstall = true
|
||||
let mockLangGeniusVersionInfo = { current_version: '1.0.0' }
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
langGeniusVersionInfoAtom: Symbol('langGeniusVersionInfoAtom'),
|
||||
langGeniusVersionInfo: { current_version: '1.0.0' as string | null },
|
||||
}))
|
||||
|
||||
// Mock useCheckInstalled
|
||||
vi.mock('@/app/components/plugins/install-plugin/hooks/use-check-installed', () => ({
|
||||
@ -71,10 +74,17 @@ vi.mock('@/app/components/plugins/install-plugin/hooks/use-check-installed', ()
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({
|
||||
langGeniusVersionInfo: mockLangGeniusVersionInfo,
|
||||
}),
|
||||
vi.mock('@/context/app-context-state', () => ({
|
||||
langGeniusVersionInfoAtom: mockAppContextState.langGeniusVersionInfoAtom,
|
||||
}))
|
||||
|
||||
vi.mock('jotai', () => ({
|
||||
useAtomValue: (atom: unknown) => {
|
||||
if (atom === mockAppContextState.langGeniusVersionInfoAtom)
|
||||
return mockAppContextState.langGeniusVersionInfo
|
||||
|
||||
throw new Error('Unexpected atom')
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock service hooks
|
||||
@ -104,7 +114,7 @@ vi.mock('../../../base/check-task-status', () => ({
|
||||
vi.mock('@/app/components/plugins/install-plugin/hooks/use-plugin-install-permission', () => ({
|
||||
default: () => ({
|
||||
canInstallPlugin: true,
|
||||
currentDifyVersion: mockLangGeniusVersionInfo.current_version,
|
||||
currentDifyVersion: mockAppContextState.langGeniusVersionInfo.current_version,
|
||||
}),
|
||||
}))
|
||||
|
||||
@ -170,7 +180,7 @@ describe('Install Component (steps/install.tsx)', () => {
|
||||
mockIsLoading = false
|
||||
mockPluginDeclaration = undefined
|
||||
mockCanInstall = true
|
||||
mockLangGeniusVersionInfo = { current_version: '1.0.0' }
|
||||
mockAppContextState.langGeniusVersionInfo = { current_version: '1.0.0' }
|
||||
mockInstallPackageFromMarketPlace.mockResolvedValue({
|
||||
all_installed: false,
|
||||
task_id: 'task-123',
|
||||
@ -281,7 +291,7 @@ describe('Install Component (steps/install.tsx)', () => {
|
||||
})
|
||||
|
||||
it('should not show warning when dify version is compatible', () => {
|
||||
mockLangGeniusVersionInfo = { current_version: '2.0.0' }
|
||||
mockAppContextState.langGeniusVersionInfo = { current_version: '2.0.0' }
|
||||
mockPluginDeclaration = {
|
||||
manifest: { meta: { minimum_dify_version: '1.0.0' } },
|
||||
}
|
||||
@ -291,7 +301,7 @@ describe('Install Component (steps/install.tsx)', () => {
|
||||
})
|
||||
|
||||
it('should show warning when dify version is incompatible', () => {
|
||||
mockLangGeniusVersionInfo = { current_version: '1.0.0' }
|
||||
mockAppContextState.langGeniusVersionInfo = { current_version: '1.0.0' }
|
||||
mockPluginDeclaration = {
|
||||
manifest: { meta: { minimum_dify_version: '2.0.0' } },
|
||||
}
|
||||
@ -749,7 +759,7 @@ describe('Install Component (steps/install.tsx)', () => {
|
||||
})
|
||||
|
||||
it('should handle null current_version in langGeniusVersionInfo', () => {
|
||||
mockLangGeniusVersionInfo = { current_version: null as unknown as string }
|
||||
mockAppContextState.langGeniusVersionInfo = { current_version: null as unknown as string }
|
||||
mockPluginDeclaration = {
|
||||
manifest: { meta: { minimum_dify_version: '1.0.0' } },
|
||||
}
|
||||
|
||||
@ -3,11 +3,12 @@ import type { FC } from 'react'
|
||||
import type { InstallPackageResponse, Plugin, PluginManifestInMarket } from '../../../types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { RiLoader2Line } from '@remixicon/react'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { langGeniusVersionInfoAtom } from '@/context/app-context-state'
|
||||
import { useInstallPackageFromMarketPlace, usePluginDeclarationFromMarketPlace, usePluginTaskList, useUpdatePackageFromMarketPlace } from '@/service/use-plugins'
|
||||
import { isEqualOrLaterThanVersion } from '@/utils/semver'
|
||||
import Card from '../../../card'
|
||||
@ -133,7 +134,7 @@ const Installed: FC<Props> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const { langGeniusVersionInfo } = useAppContext()
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const { data: pluginDeclaration } = usePluginDeclarationFromMarketPlace(uniqueIdentifier)
|
||||
const isDifyVersionCompatible = useMemo(() => {
|
||||
if (!pluginDeclaration || !langGeniusVersionInfo.current_version)
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { PluginInstallPermissionProvider } from '@/app/components/plugins/install-plugin/components/plugin-install-permission-provider'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
|
||||
type MarketplaceInstallPermissionProviderProps = {
|
||||
@ -12,7 +13,7 @@ type MarketplaceInstallPermissionProviderProps = {
|
||||
const MarketplaceInstallPermissionProvider = ({
|
||||
children,
|
||||
}: MarketplaceInstallPermissionProviderProps) => {
|
||||
const { workspacePermissionKeys } = useAppContext()
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const canInstallPlugin = hasPermission(workspacePermissionKeys, 'plugin.install')
|
||||
|
||||
return (
|
||||
|
||||
@ -50,6 +50,20 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
userProfile: mockUserProfile,
|
||||
isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager(),
|
||||
workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'],
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/hooks/use-oauth', () => ({
|
||||
openOAuthPopup: vi.fn(),
|
||||
}))
|
||||
|
||||
@ -30,6 +30,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockAppContext.workspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/context/modal-context', () => ({
|
||||
useModalContext: () => ({
|
||||
setShowAccountSettingModal: mockSetShowAccountSettingModal,
|
||||
|
||||
@ -71,6 +71,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockAppContext.workspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
// Mock service/use-triggers - API service
|
||||
vi.mock('@/service/use-triggers', () => ({
|
||||
useTriggerPluginDynamicOptions: () => ({
|
||||
|
||||
@ -86,6 +86,19 @@ vi.mock('@/context/app-context', () => ({
|
||||
}) => unknown) => selector(mockAppContext),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
userProfile: mockAppContext.userProfile,
|
||||
workspacePermissionKeys: mockAppContext.workspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
// Mock service/use-triggers
|
||||
vi.mock('@/service/use-triggers', () => ({
|
||||
useTriggerPluginDynamicOptions: () => ({
|
||||
|
||||
@ -5,16 +5,18 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { CredentialTypeEnum } from '../../types'
|
||||
import Item from '../item'
|
||||
|
||||
// Item uses useAppContextWithSelector(state => state.userProfile) for the
|
||||
// borrowed-row heuristic; provide a minimal mock so the selector resolves.
|
||||
const mockUserProfile = { id: 'test-user', name: 'Test User', email: 'test@example.com', avatar_url: '' }
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useSelector: (selector: (state: { userProfile: typeof mockUserProfile, workspacePermissionKeys: string[] }) => unknown) =>
|
||||
selector({
|
||||
userProfile: mockUserProfile,
|
||||
workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'],
|
||||
}),
|
||||
}))
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
userProfile: { id: 'test-user' },
|
||||
workspacePermissionKeys: ['credential.use', 'credential.create', 'credential.manage'],
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
// ==================== Test Utilities ====================
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/too
|
||||
import {
|
||||
RiInformationLine,
|
||||
} from '@remixicon/react'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
@ -15,7 +16,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { userProfileIdAtom } from '@/context/app-context-state'
|
||||
import { useCredentialPermissions } from '@/hooks/use-credential-permissions'
|
||||
import { CredentialTypeEnum } from '../types'
|
||||
|
||||
@ -58,14 +59,14 @@ const Item = ({
|
||||
const { canUseCredential, canManageCredential } = useCredentialPermissions()
|
||||
const isOAuth = credential.credential_type === CredentialTypeEnum.OAUTH2
|
||||
const isPersonal = credential.visibility === 'only_me'
|
||||
const userProfile = useAppContextWithSelector(state => state.userProfile)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
// Borrowed-from-teammate: the backend explicitly flagged this row as another member's
|
||||
// only_me credential, returned only because the current node still references it.
|
||||
// Fallback heuristic (created_by mismatch on a selected row) is kept for backends
|
||||
// that don't yet emit the flag.
|
||||
const isSelected = showSelectedIcon && selectedCredentialId === credential.id
|
||||
const isConfiguredByOther
|
||||
= !!credential.created_by && !!userProfile?.id && credential.created_by !== userProfile.id
|
||||
= !!credential.created_by && !!currentUserId && credential.created_by !== currentUserId
|
||||
const isBorrowed
|
||||
= !!credential.from_other_member || (isSelected && isConfiguredByOther && isPersonal)
|
||||
const showSwitchAwayHint = isBorrowed
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
// import { useAppContext } from '@/context/app-context'
|
||||
// import { Button } from '@langgenius/dify-ui/button'
|
||||
// import { StatusDot } from '@langgenius/dify-ui/status-dot'
|
||||
// import ToolItem from '@/app/components/tools/provider/tool-item'
|
||||
@ -18,7 +17,6 @@ const ActionList = ({
|
||||
detail,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
// const { isCurrentWorkspaceManager } = useAppContext()
|
||||
// const providerBriefInfo = detail.declaration.datasource?.identity
|
||||
// const providerKey = `${detail.plugin_id}/${providerBriefInfo?.name}`
|
||||
const { data: dataSourceList } = useDataSourceList(true)
|
||||
|
||||
@ -55,13 +55,36 @@ vi.mock('@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list',
|
||||
}))
|
||||
|
||||
const mockLangGeniusVersionInfo = vi.fn(() => ({
|
||||
current_env: '',
|
||||
current_version: '1.0.0',
|
||||
latest_version: '',
|
||||
release_date: '',
|
||||
release_notes: '',
|
||||
version: '',
|
||||
can_auto_update: false,
|
||||
}))
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({
|
||||
|
||||
const createLangGeniusVersionInfo = (currentVersion: string) => ({
|
||||
current_env: '',
|
||||
current_version: currentVersion,
|
||||
latest_version: '',
|
||||
release_date: '',
|
||||
release_notes: '',
|
||||
version: '',
|
||||
can_auto_update: false,
|
||||
})
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
langGeniusVersionInfo: mockLangGeniusVersionInfo(),
|
||||
}),
|
||||
}))
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('../action', () => ({
|
||||
default: ({ onDelete, pluginName }: { onDelete: () => void, pluginName: string }) => (
|
||||
@ -162,7 +185,7 @@ describe('PluginItem', () => {
|
||||
mockTheme.mockReturnValue('light')
|
||||
mockCurrentPluginID.mockReturnValue(undefined)
|
||||
mockEnableMarketplace.mockReturnValue(true)
|
||||
mockLangGeniusVersionInfo.mockReturnValue({ current_version: '1.0.0' })
|
||||
mockLangGeniusVersionInfo.mockReturnValue(createLangGeniusVersionInfo('1.0.0'))
|
||||
mockGetValueFromI18nObject.mockImplementation((obj: Record<string, string>) => obj?.en_US || '')
|
||||
})
|
||||
|
||||
@ -359,7 +382,7 @@ describe('PluginItem', () => {
|
||||
describe('Version Compatibility', () => {
|
||||
it('should show warning icon when Dify version is not compatible', () => {
|
||||
// Arrange
|
||||
mockLangGeniusVersionInfo.mockReturnValue({ current_version: '0.3.0' })
|
||||
mockLangGeniusVersionInfo.mockReturnValue(createLangGeniusVersionInfo('0.3.0'))
|
||||
const plugin = createPluginDetail({
|
||||
declaration: createPluginDeclaration({
|
||||
meta: { version: '1.0.0', minimum_dify_version: '0.5.0' },
|
||||
@ -376,7 +399,7 @@ describe('PluginItem', () => {
|
||||
|
||||
it('should not show warning when Dify version is compatible', () => {
|
||||
// Arrange
|
||||
mockLangGeniusVersionInfo.mockReturnValue({ current_version: '1.0.0' })
|
||||
mockLangGeniusVersionInfo.mockReturnValue(createLangGeniusVersionInfo('1.0.0'))
|
||||
const plugin = createPluginDetail({
|
||||
declaration: createPluginDeclaration({
|
||||
meta: { version: '1.0.0', minimum_dify_version: '0.5.0' },
|
||||
@ -393,7 +416,7 @@ describe('PluginItem', () => {
|
||||
|
||||
it('should handle missing current_version gracefully', () => {
|
||||
// Arrange
|
||||
mockLangGeniusVersionInfo.mockReturnValue({ current_version: '' })
|
||||
mockLangGeniusVersionInfo.mockReturnValue(createLangGeniusVersionInfo(''))
|
||||
const plugin = createPluginDetail()
|
||||
|
||||
// Act
|
||||
|
||||
@ -11,12 +11,13 @@ import {
|
||||
RiLoginCircleLine,
|
||||
} from '@remixicon/react'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
|
||||
import { API_PREFIX } from '@/config'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { langGeniusVersionInfoAtom } from '@/context/app-context-state'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { useRenderI18nObject } from '@/hooks/use-i18n'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
@ -69,7 +70,7 @@ const PluginItem: FC<Props> = ({
|
||||
return [PluginSource.github, PluginSource.marketplace].includes(source) ? author : ''
|
||||
}, [source, author])
|
||||
|
||||
const { langGeniusVersionInfo } = useAppContext()
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
|
||||
const isDifyVersionCompatible = useMemo(() => {
|
||||
if (!langGeniusVersionInfo.current_version)
|
||||
|
||||
@ -51,11 +51,42 @@ vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0' },
|
||||
langGeniusVersionInfo: {
|
||||
current_env: 'CLOUD',
|
||||
current_version: '1.0.0',
|
||||
latest_version: '1.0.0',
|
||||
version: '1.0.0',
|
||||
release_date: '',
|
||||
release_notes: '',
|
||||
can_auto_update: false,
|
||||
},
|
||||
workspacePermissionKeys: ['plugin.install', 'plugin.delete', 'plugin.debug', 'plugin.plugin_preferences'],
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: {
|
||||
current_env: 'CLOUD',
|
||||
current_version: '1.0.0',
|
||||
latest_version: '1.0.0',
|
||||
version: '1.0.0',
|
||||
release_date: '',
|
||||
release_notes: '',
|
||||
can_auto_update: false,
|
||||
},
|
||||
workspacePermissionKeys: ['plugin.install', 'plugin.delete', 'plugin.debug', 'plugin.plugin_preferences'],
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/service/use-plugins', () => ({
|
||||
hasPluginPermission: (permission: string | undefined, isAdmin: boolean) => {
|
||||
if (!permission)
|
||||
|
||||
@ -1,20 +1,49 @@
|
||||
// Import mocks for assertions
|
||||
import type { AppContextStateMockState } from '@/__tests__/utils/mock-app-context-state'
|
||||
import type { LangGeniusVersionResponse } from '@/models/common'
|
||||
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 { useAppContext } from '@/context/app-context'
|
||||
|
||||
import { useInvalidateReferenceSettings, useMutationPluginPermissionSettings, useMutationReferenceSettings, usePluginAutoUpgradeSettings, usePluginPermissionSettings } from '@/service/use-plugins'
|
||||
import { PermissionType, PluginCategoryEnum } from '../../types'
|
||||
import useReferenceSetting, { useCanInstallPluginFromMarketplace } from '../use-reference-setting'
|
||||
|
||||
vi.mock('@/context/app-context', async () => {
|
||||
const actual = await vi.importActual('@/context/app-context')
|
||||
return {
|
||||
...actual,
|
||||
useAppContext: vi.fn(),
|
||||
const defaultLangGeniusVersionInfo: LangGeniusVersionResponse = {
|
||||
current_env: '',
|
||||
current_version: '1.0.0',
|
||||
latest_version: '',
|
||||
release_date: '',
|
||||
release_notes: '',
|
||||
version: '',
|
||||
can_auto_update: false,
|
||||
}
|
||||
|
||||
type MockAppContextState = Omit<AppContextStateMockState, 'langGeniusVersionInfo'> & {
|
||||
langGeniusVersionInfo?: Partial<LangGeniusVersionResponse>
|
||||
}
|
||||
|
||||
let mockAppContextState: AppContextStateMockState = {}
|
||||
|
||||
const setAppContextState = (state: MockAppContextState) => {
|
||||
mockAppContextState = {
|
||||
...state,
|
||||
langGeniusVersionInfo: {
|
||||
...defaultLangGeniusVersionInfo,
|
||||
...state.langGeniusVersionInfo,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
vi.mock('@/context/app-context-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', () => ({
|
||||
@ -33,12 +62,12 @@ describe('useReferenceSetting Hook', () => {
|
||||
toastSuccessSpy.mockClear()
|
||||
|
||||
// Default mocks
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextState({
|
||||
isCurrentWorkspaceManager: false,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
|
||||
workspacePermissionKeys: [] as string[],
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
|
||||
vi.mocked(usePluginAutoUpgradeSettings).mockReturnValue({
|
||||
data: {
|
||||
@ -109,12 +138,12 @@ describe('useReferenceSetting Hook', () => {
|
||||
})
|
||||
|
||||
it('should allow install and debug when plugin permission keys are present', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextState({
|
||||
isCurrentWorkspaceManager: false,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
|
||||
workspacePermissionKeys: ['plugin.install', 'plugin.debug'],
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
vi.mocked(usePluginPermissionSettings).mockReturnValue({
|
||||
data: {
|
||||
install_permission: PermissionType.everyone,
|
||||
@ -129,12 +158,12 @@ describe('useReferenceSetting Hook', () => {
|
||||
})
|
||||
|
||||
it('should allow debug for managers with legacy admin permission when RBAC is disabled', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextState({
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
|
||||
workspacePermissionKeys: [] as string[],
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
|
||||
vi.mocked(usePluginPermissionSettings).mockReturnValue({
|
||||
data: {
|
||||
@ -150,12 +179,12 @@ describe('useReferenceSetting Hook', () => {
|
||||
})
|
||||
|
||||
it('should allow debug for owners with legacy admin permission when RBAC is disabled', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextState({
|
||||
isCurrentWorkspaceManager: false,
|
||||
isCurrentWorkspaceOwner: true,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
|
||||
workspacePermissionKeys: [] as string[],
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
|
||||
vi.mocked(usePluginPermissionSettings).mockReturnValue({
|
||||
data: {
|
||||
@ -171,12 +200,12 @@ describe('useReferenceSetting Hook', () => {
|
||||
})
|
||||
|
||||
it('should allow debug for normal users when legacy debug permission is everyone and RBAC is disabled', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextState({
|
||||
isCurrentWorkspaceManager: false,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
|
||||
workspacePermissionKeys: ['plugin.install'],
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
|
||||
vi.mocked(usePluginPermissionSettings).mockReturnValue({
|
||||
data: {
|
||||
@ -194,12 +223,12 @@ describe('useReferenceSetting Hook', () => {
|
||||
})
|
||||
|
||||
it('should use plugin keys even when legacy admin permission is configured and RBAC is enabled', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextState({
|
||||
isCurrentWorkspaceManager: false,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
|
||||
workspacePermissionKeys: ['plugin.install', 'plugin.debug'],
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
|
||||
vi.mocked(usePluginPermissionSettings).mockReturnValue({
|
||||
data: {
|
||||
@ -217,12 +246,12 @@ describe('useReferenceSetting Hook', () => {
|
||||
})
|
||||
|
||||
it('should apply legacy noOne plugin permissions when RBAC is disabled', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextState({
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
|
||||
workspacePermissionKeys: ['plugin.install', 'plugin.delete', 'plugin.debug'],
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
vi.mocked(usePluginPermissionSettings).mockReturnValue({
|
||||
data: {
|
||||
install_permission: PermissionType.noOne,
|
||||
@ -245,12 +274,12 @@ describe('useReferenceSetting Hook', () => {
|
||||
|
||||
describe('canSetPermissions', () => {
|
||||
it('should be true with plugin preferences permission when RBAC is disabled', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextState({
|
||||
isCurrentWorkspaceManager: false,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
|
||||
workspacePermissionKeys: ['plugin.plugin_preferences'],
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool))
|
||||
|
||||
@ -258,12 +287,12 @@ describe('useReferenceSetting Hook', () => {
|
||||
})
|
||||
|
||||
it('should be false when RBAC is enabled even with plugin preferences permission', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextState({
|
||||
isCurrentWorkspaceManager: false,
|
||||
isCurrentWorkspaceOwner: true,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
|
||||
workspacePermissionKeys: ['plugin.plugin_preferences'],
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool), {
|
||||
systemFeatures: { rbac_enabled: true },
|
||||
@ -274,12 +303,12 @@ describe('useReferenceSetting Hook', () => {
|
||||
})
|
||||
|
||||
it('should be false without plugin preferences permission', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextState({
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
|
||||
workspacePermissionKeys: [] as string[],
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool))
|
||||
|
||||
@ -353,12 +382,12 @@ describe('useReferenceSetting Hook', () => {
|
||||
})
|
||||
|
||||
it('should keep permission key access available when reference setting data is still loading', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextState({
|
||||
isCurrentWorkspaceManager: false,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
|
||||
workspacePermissionKeys: ['plugin.install', 'plugin.debug'],
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
vi.mocked(usePluginAutoUpgradeSettings).mockReturnValue({
|
||||
data: undefined,
|
||||
} as unknown as ReturnType<typeof usePluginAutoUpgradeSettings>)
|
||||
@ -371,13 +400,13 @@ describe('useReferenceSetting Hook', () => {
|
||||
})
|
||||
|
||||
it('should keep permission state loading while workspace permission keys are loading', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextState({
|
||||
isCurrentWorkspaceManager: false,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
isLoadingWorkspacePermissionKeys: true,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
|
||||
workspacePermissionKeys: [] as string[],
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool))
|
||||
|
||||
@ -386,13 +415,13 @@ describe('useReferenceSetting Hook', () => {
|
||||
})
|
||||
|
||||
it('should keep permission state loading while current workspace is loading', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextState({
|
||||
isCurrentWorkspaceManager: false,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
isLoadingCurrentWorkspace: true,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
|
||||
workspacePermissionKeys: ['plugin.install'],
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool))
|
||||
|
||||
@ -402,7 +431,7 @@ describe('useReferenceSetting Hook', () => {
|
||||
|
||||
describe('RBAC permissions', () => {
|
||||
it('should use workspace permission keys when RBAC is enabled', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextState({
|
||||
isCurrentWorkspaceManager: false,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
|
||||
@ -412,7 +441,7 @@ describe('useReferenceSetting Hook', () => {
|
||||
'plugin.debug',
|
||||
'plugin.plugin_preferences',
|
||||
],
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
vi.mocked(usePluginPermissionSettings).mockReturnValue({
|
||||
data: {
|
||||
install_permission: PermissionType.noOne,
|
||||
@ -435,12 +464,12 @@ describe('useReferenceSetting Hook', () => {
|
||||
})
|
||||
|
||||
it('should ignore legacy plugin permission settings when RBAC is enabled', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextState({
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
|
||||
workspacePermissionKeys: [] as string[],
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool), {
|
||||
systemFeatures: { rbac_enabled: true },
|
||||
@ -462,12 +491,12 @@ describe('useCanInstallPluginFromMarketplace Hook', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextState({
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
|
||||
workspacePermissionKeys: ['plugin.install'],
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
|
||||
vi.mocked(usePluginPermissionSettings).mockReturnValue({
|
||||
data: {
|
||||
@ -501,12 +530,12 @@ describe('useCanInstallPluginFromMarketplace Hook', () => {
|
||||
})
|
||||
|
||||
it('should return false without plugin.install', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextState({
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
|
||||
workspacePermissionKeys: [] as string[],
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useCanInstallPluginFromMarketplace(), {
|
||||
systemFeatures: { enable_marketplace: true },
|
||||
@ -516,12 +545,12 @@ describe('useCanInstallPluginFromMarketplace Hook', () => {
|
||||
})
|
||||
|
||||
it('should return false when both marketplace is disabled and plugin.install is missing', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextState({
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
|
||||
workspacePermissionKeys: [] as string[],
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useCanInstallPluginFromMarketplace(), {
|
||||
systemFeatures: { enable_marketplace: false },
|
||||
@ -558,12 +587,12 @@ describe('useCanInstallPluginFromMarketplace Hook', () => {
|
||||
})
|
||||
|
||||
it('should use plugin.install when marketplace and RBAC are enabled', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextState({
|
||||
isCurrentWorkspaceManager: false,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
|
||||
workspacePermissionKeys: ['plugin.install'],
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
vi.mocked(usePluginPermissionSettings).mockReturnValue({
|
||||
data: {
|
||||
install_permission: PermissionType.noOne,
|
||||
|
||||
@ -1,16 +1,23 @@
|
||||
import type { PluginCategoryEnum } from '../types'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import {
|
||||
currentWorkspaceLoadingAtom,
|
||||
langGeniusVersionInfoAtom,
|
||||
workspacePermissionKeysAtom,
|
||||
workspacePermissionKeysLoadingAtom,
|
||||
workspaceRoleFlagsAtom,
|
||||
} from '@/context/app-context-state'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { useInvalidateReferenceSettings, useMutationPluginPermissionSettings, useMutationReferenceSettings, usePluginAutoUpgradeSettings, usePluginPermissionSettings } from '@/service/use-plugins'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
import { hasLegacyPluginPermissionAccess } from '../plugin-permissions'
|
||||
|
||||
const useCanSetPluginSettings = () => {
|
||||
const { workspacePermissionKeys } = useAppContext()
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const { data: rbacEnabled } = useSuspenseQuery({
|
||||
...systemFeaturesQueryOptions(),
|
||||
select: s => s.rbac_enabled,
|
||||
@ -28,11 +35,11 @@ export const usePluginSettingsAccess = () => {
|
||||
const {
|
||||
isCurrentWorkspaceManager,
|
||||
isCurrentWorkspaceOwner,
|
||||
isLoadingCurrentWorkspace,
|
||||
isLoadingWorkspacePermissionKeys,
|
||||
workspacePermissionKeys,
|
||||
langGeniusVersionInfo,
|
||||
} = useAppContext()
|
||||
} = useAtomValue(workspaceRoleFlagsAtom)
|
||||
const isLoadingCurrentWorkspace = useAtomValue(currentWorkspaceLoadingAtom)
|
||||
const isLoadingWorkspacePermissionKeys = useAtomValue(workspacePermissionKeysLoadingAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const { data: rbacEnabled } = useSuspenseQuery({
|
||||
...systemFeaturesQueryOptions(),
|
||||
select: s => s.rbac_enabled,
|
||||
@ -125,7 +132,11 @@ export const useCanInstallPluginFromMarketplace = () => {
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const marketplaceAccess = systemFeatures.enable_marketplace
|
||||
const rbacEnabled = systemFeatures.rbac_enabled
|
||||
const { isCurrentWorkspaceManager, isCurrentWorkspaceOwner, workspacePermissionKeys } = useAppContext()
|
||||
const {
|
||||
isCurrentWorkspaceManager,
|
||||
isCurrentWorkspaceOwner,
|
||||
} = useAtomValue(workspaceRoleFlagsAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const permissionQuery = usePluginPermissionSettings()
|
||||
const { data: permissions } = permissionQuery
|
||||
const legacyCanInstallPlugin = hasLegacyPluginPermissionAccess({
|
||||
|
||||
@ -1,16 +1,17 @@
|
||||
'use client'
|
||||
|
||||
import { useSelector as useAppContextSelector } from '@/context/app-context'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
|
||||
export const useCanManageTools = () => {
|
||||
const workspacePermissionKeys = useAppContextSelector(state => state.workspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
|
||||
return hasPermission(workspacePermissionKeys, 'tool.manage')
|
||||
}
|
||||
|
||||
export const useCanManageMCP = () => {
|
||||
const workspacePermissionKeys = useAppContextSelector(state => state.workspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
|
||||
return hasPermission(workspacePermissionKeys, 'mcp.manage')
|
||||
}
|
||||
|
||||
@ -40,14 +40,30 @@ vi.mock('../modal', () => ({
|
||||
},
|
||||
}))
|
||||
|
||||
let mockWorkspacePermissionKeys: string[] = ['mcp.manage']
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
workspacePermissionKeys: ['mcp.manage'] as string[],
|
||||
workspacePermissionKeysAtom: Symbol('workspacePermissionKeysAtom'),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useSelector: (selector: (state: { workspacePermissionKeys: string[] }) => unknown) => selector({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
workspacePermissionKeys: mockAppContextState.workspacePermissionKeys,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', () => ({
|
||||
workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom,
|
||||
}))
|
||||
|
||||
vi.mock('jotai', () => ({
|
||||
useAtomValue: (atom: unknown) => {
|
||||
if (atom === mockAppContextState.workspacePermissionKeysAtom)
|
||||
return mockAppContextState.workspacePermissionKeys
|
||||
|
||||
throw new Error('Unexpected atom')
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock the plugins service
|
||||
vi.mock('@/service/use-plugins', () => ({
|
||||
useInstalledPluginList: () => ({
|
||||
@ -84,7 +100,7 @@ describe('NewMCPCard', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
mockCreateMCP.mockClear()
|
||||
mockWorkspacePermissionKeys = ['mcp.manage']
|
||||
mockAppContextState.workspacePermissionKeys = ['mcp.manage']
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
@ -152,7 +168,7 @@ describe('NewMCPCard', () => {
|
||||
|
||||
describe('mcp.manage Permission', () => {
|
||||
it('should not render card when user lacks mcp.manage', () => {
|
||||
mockWorkspacePermissionKeys = []
|
||||
mockAppContextState.workspacePermissionKeys = []
|
||||
|
||||
render(<NewMCPCard {...defaultProps} />, { wrapper: createWrapper() })
|
||||
|
||||
@ -160,7 +176,7 @@ describe('NewMCPCard', () => {
|
||||
})
|
||||
|
||||
it('should not render toolbar button when user lacks mcp.manage', () => {
|
||||
mockWorkspacePermissionKeys = []
|
||||
mockAppContextState.workspacePermissionKeys = []
|
||||
|
||||
render(<NewMCPButton {...defaultProps} />, { wrapper: createWrapper() })
|
||||
|
||||
|
||||
@ -15,7 +15,10 @@ const mockRefetch = vi.fn()
|
||||
const mockUseAllToolProviders = vi.fn()
|
||||
let mockProviders: MockProvider[] = []
|
||||
let mockIsLoadingToolProviders = false
|
||||
let mockWorkspacePermissionKeys = ['mcp.manage']
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
workspacePermissionKeys: ['mcp.manage'] as string[],
|
||||
workspacePermissionKeysAtom: Symbol('workspacePermissionKeysAtom'),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-tools', () => ({
|
||||
useAllToolProviders: (enabled?: boolean) => {
|
||||
@ -30,10 +33,23 @@ vi.mock('@/service/use-tools', () => ({
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useSelector: (selector: (state: { workspacePermissionKeys: string[] }) => unknown) => selector({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
workspacePermissionKeys: mockAppContextState.workspacePermissionKeys,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', () => ({
|
||||
workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom,
|
||||
}))
|
||||
|
||||
vi.mock('jotai', () => ({
|
||||
useAtomValue: (atom: unknown) => {
|
||||
if (atom === mockAppContextState.workspacePermissionKeysAtom)
|
||||
return mockAppContextState.workspacePermissionKeys
|
||||
|
||||
throw new Error('Unexpected atom')
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/tools/provider/tool-card-skeleton', () => ({
|
||||
default: ({ variant }: { variant?: string }) => (
|
||||
<>
|
||||
@ -89,7 +105,7 @@ describe('MCPList', () => {
|
||||
vi.useFakeTimers()
|
||||
mockProviders = []
|
||||
mockIsLoadingToolProviders = false
|
||||
mockWorkspacePermissionKeys = ['mcp.manage']
|
||||
mockAppContextState.workspacePermissionKeys = ['mcp.manage']
|
||||
mockRefetch.mockResolvedValue(undefined)
|
||||
})
|
||||
|
||||
@ -111,7 +127,7 @@ describe('MCPList', () => {
|
||||
})
|
||||
|
||||
it('should render providers read-only when user lacks mcp.manage', () => {
|
||||
mockWorkspacePermissionKeys = []
|
||||
mockAppContextState.workspacePermissionKeys = []
|
||||
mockProviders = [
|
||||
{ id: '1', name: 'Provider 1', type: 'mcp' },
|
||||
]
|
||||
|
||||
@ -81,14 +81,30 @@ vi.mock('../detail/operation-dropdown', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
let mockWorkspacePermissionKeys: string[] = ['mcp.manage']
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
workspacePermissionKeys: ['mcp.manage'] as string[],
|
||||
workspacePermissionKeysAtom: Symbol('workspacePermissionKeysAtom'),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useSelector: (selector: (state: { workspacePermissionKeys: string[] }) => unknown) => selector({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
workspacePermissionKeys: mockAppContextState.workspacePermissionKeys,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', () => ({
|
||||
workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom,
|
||||
}))
|
||||
|
||||
vi.mock('jotai', () => ({
|
||||
useAtomValue: (atom: unknown) => {
|
||||
if (atom === mockAppContextState.workspacePermissionKeysAtom)
|
||||
return mockAppContextState.workspacePermissionKeys
|
||||
|
||||
throw new Error('Unexpected atom')
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock the format time hook
|
||||
vi.mock('@/hooks/use-format-time-from-now', () => ({
|
||||
useFormatTimeFromNow: () => ({
|
||||
@ -155,7 +171,7 @@ describe('MCPCard', () => {
|
||||
mockDeleteMCP.mockClear()
|
||||
mockUpdateMCP.mockResolvedValue({ result: 'success' })
|
||||
mockDeleteMCP.mockResolvedValue({ result: 'success' })
|
||||
mockWorkspacePermissionKeys = ['mcp.manage']
|
||||
mockAppContextState.workspacePermissionKeys = ['mcp.manage']
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
@ -343,7 +359,7 @@ describe('MCPCard', () => {
|
||||
})
|
||||
|
||||
it('should not render operation dropdown when user lacks mcp.manage', () => {
|
||||
mockWorkspacePermissionKeys = []
|
||||
mockAppContextState.workspacePermissionKeys = []
|
||||
|
||||
render(<MCPCard {...defaultProps} />, { wrapper: createWrapper() })
|
||||
|
||||
|
||||
@ -107,16 +107,31 @@ vi.mock('../tool-item', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
// Mutable workspace permission state
|
||||
let mockWorkspacePermissionKeys: string[] = ['mcp.manage']
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
workspacePermissionKeys: ['mcp.manage'] as string[],
|
||||
workspacePermissionKeysAtom: Symbol('workspacePermissionKeysAtom'),
|
||||
}))
|
||||
|
||||
// Mock the app context
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useSelector: (selector: (state: { workspacePermissionKeys: string[] }) => unknown) => selector({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
workspacePermissionKeys: mockAppContextState.workspacePermissionKeys,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', () => ({
|
||||
workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom,
|
||||
}))
|
||||
|
||||
vi.mock('jotai', () => ({
|
||||
useAtomValue: (atom: unknown) => {
|
||||
if (atom === mockAppContextState.workspacePermissionKeysAtom)
|
||||
return mockAppContextState.workspacePermissionKeys
|
||||
|
||||
throw new Error('Unexpected atom')
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock the plugins service
|
||||
vi.mock('@/service/use-plugins', () => ({
|
||||
useInstalledPluginList: () => ({
|
||||
@ -195,7 +210,7 @@ describe('MCPDetailContent', () => {
|
||||
mockIsFetching = false
|
||||
mockIsUpdating = false
|
||||
mockIsAuthorizing = false
|
||||
mockWorkspacePermissionKeys = ['mcp.manage']
|
||||
mockAppContextState.workspacePermissionKeys = ['mcp.manage']
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
@ -232,7 +247,7 @@ describe('MCPDetailContent', () => {
|
||||
})
|
||||
|
||||
it('should render read-only detail when user lacks mcp.manage', () => {
|
||||
mockWorkspacePermissionKeys = []
|
||||
mockAppContextState.workspacePermissionKeys = []
|
||||
|
||||
render(<MCPDetailContent {...defaultProps} />, { wrapper: createWrapper() })
|
||||
|
||||
@ -466,7 +481,7 @@ describe('MCPDetailContent', () => {
|
||||
})
|
||||
|
||||
it('should disable authorize action when user lacks mcp.manage', () => {
|
||||
mockWorkspacePermissionKeys = []
|
||||
mockAppContextState.workspacePermissionKeys = []
|
||||
const detail = createMockDetail({ is_team_authorization: false })
|
||||
render(
|
||||
<MCPDetailContent {...defaultProps} detail={detail} />,
|
||||
@ -756,7 +771,7 @@ describe('MCPDetailContent', () => {
|
||||
})
|
||||
|
||||
it('should not run OAuth authorization when user lacks mcp.manage', async () => {
|
||||
mockWorkspacePermissionKeys = []
|
||||
mockAppContextState.workspacePermissionKeys = []
|
||||
mockAuthorizeMcp.mockResolvedValue({ authorization_url: 'https://oauth.example.com' })
|
||||
const detail = createMockDetail({ is_team_authorization: false })
|
||||
|
||||
@ -800,7 +815,7 @@ describe('MCPDetailContent', () => {
|
||||
})
|
||||
|
||||
it('should disable authorized button when user lacks mcp.manage', () => {
|
||||
mockWorkspacePermissionKeys = []
|
||||
mockAppContextState.workspacePermissionKeys = []
|
||||
const detail = createMockDetail({ is_team_authorization: true })
|
||||
render(
|
||||
<MCPDetailContent {...defaultProps} detail={detail} />,
|
||||
|
||||
@ -2,9 +2,10 @@
|
||||
import type { AppDetailResponse } from '@/models/app'
|
||||
import type { AppSSO } from '@/types/app'
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { userProfileIdAtom, workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { fetchAppDetail } from '@/service/apps'
|
||||
import {
|
||||
useInvalidateMCPServerDetail,
|
||||
@ -36,8 +37,8 @@ export const useMCPServiceCardState = (
|
||||
const { mutateAsync: updateMCPServer } = useUpdateMCPServer()
|
||||
const { mutateAsync: refreshMCPServerCode, isPending: genLoading } = useRefreshMCPServerCode()
|
||||
const invalidateMCPServerDetail = useInvalidateMCPServerDetail()
|
||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
|
||||
const canManageMCP = useMemo(
|
||||
() => getAppACLCapabilities(appInfo.permission_keys, {
|
||||
|
||||
@ -4,14 +4,30 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { AuthType } from '../../types'
|
||||
import CustomCreateCard, { NewCustomToolButton } from '../custom-create-card'
|
||||
|
||||
let mockWorkspacePermissionKeys: string[] = ['tool.manage']
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
workspacePermissionKeys: ['tool.manage'] as string[],
|
||||
workspacePermissionKeysAtom: Symbol('workspacePermissionKeysAtom'),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useSelector: <T,>(selector: (state: { workspacePermissionKeys: string[] }) => T): T => selector({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
workspacePermissionKeys: mockAppContextState.workspacePermissionKeys,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', () => ({
|
||||
workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom,
|
||||
}))
|
||||
|
||||
vi.mock('jotai', () => ({
|
||||
useAtomValue: (atom: unknown) => {
|
||||
if (atom === mockAppContextState.workspacePermissionKeysAtom)
|
||||
return mockAppContextState.workspacePermissionKeys
|
||||
|
||||
throw new Error('Unexpected atom')
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock useLocale and useDocLink
|
||||
vi.mock('@/context/i18n', () => ({
|
||||
useLocale: () => 'en-US',
|
||||
@ -81,7 +97,7 @@ describe('CustomCreateCard', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockWorkspacePermissionKeys = ['tool.manage']
|
||||
mockAppContextState.workspacePermissionKeys = ['tool.manage']
|
||||
mockModalVisible = false
|
||||
mockCreateCustomCollection.mockResolvedValue({})
|
||||
})
|
||||
@ -94,7 +110,7 @@ describe('CustomCreateCard', () => {
|
||||
})
|
||||
|
||||
it('should not render anything when user does not have tool.manage', () => {
|
||||
mockWorkspacePermissionKeys = []
|
||||
mockAppContextState.workspacePermissionKeys = []
|
||||
|
||||
const { container } = render(<CustomCreateCard onRefreshData={mockOnRefreshData} />)
|
||||
|
||||
@ -146,7 +162,7 @@ describe('CustomCreateCard', () => {
|
||||
})
|
||||
|
||||
it('should not render toolbar add button when user does not have tool.manage', () => {
|
||||
mockWorkspacePermissionKeys = []
|
||||
mockAppContextState.workspacePermissionKeys = []
|
||||
|
||||
const { container } = render(<NewCustomToolButton onRefreshData={mockOnRefreshData} />)
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ vi.mock('@/i18n-config/language', () => ({
|
||||
const mockIsCurrentWorkspaceManager = vi.fn(() => true)
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
workspacePermissionKeys: ['tool.manage', 'credential.use', 'credential.create', 'credential.manage'] as string[],
|
||||
workspacePermissionKeysAtom: Symbol('workspacePermissionKeysAtom'),
|
||||
}))
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({
|
||||
@ -25,6 +26,19 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', () => ({
|
||||
workspacePermissionKeysAtom: mockAppContextState.workspacePermissionKeysAtom,
|
||||
}))
|
||||
|
||||
vi.mock('jotai', () => ({
|
||||
useAtomValue: (atom: unknown) => {
|
||||
if (atom === mockAppContextState.workspacePermissionKeysAtom)
|
||||
return mockAppContextState.workspacePermissionKeys
|
||||
|
||||
throw new Error('Unexpected atom')
|
||||
},
|
||||
}))
|
||||
|
||||
const mockSetShowModelModal = vi.fn()
|
||||
vi.mock('@/context/modal-context', () => ({
|
||||
useModalContext: () => ({
|
||||
|
||||
@ -69,6 +69,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/hooks/use-theme', () => ({
|
||||
default: vi.fn(),
|
||||
}))
|
||||
|
||||
@ -21,6 +21,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/service/use-tools', () => ({
|
||||
useAllBuiltInTools: (enabled: boolean) => mockBuiltInTools(enabled),
|
||||
useAllCustomTools: (enabled: boolean) => mockCustomTools(enabled),
|
||||
|
||||
@ -1,13 +1,19 @@
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import { useCredentialPermissions } from './use-credential-permissions'
|
||||
|
||||
let mockWorkspacePermissionKeys: string[] | null = []
|
||||
let mockWorkspacePermissionKeys: string[] = []
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useSelector: (selector: (state: { workspacePermissionKeys: string[] | null }) => unknown) => selector({
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
}),
|
||||
}))
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
describe('useCredentialPermissions', () => {
|
||||
beforeEach(() => {
|
||||
@ -39,8 +45,8 @@ describe('useCredentialPermissions', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle missing workspace permissions as no credential capabilities', () => {
|
||||
mockWorkspacePermissionKeys = null
|
||||
it('should handle empty workspace permissions as no credential capabilities', () => {
|
||||
mockWorkspacePermissionKeys = []
|
||||
|
||||
const { result } = renderHook(() => useCredentialPermissions())
|
||||
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { useSelector as useAppContextSelector } from '@/context/app-context'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
|
||||
export const useCredentialPermissions = () => {
|
||||
const workspacePermissionKeys = useAppContextSelector(state => state.workspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
|
||||
return {
|
||||
canUseCredential: hasPermission(workspacePermissionKeys, 'credential.use'),
|
||||
|
||||
@ -19,9 +19,11 @@ import {
|
||||
const {
|
||||
mockGet,
|
||||
mockPost,
|
||||
mockWorkspacePermissionKeysAtom,
|
||||
} = vi.hoisted(() => ({
|
||||
mockGet: vi.fn(),
|
||||
mockPost: vi.fn(),
|
||||
mockWorkspacePermissionKeysAtom: Symbol('workspacePermissionKeysAtom'),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/base', () => ({
|
||||
@ -37,12 +39,17 @@ vi.mock('@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list',
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
workspacePermissionKeys: ['plugin.install'],
|
||||
}),
|
||||
vi.mock('@/context/app-context-state', () => ({
|
||||
workspacePermissionKeysAtom: mockWorkspacePermissionKeysAtom,
|
||||
}))
|
||||
|
||||
vi.mock('jotai', () => ({
|
||||
useAtomValue: (atom: unknown) => {
|
||||
if (atom === mockWorkspacePermissionKeysAtom)
|
||||
return ['plugin.install']
|
||||
|
||||
throw new Error('Unexpected atom')
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('../use-tools', () => ({
|
||||
|
||||
@ -40,12 +40,13 @@ import {
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
import { cloneDeep } from 'es-toolkit/object'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useEffect, useRef } from 'react'
|
||||
import { FormTypeEnum } from '@/app/components/base/form/types'
|
||||
import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
|
||||
import { getFormattedPlugin } from '@/app/components/plugins/marketplace/utils'
|
||||
import { PluginCategoryEnum, PluginSource, TaskStatus } from '@/app/components/plugins/types'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { fetchModelProviderModelList } from '@/service/common'
|
||||
import { fetchPluginInfoFromMarketPlace, uninstallPlugin } from '@/service/plugins'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
@ -1233,7 +1234,7 @@ export const useFetchPluginsInMarketPlaceByInfo = (infos: MarketplacePluginInfoR
|
||||
export const usePluginTaskList = (category?: PluginCategoryEnum | string) => {
|
||||
const initializedRef = useRef(false)
|
||||
const queryClient = useQueryClient()
|
||||
const { workspacePermissionKeys } = useAppContext()
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const canManagement = hasPermission(workspacePermissionKeys, 'plugin.install')
|
||||
const { refreshPluginList } = useRefreshPluginList()
|
||||
const query = useQuery<PluginTaskListResponse>({
|
||||
|
||||
Loading…
Reference in New Issue
Block a user