89 lines
3.7 KiB
TypeScript
89 lines
3.7 KiB
TypeScript
// @vitest-environment node
|
|
|
|
import { mkdtemp, rm } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import path from 'node:path';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import type { AgentBrowserModule } from '../../electron/agent-browser';
|
|
import { createCodingComposition } from '../../electron/api/coding-composition';
|
|
import { createProjectPluginService } from '../../electron/coding-plugins/project-service';
|
|
import { readCodingProjectConfigV2 } from '../../electron/coding-projects/project-config';
|
|
import {
|
|
createCodingProjectStore,
|
|
createLocalCodingProject,
|
|
createMemoryCodingProjectStorage,
|
|
} from '../../electron/coding-projects/project-store';
|
|
import { DATA_SERVICE_PLUGIN_ID } from '../../shared/coding-plugins';
|
|
|
|
const roots: string[] = [];
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
});
|
|
|
|
describe('coding plugin lifecycle wiring', () => {
|
|
it('marks managed inputs stale and invalidates/deactivates only after a real disable', async () => {
|
|
const root = await mkdtemp(path.join(tmpdir(), 'makelore-plugin-lifecycle-'));
|
|
roots.push(root);
|
|
const stale = vi.fn();
|
|
const invalidate = vi.fn();
|
|
const deactivate = vi.fn().mockResolvedValue(undefined);
|
|
const service = createProjectPluginService({
|
|
now: () => '2026-08-27T00:00:00.000Z',
|
|
onManagedInputsChanged: stale,
|
|
onAdapterDeactivated: async ({ projectPath }) => {
|
|
invalidate('project_deactivated');
|
|
await deactivate(projectPath);
|
|
},
|
|
});
|
|
|
|
await service.enable(root, DATA_SERVICE_PLUGIN_ID);
|
|
await service.disable(root, DATA_SERVICE_PLUGIN_ID);
|
|
await service.disable(root, DATA_SERVICE_PLUGIN_ID);
|
|
|
|
expect(stale).toHaveBeenCalledTimes(2);
|
|
expect(stale.mock.calls.map(([event]) => event.revision)).toEqual([1, 2]);
|
|
expect(invalidate).toHaveBeenCalledOnce();
|
|
expect(deactivate).toHaveBeenCalledOnce();
|
|
expect(deactivate).toHaveBeenCalledWith(path.resolve(root));
|
|
});
|
|
|
|
it('deactivates before identity write and before runtime shutdown', async () => {
|
|
const projectPath = await mkdtemp(path.join(tmpdir(), 'makelore-plugin-identity-'));
|
|
const userDataDir = await mkdtemp(path.join(tmpdir(), 'makelore-plugin-runtime-'));
|
|
roots.push(projectPath, userDataDir);
|
|
const storage = createMemoryCodingProjectStorage();
|
|
const store = createCodingProjectStore(storage);
|
|
const legacy = await createLocalCodingProject({ projectPath }, store);
|
|
const composition = createCodingComposition({
|
|
storage,
|
|
projectStore: store,
|
|
browser: { close: vi.fn().mockResolvedValue(undefined) } as unknown as AgentBrowserModule,
|
|
paths: {
|
|
executablePath: process.execPath,
|
|
cliPath: path.join(projectPath, 'unused-cli.js'),
|
|
userDataDir,
|
|
bundledSkillsDir: path.resolve('resources/coding-skills'),
|
|
},
|
|
});
|
|
const secondId = '22222222-2222-4222-8222-222222222222';
|
|
const order: string[] = [];
|
|
vi.spyOn(composition.plugins, 'deactivate').mockImplementation(async () => {
|
|
const snapshot = await readCodingProjectConfigV2(projectPath);
|
|
order.push(`deactivate:${snapshot.status === 'valid' ? snapshot.config.projectId : 'invalid'}`);
|
|
});
|
|
vi.spyOn(composition.runtime, 'shutdown').mockImplementation(async () => {
|
|
order.push('runtime-shutdown');
|
|
});
|
|
|
|
await composition.projects.resolveProjectIdentity(legacy.project.id, {
|
|
kind: 'bind', projectId: secondId,
|
|
});
|
|
await composition.shutdown();
|
|
|
|
expect(order[0]).toBe('deactivate:undefined');
|
|
expect(order).toContain(`deactivate:${secondId}`);
|
|
expect(order.at(-1)).toBe('runtime-shutdown');
|
|
});
|
|
});
|