UNPKG

ruch

Version:

Revolutionary React TypeScript CLI with hexagonal architecture & AI-powered development assistance. Create maintainable, scalable applications with domain-driven design and integrated AI tooling.

473 lines (378 loc) 21 kB
import { describe, it, expect, beforeEach, vi } from 'vitest'; import fs from 'fs-extra'; import path from 'path'; import { logger } from '../../src/utils/logger'; import { initMsw, generateMswHandlers, generateMswMocks, updateMsw, updateMswDomain } from '../../src/commands/msw'; import * as templates from '../../src/templates/msw'; import * as dynamicTemplates from '../../src/templates/msw-dynamic'; import * as domainAnalyzer from '../../src/utils/domain-analyzer'; import * as domainFileAnalyzer from '../../src/utils/domain-file-analyzer'; // Mock all dependencies vi.mock('fs-extra'); vi.mock('../../src/utils/logger', () => ({ logger: { error: vi.fn(), success: vi.fn(), info: vi.fn(), warning: vi.fn(), log: vi.fn(), } })); vi.mock('../../src/templates/msw', () => ({ getMswSetupTemplate: vi.fn(), getMswBrowserTemplate: vi.fn(), getMswServerTemplate: vi.fn(), getMswHandlersTemplate: vi.fn(), getMswHandlersIndexTemplate: vi.fn(), getMswMocksTemplate: vi.fn(), getMswSetupTestsTemplate: vi.fn(), })); vi.mock('../../src/templates/msw-dynamic', () => ({ generateDynamicMswHandlers: vi.fn(), generateDynamicMockData: vi.fn(), })); vi.mock('../../src/utils/domain-analyzer', () => ({ getExistingDomains: vi.fn(), })); vi.mock('../../src/utils/domain-file-analyzer', () => ({ analyzeDomain: vi.fn(), })); describe('MSW Commands', () => { const projectRoot = '/test/project'; const srcPath = path.join(projectRoot, 'src'); const mswPath = path.join(srcPath, 'mocks'); const handlersPath = path.join(mswPath, 'handlers'); beforeEach(() => { vi.clearAllMocks(); }); describe('initMsw', () => { it('should initialize MSW configuration when not exists', async () => { const { getMswSetupTemplate, getMswBrowserTemplate, getMswServerTemplate, getMswSetupTestsTemplate, getMswHandlersIndexTemplate } = await import('../../src/templates/msw'); (fs.exists as any).mockResolvedValue(false); (fs.ensureDir as any).mockResolvedValue(undefined); (fs.writeFile as any).mockResolvedValue(undefined); (getMswSetupTemplate as any).mockReturnValue('// setup'); (getMswBrowserTemplate as any).mockReturnValue('// browser'); (getMswServerTemplate as any).mockReturnValue('// server'); (getMswSetupTestsTemplate as any).mockReturnValue('// setupTests'); (getMswHandlersIndexTemplate as any).mockReturnValue('// handlers index'); await initMsw(fs, logger, projectRoot); expect(fs.exists).toHaveBeenCalledWith(path.join(mswPath, 'setup.ts')); expect(fs.ensureDir).toHaveBeenCalledWith(mswPath); expect(fs.ensureDir).toHaveBeenCalledWith(handlersPath); expect(fs.writeFile).toHaveBeenCalledTimes(5); expect(logger.success).toHaveBeenCalledWith('MSW initialized successfully!'); expect(logger.info).toHaveBeenCalledWith('💡 Use "ruch msw handlers generate" to create domain handlers'); }); it('should not initialize when already exists', async () => { (fs.exists as any).mockResolvedValue(true); await initMsw(fs, logger, projectRoot); expect(fs.ensureDir).not.toHaveBeenCalled(); expect(fs.writeFile).not.toHaveBeenCalled(); expect(logger.warning).toHaveBeenCalledWith('MSW is already initialized in this project'); }); it('should handle errors during initialization', async () => { (fs.exists as any).mockResolvedValue(false); (fs.ensureDir as any).mockRejectedValue(new Error('Permission denied')); await initMsw(fs, logger, projectRoot); expect(logger.error).toHaveBeenCalledWith('Error initializing MSW: Permission denied'); }); }); describe('generateMswHandlers', () => { it('should generate handlers for all domains', async () => { const { getExistingDomains } = await import('../../src/utils/domain-analyzer'); const { getMswHandlersTemplate, getMswSetupTemplate, getMswBrowserTemplate, getMswServerTemplate, getMswHandlersIndexTemplate } = await import('../../src/templates/msw'); const mockDomains = [ { name: 'user', path: '/test/domains/user' }, { name: 'product', path: '/test/domains/product' } ]; (fs.exists as any).mockImplementation((filePath: string) => { if (filePath === mswPath) return Promise.resolve(true); if (filePath.includes('user.ts') || filePath.includes('product.ts')) return Promise.resolve(false); return Promise.resolve(false); }); (fs.ensureDir as any).mockResolvedValue(undefined); (fs.writeFile as any).mockResolvedValue(undefined); (getExistingDomains as any).mockResolvedValue(mockDomains); (getMswHandlersTemplate as any).mockReturnValue('// handler'); (getMswSetupTemplate as any).mockReturnValue('// setup'); (getMswBrowserTemplate as any).mockReturnValue('// browser'); (getMswServerTemplate as any).mockReturnValue('// server'); (getMswHandlersIndexTemplate as any).mockReturnValue('// handlers index'); await generateMswHandlers(fs, logger, projectRoot); expect(getExistingDomains).toHaveBeenCalledWith(fs, projectRoot); expect(fs.writeFile).toHaveBeenCalledWith( path.join(handlersPath, 'user.ts'), '// handler', 'utf8' ); expect(fs.writeFile).toHaveBeenCalledWith( path.join(handlersPath, 'product.ts'), '// handler', 'utf8' ); expect(logger.success).toHaveBeenCalledWith('MSW handlers generated successfully!'); expect(logger.info).toHaveBeenCalledWith('📁 Generated 2 new handler(s)'); }); it('should not generate when MSW not initialized', async () => { (fs.exists as any).mockResolvedValue(false); await generateMswHandlers(fs, logger, projectRoot); expect(logger.error).toHaveBeenCalledWith('MSW not initialized. Run "ruch msw init" first.'); expect(fs.writeFile).not.toHaveBeenCalled(); }); it('should warn when no domains exist', async () => { const { getExistingDomains } = await import('../../src/utils/domain-analyzer'); (fs.exists as any).mockResolvedValue(true); (getExistingDomains as any).mockResolvedValue([]); await generateMswHandlers(fs, logger, projectRoot); expect(logger.warning).toHaveBeenCalledWith('No domains found. Create domains first with "ruch create <domain-name>".'); }); it('should skip existing handlers', async () => { const { getExistingDomains } = await import('../../src/utils/domain-analyzer'); const mockDomains = [{ name: 'user', path: '/test/domains/user' }]; (fs.exists as any).mockImplementation((filePath: string) => { if (filePath === mswPath) return Promise.resolve(true); if (filePath.includes('user.ts')) return Promise.resolve(true); return Promise.resolve(false); }); (getExistingDomains as any).mockResolvedValue(mockDomains); await generateMswHandlers(fs, logger, projectRoot); expect(logger.info).toHaveBeenCalledWith('⏭️ Handler already exists: user.ts'); expect(logger.info).toHaveBeenCalledWith('📁 Generated 0 new handler(s)'); }); it('should handle errors', async () => { const { getExistingDomains } = await import('../../src/utils/domain-analyzer'); (fs.exists as any).mockResolvedValue(true); (getExistingDomains as any).mockRejectedValue(new Error('Analysis failed')); await generateMswHandlers(fs, logger, projectRoot); expect(logger.error).toHaveBeenCalledWith('Error generating MSW handlers: Analysis failed'); }); }); describe('generateMswMocks', () => { it('should generate mock data for all domains', async () => { const { getExistingDomains } = await import('../../src/utils/domain-analyzer'); const { getMswMocksTemplate } = await import('../../src/templates/msw'); const mockDomains = [ { name: 'user', path: '/test/domains/user' }, { name: 'product', path: '/test/domains/product' } ]; (fs.exists as any).mockImplementation((filePath: string) => { if (filePath === mswPath) return Promise.resolve(true); if (filePath.includes('mockData.ts')) return Promise.resolve(false); return Promise.resolve(false); }); (fs.ensureDir as any).mockResolvedValue(undefined); (fs.writeFile as any).mockResolvedValue(undefined); (getExistingDomains as any).mockResolvedValue(mockDomains); (getMswMocksTemplate as any).mockReturnValue('// mock data'); await generateMswMocks(fs, logger, projectRoot); expect(fs.writeFile).toHaveBeenCalledWith( path.join(srcPath, 'domains', 'user', 'mocks', 'mockData.ts'), '// mock data', 'utf8' ); expect(fs.writeFile).toHaveBeenCalledWith( path.join(srcPath, 'domains', 'product', 'mocks', 'mockData.ts'), '// mock data', 'utf8' ); expect(logger.success).toHaveBeenCalledWith('Mock data generated successfully!'); expect(logger.info).toHaveBeenCalledWith('📁 Generated 2 new mock file(s)'); }); it('should not generate when MSW not initialized', async () => { (fs.exists as any).mockResolvedValue(false); await generateMswMocks(fs, logger, projectRoot); expect(logger.error).toHaveBeenCalledWith('MSW not initialized. Run "ruch msw init" first.'); expect(fs.writeFile).not.toHaveBeenCalled(); }); it('should skip existing mock files', async () => { const { getExistingDomains } = await import('../../src/utils/domain-analyzer'); const mockDomains = [{ name: 'user', path: '/test/domains/user' }]; (fs.exists as any).mockImplementation((filePath: string) => { if (filePath === mswPath) return Promise.resolve(true); if (filePath.includes('mockData.ts')) return Promise.resolve(true); return Promise.resolve(false); }); (getExistingDomains as any).mockResolvedValue(mockDomains); await generateMswMocks(fs, logger, projectRoot); expect(logger.info).toHaveBeenCalledWith('⏭️ Mock data already exists: user/mocks/mockData.ts'); expect(logger.info).toHaveBeenCalledWith('📁 Generated 0 new mock file(s)'); }); it('should handle errors', async () => { const { getExistingDomains } = await import('../../src/utils/domain-analyzer'); (fs.exists as any).mockResolvedValue(true); (getExistingDomains as any).mockRejectedValue(new Error('Mock generation failed')); await generateMswMocks(fs, logger, projectRoot); expect(logger.error).toHaveBeenCalledWith('Error generating mock data: Mock generation failed'); }); }); describe('updateMswDomain', () => { const domainName = 'user'; const mockAnalysis = { domainName: 'user', entities: [{ name: 'User', fields: [], filePath: '' }], ports: [{ name: 'UserPort', methods: [], filePath: '' }], adapters: [{ name: 'UserAdapter', methods: [], filePath: '' }], primaryEntity: { name: 'User', fields: [], filePath: '' } }; it('should update domain with dynamic analysis', async () => { const { getExistingDomains } = await import('../../src/utils/domain-analyzer'); const { analyzeDomain } = await import('../../src/utils/domain-file-analyzer'); const { generateDynamicMswHandlers, generateDynamicMockData } = await import('../../src/templates/msw-dynamic'); const { getMswHandlersIndexTemplate } = await import('../../src/templates/msw'); const mockDomains = [{ name: 'user', path: '/test/domains/user' }]; (fs.exists as any).mockImplementation((filePath: string) => { if (filePath === mswPath) return Promise.resolve(true); if (filePath.includes('user.ts') || filePath.includes('mockData.ts')) return Promise.resolve(true); return Promise.resolve(false); }); (fs.readFile as any).mockResolvedValue('// existing content'); (fs.writeFile as any).mockResolvedValue(undefined); (fs.ensureDir as any).mockResolvedValue(undefined); (getExistingDomains as any).mockResolvedValue(mockDomains); (analyzeDomain as any).mockResolvedValue(mockAnalysis); (generateDynamicMswHandlers as any).mockReturnValue('// dynamic handler'); (generateDynamicMockData as any).mockReturnValue('// dynamic mock'); (getMswHandlersIndexTemplate as any).mockReturnValue('// handlers index'); await updateMswDomain(domainName, fs, logger, projectRoot, true); expect(analyzeDomain).toHaveBeenCalledWith( path.join(srcPath, 'domains', domainName), domainName ); expect(generateDynamicMswHandlers).toHaveBeenCalledWith(mockAnalysis); expect(generateDynamicMockData).toHaveBeenCalledWith(mockAnalysis); expect(logger.success).toHaveBeenCalledWith('MSW configuration updated for domain "user"!'); expect(logger.warning).toHaveBeenCalledWith('⚠️ IMPORTANT: MSW handlers are based on your Port interfaces.'); expect(logger.warning).toHaveBeenCalledWith('⚠️ IMPORTANT: Mock data is generated from your Entity definitions.'); }); it('should not update when MSW not initialized', async () => { (fs.exists as any).mockResolvedValue(false); await updateMswDomain(domainName, fs, logger, projectRoot, true); expect(logger.error).toHaveBeenCalledWith('MSW not initialized. Run "ruch msw init" first.'); }); it('should handle non-existent domain', async () => { const { getExistingDomains } = await import('../../src/utils/domain-analyzer'); (fs.exists as any).mockResolvedValue(true); (getExistingDomains as any).mockResolvedValue([]); await updateMswDomain(domainName, fs, logger, projectRoot, true); expect(logger.error).toHaveBeenCalledWith('Domain "user" not found. Available domains: '); }); it('should fallback to generic templates on analysis error', async () => { const { getExistingDomains } = await import('../../src/utils/domain-analyzer'); const { analyzeDomain } = await import('../../src/utils/domain-file-analyzer'); const { getMswHandlersTemplate, getMswMocksTemplate } = await import('../../src/templates/msw'); const mockDomains = [{ name: 'user', path: '/test/domains/user' }]; (fs.exists as any).mockImplementation((filePath: string) => { if (filePath === mswPath) return Promise.resolve(true); if (filePath.includes('user.ts') || filePath.includes('mockData.ts')) return Promise.resolve(true); return Promise.resolve(false); }); (fs.readFile as any).mockResolvedValue('// existing content'); (fs.writeFile as any).mockResolvedValue(undefined); (fs.ensureDir as any).mockResolvedValue(undefined); (getExistingDomains as any).mockResolvedValue(mockDomains); (analyzeDomain as any).mockRejectedValue(new Error('Analysis failed')); (getMswHandlersTemplate as any).mockReturnValue('// generic handler'); (getMswMocksTemplate as any).mockReturnValue('// generic mock'); await updateMswDomain(domainName, fs, logger, projectRoot, true); expect(logger.warning).toHaveBeenCalledWith('⚠️ Could not analyze domain structure: Analysis failed'); expect(logger.info).toHaveBeenCalledWith('📄 Falling back to generic templates...'); expect(getMswHandlersTemplate).toHaveBeenCalledWith(domainName); expect(getMswMocksTemplate).toHaveBeenCalledWith(domainName); }); it('should create backups before updating', async () => { const { getExistingDomains } = await import('../../src/utils/domain-analyzer'); const { analyzeDomain } = await import('../../src/utils/domain-file-analyzer'); const mockDomains = [{ name: 'user', path: '/test/domains/user' }]; (fs.exists as any).mockImplementation((filePath: string) => { if (filePath === mswPath) return Promise.resolve(true); if (filePath.includes('user.ts') || filePath.includes('mockData.ts')) return Promise.resolve(true); return Promise.resolve(false); }); (fs.readFile as any).mockResolvedValue('// existing content'); (fs.writeFile as any).mockResolvedValue(undefined); (getExistingDomains as any).mockResolvedValue(mockDomains); (analyzeDomain as any).mockResolvedValue(mockAnalysis); await updateMswDomain(domainName, fs, logger, projectRoot, true); // Check that backups were created const writeFileCalls = (fs.writeFile as any).mock.calls; const backupCalls = writeFileCalls.filter((call: any[]) => call[0].includes('.backup.')); expect(backupCalls.length).toBeGreaterThan(0); expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('📁 Handler backup:')); expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('📁 Mock data backup:')); }); it('should handle errors', async () => { const { getExistingDomains } = await import('../../src/utils/domain-analyzer'); (fs.exists as any).mockResolvedValue(true); (getExistingDomains as any).mockRejectedValue(new Error('Update failed')); await updateMswDomain(domainName, fs, logger, projectRoot, true); expect(logger.error).toHaveBeenCalledWith('Error updating MSW for domain "user": Update failed'); }); }); describe('updateMsw', () => { it('should update MSW with missing handlers', async () => { const { getExistingDomains } = await import('../../src/utils/domain-analyzer'); const { getMswHandlersTemplate, getMswSetupTemplate, getMswBrowserTemplate, getMswServerTemplate, getMswHandlersIndexTemplate } = await import('../../src/templates/msw'); const mockDomains = [ { name: 'user', path: '/test/domains/user' }, { name: 'product', path: '/test/domains/product' } ]; (fs.exists as any).mockImplementation((filePath: string) => { if (filePath === mswPath) return Promise.resolve(true); if (filePath.includes('user.ts')) return Promise.resolve(true); if (filePath.includes('product.ts')) return Promise.resolve(false); if (filePath.includes('setup.ts') || filePath.includes('browser.ts') || filePath.includes('server.ts')) return Promise.resolve(true); return Promise.resolve(false); }); (fs.readFile as any).mockResolvedValue('// existing content'); (fs.writeFile as any).mockResolvedValue(undefined); (fs.ensureDir as any).mockResolvedValue(undefined); (getExistingDomains as any).mockResolvedValue(mockDomains); (getMswHandlersTemplate as any).mockReturnValue('// handler'); (getMswSetupTemplate as any).mockReturnValue('// setup'); (getMswBrowserTemplate as any).mockReturnValue('// browser'); (getMswServerTemplate as any).mockReturnValue('// server'); (getMswHandlersIndexTemplate as any).mockReturnValue('// handlers index'); await updateMsw(fs, logger, projectRoot, true); expect(logger.info).toHaveBeenCalledWith('Found 1 missing handler(s):'); expect(logger.info).toHaveBeenCalledWith(' - product'); expect(fs.writeFile).toHaveBeenCalledWith( path.join(handlersPath, 'product.ts'), '// handler', 'utf8' ); expect(logger.success).toHaveBeenCalledWith('MSW configuration updated successfully!'); expect(logger.info).toHaveBeenCalledWith('Added 1 handler(s) to MSW configuration'); }); it('should not update when MSW not initialized', async () => { (fs.exists as any).mockResolvedValue(false); await updateMsw(fs, logger, projectRoot, true); expect(logger.error).toHaveBeenCalledWith('MSW not initialized. Run "ruch msw init" first.'); }); it('should show message when no missing handlers', async () => { const { getExistingDomains } = await import('../../src/utils/domain-analyzer'); const mockDomains = [{ name: 'user', path: '/test/domains/user' }]; (fs.exists as any).mockImplementation((filePath: string) => { if (filePath === mswPath) return Promise.resolve(true); if (filePath.includes('user.ts')) return Promise.resolve(true); return Promise.resolve(false); }); (getExistingDomains as any).mockResolvedValue(mockDomains); await updateMsw(fs, logger, projectRoot, true); expect(logger.info).toHaveBeenCalledWith('MSW configuration is already up to date. No missing handlers found.'); }); it('should handle errors', async () => { const { getExistingDomains } = await import('../../src/utils/domain-analyzer'); (fs.exists as any).mockResolvedValue(true); (getExistingDomains as any).mockRejectedValue(new Error('Global update failed')); await updateMsw(fs, logger, projectRoot, true); expect(logger.error).toHaveBeenCalledWith('Error updating MSW: Global update failed'); }); }); });