UNPKG

@apistudio/apim-cli

Version:

CLI for API Management Products

316 lines (262 loc) 12.9 kB
import { ConfigLoader } from '../core/impl/config-loader.impl'; import fs from 'fs/promises'; import path from 'path'; // Mock fs/promises jest.mock('fs/promises', () => ({ access: jest.fn().mockResolvedValue(undefined), readFile: jest.fn() })); // Mock path jest.mock('path', () => ({ join: jest.fn((base, ...parts) => `${base}/${parts.join('/')}`), isAbsolute: jest.fn(p => p.startsWith('/')), })); describe('ConfigLoader', () => { let configLoader: ConfigLoader; const baseDir = '/test/base/dir'; beforeEach(() => { jest.clearAllMocks(); // Reset mock implementations to defaults (path.join as jest.Mock).mockImplementation((base, ...parts) => `${base}/${parts.join('/')}`); (path.isAbsolute as jest.Mock).mockImplementation(p => p.startsWith('/')); (fs.access as jest.Mock).mockResolvedValue(undefined); // Create a fresh instance for each test configLoader = new ConfigLoader(baseDir); }); describe('loadConfig', () => { it('should load JSON config file', async () => { // Setup const configPath = 'test-config.json'; const resolvedPath = `${baseDir}/${configPath}`; const configContent = JSON.stringify({ skipTransform: false, transformations: { mappings: [{ source: '$.spec.value', target: '$.Test.value' }], replacements: [] } }); (fs.readFile as jest.Mock).mockResolvedValue(configContent); (path.join as jest.Mock).mockReturnValue(resolvedPath); // Execute const result = await configLoader.loadConfig(configPath); // Verify expect(fs.access).toHaveBeenCalledWith(resolvedPath); expect(fs.readFile).toHaveBeenCalledWith(resolvedPath, 'utf-8'); expect(result).toEqual({ skipTransform: false, transformations: { mappings: [{ source: '$.spec.value', target: '$.Test.value' }], replacements: [] } }); }); it('should handle config with inheritance', async () => { // Setup const configPath = 'child-config.json'; const parentPath = 'parent-config.json'; const resolvedConfigPath = `${baseDir}/${configPath}`; const resolvedParentPath = `${baseDir}/${parentPath}`; const parentConfig = { skipTransform: false, transformations: { mappings: [{ source: '$.spec.parentValue', target: '$.Test.parentValue' }], replacements: [] } }; const childConfig = { extends: parentPath, transformations: { mappings: [{ source: '$.spec.childValue', target: '$.Test.childValue' }], replacements: [{ target: '$.Test.replacement', value: 'test', precedence: 10, operation: 'replace' }] } }; (fs.readFile as jest.Mock).mockImplementation((path) => { if (path === resolvedConfigPath) { return Promise.resolve(JSON.stringify(childConfig)); } else if (path === resolvedParentPath) { return Promise.resolve(JSON.stringify(parentConfig)); } return Promise.reject(new Error(`Unexpected path: ${path}`)); }); (path.join as jest.Mock).mockImplementation((base, file) => { if (file === configPath) return resolvedConfigPath; if (file === parentPath) return resolvedParentPath; return `${base}/${file}`; }); // Execute const result = await configLoader.loadConfig(configPath); // Verify expect(fs.access).toHaveBeenCalledWith(resolvedConfigPath); expect(fs.access).toHaveBeenCalledWith(resolvedParentPath); expect(fs.readFile).toHaveBeenCalledWith(resolvedConfigPath, 'utf-8'); expect(fs.readFile).toHaveBeenCalledWith(resolvedParentPath, 'utf-8'); // The actual implementation might not set skipTransform if it's not in the config expect(result).toHaveProperty('extends', parentPath); expect(result.transformations).toBeDefined(); if (result.transformations) { expect(result.transformations.mappings).toContainEqual({ source: '$.spec.parentValue', target: '$.Test.parentValue' }); expect(result.transformations.mappings).toContainEqual({ source: '$.spec.childValue', target: '$.Test.childValue' }); expect(result.transformations.replacements).toContainEqual({ target: '$.Test.replacement', value: 'test', precedence: 10, operation: 'replace' }); } }); it('should handle config with custom transformer', async () => { // Setup const configPath = 'custom-config.json'; const resolvedPath = `${baseDir}/${configPath}`; const configContent = JSON.stringify({ skipTransform: false, custom: 'custom-transformer', transformations: { mappings: [{ source: '$.spec.value', target: '$.Test.value' }], replacements: [] } }); (fs.readFile as jest.Mock).mockResolvedValue(configContent); (path.join as jest.Mock).mockReturnValue(resolvedPath); // Execute const result = await configLoader.loadConfig(configPath); // Verify expect(result).toEqual({ skipTransform: false, custom: 'custom-transformer' // transformations should be removed when custom is specified }); }); it('should handle config with inheritance and custom transformer', async () => { // Setup const configPath = 'child-config.json'; const parentPath = 'parent-config.json'; const resolvedConfigPath = `${baseDir}/${configPath}`; const resolvedParentPath = `${baseDir}/${parentPath}`; const parentConfig = { skipTransform: false, transformations: { mappings: [{ source: '$.spec.parentValue', target: '$.Test.parentValue' }], replacements: [] } }; const childConfig = { extends: parentPath, custom: 'custom-transformer', transformations: { mappings: [{ source: '$.spec.childValue', target: '$.Test.childValue' }], replacements: [] } }; (fs.readFile as jest.Mock).mockImplementation((path) => { if (path === resolvedConfigPath) { return Promise.resolve(JSON.stringify(childConfig)); } else if (path === resolvedParentPath) { return Promise.resolve(JSON.stringify(parentConfig)); } return Promise.reject(new Error(`Unexpected path: ${path}`)); }); (path.join as jest.Mock).mockImplementation((base, file) => { if (file === configPath) return resolvedConfigPath; if (file === parentPath) return resolvedParentPath; return `${base}/${file}`; }); // Execute const result = await configLoader.loadConfig(configPath); // Verify // The actual implementation might not set skipTransform if it's not in the config expect(result).toHaveProperty('extends', parentPath); expect(result).toHaveProperty('custom', 'custom-transformer'); // transformations should be removed when custom is specified expect(result.transformations).toBeUndefined(); }); it('should cache loaded configs', async () => { // Setup const configPath = 'test-config.json'; const resolvedPath = `${baseDir}/${configPath}`; const configContent = JSON.stringify({ skipTransform: false, transformations: { mappings: [{ source: '$.spec.value', target: '$.Test.value' }], replacements: [] } }); (fs.readFile as jest.Mock).mockResolvedValue(configContent); (path.join as jest.Mock).mockReturnValue(resolvedPath); // Execute await configLoader.loadConfig(configPath); await configLoader.loadConfig(configPath); // Verify expect(fs.readFile).toHaveBeenCalledTimes(1); }); it('should handle errors when loading config', async () => { // Setup const configPath = 'nonexistent-config.json'; const resolvedPath = `${baseDir}/${configPath}`; (fs.access as jest.Mock).mockRejectedValue(new Error('File not found')); (path.join as jest.Mock).mockReturnValue(resolvedPath); // Execute & Verify await expect(configLoader.loadConfig(configPath)).rejects.toThrow(); }); }); describe('resolveConfigPath', () => { it('should resolve paths starting with src/', async () => { // Setup const configPath = 'src/configs/test-config.json'; const expectedPath = `${baseDir}/configs/test-config.json`; (path.join as jest.Mock).mockReturnValueOnce(expectedPath); // Execute const resolvedPath = await (configLoader as any).resolveConfigPath(configPath); // Verify expect(resolvedPath).toBe(expectedPath); expect(path.join).toHaveBeenCalledWith(baseDir, 'configs/test-config.json'); }); it('should resolve paths starting with /src/', async () => { // Setup const configPath = '/src/configs/test-config.json'; const expectedPath = `${baseDir}/configs/test-config.json`; (path.join as jest.Mock).mockReturnValue(expectedPath); // Execute const resolvedPath = await (configLoader as any).resolveConfigPath(configPath); // Verify expect(resolvedPath).toBe(expectedPath); expect(path.join).toHaveBeenCalledWith(baseDir, 'configs/test-config.json'); }); it('should resolve relative paths', async () => { // Setup const configPath = 'configs/test-config.json'; const expectedPath = `${baseDir}/configs/test-config.json`; (path.join as jest.Mock).mockReturnValue(expectedPath); (path.isAbsolute as jest.Mock).mockReturnValue(false); // Execute const resolvedPath = await (configLoader as any).resolveConfigPath(configPath); // Verify expect(resolvedPath).toBe(expectedPath); expect(path.join).toHaveBeenCalledWith(baseDir, configPath); }); it('should resolve absolute paths containing /src/', async () => { // Setup const configPath = '/absolute/path/src/configs/test-config.json'; const expectedPath = `${baseDir}/configs/test-config.json`; (path.isAbsolute as jest.Mock).mockReturnValue(true); (path.join as jest.Mock).mockReturnValue(expectedPath); // Execute const resolvedPath = await (configLoader as any).resolveConfigPath(configPath); // Verify expect(resolvedPath).toBe(expectedPath); }); it('should handle other absolute paths', async () => { // Setup const configPath = '/absolute/path/configs/test-config.json'; (path.isAbsolute as jest.Mock).mockReturnValue(true); // Execute const resolvedPath = await (configLoader as any).resolveConfigPath(configPath); // Verify expect(resolvedPath).toBe(configPath); }); it('should throw error if file does not exist', async () => { // Setup const configPath = 'nonexistent-config.json'; const expectedPath = `${baseDir}/nonexistent-config.json`; (path.join as jest.Mock).mockReturnValue(expectedPath); (fs.access as jest.Mock).mockRejectedValue(new Error('File not found')); // Execute & Verify await expect((configLoader as any).resolveConfigPath(configPath)).rejects.toThrow(); }); }); });