UNPKG

apisurf

Version:

Analyze API surface changes between npm package versions to catch breaking changes

181 lines (180 loc) 8.5 kB
import { describe, expect, it, jest, beforeEach } from '@jest/globals'; // Create mocks const mockFs = { readFileSync: jest.fn() }; const mockVm = { createContext: jest.fn(), runInContext: jest.fn() }; // Mock dependencies jest.unstable_mockModule('fs', () => mockFs); jest.unstable_mockModule('vm', () => mockVm); // Import after mocking const { loadModuleWithVM } = await import('./loadModuleWithVM.js'); describe('loadModuleWithVM', () => { beforeEach(() => { jest.clearAllMocks(); // Mock console.warn to suppress warning messages during tests jest.spyOn(console, 'warn').mockImplementation(() => { }); }); it('should successfully load a simple CommonJS module', () => { const modulePath = '/test/module.js'; const moduleSource = ` exports.simpleFunction = function() { return 'hello'; }; exports.simpleValue = 42; `; mockFs.readFileSync.mockReturnValue(moduleSource); const mockContext = { module: { exports: {} }, exports: {}, require: jest.fn() }; mockVm.createContext.mockReturnValue(mockContext); // Simulate the module execution mockVm.runInContext.mockImplementation((_code, context) => { // Simulate the effect of running the CommonJS module context.exports.simpleFunction = function () { return 'hello'; }; context.exports.simpleValue = 42; context.module.exports = context.exports; return context.module.exports; }); const result = loadModuleWithVM(modulePath); expect(mockFs.readFileSync).toHaveBeenCalledWith(modulePath, 'utf8'); expect(mockVm.createContext).toHaveBeenCalled(); expect(mockVm.runInContext).toHaveBeenCalledWith(moduleSource, mockContext, { filename: modulePath, timeout: 10000 }); expect(result).toBeDefined(); }); it('should handle module.exports assignment', () => { const modulePath = '/test/default-export.js'; const moduleSource = 'function MyClass() {} module.exports = MyClass;'; mockFs.readFileSync.mockReturnValue(moduleSource); mockVm.createContext.mockReturnValue({}); mockVm.runInContext.mockImplementation(() => { // Simulate successful execution }); const result = loadModuleWithVM(modulePath); // Just verify it returns something (not null) and basic calls were made expect(result).toBeDefined(); expect(mockFs.readFileSync).toHaveBeenCalledWith(modulePath, 'utf8'); expect(mockVm.createContext).toHaveBeenCalled(); expect(mockVm.runInContext).toHaveBeenCalled(); }); it('should return null when VM execution fails', () => { const modulePath = '/test/failing-module.js'; const moduleSource = 'throw new Error("Module failed to load");'; mockFs.readFileSync.mockReturnValue(moduleSource); mockVm.createContext.mockReturnValue({}); mockVm.runInContext.mockImplementation(() => { throw new Error('Execution failed'); }); const result = loadModuleWithVM(modulePath); expect(result).toBeNull(); expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('VM execution failed while parsing unknown package'), expect.any(Error)); }); it('should return null when file reading fails', () => { const modulePath = '/test/non-existent.js'; mockFs.readFileSync.mockImplementation(() => { throw new Error('File not found'); }); const result = loadModuleWithVM(modulePath); expect(result).toBeNull(); expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('VM execution failed while parsing unknown package'), expect.any(Error)); }); it('should include package info in error messages when provided', () => { const modulePath = '/test/module.js'; mockFs.readFileSync.mockImplementation(() => { throw new Error('Read failed'); }); const result = loadModuleWithVM(modulePath, 'test-package', '1.0.0'); expect(result).toBeNull(); expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('VM execution failed while parsing test-package@1.0.0'), expect.any(Error)); }); it('should set up proper sandbox environment', () => { const modulePath = '/test/module.js'; const moduleSource = 'exports.test = true;'; mockFs.readFileSync.mockReturnValue(moduleSource); let capturedSandbox; mockVm.createContext.mockImplementation((sandbox) => { capturedSandbox = sandbox; return sandbox; }); mockVm.runInContext.mockImplementation(() => { }); loadModuleWithVM(modulePath); // Verify sandbox has all required properties expect(capturedSandbox).toBeDefined(); expect(capturedSandbox.require).toBeInstanceOf(Function); expect(capturedSandbox.module).toHaveProperty('exports'); expect(capturedSandbox.exports).toBeDefined(); expect(capturedSandbox.__filename).toBe(modulePath); expect(capturedSandbox.__dirname).toBe('/test'); expect(capturedSandbox.process).toHaveProperty('env'); expect(capturedSandbox.console).toHaveProperty('log'); expect(capturedSandbox.Buffer).toBe(Buffer); expect(capturedSandbox.Object).toBe(Object); expect(capturedSandbox.global).toBe(capturedSandbox); expect(capturedSandbox.globalThis).toBe(capturedSandbox); }); it('should handle module requires correctly', () => { const modulePath = '/test/module.js'; const moduleSource = 'const dep = require("dependency");'; mockFs.readFileSync.mockReturnValue(moduleSource); let capturedRequire; mockVm.createContext.mockImplementation((sandbox) => { capturedRequire = sandbox.require; return sandbox; }); mockVm.runInContext.mockImplementation(() => { }); loadModuleWithVM(modulePath); // Verify the require function was set up in the sandbox expect(capturedRequire).toBeInstanceOf(Function); // Test that require is called for relative paths // Since we can't mock the global require, we'll just test the function exists expect(() => capturedRequire('./relative')).not.toThrow(); }); it('should return Object.assign for object-assign package', () => { const modulePath = '/test/react-module.js'; const moduleSource = 'const _assign = require("object-assign");'; mockFs.readFileSync.mockReturnValue(moduleSource); let capturedRequire; mockVm.createContext.mockImplementation((sandbox) => { capturedRequire = sandbox.require; return sandbox; }); mockVm.runInContext.mockImplementation(() => { }); loadModuleWithVM(modulePath); // Test that requiring 'object-assign' returns Object.assign const result = capturedRequire('object-assign'); expect(result).toBe(Object.assign); }); it('should return empty object when require fails', () => { const modulePath = '/test/module.js'; const moduleSource = 'const dep = require("missing");'; mockFs.readFileSync.mockReturnValue(moduleSource); let capturedRequire; mockVm.createContext.mockImplementation((sandbox) => { capturedRequire = sandbox.require; return sandbox; }); mockVm.runInContext.mockImplementation(() => { }); loadModuleWithVM(modulePath); // The require function in sandbox should return empty object for missing modules // This is handled by the catch block in the sandbox's require implementation const result = capturedRequire('missing-module-that-does-not-exist'); expect(result).toEqual({}); }); it('should handle timeout correctly', () => { const modulePath = '/test/infinite-loop.js'; const moduleSource = 'while(true) {}'; mockFs.readFileSync.mockReturnValue(moduleSource); mockVm.createContext.mockReturnValue({}); // Check that timeout is set to 10000ms mockVm.runInContext.mockImplementation((_code, _context, options) => { expect(options?.timeout).toBe(10000); }); loadModuleWithVM(modulePath); }); });