252 lines
8.1 KiB
TypeScript
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()
|
|
})
|
|
})
|