Spaces:
Sleeping
Sleeping
| import { describe, it, expect, vi } from "vitest"; | |
| import { safeJsRun } from "./safe-js-run"; | |
| describe("safe-js-run", () => { | |
| it("should execute basic code with console.log", async () => { | |
| const result = await safeJsRun({ code: "console.log(2 + 3);" }); | |
| expect(result.success).toBe(true); | |
| expect(result.logs).toHaveLength(1); | |
| expect(result.logs[0]).toEqual({ | |
| type: "log", | |
| args: [{ type: "data", value: 5 }], | |
| }); | |
| }); | |
| it("should work with Math API", async () => { | |
| const result = await safeJsRun({ code: "console.log(Math.max(1, 2, 3));" }); | |
| expect(result.success).toBe(true); | |
| expect(result.logs).toHaveLength(1); | |
| expect(result.logs[0]).toEqual({ | |
| type: "log", | |
| args: [{ type: "data", value: 3 }], | |
| }); | |
| }); | |
| it("should work with JSON API", async () => { | |
| const result = await safeJsRun({ | |
| code: 'const obj = {name: "test"}; console.log(JSON.stringify(obj));', | |
| }); | |
| expect(result.success).toBe(true); | |
| expect(result.logs).toHaveLength(1); | |
| expect(result.logs[0]).toEqual({ | |
| type: "log", | |
| args: [{ type: "data", value: '{"name":"test"}' }], | |
| }); | |
| }); | |
| it("should capture multiple console methods", async () => { | |
| const result = await safeJsRun({ | |
| code: 'console.log("hello"); console.warn("warning"); console.error("error");', | |
| }); | |
| expect(result.success).toBe(true); | |
| expect(result.logs).toHaveLength(3); | |
| expect(result.logs[0]).toEqual({ | |
| type: "log", | |
| args: [{ type: "data", value: "hello" }], | |
| }); | |
| expect(result.logs[1]).toEqual({ | |
| type: "warn", | |
| args: [{ type: "data", value: "warning" }], | |
| }); | |
| expect(result.logs[2]).toEqual({ | |
| type: "error", | |
| args: [{ type: "data", value: "error" }], | |
| }); | |
| }); | |
| it("should capture console.log with multiple arguments", async () => { | |
| const result = await safeJsRun({ | |
| code: 'console.log("hello", "world", 42);', | |
| }); | |
| expect(result.success).toBe(true); | |
| expect(result.logs).toHaveLength(1); | |
| expect(result.logs[0]).toEqual({ | |
| type: "log", | |
| args: [ | |
| { type: "data", value: "hello" }, | |
| { type: "data", value: "world" }, | |
| { type: "data", value: 42 }, | |
| ], | |
| }); | |
| }); | |
| it("should call onLog callback when provided", async () => { | |
| const onLogSpy = vi.fn(); | |
| const result = await safeJsRun({ | |
| code: 'console.log("test"); console.warn("warning");', | |
| timeout: 5000, | |
| onLog: onLogSpy, | |
| }); | |
| expect(result.success).toBe(true); | |
| expect(onLogSpy).toHaveBeenCalledTimes(2); | |
| expect(onLogSpy).toHaveBeenCalledWith({ | |
| type: "log", | |
| args: [{ type: "data", value: "test" }], | |
| }); | |
| expect(onLogSpy).toHaveBeenCalledWith({ | |
| type: "warn", | |
| args: [{ type: "data", value: "warning" }], | |
| }); | |
| }); | |
| it("should handle syntax errors", async () => { | |
| const result = await safeJsRun({ code: "console.log(2 +);" }); | |
| expect(result.success).toBe(false); | |
| expect(result.error).toContain("Unexpected token"); | |
| }); | |
| it("should block forbidden keywords", async () => { | |
| const result = await safeJsRun({ code: "window.alert('hack');" }); | |
| expect(result.success).toBe(false); | |
| expect(result.error).toContain("Forbidden keyword: 'window'"); | |
| }); | |
| it("should detect Function constructor", async () => { | |
| const result = await safeJsRun({ code: "new Function('return 1')();" }); | |
| expect(result.success).toBe(false); | |
| expect(result.error).toContain("Function constructor"); | |
| }); | |
| it("should detect infinite loop patterns", async () => { | |
| const result = await safeJsRun({ code: "while(true) {}" }); | |
| expect(result.success).toBe(false); | |
| expect(result.error).toContain("Infinite while loop"); | |
| }); | |
| it("should handle code with no console output", async () => { | |
| const result = await safeJsRun({ code: "const x = 5; const y = 10;" }); | |
| expect(result.success).toBe(true); | |
| expect(result.logs).toHaveLength(0); | |
| }); | |
| it("should handle different console methods correctly", async () => { | |
| const result = await safeJsRun({ | |
| code: 'console.info("info"); console.debug("debug"); console.trace("trace");', | |
| }); | |
| expect(result.success).toBe(true); | |
| expect(result.logs).toHaveLength(3); | |
| expect(result.logs[0]).toEqual({ | |
| type: "info", | |
| args: [{ type: "data", value: "info" }], | |
| }); | |
| expect(result.logs[1]).toEqual({ | |
| type: "debug", | |
| args: [{ type: "data", value: "debug" }], | |
| }); | |
| expect(result.logs[2]).toEqual({ | |
| type: "trace", | |
| args: [{ type: "data", value: "trace" }], | |
| }); | |
| }); | |
| it("should handle async/await operations", async () => { | |
| const result = await safeJsRun({ | |
| code: ` | |
| const delay = (ms) => new Promise(resolve => resolve("completed")); | |
| const result = await delay(10); | |
| console.log("async operation", result); | |
| `, | |
| }); | |
| expect(result.success).toBe(true); | |
| expect(result.logs).toHaveLength(1); | |
| expect(result.logs[0]).toEqual({ | |
| type: "log", | |
| args: [ | |
| { type: "data", value: "async operation" }, | |
| { type: "data", value: "completed" }, | |
| ], | |
| }); | |
| }); | |
| it("should handle Promise-based operations", async () => { | |
| const result = await safeJsRun({ | |
| code: ` | |
| const result = await Promise.resolve(42); | |
| console.log("Promise result:", result); | |
| `, | |
| }); | |
| expect(result.success).toBe(true); | |
| expect(result.logs).toHaveLength(1); | |
| expect(result.logs[0]).toEqual({ | |
| type: "log", | |
| args: [ | |
| { type: "data", value: "Promise result:" }, | |
| { type: "data", value: 42 }, | |
| ], | |
| }); | |
| }); | |
| it("should handle async errors properly", async () => { | |
| const result = await safeJsRun({ | |
| code: ` | |
| try { | |
| await Promise.reject(new Error("async error")); | |
| } catch (error) { | |
| console.error("Caught async error:", error.message); | |
| } | |
| `, | |
| }); | |
| expect(result.success).toBe(true); | |
| expect(result.logs).toHaveLength(1); | |
| expect(result.logs[0]).toEqual({ | |
| type: "error", | |
| args: [ | |
| { type: "data", value: "Caught async error:" }, | |
| { type: "data", value: "async error" }, | |
| ], | |
| }); | |
| }); | |
| it("should handle sequential async operations with delay", async () => { | |
| const result = await safeJsRun({ | |
| code: ` | |
| async function delay(ms) { | |
| return new Promise(resolve => setTimeout(resolve, ms)); | |
| } | |
| function generateEmployee(id) { | |
| const names = ['Kim Minjun', 'Lee Jisoo', 'Park Hyunwoo']; | |
| const departments = ['Engineering', 'Design', 'Marketing']; | |
| return { | |
| id, | |
| name: names[id - 1], | |
| department: departments[id - 1], | |
| createdAt: Date.now() | |
| }; | |
| } | |
| async function processEmployees() { | |
| const employees = []; | |
| console.log("Starting employee processing..."); | |
| for (let i = 1; i <= 3; i++) { | |
| console.log(\`[\${i}] Creating employee data...\`); | |
| const employee = generateEmployee(i); | |
| console.log(\`[\${i}] Saving to database...\`); | |
| await delay(50); // 50ms database operation simulation | |
| console.log(\`[\${i}] Employee \${employee.name} saved successfully\`); | |
| employees.push(employee); | |
| } | |
| console.log(\`All \${employees.length} employees processed\`); | |
| return employees; | |
| } | |
| await processEmployees(); | |
| `, | |
| timeout: 2000, | |
| }); | |
| expect(result.success).toBe(true); | |
| expect(result.logs.length).toBeGreaterThanOrEqual(10); | |
| expect(result.logs[0].args[0].value).toBe( | |
| "Starting employee processing...", | |
| ); | |
| expect(result.logs[result.logs.length - 1].args[0].value).toBe( | |
| "All 3 employees processed", | |
| ); | |
| // Check that delays actually happened (execution time should be > 150ms) | |
| expect(result.executionTimeMs).toBeGreaterThan(140); // 50ms × 3 + margin | |
| }); | |
| it("should demonstrate timeout behavior (test environment limitation)", async () => { | |
| const result = await safeJsRun({ | |
| code: ` | |
| console.log("Starting operation..."); | |
| // In real browser environment, this would trigger timeout | |
| // But in test environment, timeout may not work as expected | |
| await new Promise(() => {}); | |
| console.log("This should not appear"); | |
| `, | |
| timeout: 100, | |
| }); | |
| if (!result.success) { | |
| expect(result.error).toContain("timeout"); | |
| } else { | |
| expect(result.success).toBe(true); | |
| expect(result.logs[0].args[0].value).toBe("Starting operation..."); | |
| } | |
| }); | |
| it("should return value from code", async () => { | |
| const result = await safeJsRun({ code: "return 42;" }); | |
| expect(result.success).toBe(true); | |
| expect(result.result).toBe(42); | |
| }); | |
| it("should return value from if/else", async () => { | |
| const result = await safeJsRun({ | |
| code: "if (1 > 0) return 'ok'; else return 'no';", | |
| }); | |
| expect(result.success).toBe(true); | |
| expect(result.result).toBe("ok"); | |
| }); | |
| it("should have undefined result if no return", async () => { | |
| const result = await safeJsRun({ code: "console.log('hi');" }); | |
| expect(result.success).toBe(true); | |
| expect(result.result).toBeUndefined(); | |
| }); | |
| it("should return value from async function", async () => { | |
| const result = await safeJsRun({ | |
| code: ` | |
| async function foo() { | |
| await Promise.resolve(); | |
| return 123; | |
| } | |
| return await foo(); | |
| `, | |
| }); | |
| expect(result.success).toBe(true); | |
| expect(result.result).toBe(123); | |
| }); | |
| }); | |