Files
Agent-System-Frontend/test/open-conversation-no-project.test.ts
T
agent-exec-transport 16a059381c
governance / governance (pull_request) Successful in 23s
ci / test-and-build (pull_request) Successful in 31s
agent-exec transport ce569400d5a47d09bea58e39b89b8843/8043305a983f
2026-10-20 15:38:38 +00:00

65 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ps-frontend-no-project-surfaces-r2:openConversation 在 state.currentProjectId 为空时
// 不再触发 /api/projects/<id>/surfaces 请求(避免被移除项目成员打开历史会话时的 404 噪音)。
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { api } from '../src/api'
import { store } from '../src/store'
import type { Conversation, Message, SurfaceMeta } from '../src/contracts'
function detail(id: string) {
return {
conversation: { id, projectId: '', title: id, createdAt: '', updatedAt: '' } as Conversation,
messages: [] as Message[], runs: [], activeHitl: [], lastSeq: 0, resumeSeq: 0,
}
}
class FakeEventSource {
onopen = null
onerror = null
onmessage = null
addEventListener() {}
close() {}
}
let projectSurfacesSpy: ReturnType<typeof vi.spyOn>
beforeEach(async () => {
vi.stubGlobal('EventSource', FakeEventSource)
vi.spyOn(api, 'conversationDetail').mockImplementation(async id => detail(id))
vi.spyOn(api, 'getPlan').mockResolvedValue(null)
vi.spyOn(api, 'getTodos').mockResolvedValue(null)
vi.spyOn(api, 'listCards').mockResolvedValue([])
vi.spyOn(api, 'listTasks').mockResolvedValue([])
vi.spyOn(api, 'listArtifacts').mockResolvedValue([])
vi.spyOn(api, 'surfaceList').mockResolvedValue([])
projectSurfacesSpy = vi.spyOn(api, 'projectSurfaces').mockResolvedValue([])
vi.spyOn(api, 'listProjectCards').mockResolvedValue([])
// 还原至 currentProjectId = null 的干净投影,避免别的套件残留影响本次断言。
store.resetIdentity()
})
afterEach(() => { vi.useRealTimers(); vi.restoreAllMocks(); vi.unstubAllGlobals() })
describe('openConversation projectSurfaces gating', () => {
it('skips the /api/projects/.../surfaces request and keeps projectSurfaces empty when no project is current', async () => {
expect(store.getState().currentProjectId).toBeNull()
await store.openConversation('cnv_no_project')
// 让微任务(包含 surfaceList/Stream 等链)落定,但不改变当前结论。
await new Promise(r => setTimeout(r, 0))
expect(projectSurfacesSpy).not.toHaveBeenCalled()
expect(store.getState().projectSurfaces).toEqual([])
expect(store.getState().currentProjectId).toBeNull()
})
it('still requests /api/projects/<id>/surfaces when a project is current', async () => {
const metas: SurfaceMeta[] = [{ surfaceId: 'surf_1', title: '页面一', pluginId: 'plg_1', revision: 1, updatedAt: '2026-10-12T00:00:00Z' }]
projectSurfacesSpy.mockResolvedValueOnce(metas)
await store.openProject('prj_with_surfaces')
expect(store.getState().currentProjectId).toBe('prj_with_surfaces')
projectSurfacesSpy.mockClear()
await store.openConversation('cnv_with_project')
expect(projectSurfacesSpy).toHaveBeenCalledTimes(1)
expect(projectSurfacesSpy.mock.calls[0][0]).toBe('prj_with_surfaces')
// 微任务处理完 .then 后,projectSurfaces 投影被填进服务端的元数据列表。
await new Promise(r => setTimeout(r, 0))
expect(store.getState().projectSurfaces).toEqual(metas)
})
})