backend-mcp
Version:
Generador automΓ‘tico de backends con Node.js, Express, Prisma y mΓ³dulos configurables. Servidor MCP compatible con npx para agentes IA. Soporta PostgreSQL, MySQL, MongoDB y SQLite.
1,238 lines (1,062 loc) β’ 31.7 kB
JavaScript
// modules/testing/init.js
const fs = require('fs');
const path = require('path');
class TestingModule {
constructor(config = {}) {
this.config = {
framework: config.framework || 'jest',
testTypes: config.testTypes || ['unit', 'integration', 'e2e'],
coverage: config.coverage !== false,
coverageThreshold: config.coverageThreshold || 80,
database: config.database || false,
auth: config.auth || false,
api: config.api !== false,
performance: config.performance || false,
e2e: config.e2e || false,
mocking: config.mocking !== false,
...config
};
this.projectRoot = config.projectRoot || process.cwd();
}
async init() {
console.log('π§ͺ Initializing Testing Module...');
try {
await this.setupDirectories();
await this.generateJestConfigs();
await this.generateTestUtilities();
await this.generateTestHelpers();
await this.generateMocks();
await this.generateFixtures();
await this.generateExampleTests();
if (this.config.e2e) {
await this.generatePlaywrightConfig();
}
await this.generateTestScripts();
await this.updatePackageJson();
console.log('β
Testing Module initialized successfully!');
console.log('π Generated files:', this.getGeneratedFiles());
return {
success: true,
message: 'Testing module initialized successfully',
files: this.getGeneratedFiles()
};
} catch (error) {
console.error('β Error initializing testing module:', error);
throw error;
}
}
async setupDirectories() {
const dirs = [
'tests',
'tests/unit',
'tests/integration',
'tests/e2e',
'tests/helpers',
'tests/mocks',
'tests/fixtures',
'tests/performance',
'coverage'
];
for (const dir of dirs) {
const dirPath = path.join(this.projectRoot, dir);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
console.log(`π Created directory: ${dir}`);
}
}
}
async generateJestConfigs() {
// Main Jest configuration
const mainJestConfig = `// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src', '<rootDir>/tests'],
testMatch: [
'**/__tests__/**/*.ts',
'**/?(*.)+(spec|test).ts'
],
transform: {
'^.+\\.ts$': 'ts-jest'
},
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/**/*.test.ts',
'!src/**/*.spec.ts',
'!src/index.ts'
],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html', 'json', 'cobertura'],
${this.config.coverage ? `coverageThreshold: {
global: {
branches: ${this.config.coverageThreshold},
functions: ${this.config.coverageThreshold},
lines: ${this.config.coverageThreshold},
statements: ${this.config.coverageThreshold}
}
},` : ''}
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
globalTeardown: '<rootDir>/tests/teardown.ts',
testTimeout: 10000,
verbose: true,
forceExit: true,
clearMocks: true,
restoreMocks: true,
resetMocks: true
};
`;
// Unit tests configuration
const unitJestConfig = `// jest.unit.config.js
const baseConfig = require('./jest.config');
module.exports = {
...baseConfig,
displayName: 'Unit Tests',
testMatch: [
'<rootDir>/tests/unit/**/*.test.ts',
'<rootDir>/src/**/*.test.ts'
],
testTimeout: 5000,
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/**/*.test.ts',
'!src/**/*.integration.test.ts',
'!src/**/*.e2e.test.ts'
]
};
`;
// Integration tests configuration
const integrationJestConfig = `// jest.integration.config.js
const baseConfig = require('./jest.config');
module.exports = {
...baseConfig,
displayName: 'Integration Tests',
testMatch: [
'<rootDir>/tests/integration/**/*.test.ts',
'<rootDir>/src/**/*.integration.test.ts'
],
testTimeout: 30000,
setupFilesAfterEnv: [
'<rootDir>/tests/setup.ts',
'<rootDir>/tests/helpers/db-helper.ts'
],
${this.config.database ? `globalSetup: '<rootDir>/tests/helpers/db-setup.ts',
globalTeardown: '<rootDir>/tests/helpers/db-teardown.ts',` : ''}
maxWorkers: 1 // Run integration tests sequentially
};
`;
// E2E tests configuration
const e2eJestConfig = `// jest.e2e.config.js
const baseConfig = require('./jest.config');
module.exports = {
...baseConfig,
displayName: 'E2E Tests',
testMatch: [
'<rootDir>/tests/e2e/**/*.test.ts'
],
testTimeout: 60000,
setupFilesAfterEnv: [
'<rootDir>/tests/setup.ts',
'<rootDir>/tests/helpers/e2e-setup.ts'
],
maxWorkers: 1, // Run E2E tests sequentially
detectOpenHandles: true,
forceExit: true
};
`;
// Write Jest configuration files
fs.writeFileSync(path.join(this.projectRoot, 'jest.config.js'), mainJestConfig);
fs.writeFileSync(path.join(this.projectRoot, 'jest.unit.config.js'), unitJestConfig);
fs.writeFileSync(path.join(this.projectRoot, 'jest.integration.config.js'), integrationJestConfig);
fs.writeFileSync(path.join(this.projectRoot, 'jest.e2e.config.js'), e2eJestConfig);
console.log('β
Generated Jest configurations');
}
async generateTestUtilities() {
// Test setup file
const testSetup = `// tests/setup.ts
import 'reflect-metadata';
// Global test setup
beforeAll(async () => {
// Set test environment
process.env.NODE_ENV = 'test';
// Increase timeout for slow tests
jest.setTimeout(30000);
console.log('π§ͺ Test environment initialized');
});
afterAll(async () => {
// Cleanup after all tests
console.log('π§Ή Test environment cleanup completed');
});
// Global error handler
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
});
`;
// Test teardown file
const testTeardown = `// tests/teardown.ts
export default async function teardown() {
console.log('π§Ή Global test teardown...');
// Close any open connections
// Clean up test data
// Reset global state
console.log('β
Global test teardown completed');
}
`;
// Test utilities
const testUtils = `// tests/helpers/test-utils.ts
import { Request, Response } from 'express';
import { DeepPartial } from 'typeorm';
/**
* Create a mock Express request object
*/
export const createMockRequest = (overrides: Partial<Request> = {}): Partial<Request> => {
return {
body: {},
params: {},
query: {},
headers: {},
user: undefined,
...overrides
};
};
/**
* Create a mock Express response object
*/
export const createMockResponse = (): Partial<Response> => {
const res: Partial<Response> = {
status: jest.fn().mockReturnThis(),
json: jest.fn().mockReturnThis(),
send: jest.fn().mockReturnThis(),
cookie: jest.fn().mockReturnThis(),
clearCookie: jest.fn().mockReturnThis(),
redirect: jest.fn().mockReturnThis()
};
return res;
};
/**
* Wait for a specified amount of time
*/
export const wait = (ms: number): Promise<void> => {
return new Promise(resolve => setTimeout(resolve, ms));
};
/**
* Generate random string
*/
export const randomString = (length: number = 10): string => {
return Math.random().toString(36).substring(2, length + 2);
};
/**
* Generate random email
*/
export const randomEmail = (): string => {
return \`test-\${randomString()}.com\`;
};
/**
* Generate random number between min and max
*/
export const randomNumber = (min: number = 1, max: number = 100): number => {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
/**
* Deep clone an object
*/
export const deepClone = <T>(obj: T): T => {
return JSON.parse(JSON.stringify(obj));
};
/**
* Assert that a function throws an error
*/
export const expectToThrow = async (fn: () => Promise<any>, errorMessage?: string) => {
try {
await fn();
throw new Error('Expected function to throw an error');
} catch (error) {
if (errorMessage) {
expect(error.message).toContain(errorMessage);
}
}
};
/**
* Mock console methods
*/
export const mockConsole = () => {
const originalConsole = { ...console };
beforeEach(() => {
jest.spyOn(console, 'log').mockImplementation();
jest.spyOn(console, 'error').mockImplementation();
jest.spyOn(console, 'warn').mockImplementation();
jest.spyOn(console, 'info').mockImplementation();
});
afterEach(() => {
Object.assign(console, originalConsole);
});
};
/**
* Test data factory
*/
export class TestDataFactory {
static user(overrides: any = {}) {
return {
id: randomNumber(1, 1000),
email: randomEmail(),
name: \`Test User \${randomString(5)}\`,
password: 'hashedPassword123',
createdAt: new Date(),
updatedAt: new Date(),
...overrides
};
}
static product(overrides: any = {}) {
return {
id: randomNumber(1, 1000),
name: \`Test Product \${randomString(5)}\`,
description: 'Test product description',
price: randomNumber(10, 1000),
stock: randomNumber(0, 100),
createdAt: new Date(),
updatedAt: new Date(),
...overrides
};
}
}
`;
// Write test utility files
fs.writeFileSync(path.join(this.projectRoot, 'tests/setup.ts'), testSetup);
fs.writeFileSync(path.join(this.projectRoot, 'tests/teardown.ts'), testTeardown);
fs.writeFileSync(path.join(this.projectRoot, 'tests/helpers/test-utils.ts'), testUtils);
console.log('β
Generated test utilities');
}
async generateTestHelpers() {
if (this.config.database) {
await this.generateDatabaseHelpers();
}
if (this.config.auth) {
await this.generateAuthHelpers();
}
if (this.config.api) {
await this.generateApiHelpers();
}
}
async generateDatabaseHelpers() {
// Database helper
const dbHelper = `// tests/helpers/db-helper.ts
import { PrismaClient } from '@prisma/client';
let prisma: PrismaClient;
beforeAll(async () => {
prisma = new PrismaClient({
datasources: {
db: {
url: process.env.TEST_DATABASE_URL || process.env.DATABASE_URL
}
}
});
await prisma.$connect();
console.log('ποΈ Test database connected');
});
afterAll(async () => {
await prisma.$disconnect();
console.log('ποΈ Test database disconnected');
});
beforeEach(async () => {
// Start a transaction for each test
await prisma.$executeRaw\`BEGIN\`;
});
afterEach(async () => {
// Rollback transaction after each test
await prisma.$executeRaw\`ROLLBACK\`;
});
export { prisma };
/**
* Clean all tables
*/
export const cleanDatabase = async () => {
const tablenames = await prisma.$queryRaw<Array<{ tablename: string }>>\`
SELECT tablename FROM pg_tables WHERE schemaname='public'
\`;
for (const { tablename } of tablenames) {
if (tablename !== '_prisma_migrations') {
try {
await prisma.$executeRawUnsafe(\`TRUNCATE TABLE "\${tablename}" CASCADE;\`);
} catch (error) {
console.log(\`Could not truncate \${tablename}, skipping...\`);
}
}
}
};
/**
* Seed test data
*/
export const seedTestData = async () => {
// Add your test data seeding logic here
console.log('π± Seeding test data...');
};
`;
// Database setup
const dbSetup = `// tests/helpers/db-setup.ts
export default async function setup() {
console.log('ποΈ Setting up test database...');
// Run migrations
// Seed initial data
console.log('β
Test database setup completed');
}
`;
// Database teardown
const dbTeardown = `// tests/helpers/db-teardown.ts
export default async function teardown() {
console.log('ποΈ Tearing down test database...');
// Clean up test data
// Close connections
console.log('β
Test database teardown completed');
}
`;
fs.writeFileSync(path.join(this.projectRoot, 'tests/helpers/db-helper.ts'), dbHelper);
fs.writeFileSync(path.join(this.projectRoot, 'tests/helpers/db-setup.ts'), dbSetup);
fs.writeFileSync(path.join(this.projectRoot, 'tests/helpers/db-teardown.ts'), dbTeardown);
}
async generateAuthHelpers() {
const authHelper = `// tests/helpers/auth-helper.ts
import jwt from 'jsonwebtoken';
import { TestDataFactory } from './test-utils';
const JWT_SECRET = process.env.TEST_JWT_SECRET || 'test-secret';
/**
* Generate a test JWT token
*/
export const generateTestToken = (payload: any = {}) => {
const defaultPayload = {
userId: 1,
email: 'test@example.com',
role: 'user',
...payload
};
return jwt.sign(defaultPayload, JWT_SECRET, { expiresIn: '1h' });
};
/**
* Generate authorization header
*/
export const getAuthHeader = (token?: string) => {
const authToken = token || generateTestToken();
return { Authorization: \`Bearer \${authToken}\` };
};
/**
* Create test user with token
*/
export const createTestUserWithToken = (userOverrides: any = {}) => {
const user = TestDataFactory.user(userOverrides);
const token = generateTestToken({ userId: user.id, email: user.email });
return { user, token, headers: getAuthHeader(token) };
};
/**
* Mock authentication middleware
*/
export const mockAuthMiddleware = (user: any = null) => {
return (req: any, res: any, next: any) => {
req.user = user || TestDataFactory.user();
next();
};
};
/**
* Verify JWT token
*/
export const verifyTestToken = (token: string) => {
try {
return jwt.verify(token, JWT_SECRET);
} catch (error) {
throw new Error('Invalid test token');
}
};
`;
fs.writeFileSync(path.join(this.projectRoot, 'tests/helpers/auth-helper.ts'), authHelper);
}
async generateApiHelpers() {
const apiHelper = `// tests/helpers/api-helper.ts
import request from 'supertest';
import { Express } from 'express';
/**
* API test helper class
*/
export class ApiTestHelper {
constructor(private app: Express) {}
/**
* Make authenticated GET request
*/
async get(url: string, token?: string) {
const req = request(this.app).get(url);
if (token) {
req.set('Authorization', \`Bearer \${token}\`);
}
return req;
}
/**
* Make authenticated POST request
*/
async post(url: string, data: any = {}, token?: string) {
const req = request(this.app)
.post(url)
.send(data);
if (token) {
req.set('Authorization', \`Bearer \${token}\`);
}
return req;
}
/**
* Make authenticated PUT request
*/
async put(url: string, data: any = {}, token?: string) {
const req = request(this.app)
.put(url)
.send(data);
if (token) {
req.set('Authorization', \`Bearer \${token}\`);
}
return req;
}
/**
* Make authenticated DELETE request
*/
async delete(url: string, token?: string) {
const req = request(this.app).delete(url);
if (token) {
req.set('Authorization', \`Bearer \${token}\`);
}
return req;
}
}
/**
* Common API test assertions
*/
export const apiAssertions = {
/**
* Assert successful response
*/
expectSuccess: (response: any, statusCode: number = 200) => {
expect(response.status).toBe(statusCode);
expect(response.body).toBeDefined();
},
/**
* Assert error response
*/
expectError: (response: any, statusCode: number, message?: string) => {
expect(response.status).toBe(statusCode);
expect(response.body.error).toBeDefined();
if (message) {
expect(response.body.error).toContain(message);
}
},
/**
* Assert validation error
*/
expectValidationError: (response: any, field?: string) => {
expect(response.status).toBe(400);
expect(response.body.errors).toBeDefined();
if (field) {
expect(response.body.errors).toHaveProperty(field);
}
},
/**
* Assert unauthorized
*/
expectUnauthorized: (response: any) => {
expect(response.status).toBe(401);
},
/**
* Assert forbidden
*/
expectForbidden: (response: any) => {
expect(response.status).toBe(403);
},
/**
* Assert not found
*/
expectNotFound: (response: any) => {
expect(response.status).toBe(404);
}
};
`;
fs.writeFileSync(path.join(this.projectRoot, 'tests/helpers/api-helper.ts'), apiHelper);
}
async generateMocks() {
// Email mock
const emailMock = `// tests/mocks/email.mock.ts
export const mockEmailService = {
sendEmail: jest.fn().mockResolvedValue({ success: true, messageId: 'test-message-id' }),
sendWelcomeEmail: jest.fn().mockResolvedValue({ success: true }),
sendPasswordResetEmail: jest.fn().mockResolvedValue({ success: true }),
sendVerificationEmail: jest.fn().mockResolvedValue({ success: true })
};
// Mock nodemailer
jest.mock('nodemailer', () => ({
createTransporter: jest.fn(() => ({
sendMail: jest.fn().mockResolvedValue({ messageId: 'test-message-id' })
}))
}));
`;
// Redis mock
const redisMock = `// tests/mocks/redis.mock.ts
export const mockRedisClient = {
get: jest.fn(),
set: jest.fn(),
del: jest.fn(),
exists: jest.fn(),
expire: jest.fn(),
flushall: jest.fn(),
quit: jest.fn()
};
// Mock ioredis
jest.mock('ioredis', () => {
return jest.fn().mockImplementation(() => mockRedisClient);
});
`;
// External API mock
const externalApiMock = `// tests/mocks/external-api.mock.ts
import nock from 'nock';
/**
* Mock external API calls
*/
export const mockExternalApi = {
/**
* Mock successful API response
*/
mockSuccess: (baseUrl: string, path: string, response: any) => {
return nock(baseUrl)
.get(path)
.reply(200, response);
},
/**
* Mock API error
*/
mockError: (baseUrl: string, path: string, statusCode: number = 500) => {
return nock(baseUrl)
.get(path)
.reply(statusCode, { error: 'External API error' });
},
/**
* Clean all mocks
*/
cleanAll: () => {
nock.cleanAll();
}
};
// Setup and teardown
beforeEach(() => {
nock.disableNetConnect();
nock.enableNetConnect('127.0.0.1');
});
afterEach(() => {
nock.cleanAll();
});
`;
fs.writeFileSync(path.join(this.projectRoot, 'tests/mocks/email.mock.ts'), emailMock);
fs.writeFileSync(path.join(this.projectRoot, 'tests/mocks/redis.mock.ts'), redisMock);
fs.writeFileSync(path.join(this.projectRoot, 'tests/mocks/external-api.mock.ts'), externalApiMock);
console.log('β
Generated test mocks');
}
async generateFixtures() {
// User fixtures
const userFixtures = `[
{
"id": 1,
"email": "admin@example.com",
"name": "Admin User",
"role": "admin",
"isActive": true,
"createdAt": "2024-01-01T00:00:00.000Z"
},
{
"id": 2,
"email": "user@example.com",
"name": "Regular User",
"role": "user",
"isActive": true,
"createdAt": "2024-01-01T00:00:00.000Z"
},
{
"id": 3,
"email": "inactive@example.com",
"name": "Inactive User",
"role": "user",
"isActive": false,
"createdAt": "2024-01-01T00:00:00.000Z"
}
]
`;
// Product fixtures
const productFixtures = `[
{
"id": 1,
"name": "Test Product 1",
"description": "This is a test product",
"price": 99.99,
"stock": 100,
"isActive": true,
"createdAt": "2024-01-01T00:00:00.000Z"
},
{
"id": 2,
"name": "Test Product 2",
"description": "Another test product",
"price": 149.99,
"stock": 50,
"isActive": true,
"createdAt": "2024-01-01T00:00:00.000Z"
},
{
"id": 3,
"name": "Out of Stock Product",
"description": "Product with no stock",
"price": 199.99,
"stock": 0,
"isActive": false,
"createdAt": "2024-01-01T00:00:00.000Z"
}
]
`;
fs.writeFileSync(path.join(this.projectRoot, 'tests/fixtures/users.json'), userFixtures);
fs.writeFileSync(path.join(this.projectRoot, 'tests/fixtures/products.json'), productFixtures);
console.log('β
Generated test fixtures');
}
async generateExampleTests() {
// Unit test example
const unitTestExample = `// tests/unit/example.test.ts
import { TestDataFactory } from '../helpers/test-utils';
describe('Example Unit Tests', () => {
describe('TestDataFactory', () => {
it('should create a user with default values', () => {
const user = TestDataFactory.user();
expect(user).toHaveProperty('id');
expect(user).toHaveProperty('email');
expect(user).toHaveProperty('name');
expect(user.email).toContain('@example.com');
});
it('should create a user with custom values', () => {
const customUser = TestDataFactory.user({
email: 'custom@test.com',
name: 'Custom User'
});
expect(customUser.email).toBe('custom@test.com');
expect(customUser.name).toBe('Custom User');
});
});
describe('String utilities', () => {
it('should validate email format', () => {
const isValidEmail = (email: string) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
expect(isValidEmail('test@example.com')).toBe(true);
expect(isValidEmail('invalid-email')).toBe(false);
});
});
});
`;
// Integration test example
const integrationTestExample = `// tests/integration/api.test.ts
import request from 'supertest';
import { app } from '../../src/app';
import { generateTestToken } from '../helpers/auth-helper';
import { ApiTestHelper, apiAssertions } from '../helpers/api-helper';
describe('API Integration Tests', () => {
let apiHelper: ApiTestHelper;
let authToken: string;
beforeAll(() => {
apiHelper = new ApiTestHelper(app);
authToken = generateTestToken();
});
describe('GET /health', () => {
it('should return health status', async () => {
const response = await request(app)
.get('/health')
.expect(200);
expect(response.body).toHaveProperty('status', 'ok');
expect(response.body).toHaveProperty('timestamp');
});
});
describe('Authentication endpoints', () => {
it('should require authentication for protected routes', async () => {
const response = await request(app)
.get('/api/users/profile')
.expect(401);
apiAssertions.expectUnauthorized(response);
});
it('should allow access with valid token', async () => {
const response = await apiHelper.get('/api/users/profile', authToken);
apiAssertions.expectSuccess(response);
});
});
});
`;
// E2E test example
const e2eTestExample = `// tests/e2e/user-flow.test.ts
import request from 'supertest';
import { app } from '../../src/app';
import { cleanDatabase, seedTestData } from '../helpers/db-helper';
import { TestDataFactory } from '../helpers/test-utils';
describe('User Flow E2E Tests', () => {
beforeEach(async () => {
await cleanDatabase();
await seedTestData();
});
describe('User registration and login flow', () => {
it('should complete full user registration and login flow', async () => {
const userData = {
email: 'newuser@example.com',
password: 'password123',
name: 'New User'
};
// 1. Register user
const registerResponse = await request(app)
.post('/api/auth/register')
.send(userData)
.expect(201);
expect(registerResponse.body).toHaveProperty('user');
expect(registerResponse.body.user.email).toBe(userData.email);
// 2. Login user
const loginResponse = await request(app)
.post('/api/auth/login')
.send({
email: userData.email,
password: userData.password
})
.expect(200);
expect(loginResponse.body).toHaveProperty('token');
expect(loginResponse.body).toHaveProperty('user');
const { token } = loginResponse.body;
// 3. Access protected route
const profileResponse = await request(app)
.get('/api/users/profile')
.set('Authorization', \`Bearer \${token}\`)
.expect(200);
expect(profileResponse.body.email).toBe(userData.email);
// 4. Update profile
const updateResponse = await request(app)
.put('/api/users/profile')
.set('Authorization', \`Bearer \${token}\`)
.send({ name: 'Updated Name' })
.expect(200);
expect(updateResponse.body.name).toBe('Updated Name');
});
});
});
`;
fs.writeFileSync(path.join(this.projectRoot, 'tests/unit/example.test.ts'), unitTestExample);
fs.writeFileSync(path.join(this.projectRoot, 'tests/integration/api.test.ts'), integrationTestExample);
fs.writeFileSync(path.join(this.projectRoot, 'tests/e2e/user-flow.test.ts'), e2eTestExample);
console.log('β
Generated example tests');
}
async generatePlaywrightConfig() {
const playwrightConfig = `// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure'
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] }
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] }
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] }
}
],
webServer: {
command: 'npm run start:test',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI
}
});
`;
fs.writeFileSync(path.join(this.projectRoot, 'playwright.config.ts'), playwrightConfig);
console.log('β
Generated Playwright configuration');
}
async generateTestScripts() {
// Test runner script
const runTestsScript = `#!/bin/bash
# scripts/run-tests.sh
set -e
echo "π§ͺ Running test suite..."
# Set test environment
export NODE_ENV=test
# Run different test types based on argument
case "\$1" in
"unit")
echo "π¬ Running unit tests..."
npm run test:unit
;;
"integration")
echo "π Running integration tests..."
npm run test:integration
;;
"e2e")
echo "π Running E2E tests..."
npm run test:e2e
;;
"coverage")
echo "π Running tests with coverage..."
npm run test:coverage
;;
"all")
echo "π Running all tests..."
npm run test:unit
npm run test:integration
npm run test:e2e
;;
*)
echo "Usage: \$0 {unit|integration|e2e|coverage|all}"
exit 1
;;
esac
echo "β
Tests completed successfully!"
`;
// Test setup script
const setupTestsScript = `#!/bin/bash
# scripts/setup-tests.sh
set -e
echo "π§ Setting up test environment..."
# Create test database
echo "ποΈ Setting up test database..."
if [ ! -z "\$TEST_DATABASE_URL" ]; then
npx prisma migrate deploy --schema=./prisma/schema.prisma
echo "β
Test database migrations applied"
else
echo "β οΈ TEST_DATABASE_URL not set, skipping database setup"
fi
# Install test dependencies
echo "π¦ Installing test dependencies..."
npm install --save-dev \
jest \
/jest \
ts-jest \
supertest \
/supertest \
nock \
/nock
# Create test directories
mkdir -p tests/{unit,integration,e2e,helpers,mocks,fixtures}
echo "β
Test environment setup completed!"
`;
fs.writeFileSync(path.join(this.projectRoot, 'scripts/run-tests.sh'), runTestsScript);
fs.writeFileSync(path.join(this.projectRoot, 'scripts/setup-tests.sh'), setupTestsScript);
// Make scripts executable (Unix systems)
if (process.platform !== 'win32') {
try {
fs.chmodSync(path.join(this.projectRoot, 'scripts/run-tests.sh'), '755');
fs.chmodSync(path.join(this.projectRoot, 'scripts/setup-tests.sh'), '755');
} catch (error) {
console.warn('β οΈ Could not make scripts executable:', error.message);
}
}
console.log('β
Generated test scripts');
}
async updatePackageJson() {
const packageJsonPath = path.join(this.projectRoot, 'package.json');
let packageJson = {};
if (fs.existsSync(packageJsonPath)) {
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
}
// Add test scripts
packageJson.scripts = {
...packageJson.scripts,
// Test commands
"test": "jest",
"test:watch": "jest --watch",
"test:unit": "jest --config jest.unit.config.js",
"test:integration": "jest --config jest.integration.config.js",
"test:e2e": "jest --config jest.e2e.config.js",
"test:coverage": "jest --coverage",
"test:ci": "jest --ci --coverage --watchAll=false",
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand",
// Playwright E2E (if enabled)
...(this.config.e2e ? {
"test:e2e:playwright": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:headed": "playwright test --headed"
} : {}),
// Test utilities
"test:setup": "./scripts/setup-tests.sh",
"test:clean": "rm -rf coverage/ test-results/ playwright-report/",
"test:db:reset": "npx prisma migrate reset --force --skip-seed",
// Performance testing (if enabled)
...(this.config.performance ? {
"test:perf": "artillery run tests/performance/load-test.yml",
"test:load": "k6 run tests/performance/load-test.js"
} : {})
};
// Add test dependencies
packageJson.devDependencies = {
...packageJson.devDependencies,
"jest": "^29.7.0",
"@types/jest": "^29.5.5",
"ts-jest": "^29.1.1",
"supertest": "^6.3.3",
"@types/supertest": "^2.0.15",
"nock": "^13.3.3",
"@types/nock": "^11.1.0",
...(this.config.e2e ? {
"@playwright/test": "^1.40.0"
} : {}),
...(this.config.performance ? {
"artillery": "^2.0.0",
"k6": "^0.47.0"
} : {})
};
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
console.log('β
Updated package.json with testing dependencies and scripts');
}
getGeneratedFiles() {
const files = [
'jest.config.js',
'jest.unit.config.js',
'jest.integration.config.js',
'jest.e2e.config.js',
'tests/setup.ts',
'tests/teardown.ts',
'tests/helpers/test-utils.ts',
'tests/mocks/email.mock.ts',
'tests/mocks/redis.mock.ts',
'tests/mocks/external-api.mock.ts',
'tests/fixtures/users.json',
'tests/fixtures/products.json',
'tests/unit/example.test.ts',
'tests/integration/api.test.ts',
'tests/e2e/user-flow.test.ts',
'scripts/run-tests.sh',
'scripts/setup-tests.sh'
];
if (this.config.database) {
files.push(
'tests/helpers/db-helper.ts',
'tests/helpers/db-setup.ts',
'tests/helpers/db-teardown.ts'
);
}
if (this.config.auth) {
files.push('tests/helpers/auth-helper.ts');
}
if (this.config.api) {
files.push('tests/helpers/api-helper.ts');
}
if (this.config.e2e) {
files.push('playwright.config.ts');
}
return files;
}
}
module.exports = TestingModule;