|
| 1 | +/* eslint-disable @typescript-eslint/no-var-requires */ |
| 2 | + |
| 3 | +const originalPlatform = process.platform; |
| 4 | + |
| 5 | +function mockPlatform(platform: string) { |
| 6 | + Object.defineProperty(process, 'platform', { value: platform, writable: true }); |
| 7 | +} |
| 8 | + |
| 9 | +afterEach(() => { |
| 10 | + Object.defineProperty(process, 'platform', { value: originalPlatform, writable: true }); |
| 11 | + jest.restoreAllMocks(); |
| 12 | + jest.resetModules(); |
| 13 | +}); |
| 14 | + |
| 15 | +// Mock electron |
| 16 | +jest.mock('electron', () => ({ |
| 17 | + app: { isPackaged: false, getPath: jest.fn().mockReturnValue('/tmp') }, |
| 18 | +})); |
| 19 | + |
| 20 | +// Mock AppPaths |
| 21 | +jest.mock('./AppPaths', () => ({ |
| 22 | + getBundledNodeRoot: jest.fn().mockReturnValue(null), |
| 23 | + getCopilotBootstrapDir: jest.fn().mockReturnValue('/tmp/copilot-bootstrap'), |
| 24 | + getCopilotLocalNodeModulesDir: jest.fn().mockReturnValue('/tmp/copilot-bootstrap/node_modules'), |
| 25 | + getCopilotLogsDir: jest.fn().mockReturnValue('/tmp/copilot-logs'), |
| 26 | +})); |
| 27 | + |
| 28 | +// Mock CopilotBootstrap |
| 29 | +jest.mock('./CopilotBootstrap', () => ({ |
| 30 | + ensureCopilotInstalled: jest.fn().mockResolvedValue(undefined), |
| 31 | + getLocalCopilotCliPath: jest.fn().mockReturnValue(null), |
| 32 | + getLocalCopilotNodeModulesDir: jest.fn().mockReturnValue('/tmp/copilot-bootstrap/node_modules'), |
| 33 | + isLocalCopilotInstallReady: jest.fn().mockReturnValue(false), |
| 34 | +})); |
| 35 | + |
| 36 | +// Mock fs and os at module level so properties are configurable |
| 37 | +jest.mock('fs'); |
| 38 | +jest.mock('os'); |
| 39 | + |
| 40 | +// Mock child_process to prevent real execSync calls |
| 41 | +jest.mock('child_process', () => ({ |
| 42 | + execSync: jest.fn().mockImplementation(() => { throw new Error('npm not found'); }), |
| 43 | +})); |
| 44 | + |
| 45 | +describe('SdkLoader platform-specific paths', () => { |
| 46 | + describe('Well-known prefixes on macOS', () => { |
| 47 | + test('probes /usr/local and /opt/homebrew on darwin', () => { |
| 48 | + mockPlatform('darwin'); |
| 49 | + const os = require('os'); |
| 50 | + const fs = require('fs'); |
| 51 | + os.homedir.mockReturnValue('/Users/testuser'); |
| 52 | + fs.existsSync.mockReturnValue(false); |
| 53 | + fs.readFileSync.mockImplementation(() => { throw new Error('not found'); }); |
| 54 | + |
| 55 | + jest.isolateModules(() => { |
| 56 | + const { loadSdk } = require('./SdkLoader'); |
| 57 | + expect(loadSdk()).rejects.toThrow(); |
| 58 | + }); |
| 59 | + }); |
| 60 | + }); |
| 61 | + |
| 62 | + describe('Well-known prefixes on Windows', () => { |
| 63 | + test('checks APPDATA/npm on Windows', () => { |
| 64 | + mockPlatform('win32'); |
| 65 | + const origAppData = process.env.APPDATA; |
| 66 | + process.env.APPDATA = 'C:\\Users\\test\\AppData\\Roaming'; |
| 67 | + const os = require('os'); |
| 68 | + const fs = require('fs'); |
| 69 | + os.homedir.mockReturnValue('C:\\Users\\test'); |
| 70 | + fs.existsSync.mockReturnValue(false); |
| 71 | + fs.readFileSync.mockImplementation(() => { throw new Error('not found'); }); |
| 72 | + |
| 73 | + jest.isolateModules(() => { |
| 74 | + const { loadSdk } = require('./SdkLoader'); |
| 75 | + expect(loadSdk()).rejects.toThrow(); |
| 76 | + }); |
| 77 | + |
| 78 | + process.env.APPDATA = origAppData; |
| 79 | + }); |
| 80 | + }); |
| 81 | + |
| 82 | + describe('SDK loading with global install', () => { |
| 83 | + test('finds SDK in global node_modules on Unix', () => { |
| 84 | + mockPlatform('linux'); |
| 85 | + const os = require('os'); |
| 86 | + const fs = require('fs'); |
| 87 | + os.homedir.mockReturnValue('/home/testuser'); |
| 88 | + |
| 89 | + fs.existsSync.mockImplementation((p: string) => { |
| 90 | + const s = p.toString(); |
| 91 | + if (s.includes('/usr/local/lib/node_modules/@github/copilot-sdk/package.json')) return true; |
| 92 | + if (s.includes('/usr/local/lib/node_modules/@github/copilot-sdk/dist/index.js')) return true; |
| 93 | + return false; |
| 94 | + }); |
| 95 | + fs.readFileSync.mockImplementation(() => { throw new Error('not found'); }); |
| 96 | + |
| 97 | + jest.isolateModules(() => { |
| 98 | + const { loadSdk } = require('./SdkLoader'); |
| 99 | + // loadSdk will try dynamic import which will fail in test env, |
| 100 | + // but it should get past the prefix resolution step |
| 101 | + expect(loadSdk()).rejects.toThrow(); |
| 102 | + }); |
| 103 | + }); |
| 104 | + |
| 105 | + test('finds SDK in APPDATA/npm/node_modules on Windows', () => { |
| 106 | + mockPlatform('win32'); |
| 107 | + const origAppData = process.env.APPDATA; |
| 108 | + process.env.APPDATA = 'C:\\Users\\test\\AppData\\Roaming'; |
| 109 | + const os = require('os'); |
| 110 | + const fs = require('fs'); |
| 111 | + os.homedir.mockReturnValue('C:\\Users\\test'); |
| 112 | + |
| 113 | + fs.existsSync.mockImplementation((p: string) => { |
| 114 | + const s = p.toString(); |
| 115 | + if (s.includes('npm\\node_modules\\@github\\copilot-sdk\\package.json')) return true; |
| 116 | + return false; |
| 117 | + }); |
| 118 | + fs.readFileSync.mockImplementation(() => { throw new Error('not found'); }); |
| 119 | + |
| 120 | + jest.isolateModules(() => { |
| 121 | + const { loadSdk } = require('./SdkLoader'); |
| 122 | + expect(loadSdk()).rejects.toThrow(); |
| 123 | + }); |
| 124 | + |
| 125 | + process.env.APPDATA = origAppData; |
| 126 | + }); |
| 127 | + }); |
| 128 | +}); |
0 commit comments