import { describe, expect, it } from 'vitest'; import { BDS_PYTHON_API_CONTRACT_V1, getPythonApiMethodContract, listPythonApiMethodNames, } from '../../../src/renderer/python/pythonApiContractV1'; import { generatePythonApiModuleV1 } from '../../../src/renderer/python/generatePythonApiModuleV1'; describe('pythonApiContractV1', () => { it('exposes broad stable method names for v1 contract', () => { const methodNames = listPythonApiMethodNames(); expect(methodNames.length).toBeGreaterThan(40); expect(methodNames).toEqual(expect.arrayContaining([ 'projects.getAll', 'posts.get', 'posts.getAll', 'posts.search', 'media.get', 'media.search', 'meta.getProjectMetadata', 'tags.getAll', 'scripts.getAll', 'tasks.getAll', 'app.getSystemLanguage', 'sync.getRepoState', 'sync.commitAll', 'publish.uploadSite', ])); }); it('returns method contract metadata by name', () => { expect(getPythonApiMethodContract('posts.get')).toEqual({ method: 'posts.get', description: 'Fetch one post by id.', params: [ { name: 'postId', type: 'string', required: true, }, ], returns: 'PostData | null', }); }); 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: 'message', type: 'string', required: true, }, ], returns: 'GitActionResult', }); }); it('exposes analyzeMediaImage and detectPostLanguage from chat namespace', () => { const methodNames = listPythonApiMethodNames(); const chatMethods = methodNames.filter((m) => m.startsWith('chat.')); expect(chatMethods).toEqual(['chat.analyzeMediaImage', 'chat.detectPostLanguage', 'chat.analyzePost']); }); it('documents chat.analyzeMediaImage contract with mediaId and language params', () => { expect(getPythonApiMethodContract('chat.analyzeMediaImage')).toEqual({ method: 'chat.analyzeMediaImage', description: 'Analyze an image and generate title, alt text, and caption using AI.', params: [ { name: 'mediaId', type: 'string', required: true }, { name: 'language', type: 'string', required: false }, ], returns: 'ImageAnalysisResult', }); }); it('contains semantic version metadata for compatibility checks', () => { expect(BDS_PYTHON_API_CONTRACT_V1).toMatchObject({ version: '1.13.0', generatedAt: expect.any(String), }); }); it('includes canonical data structures for response documentation', () => { expect(BDS_PYTHON_API_CONTRACT_V1.dataStructures).toEqual(expect.arrayContaining([ expect.objectContaining({ name: 'PostData' }), expect.objectContaining({ name: 'MediaData' }), expect.objectContaining({ name: 'ProjectData' }), expect.objectContaining({ name: 'ImageAnalysisResult' }), ])); }); }); describe('generatePythonApiModuleV1', () => { it('generates python facade that hides transport details', () => { const moduleCode = generatePythonApiModuleV1(); expect(moduleCode).toContain('class BdsApiError(Exception):'); expect(moduleCode).toContain('class ProjectsApi:'); expect(moduleCode).toContain('class PostsApi:'); expect(moduleCode).toContain('class MediaApi:'); expect(moduleCode).toContain('class MetaApi:'); 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 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).toContain('class ChatApi:'); expect(moduleCode).toContain('async def analyze_media_image(self, media_id, language=None):'); expect(moduleCode).toContain('async def detect_post_language(self, title, content):'); }); it('escapes python keyword method names to valid identifiers', () => { const moduleCode = generatePythonApiModuleV1(); expect(moduleCode).toContain('return await self._transport.call("media.import", { "sourcePath": source_path, "metadata": metadata })'); expect(moduleCode).toContain('async def import_(self, source_path, metadata=None):'); expect(moduleCode).not.toContain('async def import(self, source_path, metadata=None):'); }); });