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.
255 lines (218 loc) • 9.3 kB
text/typescript
import { describe, it, expect, beforeEach, vi } from 'vitest';
import fs from 'fs-extra';
import { logger } from '../../src/utils/logger';
import * as templates from '../../src/templates/domains';
import { createDomain } from '../../src/commands/create';
import path from 'path';
import * as paths from '../../src/utils/paths';
import type { FileSystem } from '../../src/utils/file-operations';
import type { Logger } from '../../src/utils/logging';
import type { DomainGenerationOptions } from '../../src/templates/domains';
vi.mock('fs-extra', () => ({
default: {
exists: vi.fn(),
ensureDir: vi.fn(),
writeFile: vi.fn(),
remove: vi.fn(),
},
}));
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/domains', () => ({
getDomainFiles: vi.fn(),
}));
vi.mock('../src/utils/paths', () => ({
validateDomainName: vi.fn(),
buildDomainDirectoryPath: vi.fn(),
getDomainsSourceDirectoryPath: vi.fn(),
}));
const domainName = 'testdomain';
const domainPath = path.join(process.cwd(), 'src', 'domains', domainName);
const domainsSourcePath = path.join(process.cwd(), 'src', 'domains');
const fullOptions: DomainGenerationOptions = {
withApi: true,
withQueries: true,
withStore: true,
withUi: true,
};
const minimalOptions: DomainGenerationOptions = {
withApi: false,
withQueries: false,
withStore: false,
withUi: false,
};
describe('createDomain', () => {
let mockFileSystem: FileSystem;
let mockLogger: Logger;
beforeEach(() => {
vi.clearAllMocks();
vi.spyOn(paths, 'buildDomainDirectoryPath').mockReturnValue(domainPath);
vi.spyOn(paths, 'validateDomainName').mockReturnValue(true);
vi.spyOn(templates, 'getDomainFiles').mockImplementation((domainName, options = { withUi: true, withStore: true, withQueries: true, withApi: true }) => {
const files = [
{ path: 'entities/Testdomain.ts', content: '// entities' },
{ path: 'entities/Testdomain.test.ts', content: '// entities test' },
{ path: 'ports/TestdomainPort.ts', content: '// ports' },
{ path: 'services/TestdomainService.ts', content: '// services' },
{ path: 'services/TestdomainService.test.ts', content: '// services test' },
{ path: 'adapters/TestdomainAdapter.ts', content: '// adapters' },
{ path: 'adapters/TestdomainAdapter.test.ts', content: '// adapters test' },
{ path: 'hooks/useTestdomain.ts', content: '// hooks' },
{ path: 'hooks/useTestdomain.test.ts', content: '// hooks test' }
];
if (options.withUi) {
files.push({ path: 'ui/TestdomainView.tsx', content: '// ui' });
files.push({ path: 'ui/TestdomainView.test.tsx', content: '// ui test' });
}
return files;
});
// Mocks FileSystem
mockFileSystem = {
exists: vi.fn(),
pathExists: vi.fn(),
ensureDir: vi.fn(),
writeFile: vi.fn(),
remove: vi.fn(),
readFile: vi.fn(),
readdir: vi.fn(),
stat: vi.fn(),
mkdir: vi.fn(),
} as unknown as FileSystem;
// Mocks Logger
mockLogger = {
error: vi.fn(),
success: vi.fn(),
info: vi.fn(),
warning: vi.fn(),
log: vi.fn(),
};
});
it('should create a domain with all modules', async () => {
const existsMock = vi.fn().mockResolvedValue(false);
const ensureDirMock = vi.fn().mockResolvedValue(undefined);
const writeFileMock = vi.fn().mockResolvedValue(undefined);
vi.mocked(mockFileSystem.exists).mockImplementation(existsMock);
vi.mocked(mockFileSystem.ensureDir).mockImplementation(ensureDirMock);
vi.mocked(mockFileSystem.writeFile).mockImplementation(writeFileMock);
await createDomain(domainName, {
withUi: true,
withStore: true,
withQueries: true,
withApi: true
}, mockFileSystem, mockLogger);
const expectedDirs = [
domainPath,
path.join(domainPath, 'entities'),
path.join(domainPath, 'ports'),
path.join(domainPath, 'services'),
path.join(domainPath, 'adapters'),
path.join(domainPath, 'hooks'),
path.join(domainPath, 'ui')
];
const ensureDirCalls = vi.mocked(mockFileSystem.ensureDir).mock.calls.map(call => call[0]);
expectedDirs.forEach(dir => {
expect(ensureDirCalls).toContain(dir);
});
const expectedFiles = [
path.join(domainPath, 'entities', 'Testdomain.ts'),
path.join(domainPath, 'entities', 'Testdomain.test.ts'),
path.join(domainPath, 'ports', 'TestdomainPort.ts'),
path.join(domainPath, 'services', 'TestdomainService.ts'),
path.join(domainPath, 'services', 'TestdomainService.test.ts'),
path.join(domainPath, 'adapters', 'TestdomainAdapter.ts'),
path.join(domainPath, 'adapters', 'TestdomainAdapter.test.ts'),
path.join(domainPath, 'hooks', 'useTestdomain.ts'),
path.join(domainPath, 'hooks', 'useTestdomain.test.ts'),
path.join(domainPath, 'ui', 'TestdomainView.tsx'),
path.join(domainPath, 'ui', 'TestdomainView.test.tsx')
];
expectedFiles.forEach(file => {
expect(mockFileSystem.writeFile).toHaveBeenCalledWith(file, expect.any(String), 'utf8');
});
expect(mockLogger.success).toHaveBeenCalledWith(`Domain "${domainName}" created successfully!`);
});
it('should create a domain with minimal structure', async () => {
const existsMock = vi.fn().mockResolvedValue(false);
const ensureDirMock = vi.fn().mockResolvedValue(undefined);
const writeFileMock = vi.fn().mockResolvedValue(undefined);
vi.mocked(mockFileSystem.exists).mockImplementation(existsMock);
vi.mocked(mockFileSystem.ensureDir).mockImplementation(ensureDirMock);
vi.mocked(mockFileSystem.writeFile).mockImplementation(writeFileMock);
await createDomain(domainName, {
withUi: false,
withStore: false,
withQueries: false,
withApi: false
}, mockFileSystem, mockLogger);
const expectedDirs = [
domainPath,
path.join(domainPath, 'entities'),
path.join(domainPath, 'ports'),
path.join(domainPath, 'services'),
path.join(domainPath, 'adapters'),
path.join(domainPath, 'hooks')
];
const ensureDirCalls = vi.mocked(mockFileSystem.ensureDir).mock.calls.map(call => call[0]);
expectedDirs.forEach(dir => {
expect(ensureDirCalls).toContain(dir);
});
const expectedFiles = [
path.join(domainPath, 'entities', 'Testdomain.ts'),
path.join(domainPath, 'entities', 'Testdomain.test.ts'),
path.join(domainPath, 'ports', 'TestdomainPort.ts'),
path.join(domainPath, 'services', 'TestdomainService.ts'),
path.join(domainPath, 'services', 'TestdomainService.test.ts'),
path.join(domainPath, 'adapters', 'TestdomainAdapter.ts'),
path.join(domainPath, 'adapters', 'TestdomainAdapter.test.ts'),
path.join(domainPath, 'hooks', 'useTestdomain.ts'),
path.join(domainPath, 'hooks', 'useTestdomain.test.ts')
];
expectedFiles.forEach(file => {
expect(mockFileSystem.writeFile).toHaveBeenCalledWith(file, expect.any(String), 'utf8');
});
// Vérifier que les fichiers UI ne sont pas créés
const uiFiles = [
path.join(domainPath, 'ui', 'TestdomainView.tsx'),
path.join(domainPath, 'ui', 'TestdomainView.test.tsx')
];
uiFiles.forEach(file => {
const matchingCall = vi.mocked(mockFileSystem.writeFile).mock.calls.find(
(call) => call[0] === file
);
expect(matchingCall).toBeUndefined();
});
expect(mockLogger.success).toHaveBeenCalledWith(`Domain "${domainName}" created successfully!`);
});
it('should not create domain if it already exists', async () => {
(mockFileSystem.exists as any).mockResolvedValue(true);
await createDomain(domainName, fullOptions, mockFileSystem, mockLogger);
expect(mockFileSystem.ensureDir).not.toHaveBeenCalled();
expect(mockFileSystem.writeFile).not.toHaveBeenCalled();
expect(mockLogger.error).toHaveBeenCalledWith(`Domain "${domainName}" already exists at ${domainPath}`);
});
it('should not create domain if name is invalid', async () => {
(paths.validateDomainName as any).mockReturnValue(false);
await createDomain(domainName, fullOptions, mockFileSystem, mockLogger);
expect(mockFileSystem.ensureDir).not.toHaveBeenCalled();
expect(mockFileSystem.writeFile).not.toHaveBeenCalled();
expect(mockLogger.error).toHaveBeenCalledWith(`❌ Invalid domain name: "${domainName}"`);
});
it('should clean up on error', async () => {
(mockFileSystem.exists as any)
.mockResolvedValueOnce(false) // avant création
.mockResolvedValueOnce(true); // après l'erreur, pour cleanup
(mockFileSystem.ensureDir as any).mockResolvedValue(undefined);
(mockFileSystem.writeFile as any).mockRejectedValue(new Error('Test error'));
await createDomain(domainName, fullOptions, mockFileSystem, mockLogger);
expect(mockFileSystem.remove).toHaveBeenCalledWith(domainPath);
expect(mockLogger.error).toHaveBeenCalledWith('Error creating domain: Test error');
expect(mockLogger.info).toHaveBeenCalledWith('Cleanup completed after error.');
});
});