Files
makelore/tests/unit/coding-plugins-store.test.ts

115 lines
5.4 KiB
TypeScript

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<typeof projection>) => void;
const list = vi.fn(() => new Promise<ReturnType<typeof projection>>((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<string, {
resolve: (value: ReturnType<typeof projection>) => void;
reject: (reason: Error) => void;
}>();
const list = vi.fn((projectId: string) => new Promise<ReturnType<typeof projection>>((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);
});
});