fix(plugin): remediate ML-07 R2 findings
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { createCodingPluginsStore } from '@/stores/coding-plugins';
|
||||
import type {
|
||||
DataServiceCollectionRemoval,
|
||||
DataServiceHostResult,
|
||||
DataServiceInstanceRemoval,
|
||||
DataServiceInstanceState,
|
||||
} from '../../shared/data-service';
|
||||
|
||||
function projection(enabled = false, projectId = 'local-project') {
|
||||
return {
|
||||
@@ -15,6 +21,31 @@ function projection(enabled = false, projectId = 'local-project') {
|
||||
};
|
||||
}
|
||||
|
||||
function deferred<T>() {
|
||||
let resolve!: (value: T) => void;
|
||||
const promise = new Promise<T>((done) => { resolve = done; });
|
||||
return { promise, resolve };
|
||||
}
|
||||
|
||||
function dataServiceInstance(instanceId: string): DataServiceInstanceState {
|
||||
return {
|
||||
instance_id: instanceId, project_id: 'cloud-project', collections: [],
|
||||
usage: { document_count: 0, total_bytes: 0 },
|
||||
limits: {
|
||||
max_collections: 20, max_documents: 1000, max_total_bytes: 20_971_520,
|
||||
max_document_bytes: 65_536, list_default_limit: 50, list_max_limit: 100,
|
||||
list_max_data_bytes: 1_048_576, mutations_per_minute: 120,
|
||||
},
|
||||
created_at: '2026-08-27T00:00:00Z', updated_at: '2026-08-27T00:00:00Z',
|
||||
};
|
||||
}
|
||||
|
||||
function success<T>(data: T): DataServiceHostResult<T> {
|
||||
return {
|
||||
success: true, status: 200, code: null, error: null, retryable: false, data,
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -87,6 +118,66 @@ describe('coding plugins store', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('does not let a late project A enable mutation overwrite loaded project B', async () => {
|
||||
const enabledA = deferred<ReturnType<typeof projection>>();
|
||||
const inspectDataService = vi.fn();
|
||||
const store = createCodingPluginsStore({
|
||||
list: vi.fn().mockResolvedValue(projection(false, 'project-b')),
|
||||
setEnabled: vi.fn(() => enabledA.promise),
|
||||
inspectDataService,
|
||||
});
|
||||
store.setState({ projectId: 'project-a', projection: projection(false, 'project-a') });
|
||||
|
||||
const mutation = store.getState().setEnabled('project-a', 'makelore.data-service', true);
|
||||
await store.getState().load('project-b');
|
||||
enabledA.resolve(projection(true, 'project-a'));
|
||||
await mutation;
|
||||
|
||||
expect(store.getState()).toMatchObject({
|
||||
projectId: 'project-b', projection: { project: { localProjectId: 'project-b' } },
|
||||
});
|
||||
expect(inspectDataService).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not let adjacent late Data Service mutations commit or reload over project B', async () => {
|
||||
const configure = deferred<DataServiceHostResult<DataServiceInstanceState>>();
|
||||
const reset = deferred<DataServiceHostResult<DataServiceInstanceState>>();
|
||||
const removeCollection = deferred<DataServiceHostResult<DataServiceCollectionRemoval>>();
|
||||
const removeProject = deferred<DataServiceHostResult<DataServiceInstanceRemoval>>();
|
||||
const inspectDataService = vi.fn();
|
||||
const list = vi.fn().mockResolvedValue(projection(false, 'project-b'));
|
||||
const store = createCodingPluginsStore({
|
||||
list,
|
||||
inspectDataService,
|
||||
configureDataService: vi.fn(() => configure.promise),
|
||||
resetDataService: vi.fn(() => reset.promise),
|
||||
removeCollection: vi.fn(() => removeCollection.promise),
|
||||
removeProject: vi.fn(() => removeProject.promise),
|
||||
});
|
||||
store.setState({ projectId: 'project-a', projection: projection(false, 'project-a') });
|
||||
|
||||
const mutations = [
|
||||
store.getState().configure(['todos']),
|
||||
store.getState().reset(),
|
||||
store.getState().removeCollection('todos'),
|
||||
store.getState().removeProject(),
|
||||
];
|
||||
await store.getState().load('project-b');
|
||||
configure.resolve(success(dataServiceInstance('late-configure')));
|
||||
reset.resolve(success(dataServiceInstance('late-reset')));
|
||||
removeCollection.resolve(success({ removed: true, usage: { document_count: 0, total_bytes: 0 } }));
|
||||
removeProject.resolve(success({ removed: true }));
|
||||
await Promise.all(mutations);
|
||||
|
||||
expect(store.getState()).toMatchObject({
|
||||
projectId: 'project-b',
|
||||
projection: { project: { localProjectId: 'project-b' } },
|
||||
dataService: null,
|
||||
});
|
||||
expect(inspectDataService).not.toHaveBeenCalled();
|
||||
expect(list).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('retains ready Data Service usage when enabling an existing instance', async () => {
|
||||
const readyProjection = projection(true);
|
||||
readyProjection.items[0] = {
|
||||
@@ -104,6 +195,7 @@ describe('coding plugins store', () => {
|
||||
const store = createCodingPluginsStore({
|
||||
setEnabled: vi.fn().mockResolvedValue(readyProjection), inspectDataService,
|
||||
});
|
||||
store.setState({ projectId: 'local-project' });
|
||||
|
||||
await store.getState().setEnabled('local-project', 'makelore.data-service', true);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user