fix: proper project setting

This commit is contained in:
2026-02-28 22:21:01 +01:00
parent aedd013d88
commit 6d2c47b64d
2 changed files with 221 additions and 0 deletions

View File

@@ -74,6 +74,19 @@ async function main(): Promise<void> {
const templateEngine = new TemplateEngine(notifier); const templateEngine = new TemplateEngine(notifier);
const metaEngine = new MetaEngine(); const metaEngine = new MetaEngine();
// 3b. Point every engine at the active project so queries/mutations
// target the correct project instead of the hardcoded 'default'.
const dataDir = activeProject.dataPath
?? path.join(userData, 'projects', activeProject.id);
postEngine.setProjectContext(activeProject.id, dataDir);
mediaEngine.setProjectContext(activeProject.id, dataDir, dataDir);
postMediaEngine.setProjectContext(activeProject.id);
tagEngine.setProjectContext(activeProject.id, dataDir);
scriptEngine.setProjectContext(activeProject.id, dataDir);
templateEngine.setProjectContext(activeProject.id, dataDir);
metaEngine.setProjectContext(activeProject.id, dataDir);
// 4. Create the MCP server with an 8-hour proposal TTL (CLI sessions can // 4. Create the MCP server with an 8-hour proposal TTL (CLI sessions can
// last overnight). // last overnight).
const mcpServer = new MCPServer( const mcpServer = new MCPServer(

View File

@@ -0,0 +1,208 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
/**
* Verifies that the MCP standalone CLI calls setProjectContext() on every
* engine after resolving the active project from the database.
*/
// ── Spies that each mock-engine instance populates ──────────────────────────
let postEngineSetProjectContext: ReturnType<typeof vi.fn>;
let mediaEngineSetProjectContext: ReturnType<typeof vi.fn>;
let postMediaEngineSetProjectContext: ReturnType<typeof vi.fn>;
let tagEngineSetProjectContext: ReturnType<typeof vi.fn>;
let scriptEngineSetProjectContext: ReturnType<typeof vi.fn>;
let templateEngineSetProjectContext: ReturnType<typeof vi.fn>;
let metaEngineSetProjectContext: ReturnType<typeof vi.fn>;
function resetSpies(): void {
postEngineSetProjectContext = vi.fn();
mediaEngineSetProjectContext = vi.fn();
postMediaEngineSetProjectContext = vi.fn();
tagEngineSetProjectContext = vi.fn();
scriptEngineSetProjectContext = vi.fn();
templateEngineSetProjectContext = vi.fn();
metaEngineSetProjectContext = vi.fn();
}
// ── Active project data ─────────────────────────────────────────────────────
const ACTIVE_PROJECT_CUSTOM_PATH = {
id: 'my-blog',
name: 'My Blog',
slug: 'my-blog',
description: 'Test project',
dataPath: '/custom/data/path',
createdAt: new Date(),
updatedAt: new Date(),
isActive: true,
};
const ACTIVE_PROJECT_DEFAULT_PATH = {
id: 'internal-project',
name: 'Internal Project',
slug: 'internal-project',
description: null,
dataPath: null,
createdAt: new Date(),
updatedAt: new Date(),
isActive: true,
};
// ── Chainable query mock ────────────────────────────────────────────────────
let mockActiveProject: typeof ACTIVE_PROJECT_CUSTOM_PATH | typeof ACTIVE_PROJECT_DEFAULT_PATH = ACTIVE_PROJECT_CUSTOM_PATH;
function makeMockLocalDb() {
return {
select: vi.fn().mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
get: vi.fn().mockResolvedValue(mockActiveProject),
}),
}),
}),
};
}
// ── Hoisted mocks ───────────────────────────────────────────────────────────
vi.mock('../../src/cli/platform', () => ({
platformConfigPath: vi.fn(() => '/tmp/mock-userdata'),
}));
vi.mock('../../src/main/database/connection', () => {
const mockDb = {
initializeLocal: vi.fn().mockResolvedValue(undefined),
getLocal: vi.fn(() => makeMockLocalDb()),
close: vi.fn().mockResolvedValue(undefined),
};
return {
initDatabase: vi.fn(() => mockDb),
getDatabase: vi.fn(() => mockDb),
DatabaseConnection: vi.fn(),
};
});
vi.mock('../../src/main/database/schema', () => ({
projects: { isActive: 'isActive' },
}));
vi.mock('drizzle-orm', () => ({
eq: vi.fn((_col: unknown, _val: unknown) => 'isActive=true'),
}));
vi.mock('../../src/main/engine/CliNotifier', () => ({
DbNotifier: vi.fn().mockImplementation(function(this: Record<string, unknown>) { return this; }),
}));
vi.mock('../../src/main/engine/PostEngine', () => ({
PostEngine: vi.fn().mockImplementation(function(this: Record<string, unknown>) {
this.setProjectContext = (...args: unknown[]) => postEngineSetProjectContext(...args);
return this;
}),
}));
vi.mock('../../src/main/engine/MediaEngine', () => ({
MediaEngine: vi.fn().mockImplementation(function(this: Record<string, unknown>) {
this.setProjectContext = (...args: unknown[]) => mediaEngineSetProjectContext(...args);
return this;
}),
}));
vi.mock('../../src/main/engine/PostMediaEngine', () => ({
PostMediaEngine: vi.fn().mockImplementation(function(this: Record<string, unknown>) {
this.setProjectContext = (...args: unknown[]) => postMediaEngineSetProjectContext(...args);
return this;
}),
}));
vi.mock('../../src/main/engine/TagEngine', () => ({
TagEngine: vi.fn().mockImplementation(function(this: Record<string, unknown>) {
this.setProjectContext = (...args: unknown[]) => tagEngineSetProjectContext(...args);
return this;
}),
}));
vi.mock('../../src/main/engine/ScriptEngine', () => ({
ScriptEngine: vi.fn().mockImplementation(function(this: Record<string, unknown>) {
this.setProjectContext = (...args: unknown[]) => scriptEngineSetProjectContext(...args);
return this;
}),
}));
vi.mock('../../src/main/engine/TemplateEngine', () => ({
TemplateEngine: vi.fn().mockImplementation(function(this: Record<string, unknown>) {
this.setProjectContext = (...args: unknown[]) => templateEngineSetProjectContext(...args);
return this;
}),
}));
vi.mock('../../src/main/engine/MetaEngine', () => ({
MetaEngine: vi.fn().mockImplementation(function(this: Record<string, unknown>) {
this.setProjectContext = (...args: unknown[]) => metaEngineSetProjectContext(...args);
return this;
}),
}));
vi.mock('../../src/main/engine/MCPServer', () => ({
MCPServer: vi.fn().mockImplementation(function(this: Record<string, unknown>) {
this.startCli = vi.fn().mockResolvedValue(undefined);
this.cleanup = vi.fn().mockResolvedValue(undefined);
return this;
}),
}));
// ── Tests ───────────────────────────────────────────────────────────────────
describe('bds-mcp project context initialisation', () => {
const originalExit = process.exit;
beforeEach(() => {
resetSpies();
// Prevent process.exit from actually killing the test runner
process.exit = vi.fn() as never;
});
afterEach(() => {
process.exit = originalExit;
vi.restoreAllMocks();
vi.resetModules();
});
it('calls setProjectContext on all engines with the active project (custom dataPath)', async () => {
mockActiveProject = ACTIVE_PROJECT_CUSTOM_PATH;
await import('../../src/cli/bds-mcp');
// Give main() a tick to complete
await new Promise((r) => setTimeout(r, 100));
const expectedProjectId = 'my-blog';
const expectedDataDir = '/custom/data/path';
expect(postEngineSetProjectContext).toHaveBeenCalledWith(expectedProjectId, expectedDataDir);
expect(mediaEngineSetProjectContext).toHaveBeenCalledWith(expectedProjectId, expectedDataDir, expectedDataDir);
expect(postMediaEngineSetProjectContext).toHaveBeenCalledWith(expectedProjectId);
expect(tagEngineSetProjectContext).toHaveBeenCalledWith(expectedProjectId, expectedDataDir);
expect(scriptEngineSetProjectContext).toHaveBeenCalledWith(expectedProjectId, expectedDataDir);
expect(templateEngineSetProjectContext).toHaveBeenCalledWith(expectedProjectId, expectedDataDir);
expect(metaEngineSetProjectContext).toHaveBeenCalledWith(expectedProjectId, expectedDataDir);
});
it('uses internal userData path when project has no custom dataPath', async () => {
mockActiveProject = ACTIVE_PROJECT_DEFAULT_PATH;
await import('../../src/cli/bds-mcp');
await new Promise((r) => setTimeout(r, 100));
const expectedDataDir = '/tmp/mock-userdata/projects/internal-project';
expect(postEngineSetProjectContext).toHaveBeenCalledWith('internal-project', expectedDataDir);
expect(mediaEngineSetProjectContext).toHaveBeenCalledWith('internal-project', expectedDataDir, expectedDataDir);
expect(postMediaEngineSetProjectContext).toHaveBeenCalledWith('internal-project');
expect(tagEngineSetProjectContext).toHaveBeenCalledWith('internal-project', expectedDataDir);
expect(scriptEngineSetProjectContext).toHaveBeenCalledWith('internal-project', expectedDataDir);
expect(templateEngineSetProjectContext).toHaveBeenCalledWith('internal-project', expectedDataDir);
expect(metaEngineSetProjectContext).toHaveBeenCalledWith('internal-project', expectedDataDir);
});
});