fix: reopen preserved coding projects when selecting their folder
This commit is contained in:
@@ -6,7 +6,9 @@ import path from 'node:path';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { HostApiContext } from '../../electron/api/context';
|
||||
import { dispatchHostApiRequest } from '../../electron/api/host-api-dispatcher';
|
||||
import { createCodingConversationStore } from '../../electron/coding-projects/conversation-store';
|
||||
import {
|
||||
createCodingProjectAgent,
|
||||
createCodingProjectConfigV2,
|
||||
isCanonicalCodingProjectId,
|
||||
normalizeCodingProjectConfigV2,
|
||||
@@ -45,6 +47,92 @@ function makeStore(localId = 'local-project-id') {
|
||||
}
|
||||
|
||||
describe('coding project durable identity', () => {
|
||||
it('reopens a removed project when its folder is selected for creation again', async () => {
|
||||
const projectPath = await makeRoot();
|
||||
const store = makeStore();
|
||||
const projects = new CodingProjectService(store, {
|
||||
createProjectId: vi.fn().mockReturnValueOnce(PROJECT_ID).mockReturnValue(NEXT_PROJECT_ID),
|
||||
});
|
||||
const input = { projectPath, projectType: 'interactive_ai_app' as const, identity: { kind: 'create' as const } };
|
||||
const created = await projects.createProject({ ...input, projectType: 'custom' });
|
||||
const agent = await createCodingProjectAgent(projectPath, {
|
||||
id: 'builder', avatarId: 'avatar-01', roleName: 'Builder', name: 'My builder',
|
||||
model: null, modelResolution: 'required', prompt: 'Keep my instructions', skillIds: [],
|
||||
responsibility: { mission: 'Build my app', owns: [], boundaries: [], collaborators: [], principles: [] },
|
||||
});
|
||||
const conversations = createCodingConversationStore(projectPath);
|
||||
const conversation = await conversations.create({
|
||||
agentId: agent.id, title: 'Existing work', model: null, modelResolution: 'required',
|
||||
});
|
||||
const conversationPath = path.join(projectPath, '.makelore', 'conversations.json');
|
||||
const originalConversations = await readFile(conversationPath, 'utf8');
|
||||
const configPath = path.join(projectPath, '.makelore', 'project.json');
|
||||
const originalConfig = await readFile(configPath, 'utf8');
|
||||
await writeFile(path.join(projectPath, 'knowledge', 'notes.md'), 'Keep my project notes');
|
||||
|
||||
await projects.removeProject(created.project.id);
|
||||
expect(await projects.listProjects()).toEqual([]);
|
||||
expect(await projects.getActiveProject()).toBeNull();
|
||||
|
||||
const reopened = await projects.createProject(input);
|
||||
expect(reopened.config.projectId).toBe(PROJECT_ID);
|
||||
expect(reopened.config.projectType).toBe('custom');
|
||||
expect(reopened.config.agents).toEqual([agent]);
|
||||
expect(reopened.knowledgeFiles).toEqual(['notes.md']);
|
||||
expect(await projects.getActiveProject()).toEqual(reopened.project);
|
||||
expect(await projects.listProjects()).toEqual([reopened.project]);
|
||||
expect(await readFile(configPath, 'utf8')).toBe(originalConfig);
|
||||
expect(await readFile(conversationPath, 'utf8')).toBe(originalConversations);
|
||||
expect((await createCodingConversationStore(reopened.project.path).read()).conversations).toEqual([conversation]);
|
||||
expect(await readFile(path.join(projectPath, 'knowledge', 'notes.md'), 'utf8')).toBe('Keep my project notes');
|
||||
|
||||
const repeated = await projects.createProject(input);
|
||||
expect(repeated.project.id).toBe(reopened.project.id);
|
||||
expect(await projects.listProjects()).toEqual([repeated.project]);
|
||||
});
|
||||
|
||||
it('keeps child-directory collisions and explicit identity binding as creation errors', async () => {
|
||||
const parentPath = await makeRoot();
|
||||
const projectPath = path.join(parentPath, 'existing');
|
||||
const projects = new CodingProjectService(makeStore(), { createProjectId: () => PROJECT_ID });
|
||||
const created = await projects.createProject({ projectPath, identity: { kind: 'create' } });
|
||||
await projects.removeProject(created.project.id);
|
||||
|
||||
await expect(projects.createProject({
|
||||
parentPath, projectName: 'existing', identity: { kind: 'create' },
|
||||
})).rejects.toMatchObject({ status: 409, code: 'CODING_PROJECT_ALREADY_EXISTS' });
|
||||
await expect(projects.createProject({
|
||||
projectPath, identity: { kind: 'bind', projectId: NEXT_PROJECT_ID },
|
||||
})).rejects.toMatchObject({ status: 409, code: 'CODING_PROJECT_ALREADY_EXISTS' });
|
||||
expect(await projects.listProjects()).toEqual([]);
|
||||
expect(await readCodingProjectConfigV2(projectPath)).toMatchObject({ config: { projectId: PROJECT_ID } });
|
||||
});
|
||||
|
||||
it('reports invalid existing metadata without overwriting or registering it', async () => {
|
||||
const projectPath = await makeRoot();
|
||||
await mkdir(path.join(projectPath, '.makelore'));
|
||||
const configPath = path.join(projectPath, '.makelore', 'project.json');
|
||||
await writeFile(configPath, '{invalid json');
|
||||
const projects = new CodingProjectService(makeStore());
|
||||
|
||||
await expect(projects.createProject({ projectPath, identity: { kind: 'create' } }))
|
||||
.rejects.toMatchObject({ status: 409, code: 'CODING_PROJECT_CONFIG_INVALID' });
|
||||
expect(await readFile(configPath, 'utf8')).toBe('{invalid json');
|
||||
expect(await projects.listProjects()).toEqual([]);
|
||||
});
|
||||
|
||||
it('reuses the existing identity-backfill path for a selected legacy project', async () => {
|
||||
const projectPath = await makeRoot();
|
||||
const store = makeStore();
|
||||
const local = await createLocalCodingProject({ projectPath, now: CREATED }, store);
|
||||
const projects = new CodingProjectService(store, { createProjectId: () => PROJECT_ID });
|
||||
await projects.removeProject(local.project.id);
|
||||
|
||||
const reopened = await projects.createProject({ projectPath, identity: { kind: 'create' } });
|
||||
expect(reopened.config).toMatchObject({ projectId: PROJECT_ID, createdAt: CREATED });
|
||||
expect(await readCodingProjectConfigV2(projectPath)).toMatchObject({ config: { projectId: PROJECT_ID } });
|
||||
});
|
||||
|
||||
it.each(['interactive_ai_app', 'custom'] as const)(
|
||||
'creates only project-owned metadata for %s projects',
|
||||
async (projectType) => {
|
||||
|
||||
Reference in New Issue
Block a user