|
6 | 6 |
|
7 | 7 | import { describe, it, vi, expect } from 'vitest'; |
8 | 8 | import { runPoc } from './poc.js'; |
| 9 | +import { POC_DIR } from './constants.js'; |
9 | 10 |
|
10 | 11 | describe('runPoc', () => { |
| 12 | + const mockPath = { |
| 13 | + dirname: (p: string) => p.substring(0, p.lastIndexOf('/')), |
| 14 | + resolve: (p1: string, p2?: string) => { |
| 15 | + if (p2 && p2.startsWith('/')) return p2; |
| 16 | + if (p2) return p1 + '/' + p2; |
| 17 | + return p1; |
| 18 | + }, |
| 19 | + sep: '/', |
| 20 | + }; |
| 21 | + |
11 | 22 | it('should execute the file at the given path', async () => { |
12 | | - const mockPath = { |
13 | | - dirname: (p: string) => p.substring(0, p.lastIndexOf('/')), |
14 | | - }; |
15 | 23 | const mockExecAsync = vi.fn(async (cmd: string) => { |
16 | 24 | if (cmd.startsWith('npm install')) { |
17 | 25 | return { stdout: '', stderr: '' }; |
18 | 26 | } |
19 | 27 | return { stdout: 'output', stderr: '' }; |
20 | 28 | }); |
| 29 | + const mockExecFileAsync = vi.fn(async (file: string, args?: string[]) => { |
| 30 | + return { stdout: 'output', stderr: '' }; |
| 31 | + }); |
21 | 32 |
|
22 | 33 | const result = await runPoc( |
23 | | - { filePath: '/tmp/test.js' }, |
24 | | - { fs: {} as any, path: mockPath as any, execAsync: mockExecAsync as any } |
| 34 | + { filePath: `${POC_DIR}/test.js` }, |
| 35 | + { fs: {} as any, path: mockPath as any, execAsync: mockExecAsync as any, execFileAsync: mockExecFileAsync as any } |
25 | 36 | ); |
26 | 37 |
|
27 | | - expect(mockExecAsync).toHaveBeenCalledTimes(2); |
28 | | - expect(mockExecAsync).toHaveBeenNthCalledWith( |
29 | | - 1, |
| 38 | + expect(mockExecAsync).toHaveBeenCalledTimes(1); |
| 39 | + expect(mockExecAsync).toHaveBeenCalledWith( |
30 | 40 | 'npm install --registry=https://registry.npmjs.org/', |
31 | | - { cwd: '/tmp' } |
| 41 | + { cwd: POC_DIR } |
32 | 42 | ); |
33 | | - expect(mockExecAsync).toHaveBeenNthCalledWith(2, 'node /tmp/test.js'); |
| 43 | + expect(mockExecFileAsync).toHaveBeenCalledTimes(1); |
| 44 | + expect(mockExecFileAsync).toHaveBeenCalledWith('node', [`${POC_DIR}/test.js`]); |
34 | 45 | expect((result.content[0] as any).text).toBe( |
35 | 46 | JSON.stringify({ stdout: 'output', stderr: '' }) |
36 | 47 | ); |
37 | 48 | }); |
38 | 49 |
|
39 | 50 | it('should handle execution errors', async () => { |
40 | | - const mockPath = { |
41 | | - dirname: (p: string) => p.substring(0, p.lastIndexOf('/')), |
42 | | - }; |
43 | 51 | const mockExecAsync = vi.fn(async (cmd: string) => { |
44 | | - if (cmd.startsWith('node')) { |
45 | | - throw new Error('Execution failed'); |
46 | | - } |
47 | 52 | return { stdout: '', stderr: '' }; |
48 | 53 | }); |
| 54 | + const mockExecFileAsync = vi.fn(async (file: string, args?: string[]) => { |
| 55 | + throw new Error('Execution failed'); |
| 56 | + }); |
49 | 57 |
|
50 | 58 | const result = await runPoc( |
51 | | - { filePath: '/tmp/error.js' }, |
52 | | - { fs: {} as any, path: mockPath as any, execAsync: mockExecAsync as any } |
| 59 | + { filePath: `${POC_DIR}/error.js` }, |
| 60 | + { fs: {} as any, path: mockPath as any, execAsync: mockExecAsync as any, execFileAsync: mockExecFileAsync as any } |
53 | 61 | ); |
54 | 62 |
|
55 | 63 | expect(result.isError).toBe(true); |
56 | 64 | expect((result.content[0] as any).text).toBe( |
57 | 65 | JSON.stringify({ error: 'Execution failed' }) |
58 | 66 | ); |
59 | 67 | }); |
| 68 | + |
| 69 | + it('should fail when accessing file outside of allowed directory', async () => { |
| 70 | + const mockExecAsync = vi.fn(); |
| 71 | + const mockExecFileAsync = vi.fn(); |
| 72 | + |
| 73 | + const result = await runPoc( |
| 74 | + { filePath: '/tmp/malicious.js' }, |
| 75 | + { fs: {} as any, path: mockPath as any, execAsync: mockExecAsync as any, execFileAsync: mockExecFileAsync as any } |
| 76 | + ); |
| 77 | + |
| 78 | + expect(result.isError).toBe(true); |
| 79 | + expect((result.content[0] as any).text).toContain('Security Error: PoC execution is restricted'); |
| 80 | + expect(mockExecAsync).not.toHaveBeenCalled(); |
| 81 | + expect(mockExecFileAsync).not.toHaveBeenCalled(); |
| 82 | + }); |
60 | 83 | }); |
0 commit comments