// @vitest-environment happy-dom import { createPinia, setActivePinia } from 'pinia'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { useUserStore } from '@easyflow/stores'; import { allocateAgentComposerSession, deleteAgentComposerDraft, getAgentComposerDraft, saveAgentComposerDraft, } from './mediaApi'; import { useAgentComposerDraft } from './useAgentComposerDraft'; vi.mock('./mediaApi', () => ({ allocateAgentComposerSession: vi.fn(), deleteAgentChatImage: vi.fn(), deleteAgentComposerDraft: vi.fn(), getAgentComposerDraft: vi.fn(), loadAgentChatImage: vi.fn(), saveAgentComposerDraft: vi.fn(), uploadAgentChatImage: vi.fn(), })); describe('useAgentComposerDraft', () => { beforeEach(() => { setActivePinia(createPinia()); useUserStore().setUserInfo({ avatar: '', id: 'user-1', loginName: 'admin', nickname: '管理员', tenantId: 'tenant-1', }); localStorage.clear(); vi.clearAllMocks(); vi.mocked(allocateAgentComposerSession).mockResolvedValue({ data: { mode: 'FORMAL', sessionId: '100' }, errorCode: 0, }); vi.mocked(getAgentComposerDraft).mockResolvedValue({ data: null, errorCode: 0, }); vi.mocked(deleteAgentComposerDraft).mockResolvedValue({ data: undefined, errorCode: 0, }); vi.mocked(saveAgentComposerDraft).mockImplementation(async (draft) => ({ data: { ...draft, expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), revision: draft.revision + 1, }, errorCode: 0, })); }); it('restores an unsynced local shadow and writes it back to Redis', async () => { vi.useFakeTimers(); const first = useAgentComposerDraft('FORMAL'); await first.activate('agent-1'); first.text.value = '刷新前尚未发送的内容'; first.scheduleSave(); const restored = useAgentComposerDraft('FORMAL'); await restored.activate('agent-1'); expect(restored.sessionId.value).toBe('100'); expect(restored.text.value).toBe('刷新前尚未发送的内容'); expect(saveAgentComposerDraft).toHaveBeenCalledWith( expect.objectContaining({ agentId: 'agent-1', sessionId: '100', text: '刷新前尚未发送的内容', }), ); vi.useRealTimers(); }); it('keeps local shadows isolated by the logged-in account', async () => { vi.useFakeTimers(); const first = useAgentComposerDraft('FORMAL'); await first.activate('agent-1'); first.text.value = '账号一的草稿'; first.scheduleSave(); useUserStore().setUserInfo({ avatar: '', id: 'user-2', loginName: 'other', nickname: '其他用户', tenantId: 'tenant-1', }); vi.mocked(allocateAgentComposerSession).mockResolvedValueOnce({ data: { mode: 'FORMAL', sessionId: '200' }, errorCode: 0, }); const second = useAgentComposerDraft('FORMAL'); await second.activate('agent-1'); expect(second.sessionId.value).toBe('200'); expect(second.text.value).toBe(''); vi.useRealTimers(); }); it('serializes a debounced save and the send-time flush', async () => { vi.useFakeTimers(); let resolveFirstSave: (() => void) | undefined; vi.mocked(saveAgentComposerDraft).mockImplementation((draft) => { if (!resolveFirstSave) { return new Promise((resolve) => { resolveFirstSave = () => resolve({ data: { ...draft, revision: 1 }, errorCode: 0, }); }); } return Promise.resolve({ data: { ...draft, revision: draft.revision + 1 }, errorCode: 0, }); }); const composer = useAgentComposerDraft('FORMAL'); await composer.activate('agent-1'); composer.text.value = '并发保存内容'; composer.scheduleSave(); await vi.advanceTimersByTimeAsync(500); await Promise.resolve(); const flushPromise = composer.flush(); await Promise.resolve(); expect(saveAgentComposerDraft).toHaveBeenCalledTimes(1); resolveFirstSave?.(); await flushPromise; expect(saveAgentComposerDraft).toHaveBeenCalledTimes(2); expect(saveAgentComposerDraft).toHaveBeenLastCalledWith( expect.objectContaining({ revision: 1, text: '并发保存内容' }), ); vi.useRealTimers(); }); it('waits for an in-flight save before clearing an accepted draft', async () => { vi.useFakeTimers(); let resolveSave: (() => void) | undefined; vi.mocked(saveAgentComposerDraft).mockImplementation( (draft) => new Promise((resolve) => { resolveSave = () => resolve({ data: { ...draft, revision: 1 }, errorCode: 0, }); }), ); const composer = useAgentComposerDraft('DRAFT'); await composer.activate('agent-1', 'agent-draft-100'); composer.text.value = '试运行内容'; composer.scheduleSave(); await vi.advanceTimersByTimeAsync(500); await Promise.resolve(); const accepted = composer.markAccepted(); await Promise.resolve(); expect(deleteAgentComposerDraft).not.toHaveBeenCalled(); resolveSave?.(); await accepted; expect(deleteAgentComposerDraft).toHaveBeenCalledWith({ agentId: 'agent-1', deleteUploads: false, imageUploadIds: [], mode: 'DRAFT', sessionId: 'agent-draft-100', }); expect(composer.text.value).toBe(''); vi.useRealTimers(); }); });