|
| 1 | +/** @vitest-environment node */ |
| 2 | +import { QueryClient } from '@tanstack/react-query' |
| 3 | +import { describe, expect, it } from 'vitest' |
| 4 | +import { invalidateResourceQueries } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-registry/resource-invalidation' |
| 5 | +import { deploymentKeys } from '@/hooks/queries/deployments' |
| 6 | +import { connectorDocumentKeys, connectorKeys } from '@/hooks/queries/kb/connectors' |
| 7 | +import { logKeys } from '@/hooks/queries/logs' |
| 8 | +import { mothershipChatKeys } from '@/hooks/queries/mothership-chats' |
| 9 | +import { folderKeys } from '@/hooks/queries/utils/folder-keys' |
| 10 | +import { knowledgeKeys } from '@/hooks/queries/utils/knowledge-keys' |
| 11 | +import { tableKeys } from '@/hooks/queries/utils/table-keys' |
| 12 | +import { workflowKeys } from '@/hooks/queries/utils/workflow-keys' |
| 13 | +import { workspaceFilesKeys } from '@/hooks/queries/workspace-files' |
| 14 | + |
| 15 | +describe('resource cache reconciliation', () => { |
| 16 | + it('refreshes table rows, schema and saved views without expiring another table', () => { |
| 17 | + const client = new QueryClient() |
| 18 | + const affected = [ |
| 19 | + tableKeys.detail('t'), |
| 20 | + tableKeys.views('t'), |
| 21 | + tableKeys.infiniteRows('t', 'filter'), |
| 22 | + ] |
| 23 | + for (const key of [...affected, tableKeys.views('other')]) client.setQueryData(key, {}) |
| 24 | + invalidateResourceQueries(client, 'w', 'table', 't') |
| 25 | + for (const key of affected) expect(client.getQueryState(key)?.isInvalidated).toBe(true) |
| 26 | + expect(client.getQueryState(tableKeys.views('other'))?.isInvalidated).toBe(false) |
| 27 | + }) |
| 28 | + |
| 29 | + it('covers knowledge documents, chunks, tags and connectors through the parent key', () => { |
| 30 | + const client = new QueryClient() |
| 31 | + const affected = [ |
| 32 | + knowledgeKeys.documents('kb', 'page'), |
| 33 | + knowledgeKeys.chunks('kb', 'document', 'page'), |
| 34 | + knowledgeKeys.tagDefinitions('kb'), |
| 35 | + connectorKeys.detail('kb', 'connector'), |
| 36 | + connectorDocumentKeys.list('kb', 'connector', true), |
| 37 | + ] |
| 38 | + for (const key of affected) client.setQueryData(key, {}) |
| 39 | + invalidateResourceQueries(client, 'w', 'knowledgebase', 'kb') |
| 40 | + for (const key of affected) expect(client.getQueryState(key)?.isInvalidated).toBe(true) |
| 41 | + }) |
| 42 | + |
| 43 | + it('invalidates stored workflow state and file contents, including collection refreshes', () => { |
| 44 | + const client = new QueryClient() |
| 45 | + const workflow = workflowKeys.state('wf') |
| 46 | + const file = workspaceFilesKeys.contentFile('w', 'file') |
| 47 | + client.setQueryData(workflow, {}) |
| 48 | + client.setQueryData(file, {}) |
| 49 | + invalidateResourceQueries(client, 'w', 'workflow', 'wf') |
| 50 | + invalidateResourceQueries(client, 'w', 'file') |
| 51 | + expect(client.getQueryState(workflow)?.isInvalidated).toBe(true) |
| 52 | + expect(client.getQueryState(file)?.isInvalidated).toBe(true) |
| 53 | + }) |
| 54 | + |
| 55 | + it('updates workflow publication metadata shown beside the draft', () => { |
| 56 | + const client = new QueryClient() |
| 57 | + const affected = [ |
| 58 | + deploymentKeys.info('wf'), |
| 59 | + deploymentKeys.deployedState('wf'), |
| 60 | + deploymentKeys.versions('wf'), |
| 61 | + deploymentKeys.chatStatus('wf'), |
| 62 | + ] |
| 63 | + for (const key of affected) client.setQueryData(key, {}) |
| 64 | + invalidateResourceQueries(client, 'w', 'workflow', 'wf') |
| 65 | + for (const key of affected) expect(client.getQueryState(key)?.isInvalidated).toBe(true) |
| 66 | + }) |
| 67 | + |
| 68 | + it('refreshes every folder family and both task navigation scopes', () => { |
| 69 | + const client = new QueryClient() |
| 70 | + const affected = [ |
| 71 | + folderKeys.list('w', 'active', 'workflow'), |
| 72 | + folderKeys.list('w', 'active', 'table'), |
| 73 | + folderKeys.list('w', 'archived', 'knowledge_base'), |
| 74 | + mothershipChatKeys.list('w', 'active'), |
| 75 | + mothershipChatKeys.list('w', 'archived'), |
| 76 | + mothershipChatKeys.detail('chat'), |
| 77 | + ] |
| 78 | + for (const key of affected) client.setQueryData(key, {}) |
| 79 | + invalidateResourceQueries(client, 'w', 'folder') |
| 80 | + invalidateResourceQueries(client, 'w', 'task', 'chat') |
| 81 | + for (const key of affected) expect(client.getQueryState(key)?.isInvalidated).toBe(true) |
| 82 | + }) |
| 83 | + |
| 84 | + it('leaves desktop and ephemeral panels under their existing owners', () => { |
| 85 | + const client = new QueryClient() |
| 86 | + client.setQueryData(tableKeys.detail('t'), {}) |
| 87 | + for (const type of ['browser', 'terminal', 'generic'] as const) |
| 88 | + invalidateResourceQueries(client, 'w', type) |
| 89 | + expect(client.getQueryState(tableKeys.detail('t'))?.isInvalidated).toBe(false) |
| 90 | + }) |
| 91 | + |
| 92 | + it('refreshes log navigation and execution lookup while preserving other workspaces', () => { |
| 93 | + const client = new QueryClient() |
| 94 | + const affected = [ |
| 95 | + [...logKeys.lists(), 'w', { workflowIds: ['workflow'] }], |
| 96 | + logKeys.stat('w', {}), |
| 97 | + logKeys.detail('w', 'row'), |
| 98 | + logKeys.byExecution('w', 'execution'), |
| 99 | + ] |
| 100 | + const unrelated = [logKeys.detail('other', 'row'), logKeys.byExecution('other', 'execution')] |
| 101 | + for (const key of [...affected, ...unrelated]) client.setQueryData(key, {}) |
| 102 | + invalidateResourceQueries(client, 'w', 'log', 'row') |
| 103 | + for (const key of affected) expect(client.getQueryState(key)?.isInvalidated).toBe(true) |
| 104 | + for (const key of unrelated) expect(client.getQueryState(key)?.isInvalidated).toBe(false) |
| 105 | + }) |
| 106 | +}) |
0 commit comments