marketing-post-generator-mcp
Version:
A powerful MCP server for AI-powered marketing blog post generation with Claude integration
216 lines • 7.08 kB
JavaScript
import { jest } from '@jest/globals';
import path from 'path';
import { MockClaudeService, MockSearchService } from './mocks.js';
import { DIContainer } from '../../core/container/DIContainer.js';
/**
* Creates a test configuration object with sensible defaults
*/
export const createTestConfig = (overrides = {}) => ({
server: {
mode: 'local',
transport: 'stdio',
port: 3000,
host: '0.0.0.0',
...overrides.server
},
claude: {
apiKey: 'test-claude-api-key',
model: 'claude-3-sonnet-20240229',
maxTokens: 1000,
...overrides.claude
},
search: {
provider: 'firecrawl',
firecrawl: {
apiKey: 'test-firecrawl-api-key',
baseUrl: 'https://api.firecrawl.dev'
},
...overrides.search
},
logging: {
level: 'error', // Reduce log noise in tests
format: 'json',
...overrides.logging
},
cors: {
allowedOrigins: ['*'],
allowedHeaders: ['*'],
credentials: false,
...overrides.cors
},
...overrides
});
/**
* Creates a test DI container with mock services
*/
export const createTestContainer = () => {
const container = new DIContainer();
// Register mock services
container.register('ClaudeService', () => new MockClaudeService());
container.register('SearchService', () => new MockSearchService());
container.register('Logger', () => ({
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
debug: jest.fn()
}));
container.register('Config', () => createTestConfig());
return container;
};
/**
* Creates a test file path within the project
*/
export const createTestPath = (...segments) => {
return path.join(process.cwd(), 'test-fixtures', ...segments);
};
/**
* Creates a mock .postgen directory structure
*/
export const mockPostgenStructure = {
config: {
'config.json': JSON.stringify({
domain: 'example.com',
initialized: '2023-01-01T00:00:00.000Z'
})
},
analysis: {
'sample-analysis': {
'sample-analysis.json': JSON.stringify({
domain: 'example.com',
posts: [
{ title: 'Sample Post 1', url: 'https://example.com/post1' },
{ title: 'Sample Post 2', url: 'https://example.com/post2' }
],
analysis: { tone: 'professional', topics: ['tech', 'business'] }
})
},
'tone-analysis': {
'tone-analysis.json': JSON.stringify({
domain: 'example.com',
tone: 'professional',
characteristics: ['clear', 'concise'],
confidence: 0.9
})
}
},
'content-plans': {
'content-plan-2023-01.json': JSON.stringify({
timeframe: 'month',
posts: [
{ id: '1', title: 'Test Post 1', topic: 'Testing' },
{ id: '2', title: 'Test Post 2', topic: 'Development' }
]
})
},
narratives: {
'narrative-1.json': JSON.stringify({
postId: '1',
narrative: 'Test narrative content',
bulletPoints: ['Point 1', 'Point 2']
})
},
posts: {
'post-1.md': '# Test Post\n\nThis is a test post.',
'post-1.json': JSON.stringify({
id: '1',
title: 'Test Post',
created: '2023-01-01T00:00:00.000Z'
})
}
};
/**
* Sets up file system mocks based on the postgen structure
*/
export const setupPostgenMocks = () => {
const fs = require('fs/promises');
// Override readFile to return data from our mock structure
fs.readFile.mockImplementation((filePath) => {
const relativePath = filePath.replace(process.cwd(), '').replace(/^\//, '');
if (relativePath.includes('.postgen/config/config.json')) {
return Promise.resolve(mockPostgenStructure.config['config.json']);
}
if (relativePath.includes('sample-analysis.json')) {
return Promise.resolve(mockPostgenStructure.analysis['sample-analysis']['sample-analysis.json']);
}
if (relativePath.includes('tone-analysis.json')) {
return Promise.resolve(mockPostgenStructure.analysis['tone-analysis']['tone-analysis.json']);
}
if (relativePath.includes('content-plan-')) {
return Promise.resolve(mockPostgenStructure['content-plans']['content-plan-2023-01.json']);
}
if (relativePath.includes('narrative-')) {
return Promise.resolve(mockPostgenStructure.narratives['narrative-1.json']);
}
if (relativePath.includes('post-') && relativePath.endsWith('.md')) {
return Promise.resolve(mockPostgenStructure.posts['post-1.md']);
}
if (relativePath.includes('post-') && relativePath.endsWith('.json')) {
return Promise.resolve(mockPostgenStructure.posts['post-1.json']);
}
return Promise.reject(new Error(`Mock file not found: ${filePath}`));
});
// Override access to simulate file/directory existence
fs.access.mockImplementation((filePath) => {
if (filePath.includes('.postgen')) {
return Promise.resolve(); // .postgen directory exists
}
return Promise.reject(new Error(`Mock path not found: ${filePath}`));
});
};
/**
* Waits for a specified number of milliseconds
*/
export const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
/**
* Captures console output during test execution
*/
export const captureConsole = () => {
const originalConsole = { ...console };
const logs = [];
const errors = [];
const warns = [];
console.log = jest.fn((...args) => logs.push(args.join(' ')));
console.error = jest.fn((...args) => errors.push(args.join(' ')));
console.warn = jest.fn((...args) => warns.push(args.join(' ')));
return {
logs,
errors,
warns,
restore: () => {
Object.assign(console, originalConsole);
}
};
};
/**
* Creates a test that times out after a specified duration
*/
export const withTimeout = (promise, timeoutMs) => {
return Promise.race([
promise,
new Promise((_, reject) => setTimeout(() => reject(new Error(`Test timed out after ${timeoutMs}ms`)), timeoutMs))
]);
};
/**
* Asserts that a function throws an error with a specific message
*/
export const expectToThrow = async (fn, expectedMessage) => {
let error;
try {
await fn();
}
catch (e) {
error = e;
}
if (!error) {
throw new Error('Expected function to throw an error');
}
if (expectedMessage) {
if (typeof expectedMessage === 'string') {
expect(error.message).toContain(expectedMessage);
}
else {
expect(error.message).toMatch(expectedMessage);
}
}
};
//# sourceMappingURL=testHelpers.js.map