import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { QueryClient } from '@tanstack/vue-query' import { createPinia, setActivePinia } from 'pinia' import { applyAgentFileWrite, fetchAgentActivity, fetchAgentDetail, fetchAgentFile, fetchAgentFiles, fetchAgentSummary, type AgentFileCollectionDto, type AgentFileDto, type AgentFileWriteDto, } from '../src/api/agentDetail' import { fetchDoc, fetchDocs, fetchMemoryFile, fetchMemoryFiles, fetchMemorySearch, } from '../src/api/knowledge' import { fetchIncident, fetchIncidents } from '../src/api/incidents' import { queryKeys } from '../src/api/queryClient' import { fetchSecurityStatus } from '../src/api/security' import { useAuthStore } from '../src/stores/auth' function requestUrl(input: RequestInfo | URL): URL { const raw = input instanceof Request ? input.url : String(input) return new URL(raw, 'http://localhost') } function jsonResponse(value: unknown) { return new Response(JSON.stringify(value), { status: 200, headers: { 'Content-Type': 'application/json' }, }) } const file: AgentFileDto = { agentId: 'iris', name: 'SOUL.md', missing: false, size: 42, updatedAt: '2026-07-31T10:00:00.000Z', content: '# Iris', contentHash: 'hash-1', checkedAt: '2026-07-31T10:00:01.000Z', } beforeEach(() => { setActivePinia(createPinia()) const auth = useAuthStore() auth.initialized = true auth.applySession({ accessToken: 'server-state-token', expiresAt: '2026-07-31T12:00:00.000Z', user: { id: 'bao', email: 'bao@nexus.local', displayName: 'Bao', role: 'owner', }, }) }) afterEach(() => { vi.unstubAllGlobals() }) describe('remaining server-state fetchers', () => { it('loads agent detail, activity, summary and generated OpenClaw file contracts', async () => { const responses = new Map([ ['/api/v1/agents/iris', { id: 'iris', name: 'Iris', role: 'orchestrator', model: 'gpt-5', status: 'Online', }], ['/api/v1/agents/iris/activity', [{ id: 1, type: 'task', message: 'Delegated work', at: '2026-07-31T09:00:00.000Z', source: 'openclaw', }]], ['/api/v1/agents/iris/summary', { now: { text: 'Working', source: 'openclaw' }, today: { text: 'Completed review', source: 'openclaw' }, generatedAt: '2026-07-31T10:00:00.000Z', }], ['/api/v1/openclaw/agents/iris/files', { agentId: 'iris', files: [{ name: file.name, missing: file.missing, size: file.size, updatedAt: file.updatedAt, contentHash: file.contentHash, }], checkedAt: file.checkedAt, }], ['/api/v1/openclaw/agents/iris/files/SOUL.md', file], ]) const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { const value = responses.get(requestUrl(input).pathname) return value === undefined ? new Response(null, { status: 404 }) : jsonResponse(value) }) vi.stubGlobal('fetch', fetchMock) await expect(fetchAgentDetail('iris')).resolves.toMatchObject({ id: 'iris' }) await expect(fetchAgentActivity('iris')).resolves.toHaveLength(1) await expect(fetchAgentSummary('iris')).resolves.toMatchObject({ now: { text: 'Working' }, }) await expect(fetchAgentFiles('iris')).resolves.toMatchObject({ files: [{ name: 'SOUL.md' }], }) await expect(fetchAgentFile('iris', 'SOUL.md')).resolves.toEqual(file) expect(fetchMock).toHaveBeenCalledTimes(5) for (const [, init] of fetchMock.mock.calls) { expect(new Headers(init?.headers).get('Authorization')) .toBe('Bearer server-state-token') } }) it('loads docs, memory, incidents and security through authenticated fetchers', async () => { const fetchMock = vi.fn(async (input: RequestInfo | URL) => { const url = requestUrl(input) if (url.pathname === '/api/v1/docs') return jsonResponse([]) if (url.pathname === '/api/v1/docs/runbook%2Fdeploy.md') { return jsonResponse({ name: 'deploy.md', path: 'runbook/deploy.md', content: '# Deploy' }) } if (url.pathname === '/api/v1/memory') return jsonResponse([]) if (url.pathname === '/api/v1/memory/2026-07-31.md') { return jsonResponse({ name: '2026-07-31.md', content: 'Memory' }) } if (url.pathname === '/api/v1/memory/search') { return jsonResponse([{ name: '2026-07-31.md', excerpt: 'Mission control' }]) } if (url.pathname === '/api/v1/incidents') return jsonResponse([]) if (url.pathname === '/api/v1/incidents/gateway.md') { return jsonResponse({ name: 'gateway.md', title: 'Gateway', content: '# Gateway' }) } if (url.pathname === '/api/v1/security/status') { return jsonResponse({ authMethod: 'JWT', tokenConfig: { issuer: 'nexus', audience: 'nexus-web', refreshTokenDays: 7, accessTokenMinutes: 15, }, rateLimit: 'enabled', passwordPolicy: 'strong', cookieConfig: { httpOnly: true, secure: true, sameSite: 'Strict' }, twoFactorEnabled: false, passkeyEnabled: false, }) } return new Response(null, { status: 404 }) }) vi.stubGlobal('fetch', fetchMock) await expect(fetchDocs()).resolves.toEqual([]) await expect(fetchDoc('runbook/deploy.md')).resolves.toMatchObject({ name: 'deploy.md' }) await expect(fetchMemoryFiles()).resolves.toEqual([]) await expect(fetchMemoryFile('2026-07-31.md')).resolves.toMatchObject({ content: 'Memory', }) await expect(fetchMemorySearch('mission control')).resolves.toHaveLength(1) await expect(fetchIncidents()).resolves.toEqual([]) await expect(fetchIncident('gateway.md')).resolves.toMatchObject({ title: 'Gateway' }) await expect(fetchSecurityStatus()).resolves.toMatchObject({ authMethod: 'JWT' }) const urls = fetchMock.mock.calls.map(([input]) => requestUrl(input)) expect(urls.find(url => url.pathname === '/api/v1/memory/search') ?.searchParams.get('q')).toBe('mission control') }) }) describe('agent file cache synchronization', () => { it('patches and invalidates only the written file and its collection', async () => { const client = new QueryClient() const collection: AgentFileCollectionDto = { agentId: 'iris', files: [{ name: file.name, missing: true, size: null, updatedAt: null, contentHash: null, }], checkedAt: '2026-07-31T09:00:00.000Z', } const previousFile: AgentFileDto = { ...file, missing: true, size: null, updatedAt: null, content: null, contentHash: 'missing', checkedAt: collection.checkedAt, } const writeResult: AgentFileWriteDto = { ok: true, state: 'saved', message: 'Saved and verified.', file, verified: true, idempotencyKey: 'idem-1', correlationId: 'corr-1', completedAt: file.checkedAt, } client.setQueryData(queryKeys.agentFiles('iris'), collection) client.setQueryData(queryKeys.agentFile('iris', 'SOUL.md'), previousFile) client.setQueryData(queryKeys.agentFile('iris', 'TOOLS.md'), { marker: 'other' }) await applyAgentFileWrite(client, 'iris', writeResult) expect(client.getQueryData(queryKeys.agentFile('iris', 'SOUL.md'))).toEqual(file) expect(client.getQueryData(queryKeys.agentFiles('iris'))) .toMatchObject({ checkedAt: file.checkedAt, files: [{ name: 'SOUL.md', missing: false, contentHash: 'hash-1' }], }) expect(client.getQueryState(queryKeys.agentFiles('iris'))?.isInvalidated).toBe(true) expect(client.getQueryState(queryKeys.agentFile('iris', 'SOUL.md'))?.isInvalidated) .toBe(true) expect(client.getQueryState(queryKeys.agentFile('iris', 'TOOLS.md'))?.isInvalidated) .toBe(false) }) })