mirror of
https://github.com/langgenius/dify.git
synced 2026-11-10 21:35:45 +08:00
refactor(web): use generated oRPC query keys (#44415)
This commit is contained in:
1 parent
a30dd002ee
commit
33c881e893
32 files changed
+551
-527
No files matched your search
@@ -1692,11 +1692,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/snippet-list/__tests__/reflow.browser.spec.tsx": {
|
||||
"@tanstack/query/prefer-query-options": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/snippets/hooks/use-nodes-sync-draft.ts": {
|
||||
"dify/require-disable-directive-description": {
|
||||
"count": 1
|
||||
@@ -3035,11 +3030,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/features/agent-v2/agent-detail/configure/components/orchestrate/files/__tests__/index.spec.tsx": {
|
||||
"@tanstack/query/prefer-query-options": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/features/agent-v2/agent-detail/configure/components/preview/working-directory-panel.tsx": {
|
||||
"@tanstack/query/prefer-query-options": {
|
||||
"count": 2
|
||||
|
||||
@@ -4,6 +4,7 @@ import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-quer
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { httpErrorFromResponse } from '@/service/http-error'
|
||||
import { consoleQueryKey } from '@/test/console/operation-hooks'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
queryClient: undefined as QueryClient | undefined,
|
||||
@@ -22,7 +23,7 @@ vi.mock('@/next/headers', () => ({ headers: () => mocks.headers() }))
|
||||
vi.mock('@/next/navigation', () => ({ redirect: (url: string) => mocks.redirect(url) }))
|
||||
vi.mock('@/features/account-profile/server', () => ({
|
||||
serverUserProfileQueryOptions: () => ({
|
||||
queryKey: ['common', 'user-profile'],
|
||||
queryKey: consoleQueryKey('account.profile.get'),
|
||||
queryFn: mocks.profile,
|
||||
retry: false,
|
||||
}),
|
||||
@@ -100,7 +101,7 @@ it('hydrates the profile, workspace, permissions and feature data', async () =>
|
||||
render(<QueryClientProvider client={client}>{element}</QueryClientProvider>)
|
||||
|
||||
expect(screen.getByText('Common shell')).toBeInTheDocument()
|
||||
expect(client.getQueryData(['common', 'user-profile'])).toMatchObject({
|
||||
expect(client.getQueryData(consoleQueryKey('account.profile.get'))).toMatchObject({
|
||||
profile: { id: 'account-id' },
|
||||
})
|
||||
expect(client.getQueryData(keys.workspace)).toMatchObject({ id: 'workspace-id' })
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { ReactElement } from 'react'
|
||||
import { screen } from '@testing-library/react'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { createHttpError } from '@/service/http-error'
|
||||
import { consoleQueryKey } from '@/test/console/operation-hooks'
|
||||
import { renderWithConsoleQuery as renderWithConsole } from '@/test/console/query-data'
|
||||
import { createAppDetailFixture } from '@/test/fixtures/app'
|
||||
import { createTestQueryClient } from '@/test/query-client'
|
||||
@@ -29,12 +30,12 @@ const mocks = vi.hoisted(() => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/features/system-features/client', () => ({
|
||||
systemFeaturesQueryOptions: () => ({ queryKey: ['system-features'] }),
|
||||
systemFeaturesQueryOptions: () => ({ queryKey: consoleQueryKey('systemFeatures.get') }),
|
||||
}))
|
||||
|
||||
function render(ui: ReactElement) {
|
||||
const queryClient = createTestQueryClient()
|
||||
queryClient.setQueryData(['system-features'], {
|
||||
queryClient.setQueryData(consoleQueryKey('systemFeatures.get'), {
|
||||
webapp_auth: { enabled: true },
|
||||
rbac_enabled: true,
|
||||
})
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { QueryClient } from '@tanstack/react-query'
|
||||
import { render } from '@testing-library/react'
|
||||
import { REGISTRATION_SUCCESS_STORAGE_KEY } from '@/app/components/base/amplitude/registration-session-state'
|
||||
import { consoleQueryKey } from '@/test/console/operation-hooks'
|
||||
|
||||
let queryClient: QueryClient
|
||||
const systemFeaturesQueryKey = consoleQueryKey('systemFeatures.get')
|
||||
|
||||
type ConfigState = {
|
||||
cookieYesSiteKey: string
|
||||
@@ -10,7 +12,7 @@ type ConfigState = {
|
||||
webPrefix: string | undefined
|
||||
}
|
||||
|
||||
const { configState, mockHeadersGet, mockBoundary, systemFeaturesQueryKey } = vi.hoisted(() => ({
|
||||
const { configState, mockHeadersGet, mockBoundary } = vi.hoisted(() => ({
|
||||
configState: {
|
||||
cookieYesSiteKey: 'site-key',
|
||||
isProd: true,
|
||||
@@ -18,7 +20,6 @@ const { configState, mockHeadersGet, mockBoundary, systemFeaturesQueryKey } = vi
|
||||
} as ConfigState,
|
||||
mockHeadersGet: vi.fn(),
|
||||
mockBoundary: vi.fn(),
|
||||
systemFeaturesQueryKey: ['console', 'system-features'] as const,
|
||||
}))
|
||||
|
||||
vi.mock('@/features/system-features/server', () => ({
|
||||
|
||||
+2
-1
@@ -2,6 +2,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { useState } from 'react'
|
||||
import { userEvent } from 'vite-plus/test/browser'
|
||||
import { render } from 'vitest-browser-react'
|
||||
import { consoleQueryKey } from '@/test/console/operation-hooks'
|
||||
import ChatWithHistory from '../index'
|
||||
|
||||
vi.mock('@/hooks/use-breakpoints', () => ({
|
||||
@@ -17,7 +18,7 @@ vi.mock('@/next/navigation', () => ({
|
||||
vi.mock('@/context/web-app-context', () => ({ useWebAppStore: () => null }))
|
||||
vi.mock('@/features/system-features/client', () => ({
|
||||
systemFeaturesQueryOptions: () => ({
|
||||
queryKey: ['sidebar-focus-system-features'],
|
||||
queryKey: consoleQueryKey('systemFeatures.get'),
|
||||
initialData: { branding: { enabled: false } },
|
||||
queryFn: async () => ({ branding: { enabled: false } }),
|
||||
staleTime: Infinity,
|
||||
|
||||
+4
-1
@@ -6,6 +6,7 @@ import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
|
||||
import { ChunkingMode } from '@/models/datasets'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { mockEmojiData, renderWithEmoji as testingLibraryRender } from '@/test/emoji-picker'
|
||||
import EditPipelineInfo from '../edit-pipeline-info'
|
||||
|
||||
@@ -143,7 +144,9 @@ describe('EditPipelineInfo', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(QueryClient.prototype.invalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: expect.arrayContaining([['console', 'rag', 'pipeline', 'templates', 'get']]),
|
||||
queryKey: consoleQuery.rag.pipeline.templates.get.queryKey({
|
||||
input: { query: { type: 'customized' } },
|
||||
}),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+3
-2
@@ -3,6 +3,7 @@ import { userEvent } from 'vite-plus/test/browser'
|
||||
import { render } from 'vitest-browser-react'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { consoleQueryKey } from '@/test/console/operation-hooks'
|
||||
import { createSystemFeaturesFixture } from '@/test/console/system-features'
|
||||
import { createDatasetFixture } from '@/test/fixtures/dataset'
|
||||
import { DatasetACLPermission } from '@/utils/permission'
|
||||
@@ -21,14 +22,14 @@ vi.mock('@/context/permission-state', async () => {
|
||||
|
||||
vi.mock('@/features/account-profile/client', () => ({
|
||||
userProfileQueryOptions: () => ({
|
||||
queryKey: ['account-profile'],
|
||||
queryKey: consoleQueryKey('account.profile.get'),
|
||||
queryFn: async () => ({ profile: { id: 'user-1' } }),
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/features/system-features/client', () => ({
|
||||
systemFeaturesQueryOptions: () => ({
|
||||
queryKey: ['system-features'],
|
||||
queryKey: consoleQueryKey('systemFeatures.get'),
|
||||
queryFn: async () => ({ rbac_enabled: true }),
|
||||
}),
|
||||
}))
|
||||
|
||||
+11
-18
@@ -18,24 +18,17 @@ vi.mock('@/context/permission-state', async () => {
|
||||
const { atom } = await import('jotai')
|
||||
return { workspacePermissionKeysAtom: atom(['app_library.access']) }
|
||||
})
|
||||
vi.mock('@/service/console', () => ({
|
||||
consoleQuery: {
|
||||
installedApps: {
|
||||
get: {
|
||||
infiniteOptions: (options: {
|
||||
input: (cursor?: string) => { query: { name?: string } }
|
||||
}) => ({
|
||||
...options,
|
||||
queryKey: ['installed-apps', options.input().query.name ?? ''],
|
||||
queryFn: () => service.list(),
|
||||
}),
|
||||
},
|
||||
byInstalledAppId: {
|
||||
patch: { mutationOptions: () => ({ mutationFn: service.pin }) },
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
vi.mock('@/service/console', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/service/console')>()
|
||||
const { withConsoleResponses } = await import('@/test/console/service-responses')
|
||||
return {
|
||||
...actual,
|
||||
consoleQuery: withConsoleResponses(actual.consoleQuery, {
|
||||
'installedApps.get': () => service.list(),
|
||||
'installedApps.byInstalledAppId.patch': () => service.pin(),
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
function createApp(index: number): InstalledAppResponse {
|
||||
return {
|
||||
|
||||
+9
-5
@@ -43,15 +43,19 @@ vi.mock('@/service/marketplace', async (importOriginal) => {
|
||||
...original,
|
||||
marketplaceQuery: {
|
||||
searchAdvanced: {
|
||||
queryOptions: ({ input }: { input: unknown }) => ({
|
||||
queryKey: ['marketplace', 'plugins', input],
|
||||
queryOptions: (
|
||||
options: Parameters<typeof original.marketplaceQuery.searchAdvanced.queryOptions>[0],
|
||||
) => ({
|
||||
...original.marketplaceQuery.searchAdvanced.queryOptions(options),
|
||||
queryFn: () => ({ data: { plugins: [], total: 0 } }),
|
||||
}),
|
||||
},
|
||||
templateSearch: {
|
||||
queryOptions: ({ input }: { input: unknown }) => ({
|
||||
queryKey: ['marketplace', 'templates', input],
|
||||
queryFn: () => mockTemplateSearch(input),
|
||||
queryOptions: (
|
||||
options: Parameters<typeof original.marketplaceQuery.templateSearch.queryOptions>[0],
|
||||
) => ({
|
||||
...original.marketplaceQuery.templateSearch.queryOptions(options),
|
||||
queryFn: () => mockTemplateSearch(options.input),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { createStore, Provider } from 'jotai'
|
||||
import { page } from 'vite-plus/test/browser'
|
||||
import { render } from 'vitest-browser-react'
|
||||
import { consoleQueryKey } from '@/test/console/operation-hooks'
|
||||
import { seedRegisteredConsoleStateFixture } from '@/test/console/state-fixture'
|
||||
import SnippetList from '..'
|
||||
|
||||
@@ -46,7 +47,7 @@ vi.mock('@/context/permission-state', async () => {
|
||||
vi.mock('@/features/account-profile/client', () => ({
|
||||
userProfileQueryOptions: () => ({
|
||||
queryFn: async () => ({ profile: { id: 'creator-1' } }),
|
||||
queryKey: ['account-profile'],
|
||||
queryKey: consoleQueryKey('account.profile.get'),
|
||||
}),
|
||||
}))
|
||||
|
||||
@@ -106,7 +107,7 @@ const renderSnippetList = async () => {
|
||||
queries: { retry: false },
|
||||
},
|
||||
})
|
||||
queryClient.setQueryData(['account-profile'], { profile: { id: 'creator-1' } })
|
||||
queryClient.setQueryData(consoleQueryKey('account.profile.get'), { profile: { id: 'creator-1' } })
|
||||
|
||||
const store = createStore()
|
||||
seedRegisteredConsoleStateFixture(store)
|
||||
|
||||
@@ -4,13 +4,14 @@ import { Suspense, useState } from 'react'
|
||||
import ReactFlow, { ReactFlowProvider } from 'reactflow'
|
||||
import { page, userEvent } from 'vite-plus/test/browser'
|
||||
import { render } from 'vitest-browser-react'
|
||||
import { consoleQueryKey } from '@/test/console/operation-hooks'
|
||||
import { CommentIcon } from './comment-icon'
|
||||
import { CommentInput } from './comment-input'
|
||||
import 'reactflow/dist/style.css'
|
||||
|
||||
vi.mock('@/features/account-profile/client', () => ({
|
||||
userProfileQueryOptions: () => ({
|
||||
queryKey: ['profile'],
|
||||
queryKey: consoleQueryKey('account.profile.get'),
|
||||
queryFn: async () => ({ profile: { id: 'author', name: 'Alice', avatar_url: null } }),
|
||||
}),
|
||||
}))
|
||||
|
||||
@@ -3,6 +3,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { Suspense, useState } from 'react'
|
||||
import { page, userEvent } from 'vite-plus/test/browser'
|
||||
import { render } from 'vitest-browser-react'
|
||||
import { consoleQueryKey } from '@/test/console/operation-hooks'
|
||||
import { EditorResourceScope } from '../editor-resource/provider'
|
||||
import { CommentThread } from './thread'
|
||||
|
||||
@@ -20,7 +21,7 @@ vi.mock('../store', () => ({
|
||||
}))
|
||||
vi.mock('@/features/account-profile/client', () => ({
|
||||
userProfileQueryOptions: () => ({
|
||||
queryKey: ['profile'],
|
||||
queryKey: consoleQueryKey('account.profile.get'),
|
||||
queryFn: async () => ({ profile: { id: 'author', name: 'Alice', avatar_url: null } }),
|
||||
}),
|
||||
}))
|
||||
|
||||
@@ -2,6 +2,7 @@ import { screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
|
||||
import { DeviceFlowError } from '@/service/device-flow'
|
||||
import { consoleQueryKey } from '@/test/console/operation-hooks'
|
||||
import { renderWithConsoleQuery as render } from '@/test/console/query-data'
|
||||
import DevicePage from '../page'
|
||||
|
||||
@@ -22,7 +23,10 @@ vi.mock('@/service/device-flow', async (importOriginal) => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/features/account-profile/client', () => ({
|
||||
userProfileQueryOptions: () => ({ queryKey: ['profile'], queryFn: async () => null }),
|
||||
userProfileQueryOptions: () => ({
|
||||
queryKey: consoleQueryKey('account.profile.get'),
|
||||
queryFn: async () => null,
|
||||
}),
|
||||
}))
|
||||
|
||||
const VALID_CODE = 'ABCD-3456'
|
||||
@@ -40,7 +44,7 @@ beforeEach(() => {
|
||||
|
||||
async function reachTerminal(rejectWith: unknown) {
|
||||
mockDeviceLookup.mockRejectedValue(rejectWith)
|
||||
render(<DevicePage />)
|
||||
render(<DevicePage />, { accountProfile: null })
|
||||
const user = userEvent.setup()
|
||||
await user.type(screen.getByRole('textbox'), VALID_CODE)
|
||||
await user.click(screen.getByRole('button', { name: /deviceFlow.codeEntry.continue/i }))
|
||||
@@ -80,7 +84,7 @@ describe('error_sso dedicated view', () => {
|
||||
|
||||
it('renders the dedicated SSO error screen (not the code-entry page)', async () => {
|
||||
mockSearchParams = { sso_error: 'sso_failed', user_code: 'ABCD-3456' }
|
||||
render(<DevicePage />)
|
||||
render(<DevicePage />, { accountProfile: null })
|
||||
expect(await screen.findByText(TITLE)).toBeInTheDocument()
|
||||
expect(await screen.findByText(GENERIC)).toBeInTheDocument()
|
||||
expect(screen.queryByRole('textbox')).not.toBeInTheDocument()
|
||||
@@ -89,7 +93,7 @@ describe('error_sso dedicated view', () => {
|
||||
|
||||
it('shows the email special-case copy', async () => {
|
||||
mockSearchParams = { sso_error: 'email_belongs_to_dify_account', user_code: 'ABCD-3456' }
|
||||
render(<DevicePage />)
|
||||
render(<DevicePage />, { accountProfile: null })
|
||||
expect(await screen.findByText(EMAIL_COPY)).toBeInTheDocument()
|
||||
expect(screen.queryByText('email_belongs_to_dify_account')).not.toBeInTheDocument()
|
||||
})
|
||||
@@ -98,7 +102,7 @@ describe('error_sso dedicated view', () => {
|
||||
const user = userEvent.setup()
|
||||
mockSearchParams = { sso_error: 'sso_failed', user_code: 'ABCD-3456' }
|
||||
mockDeviceLookup.mockResolvedValue({ valid: true })
|
||||
render(<DevicePage />)
|
||||
render(<DevicePage />, { accountProfile: null })
|
||||
await screen.findByText(TITLE)
|
||||
await user.click(screen.getByRole('button', { name: BACK_TO_LOGIN }))
|
||||
await screen.findByText('deviceFlow.chooser.subtitle')
|
||||
@@ -106,7 +110,7 @@ describe('error_sso dedicated view', () => {
|
||||
})
|
||||
|
||||
it('shows no SSO error screen when sso_error is absent', () => {
|
||||
render(<DevicePage />)
|
||||
render(<DevicePage />, { accountProfile: null })
|
||||
expect(screen.getByRole('textbox')).toBeInTheDocument()
|
||||
expect(screen.queryByText(TITLE)).not.toBeInTheDocument()
|
||||
expect(document.title).toBe('deviceFlow.codeEntry.title - Dify')
|
||||
|
||||
+61
-69
@@ -187,31 +187,14 @@ vi.mock('@/service/console', async () => {
|
||||
},
|
||||
},
|
||||
agent: {
|
||||
get: {
|
||||
key: () => ['agents'],
|
||||
},
|
||||
get: consoleOperationStub('agent.get'),
|
||||
byAgentId: {
|
||||
get: {
|
||||
queryKey: ({ input }: { input: { params: { agent_id: string } } }) => [
|
||||
'agent-detail',
|
||||
input.params.agent_id,
|
||||
],
|
||||
},
|
||||
get: consoleOperationStub('agent.byAgentId.get'),
|
||||
apiAccess: {
|
||||
get: {
|
||||
queryKey: ({ input }: { input: { params: { agent_id: string } } }) => [
|
||||
'agent-api-access',
|
||||
input.params.agent_id,
|
||||
],
|
||||
},
|
||||
get: consoleOperationStub('agent.byAgentId.apiAccess.get'),
|
||||
},
|
||||
composer: {
|
||||
get: {
|
||||
queryKey: ({ input }: { input: { params: { agent_id: string } } }) => [
|
||||
'agent-composer',
|
||||
input.params.agent_id,
|
||||
],
|
||||
},
|
||||
get: consoleOperationStub('agent.byAgentId.composer.get'),
|
||||
put: {
|
||||
mutationOptions: composerPutMutationOptions,
|
||||
},
|
||||
@@ -222,9 +205,7 @@ vi.mock('@/service/console', async () => {
|
||||
},
|
||||
},
|
||||
versions: {
|
||||
get: {
|
||||
key: () => ['agent-versions'],
|
||||
},
|
||||
get: consoleOperationStub('agent.byAgentId.versions.get'),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -370,11 +351,10 @@ describe('useAgentConfigureSync', () => {
|
||||
|
||||
it('should automatically save configure page changes to draft', async () => {
|
||||
const { queryClient, store } = renderUseAgentConfigureSync()
|
||||
queryClient.setQueryData(['agent-detail', 'agent-1'], {
|
||||
active_config_is_published: true,
|
||||
name: 'Agent',
|
||||
permission_keys: Object.values(AgentPermission),
|
||||
})
|
||||
queryClient.setQueryData(
|
||||
consoleQuery.agent.byAgentId.get.queryKey({ input: { params: { agent_id: 'agent-1' } } }),
|
||||
createAgentFixture({ name: 'Agent', permission_keys: Object.values(AgentPermission) }),
|
||||
)
|
||||
|
||||
act(() => {
|
||||
store.set(agentComposerDraftAtom, {
|
||||
@@ -383,11 +363,13 @@ describe('useAgentConfigureSync', () => {
|
||||
})
|
||||
})
|
||||
|
||||
expect(queryClient.getQueryData(['agent-detail', 'agent-1'])).toEqual({
|
||||
active_config_is_published: true,
|
||||
name: 'Agent',
|
||||
permission_keys: Object.values(AgentPermission),
|
||||
})
|
||||
expect(
|
||||
queryClient.getQueryData(
|
||||
consoleQuery.agent.byAgentId.get.queryKey({ input: { params: { agent_id: 'agent-1' } } }),
|
||||
),
|
||||
).toEqual(
|
||||
createAgentFixture({ name: 'Agent', permission_keys: Object.values(AgentPermission) }),
|
||||
)
|
||||
expect(composerPutMutationFn).not.toHaveBeenCalled()
|
||||
|
||||
await act(async () => {
|
||||
@@ -410,11 +392,13 @@ describe('useAgentConfigureSync', () => {
|
||||
}),
|
||||
}),
|
||||
)
|
||||
expect(queryClient.getQueryData(['agent-detail', 'agent-1'])).toEqual({
|
||||
active_config_is_published: true,
|
||||
name: 'Agent',
|
||||
permission_keys: Object.values(AgentPermission),
|
||||
})
|
||||
expect(
|
||||
queryClient.getQueryData(
|
||||
consoleQuery.agent.byAgentId.get.queryKey({ input: { params: { agent_id: 'agent-1' } } }),
|
||||
),
|
||||
).toEqual(
|
||||
createAgentFixture({ name: 'Agent', permission_keys: Object.values(AgentPermission) }),
|
||||
)
|
||||
})
|
||||
|
||||
it('should autosave only the latest committed Agent configuration', async () => {
|
||||
@@ -507,11 +491,10 @@ describe('useAgentConfigureSync', () => {
|
||||
|
||||
it('should cancel pending autosave when the draft returns to the saved baseline', async () => {
|
||||
const { queryClient, store } = renderUseAgentConfigureSync()
|
||||
queryClient.setQueryData(['agent-detail', 'agent-1'], {
|
||||
active_config_is_published: true,
|
||||
name: 'Agent',
|
||||
permission_keys: Object.values(AgentPermission),
|
||||
})
|
||||
queryClient.setQueryData(
|
||||
consoleQuery.agent.byAgentId.get.queryKey({ input: { params: { agent_id: 'agent-1' } } }),
|
||||
createAgentFixture({ name: 'Agent', permission_keys: Object.values(AgentPermission) }),
|
||||
)
|
||||
|
||||
act(() => {
|
||||
store.set(agentComposerDraftAtom, {
|
||||
@@ -528,11 +511,13 @@ describe('useAgentConfigureSync', () => {
|
||||
})
|
||||
|
||||
expect(composerPutMutationFn).not.toHaveBeenCalled()
|
||||
expect(queryClient.getQueryData(['agent-detail', 'agent-1'])).toEqual({
|
||||
active_config_is_published: true,
|
||||
name: 'Agent',
|
||||
permission_keys: Object.values(AgentPermission),
|
||||
})
|
||||
expect(
|
||||
queryClient.getQueryData(
|
||||
consoleQuery.agent.byAgentId.get.queryKey({ input: { params: { agent_id: 'agent-1' } } }),
|
||||
),
|
||||
).toEqual(
|
||||
createAgentFixture({ name: 'Agent', permission_keys: Object.values(AgentPermission) }),
|
||||
)
|
||||
})
|
||||
|
||||
it('should save dirty draft once when the page is closing', async () => {
|
||||
@@ -1027,22 +1012,23 @@ describe('useAgentConfigureSync', () => {
|
||||
|
||||
it('should not save the draft immediately when the composer draft is unchanged', async () => {
|
||||
const { queryClient, result } = renderUseAgentConfigureSync()
|
||||
queryClient.setQueryData(['agent-detail', 'agent-1'], {
|
||||
active_config_is_published: true,
|
||||
name: 'Agent',
|
||||
permission_keys: Object.values(AgentPermission),
|
||||
})
|
||||
queryClient.setQueryData(
|
||||
consoleQuery.agent.byAgentId.get.queryKey({ input: { params: { agent_id: 'agent-1' } } }),
|
||||
createAgentFixture({ name: 'Agent', permission_keys: Object.values(AgentPermission) }),
|
||||
)
|
||||
|
||||
await act(async () => {
|
||||
await result.current.saveDraft()
|
||||
})
|
||||
|
||||
expect(composerPutMutationFn).not.toHaveBeenCalled()
|
||||
expect(queryClient.getQueryData(['agent-detail', 'agent-1'])).toEqual({
|
||||
active_config_is_published: true,
|
||||
name: 'Agent',
|
||||
permission_keys: Object.values(AgentPermission),
|
||||
})
|
||||
expect(
|
||||
queryClient.getQueryData(
|
||||
consoleQuery.agent.byAgentId.get.queryKey({ input: { params: { agent_id: 'agent-1' } } }),
|
||||
),
|
||||
).toEqual(
|
||||
createAgentFixture({ name: 'Agent', permission_keys: Object.values(AgentPermission) }),
|
||||
)
|
||||
})
|
||||
|
||||
it('saves a newly selected model from the draft without waiting for a render', async () => {
|
||||
@@ -1632,9 +1618,10 @@ describe('useAgentConfigureSync', () => {
|
||||
enabled: false,
|
||||
currentModel: configuredModel,
|
||||
})
|
||||
queryClient.setQueryData(['agent-detail', 'agent-1'], {
|
||||
permission_keys: [AgentPermission.TestAndRun],
|
||||
})
|
||||
queryClient.setQueryData(
|
||||
consoleQuery.agent.byAgentId.get.queryKey({ input: { params: { agent_id: 'agent-1' } } }),
|
||||
createAgentFixture({ permission_keys: [AgentPermission.TestAndRun] }),
|
||||
)
|
||||
act(() => {
|
||||
store.set(agentComposerPromptAtom, 'Unsaved local changes')
|
||||
})
|
||||
@@ -1654,9 +1641,10 @@ describe('useAgentConfigureSync', () => {
|
||||
publishEnabled: true,
|
||||
currentModel: configuredModel,
|
||||
})
|
||||
queryClient.setQueryData(['agent-detail', 'agent-1'], {
|
||||
permission_keys: [AgentPermission.ReleaseAndVersion],
|
||||
})
|
||||
queryClient.setQueryData(
|
||||
consoleQuery.agent.byAgentId.get.queryKey({ input: { params: { agent_id: 'agent-1' } } }),
|
||||
createAgentFixture({ permission_keys: [AgentPermission.ReleaseAndVersion] }),
|
||||
)
|
||||
await act(async () => {
|
||||
await expect(result.current.publishDraft()).resolves.toMatchObject({ kind: 'update' })
|
||||
})
|
||||
@@ -1669,7 +1657,10 @@ describe('useAgentConfigureSync', () => {
|
||||
const { queryClient, store, unmount } = renderUseAgentConfigureSync()
|
||||
act(() => {
|
||||
store.set(agentComposerPromptAtom, 'Dirty before revocation')
|
||||
queryClient.setQueryData(['agent-detail', 'agent-1'], { permission_keys: [] })
|
||||
queryClient.setQueryData(
|
||||
consoleQuery.agent.byAgentId.get.queryKey({ input: { params: { agent_id: 'agent-1' } } }),
|
||||
createAgentFixture({ permission_keys: [] }),
|
||||
)
|
||||
unmount()
|
||||
})
|
||||
await act(async () => {
|
||||
@@ -1689,9 +1680,10 @@ describe('useAgentConfigureSync', () => {
|
||||
await act(async () => {
|
||||
await Promise.resolve()
|
||||
})
|
||||
queryClient.setQueryData(['agent-detail', 'agent-1'], {
|
||||
permission_keys: [AgentPermission.Edit],
|
||||
})
|
||||
queryClient.setQueryData(
|
||||
consoleQuery.agent.byAgentId.get.queryKey({ input: { params: { agent_id: 'agent-1' } } }),
|
||||
createAgentFixture({ permission_keys: [AgentPermission.Edit] }),
|
||||
)
|
||||
await act(async () => {
|
||||
saving.resolve({ agent_soul: {} })
|
||||
await publishing
|
||||
|
||||
+58
-37
@@ -1,6 +1,6 @@
|
||||
import type { AgentConfigApiContext } from '../../config-context'
|
||||
import type { AgentSoulConfigFormState } from '@/features/agent-v2/agent-composer/form-state'
|
||||
import { QueryClient } from '@tanstack/react-query'
|
||||
import { QueryClient, skipToken } from '@tanstack/react-query'
|
||||
import { act, render, screen, waitFor, within } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { useAtomValue } from 'jotai'
|
||||
@@ -71,17 +71,37 @@ vi.mock('@/utils/download', () => ({
|
||||
downloadUrl: mocks.downloadUrl,
|
||||
}))
|
||||
|
||||
vi.mock('@/service/console', async () => {
|
||||
vi.mock('@/service/console', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/service/console')>()
|
||||
const { consoleOperationStub } = await import('@/test/console/operation-hooks')
|
||||
const { withConsoleQueryOverrides } = await import('@/test/console/service-responses')
|
||||
const query = withConsoleQueryOverrides(actual.consoleQuery, {
|
||||
'agent.byAgentId.config.files.byName.preview.get': { queryOptions: mocks.previewQueryOptions },
|
||||
'agent.byAgentId.config.files.byName.download.get': {
|
||||
queryOptions: mocks.downloadQueryOptions,
|
||||
},
|
||||
'apps.byAppId.agent.config.files.byName.preview.get': {
|
||||
queryOptions: mocks.previewQueryOptions,
|
||||
},
|
||||
'apps.byAppId.agent.config.files.byName.download.get': {
|
||||
queryOptions: mocks.downloadQueryOptions,
|
||||
},
|
||||
'trialApps.byAppId.agent.config.files.byName.preview.get': {
|
||||
queryOptions: mocks.previewQueryOptions,
|
||||
},
|
||||
'trialApps.byAppId.agent.config.files.byName.download.get': {
|
||||
queryOptions: mocks.downloadQueryOptions,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
consoleQuery: {
|
||||
systemFeatures: {
|
||||
get: {
|
||||
queryKey: () => ['console', 'systemFeatures', 'get'],
|
||||
...consoleOperationStub('systemFeatures.get'),
|
||||
queryOptions: (options?: Record<string, unknown>) => ({
|
||||
queryKey: ['console', 'systemFeatures', 'get'],
|
||||
...consoleOperationStub('systemFeatures.get').queryOptions(options),
|
||||
queryFn: () => new Promise(() => {}),
|
||||
...options,
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -97,14 +117,10 @@ vi.mock('@/service/console', async () => {
|
||||
mutationOptions: () => ({ mutationFn: mocks.deleteFileMutationFn }),
|
||||
},
|
||||
preview: {
|
||||
get: {
|
||||
queryOptions: mocks.previewQueryOptions,
|
||||
},
|
||||
get: query.agent.byAgentId.config.files.byName.preview.get,
|
||||
},
|
||||
download: {
|
||||
get: {
|
||||
queryOptions: mocks.downloadQueryOptions,
|
||||
},
|
||||
get: query.agent.byAgentId.config.files.byName.download.get,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -124,14 +140,10 @@ vi.mock('@/service/console', async () => {
|
||||
mutationOptions: () => ({ mutationFn: mocks.deleteFileMutationFn }),
|
||||
},
|
||||
preview: {
|
||||
get: {
|
||||
queryOptions: mocks.previewQueryOptions,
|
||||
},
|
||||
get: query.apps.byAppId.agent.config.files.byName.preview.get,
|
||||
},
|
||||
download: {
|
||||
get: {
|
||||
queryOptions: mocks.downloadQueryOptions,
|
||||
},
|
||||
get: query.apps.byAppId.agent.config.files.byName.download.get,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -145,8 +157,12 @@ vi.mock('@/service/console', async () => {
|
||||
config: {
|
||||
files: {
|
||||
byName: {
|
||||
preview: { get: { queryOptions: mocks.previewQueryOptions } },
|
||||
download: { get: { queryOptions: mocks.downloadQueryOptions } },
|
||||
preview: {
|
||||
get: query.trialApps.byAppId.agent.config.files.byName.preview.get,
|
||||
},
|
||||
download: {
|
||||
get: query.trialApps.byAppId.agent.config.files.byName.download.get,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -217,7 +233,7 @@ function renderAgentFiles({
|
||||
},
|
||||
})
|
||||
queryClient.setQueryData(
|
||||
['console', 'systemFeatures', 'get'],
|
||||
consoleQuery.systemFeatures.get.queryKey(),
|
||||
createSystemFeaturesFixture({ deployment_edition: 'COMMUNITY' }),
|
||||
)
|
||||
|
||||
@@ -256,7 +272,6 @@ describe('AgentFiles', () => {
|
||||
workflow_file_upload_limit: 10,
|
||||
})
|
||||
mocks.previewQueryOptions.mockImplementation(({ input }) => ({
|
||||
queryKey: ['preview-config-file', input],
|
||||
queryFn: async () => ({
|
||||
name: input.params.name,
|
||||
binary: false,
|
||||
@@ -265,7 +280,6 @@ describe('AgentFiles', () => {
|
||||
}),
|
||||
}))
|
||||
mocks.downloadQueryOptions.mockImplementation(({ input }) => ({
|
||||
queryKey: ['download-config-file', input],
|
||||
queryFn: async () => ({
|
||||
url: `https://example.com/${input.params.name}`,
|
||||
}),
|
||||
@@ -528,19 +542,17 @@ describe('AgentFiles', () => {
|
||||
resolveDownload = resolve
|
||||
})
|
||||
const downloadQueryFn = vi.fn(() => downloadResponse)
|
||||
mocks.downloadQueryOptions.mockImplementation(({ input }) => ({
|
||||
queryKey: ['download-config-file', input],
|
||||
mocks.downloadQueryOptions.mockImplementation(() => ({
|
||||
queryFn: downloadQueryFn,
|
||||
}))
|
||||
const { queryClient } = renderAgentFiles()
|
||||
queryClient.setQueryData(
|
||||
[
|
||||
'download-config-file',
|
||||
{
|
||||
consoleQuery.agent.byAgentId.config.files.byName.download.get.queryKey({
|
||||
input: {
|
||||
params: { agent_id: 'agent-1', name: 'diagram.png' },
|
||||
query: { draft_type: 'draft', version_id: undefined },
|
||||
},
|
||||
],
|
||||
}),
|
||||
{ url: 'https://example.com/expired-diagram.png' },
|
||||
)
|
||||
|
||||
@@ -563,13 +575,12 @@ describe('AgentFiles', () => {
|
||||
const user = userEvent.setup()
|
||||
const { queryClient } = renderAgentFiles()
|
||||
queryClient.setQueryData(
|
||||
[
|
||||
'download-config-file',
|
||||
{
|
||||
consoleQuery.agent.byAgentId.config.files.byName.download.get.queryKey({
|
||||
input: {
|
||||
params: { agent_id: 'agent-1', name: 'diagram.png' },
|
||||
query: { draft_type: 'draft', version_id: undefined },
|
||||
},
|
||||
],
|
||||
}),
|
||||
{ url: 'https://example.com/stale-diagram.png' },
|
||||
)
|
||||
|
||||
@@ -599,7 +610,7 @@ describe('AgentFiles', () => {
|
||||
|
||||
it('should download the selected file from the preview header action', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderAgentFiles()
|
||||
const { queryClient } = renderAgentFiles()
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'diagram.png' }))
|
||||
const dialog = await screen.findByRole('dialog')
|
||||
@@ -626,6 +637,14 @@ describe('AgentFiles', () => {
|
||||
url: 'https://example.com/diagram.png',
|
||||
fileName: 'diagram.png',
|
||||
})
|
||||
const trialFile = consoleQuery.trialApps.byAppId.agent.config.files.byName
|
||||
for (const operation of [trialFile.preview.get, trialFile.download.get]) {
|
||||
expect(queryClient.getQueryState(operation.queryKey({ input: skipToken }))).toMatchObject({
|
||||
status: 'pending',
|
||||
fetchStatus: 'idle',
|
||||
error: null,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('should show config note as a virtual build note file and preview its content locally', async () => {
|
||||
@@ -791,10 +810,12 @@ describe('AgentFiles', () => {
|
||||
|
||||
await user.click(within(imageDialog).getByRole('button', { name: 'common.operation.close' }))
|
||||
queryClient.setQueryData(
|
||||
[
|
||||
'preview-config-file',
|
||||
{ params: { app_id: 'trial-1', name: 'brief.md' }, query: { version_id: undefined } },
|
||||
],
|
||||
consoleQuery.trialApps.byAppId.agent.config.files.byName.preview.get.queryKey({
|
||||
input: {
|
||||
params: { app_id: 'trial-1', name: 'brief.md' },
|
||||
query: { version_id: undefined },
|
||||
},
|
||||
}),
|
||||
{ name: 'brief.md', binary: true, truncated: false, text: null },
|
||||
)
|
||||
await user.click(screen.getByRole('button', { name: 'brief.md' }))
|
||||
|
||||
+19
-23
@@ -134,17 +134,15 @@ function AgentFileItem({
|
||||
enabled: isPreviewOpen && !!previewFileId && !isVirtualPreviewFile && !!apiContext.workflow,
|
||||
})
|
||||
const trialPreviewQuery = useQuery(
|
||||
apiContext.trialAppId
|
||||
? {
|
||||
...consoleQuery.trialApps.byAppId.agent.config.files.byName.preview.get.queryOptions({
|
||||
input: {
|
||||
params: { app_id: apiContext.trialAppId, name: previewFileId ?? '' },
|
||||
query: { version_id: apiContext.versionId },
|
||||
},
|
||||
}),
|
||||
enabled: isPreviewOpen && !!previewFileId && !isVirtualPreviewFile,
|
||||
}
|
||||
: { queryKey: ['agent-v2', 'trial-file-preview-disabled'], queryFn: skipToken },
|
||||
consoleQuery.trialApps.byAppId.agent.config.files.byName.preview.get.queryOptions({
|
||||
input: apiContext.trialAppId
|
||||
? {
|
||||
params: { app_id: apiContext.trialAppId, name: previewFileId ?? '' },
|
||||
query: { version_id: apiContext.versionId },
|
||||
}
|
||||
: skipToken,
|
||||
enabled: !!apiContext.trialAppId && isPreviewOpen && !!previewFileId && !isVirtualPreviewFile,
|
||||
}),
|
||||
)
|
||||
const previewQuery = apiContext.trialAppId
|
||||
? trialPreviewQuery
|
||||
@@ -191,18 +189,16 @@ function AgentFileItem({
|
||||
enabled: shouldDownloadPreviewFile && !!apiContext.workflow,
|
||||
})
|
||||
const trialDownloadQuery = useQuery(
|
||||
apiContext.trialAppId
|
||||
? {
|
||||
...consoleQuery.trialApps.byAppId.agent.config.files.byName.download.get.queryOptions({
|
||||
input: {
|
||||
params: { app_id: apiContext.trialAppId, name: previewFileId ?? '' },
|
||||
query: { version_id: apiContext.versionId },
|
||||
},
|
||||
}),
|
||||
staleTime: 0,
|
||||
enabled: shouldDownloadPreviewFile,
|
||||
}
|
||||
: { queryKey: ['agent-v2', 'trial-file-download-disabled'], queryFn: skipToken },
|
||||
consoleQuery.trialApps.byAppId.agent.config.files.byName.download.get.queryOptions({
|
||||
input: apiContext.trialAppId
|
||||
? {
|
||||
params: { app_id: apiContext.trialAppId, name: previewFileId ?? '' },
|
||||
query: { version_id: apiContext.versionId },
|
||||
}
|
||||
: skipToken,
|
||||
enabled: !!apiContext.trialAppId && shouldDownloadPreviewFile,
|
||||
staleTime: 0,
|
||||
}),
|
||||
)
|
||||
const downloadQuery = apiContext.trialAppId
|
||||
? trialDownloadQuery
|
||||
|
||||
+104
-82
@@ -1,7 +1,7 @@
|
||||
import type { SkillResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { AgentConfigApiContext } from '../../config-context'
|
||||
import type { AgentSoulConfigFormState } from '@/features/agent-v2/agent-composer/form-state'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { QueryClient, QueryClientProvider, skipToken } from '@tanstack/react-query'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { useAtomValue } from 'jotai'
|
||||
@@ -50,7 +50,6 @@ type ConfigSkillDownloadQueryOptionsInput = {
|
||||
}
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
agentSkillBindingsKey: vi.fn((_options: unknown): unknown[] => ['workspace-agent-skills']),
|
||||
agentSkillBindingsQueryOptions: vi.fn((_options: unknown) => ({})),
|
||||
deleteSkillMutationFn: vi.fn(async (_input: unknown) => ({
|
||||
removed_names: ['Tender Analyzer'],
|
||||
@@ -114,21 +113,66 @@ vi.mock('@/context/permission-state', async () => {
|
||||
})
|
||||
|
||||
vi.mock('@/service/console', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/service/console')>()
|
||||
const { consoleOperationStub } = await import('@/test/console/operation-hooks')
|
||||
const { withConsoleQueryOverrides } = await import('@/test/console/service-responses')
|
||||
const query = withConsoleQueryOverrides(actual.consoleQuery, {
|
||||
'tags.get': { queryOptions: mocks.workspaceSkillTagsQueryOptions },
|
||||
'agent.byAgentId.config.skills.byName.download.get': {
|
||||
queryOptions: mocks.skillDownloadQueryOptions,
|
||||
},
|
||||
'agent.byAgentId.config.skills.byName.inspect.get': { queryOptions: mocks.inspectQueryOptions },
|
||||
'agent.byAgentId.config.skills.byName.files.preview.get': {
|
||||
queryOptions: mocks.previewQueryOptions,
|
||||
},
|
||||
'agent.byAgentId.config.skills.byName.files.download.get': {
|
||||
queryOptions: mocks.downloadQueryOptions,
|
||||
},
|
||||
'apps.byAppId.agent.config.skills.byName.download.get': {
|
||||
queryOptions: mocks.skillDownloadQueryOptions,
|
||||
},
|
||||
'apps.byAppId.agent.config.skills.byName.inspect.get': {
|
||||
queryOptions: mocks.inspectQueryOptions,
|
||||
},
|
||||
'apps.byAppId.agent.config.skills.byName.files.preview.get': {
|
||||
queryOptions: mocks.previewQueryOptions,
|
||||
},
|
||||
'apps.byAppId.agent.config.skills.byName.files.download.get': {
|
||||
queryOptions: mocks.downloadQueryOptions,
|
||||
},
|
||||
'trialApps.byAppId.agent.config.skills.byName.download.get': {
|
||||
queryOptions: mocks.skillDownloadQueryOptions,
|
||||
},
|
||||
'trialApps.byAppId.agent.config.skills.byName.inspect.get': {
|
||||
queryOptions: mocks.inspectQueryOptions,
|
||||
},
|
||||
'trialApps.byAppId.agent.config.skills.byName.files.preview.get': {
|
||||
queryOptions: mocks.previewQueryOptions,
|
||||
},
|
||||
'trialApps.byAppId.agent.config.skills.byName.files.download.get': {
|
||||
queryOptions: mocks.downloadQueryOptions,
|
||||
},
|
||||
'workspaces.current.agents.byAgentId.skills.get': {
|
||||
queryOptions: mocks.agentSkillBindingsQueryOptions,
|
||||
},
|
||||
'workspaces.current.skills.get': {
|
||||
queryOptions: mocks.workspaceSkillsQueryOptions,
|
||||
infiniteOptions: mocks.workspaceSkillsInfiniteOptions,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
consoleQuery: {
|
||||
files: { upload: { get: consoleOperationStub('files.upload.get') } },
|
||||
features: (await importOriginal<typeof import('@/service/console')>()).consoleQuery.features,
|
||||
tags: {
|
||||
get: {
|
||||
queryOptions: mocks.workspaceSkillTagsQueryOptions,
|
||||
},
|
||||
get: query.tags.get,
|
||||
},
|
||||
agent: {
|
||||
byAgentId: {
|
||||
composer: {
|
||||
get: {
|
||||
key: vi.fn((_options: unknown): unknown[] => ['agent-composer']),
|
||||
key: actual.consoleQuery.agent.byAgentId.composer.get.key,
|
||||
},
|
||||
},
|
||||
config: {
|
||||
@@ -143,25 +187,17 @@ vi.mock('@/service/console', async (importOriginal) => {
|
||||
mutationOptions: () => ({ mutationFn: mocks.deleteSkillMutationFn }),
|
||||
},
|
||||
download: {
|
||||
get: {
|
||||
queryOptions: mocks.skillDownloadQueryOptions,
|
||||
},
|
||||
get: query.agent.byAgentId.config.skills.byName.download.get,
|
||||
},
|
||||
inspect: {
|
||||
get: {
|
||||
queryOptions: mocks.inspectQueryOptions,
|
||||
},
|
||||
get: query.agent.byAgentId.config.skills.byName.inspect.get,
|
||||
},
|
||||
files: {
|
||||
preview: {
|
||||
get: {
|
||||
queryOptions: mocks.previewQueryOptions,
|
||||
},
|
||||
get: query.agent.byAgentId.config.skills.byName.files.preview.get,
|
||||
},
|
||||
download: {
|
||||
get: {
|
||||
queryOptions: mocks.downloadQueryOptions,
|
||||
},
|
||||
get: query.agent.byAgentId.config.skills.byName.files.download.get,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -184,25 +220,17 @@ vi.mock('@/service/console', async (importOriginal) => {
|
||||
mutationOptions: () => ({ mutationFn: mocks.deleteSkillMutationFn }),
|
||||
},
|
||||
download: {
|
||||
get: {
|
||||
queryOptions: mocks.skillDownloadQueryOptions,
|
||||
},
|
||||
get: query.apps.byAppId.agent.config.skills.byName.download.get,
|
||||
},
|
||||
inspect: {
|
||||
get: {
|
||||
queryOptions: mocks.inspectQueryOptions,
|
||||
},
|
||||
get: query.apps.byAppId.agent.config.skills.byName.inspect.get,
|
||||
},
|
||||
files: {
|
||||
preview: {
|
||||
get: {
|
||||
queryOptions: mocks.previewQueryOptions,
|
||||
},
|
||||
get: query.apps.byAppId.agent.config.skills.byName.files.preview.get,
|
||||
},
|
||||
download: {
|
||||
get: {
|
||||
queryOptions: mocks.downloadQueryOptions,
|
||||
},
|
||||
get: query.apps.byAppId.agent.config.skills.byName.files.download.get,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -217,11 +245,19 @@ vi.mock('@/service/console', async (importOriginal) => {
|
||||
config: {
|
||||
skills: {
|
||||
byName: {
|
||||
download: { get: { queryOptions: mocks.skillDownloadQueryOptions } },
|
||||
inspect: { get: { queryOptions: mocks.inspectQueryOptions } },
|
||||
download: {
|
||||
get: query.trialApps.byAppId.agent.config.skills.byName.download.get,
|
||||
},
|
||||
inspect: {
|
||||
get: query.trialApps.byAppId.agent.config.skills.byName.inspect.get,
|
||||
},
|
||||
files: {
|
||||
preview: { get: { queryOptions: mocks.previewQueryOptions } },
|
||||
download: { get: { queryOptions: mocks.downloadQueryOptions } },
|
||||
preview: {
|
||||
get: query.trialApps.byAppId.agent.config.skills.byName.files.preview.get,
|
||||
},
|
||||
download: {
|
||||
get: query.trialApps.byAppId.agent.config.skills.byName.files.download.get,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -234,10 +270,7 @@ vi.mock('@/service/console', async (importOriginal) => {
|
||||
agents: {
|
||||
byAgentId: {
|
||||
skills: {
|
||||
get: {
|
||||
key: mocks.agentSkillBindingsKey,
|
||||
queryOptions: mocks.agentSkillBindingsQueryOptions,
|
||||
},
|
||||
get: query.workspaces.current.agents.byAgentId.skills.get,
|
||||
put: {
|
||||
mutationOptions: () => ({
|
||||
mutationFn: mocks.replaceAgentSkillBindingsMutationFn,
|
||||
@@ -248,8 +281,8 @@ vi.mock('@/service/console', async (importOriginal) => {
|
||||
},
|
||||
skills: {
|
||||
get: {
|
||||
queryOptions: mocks.workspaceSkillsQueryOptions,
|
||||
infiniteOptions: mocks.workspaceSkillsInfiniteOptions,
|
||||
...query.workspaces.current.skills.get,
|
||||
infiniteOptions: query.workspaces.current.skills.get.infiniteOptions,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -401,15 +434,10 @@ describe('AgentSkills', () => {
|
||||
headers: { 'Content-Type': 'application/octet-stream' },
|
||||
}),
|
||||
)
|
||||
mocks.agentSkillBindingsKey.mockImplementation((options) => {
|
||||
const { input } = options as { input: { params: { agent_id: string } } }
|
||||
return ['workspace-agent-skills', input]
|
||||
})
|
||||
mocks.agentSkillBindingsQueryOptions.mockImplementation((options) => {
|
||||
const { input } = options as { input: { params: { agent_id: string } } }
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-agent-skills', input],
|
||||
queryFn: async () => ({
|
||||
agent_id: input.params.agent_id,
|
||||
skill_ids: [],
|
||||
@@ -418,7 +446,6 @@ describe('AgentSkills', () => {
|
||||
}
|
||||
})
|
||||
mocks.inspectQueryOptions.mockImplementation(({ input }) => ({
|
||||
queryKey: ['inspect-skill', input],
|
||||
queryFn: async () => ({
|
||||
id: input.params.name,
|
||||
name: input.params.name,
|
||||
@@ -465,7 +492,6 @@ describe('AgentSkills', () => {
|
||||
}),
|
||||
}))
|
||||
mocks.previewQueryOptions.mockImplementation(({ input }) => ({
|
||||
queryKey: ['preview-skill-file', input],
|
||||
queryFn: async () => ({
|
||||
path: input.query.path,
|
||||
binary: input.query.path.endsWith('.bin'),
|
||||
@@ -474,22 +500,17 @@ describe('AgentSkills', () => {
|
||||
}),
|
||||
}))
|
||||
mocks.downloadQueryOptions.mockImplementation(({ input }) => ({
|
||||
queryKey: ['download-skill-file', input],
|
||||
queryFn: async () => ({
|
||||
url: `/console/api/agent/agent-1/config/skills/Tender%20Analyzer/files/content?path=${encodeURIComponent(input.query.path)}`,
|
||||
}),
|
||||
}))
|
||||
mocks.skillDownloadQueryOptions.mockImplementation(({ input }) => ({
|
||||
queryKey: ['download-skill', input],
|
||||
queryFn: async () => ({
|
||||
url: `https://example.com/${input.params.name}.skill`,
|
||||
}),
|
||||
}))
|
||||
mocks.workspaceSkillsQueryOptions.mockImplementation((options) => {
|
||||
const { input } = options as { input: { query?: { keyword?: string } } }
|
||||
|
||||
mocks.workspaceSkillsQueryOptions.mockImplementation(() => {
|
||||
return {
|
||||
queryKey: ['workspace-skills', input],
|
||||
queryFn: async () => ({
|
||||
data: [],
|
||||
}),
|
||||
@@ -505,7 +526,6 @@ describe('AgentSkills', () => {
|
||||
}
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-skills', input(initialPageParam)],
|
||||
queryFn: async ({ pageParam = initialPageParam }: { pageParam?: number }) => ({
|
||||
data: [],
|
||||
has_more: false,
|
||||
@@ -518,7 +538,6 @@ describe('AgentSkills', () => {
|
||||
}
|
||||
})
|
||||
mocks.workspaceSkillTagsQueryOptions.mockImplementation(() => ({
|
||||
queryKey: ['workspace-skill-tags'],
|
||||
queryFn: async () => [
|
||||
{ id: 'tag-support', name: 'support', type: 'skill', binding_count: '1' },
|
||||
{ id: 'tag-sales', name: 'sales', type: 'skill', binding_count: '1' },
|
||||
@@ -723,7 +742,7 @@ describe('AgentSkills', () => {
|
||||
it('should bind workspace skills without adding them to inline config skills', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.workspaceSkillsInfiniteOptions.mockImplementation((options) => {
|
||||
const { input, getNextPageParam, initialPageParam } = options as {
|
||||
const { getNextPageParam, initialPageParam } = options as {
|
||||
input: (pageParam: number) => {
|
||||
query?: { keyword?: string; limit?: number; page?: number }
|
||||
}
|
||||
@@ -732,7 +751,6 @@ describe('AgentSkills', () => {
|
||||
}
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-skills', input(initialPageParam)],
|
||||
queryFn: async ({ pageParam = initialPageParam }: { pageParam?: number }) => ({
|
||||
data: [
|
||||
{
|
||||
@@ -821,11 +839,8 @@ describe('AgentSkills', () => {
|
||||
let resolveBindings:
|
||||
| ((value: { agent_id: string; skill_ids: string[]; data: never[] }) => void)
|
||||
| undefined
|
||||
mocks.agentSkillBindingsQueryOptions.mockImplementation((options) => {
|
||||
const { input } = options as { input: { params: { agent_id: string } } }
|
||||
|
||||
mocks.agentSkillBindingsQueryOptions.mockImplementation(() => {
|
||||
return {
|
||||
queryKey: ['workspace-agent-skills', input],
|
||||
queryFn: () =>
|
||||
new Promise<{ agent_id: string; skill_ids: string[]; data: never[] }>((resolve) => {
|
||||
resolveBindings = resolve
|
||||
@@ -840,7 +855,6 @@ describe('AgentSkills', () => {
|
||||
}
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-skills', input(initialPageParam)],
|
||||
queryFn: async ({ pageParam = initialPageParam }: { pageParam?: number }) => ({
|
||||
data: [createWorkspaceSkill()],
|
||||
has_more: false,
|
||||
@@ -900,7 +914,6 @@ describe('AgentSkills', () => {
|
||||
}
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-skills', input(initialPageParam)],
|
||||
queryFn: async ({ pageParam = initialPageParam }: { pageParam?: number }) => ({
|
||||
data: [createWorkspaceSkill()],
|
||||
has_more: false,
|
||||
@@ -937,7 +950,6 @@ describe('AgentSkills', () => {
|
||||
const { input } = options as { input: { params: { agent_id: string } } }
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-agent-skills', input],
|
||||
queryFn: async () => ({
|
||||
agent_id: input.params.agent_id,
|
||||
skill_ids: boundSkills.map((skill) => skill.id),
|
||||
@@ -959,7 +971,6 @@ describe('AgentSkills', () => {
|
||||
}
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-skills', input(initialPageParam)],
|
||||
queryFn: async ({ pageParam = initialPageParam }: { pageParam?: number }) => ({
|
||||
data: [createWorkspaceSkill({ id: 'candidate-skill' })],
|
||||
has_more: false,
|
||||
@@ -1004,7 +1015,6 @@ describe('AgentSkills', () => {
|
||||
}
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-skills', input(initialPageParam)],
|
||||
queryFn: async ({ pageParam = initialPageParam }: { pageParam?: number }) => ({
|
||||
data: [createWorkspaceSkill()],
|
||||
has_more: false,
|
||||
@@ -1027,7 +1037,7 @@ describe('AgentSkills', () => {
|
||||
it('should allow workflow agent nodes to bind workspace skills', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.workspaceSkillsInfiniteOptions.mockImplementation((options) => {
|
||||
const { input, getNextPageParam, initialPageParam } = options as {
|
||||
const { getNextPageParam, initialPageParam } = options as {
|
||||
input: (pageParam: number) => {
|
||||
query?: { keyword?: string; limit?: number; page?: number }
|
||||
}
|
||||
@@ -1036,7 +1046,6 @@ describe('AgentSkills', () => {
|
||||
}
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-skills', input(initialPageParam)],
|
||||
queryFn: async ({ pageParam = initialPageParam }: { pageParam?: number }) => ({
|
||||
data: [
|
||||
{
|
||||
@@ -1106,7 +1115,6 @@ describe('AgentSkills', () => {
|
||||
const { input } = options as { input: { params: { agent_id: string } } }
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-agent-skills', input],
|
||||
queryFn: async () => ({
|
||||
agent_id: input.params.agent_id,
|
||||
skill_ids: ['workspace-skill-1'],
|
||||
@@ -1123,7 +1131,7 @@ describe('AgentSkills', () => {
|
||||
}
|
||||
})
|
||||
mocks.workspaceSkillsInfiniteOptions.mockImplementation((options) => {
|
||||
const { input, getNextPageParam, initialPageParam } = options as {
|
||||
const { getNextPageParam, initialPageParam } = options as {
|
||||
input: (pageParam: number) => {
|
||||
query?: { keyword?: string; limit?: number; page?: number }
|
||||
}
|
||||
@@ -1132,7 +1140,6 @@ describe('AgentSkills', () => {
|
||||
}
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-skills', input(initialPageParam)],
|
||||
queryFn: async ({ pageParam = initialPageParam }: { pageParam?: number }) => ({
|
||||
data: [
|
||||
createWorkspaceSkill(),
|
||||
@@ -1177,7 +1184,7 @@ describe('AgentSkills', () => {
|
||||
it('should fetch the next workspace skill page when scrolling the selector', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.workspaceSkillsInfiniteOptions.mockImplementation((options) => {
|
||||
const { input, getNextPageParam, initialPageParam } = options as {
|
||||
const { getNextPageParam, initialPageParam } = options as {
|
||||
input: (pageParam: number) => {
|
||||
query?: { keyword?: string; limit?: number; page?: number }
|
||||
}
|
||||
@@ -1186,7 +1193,6 @@ describe('AgentSkills', () => {
|
||||
}
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-skills', input(initialPageParam)],
|
||||
queryFn: async ({ pageParam = initialPageParam }: { pageParam?: number }) => ({
|
||||
data:
|
||||
pageParam === 1
|
||||
@@ -1232,7 +1238,6 @@ describe('AgentSkills', () => {
|
||||
const { input } = options as { input: { params: { agent_id: string } } }
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-agent-skills', input],
|
||||
queryFn: async () => ({
|
||||
agent_id: input.params.agent_id,
|
||||
skill_ids: ['workspace-skill-1'],
|
||||
@@ -1285,7 +1290,6 @@ describe('AgentSkills', () => {
|
||||
const { input } = options as { input: { params: { agent_id: string } } }
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-agent-skills', input],
|
||||
queryFn: async () => ({
|
||||
agent_id: input.params.agent_id,
|
||||
skill_ids: ['workspace-skill-1'],
|
||||
@@ -1463,13 +1467,12 @@ describe('AgentSkills', () => {
|
||||
const user = userEvent.setup()
|
||||
const { queryClient } = renderAgentSkills()
|
||||
queryClient.setQueryData(
|
||||
[
|
||||
'download-skill',
|
||||
{
|
||||
consoleQuery.agent.byAgentId.config.skills.byName.download.get.queryKey({
|
||||
input: {
|
||||
params: { agent_id: 'agent-1', name: 'Tender Analyzer' },
|
||||
query: { draft_type: 'draft', version_id: undefined },
|
||||
},
|
||||
],
|
||||
}),
|
||||
{ url: 'https://example.com/stale.skill' },
|
||||
)
|
||||
|
||||
@@ -1662,7 +1665,7 @@ describe('AgentSkills', () => {
|
||||
},
|
||||
query: {
|
||||
path: 'references/guide.md',
|
||||
draft_type: 'draft',
|
||||
draft_type: 'draft' as const,
|
||||
version_id: undefined,
|
||||
},
|
||||
}
|
||||
@@ -1670,7 +1673,12 @@ describe('AgentSkills', () => {
|
||||
const currentContentUrl =
|
||||
'/console/api/agent/agent-1/config/skills/Tender%20Analyzer/files/content?path=references%2Fguide.md'
|
||||
const staleBlob = new Blob(['stale skill file'])
|
||||
queryClient.setQueryData(['download-skill-file', downloadInput], { url: staleContentUrl })
|
||||
queryClient.setQueryData(
|
||||
consoleQuery.agent.byAgentId.config.skills.byName.files.download.get.queryKey({
|
||||
input: downloadInput,
|
||||
}),
|
||||
{ url: staleContentUrl },
|
||||
)
|
||||
queryClient.setQueryData(['agent-v2', 'skill-file-content', staleContentUrl], staleBlob)
|
||||
queryClient.setQueryData(['agent-v2', 'skill-file-content', currentContentUrl], staleBlob)
|
||||
mocks.fetch.mockResolvedValueOnce(
|
||||
@@ -1816,6 +1824,21 @@ describe('AgentSkills', () => {
|
||||
})
|
||||
})
|
||||
|
||||
const trialSkill = consoleQuery.trialApps.byAppId.agent.config.skills.byName
|
||||
for (const operation of [
|
||||
trialSkill.inspect.get,
|
||||
trialSkill.files.preview.get,
|
||||
trialSkill.files.download.get,
|
||||
]) {
|
||||
expect(
|
||||
view.queryClient.getQueryState(operation.queryKey({ input: skipToken })),
|
||||
).toMatchObject({
|
||||
status: 'pending',
|
||||
fetchStatus: 'idle',
|
||||
error: null,
|
||||
})
|
||||
}
|
||||
|
||||
view.unmount()
|
||||
expect(revokeObjectURL).toHaveBeenCalledWith('blob:skill-image')
|
||||
})
|
||||
@@ -1854,7 +1877,6 @@ describe('AgentSkills', () => {
|
||||
const { input } = options as { input: { params: { agent_id: string } } }
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-agent-skills', input],
|
||||
queryFn: async () => ({
|
||||
agent_id: input.params.agent_id,
|
||||
skill_ids: ['workspace-skill-1'],
|
||||
|
||||
+28
-38
@@ -217,17 +217,15 @@ export function useAgentSkillDetail({
|
||||
enabled: isOpen && !!apiContext.workflow,
|
||||
})
|
||||
const trialSkillInspectQuery = useQuery(
|
||||
apiContext.trialAppId
|
||||
? {
|
||||
...consoleQuery.trialApps.byAppId.agent.config.skills.byName.inspect.get.queryOptions({
|
||||
input: {
|
||||
params: { app_id: apiContext.trialAppId, name: skill.name },
|
||||
query: { version_id: apiContext.versionId },
|
||||
},
|
||||
}),
|
||||
enabled: isOpen,
|
||||
}
|
||||
: { queryKey: ['agent-v2', 'trial-skill-inspect-disabled'], queryFn: skipToken },
|
||||
consoleQuery.trialApps.byAppId.agent.config.skills.byName.inspect.get.queryOptions({
|
||||
input: apiContext.trialAppId
|
||||
? {
|
||||
params: { app_id: apiContext.trialAppId, name: skill.name },
|
||||
query: { version_id: apiContext.versionId },
|
||||
}
|
||||
: skipToken,
|
||||
enabled: !!apiContext.trialAppId && isOpen,
|
||||
}),
|
||||
)
|
||||
const inspectQuery = apiContext.trialAppId
|
||||
? trialSkillInspectQuery
|
||||
@@ -284,19 +282,15 @@ export function useAgentSkillDetail({
|
||||
enabled: isOpen && !!selectedPreviewPath && !!apiContext.workflow,
|
||||
})
|
||||
const trialPreviewQuery = useQuery(
|
||||
apiContext.trialAppId
|
||||
? {
|
||||
...consoleQuery.trialApps.byAppId.agent.config.skills.byName.files.preview.get.queryOptions(
|
||||
{
|
||||
input: {
|
||||
params: { app_id: apiContext.trialAppId, name: skill.name },
|
||||
query: { path: selectedPreviewPath ?? '', version_id: apiContext.versionId },
|
||||
},
|
||||
},
|
||||
),
|
||||
enabled: isOpen && !!selectedPreviewPath,
|
||||
}
|
||||
: { queryKey: ['agent-v2', 'trial-skill-preview-disabled'], queryFn: skipToken },
|
||||
consoleQuery.trialApps.byAppId.agent.config.skills.byName.files.preview.get.queryOptions({
|
||||
input: apiContext.trialAppId
|
||||
? {
|
||||
params: { app_id: apiContext.trialAppId, name: skill.name },
|
||||
query: { path: selectedPreviewPath ?? '', version_id: apiContext.versionId },
|
||||
}
|
||||
: skipToken,
|
||||
enabled: !!apiContext.trialAppId && isOpen && !!selectedPreviewPath,
|
||||
}),
|
||||
)
|
||||
const previewQuery = apiContext.trialAppId
|
||||
? trialPreviewQuery
|
||||
@@ -341,20 +335,16 @@ export function useAgentSkillDetail({
|
||||
enabled: shouldLoadImagePreview && !!apiContext.workflow,
|
||||
})
|
||||
const trialDownloadQuery = useQuery(
|
||||
apiContext.trialAppId
|
||||
? {
|
||||
...consoleQuery.trialApps.byAppId.agent.config.skills.byName.files.download.get.queryOptions(
|
||||
{
|
||||
input: {
|
||||
params: { app_id: apiContext.trialAppId, name: skill.name },
|
||||
query: { path: selectedPreviewPath ?? '', version_id: apiContext.versionId },
|
||||
},
|
||||
},
|
||||
),
|
||||
staleTime: 0,
|
||||
enabled: shouldLoadImagePreview,
|
||||
}
|
||||
: { queryKey: ['agent-v2', 'trial-skill-download-disabled'], queryFn: skipToken },
|
||||
consoleQuery.trialApps.byAppId.agent.config.skills.byName.files.download.get.queryOptions({
|
||||
input: apiContext.trialAppId
|
||||
? {
|
||||
params: { app_id: apiContext.trialAppId, name: skill.name },
|
||||
query: { path: selectedPreviewPath ?? '', version_id: apiContext.versionId },
|
||||
}
|
||||
: skipToken,
|
||||
enabled: !!apiContext.trialAppId && shouldLoadImagePreview,
|
||||
staleTime: 0,
|
||||
}),
|
||||
)
|
||||
const downloadQuery = apiContext.trialAppId
|
||||
? trialDownloadQuery
|
||||
|
||||
+77
-66
@@ -86,29 +86,44 @@ const previewSourceCases = [
|
||||
},
|
||||
] as const
|
||||
|
||||
vi.mock('@/service/console', () => ({
|
||||
consoleClient: {
|
||||
agent: {
|
||||
byAgentId: {
|
||||
sandbox: {
|
||||
files: {
|
||||
download: {
|
||||
post: mocks.sandboxFileDownloadClientPost,
|
||||
vi.mock('@/service/console', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/service/console')>()
|
||||
const { withConsoleQueryOverrides } = await import('@/test/console/service-responses')
|
||||
const query = withConsoleQueryOverrides(actual.consoleQuery, {
|
||||
'agent.byAgentId.sandbox.get': { queryOptions: mocks.sandboxInfoQueryOptions },
|
||||
'agent.byAgentId.sandbox.files.get': { queryOptions: mocks.sandboxFilesQueryOptions },
|
||||
'agent.byAgentId.sandbox.files.read.get': { queryOptions: mocks.sandboxFileReadQueryOptions },
|
||||
'apps.byAppId.workflowRuns.byWorkflowRunId.agentNodes.byNodeId.sandbox.files.get': {
|
||||
queryOptions: mocks.workflowSandboxFilesQueryOptions,
|
||||
},
|
||||
'apps.byAppId.workflowRuns.byWorkflowRunId.agentNodes.byNodeId.sandbox.files.read.get': {
|
||||
queryOptions: mocks.workflowSandboxFileReadQueryOptions,
|
||||
},
|
||||
})
|
||||
return {
|
||||
consoleClient: {
|
||||
agent: {
|
||||
byAgentId: {
|
||||
sandbox: {
|
||||
files: {
|
||||
download: {
|
||||
post: mocks.sandboxFileDownloadClientPost,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
apps: {
|
||||
byAppId: {
|
||||
workflowRuns: {
|
||||
byWorkflowRunId: {
|
||||
agentNodes: {
|
||||
byNodeId: {
|
||||
sandbox: {
|
||||
files: {
|
||||
download: {
|
||||
post: mocks.workflowSandboxFileDownloadClientPost,
|
||||
apps: {
|
||||
byAppId: {
|
||||
workflowRuns: {
|
||||
byWorkflowRunId: {
|
||||
agentNodes: {
|
||||
byNodeId: {
|
||||
sandbox: {
|
||||
files: {
|
||||
download: {
|
||||
post: mocks.workflowSandboxFileDownloadClientPost,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -118,53 +133,57 @@ vi.mock('@/service/console', () => ({
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
consoleQuery: {
|
||||
agent: {
|
||||
byAgentId: {
|
||||
sandbox: {
|
||||
get: {
|
||||
queryOptions: mocks.sandboxInfoQueryOptions,
|
||||
},
|
||||
files: {
|
||||
consoleQuery: {
|
||||
agent: {
|
||||
byAgentId: {
|
||||
sandbox: {
|
||||
get: {
|
||||
queryOptions: mocks.sandboxFilesQueryOptions,
|
||||
queryOptions: query.agent.byAgentId.sandbox.get.queryOptions,
|
||||
},
|
||||
read: {
|
||||
files: {
|
||||
get: {
|
||||
queryOptions: mocks.sandboxFileReadQueryOptions,
|
||||
queryOptions: query.agent.byAgentId.sandbox.files.get.queryOptions,
|
||||
},
|
||||
},
|
||||
download: {
|
||||
post: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.sandboxFileDownloadMutationFn }),
|
||||
read: {
|
||||
get: {
|
||||
queryOptions: query.agent.byAgentId.sandbox.files.read.get.queryOptions,
|
||||
},
|
||||
},
|
||||
download: {
|
||||
post: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.sandboxFileDownloadMutationFn }),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
apps: {
|
||||
byAppId: {
|
||||
workflowRuns: {
|
||||
byWorkflowRunId: {
|
||||
agentNodes: {
|
||||
byNodeId: {
|
||||
sandbox: {
|
||||
files: {
|
||||
get: {
|
||||
queryOptions: mocks.workflowSandboxFilesQueryOptions,
|
||||
},
|
||||
read: {
|
||||
apps: {
|
||||
byAppId: {
|
||||
workflowRuns: {
|
||||
byWorkflowRunId: {
|
||||
agentNodes: {
|
||||
byNodeId: {
|
||||
sandbox: {
|
||||
files: {
|
||||
get: {
|
||||
queryOptions: mocks.workflowSandboxFileReadQueryOptions,
|
||||
queryOptions:
|
||||
query.apps.byAppId.workflowRuns.byWorkflowRunId.agentNodes.byNodeId
|
||||
.sandbox.files.get.queryOptions,
|
||||
},
|
||||
},
|
||||
download: {
|
||||
post: {
|
||||
mutationOptions: () => ({
|
||||
mutationFn: mocks.workflowSandboxFileDownloadMutationFn,
|
||||
}),
|
||||
read: {
|
||||
get: {
|
||||
queryOptions:
|
||||
query.apps.byAppId.workflowRuns.byWorkflowRunId.agentNodes.byNodeId
|
||||
.sandbox.files.read.get.queryOptions,
|
||||
},
|
||||
},
|
||||
download: {
|
||||
post: {
|
||||
mutationOptions: () => ({
|
||||
mutationFn: mocks.workflowSandboxFileDownloadMutationFn,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -175,8 +194,8 @@ vi.mock('@/service/console', () => ({
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/utils/download', () => ({
|
||||
downloadUrl: mocks.downloadUrl,
|
||||
@@ -195,6 +214,7 @@ function createDeferred<T>() {
|
||||
resolve = promiseResolve
|
||||
reject = promiseReject
|
||||
})
|
||||
|
||||
return { promise, reject, resolve }
|
||||
}
|
||||
|
||||
@@ -247,7 +267,6 @@ function mockFileListEntries(
|
||||
? mocks.sandboxFilesQueryOptions
|
||||
: mocks.workflowSandboxFilesQueryOptions
|
||||
queryOptions.mockImplementation(({ input }: QueryOptionsInput) => ({
|
||||
queryKey: [`${source.type}-sandbox-files`, input, entries],
|
||||
queryFn: async () => ({
|
||||
path: input.query?.path ?? '~',
|
||||
entries,
|
||||
@@ -261,7 +280,6 @@ function mockFileReadAsBinary(source: AgentWorkingDirectorySource) {
|
||||
? mocks.sandboxFileReadQueryOptions
|
||||
: mocks.workflowSandboxFileReadQueryOptions
|
||||
queryOptions.mockImplementation(({ input }: QueryOptionsInput) => ({
|
||||
queryKey: [`${source.type}-sandbox-file-read`, input],
|
||||
queryFn: async () => ({
|
||||
binary: true,
|
||||
path: input.query?.path ?? '',
|
||||
@@ -304,13 +322,11 @@ describe('AgentWorkingDirectoryPanel', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mocks.sandboxInfoQueryOptions.mockImplementation(() => ({
|
||||
queryKey: ['sandbox-info'],
|
||||
queryFn: async () => ({
|
||||
workspace_cwd: 'workspace',
|
||||
}),
|
||||
}))
|
||||
mocks.sandboxFilesQueryOptions.mockImplementation(({ input }: QueryOptionsInput) => ({
|
||||
queryKey: ['sandbox-files', input],
|
||||
queryFn: async () => ({
|
||||
path: input.query?.path ?? '~',
|
||||
entries: [
|
||||
@@ -322,7 +338,6 @@ describe('AgentWorkingDirectoryPanel', () => {
|
||||
}),
|
||||
}))
|
||||
mocks.sandboxFileReadQueryOptions.mockImplementation(({ input }: QueryOptionsInput) => ({
|
||||
queryKey: ['sandbox-file-read', input],
|
||||
queryFn: async () => ({
|
||||
binary: input.query?.path?.endsWith('model.bin') ?? false,
|
||||
path: input.query?.path ?? '',
|
||||
@@ -331,7 +346,6 @@ describe('AgentWorkingDirectoryPanel', () => {
|
||||
}),
|
||||
}))
|
||||
mocks.workflowSandboxFilesQueryOptions.mockImplementation(({ input }: QueryOptionsInput) => ({
|
||||
queryKey: ['workflow-sandbox-files', input],
|
||||
queryFn: async () => ({
|
||||
path: input.query?.path ?? '.',
|
||||
entries: [{ name: 'chart.png', type: 'file' }],
|
||||
@@ -339,7 +353,6 @@ describe('AgentWorkingDirectoryPanel', () => {
|
||||
}))
|
||||
mocks.workflowSandboxFileReadQueryOptions.mockImplementation(
|
||||
({ input }: QueryOptionsInput) => ({
|
||||
queryKey: ['workflow-sandbox-file-read', input],
|
||||
queryFn: async () => ({
|
||||
binary: false,
|
||||
path: input.query?.path ?? '',
|
||||
@@ -355,7 +368,6 @@ describe('AgentWorkingDirectoryPanel', () => {
|
||||
mocks.sandboxFilesQueryOptions.mockImplementation(({ input }: QueryOptionsInput) => {
|
||||
const path = input.query?.path ?? '~'
|
||||
return {
|
||||
queryKey: ['sandbox-files-by-folder', path],
|
||||
queryFn: async () => ({
|
||||
path,
|
||||
entries:
|
||||
@@ -393,7 +405,6 @@ describe('AgentWorkingDirectoryPanel', () => {
|
||||
it('should separate persistent and temporary files by their sandbox path roots', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.sandboxFilesQueryOptions.mockImplementation(({ input }: QueryOptionsInput) => ({
|
||||
queryKey: ['sandbox-files-by-root', input],
|
||||
queryFn: async () => ({
|
||||
path: input.query?.path ?? '~',
|
||||
entries:
|
||||
|
||||
@@ -326,9 +326,8 @@ describe('SkillDetailPage clipboard', () => {
|
||||
})
|
||||
return mocks.skillDetail
|
||||
})
|
||||
mocks.skillDetailKey.mockReturnValue(['skill-detail'])
|
||||
|
||||
mocks.skillDetailQueryOptions.mockImplementation(() => ({
|
||||
queryKey: ['skill-detail'],
|
||||
queryFn: async () => mocks.skillDetail,
|
||||
}))
|
||||
renderSkillDetailPage()
|
||||
@@ -390,9 +389,8 @@ describe('SkillDetailPage clipboard', () => {
|
||||
return copiedDetail
|
||||
})
|
||||
mocks.skillDetailGetFn.mockResolvedValueOnce(latestDetail)
|
||||
mocks.skillDetailKey.mockReturnValue(['skill-detail'])
|
||||
|
||||
mocks.skillDetailQueryOptions.mockImplementation(() => ({
|
||||
queryKey: ['skill-detail'],
|
||||
queryFn: async () => mocks.skillDetail,
|
||||
}))
|
||||
renderSkillDetailPage()
|
||||
@@ -472,9 +470,8 @@ describe('SkillDetailPage clipboard', () => {
|
||||
.mockRejectedValueOnce(conflict)
|
||||
.mockResolvedValueOnce(afterBetaCopy)
|
||||
mocks.skillDetailGetFn.mockResolvedValueOnce(refreshedDetail)
|
||||
mocks.skillDetailKey.mockReturnValue(['skill-detail'])
|
||||
|
||||
mocks.skillDetailQueryOptions.mockImplementation(() => ({
|
||||
queryKey: ['skill-detail'],
|
||||
queryFn: async () => mocks.skillDetail,
|
||||
}))
|
||||
renderSkillDetailPage()
|
||||
|
||||
@@ -152,9 +152,8 @@ describe('SkillDetailPage metadata', () => {
|
||||
it('keeps selected tags visible and prevents reopening while the metadata request is pending', async () => {
|
||||
const user = userEvent.setup()
|
||||
let resolveMutation!: (detail: SkillDetailResponse) => void
|
||||
mocks.skillDetailKey.mockReturnValue(['skill-detail'])
|
||||
|
||||
mocks.skillDetailQueryOptions.mockImplementation(() => ({
|
||||
queryKey: ['skill-detail'],
|
||||
queryFn: async () => mocks.skillDetail,
|
||||
}))
|
||||
mocks.skillMetadataMutationFn.mockImplementation(
|
||||
@@ -207,9 +206,8 @@ describe('SkillDetailPage metadata', () => {
|
||||
it('restores persisted tags when saving the visible selection fails', async () => {
|
||||
const user = userEvent.setup()
|
||||
let rejectMutation!: (error: Error) => void
|
||||
mocks.skillDetailKey.mockReturnValue(['skill-detail'])
|
||||
|
||||
mocks.skillDetailQueryOptions.mockImplementation(() => ({
|
||||
queryKey: ['skill-detail'],
|
||||
queryFn: async () => mocks.skillDetail,
|
||||
}))
|
||||
mocks.skillDetail = createSkillDetail({ tags: ['Search'] })
|
||||
@@ -275,9 +273,8 @@ describe('SkillDetailPage metadata', () => {
|
||||
it('hides a removed tag only after the metadata request finishes', async () => {
|
||||
const user = userEvent.setup()
|
||||
let resolveMutation!: (detail: SkillDetailResponse) => void
|
||||
mocks.skillDetailKey.mockReturnValue(['skill-detail'])
|
||||
|
||||
mocks.skillDetailQueryOptions.mockImplementation(() => ({
|
||||
queryKey: ['skill-detail'],
|
||||
queryFn: async () => mocks.skillDetail,
|
||||
}))
|
||||
mocks.skillDetail = createSkillDetail({
|
||||
@@ -457,8 +454,7 @@ describe('SkillDetailPage metadata', () => {
|
||||
const detailPromise = new Promise<SkillDetailResponse>((resolve) => {
|
||||
resolveDetail = resolve
|
||||
})
|
||||
mocks.skillDetailQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-detail', options],
|
||||
mocks.skillDetailQueryOptions.mockImplementation(() => ({
|
||||
queryFn: () => detailPromise,
|
||||
}))
|
||||
const { onUrlUpdate, queryClient } = renderSkillDetailPage({
|
||||
@@ -521,8 +517,7 @@ describe('SkillDetailPage metadata', () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.skillDetail = createSkillDetail({ reference_count: 1 })
|
||||
preserveDraftFilesOnSave()
|
||||
mocks.skillReferencesQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-references', options],
|
||||
mocks.skillReferencesQueryOptions.mockImplementation(() => ({
|
||||
queryFn: async () => ({ data: [createAgentReference()] }),
|
||||
}))
|
||||
mocks.deleteSkillMutationFn.mockResolvedValue({})
|
||||
@@ -568,8 +563,7 @@ describe('SkillDetailPage metadata', () => {
|
||||
let referenceRequestCount = 0
|
||||
let shouldHangReferenceRequest = false
|
||||
mocks.skillDetail = createSkillDetail({ reference_count: 0 })
|
||||
mocks.skillReferencesQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-references-pending', options],
|
||||
mocks.skillReferencesQueryOptions.mockImplementation(() => ({
|
||||
queryFn: () => {
|
||||
referenceRequestCount += 1
|
||||
if (!shouldHangReferenceRequest) return Promise.resolve({ data: [] })
|
||||
@@ -611,8 +605,7 @@ describe('SkillDetailPage metadata', () => {
|
||||
})
|
||||
|
||||
it('does not expose mutable More actions while viewing a published version', async () => {
|
||||
mocks.skillVersionsQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-versions', options],
|
||||
mocks.skillVersionsQueryOptions.mockImplementation(() => ({
|
||||
queryFn: async () => ({ data: [createSkillVersion()] }),
|
||||
}))
|
||||
renderSkillDetailPage()
|
||||
|
||||
@@ -84,12 +84,10 @@ describe('SkillDetailPage navigation', () => {
|
||||
const { default: SkillDetailPageRoute } = await import('../detail-page')
|
||||
const user = userEvent.setup()
|
||||
const version = createSkillVersion({ id: 'original-version', version_name: 'Original version' })
|
||||
mocks.skillVersionsQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-versions', options],
|
||||
mocks.skillVersionsQueryOptions.mockImplementation(() => ({
|
||||
queryFn: async () => ({ data: [version] }),
|
||||
}))
|
||||
mocks.skillVersionDetailQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-version-detail', options],
|
||||
mocks.skillVersionDetailQueryOptions.mockImplementation(() => ({
|
||||
queryFn: async () => ({
|
||||
...version,
|
||||
files: createSkillDetail().files,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { QueryClient } from '@tanstack/react-query'
|
||||
import { act, screen, waitFor, within } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
|
||||
import { consoleQueryKey } from '@/test/console/operation-hooks'
|
||||
import {
|
||||
createAgentReference,
|
||||
createSkillDetail,
|
||||
@@ -52,8 +53,7 @@ describe('SkillDetailPage publishing', () => {
|
||||
it('shows a publish confirmation for referenced skills before publishing updates', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.skillDetail = createSkillDetail({ reference_count: 1 })
|
||||
mocks.skillReferencesQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-references', options],
|
||||
mocks.skillReferencesQueryOptions.mockImplementation(() => ({
|
||||
queryFn: async () => ({
|
||||
data: [createAgentReference()],
|
||||
}),
|
||||
@@ -140,19 +140,18 @@ describe('SkillDetailPage publishing', () => {
|
||||
mutations: { retry: false },
|
||||
},
|
||||
})
|
||||
const referencesQueryKey = [
|
||||
'skill-references',
|
||||
const referencesQueryKey = consoleQueryKey(
|
||||
'workspaces.current.skills.bySkillId.references.get',
|
||||
{
|
||||
params: {
|
||||
skill_id: 'skill-1',
|
||||
},
|
||||
},
|
||||
]
|
||||
)
|
||||
let remoteReferences: SkillReferenceResponse[] = []
|
||||
const referencesQueryFn = vi.fn(async () => ({ data: remoteReferences }))
|
||||
mocks.skillDetail = createSkillDetail({ reference_count: 0 })
|
||||
mocks.skillReferencesQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-references', (options as { input: unknown }).input],
|
||||
mocks.skillReferencesQueryOptions.mockImplementation(() => ({
|
||||
queryFn: referencesQueryFn,
|
||||
}))
|
||||
queryClient.setQueryData(referencesQueryKey, { data: [] })
|
||||
@@ -204,8 +203,7 @@ describe('SkillDetailPage publishing', () => {
|
||||
return { data: [] }
|
||||
})
|
||||
mocks.skillDetail = createSkillDetail({ reference_count: 0 })
|
||||
mocks.skillReferencesQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-references', (options as { input: unknown }).input],
|
||||
mocks.skillReferencesQueryOptions.mockImplementation(() => ({
|
||||
queryFn: referencesQueryFn,
|
||||
}))
|
||||
|
||||
@@ -241,8 +239,7 @@ describe('SkillDetailPage publishing', () => {
|
||||
it('cancels publishing from the referenced skill confirmation dialog', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.skillDetail = createSkillDetail({ reference_count: 1 })
|
||||
mocks.skillReferencesQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-references', options],
|
||||
mocks.skillReferencesQueryOptions.mockImplementation(() => ({
|
||||
queryFn: async () => ({
|
||||
data: [createAgentReference()],
|
||||
}),
|
||||
@@ -274,8 +271,7 @@ describe('SkillDetailPage publishing', () => {
|
||||
it('opens the sidebar references panel from the file tree footer', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.skillDetail = createSkillDetail({ reference_count: 1 })
|
||||
mocks.skillReferencesQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-references', options],
|
||||
mocks.skillReferencesQueryOptions.mockImplementation(() => ({
|
||||
queryFn: async () => ({
|
||||
data: [createAgentReference({ display_name: 'Sidebar Agent', name: 'sidebar-agent' })],
|
||||
}),
|
||||
@@ -301,8 +297,7 @@ describe('SkillDetailPage publishing', () => {
|
||||
|
||||
it('renders a non-interactive reference count when no agent uses the skill', async () => {
|
||||
mocks.skillDetail = createSkillDetail({ reference_count: 0 })
|
||||
mocks.skillReferencesQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-references', options],
|
||||
mocks.skillReferencesQueryOptions.mockImplementation(() => ({
|
||||
queryFn: async () => ({ data: [] }),
|
||||
}))
|
||||
|
||||
@@ -321,8 +316,7 @@ describe('SkillDetailPage publishing', () => {
|
||||
|
||||
it('uses the references query count when the cached sidebar reference count is stale', async () => {
|
||||
mocks.skillDetail = createSkillDetail({ reference_count: 0 })
|
||||
mocks.skillReferencesQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-references', options],
|
||||
mocks.skillReferencesQueryOptions.mockImplementation(() => ({
|
||||
queryFn: async () => ({
|
||||
data: [createAgentReference({ display_name: 'Sidebar Agent', name: 'sidebar-agent' })],
|
||||
}),
|
||||
@@ -348,8 +342,7 @@ describe('SkillDetailPage publishing', () => {
|
||||
}),
|
||||
)
|
||||
mocks.skillDetail = createSkillDetail({ reference_count: references.length })
|
||||
mocks.skillReferencesQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-references', options],
|
||||
mocks.skillReferencesQueryOptions.mockImplementation(() => ({
|
||||
queryFn: async () => ({ data: references }),
|
||||
}))
|
||||
|
||||
@@ -379,14 +372,12 @@ describe('SkillDetailPage publishing', () => {
|
||||
id: 'version-1',
|
||||
version_name: 'Rollback target',
|
||||
})
|
||||
mocks.skillVersionsQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-versions', options],
|
||||
mocks.skillVersionsQueryOptions.mockImplementation(() => ({
|
||||
queryFn: async () => ({
|
||||
data: [version],
|
||||
}),
|
||||
}))
|
||||
mocks.skillVersionDetailQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-version-detail', options],
|
||||
mocks.skillVersionDetailQueryOptions.mockImplementation(() => ({
|
||||
queryFn: async () => ({
|
||||
...version,
|
||||
files: [
|
||||
@@ -447,14 +438,12 @@ describe('SkillDetailPage publishing', () => {
|
||||
version_name: '',
|
||||
publish_note: 'Release note only',
|
||||
})
|
||||
mocks.skillVersionsQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-versions', options],
|
||||
mocks.skillVersionsQueryOptions.mockImplementation(() => ({
|
||||
queryFn: async () => ({
|
||||
data: [version],
|
||||
}),
|
||||
}))
|
||||
mocks.skillVersionDetailQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-version-detail', options],
|
||||
mocks.skillVersionDetailQueryOptions.mockImplementation(() => ({
|
||||
queryFn: async () => ({
|
||||
...version,
|
||||
files: [
|
||||
@@ -511,8 +500,7 @@ describe('SkillDetailPage publishing', () => {
|
||||
version_number: 2,
|
||||
version_name: '',
|
||||
})
|
||||
mocks.skillVersionsQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-versions', options],
|
||||
mocks.skillVersionsQueryOptions.mockImplementation(() => ({
|
||||
queryFn: async () => ({
|
||||
data: [namedVersion, unnamedVersion],
|
||||
}),
|
||||
@@ -553,8 +541,7 @@ describe('SkillDetailPage publishing', () => {
|
||||
publish_note: 'Initial note',
|
||||
version_name: 'Initial version',
|
||||
})
|
||||
mocks.skillVersionsQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-versions', options],
|
||||
mocks.skillVersionsQueryOptions.mockImplementation(() => ({
|
||||
queryFn: async () => ({
|
||||
data: [version],
|
||||
}),
|
||||
@@ -606,11 +593,9 @@ describe('SkillDetailPage publishing', () => {
|
||||
: 'skill.skillManagement.detail.restoreVersion'
|
||||
const version = createSkillVersion({ version_name: 'Target version' })
|
||||
mocks.skillVersionsQueryOptions.mockImplementation(() => ({
|
||||
queryKey: ['skill-versions'],
|
||||
queryFn: async () => ({ data: [version] }),
|
||||
}))
|
||||
mocks.skillVersionDetailQueryOptions.mockImplementation(() => ({
|
||||
queryKey: ['skill-version-detail'],
|
||||
queryFn: async () => ({ ...version, files: createSkillDetail().files }),
|
||||
}))
|
||||
renderSkillDetailPage()
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import type {
|
||||
GetWorkspacesCurrentModelsModelTypesByModelTypeData,
|
||||
SkillDetailResponse,
|
||||
SkillReferenceResponse,
|
||||
SkillVersionResponse,
|
||||
} from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { OperationKey } from '@orpc/tanstack-query'
|
||||
import type userEvent from '@testing-library/user-event'
|
||||
import type { ReactNode } from 'react'
|
||||
import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
@@ -41,18 +39,11 @@ const mocks = vi.hoisted(() => ({
|
||||
| undefined,
|
||||
skillDetail: undefined as SkillDetailResponse | undefined,
|
||||
skillDetailGetFn: vi.fn(),
|
||||
skillDetailKey: vi.fn((_options: { input?: unknown; type?: string }): unknown[] => [
|
||||
'skill-detail',
|
||||
]),
|
||||
skillDetailQueryOptions: vi.fn((_options: unknown) => ({})),
|
||||
agentSkillBindingsKey: vi.fn((_options: unknown): unknown[] => ['agent-skill-bindings']),
|
||||
skillListKey: vi.fn((_options: unknown): unknown[] => ['skills']),
|
||||
skillTags: [] as { count: number; tag: string }[],
|
||||
skillMetadataMutationFn: vi.fn(),
|
||||
skillReferencesQueryOptions: vi.fn((_options: unknown) => ({})),
|
||||
skillTagsKey: vi.fn((_options: unknown): unknown[] => ['skill-tags']),
|
||||
skillTagsQueryOptions: vi.fn((_options: unknown) => ({})),
|
||||
skillVersionsKey: vi.fn((_options: unknown): unknown[] => ['skill-versions']),
|
||||
skillVersionsQueryOptions: vi.fn((_options: unknown) => ({})),
|
||||
skillVersionDetailQueryOptions: vi.fn((_options: unknown) => ({})),
|
||||
textGenerationModelList: [] as {
|
||||
@@ -195,6 +186,26 @@ vi.mock('@/utils/download', () => ({
|
||||
|
||||
vi.mock('@/service/console', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/service/console')>()
|
||||
const { withConsoleQueryOverrides, withConsoleResponses } =
|
||||
await import('@/test/console/service-responses')
|
||||
const modelQuery = withConsoleResponses(actual.consoleQuery, {
|
||||
'workspaces.current.models.modelTypes.byModelType.get': () =>
|
||||
mocks.textGenerationModelListGet(),
|
||||
})
|
||||
const query = withConsoleQueryOverrides(actual.consoleQuery, {
|
||||
'workspaces.current.skills.tags.get': { queryOptions: mocks.skillTagsQueryOptions },
|
||||
'workspaces.current.skills.bySkillId.get': { queryOptions: mocks.skillDetailQueryOptions },
|
||||
'workspaces.current.skills.bySkillId.references.get': {
|
||||
queryOptions: mocks.skillReferencesQueryOptions,
|
||||
},
|
||||
'workspaces.current.skills.bySkillId.versions.get': {
|
||||
queryOptions: mocks.skillVersionsQueryOptions,
|
||||
},
|
||||
'workspaces.current.skills.bySkillId.versions.byVersionId.get': {
|
||||
queryOptions: mocks.skillVersionDetailQueryOptions,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
consoleClient: {
|
||||
workspaces: {
|
||||
@@ -210,25 +221,25 @@ vi.mock('@/service/console', async (importOriginal) => {
|
||||
consoleQuery: {
|
||||
workspaces: {
|
||||
current: {
|
||||
models: actual.consoleQuery.workspaces.current.models,
|
||||
models: modelQuery.workspaces.current.models,
|
||||
modelProviders: actual.consoleQuery.workspaces.current.modelProviders,
|
||||
agents: {
|
||||
byAgentId: {
|
||||
skills: {
|
||||
get: {
|
||||
key: mocks.agentSkillBindingsKey,
|
||||
key: actual.consoleQuery.workspaces.current.agents.byAgentId.skills.get.key,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
skills: {
|
||||
get: {
|
||||
key: mocks.skillListKey,
|
||||
key: actual.consoleQuery.workspaces.current.skills.get.key,
|
||||
},
|
||||
tags: {
|
||||
get: {
|
||||
key: mocks.skillTagsKey,
|
||||
queryOptions: mocks.skillTagsQueryOptions,
|
||||
key: actual.consoleQuery.workspaces.current.skills.tags.get.key,
|
||||
queryOptions: query.workspaces.current.skills.tags.get.queryOptions,
|
||||
},
|
||||
},
|
||||
bySkillId: {
|
||||
@@ -241,8 +252,8 @@ vi.mock('@/service/console', async (importOriginal) => {
|
||||
},
|
||||
},
|
||||
get: {
|
||||
key: mocks.skillDetailKey,
|
||||
queryOptions: mocks.skillDetailQueryOptions,
|
||||
key: actual.consoleQuery.workspaces.current.skills.bySkillId.get.key,
|
||||
queryOptions: query.workspaces.current.skills.bySkillId.get.queryOptions,
|
||||
},
|
||||
patch: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.skillMetadataMutationFn }),
|
||||
@@ -257,7 +268,8 @@ vi.mock('@/service/console', async (importOriginal) => {
|
||||
},
|
||||
references: {
|
||||
get: {
|
||||
queryOptions: mocks.skillReferencesQueryOptions,
|
||||
queryOptions:
|
||||
query.workspaces.current.skills.bySkillId.references.get.queryOptions,
|
||||
},
|
||||
},
|
||||
restore: actual.consoleQuery.workspaces.current.skills.bySkillId.restore,
|
||||
@@ -273,12 +285,14 @@ vi.mock('@/service/console', async (importOriginal) => {
|
||||
},
|
||||
versions: {
|
||||
get: {
|
||||
key: mocks.skillVersionsKey,
|
||||
queryOptions: mocks.skillVersionsQueryOptions,
|
||||
key: actual.consoleQuery.workspaces.current.skills.bySkillId.versions.get.key,
|
||||
queryOptions: query.workspaces.current.skills.bySkillId.versions.get.queryOptions,
|
||||
},
|
||||
byVersionId: {
|
||||
get: {
|
||||
queryOptions: mocks.skillVersionDetailQueryOptions,
|
||||
queryOptions:
|
||||
query.workspaces.current.skills.bySkillId.versions.byVersionId.get
|
||||
.queryOptions,
|
||||
},
|
||||
patch: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.versionPatchMutationFn }),
|
||||
@@ -723,47 +737,38 @@ export function resetDetailPageFixture() {
|
||||
}))
|
||||
mocks.skillDetail = createSkillDetail()
|
||||
mocks.skillDetailGetFn.mockImplementation(async () => mocks.skillDetail)
|
||||
mocks.skillDetailKey.mockImplementation(({ input }) => ['skill-detail', { input }])
|
||||
mocks.agentSkillBindingsKey.mockImplementation((options) => ['agent-skill-bindings', options])
|
||||
mocks.skillVersionsKey.mockImplementation((options) => ['skill-versions', options])
|
||||
mocks.skillListKey.mockImplementation((options) => ['skills', options])
|
||||
|
||||
mocks.skillTags = [
|
||||
{ count: 3, tag: 'Search' },
|
||||
{ count: 2, tag: 'Productivity' },
|
||||
{ count: 1, tag: 'Utilities' },
|
||||
]
|
||||
mocks.skillTagsKey.mockImplementation((options) => ['skill-tags', options])
|
||||
|
||||
mocks.skillTagsQueryOptions.mockImplementation(() => ({
|
||||
queryKey: ['skill-tags'],
|
||||
queryFn: async () => ({
|
||||
data: [],
|
||||
}),
|
||||
}))
|
||||
mocks.skillDetailQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-detail', options],
|
||||
mocks.skillDetailQueryOptions.mockImplementation(() => ({
|
||||
queryFn: async () => mocks.skillDetail,
|
||||
}))
|
||||
mocks.skillVersionsQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-versions', options],
|
||||
mocks.skillVersionsQueryOptions.mockImplementation(() => ({
|
||||
queryFn: async () => ({
|
||||
data: [],
|
||||
}),
|
||||
}))
|
||||
mocks.skillVersionDetailQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-version-detail', options],
|
||||
mocks.skillVersionDetailQueryOptions.mockImplementation(() => ({
|
||||
queryFn: async () => ({
|
||||
...mocks.skillDetail,
|
||||
files: [],
|
||||
}),
|
||||
}))
|
||||
mocks.skillReferencesQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-references', options],
|
||||
mocks.skillReferencesQueryOptions.mockImplementation(() => ({
|
||||
queryFn: async () => ({
|
||||
data: [],
|
||||
}),
|
||||
}))
|
||||
mocks.skillTagsQueryOptions.mockImplementation(() => ({
|
||||
queryKey: ['skill-tags'],
|
||||
queryFn: async () => ({
|
||||
data: mocks.skillTags,
|
||||
}),
|
||||
@@ -873,19 +878,3 @@ export function resetDetailPageFixture() {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...actual,
|
||||
useQuery: (options: {
|
||||
queryKey: OperationKey<
|
||||
'query',
|
||||
{ params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] }
|
||||
>
|
||||
}) =>
|
||||
options.queryKey[0].includes('modelTypes')
|
||||
? actual.useQuery({ ...options, queryFn: mocks.textGenerationModelListGet })
|
||||
: actual.useQuery(options),
|
||||
}
|
||||
})
|
||||
@@ -1,12 +1,12 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import { QueryClient } from '@tanstack/react-query'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
connection: vi.fn(async () => undefined),
|
||||
getQueryClient: vi.fn(),
|
||||
getSystemFeatures: vi.fn(),
|
||||
queryKey: [['console', 'systemFeatures', 'get'], { type: 'query' }] as const,
|
||||
}))
|
||||
|
||||
vi.mock('server-only', () => ({}))
|
||||
@@ -19,20 +19,16 @@ vi.mock('@/next/server', () => ({
|
||||
connection: mocks.connection,
|
||||
}))
|
||||
|
||||
vi.mock('@/service/console', () => ({
|
||||
consoleQuery: {
|
||||
systemFeatures: {
|
||||
get: {
|
||||
queryOptions: (options?: { staleTime?: 'static' }) => ({
|
||||
queryKey: mocks.queryKey,
|
||||
queryFn: mocks.getSystemFeatures,
|
||||
retry: false,
|
||||
...options,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
vi.mock('@/service/console', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/service/console')>()
|
||||
const { withConsoleResponses } = await import('@/test/console/service-responses')
|
||||
return {
|
||||
...actual,
|
||||
consoleQuery: withConsoleResponses(actual.consoleQuery, {
|
||||
'systemFeatures.get': () => mocks.getSystemFeatures(),
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
describe('System Features server requests', () => {
|
||||
beforeEach(() => {
|
||||
@@ -54,7 +50,7 @@ describe('System Features server requests', () => {
|
||||
expect(mocks.getSystemFeatures).toHaveBeenCalledOnce()
|
||||
expect(dehydrateSystemFeatures().queries).toEqual([
|
||||
expect.objectContaining({
|
||||
queryKey: mocks.queryKey,
|
||||
queryKey: consoleQuery.systemFeatures.get.queryKey(),
|
||||
state: expect.objectContaining({
|
||||
data: { deployment_edition: 'CLOUD' },
|
||||
status: 'success',
|
||||
|
||||
@@ -26,24 +26,12 @@ vi.mock('@/service/share', () => ({
|
||||
|
||||
vi.mock('@/service/console', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/service/console')>()
|
||||
const { withConsoleResponses } = await import('@/test/console/service-responses')
|
||||
return {
|
||||
...actual,
|
||||
consoleQuery: {
|
||||
...actual.consoleQuery,
|
||||
systemFeatures: actual.consoleQuery.systemFeatures,
|
||||
enterprise: {
|
||||
...actual.consoleQuery.enterprise,
|
||||
webAppAuth: {
|
||||
...actual.consoleQuery.enterprise.webAppAuth,
|
||||
getWebAppWhitelistSubjects: {
|
||||
queryOptions: ({ input }: { input: { query: { appId?: string } } }) => ({
|
||||
queryKey: ['web-app-whitelist-subjects', input.query.appId],
|
||||
queryFn: () => mockGetWebAppWhitelistSubjects(input),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
consoleQuery: withConsoleResponses(actual.consoleQuery, {
|
||||
'enterprise.webAppAuth.getWebAppWhitelistSubjects': mockGetWebAppWhitelistSubjects,
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -4,15 +4,13 @@ import type { RenderOptions } from '@testing-library/react'
|
||||
import type { ReactElement } from 'react'
|
||||
import type { UserProfileWithMeta } from '@/features/account-profile/client'
|
||||
import { render } from '@testing-library/react'
|
||||
import { consoleQueryKey } from '@/test/console/operation-hooks'
|
||||
import { createQueryClientWrapper } from '@/test/console/query-client'
|
||||
import { createTestQueryClient } from '@/test/query-client'
|
||||
|
||||
type AccountProfileMeta = UserProfileWithMeta['meta']
|
||||
|
||||
const accountProfileQueryKey = [
|
||||
['console', 'account', 'profile', 'get'],
|
||||
{ type: 'query' },
|
||||
] as const
|
||||
const accountProfileQueryKey = consoleQueryKey('account.profile.get')
|
||||
|
||||
const createAccountProfileFixture = (
|
||||
overrides: Partial<GetAccountProfileResponse> = {},
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import type { GetWorkspacesCurrentSummaryResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { QueryClient, QueryKey } from '@tanstack/react-query'
|
||||
import { consoleQueryKey } from '@/test/console/operation-hooks'
|
||||
|
||||
export const currentWorkspaceQueryKey = [
|
||||
['console', 'workspaces', 'current', 'summary', 'get'],
|
||||
{ type: 'query' },
|
||||
] as const
|
||||
export const currentWorkspaceQueryKey = consoleQueryKey('workspaces.current.summary.get')
|
||||
|
||||
const createCurrentWorkspaceFixture = (
|
||||
overrides: Partial<GetWorkspacesCurrentSummaryResponse> = {},
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
import type { OperationKeyOptions, OperationType } from '@orpc/tanstack-query'
|
||||
import { generateOperationKey } from '@orpc/tanstack-query'
|
||||
|
||||
// Keep the path prefix aligned with createConsoleQuery in service/console/query-policies.ts.
|
||||
const consoleOperationKey = <TType extends OperationType, TInput = unknown>(
|
||||
path: string,
|
||||
options: OperationKeyOptions<TType, TInput> = {},
|
||||
) => generateOperationKey(['console', ...path.split('.')], options)
|
||||
|
||||
// Seed helpers also run with partial console mocks, so they cannot rely on consoleQuery.
|
||||
export const consoleQueryKey = (path: string, input?: unknown) =>
|
||||
consoleOperationKey(path, { type: 'query', input })
|
||||
|
||||
/**
|
||||
* Stands in for one console operation inside a fully mocked `@/service/console`
|
||||
* module, producing the same key shape the real utilities use.
|
||||
* module, producing keys with oRPC's own generator.
|
||||
*/
|
||||
export const consoleOperationStub = (path: string) => {
|
||||
const queryKey = (options?: { input?: unknown }) =>
|
||||
[['console', ...path.split('.')], { type: 'query', input: options?.input }] as const
|
||||
const queryKey = (options?: { input?: unknown }) => consoleQueryKey(path, options?.input)
|
||||
return {
|
||||
key: () => [['console', ...path.split('.')]] as const,
|
||||
key: () => consoleOperationKey(path),
|
||||
queryKey,
|
||||
queryOptions: (options: { input?: unknown } = {}) => ({
|
||||
...options,
|
||||
@@ -14,7 +26,7 @@ export const consoleOperationStub = (path: string) => {
|
||||
}),
|
||||
mutationOptions: (options: object = {}) => ({
|
||||
...options,
|
||||
mutationKey: [['console', ...path.split('.')], { type: 'mutation' }],
|
||||
mutationKey: consoleOperationKey(path, { type: 'mutation' }),
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
ensureCurrentWorkspaceQuery,
|
||||
seedCurrentWorkspaceQuery,
|
||||
} from '@/test/console/current-workspace'
|
||||
import { consoleQueryKey } from '@/test/console/operation-hooks'
|
||||
import { createQueryClientWrapper } from '@/test/console/query-client'
|
||||
import {
|
||||
createSystemFeaturesFixture,
|
||||
@@ -58,8 +59,8 @@ type CurrentWorkspaceQueryProvider = {
|
||||
}
|
||||
}
|
||||
|
||||
const fallbackTrialModelsQueryKey = ['console', 'trialModels', 'get'] as const
|
||||
const fallbackAppDslVersionQueryKey = ['console', 'appDslVersion', 'get'] as const
|
||||
const fallbackTrialModelsQueryKey = consoleQueryKey('trialModels.get')
|
||||
const fallbackAppDslVersionQueryKey = consoleQueryKey('appDslVersion.get')
|
||||
|
||||
const getTrialModelsQueryKey = () => {
|
||||
const trialModelsQuery = (consoleQuery as { trialModels?: TrialModelsQueryProvider }).trialModels
|
||||
|
||||
@@ -3,6 +3,41 @@ import { skipToken } from '@tanstack/react-query'
|
||||
|
||||
type ResponseHandler = (input: never) => unknown
|
||||
|
||||
type QueryOverrides = Partial<
|
||||
Record<'queryOptions' | 'infiniteOptions', (options: never) => object>
|
||||
>
|
||||
|
||||
/** Keep generated keys and policies when a scenario supplies custom query functions/options. */
|
||||
export function withConsoleQueryOverrides(
|
||||
query: typeof consoleQuery,
|
||||
overrides: Record<string, QueryOverrides>,
|
||||
): typeof consoleQuery {
|
||||
const paths = Object.keys(overrides)
|
||||
const wrap = <T extends object>(target: T, path: string): T =>
|
||||
new Proxy(target, {
|
||||
get(current, property, receiver) {
|
||||
const value = Reflect.get(current, property, receiver)
|
||||
if (typeof property !== 'string') return value
|
||||
const override =
|
||||
property === 'queryOptions' || property === 'infiniteOptions'
|
||||
? overrides[path]?.[property]
|
||||
: undefined
|
||||
if (override) {
|
||||
return (options: { input?: unknown } = {}) => {
|
||||
const original = (value as (options: unknown) => { queryKey: unknown })(options)
|
||||
if (options.input === skipToken) return original
|
||||
return { ...original, ...override(options as never), queryKey: original.queryKey }
|
||||
}
|
||||
}
|
||||
const nextPath = path ? `${path}.${property}` : property
|
||||
if (paths.some((key) => key === nextPath || key.startsWith(`${nextPath}.`)))
|
||||
return wrap(value as object, nextPath)
|
||||
return value
|
||||
},
|
||||
})
|
||||
return wrap(query, '')
|
||||
}
|
||||
|
||||
/** Replace transport responses while retaining generated keys/options and real Query observers. */
|
||||
export function withConsoleResponses(
|
||||
query: typeof consoleQuery,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { MyPermissionsResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { QueryClient } from '@tanstack/react-query'
|
||||
import { consoleQueryKey } from '@/test/console/operation-hooks'
|
||||
|
||||
const currentWorkspacePermissionsQueryKey = [
|
||||
['console', 'workspaces', 'current', 'rbac', 'myPermissions', 'get'],
|
||||
{ type: 'query' },
|
||||
] as const
|
||||
const currentWorkspacePermissionsQueryKey = consoleQueryKey(
|
||||
'workspaces.current.rbac.myPermissions.get',
|
||||
)
|
||||
|
||||
const createWorkspacePermissionsFixture = (
|
||||
permissionKeys: readonly string[] = [],
|
||||
|
||||
Reference in new issue
Block a user