334 lines
9.2 KiB
Ruby
Executable file
334 lines
9.2 KiB
Ruby
Executable file
#!/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)
|