fix: next round of cleanups
This commit is contained in:
87
tests/engine/AppApiAdapter.test.ts
Normal file
87
tests/engine/AppApiAdapter.test.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { AppApiAdapter } from '../../src/main/engine/AppApiAdapter';
|
||||
|
||||
const { mockProjectEngine, mockDatabase, mockReadFile } = vi.hoisted(() => ({
|
||||
mockProjectEngine: {
|
||||
getActiveProject: vi.fn().mockResolvedValue({ id: 'p1', dataPath: '/projects/blog' }),
|
||||
getProjectPaths: vi.fn().mockReturnValue({ posts: '/projects/blog/posts', media: '/projects/blog/media' }),
|
||||
getDefaultProjectBaseDir: vi.fn().mockResolvedValue('/home/user/bDS/p1'),
|
||||
},
|
||||
mockDatabase: {
|
||||
getDataPaths: vi.fn().mockReturnValue({ database: '/data/bds.db' }),
|
||||
},
|
||||
mockReadFile: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../src/main/engine/ProjectEngine', () => ({
|
||||
getProjectEngine: () => mockProjectEngine,
|
||||
}));
|
||||
|
||||
vi.mock('../../src/main/database', () => ({
|
||||
getDatabase: () => mockDatabase,
|
||||
}));
|
||||
|
||||
vi.mock('electron', () => ({
|
||||
app: { getLocale: () => 'en-US' },
|
||||
}));
|
||||
|
||||
vi.mock('fs/promises', () => ({
|
||||
default: { readFile: mockReadFile },
|
||||
readFile: mockReadFile,
|
||||
}));
|
||||
|
||||
describe('AppApiAdapter', () => {
|
||||
let adapter: AppApiAdapter;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockProjectEngine.getActiveProject.mockResolvedValue({ id: 'p1', dataPath: '/projects/blog' });
|
||||
mockProjectEngine.getProjectPaths.mockReturnValue({ posts: '/projects/blog/posts', media: '/projects/blog/media' });
|
||||
mockProjectEngine.getDefaultProjectBaseDir.mockResolvedValue('/home/user/bDS/p1');
|
||||
mockDatabase.getDataPaths.mockReturnValue({ database: '/data/bds.db' });
|
||||
adapter = new AppApiAdapter();
|
||||
});
|
||||
|
||||
it('getDataPaths returns database, posts, and media paths', async () => {
|
||||
const result = await adapter.getDataPaths();
|
||||
expect(result).toEqual({
|
||||
database: '/data/bds.db',
|
||||
posts: '/projects/blog/posts',
|
||||
media: '/projects/blog/media',
|
||||
});
|
||||
});
|
||||
|
||||
it('getSystemLanguage returns electron app locale', async () => {
|
||||
const result = await adapter.getSystemLanguage();
|
||||
expect(result).toBe('en-US');
|
||||
});
|
||||
|
||||
it('getDefaultProjectPath delegates to ProjectEngine', async () => {
|
||||
const result = await adapter.getDefaultProjectPath('p1');
|
||||
expect(mockProjectEngine.getDefaultProjectBaseDir).toHaveBeenCalledWith('p1');
|
||||
expect(result).toBe('/home/user/bDS/p1');
|
||||
});
|
||||
|
||||
it('readProjectMetadata returns metadata from project.json', async () => {
|
||||
mockReadFile.mockResolvedValueOnce(
|
||||
JSON.stringify({ name: 'My Blog', description: 'Test', publicUrl: 'https://blog.example.com', mainLanguage: 'en', dataPath: '/secret' }),
|
||||
);
|
||||
|
||||
const result = await adapter.readProjectMetadata('/projects/blog');
|
||||
expect(result).toEqual({
|
||||
name: 'My Blog',
|
||||
description: 'Test',
|
||||
publicUrl: 'https://blog.example.com',
|
||||
mainLanguage: 'en',
|
||||
});
|
||||
// dataPath should be excluded
|
||||
expect(result).not.toHaveProperty('dataPath');
|
||||
});
|
||||
|
||||
it('readProjectMetadata returns null when file does not exist', async () => {
|
||||
mockReadFile.mockRejectedValueOnce(new Error('ENOENT'));
|
||||
|
||||
const result = await adapter.readProjectMetadata('/nonexistent');
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
98
tests/engine/GitApiAdapter.test.ts
Normal file
98
tests/engine/GitApiAdapter.test.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { GitApiAdapter } from '../../src/main/engine/GitApiAdapter';
|
||||
|
||||
const { mockGitEngine, mockProjectEngine } = vi.hoisted(() => ({
|
||||
mockGitEngine: {
|
||||
checkAvailability: vi.fn().mockResolvedValue({ gitFound: true, version: '2.40.0' }),
|
||||
getRepoState: vi.fn().mockResolvedValue({ isRepo: true, currentBranch: 'main', hasRemote: true }),
|
||||
getStatus: vi.fn().mockResolvedValue({ files: [], counts: { untracked: 0, modified: 0, deleted: 0, renamed: 0, staged: 0 } }),
|
||||
getHistory: vi.fn().mockResolvedValue([]),
|
||||
getRemoteState: vi.fn().mockResolvedValue({ localBranch: 'main', upstreamBranch: 'origin/main', hasUpstream: true, ahead: 0, behind: 0 }),
|
||||
fetch: vi.fn().mockResolvedValue({ success: true }),
|
||||
pull: vi.fn().mockResolvedValue({ success: true }),
|
||||
push: vi.fn().mockResolvedValue({ success: true }),
|
||||
commitAll: vi.fn().mockResolvedValue({ success: true }),
|
||||
},
|
||||
mockProjectEngine: {
|
||||
getActiveProject: vi.fn().mockResolvedValue({ id: 'p1', dataPath: '/projects/blog' }),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../src/main/engine/GitEngine', () => ({
|
||||
getGitEngine: () => mockGitEngine,
|
||||
}));
|
||||
|
||||
vi.mock('../../src/main/engine/ProjectEngine', () => ({
|
||||
getProjectEngine: () => mockProjectEngine,
|
||||
}));
|
||||
|
||||
describe('GitApiAdapter', () => {
|
||||
let adapter: GitApiAdapter;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
adapter = new GitApiAdapter();
|
||||
});
|
||||
|
||||
it('checkAvailability delegates directly (no projectPath)', async () => {
|
||||
const result = await adapter.checkAvailability();
|
||||
expect(mockGitEngine.checkAvailability).toHaveBeenCalledWith();
|
||||
expect(result).toEqual({ gitFound: true, version: '2.40.0' });
|
||||
});
|
||||
|
||||
it('getRepoState resolves projectPath from active project', async () => {
|
||||
await adapter.getRepoState();
|
||||
expect(mockProjectEngine.getActiveProject).toHaveBeenCalled();
|
||||
expect(mockGitEngine.getRepoState).toHaveBeenCalledWith('/projects/blog');
|
||||
});
|
||||
|
||||
it('getStatus resolves projectPath', async () => {
|
||||
await adapter.getStatus();
|
||||
expect(mockGitEngine.getStatus).toHaveBeenCalledWith('/projects/blog');
|
||||
});
|
||||
|
||||
it('getHistory passes limit and resolves projectPath', async () => {
|
||||
await adapter.getHistory(10);
|
||||
expect(mockGitEngine.getHistory).toHaveBeenCalledWith('/projects/blog', 10);
|
||||
});
|
||||
|
||||
it('getHistory passes undefined limit', async () => {
|
||||
await adapter.getHistory();
|
||||
expect(mockGitEngine.getHistory).toHaveBeenCalledWith('/projects/blog', undefined);
|
||||
});
|
||||
|
||||
it('getRemoteState resolves projectPath', async () => {
|
||||
await adapter.getRemoteState();
|
||||
expect(mockGitEngine.getRemoteState).toHaveBeenCalledWith('/projects/blog');
|
||||
});
|
||||
|
||||
it('fetch resolves projectPath', async () => {
|
||||
await adapter.fetch();
|
||||
expect(mockGitEngine.fetch).toHaveBeenCalledWith('/projects/blog');
|
||||
});
|
||||
|
||||
it('pull resolves projectPath', async () => {
|
||||
await adapter.pull();
|
||||
expect(mockGitEngine.pull).toHaveBeenCalledWith('/projects/blog');
|
||||
});
|
||||
|
||||
it('push resolves projectPath', async () => {
|
||||
await adapter.push();
|
||||
expect(mockGitEngine.push).toHaveBeenCalledWith('/projects/blog');
|
||||
});
|
||||
|
||||
it('commitAll resolves projectPath and passes message', async () => {
|
||||
await adapter.commitAll('update files');
|
||||
expect(mockGitEngine.commitAll).toHaveBeenCalledWith('/projects/blog', 'update files');
|
||||
});
|
||||
|
||||
it('throws when no active project', async () => {
|
||||
mockProjectEngine.getActiveProject.mockResolvedValueOnce(null);
|
||||
await expect(adapter.getRepoState()).rejects.toThrow('No active project with a data path');
|
||||
});
|
||||
|
||||
it('throws when active project has no dataPath', async () => {
|
||||
mockProjectEngine.getActiveProject.mockResolvedValueOnce({ id: 'p1', dataPath: null });
|
||||
await expect(adapter.fetch()).rejects.toThrow('No active project with a data path');
|
||||
});
|
||||
});
|
||||
80
tests/engine/PublishApiAdapter.test.ts
Normal file
80
tests/engine/PublishApiAdapter.test.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { PublishApiAdapter } from '../../src/main/engine/PublishApiAdapter';
|
||||
|
||||
const { mockPublishEngine, mockProjectEngine, mockTaskManager } = vi.hoisted(() => ({
|
||||
mockPublishEngine: {
|
||||
setProjectContext: vi.fn(),
|
||||
uploadHtml: vi.fn().mockResolvedValue({ filesUploaded: 5, filesSkipped: 2 }),
|
||||
uploadThumbnails: vi.fn().mockResolvedValue({ filesUploaded: 3, filesSkipped: 1 }),
|
||||
uploadMedia: vi.fn().mockResolvedValue({ filesUploaded: 10, filesSkipped: 4 }),
|
||||
},
|
||||
mockProjectEngine: {
|
||||
getActiveProject: vi.fn().mockResolvedValue({ id: 'p1', dataPath: '/projects/blog' }),
|
||||
},
|
||||
mockTaskManager: {
|
||||
runTask: vi.fn().mockImplementation((opts: { execute: (onProgress: () => void) => Promise<unknown> }) => {
|
||||
return opts.execute(() => {});
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../src/main/engine/PublishEngine', () => ({
|
||||
getPublishEngine: () => mockPublishEngine,
|
||||
}));
|
||||
|
||||
vi.mock('../../src/main/engine/ProjectEngine', () => ({
|
||||
getProjectEngine: () => mockProjectEngine,
|
||||
}));
|
||||
|
||||
vi.mock('../../src/main/engine/TaskManager', () => ({
|
||||
taskManager: mockTaskManager,
|
||||
}));
|
||||
|
||||
describe('PublishApiAdapter', () => {
|
||||
let adapter: PublishApiAdapter;
|
||||
const creds = { sshHost: 'example.com', sshUser: 'deploy', sshRemotePath: '/var/www', sshMode: 'rsync' as const };
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockPublishEngine.uploadHtml.mockResolvedValue({ filesUploaded: 5, filesSkipped: 2 });
|
||||
mockPublishEngine.uploadThumbnails.mockResolvedValue({ filesUploaded: 3, filesSkipped: 1 });
|
||||
mockPublishEngine.uploadMedia.mockResolvedValue({ filesUploaded: 10, filesSkipped: 4 });
|
||||
mockProjectEngine.getActiveProject.mockResolvedValue({ id: 'p1', dataPath: '/projects/blog' });
|
||||
mockTaskManager.runTask.mockImplementation((opts: { execute: (onProgress: () => void) => Promise<unknown> }) => {
|
||||
return opts.execute(() => {});
|
||||
});
|
||||
adapter = new PublishApiAdapter();
|
||||
});
|
||||
|
||||
it('sets project context before uploading', async () => {
|
||||
await adapter.uploadSite(creds);
|
||||
expect(mockPublishEngine.setProjectContext).toHaveBeenCalledWith('p1', '/projects/blog');
|
||||
});
|
||||
|
||||
it('runs three parallel upload tasks', async () => {
|
||||
await adapter.uploadSite(creds);
|
||||
expect(mockTaskManager.runTask).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('returns aggregate upload results', async () => {
|
||||
const result = await adapter.uploadSite(creds);
|
||||
expect(result).toEqual({
|
||||
htmlFilesUploaded: 5,
|
||||
thumbnailFilesUploaded: 3,
|
||||
mediaFilesUploaded: 10,
|
||||
filesSkipped: 7, // 2 + 1 + 4
|
||||
});
|
||||
});
|
||||
|
||||
it('passes credentials to upload methods', async () => {
|
||||
await adapter.uploadSite(creds);
|
||||
expect(mockPublishEngine.uploadHtml).toHaveBeenCalledWith(creds, expect.any(Function));
|
||||
expect(mockPublishEngine.uploadThumbnails).toHaveBeenCalledWith(creds, expect.any(Function));
|
||||
expect(mockPublishEngine.uploadMedia).toHaveBeenCalledWith(creds, expect.any(Function));
|
||||
});
|
||||
|
||||
it('throws when no active project', async () => {
|
||||
mockProjectEngine.getActiveProject.mockResolvedValueOnce(null);
|
||||
await expect(adapter.uploadSite(creds)).rejects.toThrow('No active project');
|
||||
});
|
||||
});
|
||||
@@ -104,6 +104,29 @@ const mockTaskManager: Record<string, ReturnType<typeof vi.fn>> = {
|
||||
clearCompletedTasks: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
const mockGitApiAdapter: Record<string, ReturnType<typeof vi.fn>> = {
|
||||
checkAvailability: vi.fn().mockResolvedValue({ gitFound: true }),
|
||||
getRepoState: vi.fn().mockResolvedValue({ isRepo: true }),
|
||||
getStatus: vi.fn().mockResolvedValue({ files: [], counts: {} }),
|
||||
getHistory: vi.fn().mockResolvedValue([]),
|
||||
getRemoteState: vi.fn().mockResolvedValue({ hasUpstream: false }),
|
||||
fetch: vi.fn().mockResolvedValue({ success: true }),
|
||||
pull: vi.fn().mockResolvedValue({ success: true }),
|
||||
push: vi.fn().mockResolvedValue({ success: true }),
|
||||
commitAll: vi.fn().mockResolvedValue({ success: true }),
|
||||
};
|
||||
|
||||
const mockPublishApiAdapter: Record<string, ReturnType<typeof vi.fn>> = {
|
||||
uploadSite: vi.fn().mockResolvedValue({ htmlFilesUploaded: 0, thumbnailFilesUploaded: 0, mediaFilesUploaded: 0, filesSkipped: 0 }),
|
||||
};
|
||||
|
||||
const mockAppApiAdapter: Record<string, ReturnType<typeof vi.fn>> = {
|
||||
getDataPaths: vi.fn().mockResolvedValue({ database: '/db', posts: '/posts', media: '/media' }),
|
||||
getSystemLanguage: vi.fn().mockResolvedValue('en-US'),
|
||||
getDefaultProjectPath: vi.fn().mockResolvedValue('/path'),
|
||||
readProjectMetadata: vi.fn().mockResolvedValue(null),
|
||||
};
|
||||
|
||||
// ── Override ENGINE_MAP for testing ────────────────────────────────
|
||||
|
||||
const originalEngineMap: Record<string, typeof ENGINE_MAP[string]> = {};
|
||||
@@ -123,6 +146,9 @@ describe('invokeMainProcessPythonApi', () => {
|
||||
ENGINE_MAP.tags = () => mockTagEngine as Record<string, (...args: unknown[]) => unknown>;
|
||||
ENGINE_MAP.scripts = () => mockScriptEngine as Record<string, (...args: unknown[]) => unknown>;
|
||||
ENGINE_MAP.tasks = () => mockTaskManager as Record<string, (...args: unknown[]) => unknown>;
|
||||
ENGINE_MAP.sync = () => mockGitApiAdapter as Record<string, (...args: unknown[]) => unknown>;
|
||||
ENGINE_MAP.publish = () => mockPublishApiAdapter as Record<string, (...args: unknown[]) => unknown>;
|
||||
ENGINE_MAP.app = () => mockAppApiAdapter as Record<string, (...args: unknown[]) => unknown>;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -203,6 +229,58 @@ describe('invokeMainProcessPythonApi', () => {
|
||||
const result = await invokeMainProcessPythonApi('posts.get', { postId: 'p1' });
|
||||
expect(result).toEqual({ id: 'p1', title: 'Found' });
|
||||
});
|
||||
|
||||
// ── Sync (git) routing ────────────────────────────────────────
|
||||
|
||||
it('routes sync.checkAvailability to GitApiAdapter.checkAvailability', async () => {
|
||||
await invokeMainProcessPythonApi('sync.checkAvailability', {});
|
||||
expect(mockGitApiAdapter.checkAvailability).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('routes sync.getRepoState to GitApiAdapter.getRepoState', async () => {
|
||||
await invokeMainProcessPythonApi('sync.getRepoState', {});
|
||||
expect(mockGitApiAdapter.getRepoState).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('routes sync.commitAll to GitApiAdapter.commitAll with message', async () => {
|
||||
await invokeMainProcessPythonApi('sync.commitAll', { message: 'update files' });
|
||||
expect(mockGitApiAdapter.commitAll).toHaveBeenCalledWith('update files');
|
||||
});
|
||||
|
||||
it('routes sync.getHistory with optional limit', async () => {
|
||||
await invokeMainProcessPythonApi('sync.getHistory', { limit: 5 });
|
||||
expect(mockGitApiAdapter.getHistory).toHaveBeenCalledWith(5);
|
||||
});
|
||||
|
||||
// ── Publish routing ────────────────────────────────────────
|
||||
|
||||
it('routes publish.uploadSite to PublishApiAdapter.uploadSite', async () => {
|
||||
const creds = { sshHost: 'example.com', sshUser: 'deploy', sshRemotePath: '/var/www', sshMode: 'rsync' };
|
||||
await invokeMainProcessPythonApi('publish.uploadSite', { credentials: creds });
|
||||
expect(mockPublishApiAdapter.uploadSite).toHaveBeenCalledWith(creds);
|
||||
});
|
||||
|
||||
// ── App routing ────────────────────────────────────────
|
||||
|
||||
it('routes app.getDataPaths to AppApiAdapter.getDataPaths', async () => {
|
||||
await invokeMainProcessPythonApi('app.getDataPaths', {});
|
||||
expect(mockAppApiAdapter.getDataPaths).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('routes app.getDefaultProjectPath to AppApiAdapter', async () => {
|
||||
await invokeMainProcessPythonApi('app.getDefaultProjectPath', { projectId: 'p1' });
|
||||
expect(mockAppApiAdapter.getDefaultProjectPath).toHaveBeenCalledWith('p1');
|
||||
});
|
||||
|
||||
it('routes app.readProjectMetadata to AppApiAdapter', async () => {
|
||||
await invokeMainProcessPythonApi('app.readProjectMetadata', { folderPath: '/some/path' });
|
||||
expect(mockAppApiAdapter.readProjectMetadata).toHaveBeenCalledWith('/some/path');
|
||||
});
|
||||
|
||||
it('routes app.getSystemLanguage to AppApiAdapter', async () => {
|
||||
await invokeMainProcessPythonApi('app.getSystemLanguage', {});
|
||||
expect(mockAppApiAdapter.getSystemLanguage).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
// ── Unknown/unsupported methods ──────────────────────────────────
|
||||
@@ -244,13 +322,7 @@ describe('invokeMainProcessPythonApi', () => {
|
||||
'app.triggerMenuAction',
|
||||
'app.getBlogmarkBookmarklet',
|
||||
'app.copyToClipboard',
|
||||
'chat.sendMessage',
|
||||
'chat.abortMessage',
|
||||
'chat.analyzeTaxonomy',
|
||||
'chat.analyzeMediaImage',
|
||||
'sync.configure',
|
||||
'sync.start',
|
||||
'sync.stopAutoSync',
|
||||
'app.setPreviewPostTarget',
|
||||
];
|
||||
|
||||
for (const method of unsafeMethods) {
|
||||
|
||||
@@ -27,14 +27,18 @@ describe('generateApiDocumentationMarkdownV1', () => {
|
||||
expect(markdown).toContain('[↑ Back to Table of contents](#table-of-contents)');
|
||||
});
|
||||
|
||||
it('documents chat APIs in a dedicated module section', () => {
|
||||
it('documents sync and publish APIs in dedicated module sections', () => {
|
||||
const markdown = generateApiDocumentationMarkdownV1();
|
||||
|
||||
expect(markdown).toContain('## chat');
|
||||
expect(markdown).toContain('### chat.getConversations');
|
||||
expect(markdown).toContain('### chat.sendMessage');
|
||||
expect(markdown).toContain('- [chat](#chat)');
|
||||
expect(markdown).toContain('- [chat.sendMessage](#chatsendmessage)');
|
||||
expect(markdown).toContain('## sync');
|
||||
expect(markdown).toContain('### sync.getRepoState');
|
||||
expect(markdown).toContain('### sync.commitAll');
|
||||
expect(markdown).toContain('- [sync](#sync)');
|
||||
expect(markdown).toContain('## publish');
|
||||
expect(markdown).toContain('### publish.uploadSite');
|
||||
expect(markdown).toContain('- [publish](#publish)');
|
||||
// chat namespace should not be present
|
||||
expect(markdown).not.toContain('## chat');
|
||||
});
|
||||
|
||||
it('includes a dedicated Data Structures section with core object shapes', () => {
|
||||
|
||||
@@ -23,8 +23,9 @@ describe('pythonApiContractV1', () => {
|
||||
'scripts.getAll',
|
||||
'tasks.getAll',
|
||||
'app.getSystemLanguage',
|
||||
'chat.getConversations',
|
||||
'chat.sendMessage',
|
||||
'sync.getRepoState',
|
||||
'sync.commitAll',
|
||||
'publish.uploadSite',
|
||||
]));
|
||||
});
|
||||
|
||||
@@ -43,34 +44,30 @@ describe('pythonApiContractV1', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('documents chat.sendMessage return contract and metadata input', () => {
|
||||
expect(getPythonApiMethodContract('chat.sendMessage')).toEqual({
|
||||
method: 'chat.sendMessage',
|
||||
description: 'Send message to chat conversation.',
|
||||
it('documents sync.commitAll contract with required message param', () => {
|
||||
expect(getPythonApiMethodContract('sync.commitAll')).toEqual({
|
||||
method: 'sync.commitAll',
|
||||
description: 'Stage all changes and commit for active project.',
|
||||
params: [
|
||||
{
|
||||
name: 'conversationId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'message',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'metadata',
|
||||
type: 'object',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
returns: '{ success: boolean; message?: string; error?: string }',
|
||||
returns: 'GitActionResult',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not include chat namespace (removed in v1.7.0)', () => {
|
||||
const methodNames = listPythonApiMethodNames();
|
||||
const chatMethods = methodNames.filter((m) => m.startsWith('chat.'));
|
||||
expect(chatMethods).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('contains semantic version metadata for compatibility checks', () => {
|
||||
expect(BDS_PYTHON_API_CONTRACT_V1).toMatchObject({
|
||||
version: '1.6.0',
|
||||
version: '1.7.0',
|
||||
generatedAt: expect.any(String),
|
||||
});
|
||||
});
|
||||
@@ -93,15 +90,17 @@ describe('generatePythonApiModuleV1', () => {
|
||||
expect(moduleCode).toContain('class PostsApi:');
|
||||
expect(moduleCode).toContain('class MediaApi:');
|
||||
expect(moduleCode).toContain('class MetaApi:');
|
||||
expect(moduleCode).toContain('class ChatApi:');
|
||||
expect(moduleCode).toContain('class SyncApi:');
|
||||
expect(moduleCode).toContain('class PublishApi:');
|
||||
expect(moduleCode).toContain('async def get(self, post_id):');
|
||||
expect(moduleCode).toContain('async def get_all(self, options=None):');
|
||||
expect(moduleCode).toContain('async def search(self, query):');
|
||||
expect(moduleCode).toContain('async def get_project_metadata(self):');
|
||||
expect(moduleCode).toContain('async def get_conversations(self):');
|
||||
expect(moduleCode).toContain('async def send_message(self, conversation_id, message, metadata=None):');
|
||||
expect(moduleCode).toContain('async def commit_all(self, message):');
|
||||
expect(moduleCode).toContain('async def upload_site(self, credentials):');
|
||||
expect(moduleCode).toContain('class BdsApi:');
|
||||
expect(moduleCode).toContain('bds = BdsApi(_transport)');
|
||||
expect(moduleCode).not.toContain('class ChatApi:');
|
||||
});
|
||||
|
||||
it('escapes python keyword method names to valid identifiers', () => {
|
||||
|
||||
@@ -87,13 +87,15 @@ Object.defineProperty(globalThis, 'window', {
|
||||
getFilePath: vi.fn(),
|
||||
},
|
||||
sync: {
|
||||
configure: vi.fn(),
|
||||
start: vi.fn(),
|
||||
checkAvailability: vi.fn(),
|
||||
getRepoState: vi.fn(),
|
||||
getStatus: vi.fn(),
|
||||
isConfigured: vi.fn(),
|
||||
getPendingCount: vi.fn(),
|
||||
getLog: vi.fn(),
|
||||
stopAutoSync: vi.fn(),
|
||||
getHistory: vi.fn(),
|
||||
getRemoteState: vi.fn(),
|
||||
fetch: vi.fn(),
|
||||
pull: vi.fn(),
|
||||
push: vi.fn(),
|
||||
commitAll: vi.fn(),
|
||||
},
|
||||
dropbox: {
|
||||
configure: vi.fn(),
|
||||
|
||||
Reference in New Issue
Block a user