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
text/typescript
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');
});
});
});