118 lines
3.7 KiB
TypeScript
118 lines
3.7 KiB
TypeScript
|
|
import assert from 'node:assert/strict';
|
||
|
|
import test from 'node:test';
|
||
|
|
|
||
|
|
import { createProject } from '@/modules/projects/services/project-management.service.js';
|
||
|
|
import { AppError } from '@/shared/utils.js';
|
||
|
|
|
||
|
|
const projectRow = {
|
||
|
|
project_id: 'project-1',
|
||
|
|
project_path: '/workspace/my-project',
|
||
|
|
custom_project_name: 'my-project',
|
||
|
|
isStarred: 0,
|
||
|
|
isArchived: 0,
|
||
|
|
};
|
||
|
|
|
||
|
|
test('createProject throws when project path is missing', async () => {
|
||
|
|
await assert.rejects(
|
||
|
|
async () => createProject({ projectPath: '' }),
|
||
|
|
(error: unknown) => {
|
||
|
|
assert.ok(error instanceof AppError);
|
||
|
|
assert.equal(error.code, 'PROJECT_PATH_REQUIRED');
|
||
|
|
assert.equal(error.statusCode, 400);
|
||
|
|
return true;
|
||
|
|
},
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('createProject throws when path validation fails', async () => {
|
||
|
|
await assert.rejects(
|
||
|
|
async () =>
|
||
|
|
createProject(
|
||
|
|
{ projectPath: '/invalid/path' },
|
||
|
|
{
|
||
|
|
validatePath: async () => ({ valid: false, error: 'blocked path' }),
|
||
|
|
ensureWorkspaceDirectory: async () => undefined,
|
||
|
|
persistProjectPath: () => ({ outcome: 'created', project: projectRow }),
|
||
|
|
getProjectByPath: () => projectRow,
|
||
|
|
},
|
||
|
|
),
|
||
|
|
(error: unknown) => {
|
||
|
|
assert.ok(error instanceof AppError);
|
||
|
|
assert.equal(error.code, 'INVALID_PROJECT_PATH');
|
||
|
|
assert.equal(error.statusCode, 400);
|
||
|
|
assert.equal(error.details, 'blocked path');
|
||
|
|
return true;
|
||
|
|
},
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('createProject throws conflict when active project path already exists', async () => {
|
||
|
|
await assert.rejects(
|
||
|
|
async () =>
|
||
|
|
createProject(
|
||
|
|
{ projectPath: '/workspace/my-project' },
|
||
|
|
{
|
||
|
|
validatePath: async () => ({ valid: true, resolvedPath: '/workspace/my-project' }),
|
||
|
|
ensureWorkspaceDirectory: async () => undefined,
|
||
|
|
persistProjectPath: () => ({ outcome: 'active_conflict', project: projectRow }),
|
||
|
|
getProjectByPath: () => projectRow,
|
||
|
|
},
|
||
|
|
),
|
||
|
|
(error: unknown) => {
|
||
|
|
assert.ok(error instanceof AppError);
|
||
|
|
assert.equal(error.code, 'PROJECT_ALREADY_EXISTS');
|
||
|
|
assert.equal(error.statusCode, 409);
|
||
|
|
assert.equal(error.details, 'Project path already exists: /workspace/my-project');
|
||
|
|
return true;
|
||
|
|
},
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('createProject falls back to directory name when custom name is not provided', async () => {
|
||
|
|
let capturedCustomName: string | null = null;
|
||
|
|
|
||
|
|
const result = await createProject(
|
||
|
|
{ projectPath: '/workspace/my-project', customName: '' },
|
||
|
|
{
|
||
|
|
validatePath: async () => ({ valid: true, resolvedPath: '/workspace/my-project' }),
|
||
|
|
ensureWorkspaceDirectory: async () => undefined,
|
||
|
|
persistProjectPath: (_projectPath, customName) => {
|
||
|
|
capturedCustomName = customName;
|
||
|
|
return {
|
||
|
|
outcome: 'created',
|
||
|
|
project: {
|
||
|
|
...projectRow,
|
||
|
|
custom_project_name: customName,
|
||
|
|
},
|
||
|
|
};
|
||
|
|
},
|
||
|
|
getProjectByPath: () => projectRow,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
assert.equal(capturedCustomName, 'my-project');
|
||
|
|
assert.equal(result.outcome, 'created');
|
||
|
|
assert.equal(result.project.displayName, 'my-project');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('createProject returns archived reuse outcome when archived row is reused', async () => {
|
||
|
|
const result = await createProject(
|
||
|
|
{ projectPath: '/workspace/my-project' },
|
||
|
|
{
|
||
|
|
validatePath: async () => ({ valid: true, resolvedPath: '/workspace/my-project' }),
|
||
|
|
ensureWorkspaceDirectory: async () => undefined,
|
||
|
|
persistProjectPath: () => ({
|
||
|
|
outcome: 'reactivated_archived',
|
||
|
|
project: {
|
||
|
|
...projectRow,
|
||
|
|
isArchived: 1,
|
||
|
|
},
|
||
|
|
}),
|
||
|
|
getProjectByPath: () => projectRow,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
assert.equal(result.outcome, 'reactivated_archived');
|
||
|
|
assert.equal(result.project.isArchived, true);
|
||
|
|
});
|