UNPKG

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
// 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()}@example.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 \ @types/jest \ ts-jest \ supertest \ @types/supertest \ nock \ @types/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;