Files
nexus/frontend/tests/openclaw-query-cache.test.ts
T
AzuTear f5552218bc
CI - Build & Test / Backend (.NET) (push) Successful in 42s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 2m46s
CI - Build & Test / Security Check (push) Successful in 3s
CI - Build & Test / Deploy Nexus (push) Successful in 56s
feat: ship agent-first mission control v0.2.57
2026-07-31 22:39:47 +02:00

252 lines
8.1 KiB
TypeScript

import { QueryClient, type InfiniteData } from '@tanstack/vue-query'
import { describe, expect, it } from 'vitest'
import {
applyOpenClawCronDetail,
mergeOpenClawCronPages,
} from '../src/api/openClawCron'
import {
applyOpenClawRunOperation,
type OpenClawRunCollectionDto,
type OpenClawRunDto,
type OpenClawRunOperationDto,
} from '../src/api/openClawRuns'
import { queryKeys } from '../src/api/queryClient'
import {
invalidateOpenClawCronEventQueries,
invalidateOpenClawRunEventQueries,
} from '../src/services/domainEvents'
import type {
OpenClawCollection,
OpenClawCronJob,
OpenClawCronJobDetail,
} from '../src/types/openclaw'
function run(overrides: Partial<OpenClawRunDto> = {}): OpenClawRunDto {
return {
id: '98f927bb-3df8-44d0-9ec9-cd98cdcf12db',
title: 'Review task',
prompt: 'Review the task.',
agentId: 'iris',
sessionKey: 'agent:iris:main',
status: 'running',
taskId: '931a9025-f2d6-4e48-8a41-24e8733807db',
projectId: null,
openClawRunId: 'oc-run-1',
retriedFromRunId: null,
correlationId: 'correlation-1',
actor: 'bao',
lastError: null,
lastGatewaySequence: 1,
sequenceGapDetected: false,
canStop: true,
canRetry: false,
canResume: false,
resumeCapabilityMessage: null,
createdAt: '2026-07-31T10:00:00.000Z',
updatedAt: '2026-07-31T10:01:00.000Z',
startedAt: '2026-07-31T10:00:01.000Z',
finishedAt: null,
...overrides,
}
}
function runCollection(items: OpenClawRunDto[]): OpenClawRunCollectionDto {
return {
items,
nextCursor: 'cursor-1',
checkedAt: '2026-07-31T10:01:00.000Z',
}
}
function cronJob(id: string, enabled = true): OpenClawCronJob {
return {
id,
name: `Job ${id}`,
description: null,
schedule: '0 7 * * *',
timeZone: 'Europe/Berlin',
enabled,
status: 'idle',
agentId: 'iris',
sessionKey: 'agent:iris:main',
nextRunAt: null,
lastRunAt: null,
lastRunStatus: null,
lastError: null,
canRun: true,
resourceHash: `hash-${id}`,
}
}
function cronDetail(id: string, enabled = true): OpenClawCronJobDetail {
return {
id,
name: `Updated ${id}`,
displayName: null,
description: null,
enabled,
deleteAfterRun: false,
agentId: 'iris',
sessionKey: 'agent:iris:main',
sessionTarget: 'isolated',
wakeMode: 'now',
schedule: {
kind: 'cron',
expression: '0 8 * * *',
timeZone: 'Europe/Berlin',
at: null,
everyMs: null,
anchorMs: null,
staggerMs: null,
command: null,
workingDirectory: null,
},
payload: {
kind: 'agentTurn',
text: null,
message: 'Run a bounded check.',
model: null,
fallbacks: [],
thinking: null,
timeoutSeconds: null,
allowUnsafeExternalContent: null,
lightContext: null,
toolsAllow: [],
arguments: [],
workingDirectory: null,
environmentKeys: [],
inputConfigured: false,
noOutputTimeoutSeconds: null,
outputMaxBytes: null,
},
delivery: null,
trigger: null,
failureAlert: null,
createdAt: '2026-07-31T10:00:00.000Z',
updatedAt: '2026-07-31T10:02:00.000Z',
nextRunAt: null,
lastRunAt: null,
lastRunStatus: null,
lastError: null,
resourceHash: `updated-hash-${id}`,
canUpdate: true,
canDelete: true,
canRun: true,
}
}
describe('OpenClaw Vue Query cache adapters', () => {
it('patches generated run contracts across list and task-related caches', () => {
const client = new QueryClient()
const current = run()
const other = run({
id: '568f10e3-4fb0-4af4-acff-53030387ce72',
taskId: null,
title: 'Other run',
})
const listKey = queryKeys.openClawRuns({ limit: 50 })
const taskKey = queryKeys.taskRuns(current.taskId!)
const runningKey = queryKeys.openClawRuns({ limit: 50, status: 'running' })
client.setQueryData(listKey, runCollection([current, other]))
client.setQueryData(taskKey, runCollection([current]))
client.setQueryData(runningKey, runCollection([current]))
const completed = run({
status: 'completed',
canStop: false,
updatedAt: '2026-07-31T10:03:00.000Z',
finishedAt: '2026-07-31T10:03:00.000Z',
})
const operation: OpenClawRunOperationDto = {
ok: true,
state: 'completed',
message: 'Completed.',
run: completed,
resultRun: null,
completedAt: '2026-07-31T10:03:00.000Z',
}
applyOpenClawRunOperation(client, operation)
expect(client.getQueryData<OpenClawRunCollectionDto>(listKey)?.items)
.toEqual([completed, other])
expect(client.getQueryData<OpenClawRunCollectionDto>(listKey)?.nextCursor)
.toBe('cursor-1')
expect(client.getQueryData<OpenClawRunCollectionDto>(taskKey)?.items)
.toEqual([completed])
expect(client.getQueryData<OpenClawRunCollectionDto>(runningKey)?.items)
.toEqual([])
expect(client.getQueryData(queryKeys.openClawRun(completed.id))).toEqual(completed)
})
it('merges cron pages and updates only compatible cached lists', () => {
const client = new QueryClient()
const enabledKey = queryKeys.openClawCron({ includeDisabled: false, limit: 50 })
const allKey = queryKeys.openClawCron({ includeDisabled: true, limit: 50 })
const pages: InfiniteData<OpenClawCollection<OpenClawCronJob>, string | null> = {
pages: [
{
state: 'ready',
items: [cronJob('a'), cronJob('b')],
nextCursor: 'next',
message: null,
recovery: null,
checkedAt: '2026-07-31T10:00:00.000Z',
},
{
state: 'ready',
items: [cronJob('b'), cronJob('c')],
nextCursor: null,
message: null,
recovery: null,
checkedAt: '2026-07-31T10:01:00.000Z',
},
],
pageParams: [null, 'next'],
}
client.setQueryData(enabledKey, pages)
client.setQueryData(allKey, pages)
applyOpenClawCronDetail(client, cronDetail('a', false))
const enabled = mergeOpenClawCronPages(client.getQueryData(enabledKey))
const all = mergeOpenClawCronPages(client.getQueryData(allKey))
expect(enabled?.items.map(item => item.id)).toEqual(['b', 'c'])
expect(all?.items.find(item => item.id === 'a')).toMatchObject({
name: 'Updated a',
enabled: false,
resourceHash: 'updated-hash-a',
})
expect(all?.items.map(item => item.id)).toEqual(['a', 'b', 'c'])
expect(all?.nextCursor).toBeNull()
})
it('invalidates only the affected run and cron domains', async () => {
const runId = '98f927bb-3df8-44d0-9ec9-cd98cdcf12db'
const otherRunId = '568f10e3-4fb0-4af4-acff-53030387ce72'
const cronId = 'cron-a'
const client = (await import('../src/api/queryClient')).queryClient
client.clear()
client.setQueryData(queryKeys.openClawRuns({ limit: 50 }), runCollection([]))
client.setQueryData(queryKeys.openClawRunHistory(runId), { marker: 'target' })
client.setQueryData(queryKeys.openClawRunHistory(otherRunId), { marker: 'other' })
client.setQueryData(queryKeys.openClawCron({ includeDisabled: true, limit: 50 }), { marker: 'list' })
client.setQueryData(queryKeys.openClawCronDetail(cronId), cronDetail(cronId))
client.setQueryData(queryKeys.openClawCronDetail('cron-b'), cronDetail('cron-b'))
client.setQueryData(queryKeys.openClawAgents(), { marker: 'agents' })
await invalidateOpenClawRunEventQueries(runId)
expect(client.getQueryState(queryKeys.openClawRuns({ limit: 50 }))?.isInvalidated).toBe(true)
expect(client.getQueryState(queryKeys.openClawRunHistory(runId))?.isInvalidated).toBe(true)
expect(client.getQueryState(queryKeys.openClawRunHistory(otherRunId))?.isInvalidated).toBe(false)
expect(client.getQueryState(queryKeys.openClawAgents())?.isInvalidated).toBe(false)
await invalidateOpenClawCronEventQueries(cronId)
expect(client.getQueryState(queryKeys.openClawCron({ includeDisabled: true, limit: 50 }))?.isInvalidated).toBe(true)
expect(client.getQueryState(queryKeys.openClawCronDetail(cronId))?.isInvalidated).toBe(true)
expect(client.getQueryState(queryKeys.openClawCronDetail('cron-b'))?.isInvalidated).toBe(false)
client.clear()
})
})