Files
makelore/tests/unit/coding-plugin-lifecycle.test.ts

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');
});
});