diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json
index cde7b176ea2..7cc95780240 100644
--- a/oxlint-suppressions.json
+++ b/oxlint-suppressions.json
@@ -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
diff --git a/web/app/(commonLayout)/__tests__/hydration-boundary.spec.tsx b/web/app/(commonLayout)/__tests__/hydration-boundary.spec.tsx
index abe52c22c14..4a581d5997f 100644
--- a/web/app/(commonLayout)/__tests__/hydration-boundary.spec.tsx
+++ b/web/app/(commonLayout)/__tests__/hydration-boundary.spec.tsx
@@ -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({element})
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' })
diff --git a/web/app/components/app/access-point/__tests__/built-in-access-points.spec.tsx b/web/app/components/app/access-point/__tests__/built-in-access-points.spec.tsx
index 3dad7524762..555c9367f82 100644
--- a/web/app/components/app/access-point/__tests__/built-in-access-points.spec.tsx
+++ b/web/app/components/app/access-point/__tests__/built-in-access-points.spec.tsx
@@ -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,
})
diff --git a/web/app/components/base/analytics-consent/__tests__/cloud-analytics.spec.tsx b/web/app/components/base/analytics-consent/__tests__/cloud-analytics.spec.tsx
index 1c2ff7ccdd2..be4ef7fefd8 100644
--- a/web/app/components/base/analytics-consent/__tests__/cloud-analytics.spec.tsx
+++ b/web/app/components/base/analytics-consent/__tests__/cloud-analytics.spec.tsx
@@ -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', () => ({
diff --git a/web/app/components/base/chat/chat-with-history/__tests__/sidebar-focus.browser.spec.tsx b/web/app/components/base/chat/chat-with-history/__tests__/sidebar-focus.browser.spec.tsx
index 34605831b65..5059dbb28cf 100644
--- a/web/app/components/base/chat/chat-with-history/__tests__/sidebar-focus.browser.spec.tsx
+++ b/web/app/components/base/chat/chat-with-history/__tests__/sidebar-focus.browser.spec.tsx
@@ -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,
diff --git a/web/app/components/datasets/create-from-pipeline/list/template-card/__tests__/edit-pipeline-info.spec.tsx b/web/app/components/datasets/create-from-pipeline/list/template-card/__tests__/edit-pipeline-info.spec.tsx
index 6e2a6563cf0..4f17fa5950f 100644
--- a/web/app/components/datasets/create-from-pipeline/list/template-card/__tests__/edit-pipeline-info.spec.tsx
+++ b/web/app/components/datasets/create-from-pipeline/list/template-card/__tests__/edit-pipeline-info.spec.tsx
@@ -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' } },
+ }),
})
})
})
diff --git a/web/app/components/datasets/list/dataset-card/components/__tests__/operations-dropdown.browser.spec.tsx b/web/app/components/datasets/list/dataset-card/components/__tests__/operations-dropdown.browser.spec.tsx
index b990fbff8e4..110b74e5c24 100644
--- a/web/app/components/datasets/list/dataset-card/components/__tests__/operations-dropdown.browser.spec.tsx
+++ b/web/app/components/datasets/list/dataset-card/components/__tests__/operations-dropdown.browser.spec.tsx
@@ -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 }),
}),
}))
diff --git a/web/app/components/main-nav/components/__tests__/web-apps-section.browser.spec.tsx b/web/app/components/main-nav/components/__tests__/web-apps-section.browser.spec.tsx
index b9d55b14d1b..88f907cfb1a 100644
--- a/web/app/components/main-nav/components/__tests__/web-apps-section.browser.spec.tsx
+++ b/web/app/components/main-nav/components/__tests__/web-apps-section.browser.spec.tsx
@@ -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()
+ 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 {
diff --git a/web/app/components/plugins/marketplace/home/__tests__/marketplace-search-autocomplete.browser.spec.tsx b/web/app/components/plugins/marketplace/home/__tests__/marketplace-search-autocomplete.browser.spec.tsx
index 0f70d6a55dc..abb6a4d8211 100644
--- a/web/app/components/plugins/marketplace/home/__tests__/marketplace-search-autocomplete.browser.spec.tsx
+++ b/web/app/components/plugins/marketplace/home/__tests__/marketplace-search-autocomplete.browser.spec.tsx
@@ -43,15 +43,19 @@ vi.mock('@/service/marketplace', async (importOriginal) => {
...original,
marketplaceQuery: {
searchAdvanced: {
- queryOptions: ({ input }: { input: unknown }) => ({
- queryKey: ['marketplace', 'plugins', input],
+ queryOptions: (
+ options: Parameters[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[0],
+ ) => ({
+ ...original.marketplaceQuery.templateSearch.queryOptions(options),
+ queryFn: () => mockTemplateSearch(options.input),
}),
},
},
diff --git a/web/app/components/snippet-list/__tests__/reflow.browser.spec.tsx b/web/app/components/snippet-list/__tests__/reflow.browser.spec.tsx
index 98da0fcf086..cd0896e9688 100644
--- a/web/app/components/snippet-list/__tests__/reflow.browser.spec.tsx
+++ b/web/app/components/snippet-list/__tests__/reflow.browser.spec.tsx
@@ -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)
diff --git a/web/app/components/workflow/comment/comment-movement.browser.spec.tsx b/web/app/components/workflow/comment/comment-movement.browser.spec.tsx
index 60436afced7..60d2a15b820 100644
--- a/web/app/components/workflow/comment/comment-movement.browser.spec.tsx
+++ b/web/app/components/workflow/comment/comment-movement.browser.spec.tsx
@@ -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 } }),
}),
}))
diff --git a/web/app/components/workflow/comment/reply-deletion.browser.spec.tsx b/web/app/components/workflow/comment/reply-deletion.browser.spec.tsx
index 71d031ab9cc..3dc58be4845 100644
--- a/web/app/components/workflow/comment/reply-deletion.browser.spec.tsx
+++ b/web/app/components/workflow/comment/reply-deletion.browser.spec.tsx
@@ -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 } }),
}),
}))
diff --git a/web/app/device/__tests__/page-terminal.spec.tsx b/web/app/device/__tests__/page-terminal.spec.tsx
index 4a648c47ac6..2c08ecc11d4 100644
--- a/web/app/device/__tests__/page-terminal.spec.tsx
+++ b/web/app/device/__tests__/page-terminal.spec.tsx
@@ -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()
+ render(, { 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()
+ render(, { 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()
+ render(, { 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()
+ render(, { 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()
+ render(, { accountProfile: null })
expect(screen.getByRole('textbox')).toBeInTheDocument()
expect(screen.queryByText(TITLE)).not.toBeInTheDocument()
expect(document.title).toBe('deviceFlow.codeEntry.title - Dify')
diff --git a/web/features/agent-v2/agent-detail/configure/__tests__/use-agent-configure-sync.spec.tsx b/web/features/agent-v2/agent-detail/configure/__tests__/use-agent-configure-sync.spec.tsx
index dcbcd1932cc..f5414afc7b5 100644
--- a/web/features/agent-v2/agent-detail/configure/__tests__/use-agent-configure-sync.spec.tsx
+++ b/web/features/agent-v2/agent-detail/configure/__tests__/use-agent-configure-sync.spec.tsx
@@ -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
diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/__tests__/index.spec.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/__tests__/index.spec.tsx
index 809eff49dc0..44c625a6208 100644
--- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/__tests__/index.spec.tsx
+++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/__tests__/index.spec.tsx
@@ -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()
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) => ({
- 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' }))
diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/index.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/index.tsx
index b36e16b3b8c..6bc5da3698f 100644
--- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/index.tsx
+++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/index.tsx
@@ -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
diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/skills/__tests__/index.spec.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/skills/__tests__/index.spec.tsx
index 6f586eb80ab..3e9b9b43411 100644
--- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/skills/__tests__/index.spec.tsx
+++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/skills/__tests__/index.spec.tsx
@@ -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()
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()).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'],
diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/skills/use-skill-detail.ts b/web/features/agent-v2/agent-detail/configure/components/orchestrate/skills/use-skill-detail.ts
index 7abd66f6fef..63ca4833911 100644
--- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/skills/use-skill-detail.ts
+++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/skills/use-skill-detail.ts
@@ -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
diff --git a/web/features/agent-v2/agent-detail/configure/components/preview/__tests__/working-directory-panel.spec.tsx b/web/features/agent-v2/agent-detail/configure/components/preview/__tests__/working-directory-panel.spec.tsx
index fb395cf60f6..db410e50220 100644
--- a/web/features/agent-v2/agent-detail/configure/components/preview/__tests__/working-directory-panel.spec.tsx
+++ b/web/features/agent-v2/agent-detail/configure/components/preview/__tests__/working-directory-panel.spec.tsx
@@ -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()
+ 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() {
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:
diff --git a/web/features/skills/__tests__/detail-page-clipboard.spec.tsx b/web/features/skills/__tests__/detail-page-clipboard.spec.tsx
index b030b272bee..b99116ce8a9 100644
--- a/web/features/skills/__tests__/detail-page-clipboard.spec.tsx
+++ b/web/features/skills/__tests__/detail-page-clipboard.spec.tsx
@@ -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()
diff --git a/web/features/skills/__tests__/detail-page-metadata.spec.tsx b/web/features/skills/__tests__/detail-page-metadata.spec.tsx
index a812e7ca0f4..5a6e53cb789 100644
--- a/web/features/skills/__tests__/detail-page-metadata.spec.tsx
+++ b/web/features/skills/__tests__/detail-page-metadata.spec.tsx
@@ -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((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()
diff --git a/web/features/skills/__tests__/detail-page-navigation.spec.tsx b/web/features/skills/__tests__/detail-page-navigation.spec.tsx
index 352e17dc8a8..413a32a36f6 100644
--- a/web/features/skills/__tests__/detail-page-navigation.spec.tsx
+++ b/web/features/skills/__tests__/detail-page-navigation.spec.tsx
@@ -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,
diff --git a/web/features/skills/__tests__/detail-page-publishing.spec.tsx b/web/features/skills/__tests__/detail-page-publishing.spec.tsx
index ff20ea7f8b3..7bd065b24be 100644
--- a/web/features/skills/__tests__/detail-page-publishing.spec.tsx
+++ b/web/features/skills/__tests__/detail-page-publishing.spec.tsx
@@ -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()
diff --git a/web/features/skills/__tests__/detail-page.fixture.tsx b/web/features/skills/__tests__/detail-page.fixture.tsx
index eaa48750929..3372b1506f7 100644
--- a/web/features/skills/__tests__/detail-page.fixture.tsx
+++ b/web/features/skills/__tests__/detail-page.fixture.tsx
@@ -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()
+ 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()
- return {
- ...actual,
- useQuery: (options: {
- queryKey: OperationKey<
- 'query',
- { params: GetWorkspacesCurrentModelsModelTypesByModelTypeData['path'] }
- >
- }) =>
- options.queryKey[0].includes('modelTypes')
- ? actual.useQuery({ ...options, queryFn: mocks.textGenerationModelListGet })
- : actual.useQuery(options),
- }
-})
diff --git a/web/features/system-features/__tests__/server.spec.ts b/web/features/system-features/__tests__/server.spec.ts
index 5e23b6ff6c3..52e865f7611 100644
--- a/web/features/system-features/__tests__/server.spec.ts
+++ b/web/features/system-features/__tests__/server.spec.ts
@@ -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()
+ 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',
diff --git a/web/service/access-control/__tests__/use-app-access-control.spec.tsx b/web/service/access-control/__tests__/use-app-access-control.spec.tsx
index 0a3f6bae681..ae855e305b0 100644
--- a/web/service/access-control/__tests__/use-app-access-control.spec.tsx
+++ b/web/service/access-control/__tests__/use-app-access-control.spec.tsx
@@ -26,24 +26,12 @@ vi.mock('@/service/share', () => ({
vi.mock('@/service/console', async (importOriginal) => {
const actual = await importOriginal()
+ 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,
+ }),
}
})
diff --git a/web/test/console/account-profile.ts b/web/test/console/account-profile.ts
index 73ea4698e7e..adfcb03ac69 100644
--- a/web/test/console/account-profile.ts
+++ b/web/test/console/account-profile.ts
@@ -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 = {},
diff --git a/web/test/console/current-workspace.ts b/web/test/console/current-workspace.ts
index bcfdd56ec0d..c929604b113 100644
--- a/web/test/console/current-workspace.ts
+++ b/web/test/console/current-workspace.ts
@@ -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 = {},
diff --git a/web/test/console/operation-hooks.ts b/web/test/console/operation-hooks.ts
index 884b54811ef..38bb715dddc 100644
--- a/web/test/console/operation-hooks.ts
+++ b/web/test/console/operation-hooks.ts
@@ -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 = (
+ path: string,
+ options: OperationKeyOptions = {},
+) => 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' }),
}),
}
}
diff --git a/web/test/console/query-data.ts b/web/test/console/query-data.ts
index fbb53a6c53a..4178ea8de07 100644
--- a/web/test/console/query-data.ts
+++ b/web/test/console/query-data.ts
@@ -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
diff --git a/web/test/console/service-responses.ts b/web/test/console/service-responses.ts
index 67e1286293e..23c41506a18 100644
--- a/web/test/console/service-responses.ts
+++ b/web/test/console/service-responses.ts
@@ -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,
+): typeof consoleQuery {
+ const paths = Object.keys(overrides)
+ const wrap = (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,
diff --git a/web/test/console/workspace-permissions.ts b/web/test/console/workspace-permissions.ts
index f8e1c596d9b..3e1c59a4408 100644
--- a/web/test/console/workspace-permissions.ts
+++ b/web/test/console/workspace-permissions.ts
@@ -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[] = [],