mirror of
https://github.com/simstudioai/sim.git
synced 2026-10-12 23:33:47 +08:00
fix(chat): invalidate deployment queries after a chat mutation (#6223)
PATCH /api/chat/manage/[id] calls performFullDeploy when the workflow has drifted from its active deployment, so editing a chat can mint a new deployment version. useUpdateChat invalidated only chatStatus and chatDetail, leaving the deployment panel showing the previous version and a stale "needs redeployment" indicator until the staleTime expired. Both mutations now route through invalidateDeploymentQueries, the shared helper the rest of the deployment surface uses. That also picks up deployedState, which useCreateChat's hand-rolled list had omitted even though performChatDeploy replaces the deployed workflow state. Tests cover both mutations and were verified to fail against the previous invalidation.
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
import { act, type ReactNode } from 'react'
|
||||
import { sleep } from '@sim/utils/helpers'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { createRoot, type Root } from 'react-dom/client'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const { mockRequestJson, mockInvalidateDeploymentQueries } = vi.hoisted(() => ({
|
||||
mockRequestJson: vi.fn(),
|
||||
mockInvalidateDeploymentQueries: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/api/client/request', () => ({
|
||||
requestJson: mockRequestJson,
|
||||
}))
|
||||
|
||||
vi.mock('@/hooks/queries/deployments', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import('@/hooks/queries/deployments')>()),
|
||||
invalidateDeploymentQueries: mockInvalidateDeploymentQueries,
|
||||
}))
|
||||
|
||||
import { useCreateChat, useUpdateChat } from '@/hooks/queries/chats'
|
||||
|
||||
function renderHookWithClient<T>(useHook: () => T): { getResult: () => T } {
|
||||
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
||||
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } })
|
||||
const container = document.createElement('div')
|
||||
const root: Root = createRoot(container)
|
||||
let result: T | undefined
|
||||
|
||||
function Probe() {
|
||||
result = useHook()
|
||||
return null
|
||||
}
|
||||
|
||||
act(() => {
|
||||
root.render(
|
||||
<QueryClientProvider client={queryClient}>{(<Probe />) as ReactNode}</QueryClientProvider>
|
||||
)
|
||||
})
|
||||
|
||||
return {
|
||||
getResult: () => {
|
||||
if (result === undefined) throw new Error('Hook result is not ready')
|
||||
return result
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async function flush() {
|
||||
await act(async () => {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await Promise.resolve()
|
||||
await sleep(1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const FORM_DATA = {
|
||||
identifier: 'my-chat',
|
||||
title: 'My chat',
|
||||
description: '',
|
||||
authType: 'public' as const,
|
||||
password: '',
|
||||
emails: [],
|
||||
welcomeMessage: 'hi',
|
||||
selectedOutputBlocks: [],
|
||||
includeThinking: false,
|
||||
includeToolCalls: false,
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockRequestJson.mockResolvedValue({ chatUrl: 'https://sim.ai/chat/my-chat', chatId: 'chat-1' })
|
||||
mockInvalidateDeploymentQueries.mockResolvedValue(undefined)
|
||||
})
|
||||
|
||||
describe('chat mutations invalidate the deployment boundary', () => {
|
||||
/**
|
||||
* PATCH /api/chat/manage/[id] calls performFullDeploy when the workflow has
|
||||
* drifted, so a chat edit can mint a new deployment version. Invalidating only
|
||||
* chatStatus/chatDetail left the deployment panel showing the previous version.
|
||||
*/
|
||||
it('useUpdateChat invalidates every deployment query for the workflow', async () => {
|
||||
const { getResult } = renderHookWithClient(() => useUpdateChat())
|
||||
|
||||
await act(async () => {
|
||||
await getResult().mutateAsync({
|
||||
chatId: 'chat-1',
|
||||
workflowId: 'wf-1',
|
||||
formData: FORM_DATA,
|
||||
})
|
||||
})
|
||||
await flush()
|
||||
|
||||
expect(mockInvalidateDeploymentQueries).toHaveBeenCalledWith(expect.anything(), 'wf-1')
|
||||
})
|
||||
|
||||
it('useCreateChat invalidates every deployment query for the workflow', async () => {
|
||||
const { getResult } = renderHookWithClient(() => useCreateChat())
|
||||
|
||||
await act(async () => {
|
||||
await getResult().mutateAsync({ workflowId: 'wf-1', formData: FORM_DATA })
|
||||
})
|
||||
await flush()
|
||||
|
||||
expect(mockInvalidateDeploymentQueries).toHaveBeenCalledWith(expect.anything(), 'wf-1')
|
||||
})
|
||||
})
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
verifyChatEmailOtpContract,
|
||||
} from '@/lib/api/contracts/chats'
|
||||
import type { OutputConfig } from '@/stores/chat/types'
|
||||
import { deploymentKeys } from './deployments'
|
||||
import { deploymentKeys, invalidateDeploymentQueries } from './deployments'
|
||||
|
||||
const logger = createLogger('ChatMutations')
|
||||
|
||||
@@ -296,17 +296,8 @@ export function useCreateChat() {
|
||||
throwUserFriendlyIdentifierError(error)
|
||||
}
|
||||
},
|
||||
onSettled: (_data, _error, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: deploymentKeys.chatStatus(variables.workflowId),
|
||||
})
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: deploymentKeys.info(variables.workflowId),
|
||||
})
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: deploymentKeys.versions(variables.workflowId),
|
||||
})
|
||||
},
|
||||
onSettled: (_data, _error, variables) =>
|
||||
invalidateDeploymentQueries(queryClient, variables.workflowId),
|
||||
onError: (error) => {
|
||||
logger.error('Failed to create chat', { error })
|
||||
},
|
||||
@@ -341,12 +332,10 @@ export function useUpdateChat() {
|
||||
}
|
||||
},
|
||||
onSettled: (_data, _error, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: deploymentKeys.chatStatus(variables.workflowId),
|
||||
})
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: deploymentKeys.chatDetail(variables.chatId),
|
||||
})
|
||||
return invalidateDeploymentQueries(queryClient, variables.workflowId)
|
||||
},
|
||||
onError: (error) => {
|
||||
logger.error('Failed to update chat', { error })
|
||||
|
||||
Reference in New Issue
Block a user