Skip to main content
Glama
content-creation.test.ts11.7 kB
/** * Integration tests for content creation via Plan→Diff→Apply * * Tests the applyContentCreation() function with various scenarios * including Gutenberg blocks support. */ import { describe, it, expect } from 'vitest'; import { applyContentCreation } from '../src/safety.js'; import type { WPConfig } from '../src/config.js'; // Mock configuration (adjust for your test environment) const testConfig: WPConfig = { baseUrl: process.env.WP_BASE_URL || 'http://localhost:8888', restApi: process.env.WP_REST_API || 'http://localhost:8888/wp-json', wpnavBase: process.env.WPNAV_BASE || 'http://localhost:8888/wp-json/wpnav/v1', username: process.env.WP_APP_USER || 'admin', appPassword: process.env.WP_APP_PASS || '', }; // Mock wpRequest function const mockWpRequest = async (endpoint: string, options?: any) => { // In a real test, this would make actual HTTP requests // For now, return mock responses const mockPlanId = `plan-${Date.now()}`; if (endpoint.includes('/content/plan')) { return { plan_id: mockPlanId, post_id: 0, post_type: options?.body ? JSON.parse(options.body).post_type : 'post', is_create: true, operations: options?.body ? JSON.parse(options.body).operations : [], risk_analysis: { overall_risk: 'medium', recommendations: ['Creating draft post - safe to preview before publishing'], }, }; } if (endpoint.includes('/content/diff')) { return { diff: { changes: [ { type: 'add', path: '/title', value: 'Test Title' }, { type: 'add', path: '/content', value: 'Test content' }, ], }, format: 'json', }; } if (endpoint.includes('/content/apply')) { return { applied: true, is_create: true, post_id: 123, post_type: 'post', plan_id: mockPlanId, message: 'Post created successfully', }; } throw new Error(`Unexpected endpoint: ${endpoint}`); }; describe('Content Creation - applyContentCreation()', () => { describe('Basic Post Creation', () => { it('should create a post with title only', async () => { const result = await applyContentCreation(mockWpRequest, testConfig, { postType: 'post', title: 'Test Post Title', }); expect(result).toHaveProperty('plan'); expect(result).toHaveProperty('diff'); expect(result).toHaveProperty('apply'); expect(result.plan.is_create).toBe(true); expect(result.plan.post_type).toBe('post'); expect(result.apply.applied).toBe(true); expect(result.apply.post_id).toBeGreaterThan(0); }); it('should create a post with title and content', async () => { const result = await applyContentCreation(mockWpRequest, testConfig, { postType: 'post', title: 'Post with Content', content: 'This is the post content.', }); expect(result.plan.operations).toHaveLength(2); // title + content expect(result.apply.applied).toBe(true); }); it('should create a post with title, content, and excerpt', async () => { const result = await applyContentCreation(mockWpRequest, testConfig, { postType: 'post', title: 'Post with Excerpt', content: 'Full content here.', excerpt: 'This is the excerpt.', }); expect(result.plan.operations).toHaveLength(3); // title + content + excerpt expect(result.apply.applied).toBe(true); }); it('should create a post with custom status', async () => { const result = await applyContentCreation(mockWpRequest, testConfig, { postType: 'post', title: 'Published Post', content: 'Content', status: 'publish', }); expect(result.plan.operations).toContainEqual( expect.objectContaining({ op: 'replace', path: '/status', value: 'publish', }) ); expect(result.apply.applied).toBe(true); }); }); describe('Page Creation', () => { it('should create a page', async () => { const result = await applyContentCreation(mockWpRequest, testConfig, { postType: 'page', title: 'Test Page', content: 'Page content', }); expect(result.plan.post_type).toBe('page'); expect(result.apply.applied).toBe(true); }); }); describe('Gutenberg Blocks Support', () => { it('should create a post with single Gutenberg block', async () => { const result = await applyContentCreation(mockWpRequest, testConfig, { postType: 'post', title: 'Post with Block', blocks: [ { blockName: 'core/paragraph', attrs: { content: 'Hello from Gutenberg!' }, }, ], }); expect(result.plan.operations).toContainEqual( expect.objectContaining({ op: 'insert', path: '/blocks/0', value: expect.objectContaining({ blockName: 'core/paragraph', }), }) ); expect(result.apply.applied).toBe(true); }); it('should create a post with multiple Gutenberg blocks', async () => { const result = await applyContentCreation(mockWpRequest, testConfig, { postType: 'post', title: 'Post with Multiple Blocks', blocks: [ { blockName: 'core/heading', attrs: { level: 2, content: 'Section Heading' }, }, { blockName: 'core/paragraph', attrs: { content: 'First paragraph' }, }, { blockName: 'core/paragraph', attrs: { content: 'Second paragraph' }, }, ], }); expect(result.plan.operations).toContainEqual(expect.objectContaining({ path: '/blocks/0' })); expect(result.plan.operations).toContainEqual(expect.objectContaining({ path: '/blocks/1' })); expect(result.plan.operations).toContainEqual(expect.objectContaining({ path: '/blocks/2' })); expect(result.apply.applied).toBe(true); }); it('should create a post with nested blocks', async () => { const result = await applyContentCreation(mockWpRequest, testConfig, { postType: 'post', title: 'Post with Nested Blocks', blocks: [ { blockName: 'core/group', attrs: {}, innerBlocks: [ { blockName: 'core/heading', attrs: { level: 2, content: 'Group Heading' }, }, { blockName: 'core/paragraph', attrs: { content: 'Nested paragraph' }, }, ], }, ], }); const groupBlock = result.plan.operations.find((op: any) => op.path === '/blocks/0'); expect(groupBlock?.value.blockName).toBe('core/group'); expect(groupBlock?.value.innerBlocks).toHaveLength(2); expect(result.apply.applied).toBe(true); }); }); describe('Error Handling', () => { it('should handle plan creation failure', async () => { const failingWpRequest = async (endpoint: string) => { if (endpoint.includes('/content/plan')) { throw new Error('PLAN_FAILED: Validation error'); } return {}; }; await expect( applyContentCreation(failingWpRequest as any, testConfig, { postType: 'post', title: 'Test', }) ).rejects.toThrow('PLAN_FAILED'); }); it('should handle missing plan_id in response', async () => { const badWpRequest = async (endpoint: string) => { if (endpoint.includes('/content/plan')) { return { /* no plan_id */ }; } return {}; }; await expect( applyContentCreation(badWpRequest as any, testConfig, { postType: 'post', title: 'Test', }) ).rejects.toThrow('Could not retrieve plan_id'); }); it('should handle diff generation failure', async () => { const failingDiffRequest = async (endpoint: string, options?: any) => { if (endpoint.includes('/content/plan')) { return { plan_id: 'test-plan-id' }; } if (endpoint.includes('/content/diff')) { throw new Error('DIFF_FAILED: Could not generate diff'); } return {}; }; await expect( applyContentCreation(failingDiffRequest as any, testConfig, { postType: 'post', title: 'Test', }) ).rejects.toThrow('DIFF_FAILED'); }); it('should handle apply failure', async () => { const failingApplyRequest = async (endpoint: string, options?: any) => { if (endpoint.includes('/content/plan')) { return { plan_id: 'test-plan-id' }; } if (endpoint.includes('/content/diff')) { return { diff: {}, format: 'json' }; } if (endpoint.includes('/content/apply')) { throw new Error('APPLY_FAILED: Permission denied'); } return {}; }; await expect( applyContentCreation(failingApplyRequest as any, testConfig, { postType: 'post', title: 'Test', }) ).rejects.toThrow('APPLY_FAILED'); }); }); describe('Idempotency', () => { it('should generate unique idempotency keys for each call', async () => { const requestBodies: any[] = []; const trackingWpRequest = async (endpoint: string, options?: any) => { if (endpoint.includes('/content/plan') && options?.body) { requestBodies.push(JSON.parse(options.body)); } // Return mock responses return mockWpRequest(endpoint, options); }; // Make two separate calls await applyContentCreation(trackingWpRequest as any, testConfig, { postType: 'post', title: 'First Post', }); await applyContentCreation(trackingWpRequest as any, testConfig, { postType: 'post', title: 'Second Post', }); expect(requestBodies).toHaveLength(2); expect(requestBodies[0].idempotency_key).toBeDefined(); expect(requestBodies[1].idempotency_key).toBeDefined(); expect(requestBodies[0].idempotency_key).not.toBe(requestBodies[1].idempotency_key); }); }); describe('Operations Structure', () => { it('should build operations array correctly for minimal post', async () => { const result = await applyContentCreation(mockWpRequest, testConfig, { postType: 'post', title: 'Minimal Post', }); expect(result.plan.operations).toHaveLength(1); // title only expect(result.plan.operations[0]).toMatchObject({ op: 'replace', path: '/title', value: 'Minimal Post', }); }); it('should build operations array correctly for full post with blocks', async () => { const result = await applyContentCreation(mockWpRequest, testConfig, { postType: 'post', title: 'Full Post', content: 'Legacy content', excerpt: 'Excerpt', status: 'draft', blocks: [{ blockName: 'core/paragraph', attrs: { content: 'Block content' } }], }); // Should have: title + content + excerpt + status + 1 block = 5 operations expect(result.plan.operations).toHaveLength(5); // Verify operation types const opPaths = result.plan.operations.map((op: any) => op.path); expect(opPaths).toContain('/title'); expect(opPaths).toContain('/content'); expect(opPaths).toContain('/excerpt'); expect(opPaths).toContain('/status'); expect(opPaths).toContain('/blocks/0'); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/littlebearapps/wp-navigator-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server