un-inception/tests/unit/test_javascript.js

355 lines
11 KiB
JavaScript

#!/usr/bin/env node
/**
* Unit tests for un.js - tests internal functions without API calls
*/
const crypto = require('crypto');
const path = require('path');
const fs = require('fs');
const os = require('os');
const assert = require('assert');
// Test counters
let passed = 0;
let failed = 0;
function test(name, fn) {
try {
fn();
console.log(`${name}`);
passed++;
} catch (e) {
console.log(`${name}`);
console.log(` ${e.message}`);
failed++;
}
}
function assertEqual(actual, expected, msg = '') {
if (actual !== expected) {
throw new Error(`Expected "${expected}" but got "${actual}" ${msg}`);
}
}
function assertIncludes(str, substr) {
if (!str.includes(substr)) {
throw new Error(`Expected "${str}" to include "${substr}"`);
}
}
function assertNotEqual(a, b) {
if (a === b) {
throw new Error(`Expected values to be different but both were "${a}"`);
}
}
// ============================================================================
// Extension Mapping Tests
// ============================================================================
console.log('\n=== Extension Mapping Tests ===');
const EXT_MAP = {
".py": "python", ".js": "javascript", ".ts": "typescript",
".rb": "ruby", ".php": "php", ".pl": "perl", ".lua": "lua",
".sh": "bash", ".go": "go", ".rs": "rust", ".c": "c",
".cpp": "cpp", ".cc": "cpp", ".cxx": "cpp",
".java": "java", ".kt": "kotlin", ".cs": "csharp", ".fs": "fsharp",
".hs": "haskell", ".ml": "ocaml", ".clj": "clojure", ".scm": "scheme",
".lisp": "commonlisp", ".erl": "erlang", ".ex": "elixir", ".exs": "elixir",
".jl": "julia", ".r": "r", ".R": "r", ".cr": "crystal",
".d": "d", ".nim": "nim", ".zig": "zig", ".v": "v",
".dart": "dart", ".groovy": "groovy", ".scala": "scala",
".f90": "fortran", ".f95": "fortran", ".cob": "cobol",
".pro": "prolog", ".forth": "forth", ".4th": "forth",
".tcl": "tcl", ".raku": "raku", ".m": "objc",
};
test('Python extension maps correctly', () => {
assertEqual(EXT_MAP['.py'], 'python');
});
test('JavaScript extensions map correctly', () => {
assertEqual(EXT_MAP['.js'], 'javascript');
assertEqual(EXT_MAP['.ts'], 'typescript');
});
test('Ruby extension maps correctly', () => {
assertEqual(EXT_MAP['.rb'], 'ruby');
});
test('Go extension maps correctly', () => {
assertEqual(EXT_MAP['.go'], 'go');
});
test('Rust extension maps correctly', () => {
assertEqual(EXT_MAP['.rs'], 'rust');
});
test('C/C++ extensions map correctly', () => {
assertEqual(EXT_MAP['.c'], 'c');
assertEqual(EXT_MAP['.cpp'], 'cpp');
assertEqual(EXT_MAP['.cc'], 'cpp');
assertEqual(EXT_MAP['.cxx'], 'cpp');
});
test('JVM extensions map correctly', () => {
assertEqual(EXT_MAP['.java'], 'java');
assertEqual(EXT_MAP['.kt'], 'kotlin');
assertEqual(EXT_MAP['.groovy'], 'groovy');
assertEqual(EXT_MAP['.scala'], 'scala');
});
test('.NET extensions map correctly', () => {
assertEqual(EXT_MAP['.cs'], 'csharp');
assertEqual(EXT_MAP['.fs'], 'fsharp');
});
test('Functional language extensions map correctly', () => {
assertEqual(EXT_MAP['.hs'], 'haskell');
assertEqual(EXT_MAP['.ml'], 'ocaml');
assertEqual(EXT_MAP['.clj'], 'clojure');
assertEqual(EXT_MAP['.scm'], 'scheme');
assertEqual(EXT_MAP['.lisp'], 'commonlisp');
assertEqual(EXT_MAP['.erl'], 'erlang');
assertEqual(EXT_MAP['.ex'], 'elixir');
assertEqual(EXT_MAP['.exs'], 'elixir');
});
test('Scientific language extensions map correctly', () => {
assertEqual(EXT_MAP['.jl'], 'julia');
assertEqual(EXT_MAP['.r'], 'r');
assertEqual(EXT_MAP['.R'], 'r');
assertEqual(EXT_MAP['.f90'], 'fortran');
assertEqual(EXT_MAP['.f95'], 'fortran');
});
test('Systems language extensions map correctly', () => {
assertEqual(EXT_MAP['.d'], 'd');
assertEqual(EXT_MAP['.nim'], 'nim');
assertEqual(EXT_MAP['.zig'], 'zig');
assertEqual(EXT_MAP['.v'], 'v');
assertEqual(EXT_MAP['.cr'], 'crystal');
assertEqual(EXT_MAP['.dart'], 'dart');
});
test('Legacy/exotic extensions map correctly', () => {
assertEqual(EXT_MAP['.cob'], 'cobol');
assertEqual(EXT_MAP['.pro'], 'prolog');
assertEqual(EXT_MAP['.forth'], 'forth');
assertEqual(EXT_MAP['.4th'], 'forth');
assertEqual(EXT_MAP['.tcl'], 'tcl');
assertEqual(EXT_MAP['.raku'], 'raku');
assertEqual(EXT_MAP['.m'], 'objc');
});
// ============================================================================
// HMAC Signature Tests
// ============================================================================
console.log('\n=== HMAC Signature Tests ===');
test('HMAC-SHA256 generates 64 character hex string', () => {
const secret = 'test-secret-key';
const message = '1234567890:POST:/execute:{}';
const signature = crypto.createHmac('sha256', secret)
.update(message)
.digest('hex');
assertEqual(signature.length, 64);
});
test('Same input produces same signature', () => {
const secret = 'test-secret-key';
const message = '1234567890:POST:/execute:{}';
const sig1 = crypto.createHmac('sha256', secret).update(message).digest('hex');
const sig2 = crypto.createHmac('sha256', secret).update(message).digest('hex');
assertEqual(sig1, sig2);
});
test('Different secrets produce different signatures', () => {
const message = '1234567890:POST:/execute:{}';
const sig1 = crypto.createHmac('sha256', 'secret1').update(message).digest('hex');
const sig2 = crypto.createHmac('sha256', 'secret2').update(message).digest('hex');
assertNotEqual(sig1, sig2);
});
test('Different messages produce different signatures', () => {
const secret = 'test-secret';
const sig1 = crypto.createHmac('sha256', secret).update('message1').digest('hex');
const sig2 = crypto.createHmac('sha256', secret).update('message2').digest('hex');
assertNotEqual(sig1, sig2);
});
test('Signature format is timestamp:METHOD:path:body', () => {
const timestamp = '1704067200';
const method = 'POST';
const endpoint = '/execute';
const body = '{"language":"python","code":"print(1)"}';
const message = `${timestamp}:${method}:${endpoint}:${body}`;
// Verify format: starts with timestamp, has method and path
assertEqual(message.startsWith(timestamp), true);
assertIncludes(message, ':POST:');
assertIncludes(message, ':/execute:');
});
// ============================================================================
// Language Detection Tests
// ============================================================================
console.log('\n=== Language Detection Tests ===');
test('Detect language from .py extension', () => {
const filename = 'script.py';
const ext = path.extname(filename).toLowerCase();
assertEqual(EXT_MAP[ext], 'python');
});
test('Detect language from .js extension', () => {
const filename = 'app.js';
const ext = path.extname(filename).toLowerCase();
assertEqual(EXT_MAP[ext], 'javascript');
});
test('Python shebang detection', () => {
const content = '#!/usr/bin/env python3\nprint("hello")';
const firstLine = content.split('\n')[0];
assertEqual(firstLine.startsWith('#!'), true);
assertEqual(firstLine.includes('python'), true);
});
test('Node shebang detection', () => {
const content = '#!/usr/bin/env node\nconsole.log("hello")';
const firstLine = content.split('\n')[0];
assertEqual(firstLine.startsWith('#!'), true);
assertEqual(firstLine.includes('node'), true);
});
test('Bash shebang detection', () => {
const content = '#!/bin/bash\necho hello';
const firstLine = content.split('\n')[0];
assertEqual(firstLine.startsWith('#!'), true);
assertEqual(firstLine.includes('bash') || firstLine.includes('/sh'), true);
});
// ============================================================================
// Argument Parsing Tests
// ============================================================================
console.log('\n=== Argument Parsing Tests ===');
test('Parse -e KEY=VALUE format', () => {
const arg = 'DEBUG=1';
const [key, ...rest] = arg.split('=');
const value = rest.join('=');
assertEqual(key, 'DEBUG');
assertEqual(value, '1');
});
test('Parse -e KEY=VALUE with equals in value', () => {
const arg = 'URL=https://example.com?foo=bar';
const [key, ...rest] = arg.split('=');
const value = rest.join('=');
assertEqual(key, 'URL');
assertEqual(value, 'https://example.com?foo=bar');
});
test('Valid network modes', () => {
const validModes = ['zerotrust', 'semitrusted'];
assertEqual(validModes.includes('zerotrust'), true);
assertEqual(validModes.includes('semitrusted'), true);
assertEqual(validModes.includes('invalid'), false);
});
test('Subcommand detection', () => {
const args = ['session', '--shell', 'python3'];
const subcommands = ['session', 'service', 'key', 'restore'];
const subcommand = subcommands.includes(args[0]) ? args[0] : null;
assertEqual(subcommand, 'session');
});
// ============================================================================
// File Operations Tests
// ============================================================================
console.log('\n=== File Operations Tests ===');
test('Read text file', () => {
const tempFile = path.join(os.tmpdir(), 'test_un_js_' + Date.now() + '.py');
fs.writeFileSync(tempFile, 'print("hello world")');
try {
const content = fs.readFileSync(tempFile, 'utf-8');
assertEqual(content, 'print("hello world")');
} finally {
fs.unlinkSync(tempFile);
}
});
test('Base64 encoding/decoding', () => {
const content = 'print("hello world")';
const encoded = Buffer.from(content).toString('base64');
const decoded = Buffer.from(encoded, 'base64').toString();
assertEqual(decoded, content);
});
test('Extract file basename', () => {
const filepath = '/home/user/project/script.py';
const basename = path.basename(filepath);
assertEqual(basename, 'script.py');
});
test('Extract file extension', () => {
const filepath = '/home/user/project/script.py';
const ext = path.extname(filepath);
assertEqual(ext, '.py');
});
// ============================================================================
// API Constants Tests
// ============================================================================
console.log('\n=== API Constants Tests ===');
test('API base URL format', () => {
const API_BASE = 'https://api.unsandbox.com';
assertEqual(API_BASE.startsWith('https://'), true);
assertIncludes(API_BASE, 'unsandbox.com');
});
test('Portal base URL format', () => {
const PORTAL_BASE = 'https://unsandbox.com';
assertEqual(PORTAL_BASE.startsWith('https://'), true);
});
// ============================================================================
// Summary
// ============================================================================
console.log('\n=== Summary ===');
console.log(`Passed: ${passed}`);
console.log(`Failed: ${failed}`);
console.log(`Total: ${passed + failed}`);
process.exit(failed > 0 ? 1 : 0);