import fs from 'fs'; import path from 'path'; import { PathResolver, PathType } from '../../src/config/path-resolver'; import { EnvironmentType, IEnvironmentConfig } from '../../src/config/environment'; // Mock fs module jest.mock('fs'); describe('PathResolver', () => { let pathResolver: PathResolver; let mockEnvConfig: jest.Mocked; let mockFs: jest.Mocked; beforeEach(() => { // Clear all mocks before each test jest.clearAllMocks(); // Get the mocked fs module mockFs = fs as jest.Mocked; // Create a mock environment config mockEnvConfig = { getEnvironment: jest.fn(), isDevelopment: jest.fn(), isProduction: jest.fn() }; }); describe('constructor', () => { it('should create an instance with provided environment config', () => { pathResolver = new PathResolver(mockEnvConfig); expect(pathResolver).toBeInstanceOf(PathResolver); }); it('should create an instance with default environment config when not provided', () => { pathResolver = new PathResolver(); expect(pathResolver).toBeInstanceOf(PathResolver); }); }); describe('resolvePath', () => { beforeEach(() => { mockEnvConfig.isDevelopment.mockReturnValue(true); mockEnvConfig.isProduction.mockReturnValue(false); pathResolver = new PathResolver(mockEnvConfig); }); it('should resolve CLI_ROOT path type', () => { mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockReturnValue(JSON.stringify({ workspaces: ['packages/*'] })); const result = pathResolver.resolvePath(PathType.CLI_ROOT); expect(result).toBeDefined(); expect(typeof result).toBe('string'); }); it('should resolve PRIVATE_APP_DIST path type', () => { mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockReturnValue(JSON.stringify({ workspaces: ['packages/*'] })); const result = pathResolver.resolvePath(PathType.PRIVATE_APP_DIST); expect(result).toBeDefined(); expect(result).toContain('private-application'); expect(result).toContain('dist'); }); it('should resolve PRIVATE_APP_ROOT path type', () => { mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockReturnValue(JSON.stringify({ workspaces: ['packages/*'] })); const result = pathResolver.resolvePath(PathType.PRIVATE_APP_ROOT); expect(result).toBeDefined(); expect(result).toContain('private-application'); }); it('should resolve PUBLIC_LIBS_TEMPLATES path type', () => { mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockReturnValue(JSON.stringify({ workspaces: ['packages/*'] })); const result = pathResolver.resolvePath(PathType.PUBLIC_LIBS_TEMPLATES); expect(result).toBeDefined(); expect(result).toContain('public-libs'); expect(result).toContain('templates'); }); it('should resolve PUBLIC_LIBS_UI_WEB path type', () => { mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockReturnValue(JSON.stringify({ workspaces: ['packages/*'] })); const result = pathResolver.resolvePath(PathType.PUBLIC_LIBS_UI_WEB); expect(result).toBeDefined(); expect(result).toContain('public-libs'); expect(result).toContain('ui'); expect(result).toContain('web'); }); it('should throw error for unknown path type', () => { expect(() => { pathResolver.resolvePath('UNKNOWN_PATH' as PathType); }).toThrow('Unknown path type: UNKNOWN_PATH'); }); }); describe('getCliRoot - development mode', () => { beforeEach(() => { mockEnvConfig.isDevelopment.mockReturnValue(true); mockEnvConfig.isProduction.mockReturnValue(false); pathResolver = new PathResolver(mockEnvConfig); }); it('should find project root with workspaces in development mode', () => { mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockReturnValue(JSON.stringify({ name: 'cb-marketplace-cli', workspaces: ['packages/*'] })); const result = pathResolver.getCliRoot(); expect(result).toBeDefined(); expect(typeof result).toBe('string'); expect(mockFs.existsSync).toHaveBeenCalled(); expect(mockFs.readFileSync).toHaveBeenCalled(); }); it('should throw error if project root with workspaces not found', () => { // Mock to simulate no workspace found mockFs.existsSync.mockReturnValue(false); expect(() => { pathResolver.getCliRoot(); }).toThrow('Could not find project root with workspaces'); }); it('should cache the CLI root path', () => { mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockReturnValue(JSON.stringify({ workspaces: ['packages/*'] })); const result1 = pathResolver.getCliRoot(); const result2 = pathResolver.getCliRoot(); expect(result1).toBe(result2); // existsSync should be called only once due to caching expect(mockFs.existsSync.mock.calls.length).toBeGreaterThan(0); }); it('should skip invalid package.json and continue searching', () => { let callCount = 0; mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockImplementation(() => { callCount++; if (callCount === 1) { return 'invalid json'; // First call returns invalid JSON } return JSON.stringify({ workspaces: ['packages/*'] }); // Second call returns valid JSON }); const result = pathResolver.getCliRoot(); expect(result).toBeDefined(); }); }); describe('getCliRoot - production mode', () => { beforeEach(() => { mockEnvConfig.isDevelopment.mockReturnValue(false); mockEnvConfig.isProduction.mockReturnValue(true); pathResolver = new PathResolver(mockEnvConfig); }); it('should use package installation directory in production mode', () => { const result = pathResolver.getCliRoot(); expect(result).toBeDefined(); expect(typeof result).toBe('string'); // In production, it should not call existsSync for workspace detection expect(mockFs.existsSync).not.toHaveBeenCalled(); }); }); describe('getPrivateAppDistDir', () => { it('should return workspace path in development mode', () => { mockEnvConfig.isDevelopment.mockReturnValue(true); mockEnvConfig.isProduction.mockReturnValue(false); mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockReturnValue(JSON.stringify({ workspaces: ['packages/*'] })); pathResolver = new PathResolver(mockEnvConfig); const result = pathResolver.getPrivateAppDistDir(); expect(result).toContain('packages'); expect(result).toContain('private-application'); expect(result).toContain('dist'); }); it('should use require.resolve in production mode', () => { mockEnvConfig.isDevelopment.mockReturnValue(false); mockEnvConfig.isProduction.mockReturnValue(true); pathResolver = new PathResolver(mockEnvConfig); // Mock require.resolve to throw error (package not found) expect(() => { pathResolver.getPrivateAppDistDir(); }).toThrow('Failed to resolve private-application path'); }); }); describe('getPrivateAppRootDir', () => { it('should return workspace path in development mode', () => { mockEnvConfig.isDevelopment.mockReturnValue(true); mockEnvConfig.isProduction.mockReturnValue(false); mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockReturnValue(JSON.stringify({ workspaces: ['packages/*'] })); pathResolver = new PathResolver(mockEnvConfig); const result = pathResolver.getPrivateAppRootDir(); expect(result).toContain('packages'); expect(result).toContain('private-application'); expect(result).not.toContain('dist'); }); it('should resolve package path in production mode', () => { mockEnvConfig.isDevelopment.mockReturnValue(false); mockEnvConfig.isProduction.mockReturnValue(true); pathResolver = new PathResolver(mockEnvConfig); // In monorepo environment, require.resolve will find the package // Just verify the method returns a path const result = pathResolver.getPrivateAppRootDir(); expect(result).toBeDefined(); expect(typeof result).toBe('string'); }); }); describe('getPublicLibsTemplatesDir', () => { it('should return workspace path in development mode', () => { mockEnvConfig.isDevelopment.mockReturnValue(true); mockEnvConfig.isProduction.mockReturnValue(false); mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockReturnValue(JSON.stringify({ workspaces: ['packages/*'] })); pathResolver = new PathResolver(mockEnvConfig); const result = pathResolver.getPublicLibsTemplatesDir(); expect(result).toContain('packages'); expect(result).toContain('public-libs'); expect(result).toContain('templates'); }); it('should resolve package path in production mode', () => { mockEnvConfig.isDevelopment.mockReturnValue(false); mockEnvConfig.isProduction.mockReturnValue(true); pathResolver = new PathResolver(mockEnvConfig); // In monorepo environment, require.resolve will find the package // Just verify the method returns a path containing templates const result = pathResolver.getPublicLibsTemplatesDir(); expect(result).toBeDefined(); expect(typeof result).toBe('string'); expect(result).toContain('templates'); }); }); describe('getPublicLibsUiWebDir', () => { it('should return workspace path in development mode', () => { mockEnvConfig.isDevelopment.mockReturnValue(true); mockEnvConfig.isProduction.mockReturnValue(false); mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockReturnValue(JSON.stringify({ workspaces: ['packages/*'] })); pathResolver = new PathResolver(mockEnvConfig); const result = pathResolver.getPublicLibsUiWebDir(); expect(result).toContain('packages'); expect(result).toContain('public-libs'); expect(result).toContain('ui'); expect(result).toContain('web'); }); it('should resolve package path in production mode', () => { mockEnvConfig.isDevelopment.mockReturnValue(false); mockEnvConfig.isProduction.mockReturnValue(true); pathResolver = new PathResolver(mockEnvConfig); // In monorepo environment, require.resolve will find the package // Just verify the method returns a path containing ui/web const result = pathResolver.getPublicLibsUiWebDir(); expect(result).toBeDefined(); expect(typeof result).toBe('string'); expect(result).toContain('web'); }); }); describe('findProjectRootWithWorkspaces', () => { beforeEach(() => { mockEnvConfig.isDevelopment.mockReturnValue(true); mockEnvConfig.isProduction.mockReturnValue(false); pathResolver = new PathResolver(mockEnvConfig); }); it('should find root when package.json has workspaces', () => { mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockReturnValue(JSON.stringify({ name: 'project', workspaces: ['packages/*'] })); const result = pathResolver.getCliRoot(); expect(result).toBeDefined(); }); it('should continue searching when package.json has no workspaces', () => { let callCount = 0; mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockImplementation(() => { callCount++; if (callCount === 1) { // First call - no workspaces return JSON.stringify({ name: 'sub-package' }); } // Second call - has workspaces return JSON.stringify({ name: 'root', workspaces: ['packages/*'] }); }); const result = pathResolver.getCliRoot(); expect(result).toBeDefined(); expect(mockFs.readFileSync).toHaveBeenCalled(); }); it('should throw error when reaching filesystem root without finding workspaces', () => { // Mock to simulate reaching root without finding workspaces mockFs.existsSync.mockReturnValue(false); expect(() => { pathResolver.getCliRoot(); }).toThrow('Could not find project root with workspaces'); }); it('should handle file read errors gracefully', () => { mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockImplementation(() => { throw new Error('Permission denied'); }); expect(() => { pathResolver.getCliRoot(); }).toThrow('Could not find project root with workspaces'); }); }); describe('edge cases', () => { it('should handle multiple calls to different path getters', () => { mockEnvConfig.isDevelopment.mockReturnValue(true); mockEnvConfig.isProduction.mockReturnValue(false); mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockReturnValue(JSON.stringify({ workspaces: ['packages/*'] })); pathResolver = new PathResolver(mockEnvConfig); const cliRoot = pathResolver.getCliRoot(); const privateAppDist = pathResolver.getPrivateAppDistDir(); const privateAppRoot = pathResolver.getPrivateAppRootDir(); const templatesDir = pathResolver.getPublicLibsTemplatesDir(); const uiWebDir = pathResolver.getPublicLibsUiWebDir(); expect(cliRoot).toBeDefined(); expect(privateAppDist).toBeDefined(); expect(privateAppRoot).toBeDefined(); expect(templatesDir).toBeDefined(); expect(uiWebDir).toBeDefined(); }); it('should handle path resolution with different path separators', () => { mockEnvConfig.isDevelopment.mockReturnValue(true); mockEnvConfig.isProduction.mockReturnValue(false); mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockReturnValue(JSON.stringify({ workspaces: ['packages/*'] })); pathResolver = new PathResolver(mockEnvConfig); const result = pathResolver.getPrivateAppDistDir(); // Result should use the correct path separator for the OS expect(result).toBeDefined(); expect(result.includes(path.sep)).toBe(true); }); it('should work with custom environment config implementation', () => { const customEnvConfig: IEnvironmentConfig = { getEnvironment: () => EnvironmentType.DEVELOPMENT, isDevelopment: () => true, isProduction: () => false }; mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockReturnValue(JSON.stringify({ workspaces: ['packages/*'] })); pathResolver = new PathResolver(customEnvConfig); const result = pathResolver.getCliRoot(); expect(result).toBeDefined(); }); }); describe('PathType enum', () => { it('should have all expected path types', () => { expect(PathType.CLI_ROOT).toBe('CLI_ROOT'); expect(PathType.PRIVATE_APP_DIST).toBe('PRIVATE_APP_DIST'); expect(PathType.PRIVATE_APP_ROOT).toBe('PRIVATE_APP_ROOT'); expect(PathType.PUBLIC_LIBS_TEMPLATES).toBe('PUBLIC_LIBS_TEMPLATES'); expect(PathType.PUBLIC_LIBS_UI_WEB).toBe('PUBLIC_LIBS_UI_WEB'); }); }); });