Add unit and integration tests for all major implementations
This commit is contained in:
parent
ad074c1b91
commit
a379e061fc
10 changed files with 2523 additions and 0 deletions
64
tests/integration/run_all.sh
Executable file
64
tests/integration/run_all.sh
Executable file
|
|
@ -0,0 +1,64 @@
|
|||
#!/bin/bash
|
||||
# Run all integration tests for UN CLI implementations
|
||||
# These tests verify component interactions without making real API calls
|
||||
|
||||
set -o pipefail
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
echo -e "${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${CYAN}║ UN CLI Inception - Integration Tests ║${NC}"
|
||||
echo -e "${CYAN}║ Testing component interactions, no real API calls ║${NC}"
|
||||
echo -e "${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
passed=0
|
||||
failed=0
|
||||
skipped=0
|
||||
|
||||
run_test() {
|
||||
local name="$1"
|
||||
local cmd="$2"
|
||||
local interpreter="$3"
|
||||
|
||||
printf "%-30s" "$name"
|
||||
|
||||
if [ -n "$interpreter" ] && ! command -v "$interpreter" &> /dev/null; then
|
||||
echo -e "${YELLOW}SKIP${NC} ($interpreter not found)"
|
||||
skipped=$((skipped + 1))
|
||||
return
|
||||
fi
|
||||
|
||||
if $cmd > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}PASS${NC}"
|
||||
passed=$((passed + 1))
|
||||
else
|
||||
echo -e "${RED}FAIL${NC}"
|
||||
failed=$((failed + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
echo -e "${CYAN}━━━ Request Building Tests ━━━${NC}"
|
||||
run_test "Python Request Building" "python3 test_request_building.py" "python3"
|
||||
echo ""
|
||||
|
||||
# Summary
|
||||
echo -e "${CYAN}══════════════════════════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
total=$((passed + failed + skipped))
|
||||
echo -e "Results: ${GREEN}$passed PASS${NC} | ${RED}$failed FAIL${NC} | ${YELLOW}$skipped SKIP${NC} | Total: $total"
|
||||
echo ""
|
||||
|
||||
if [ $failed -eq 0 ]; then
|
||||
echo -e "${GREEN}All integration tests passed.${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}$failed test(s) failed.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
374
tests/integration/test_request_building.py
Executable file
374
tests/integration/test_request_building.py
Executable file
|
|
@ -0,0 +1,374 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Integration tests for request building - tests component interactions without API calls
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import hmac
|
||||
import hashlib
|
||||
import base64
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch, MagicMock
|
||||
from io import StringIO
|
||||
|
||||
# Set mock environment before importing
|
||||
os.environ['UNSANDBOX_PUBLIC_KEY'] = 'test-pk-1234'
|
||||
os.environ['UNSANDBOX_SECRET_KEY'] = 'test-sk-5678'
|
||||
|
||||
|
||||
class TestRequestBuilding(unittest.TestCase):
|
||||
"""Test that requests are built correctly with all components"""
|
||||
|
||||
def test_execute_request_structure(self):
|
||||
"""Test execute request has correct structure"""
|
||||
language = "python"
|
||||
code = 'print("hello world")'
|
||||
env_vars = {"DEBUG": "1", "NAME": "test"}
|
||||
network_mode = "zerotrust"
|
||||
|
||||
# Build request body
|
||||
body = {
|
||||
"language": language,
|
||||
"code": code,
|
||||
"env": env_vars,
|
||||
"network_mode": network_mode
|
||||
}
|
||||
|
||||
json_body = json.dumps(body)
|
||||
parsed = json.loads(json_body)
|
||||
|
||||
self.assertEqual(parsed["language"], "python")
|
||||
self.assertEqual(parsed["code"], code)
|
||||
self.assertEqual(parsed["env"]["DEBUG"], "1")
|
||||
self.assertEqual(parsed["network_mode"], "zerotrust")
|
||||
|
||||
def test_execute_request_with_files(self):
|
||||
"""Test execute request with input files"""
|
||||
# Create temp file
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
|
||||
f.write("test data content")
|
||||
temp_path = f.name
|
||||
|
||||
try:
|
||||
# Read and encode file
|
||||
with open(temp_path, 'rb') as f:
|
||||
content = f.read()
|
||||
encoded = base64.b64encode(content).decode('utf-8')
|
||||
|
||||
files = [{
|
||||
"name": os.path.basename(temp_path),
|
||||
"content": encoded
|
||||
}]
|
||||
|
||||
body = {
|
||||
"language": "python",
|
||||
"code": "print(open('test.txt').read())",
|
||||
"files": files
|
||||
}
|
||||
|
||||
json_body = json.dumps(body)
|
||||
parsed = json.loads(json_body)
|
||||
|
||||
self.assertIn("files", parsed)
|
||||
self.assertEqual(len(parsed["files"]), 1)
|
||||
self.assertIn("content", parsed["files"][0])
|
||||
|
||||
# Verify content can be decoded back
|
||||
decoded = base64.b64decode(parsed["files"][0]["content"])
|
||||
self.assertEqual(decoded.decode('utf-8'), "test data content")
|
||||
|
||||
finally:
|
||||
os.unlink(temp_path)
|
||||
|
||||
def test_hmac_signature_generation(self):
|
||||
"""Test HMAC signature is generated correctly for requests"""
|
||||
public_key = "test-pk-1234"
|
||||
secret_key = "test-sk-5678"
|
||||
timestamp = "1704067200"
|
||||
method = "POST"
|
||||
endpoint = "/execute"
|
||||
body = '{"language":"python","code":"print(1)"}'
|
||||
|
||||
# Build signature input
|
||||
signature_input = f"{timestamp}:{method}:{endpoint}:{body}"
|
||||
|
||||
# Generate signature
|
||||
signature = hmac.new(
|
||||
secret_key.encode(),
|
||||
signature_input.encode(),
|
||||
hashlib.sha256
|
||||
).hexdigest()
|
||||
|
||||
# Verify signature properties
|
||||
self.assertEqual(len(signature), 64)
|
||||
self.assertTrue(all(c in '0123456789abcdef' for c in signature))
|
||||
|
||||
# Verify same input produces same signature
|
||||
signature2 = hmac.new(
|
||||
secret_key.encode(),
|
||||
signature_input.encode(),
|
||||
hashlib.sha256
|
||||
).hexdigest()
|
||||
self.assertEqual(signature, signature2)
|
||||
|
||||
def test_session_request_structure(self):
|
||||
"""Test session request has correct structure"""
|
||||
body = {
|
||||
"shell": "python3",
|
||||
"network_mode": "semitrusted"
|
||||
}
|
||||
|
||||
json_body = json.dumps(body)
|
||||
parsed = json.loads(json_body)
|
||||
|
||||
self.assertEqual(parsed["shell"], "python3")
|
||||
self.assertEqual(parsed["network_mode"], "semitrusted")
|
||||
|
||||
def test_service_request_structure(self):
|
||||
"""Test service request has correct structure"""
|
||||
body = {
|
||||
"name": "my-service",
|
||||
"ports": [8080, 443],
|
||||
"bootstrap": "python3 -m http.server 8080",
|
||||
"network_mode": "semitrusted"
|
||||
}
|
||||
|
||||
json_body = json.dumps(body)
|
||||
parsed = json.loads(json_body)
|
||||
|
||||
self.assertEqual(parsed["name"], "my-service")
|
||||
self.assertEqual(parsed["ports"], [8080, 443])
|
||||
self.assertEqual(parsed["bootstrap"], "python3 -m http.server 8080")
|
||||
|
||||
def test_authorization_header_format(self):
|
||||
"""Test Authorization header is formatted correctly"""
|
||||
public_key = "unsb-pk-1234-5678-abcd-efgh"
|
||||
auth_header = f"Bearer {public_key}"
|
||||
|
||||
self.assertTrue(auth_header.startswith("Bearer "))
|
||||
self.assertIn(public_key, auth_header)
|
||||
|
||||
def test_request_headers_structure(self):
|
||||
"""Test all required headers are present"""
|
||||
public_key = "unsb-pk-1234"
|
||||
timestamp = "1704067200"
|
||||
signature = "abc123def456"
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {public_key}",
|
||||
"X-Timestamp": timestamp,
|
||||
"X-Signature": signature,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
self.assertIn("Authorization", headers)
|
||||
self.assertIn("X-Timestamp", headers)
|
||||
self.assertIn("X-Signature", headers)
|
||||
self.assertIn("Content-Type", headers)
|
||||
self.assertEqual(headers["Content-Type"], "application/json")
|
||||
|
||||
|
||||
class TestResponseParsing(unittest.TestCase):
|
||||
"""Test that responses are parsed correctly"""
|
||||
|
||||
def test_execute_response_parsing(self):
|
||||
"""Test execute response is parsed correctly"""
|
||||
response_json = {
|
||||
"id": "exec-12345",
|
||||
"status": "completed",
|
||||
"stdout": "hello world\n",
|
||||
"stderr": "",
|
||||
"exit_code": 0,
|
||||
"execution_time": 0.123
|
||||
}
|
||||
|
||||
json_str = json.dumps(response_json)
|
||||
parsed = json.loads(json_str)
|
||||
|
||||
self.assertEqual(parsed["id"], "exec-12345")
|
||||
self.assertEqual(parsed["status"], "completed")
|
||||
self.assertEqual(parsed["stdout"], "hello world\n")
|
||||
self.assertEqual(parsed["exit_code"], 0)
|
||||
|
||||
def test_session_response_parsing(self):
|
||||
"""Test session response is parsed correctly"""
|
||||
response_json = {
|
||||
"id": "sess-12345",
|
||||
"status": "ready",
|
||||
"websocket_url": "wss://api.unsandbox.com/ws/sess-12345"
|
||||
}
|
||||
|
||||
json_str = json.dumps(response_json)
|
||||
parsed = json.loads(json_str)
|
||||
|
||||
self.assertEqual(parsed["id"], "sess-12345")
|
||||
self.assertEqual(parsed["status"], "ready")
|
||||
self.assertIn("websocket_url", parsed)
|
||||
|
||||
def test_service_response_parsing(self):
|
||||
"""Test service response is parsed correctly"""
|
||||
response_json = {
|
||||
"id": "svc-12345",
|
||||
"name": "my-service",
|
||||
"status": "running",
|
||||
"domains": ["my-service.unsandbox.run"],
|
||||
"ports": {"8080": "https://my-service.unsandbox.run:8080"}
|
||||
}
|
||||
|
||||
json_str = json.dumps(response_json)
|
||||
parsed = json.loads(json_str)
|
||||
|
||||
self.assertEqual(parsed["id"], "svc-12345")
|
||||
self.assertEqual(parsed["name"], "my-service")
|
||||
self.assertEqual(parsed["status"], "running")
|
||||
self.assertIsInstance(parsed["domains"], list)
|
||||
|
||||
def test_error_response_parsing(self):
|
||||
"""Test error response is parsed correctly"""
|
||||
response_json = {
|
||||
"error": "Authentication failed",
|
||||
"code": "AUTH_ERROR",
|
||||
"status": 401
|
||||
}
|
||||
|
||||
json_str = json.dumps(response_json)
|
||||
parsed = json.loads(json_str)
|
||||
|
||||
self.assertIn("error", parsed)
|
||||
self.assertEqual(parsed["code"], "AUTH_ERROR")
|
||||
self.assertEqual(parsed["status"], 401)
|
||||
|
||||
def test_artifacts_response_parsing(self):
|
||||
"""Test response with artifacts is parsed correctly"""
|
||||
response_json = {
|
||||
"id": "exec-12345",
|
||||
"status": "completed",
|
||||
"stdout": "Generated output\n",
|
||||
"artifacts": [
|
||||
{
|
||||
"name": "output.png",
|
||||
"content": "iVBORw0KGgo=", # base64
|
||||
"size": 12345
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
json_str = json.dumps(response_json)
|
||||
parsed = json.loads(json_str)
|
||||
|
||||
self.assertIn("artifacts", parsed)
|
||||
self.assertEqual(len(parsed["artifacts"]), 1)
|
||||
self.assertEqual(parsed["artifacts"][0]["name"], "output.png")
|
||||
|
||||
|
||||
class TestErrorHandling(unittest.TestCase):
|
||||
"""Test error handling components"""
|
||||
|
||||
def test_missing_api_key_detection(self):
|
||||
"""Test detection of missing API keys"""
|
||||
# Save current env
|
||||
saved_pub = os.environ.get('UNSANDBOX_PUBLIC_KEY')
|
||||
saved_sec = os.environ.get('UNSANDBOX_SECRET_KEY')
|
||||
|
||||
try:
|
||||
del os.environ['UNSANDBOX_PUBLIC_KEY']
|
||||
del os.environ['UNSANDBOX_SECRET_KEY']
|
||||
|
||||
# Check that keys are missing
|
||||
public_key = os.environ.get('UNSANDBOX_PUBLIC_KEY')
|
||||
secret_key = os.environ.get('UNSANDBOX_SECRET_KEY')
|
||||
|
||||
self.assertIsNone(public_key)
|
||||
self.assertIsNone(secret_key)
|
||||
|
||||
finally:
|
||||
# Restore env
|
||||
if saved_pub:
|
||||
os.environ['UNSANDBOX_PUBLIC_KEY'] = saved_pub
|
||||
if saved_sec:
|
||||
os.environ['UNSANDBOX_SECRET_KEY'] = saved_sec
|
||||
|
||||
def test_invalid_json_handling(self):
|
||||
"""Test handling of invalid JSON responses"""
|
||||
invalid_json = "not valid json {"
|
||||
|
||||
with self.assertRaises(json.JSONDecodeError):
|
||||
json.loads(invalid_json)
|
||||
|
||||
def test_file_not_found_handling(self):
|
||||
"""Test handling of missing files"""
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
with open("/nonexistent/file/path.py", 'r') as f:
|
||||
f.read()
|
||||
|
||||
def test_invalid_extension_handling(self):
|
||||
"""Test handling of unrecognized file extensions"""
|
||||
EXT_MAP = {".py": "python", ".js": "javascript"}
|
||||
|
||||
ext = ".xyz"
|
||||
lang = EXT_MAP.get(ext)
|
||||
self.assertIsNone(lang)
|
||||
|
||||
|
||||
class TestFileProcessing(unittest.TestCase):
|
||||
"""Test file processing integration"""
|
||||
|
||||
def test_multiple_files_encoding(self):
|
||||
"""Test encoding multiple files for request"""
|
||||
files_data = []
|
||||
|
||||
# Create multiple temp files
|
||||
temp_files = []
|
||||
for i in range(3):
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix=f'.txt', delete=False) as f:
|
||||
f.write(f"File {i} content")
|
||||
temp_files.append(f.name)
|
||||
|
||||
try:
|
||||
for temp_path in temp_files:
|
||||
with open(temp_path, 'rb') as f:
|
||||
content = f.read()
|
||||
files_data.append({
|
||||
"name": os.path.basename(temp_path),
|
||||
"content": base64.b64encode(content).decode('utf-8')
|
||||
})
|
||||
|
||||
# Verify all files encoded
|
||||
self.assertEqual(len(files_data), 3)
|
||||
|
||||
# Verify each file can be decoded
|
||||
for i, file_data in enumerate(files_data):
|
||||
decoded = base64.b64decode(file_data["content"])
|
||||
self.assertIn(f"File {i} content", decoded.decode('utf-8'))
|
||||
|
||||
finally:
|
||||
for temp_path in temp_files:
|
||||
os.unlink(temp_path)
|
||||
|
||||
def test_binary_file_encoding(self):
|
||||
"""Test encoding binary files"""
|
||||
# Create binary content
|
||||
binary_content = bytes([0x00, 0x01, 0x02, 0xFF, 0xFE, 0xFD])
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='wb', delete=False) as f:
|
||||
f.write(binary_content)
|
||||
temp_path = f.name
|
||||
|
||||
try:
|
||||
with open(temp_path, 'rb') as f:
|
||||
content = f.read()
|
||||
encoded = base64.b64encode(content).decode('utf-8')
|
||||
|
||||
# Verify can decode back to original
|
||||
decoded = base64.b64decode(encoded)
|
||||
self.assertEqual(decoded, binary_content)
|
||||
|
||||
finally:
|
||||
os.unlink(temp_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
72
tests/unit/run_all.sh
Executable file
72
tests/unit/run_all.sh
Executable file
|
|
@ -0,0 +1,72 @@
|
|||
#!/bin/bash
|
||||
# Run all unit tests for UN CLI implementations
|
||||
# These tests do NOT call the API - they test internal logic only
|
||||
|
||||
set -o pipefail
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
echo -e "${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${CYAN}║ UN CLI Inception - Unit Tests ║${NC}"
|
||||
echo -e "${CYAN}║ Testing internal logic, no API calls ║${NC}"
|
||||
echo -e "${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
passed=0
|
||||
failed=0
|
||||
skipped=0
|
||||
|
||||
run_test() {
|
||||
local name="$1"
|
||||
local cmd="$2"
|
||||
local interpreter="$3"
|
||||
|
||||
printf "%-20s" "$name"
|
||||
|
||||
if [ -n "$interpreter" ] && ! command -v "$interpreter" &> /dev/null; then
|
||||
echo -e "${YELLOW}SKIP${NC} ($interpreter not found)"
|
||||
skipped=$((skipped + 1))
|
||||
return
|
||||
fi
|
||||
|
||||
if $cmd > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}PASS${NC}"
|
||||
passed=$((passed + 1))
|
||||
else
|
||||
echo -e "${RED}FAIL${NC}"
|
||||
failed=$((failed + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
echo -e "${CYAN}━━━ Scripting Languages ━━━${NC}"
|
||||
run_test "Python" "python3 test_python.py" "python3"
|
||||
run_test "JavaScript" "node test_javascript.js" "node"
|
||||
run_test "Ruby" "ruby test_ruby.rb" "ruby"
|
||||
run_test "Lua" "lua test_lua.lua" "lua"
|
||||
run_test "Bash" "bash test_bash.sh" "bash"
|
||||
echo ""
|
||||
|
||||
echo -e "${CYAN}━━━ Systems Languages ━━━${NC}"
|
||||
run_test "Go" "go run test_go.go" "go"
|
||||
echo ""
|
||||
|
||||
# Summary
|
||||
echo -e "${CYAN}══════════════════════════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
total=$((passed + failed + skipped))
|
||||
echo -e "Results: ${GREEN}$passed PASS${NC} | ${RED}$failed FAIL${NC} | ${YELLOW}$skipped SKIP${NC} | Total: $total"
|
||||
echo ""
|
||||
|
||||
if [ $failed -eq 0 ]; then
|
||||
echo -e "${GREEN}All unit tests passed.${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}$failed test(s) failed.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
278
tests/unit/test_bash.sh
Executable file
278
tests/unit/test_bash.sh
Executable file
|
|
@ -0,0 +1,278 @@
|
|||
#!/bin/bash
|
||||
# Unit tests for un.sh - tests internal functions without API calls
|
||||
|
||||
set -o pipefail
|
||||
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
test_case() {
|
||||
local name="$1"
|
||||
shift
|
||||
if "$@"; then
|
||||
echo -e " ${GREEN}✓${NC} $name"
|
||||
PASSED=$((PASSED + 1))
|
||||
else
|
||||
echo -e " ${RED}✗${NC} $name"
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
assert_equal() {
|
||||
local actual="$1"
|
||||
local expected="$2"
|
||||
[ "$actual" = "$expected" ]
|
||||
}
|
||||
|
||||
assert_not_equal() {
|
||||
local a="$1"
|
||||
local b="$2"
|
||||
[ "$a" != "$b" ]
|
||||
}
|
||||
|
||||
assert_contains() {
|
||||
local str="$1"
|
||||
local substr="$2"
|
||||
[[ "$str" == *"$substr"* ]]
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Extension Mapping Tests
|
||||
# ============================================================================
|
||||
|
||||
echo ""
|
||||
echo "=== Extension Mapping Tests ==="
|
||||
|
||||
# Extension to language mapping (from un.sh)
|
||||
get_language() {
|
||||
local ext="${1##*.}"
|
||||
case ".$ext" in
|
||||
.py) echo "python" ;;
|
||||
.js) echo "javascript" ;;
|
||||
.ts) echo "typescript" ;;
|
||||
.rb) echo "ruby" ;;
|
||||
.php) echo "php" ;;
|
||||
.pl) echo "perl" ;;
|
||||
.lua) echo "lua" ;;
|
||||
.sh) echo "bash" ;;
|
||||
.go) echo "go" ;;
|
||||
.rs) echo "rust" ;;
|
||||
.c) echo "c" ;;
|
||||
.cpp|.cc|.cxx) echo "cpp" ;;
|
||||
.java) echo "java" ;;
|
||||
.kt) echo "kotlin" ;;
|
||||
.cs) echo "csharp" ;;
|
||||
.fs) echo "fsharp" ;;
|
||||
.hs) echo "haskell" ;;
|
||||
.ml) echo "ocaml" ;;
|
||||
.clj) echo "clojure" ;;
|
||||
.scm) echo "scheme" ;;
|
||||
.lisp) echo "commonlisp" ;;
|
||||
.erl) echo "erlang" ;;
|
||||
.ex|.exs) echo "elixir" ;;
|
||||
.jl) echo "julia" ;;
|
||||
.r|.R) echo "r" ;;
|
||||
.cr) echo "crystal" ;;
|
||||
.d) echo "d" ;;
|
||||
.nim) echo "nim" ;;
|
||||
.zig) echo "zig" ;;
|
||||
.v) echo "v" ;;
|
||||
.dart) echo "dart" ;;
|
||||
.groovy) echo "groovy" ;;
|
||||
.scala) echo "scala" ;;
|
||||
.f90|.f95) echo "fortran" ;;
|
||||
.cob) echo "cobol" ;;
|
||||
.pro) echo "prolog" ;;
|
||||
.forth|.4th) echo "forth" ;;
|
||||
.tcl) echo "tcl" ;;
|
||||
.raku) echo "raku" ;;
|
||||
.m) echo "objc" ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
test_case "Python extension maps correctly" assert_equal "$(get_language script.py)" "python"
|
||||
test_case "JavaScript extension maps correctly" assert_equal "$(get_language app.js)" "javascript"
|
||||
test_case "TypeScript extension maps correctly" assert_equal "$(get_language app.ts)" "typescript"
|
||||
test_case "Ruby extension maps correctly" assert_equal "$(get_language app.rb)" "ruby"
|
||||
test_case "Go extension maps correctly" assert_equal "$(get_language main.go)" "go"
|
||||
test_case "Rust extension maps correctly" assert_equal "$(get_language main.rs)" "rust"
|
||||
test_case "C extension maps correctly" assert_equal "$(get_language main.c)" "c"
|
||||
test_case "C++ extension maps correctly" assert_equal "$(get_language main.cpp)" "cpp"
|
||||
test_case "Java extension maps correctly" assert_equal "$(get_language Main.java)" "java"
|
||||
test_case "Kotlin extension maps correctly" assert_equal "$(get_language main.kt)" "kotlin"
|
||||
test_case "Haskell extension maps correctly" assert_equal "$(get_language main.hs)" "haskell"
|
||||
test_case "Elixir extension maps correctly" assert_equal "$(get_language main.ex)" "elixir"
|
||||
test_case "Julia extension maps correctly" assert_equal "$(get_language main.jl)" "julia"
|
||||
|
||||
# ============================================================================
|
||||
# HMAC Signature Tests
|
||||
# ============================================================================
|
||||
|
||||
echo ""
|
||||
echo "=== HMAC Signature Tests ==="
|
||||
|
||||
# HMAC function (requires openssl)
|
||||
hmac_sha256() {
|
||||
local secret="$1"
|
||||
local message="$2"
|
||||
echo -n "$message" | openssl dgst -sha256 -hmac "$secret" | sed 's/^.* //'
|
||||
}
|
||||
|
||||
test_case "HMAC-SHA256 generates 64 character hex string" \
|
||||
bash -c 'sig=$(echo -n "test" | openssl dgst -sha256 -hmac "secret" | sed "s/^.* //"); [ ${#sig} -eq 64 ]'
|
||||
|
||||
test_case "Same input produces same signature" \
|
||||
bash -c '[[ "$(echo -n "msg" | openssl dgst -sha256 -hmac "key" | sed "s/^.* //")" == "$(echo -n "msg" | openssl dgst -sha256 -hmac "key" | sed "s/^.* //")" ]]'
|
||||
|
||||
SIG1=$(echo -n "msg" | openssl dgst -sha256 -hmac "key1" | sed 's/^.* //')
|
||||
SIG2=$(echo -n "msg" | openssl dgst -sha256 -hmac "key2" | sed 's/^.* //')
|
||||
test_case "Different secrets produce different signatures" assert_not_equal "$SIG1" "$SIG2"
|
||||
|
||||
SIG3=$(echo -n "msg1" | openssl dgst -sha256 -hmac "key" | sed 's/^.* //')
|
||||
SIG4=$(echo -n "msg2" | openssl dgst -sha256 -hmac "key" | sed 's/^.* //')
|
||||
test_case "Different messages produce different signatures" assert_not_equal "$SIG3" "$SIG4"
|
||||
|
||||
test_case "Signature format is timestamp:METHOD:path:body" \
|
||||
bash -c 'msg="1234567890:POST:/execute:{}"; [[ "$msg" == *":"* ]] && [[ $(echo "$msg" | tr -cd ":" | wc -c) -eq 3 ]]'
|
||||
|
||||
# ============================================================================
|
||||
# Language Detection Tests
|
||||
# ============================================================================
|
||||
|
||||
echo ""
|
||||
echo "=== Language Detection Tests ==="
|
||||
|
||||
detect_from_shebang() {
|
||||
local first_line="$1"
|
||||
if [[ "$first_line" == "#!"* ]]; then
|
||||
case "$first_line" in
|
||||
*python*) echo "python" ;;
|
||||
*node*) echo "javascript" ;;
|
||||
*ruby*) echo "ruby" ;;
|
||||
*perl*) echo "perl" ;;
|
||||
*bash*|*/sh*) echo "bash" ;;
|
||||
*lua*) echo "lua" ;;
|
||||
*php*) echo "php" ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
test_case "Python shebang detection" \
|
||||
assert_equal "$(detect_from_shebang '#!/usr/bin/env python3')" "python"
|
||||
|
||||
test_case "Node shebang detection" \
|
||||
assert_equal "$(detect_from_shebang '#!/usr/bin/env node')" "javascript"
|
||||
|
||||
test_case "Ruby shebang detection" \
|
||||
assert_equal "$(detect_from_shebang '#!/usr/bin/env ruby')" "ruby"
|
||||
|
||||
test_case "Bash shebang detection" \
|
||||
assert_equal "$(detect_from_shebang '#!/bin/bash')" "bash"
|
||||
|
||||
test_case "Sh shebang detection" \
|
||||
assert_equal "$(detect_from_shebang '#!/bin/sh')" "bash"
|
||||
|
||||
# ============================================================================
|
||||
# Argument Parsing Tests
|
||||
# ============================================================================
|
||||
|
||||
echo ""
|
||||
echo "=== Argument Parsing Tests ==="
|
||||
|
||||
parse_env_var() {
|
||||
local arg="$1"
|
||||
local key="${arg%%=*}"
|
||||
local value="${arg#*=}"
|
||||
echo "$key|$value"
|
||||
}
|
||||
|
||||
test_case "Parse -e KEY=VALUE format" \
|
||||
assert_equal "$(parse_env_var 'DEBUG=1')" "DEBUG|1"
|
||||
|
||||
test_case "Parse -e KEY=VALUE with equals in value" \
|
||||
assert_equal "$(parse_env_var 'URL=https://example.com?foo=bar')" "URL|https://example.com?foo=bar"
|
||||
|
||||
test_case "Valid network mode zerotrust" \
|
||||
bash -c '[[ "zerotrust" =~ ^(zerotrust|semitrusted)$ ]]'
|
||||
|
||||
test_case "Valid network mode semitrusted" \
|
||||
bash -c '[[ "semitrusted" =~ ^(zerotrust|semitrusted)$ ]]'
|
||||
|
||||
test_case "Invalid network mode rejected" \
|
||||
bash -c '! [[ "invalid" =~ ^(zerotrust|semitrusted)$ ]]'
|
||||
|
||||
is_subcommand() {
|
||||
local cmd="$1"
|
||||
case "$cmd" in
|
||||
session|service|key|restore) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
test_case "Subcommand detection - session" is_subcommand "session"
|
||||
test_case "Subcommand detection - service" is_subcommand "service"
|
||||
test_case "Subcommand detection - key" is_subcommand "key"
|
||||
test_case "Not a subcommand - script.py" bash -c '! is_subcommand "script.py"'
|
||||
|
||||
# ============================================================================
|
||||
# File Operations Tests
|
||||
# ============================================================================
|
||||
|
||||
echo ""
|
||||
echo "=== File Operations Tests ==="
|
||||
|
||||
TMPFILE=$(mktemp --suffix=.py)
|
||||
echo -n 'print("hello world")' > "$TMPFILE"
|
||||
|
||||
test_case "Read text file" \
|
||||
assert_equal "$(cat "$TMPFILE")" 'print("hello world")'
|
||||
|
||||
test_case "Base64 encoding/decoding" \
|
||||
bash -c '[[ "$(echo -n "hello" | base64 | base64 -d)" == "hello" ]]'
|
||||
|
||||
test_case "Extract file basename" \
|
||||
assert_equal "$(basename /home/user/project/script.py)" "script.py"
|
||||
|
||||
EXT="${TMPFILE##*.}"
|
||||
test_case "Extract file extension" \
|
||||
assert_equal "$EXT" "py"
|
||||
|
||||
rm -f "$TMPFILE"
|
||||
|
||||
# ============================================================================
|
||||
# API Constants Tests
|
||||
# ============================================================================
|
||||
|
||||
echo ""
|
||||
echo "=== API Constants Tests ==="
|
||||
|
||||
API_BASE="https://api.unsandbox.com"
|
||||
PORTAL_BASE="https://unsandbox.com"
|
||||
|
||||
test_case "API base URL starts with https" \
|
||||
bash -c '[[ "https://api.unsandbox.com" == https://* ]]'
|
||||
|
||||
test_case "API base URL contains unsandbox.com" \
|
||||
assert_contains "$API_BASE" "unsandbox.com"
|
||||
|
||||
test_case "Portal base URL starts with https" \
|
||||
bash -c '[[ "https://unsandbox.com" == https://* ]]'
|
||||
|
||||
# ============================================================================
|
||||
# Summary
|
||||
# ============================================================================
|
||||
|
||||
echo ""
|
||||
echo "=== Summary ==="
|
||||
echo "Passed: $PASSED"
|
||||
echo "Failed: $FAILED"
|
||||
echo "Total: $((PASSED + FAILED))"
|
||||
|
||||
exit $((FAILED > 0 ? 1 : 0))
|
||||
376
tests/unit/test_go.go
Normal file
376
tests/unit/test_go.go
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
// Unit tests for un.go - tests internal functions without API calls
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var passed, failed int
|
||||
|
||||
func test(name string, fn func() error) {
|
||||
if err := fn(); err != nil {
|
||||
fmt.Printf(" ✗ %s\n", name)
|
||||
fmt.Printf(" %s\n", err)
|
||||
failed++
|
||||
} else {
|
||||
fmt.Printf(" ✓ %s\n", name)
|
||||
passed++
|
||||
}
|
||||
}
|
||||
|
||||
func assertEqual(actual, expected string) error {
|
||||
if actual != expected {
|
||||
return fmt.Errorf("expected '%s' but got '%s'", expected, actual)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func assertNotEqual(a, b string) error {
|
||||
if a == b {
|
||||
return fmt.Errorf("expected values to be different but both were '%s'", a)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func assertContains(str, substr string) error {
|
||||
if !strings.Contains(str, substr) {
|
||||
return fmt.Errorf("expected '%s' to contain '%s'", str, substr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func assertTrue(val bool) error {
|
||||
if !val {
|
||||
return fmt.Errorf("expected true but got false")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Extension mapping (copied from un.go)
|
||||
var extMap = map[string]string{
|
||||
".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",
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Extension Mapping Tests
|
||||
fmt.Println("\n=== Extension Mapping Tests ===")
|
||||
|
||||
test("Python extension maps correctly", func() error {
|
||||
return assertEqual(extMap[".py"], "python")
|
||||
})
|
||||
|
||||
test("JavaScript extensions map correctly", func() error {
|
||||
if err := assertEqual(extMap[".js"], "javascript"); err != nil {
|
||||
return err
|
||||
}
|
||||
return assertEqual(extMap[".ts"], "typescript")
|
||||
})
|
||||
|
||||
test("Ruby extension maps correctly", func() error {
|
||||
return assertEqual(extMap[".rb"], "ruby")
|
||||
})
|
||||
|
||||
test("Go extension maps correctly", func() error {
|
||||
return assertEqual(extMap[".go"], "go")
|
||||
})
|
||||
|
||||
test("Rust extension maps correctly", func() error {
|
||||
return assertEqual(extMap[".rs"], "rust")
|
||||
})
|
||||
|
||||
test("C/C++ extensions map correctly", func() error {
|
||||
if err := assertEqual(extMap[".c"], "c"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := assertEqual(extMap[".cpp"], "cpp"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := assertEqual(extMap[".cc"], "cpp"); err != nil {
|
||||
return err
|
||||
}
|
||||
return assertEqual(extMap[".cxx"], "cpp")
|
||||
})
|
||||
|
||||
test("JVM extensions map correctly", func() error {
|
||||
if err := assertEqual(extMap[".java"], "java"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := assertEqual(extMap[".kt"], "kotlin"); err != nil {
|
||||
return err
|
||||
}
|
||||
return assertEqual(extMap[".groovy"], "groovy")
|
||||
})
|
||||
|
||||
test("Functional language extensions map correctly", func() error {
|
||||
if err := assertEqual(extMap[".hs"], "haskell"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := assertEqual(extMap[".ml"], "ocaml"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := assertEqual(extMap[".clj"], "clojure"); err != nil {
|
||||
return err
|
||||
}
|
||||
return assertEqual(extMap[".erl"], "erlang")
|
||||
})
|
||||
|
||||
// HMAC Signature Tests
|
||||
fmt.Println("\n=== HMAC Signature Tests ===")
|
||||
|
||||
test("HMAC-SHA256 generates 64 character hex string", func() error {
|
||||
secret := "test-secret-key"
|
||||
message := "1234567890:POST:/execute:{}"
|
||||
|
||||
h := hmac.New(sha256.New, []byte(secret))
|
||||
h.Write([]byte(message))
|
||||
signature := hex.EncodeToString(h.Sum(nil))
|
||||
|
||||
if len(signature) != 64 {
|
||||
return fmt.Errorf("expected length 64 but got %d", len(signature))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
test("Same input produces same signature", func() error {
|
||||
secret := "test-secret-key"
|
||||
message := "1234567890:POST:/execute:{}"
|
||||
|
||||
h1 := hmac.New(sha256.New, []byte(secret))
|
||||
h1.Write([]byte(message))
|
||||
sig1 := hex.EncodeToString(h1.Sum(nil))
|
||||
|
||||
h2 := hmac.New(sha256.New, []byte(secret))
|
||||
h2.Write([]byte(message))
|
||||
sig2 := hex.EncodeToString(h2.Sum(nil))
|
||||
|
||||
return assertEqual(sig1, sig2)
|
||||
})
|
||||
|
||||
test("Different secrets produce different signatures", func() error {
|
||||
message := "1234567890:POST:/execute:{}"
|
||||
|
||||
h1 := hmac.New(sha256.New, []byte("secret1"))
|
||||
h1.Write([]byte(message))
|
||||
sig1 := hex.EncodeToString(h1.Sum(nil))
|
||||
|
||||
h2 := hmac.New(sha256.New, []byte("secret2"))
|
||||
h2.Write([]byte(message))
|
||||
sig2 := hex.EncodeToString(h2.Sum(nil))
|
||||
|
||||
return assertNotEqual(sig1, sig2)
|
||||
})
|
||||
|
||||
test("Different messages produce different signatures", func() error {
|
||||
secret := "test-secret"
|
||||
|
||||
h1 := hmac.New(sha256.New, []byte(secret))
|
||||
h1.Write([]byte("message1"))
|
||||
sig1 := hex.EncodeToString(h1.Sum(nil))
|
||||
|
||||
h2 := hmac.New(sha256.New, []byte(secret))
|
||||
h2.Write([]byte("message2"))
|
||||
sig2 := hex.EncodeToString(h2.Sum(nil))
|
||||
|
||||
return assertNotEqual(sig1, sig2)
|
||||
})
|
||||
|
||||
test("Signature format is timestamp:METHOD:path:body", func() error {
|
||||
timestamp := "1704067200"
|
||||
method := "POST"
|
||||
endpoint := "/execute"
|
||||
body := `{"language":"python","code":"print(1)"}`
|
||||
|
||||
message := fmt.Sprintf("%s:%s:%s:%s", timestamp, method, endpoint, body)
|
||||
|
||||
// Verify format: starts with timestamp, has method and path
|
||||
if err := assertTrue(strings.HasPrefix(message, timestamp)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := assertContains(message, ":POST:"); err != nil {
|
||||
return err
|
||||
}
|
||||
return assertContains(message, ":/execute:")
|
||||
})
|
||||
|
||||
// Language Detection Tests
|
||||
fmt.Println("\n=== Language Detection Tests ===")
|
||||
|
||||
test("Detect language from .py extension", func() error {
|
||||
filename := "script.py"
|
||||
ext := strings.ToLower(filepath.Ext(filename))
|
||||
return assertEqual(extMap[ext], "python")
|
||||
})
|
||||
|
||||
test("Detect language from .go extension", func() error {
|
||||
filename := "main.go"
|
||||
ext := strings.ToLower(filepath.Ext(filename))
|
||||
return assertEqual(extMap[ext], "go")
|
||||
})
|
||||
|
||||
test("Python shebang detection", func() error {
|
||||
content := "#!/usr/bin/env python3\nprint('hello')"
|
||||
firstLine := strings.Split(content, "\n")[0]
|
||||
|
||||
if err := assertTrue(strings.HasPrefix(firstLine, "#!")); err != nil {
|
||||
return err
|
||||
}
|
||||
return assertContains(firstLine, "python")
|
||||
})
|
||||
|
||||
test("Bash shebang detection", func() error {
|
||||
content := "#!/bin/bash\necho hello"
|
||||
firstLine := strings.Split(content, "\n")[0]
|
||||
|
||||
if err := assertTrue(strings.HasPrefix(firstLine, "#!")); err != nil {
|
||||
return err
|
||||
}
|
||||
return assertTrue(strings.Contains(firstLine, "bash") || strings.Contains(firstLine, "/sh"))
|
||||
})
|
||||
|
||||
// Argument Parsing Tests
|
||||
fmt.Println("\n=== Argument Parsing Tests ===")
|
||||
|
||||
test("Parse -e KEY=VALUE format", func() error {
|
||||
arg := "DEBUG=1"
|
||||
parts := strings.SplitN(arg, "=", 2)
|
||||
key := parts[0]
|
||||
value := parts[1]
|
||||
|
||||
if err := assertEqual(key, "DEBUG"); err != nil {
|
||||
return err
|
||||
}
|
||||
return assertEqual(value, "1")
|
||||
})
|
||||
|
||||
test("Parse -e KEY=VALUE with equals in value", func() error {
|
||||
arg := "URL=https://example.com?foo=bar"
|
||||
parts := strings.SplitN(arg, "=", 2)
|
||||
key := parts[0]
|
||||
value := parts[1]
|
||||
|
||||
if err := assertEqual(key, "URL"); err != nil {
|
||||
return err
|
||||
}
|
||||
return assertEqual(value, "https://example.com?foo=bar")
|
||||
})
|
||||
|
||||
test("Valid network modes", func() error {
|
||||
validModes := map[string]bool{"zerotrust": true, "semitrusted": true}
|
||||
|
||||
if !validModes["zerotrust"] {
|
||||
return fmt.Errorf("zerotrust should be valid")
|
||||
}
|
||||
if !validModes["semitrusted"] {
|
||||
return fmt.Errorf("semitrusted should be valid")
|
||||
}
|
||||
if validModes["invalid"] {
|
||||
return fmt.Errorf("invalid should not be valid")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
test("Subcommand detection", func() error {
|
||||
args := []string{"session", "--shell", "python3"}
|
||||
subcommands := map[string]bool{"session": true, "service": true, "key": true, "restore": true}
|
||||
|
||||
var subcommand string
|
||||
if len(args) > 0 && subcommands[args[0]] {
|
||||
subcommand = args[0]
|
||||
}
|
||||
|
||||
return assertEqual(subcommand, "session")
|
||||
})
|
||||
|
||||
// File Operations Tests
|
||||
fmt.Println("\n=== File Operations Tests ===")
|
||||
|
||||
test("Read text file", func() error {
|
||||
tmpfile, err := os.CreateTemp("", "test_un_go_*.py")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmpfile.Name())
|
||||
|
||||
content := "print('hello world')"
|
||||
if _, err := tmpfile.WriteString(content); err != nil {
|
||||
return err
|
||||
}
|
||||
tmpfile.Close()
|
||||
|
||||
data, err := os.ReadFile(tmpfile.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return assertEqual(string(data), content)
|
||||
})
|
||||
|
||||
test("Base64 encoding/decoding", func() error {
|
||||
content := "print('hello world')"
|
||||
encoded := base64.StdEncoding.EncodeToString([]byte(content))
|
||||
decoded, err := base64.StdEncoding.DecodeString(encoded)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return assertEqual(string(decoded), content)
|
||||
})
|
||||
|
||||
test("Extract file basename", func() error {
|
||||
path := "/home/user/project/script.py"
|
||||
basename := filepath.Base(path)
|
||||
return assertEqual(basename, "script.py")
|
||||
})
|
||||
|
||||
test("Extract file extension", func() error {
|
||||
path := "/home/user/project/script.py"
|
||||
ext := filepath.Ext(path)
|
||||
return assertEqual(ext, ".py")
|
||||
})
|
||||
|
||||
// API Constants Tests
|
||||
fmt.Println("\n=== API Constants Tests ===")
|
||||
|
||||
test("API base URL format", func() error {
|
||||
apiBase := "https://api.unsandbox.com"
|
||||
|
||||
if err := assertTrue(strings.HasPrefix(apiBase, "https://")); err != nil {
|
||||
return err
|
||||
}
|
||||
return assertContains(apiBase, "unsandbox.com")
|
||||
})
|
||||
|
||||
test("Portal base URL format", func() error {
|
||||
portalBase := "https://unsandbox.com"
|
||||
return assertTrue(strings.HasPrefix(portalBase, "https://"))
|
||||
})
|
||||
|
||||
// Summary
|
||||
fmt.Println("\n=== Summary ===")
|
||||
fmt.Printf("Passed: %d\n", passed)
|
||||
fmt.Printf("Failed: %d\n", failed)
|
||||
fmt.Printf("Total: %d\n", passed+failed)
|
||||
|
||||
if failed > 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
355
tests/unit/test_javascript.js
Normal file
355
tests/unit/test_javascript.js
Normal file
|
|
@ -0,0 +1,355 @@
|
|||
#!/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);
|
||||
319
tests/unit/test_lua.lua
Executable file
319
tests/unit/test_lua.lua
Executable file
|
|
@ -0,0 +1,319 @@
|
|||
#!/usr/bin/env lua
|
||||
-- Unit tests for un.lua - tests internal functions without API calls
|
||||
|
||||
local passed = 0
|
||||
local failed = 0
|
||||
|
||||
local function test(name, fn)
|
||||
local ok, err = pcall(fn)
|
||||
if ok then
|
||||
print(" ✓ " .. name)
|
||||
passed = passed + 1
|
||||
else
|
||||
print(" ✗ " .. name)
|
||||
print(" " .. tostring(err))
|
||||
failed = failed + 1
|
||||
end
|
||||
end
|
||||
|
||||
local function assert_equal(actual, expected, msg)
|
||||
if actual ~= expected then
|
||||
error(string.format("Expected '%s' but got '%s' %s", tostring(expected), tostring(actual), msg or ""))
|
||||
end
|
||||
end
|
||||
|
||||
local function assert_not_equal(a, b)
|
||||
if a == b then
|
||||
error(string.format("Expected values to be different but both were '%s'", tostring(a)))
|
||||
end
|
||||
end
|
||||
|
||||
local function assert_contains(str, substr)
|
||||
if not string.find(str, substr, 1, true) then
|
||||
error(string.format("Expected '%s' to contain '%s'", str, substr))
|
||||
end
|
||||
end
|
||||
|
||||
local function assert_true(val)
|
||||
if not val then
|
||||
error("Expected true but got false")
|
||||
end
|
||||
end
|
||||
|
||||
-- Extension mapping (from un.lua)
|
||||
local 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",
|
||||
}
|
||||
|
||||
-- ============================================================================
|
||||
-- Extension Mapping Tests
|
||||
-- ============================================================================
|
||||
|
||||
print("\n=== Extension Mapping Tests ===")
|
||||
|
||||
test("Python extension maps correctly", function()
|
||||
assert_equal(ext_map[".py"], "python")
|
||||
end)
|
||||
|
||||
test("JavaScript extensions map correctly", function()
|
||||
assert_equal(ext_map[".js"], "javascript")
|
||||
assert_equal(ext_map[".ts"], "typescript")
|
||||
end)
|
||||
|
||||
test("Ruby extension maps correctly", function()
|
||||
assert_equal(ext_map[".rb"], "ruby")
|
||||
end)
|
||||
|
||||
test("Go extension maps correctly", function()
|
||||
assert_equal(ext_map[".go"], "go")
|
||||
end)
|
||||
|
||||
test("Rust extension maps correctly", function()
|
||||
assert_equal(ext_map[".rs"], "rust")
|
||||
end)
|
||||
|
||||
test("C/C++ extensions map correctly", function()
|
||||
assert_equal(ext_map[".c"], "c")
|
||||
assert_equal(ext_map[".cpp"], "cpp")
|
||||
assert_equal(ext_map[".cc"], "cpp")
|
||||
assert_equal(ext_map[".cxx"], "cpp")
|
||||
end)
|
||||
|
||||
test("Lua extension maps correctly", function()
|
||||
assert_equal(ext_map[".lua"], "lua")
|
||||
end)
|
||||
|
||||
test("JVM extensions map correctly", function()
|
||||
assert_equal(ext_map[".java"], "java")
|
||||
assert_equal(ext_map[".kt"], "kotlin")
|
||||
assert_equal(ext_map[".groovy"], "groovy")
|
||||
end)
|
||||
|
||||
test("Functional language extensions map correctly", function()
|
||||
assert_equal(ext_map[".hs"], "haskell")
|
||||
assert_equal(ext_map[".ml"], "ocaml")
|
||||
assert_equal(ext_map[".clj"], "clojure")
|
||||
assert_equal(ext_map[".erl"], "erlang")
|
||||
end)
|
||||
|
||||
-- ============================================================================
|
||||
-- HMAC Signature Tests (using openssl via shell)
|
||||
-- ============================================================================
|
||||
|
||||
print("\n=== HMAC Signature Tests ===")
|
||||
|
||||
local function hmac_sha256(secret, message)
|
||||
local cmd = string.format("echo -n '%s' | openssl dgst -sha256 -hmac '%s' 2>/dev/null | sed 's/^.* //'",
|
||||
message:gsub("'", "'\\''"), secret:gsub("'", "'\\''"))
|
||||
local handle = io.popen(cmd)
|
||||
if handle then
|
||||
local result = handle:read("*a"):gsub("%s+$", "")
|
||||
handle:close()
|
||||
return result
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
test("HMAC-SHA256 generates 64 character hex string", function()
|
||||
local sig = hmac_sha256("test-secret", "test-message")
|
||||
if sig then
|
||||
assert_equal(#sig, 64)
|
||||
end
|
||||
end)
|
||||
|
||||
test("Same input produces same signature", function()
|
||||
local sig1 = hmac_sha256("key", "message")
|
||||
local sig2 = hmac_sha256("key", "message")
|
||||
if sig1 and sig2 then
|
||||
assert_equal(sig1, sig2)
|
||||
end
|
||||
end)
|
||||
|
||||
test("Different secrets produce different signatures", function()
|
||||
local sig1 = hmac_sha256("key1", "message")
|
||||
local sig2 = hmac_sha256("key2", "message")
|
||||
if sig1 and sig2 then
|
||||
assert_not_equal(sig1, sig2)
|
||||
end
|
||||
end)
|
||||
|
||||
test("Different messages produce different signatures", function()
|
||||
local sig1 = hmac_sha256("key", "message1")
|
||||
local sig2 = hmac_sha256("key", "message2")
|
||||
if sig1 and sig2 then
|
||||
assert_not_equal(sig1, sig2)
|
||||
end
|
||||
end)
|
||||
|
||||
test("Signature format is timestamp:METHOD:path:body", function()
|
||||
local timestamp = "1704067200"
|
||||
local method = "POST"
|
||||
local endpoint = "/execute"
|
||||
local body = '{"language":"python","code":"print(1)"}'
|
||||
|
||||
local message = timestamp .. ":" .. method .. ":" .. endpoint .. ":" .. body
|
||||
|
||||
assert_contains(message, ":")
|
||||
local count = 0
|
||||
for _ in message:gmatch(":") do count = count + 1 end
|
||||
assert_equal(count, 3)
|
||||
assert_true(message:sub(1, #timestamp) == timestamp)
|
||||
end)
|
||||
|
||||
-- ============================================================================
|
||||
-- Language Detection Tests
|
||||
-- ============================================================================
|
||||
|
||||
print("\n=== Language Detection Tests ===")
|
||||
|
||||
local function get_extension(filename)
|
||||
return filename:match("%.([^%.]+)$")
|
||||
end
|
||||
|
||||
local function detect_from_shebang(first_line)
|
||||
if first_line:sub(1, 2) == "#!" then
|
||||
if first_line:find("python") then return "python" end
|
||||
if first_line:find("node") then return "javascript" end
|
||||
if first_line:find("ruby") then return "ruby" end
|
||||
if first_line:find("perl") then return "perl" end
|
||||
if first_line:find("bash") or first_line:find("/sh") then return "bash" end
|
||||
if first_line:find("lua") then return "lua" end
|
||||
if first_line:find("php") then return "php" end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
test("Detect language from .py extension", function()
|
||||
local ext = "." .. get_extension("script.py")
|
||||
assert_equal(ext_map[ext], "python")
|
||||
end)
|
||||
|
||||
test("Detect language from .lua extension", function()
|
||||
local ext = "." .. get_extension("script.lua")
|
||||
assert_equal(ext_map[ext], "lua")
|
||||
end)
|
||||
|
||||
test("Python shebang detection", function()
|
||||
assert_equal(detect_from_shebang("#!/usr/bin/env python3"), "python")
|
||||
end)
|
||||
|
||||
test("Node shebang detection", function()
|
||||
assert_equal(detect_from_shebang("#!/usr/bin/env node"), "javascript")
|
||||
end)
|
||||
|
||||
test("Lua shebang detection", function()
|
||||
assert_equal(detect_from_shebang("#!/usr/bin/env lua"), "lua")
|
||||
end)
|
||||
|
||||
test("Bash shebang detection", function()
|
||||
assert_equal(detect_from_shebang("#!/bin/bash"), "bash")
|
||||
end)
|
||||
|
||||
-- ============================================================================
|
||||
-- Argument Parsing Tests
|
||||
-- ============================================================================
|
||||
|
||||
print("\n=== Argument Parsing Tests ===")
|
||||
|
||||
local function parse_env_var(arg)
|
||||
local key, value = arg:match("^([^=]+)=(.*)$")
|
||||
return key, value
|
||||
end
|
||||
|
||||
test("Parse -e KEY=VALUE format", function()
|
||||
local key, value = parse_env_var("DEBUG=1")
|
||||
assert_equal(key, "DEBUG")
|
||||
assert_equal(value, "1")
|
||||
end)
|
||||
|
||||
test("Parse -e KEY=VALUE with equals in value", function()
|
||||
local key, value = parse_env_var("URL=https://example.com?foo=bar")
|
||||
assert_equal(key, "URL")
|
||||
assert_equal(value, "https://example.com?foo=bar")
|
||||
end)
|
||||
|
||||
test("Valid network modes", function()
|
||||
local valid_modes = { zerotrust = true, semitrusted = true }
|
||||
assert_true(valid_modes["zerotrust"])
|
||||
assert_true(valid_modes["semitrusted"])
|
||||
assert_true(not valid_modes["invalid"])
|
||||
end)
|
||||
|
||||
test("Subcommand detection", function()
|
||||
local subcommands = { session = true, service = true, key = true, restore = true }
|
||||
assert_true(subcommands["session"])
|
||||
assert_true(subcommands["service"])
|
||||
assert_true(not subcommands["script.py"])
|
||||
end)
|
||||
|
||||
-- ============================================================================
|
||||
-- File Operations Tests
|
||||
-- ============================================================================
|
||||
|
||||
print("\n=== File Operations Tests ===")
|
||||
|
||||
test("Read text file", function()
|
||||
local tmpname = os.tmpname() .. ".py"
|
||||
local f = io.open(tmpname, "w")
|
||||
f:write('print("hello world")')
|
||||
f:close()
|
||||
|
||||
f = io.open(tmpname, "r")
|
||||
local content = f:read("*a")
|
||||
f:close()
|
||||
os.remove(tmpname)
|
||||
|
||||
assert_equal(content, 'print("hello world")')
|
||||
end)
|
||||
|
||||
test("Extract file basename", function()
|
||||
local path = "/home/user/project/script.py"
|
||||
local basename = path:match("([^/]+)$")
|
||||
assert_equal(basename, "script.py")
|
||||
end)
|
||||
|
||||
test("Extract file extension", function()
|
||||
local path = "/home/user/project/script.py"
|
||||
local ext = path:match("%.([^%.]+)$")
|
||||
assert_equal(ext, "py")
|
||||
end)
|
||||
|
||||
-- ============================================================================
|
||||
-- API Constants Tests
|
||||
-- ============================================================================
|
||||
|
||||
print("\n=== API Constants Tests ===")
|
||||
|
||||
test("API base URL format", function()
|
||||
local api_base = "https://api.unsandbox.com"
|
||||
assert_true(api_base:sub(1, 8) == "https://")
|
||||
assert_contains(api_base, "unsandbox.com")
|
||||
end)
|
||||
|
||||
test("Portal base URL format", function()
|
||||
local portal_base = "https://unsandbox.com"
|
||||
assert_true(portal_base:sub(1, 8) == "https://")
|
||||
end)
|
||||
|
||||
-- ============================================================================
|
||||
-- Summary
|
||||
-- ============================================================================
|
||||
|
||||
print("\n=== Summary ===")
|
||||
print("Passed: " .. passed)
|
||||
print("Failed: " .. failed)
|
||||
print("Total: " .. (passed + failed))
|
||||
|
||||
os.exit(failed > 0 and 1 or 0)
|
||||
294
tests/unit/test_python.py
Executable file
294
tests/unit/test_python.py
Executable file
|
|
@ -0,0 +1,294 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Unit tests for un.py - tests internal functions without API calls
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import hmac
|
||||
import hashlib
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
# Add parent directory to path to import un module functions
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../..'))
|
||||
|
||||
# We need to mock the API key check before importing
|
||||
os.environ['UNSANDBOX_PUBLIC_KEY'] = 'test-public-key'
|
||||
os.environ['UNSANDBOX_SECRET_KEY'] = 'test-secret-key'
|
||||
|
||||
|
||||
class TestExtensionMapping(unittest.TestCase):
|
||||
"""Test the EXT_MAP extension to language mapping"""
|
||||
|
||||
def setUp(self):
|
||||
# Import here after env is set
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location("un", os.path.join(os.path.dirname(__file__), '../../un.py'))
|
||||
self.un = importlib.util.module_from_spec(spec)
|
||||
# Don't execute the module, just load the constants
|
||||
with open(os.path.join(os.path.dirname(__file__), '../../un.py')) as f:
|
||||
content = f.read()
|
||||
# Extract EXT_MAP
|
||||
exec(compile(content.split('def get_api_keys')[0], 'un.py', 'exec'), self.un.__dict__)
|
||||
|
||||
def test_python_extensions(self):
|
||||
self.assertEqual(self.un.EXT_MAP['.py'], 'python')
|
||||
|
||||
def test_javascript_extensions(self):
|
||||
self.assertEqual(self.un.EXT_MAP['.js'], 'javascript')
|
||||
self.assertEqual(self.un.EXT_MAP['.ts'], 'typescript')
|
||||
|
||||
def test_ruby_extension(self):
|
||||
self.assertEqual(self.un.EXT_MAP['.rb'], 'ruby')
|
||||
|
||||
def test_go_extension(self):
|
||||
self.assertEqual(self.un.EXT_MAP['.go'], 'go')
|
||||
|
||||
def test_rust_extension(self):
|
||||
self.assertEqual(self.un.EXT_MAP['.rs'], 'rust')
|
||||
|
||||
def test_c_extensions(self):
|
||||
self.assertEqual(self.un.EXT_MAP['.c'], 'c')
|
||||
self.assertEqual(self.un.EXT_MAP['.cpp'], 'cpp')
|
||||
self.assertEqual(self.un.EXT_MAP['.cc'], 'cpp')
|
||||
self.assertEqual(self.un.EXT_MAP['.cxx'], 'cpp')
|
||||
|
||||
def test_jvm_extensions(self):
|
||||
self.assertEqual(self.un.EXT_MAP['.java'], 'java')
|
||||
self.assertEqual(self.un.EXT_MAP['.kt'], 'kotlin')
|
||||
self.assertEqual(self.un.EXT_MAP['.groovy'], 'groovy')
|
||||
self.assertEqual(self.un.EXT_MAP['.scala'], 'scala')
|
||||
|
||||
def test_dotnet_extensions(self):
|
||||
self.assertEqual(self.un.EXT_MAP['.cs'], 'csharp')
|
||||
self.assertEqual(self.un.EXT_MAP['.fs'], 'fsharp')
|
||||
|
||||
def test_functional_extensions(self):
|
||||
self.assertEqual(self.un.EXT_MAP['.hs'], 'haskell')
|
||||
self.assertEqual(self.un.EXT_MAP['.ml'], 'ocaml')
|
||||
self.assertEqual(self.un.EXT_MAP['.clj'], 'clojure')
|
||||
self.assertEqual(self.un.EXT_MAP['.scm'], 'scheme')
|
||||
self.assertEqual(self.un.EXT_MAP['.lisp'], 'commonlisp')
|
||||
self.assertEqual(self.un.EXT_MAP['.erl'], 'erlang')
|
||||
self.assertEqual(self.un.EXT_MAP['.ex'], 'elixir')
|
||||
self.assertEqual(self.un.EXT_MAP['.exs'], 'elixir')
|
||||
|
||||
def test_scientific_extensions(self):
|
||||
self.assertEqual(self.un.EXT_MAP['.jl'], 'julia')
|
||||
self.assertEqual(self.un.EXT_MAP['.r'], 'r')
|
||||
self.assertEqual(self.un.EXT_MAP['.R'], 'r')
|
||||
self.assertEqual(self.un.EXT_MAP['.f90'], 'fortran')
|
||||
self.assertEqual(self.un.EXT_MAP['.f95'], 'fortran')
|
||||
|
||||
def test_exotic_extensions(self):
|
||||
self.assertEqual(self.un.EXT_MAP['.d'], 'd')
|
||||
self.assertEqual(self.un.EXT_MAP['.nim'], 'nim')
|
||||
self.assertEqual(self.un.EXT_MAP['.zig'], 'zig')
|
||||
self.assertEqual(self.un.EXT_MAP['.v'], 'v')
|
||||
self.assertEqual(self.un.EXT_MAP['.cr'], 'crystal')
|
||||
self.assertEqual(self.un.EXT_MAP['.dart'], 'dart')
|
||||
|
||||
def test_legacy_extensions(self):
|
||||
self.assertEqual(self.un.EXT_MAP['.cob'], 'cobol')
|
||||
self.assertEqual(self.un.EXT_MAP['.pro'], 'prolog')
|
||||
self.assertEqual(self.un.EXT_MAP['.forth'], 'forth')
|
||||
self.assertEqual(self.un.EXT_MAP['.4th'], 'forth')
|
||||
|
||||
def test_other_extensions(self):
|
||||
self.assertEqual(self.un.EXT_MAP['.tcl'], 'tcl')
|
||||
self.assertEqual(self.un.EXT_MAP['.raku'], 'raku')
|
||||
self.assertEqual(self.un.EXT_MAP['.m'], 'objc')
|
||||
self.assertEqual(self.un.EXT_MAP['.lua'], 'lua')
|
||||
self.assertEqual(self.un.EXT_MAP['.pl'], 'perl')
|
||||
self.assertEqual(self.un.EXT_MAP['.php'], 'php')
|
||||
self.assertEqual(self.un.EXT_MAP['.sh'], 'bash')
|
||||
|
||||
|
||||
class TestHMACSignature(unittest.TestCase):
|
||||
"""Test HMAC signature generation"""
|
||||
|
||||
def test_hmac_sha256_basic(self):
|
||||
"""Test basic HMAC-SHA256 generation"""
|
||||
secret = "test-secret-key"
|
||||
message = "1234567890:POST:/execute:{}"
|
||||
|
||||
expected = hmac.new(
|
||||
secret.encode(),
|
||||
message.encode(),
|
||||
hashlib.sha256
|
||||
).hexdigest()
|
||||
|
||||
# Verify we can generate the same signature
|
||||
actual = hmac.new(
|
||||
secret.encode(),
|
||||
message.encode(),
|
||||
hashlib.sha256
|
||||
).hexdigest()
|
||||
|
||||
self.assertEqual(expected, actual)
|
||||
self.assertEqual(len(actual), 64) # SHA256 hex is 64 chars
|
||||
|
||||
def test_hmac_different_secrets(self):
|
||||
"""Test that different secrets produce different signatures"""
|
||||
message = "1234567890:POST:/execute:{}"
|
||||
|
||||
sig1 = hmac.new(b"secret1", message.encode(), hashlib.sha256).hexdigest()
|
||||
sig2 = hmac.new(b"secret2", message.encode(), hashlib.sha256).hexdigest()
|
||||
|
||||
self.assertNotEqual(sig1, sig2)
|
||||
|
||||
def test_hmac_different_messages(self):
|
||||
"""Test that different messages produce different signatures"""
|
||||
secret = b"test-secret"
|
||||
|
||||
sig1 = hmac.new(secret, b"message1", hashlib.sha256).hexdigest()
|
||||
sig2 = hmac.new(secret, b"message2", hashlib.sha256).hexdigest()
|
||||
|
||||
self.assertNotEqual(sig1, sig2)
|
||||
|
||||
def test_signature_format(self):
|
||||
"""Test the signature input format: timestamp:METHOD:path:body"""
|
||||
timestamp = "1704067200"
|
||||
method = "POST"
|
||||
path = "/execute"
|
||||
body = '{"language":"python","code":"print(1)"}'
|
||||
|
||||
message = f"{timestamp}:{method}:{path}:{body}"
|
||||
|
||||
# Verify format: starts with timestamp, has method and path
|
||||
self.assertTrue(message.startswith(timestamp))
|
||||
self.assertIn(":POST:", message)
|
||||
self.assertIn(":/execute:", message)
|
||||
|
||||
|
||||
class TestLanguageDetection(unittest.TestCase):
|
||||
"""Test language detection from file extensions and shebangs"""
|
||||
|
||||
def test_detect_from_extension(self):
|
||||
"""Test detection from file extension"""
|
||||
# Create a temp file with .py extension
|
||||
with tempfile.NamedTemporaryFile(suffix='.py', delete=False) as f:
|
||||
f.write(b'print("hello")')
|
||||
temp_path = f.name
|
||||
|
||||
try:
|
||||
ext = os.path.splitext(temp_path)[1].lower()
|
||||
# Simulate EXT_MAP lookup
|
||||
EXT_MAP = {'.py': 'python', '.js': 'javascript', '.rb': 'ruby'}
|
||||
self.assertEqual(EXT_MAP.get(ext), 'python')
|
||||
finally:
|
||||
os.unlink(temp_path)
|
||||
|
||||
def test_detect_from_shebang_python(self):
|
||||
"""Test detection from python shebang"""
|
||||
content = "#!/usr/bin/env python3\nprint('hello')"
|
||||
first_line = content.split('\n')[0]
|
||||
|
||||
self.assertTrue(first_line.startswith('#!'))
|
||||
self.assertIn('python', first_line)
|
||||
|
||||
def test_detect_from_shebang_node(self):
|
||||
"""Test detection from node shebang"""
|
||||
content = "#!/usr/bin/env node\nconsole.log('hello')"
|
||||
first_line = content.split('\n')[0]
|
||||
|
||||
self.assertTrue(first_line.startswith('#!'))
|
||||
self.assertIn('node', first_line)
|
||||
|
||||
def test_detect_from_shebang_bash(self):
|
||||
"""Test detection from bash shebang"""
|
||||
content = "#!/bin/bash\necho hello"
|
||||
first_line = content.split('\n')[0]
|
||||
|
||||
self.assertTrue(first_line.startswith('#!'))
|
||||
self.assertTrue('bash' in first_line or '/sh' in first_line)
|
||||
|
||||
|
||||
class TestArgumentParsing(unittest.TestCase):
|
||||
"""Test command-line argument patterns"""
|
||||
|
||||
def test_env_var_format(self):
|
||||
"""Test -e KEY=VALUE parsing"""
|
||||
arg = "DEBUG=1"
|
||||
key, value = arg.split('=', 1)
|
||||
|
||||
self.assertEqual(key, "DEBUG")
|
||||
self.assertEqual(value, "1")
|
||||
|
||||
def test_env_var_with_equals_in_value(self):
|
||||
"""Test -e KEY=VALUE=WITH=EQUALS parsing"""
|
||||
arg = "URL=https://example.com?foo=bar"
|
||||
key, value = arg.split('=', 1)
|
||||
|
||||
self.assertEqual(key, "URL")
|
||||
self.assertEqual(value, "https://example.com?foo=bar")
|
||||
|
||||
def test_network_mode_values(self):
|
||||
"""Test valid network mode values"""
|
||||
valid_modes = ['zerotrust', 'semitrusted']
|
||||
|
||||
self.assertIn('zerotrust', valid_modes)
|
||||
self.assertIn('semitrusted', valid_modes)
|
||||
|
||||
def test_subcommand_detection(self):
|
||||
"""Test subcommand detection"""
|
||||
args = ['session', '--shell', 'python3']
|
||||
subcommand = args[0] if args and args[0] in ['session', 'service', 'key', 'restore'] else None
|
||||
|
||||
self.assertEqual(subcommand, 'session')
|
||||
|
||||
|
||||
class TestFileOperations(unittest.TestCase):
|
||||
"""Test file reading and encoding"""
|
||||
|
||||
def test_read_text_file(self):
|
||||
"""Test reading a text file"""
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
|
||||
f.write('print("hello world")')
|
||||
temp_path = f.name
|
||||
|
||||
try:
|
||||
with open(temp_path, 'r') as f:
|
||||
content = f.read()
|
||||
self.assertEqual(content, 'print("hello world")')
|
||||
finally:
|
||||
os.unlink(temp_path)
|
||||
|
||||
def test_base64_encoding(self):
|
||||
"""Test base64 encoding for file contents"""
|
||||
import base64
|
||||
|
||||
content = b'print("hello world")'
|
||||
encoded = base64.b64encode(content).decode('utf-8')
|
||||
decoded = base64.b64decode(encoded)
|
||||
|
||||
self.assertEqual(decoded, content)
|
||||
|
||||
def test_file_basename(self):
|
||||
"""Test extracting file basename"""
|
||||
path = "/home/user/project/script.py"
|
||||
basename = os.path.basename(path)
|
||||
|
||||
self.assertEqual(basename, "script.py")
|
||||
|
||||
|
||||
class TestAPIConstants(unittest.TestCase):
|
||||
"""Test API-related constants"""
|
||||
|
||||
def test_api_base_url(self):
|
||||
"""Test API base URL format"""
|
||||
API_BASE = "https://api.unsandbox.com"
|
||||
|
||||
self.assertTrue(API_BASE.startswith('https://'))
|
||||
self.assertIn('unsandbox.com', API_BASE)
|
||||
|
||||
def test_portal_base_url(self):
|
||||
"""Test portal base URL format"""
|
||||
PORTAL_BASE = "https://unsandbox.com"
|
||||
|
||||
self.assertTrue(PORTAL_BASE.startswith('https://'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
334
tests/unit/test_ruby.rb
Executable file
334
tests/unit/test_ruby.rb
Executable file
|
|
@ -0,0 +1,334 @@
|
|||
#!/usr/bin/env ruby
|
||||
# Unit tests for un.rb - tests internal functions without API calls
|
||||
|
||||
require 'openssl'
|
||||
require 'base64'
|
||||
require 'tempfile'
|
||||
require 'fileutils'
|
||||
|
||||
# Test counters
|
||||
$passed = 0
|
||||
$failed = 0
|
||||
|
||||
def test(name)
|
||||
begin
|
||||
yield
|
||||
puts " ✓ #{name}"
|
||||
$passed += 1
|
||||
rescue => e
|
||||
puts " ✗ #{name}"
|
||||
puts " #{e.message}"
|
||||
$failed += 1
|
||||
end
|
||||
end
|
||||
|
||||
def assert_equal(actual, expected, msg = '')
|
||||
raise "Expected '#{expected}' but got '#{actual}' #{msg}" unless actual == expected
|
||||
end
|
||||
|
||||
def assert_not_equal(a, b)
|
||||
raise "Expected values to be different but both were '#{a}'" if a == b
|
||||
end
|
||||
|
||||
def assert_includes(str, substr)
|
||||
raise "Expected '#{str}' to include '#{substr}'" unless str.include?(substr)
|
||||
end
|
||||
|
||||
def assert_true(val)
|
||||
raise "Expected true but got #{val}" unless val
|
||||
end
|
||||
|
||||
# Extension mapping (copied from un.rb)
|
||||
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'
|
||||
}.freeze
|
||||
|
||||
# ============================================================================
|
||||
# Extension Mapping Tests
|
||||
# ============================================================================
|
||||
|
||||
puts "\n=== Extension Mapping Tests ==="
|
||||
|
||||
test('Python extension maps correctly') do
|
||||
assert_equal EXT_MAP['.py'], 'python'
|
||||
end
|
||||
|
||||
test('JavaScript extensions map correctly') do
|
||||
assert_equal EXT_MAP['.js'], 'javascript'
|
||||
assert_equal EXT_MAP['.ts'], 'typescript'
|
||||
end
|
||||
|
||||
test('Ruby extension maps correctly') do
|
||||
assert_equal EXT_MAP['.rb'], 'ruby'
|
||||
end
|
||||
|
||||
test('Go extension maps correctly') do
|
||||
assert_equal EXT_MAP['.go'], 'go'
|
||||
end
|
||||
|
||||
test('Rust extension maps correctly') do
|
||||
assert_equal EXT_MAP['.rs'], 'rust'
|
||||
end
|
||||
|
||||
test('C/C++ extensions map correctly') do
|
||||
assert_equal EXT_MAP['.c'], 'c'
|
||||
assert_equal EXT_MAP['.cpp'], 'cpp'
|
||||
assert_equal EXT_MAP['.cc'], 'cpp'
|
||||
assert_equal EXT_MAP['.cxx'], 'cpp'
|
||||
end
|
||||
|
||||
test('JVM extensions map correctly') do
|
||||
assert_equal EXT_MAP['.java'], 'java'
|
||||
assert_equal EXT_MAP['.kt'], 'kotlin'
|
||||
assert_equal EXT_MAP['.groovy'], 'groovy'
|
||||
assert_equal EXT_MAP['.scala'], 'scala'
|
||||
end
|
||||
|
||||
test('.NET extensions map correctly') do
|
||||
assert_equal EXT_MAP['.cs'], 'csharp'
|
||||
assert_equal EXT_MAP['.fs'], 'fsharp'
|
||||
end
|
||||
|
||||
test('Functional language extensions map correctly') do
|
||||
assert_equal EXT_MAP['.hs'], 'haskell'
|
||||
assert_equal EXT_MAP['.ml'], 'ocaml'
|
||||
assert_equal EXT_MAP['.clj'], 'clojure'
|
||||
assert_equal EXT_MAP['.scm'], 'scheme'
|
||||
assert_equal EXT_MAP['.lisp'], 'commonlisp'
|
||||
assert_equal EXT_MAP['.erl'], 'erlang'
|
||||
assert_equal EXT_MAP['.ex'], 'elixir'
|
||||
end
|
||||
|
||||
test('Scientific extensions map correctly') do
|
||||
assert_equal EXT_MAP['.jl'], 'julia'
|
||||
assert_equal EXT_MAP['.r'], 'r'
|
||||
assert_equal EXT_MAP['.f90'], 'fortran'
|
||||
end
|
||||
|
||||
test('Exotic extensions map correctly') do
|
||||
assert_equal EXT_MAP['.d'], 'd'
|
||||
assert_equal EXT_MAP['.nim'], 'nim'
|
||||
assert_equal EXT_MAP['.zig'], 'zig'
|
||||
assert_equal EXT_MAP['.v'], 'v'
|
||||
assert_equal EXT_MAP['.cr'], 'crystal'
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# HMAC Signature Tests
|
||||
# ============================================================================
|
||||
|
||||
puts "\n=== HMAC Signature Tests ==="
|
||||
|
||||
test('HMAC-SHA256 generates 64 character hex string') do
|
||||
secret = 'test-secret-key'
|
||||
message = '1234567890:POST:/execute:{}'
|
||||
|
||||
signature = OpenSSL::HMAC.hexdigest('SHA256', secret, message)
|
||||
|
||||
assert_equal signature.length, 64
|
||||
end
|
||||
|
||||
test('Same input produces same signature') do
|
||||
secret = 'test-secret-key'
|
||||
message = '1234567890:POST:/execute:{}'
|
||||
|
||||
sig1 = OpenSSL::HMAC.hexdigest('SHA256', secret, message)
|
||||
sig2 = OpenSSL::HMAC.hexdigest('SHA256', secret, message)
|
||||
|
||||
assert_equal sig1, sig2
|
||||
end
|
||||
|
||||
test('Different secrets produce different signatures') do
|
||||
message = '1234567890:POST:/execute:{}'
|
||||
|
||||
sig1 = OpenSSL::HMAC.hexdigest('SHA256', 'secret1', message)
|
||||
sig2 = OpenSSL::HMAC.hexdigest('SHA256', 'secret2', message)
|
||||
|
||||
assert_not_equal sig1, sig2
|
||||
end
|
||||
|
||||
test('Different messages produce different signatures') do
|
||||
secret = 'test-secret'
|
||||
|
||||
sig1 = OpenSSL::HMAC.hexdigest('SHA256', secret, 'message1')
|
||||
sig2 = OpenSSL::HMAC.hexdigest('SHA256', secret, 'message2')
|
||||
|
||||
assert_not_equal sig1, sig2
|
||||
end
|
||||
|
||||
test('Signature format is timestamp:METHOD:path:body') do
|
||||
timestamp = '1704067200'
|
||||
method = 'POST'
|
||||
endpoint = '/execute'
|
||||
body = '{"language":"python","code":"print(1)"}'
|
||||
|
||||
message = "#{timestamp}:#{method}:#{endpoint}:#{body}"
|
||||
|
||||
# Verify format: starts with timestamp, has method and path
|
||||
assert_true message.start_with?(timestamp)
|
||||
assert_includes message, ':POST:'
|
||||
assert_includes message, ':/execute:'
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Language Detection Tests
|
||||
# ============================================================================
|
||||
|
||||
puts "\n=== Language Detection Tests ==="
|
||||
|
||||
test('Detect language from .py extension') do
|
||||
filename = 'script.py'
|
||||
ext = File.extname(filename).downcase
|
||||
assert_equal EXT_MAP[ext], 'python'
|
||||
end
|
||||
|
||||
test('Detect language from .rb extension') do
|
||||
filename = 'app.rb'
|
||||
ext = File.extname(filename).downcase
|
||||
assert_equal EXT_MAP[ext], 'ruby'
|
||||
end
|
||||
|
||||
test('Python shebang detection') do
|
||||
content = "#!/usr/bin/env python3\nprint('hello')"
|
||||
first_line = content.lines.first
|
||||
|
||||
assert_true first_line.start_with?('#!')
|
||||
assert_includes first_line, 'python'
|
||||
end
|
||||
|
||||
test('Ruby shebang detection') do
|
||||
content = "#!/usr/bin/env ruby\nputs 'hello'"
|
||||
first_line = content.lines.first
|
||||
|
||||
assert_true first_line.start_with?('#!')
|
||||
assert_includes first_line, 'ruby'
|
||||
end
|
||||
|
||||
test('Bash shebang detection') do
|
||||
content = "#!/bin/bash\necho hello"
|
||||
first_line = content.lines.first
|
||||
|
||||
assert_true first_line.start_with?('#!')
|
||||
assert_true first_line.include?('bash') || first_line.include?('/sh')
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Argument Parsing Tests
|
||||
# ============================================================================
|
||||
|
||||
puts "\n=== Argument Parsing Tests ==="
|
||||
|
||||
test('Parse -e KEY=VALUE format') do
|
||||
arg = 'DEBUG=1'
|
||||
key, value = arg.split('=', 2)
|
||||
|
||||
assert_equal key, 'DEBUG'
|
||||
assert_equal value, '1'
|
||||
end
|
||||
|
||||
test('Parse -e KEY=VALUE with equals in value') do
|
||||
arg = 'URL=https://example.com?foo=bar'
|
||||
key, value = arg.split('=', 2)
|
||||
|
||||
assert_equal key, 'URL'
|
||||
assert_equal value, 'https://example.com?foo=bar'
|
||||
end
|
||||
|
||||
test('Valid network modes') do
|
||||
valid_modes = %w[zerotrust semitrusted]
|
||||
|
||||
assert_true valid_modes.include?('zerotrust')
|
||||
assert_true valid_modes.include?('semitrusted')
|
||||
assert_true !valid_modes.include?('invalid')
|
||||
end
|
||||
|
||||
test('Subcommand detection') do
|
||||
args = %w[session --shell python3]
|
||||
subcommands = %w[session service key restore]
|
||||
subcommand = subcommands.include?(args[0]) ? args[0] : nil
|
||||
|
||||
assert_equal subcommand, 'session'
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# File Operations Tests
|
||||
# ============================================================================
|
||||
|
||||
puts "\n=== File Operations Tests ==="
|
||||
|
||||
test('Read text file') do
|
||||
file = Tempfile.new(['test', '.py'])
|
||||
file.write('print("hello world")')
|
||||
file.close
|
||||
|
||||
begin
|
||||
content = File.read(file.path)
|
||||
assert_equal content, 'print("hello world")'
|
||||
ensure
|
||||
file.unlink
|
||||
end
|
||||
end
|
||||
|
||||
test('Base64 encoding/decoding') do
|
||||
content = 'print("hello world")'
|
||||
encoded = Base64.strict_encode64(content)
|
||||
decoded = Base64.strict_decode64(encoded)
|
||||
|
||||
assert_equal decoded, content
|
||||
end
|
||||
|
||||
test('Extract file basename') do
|
||||
filepath = '/home/user/project/script.py'
|
||||
basename = File.basename(filepath)
|
||||
|
||||
assert_equal basename, 'script.py'
|
||||
end
|
||||
|
||||
test('Extract file extension') do
|
||||
filepath = '/home/user/project/script.py'
|
||||
ext = File.extname(filepath)
|
||||
|
||||
assert_equal ext, '.py'
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# API Constants Tests
|
||||
# ============================================================================
|
||||
|
||||
puts "\n=== API Constants Tests ==="
|
||||
|
||||
test('API base URL format') do
|
||||
api_base = 'https://api.unsandbox.com'
|
||||
|
||||
assert_true api_base.start_with?('https://')
|
||||
assert_includes api_base, 'unsandbox.com'
|
||||
end
|
||||
|
||||
test('Portal base URL format') do
|
||||
portal_base = 'https://unsandbox.com'
|
||||
|
||||
assert_true portal_base.start_with?('https://')
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Summary
|
||||
# ============================================================================
|
||||
|
||||
puts "\n=== Summary ==="
|
||||
puts "Passed: #{$passed}"
|
||||
puts "Failed: #{$failed}"
|
||||
puts "Total: #{$passed + $failed}"
|
||||
|
||||
exit($failed > 0 ? 1 : 0)
|
||||
Loading…
Add table
Add a link
Reference in a new issue