feat: phase 1 of python scripting

This commit is contained in:
2026-02-22 22:12:30 +01:00
parent ce050f98c3
commit 3ec8819d6d
43 changed files with 2329 additions and 14 deletions

View File

@@ -158,6 +158,16 @@ const mockPostMediaEngine = {
rebuildFromSidecars: vi.fn(),
};
const mockScriptEngine = {
on: vi.fn(),
setProjectContext: vi.fn(),
createScript: vi.fn(),
updateScript: vi.fn(),
deleteScript: vi.fn(),
getScript: vi.fn(),
getAllScripts: vi.fn(),
};
const mockGitEngine = {
checkAvailability: vi.fn(),
getHeadCommit: vi.fn(),
@@ -263,6 +273,10 @@ vi.mock('../../src/main/engine/PostMediaEngine', () => ({
getPostMediaEngine: vi.fn(() => mockPostMediaEngine),
}));
vi.mock('../../src/main/engine/ScriptEngine', () => ({
getScriptEngine: vi.fn(() => mockScriptEngine),
}));
vi.mock('../../src/main/engine/GitEngine', () => ({
getGitEngine: vi.fn(() => mockGitEngine),
}));
@@ -2593,6 +2607,113 @@ describe('IPC Handlers', () => {
});
});
// ============ Script Handlers ============
describe('Script Handlers', () => {
describe('scripts:create', () => {
it('should call ScriptEngine.createScript with payload', async () => {
const payload = {
title: 'Render Hero',
kind: 'macro',
content: 'def render(context):\n return {"html":"<h1>Hi</h1>"}',
};
const expected = {
id: 'script-1',
projectId: 'default',
...payload,
slug: 'render-hero',
entrypoint: 'render',
enabled: true,
version: 1,
filePath: '/mock/userData/projects/default/scripts/render-hero.py',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
mockScriptEngine.createScript.mockResolvedValue(expected);
const result = await invokeHandler('scripts:create', payload);
expect(mockScriptEngine.createScript).toHaveBeenCalledWith(payload);
expect(result).toEqual(expected);
});
});
describe('scripts:update', () => {
it('should call ScriptEngine.updateScript with id and updates', async () => {
const updates = { title: 'Updated Script', content: 'print("updated")' };
const expected = {
id: 'script-1',
projectId: 'default',
slug: 'updated-script',
title: 'Updated Script',
kind: 'utility',
entrypoint: 'render',
enabled: true,
version: 2,
filePath: '/mock/userData/projects/default/scripts/updated-script.py',
content: 'print("updated")',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
mockScriptEngine.updateScript.mockResolvedValue(expected);
const result = await invokeHandler('scripts:update', 'script-1', updates);
expect(mockScriptEngine.updateScript).toHaveBeenCalledWith('script-1', updates);
expect(result).toEqual(expected);
});
});
describe('scripts:delete', () => {
it('should call ScriptEngine.deleteScript with id', async () => {
mockScriptEngine.deleteScript.mockResolvedValue(true);
const result = await invokeHandler('scripts:delete', 'script-1');
expect(mockScriptEngine.deleteScript).toHaveBeenCalledWith('script-1');
expect(result).toBe(true);
});
});
describe('scripts:get', () => {
it('should call ScriptEngine.getScript with id', async () => {
const expected = {
id: 'script-1',
projectId: 'default',
slug: 'render-hero',
title: 'Render Hero',
kind: 'macro',
entrypoint: 'render',
enabled: true,
version: 1,
filePath: '/mock/userData/projects/default/scripts/render-hero.py',
content: 'def render(context):\n return {"html":"<h1>Hi</h1>"}',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
mockScriptEngine.getScript.mockResolvedValue(expected);
const result = await invokeHandler('scripts:get', 'script-1');
expect(mockScriptEngine.getScript).toHaveBeenCalledWith('script-1');
expect(result).toEqual(expected);
});
});
describe('scripts:getAll', () => {
it('should call ScriptEngine.getAllScripts', async () => {
const expected = [{ id: 'script-1' }, { id: 'script-2' }];
mockScriptEngine.getAllScripts.mockResolvedValue(expected);
const result = await invokeHandler('scripts:getAll');
expect(mockScriptEngine.getAllScripts).toHaveBeenCalled();
expect(result).toEqual(expected);
});
});
});
// ============ Error Handling ============
describe('Error Handling', () => {
it('should silently handle "Database is closing" errors', async () => {