import { describe, expect, it, vi } from 'vitest'; import { createCodingPluginsStore } from '@/stores/coding-plugins'; function projection(enabled = false, projectId = 'local-project') { return { schemaVersion: 1 as const, project: { localProjectId: projectId, durableProjectId: null }, policyStatus: 'current' as const, items: [{ id: 'makelore.data-service', version: '1.0.0', displayName: '开发数据服务', description: '项目数据', enabled, state: enabled ? 'configuration_required' as const : 'disabled' as const, backend: { status: 'unconfigured' as const }, skills: [{ id: 'data-service', assignedAgentIds: [] }], capabilities: [], settingsSurface: 'data-service', }], }; } describe('coding plugins store', () => { it('coalesces duplicate loads and mutations while retaining the last projection on failure', async () => { let resolveLoad!: (value: ReturnType) => void; const list = vi.fn(() => new Promise>((resolve) => { resolveLoad = resolve; })); const setEnabled = vi.fn().mockResolvedValue(projection(true)); const store = createCodingPluginsStore({ list, setEnabled }); const first = store.getState().load('local-project'); const second = store.getState().load('local-project'); resolveLoad(projection()); await Promise.all([first, second]); expect(list).toHaveBeenCalledOnce(); await Promise.all([ store.getState().setEnabled('local-project', 'makelore.data-service', true), store.getState().setEnabled('local-project', 'makelore.data-service', true), ]); expect(setEnabled).toHaveBeenCalledOnce(); expect(store.getState().projection?.items[0].enabled).toBe(true); list.mockRejectedValueOnce(new Error('offline')); await expect(store.getState().load('other-project')).rejects.toThrow('offline'); expect(store.getState().projection?.project.localProjectId).toBe('local-project'); expect(store.getState().loadState).toBe('error'); }); it('clears project-scoped Data Service data when another project loads', async () => { const store = createCodingPluginsStore({ list: vi.fn().mockResolvedValue(projection(false, 'other-project')) }); store.setState({ projectId: 'local-project', dataService: { instance_id: 'instance-1', project_id: 'cloud-project', collections: [], usage: { document_count: 0, total_bytes: 0 }, limits: { max_collections: 20, max_documents: 1000, max_total_bytes: 20971520, max_document_bytes: 65536, list_default_limit: 50, list_max_limit: 100, list_max_data_bytes: 1048576, mutations_per_minute: 120 }, created_at: '2026-08-27T00:00:00Z', updated_at: '2026-08-27T00:00:00Z', }, }); await store.getState().load('other-project'); expect(store.getState().dataService).toBeNull(); }); it('commits only the latest project load success or error', async () => { const pending = new Map) => void; reject: (reason: Error) => void; }>(); const list = vi.fn((projectId: string) => new Promise>((resolve, reject) => { pending.set(projectId, { resolve, reject }); })); const store = createCodingPluginsStore({ list }); const first = store.getState().load('project-a'); const second = store.getState().load('project-b'); pending.get('project-b')?.resolve(projection(false, 'project-b')); await second; pending.get('project-a')?.resolve(projection(false, 'project-a')); await first; expect(store.getState()).toMatchObject({ projectId: 'project-b', loadState: 'ready', error: null, }); const third = store.getState().load('project-a'); const fourth = store.getState().load('project-b'); pending.get('project-b')?.resolve(projection(false, 'project-b')); await fourth; pending.get('project-a')?.reject(new Error('late project-a failure')); await expect(third).rejects.toThrow('late project-a failure'); expect(store.getState()).toMatchObject({ projectId: 'project-b', loadState: 'ready', error: null, }); }); it('retains ready Data Service usage when enabling an existing instance', async () => { const readyProjection = projection(true); readyProjection.items[0] = { ...readyProjection.items[0], state: 'ready', backend: { status: 'ready' }, }; const existing = { instance_id: 'instance-1', project_id: 'cloud-project', collections: ['todos'], usage: { document_count: 3, total_bytes: 128 }, limits: { max_collections: 20, max_documents: 1000, max_total_bytes: 20971520, max_document_bytes: 65536, list_default_limit: 50, list_max_limit: 100, list_max_data_bytes: 1048576, mutations_per_minute: 120 }, created_at: '2026-08-27T00:00:00Z', updated_at: '2026-08-27T00:00:00Z', }; const inspectDataService = vi.fn().mockResolvedValue({ success: true, status: 200, code: null, error: null, retryable: false, data: existing, }); const store = createCodingPluginsStore({ setEnabled: vi.fn().mockResolvedValue(readyProjection), inspectDataService, }); await store.getState().setEnabled('local-project', 'makelore.data-service', true); expect(inspectDataService).toHaveBeenCalledOnce(); expect(store.getState().dataService).toEqual(existing); expect(store.getState().projection).toEqual(readyProjection); }); });