Add per-client Makefiles for C, Python, and Go with: - CLI mode: Tests --help, arg parsing, syntax validation - Library mode: Unit tests, import verification - Integration mode: API contract validation (with credentials) - Functional mode: Real-world scenario tests C client: - 22 library tests (SHA-256, HMAC-SHA256, detect_language) - Full unsandbox.c implementation with examples Python client (sync + async): - Delegates to sync/ and async/ subdirectories - pytest-based test suites with coverage - Examples for concurrent execution, streaming Go client: - Delegates to sync/ and async/ subdirectories - go test integration with vet and fmt Also update detect-changes.sh to detect changes in both root-level un.* files AND clients/ directory.
288 lines
8 KiB
Python
288 lines
8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Verification script for unsandbox Python SDK (Synchronous)
|
|
|
|
This script verifies that the SDK is properly configured and working.
|
|
Run from the sync/ directory: python3 verify_sdk.py
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
from pathlib import Path
|
|
|
|
# Add src to path
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
|
|
|
|
from un import (
|
|
execute_code,
|
|
execute_async,
|
|
get_job,
|
|
wait_for_job,
|
|
cancel_job,
|
|
list_jobs,
|
|
get_languages,
|
|
detect_language,
|
|
CredentialsError,
|
|
_resolve_credentials,
|
|
_sign_request,
|
|
_load_languages_cache,
|
|
_save_languages_cache,
|
|
)
|
|
|
|
|
|
def test_language_detection():
|
|
"""Test language detection"""
|
|
print("Testing language detection...")
|
|
tests = [
|
|
("hello.py", "python"),
|
|
("app.js", "javascript"),
|
|
("main.go", "go"),
|
|
("script.rs", "rust"),
|
|
("main.cpp", "cpp"),
|
|
("unknown.xyz", None),
|
|
]
|
|
|
|
for filename, expected in tests:
|
|
result = detect_language(filename)
|
|
status = "✓" if result == expected else "✗"
|
|
print(f" {status} detect_language('{filename}') = {result}")
|
|
if result != expected:
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def test_request_signing():
|
|
"""Test request signing"""
|
|
print("\nTesting request signing...")
|
|
|
|
sig1 = _sign_request("secret", 1234567890, "POST", "/execute", '{"code":"test"}')
|
|
sig2 = _sign_request("secret", 1234567890, "POST", "/execute", '{"code":"test"}')
|
|
|
|
# Test deterministic
|
|
status = "✓" if sig1 == sig2 else "✗"
|
|
print(f" {status} Signatures are deterministic: {sig1 == sig2}")
|
|
|
|
# Test format
|
|
status = "✓" if len(sig1) == 64 else "✗"
|
|
print(f" {status} Signature is 64 hex chars: {len(sig1)} chars")
|
|
|
|
# Test different secrets produce different sigs
|
|
sig_diff = _sign_request("different", 1234567890, "POST", "/execute", '{"code":"test"}')
|
|
status = "✓" if sig1 != sig_diff else "✗"
|
|
print(f" {status} Different secrets produce different signatures: {sig1 != sig_diff}")
|
|
|
|
return len(sig1) == 64 and sig1 == sig2 and sig1 != sig_diff
|
|
|
|
|
|
def test_credentials():
|
|
"""Test credential resolution"""
|
|
print("\nTesting credential resolution...")
|
|
|
|
# Test 1: Function arguments
|
|
try:
|
|
pk, sk = _resolve_credentials("func_pk", "func_sk")
|
|
status = "✓" if pk == "func_pk" and sk == "func_sk" else "✗"
|
|
print(f" {status} Function arguments: {pk == 'func_pk' and sk == 'func_sk'}")
|
|
except Exception as e:
|
|
print(f" ✗ Function arguments failed: {e}")
|
|
return False
|
|
|
|
# Test 2: Can resolve credentials (from env or config)
|
|
try:
|
|
pk, sk = _resolve_credentials()
|
|
print(f" ✓ Credentials resolved: {pk[:20]}... / {sk[:20]}...")
|
|
return True
|
|
except CredentialsError:
|
|
print(f" ✗ No credentials found (this is OK for testing)")
|
|
print(f" Set UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY environment variables")
|
|
return True # Not a test failure, just missing credentials
|
|
|
|
|
|
def test_caching():
|
|
"""Test languages cache"""
|
|
print("\nTesting caching...")
|
|
|
|
import tempfile
|
|
import json
|
|
import time
|
|
from unittest.mock import patch
|
|
|
|
try:
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
cache_path = Path(tmpdir) / "languages.json"
|
|
|
|
# Save cache
|
|
languages = ["python", "javascript", "go"]
|
|
with patch("un._get_languages_cache_path", return_value=cache_path):
|
|
_save_languages_cache(languages)
|
|
print(f" ✓ Cache saved")
|
|
|
|
# Load cache
|
|
loaded = _load_languages_cache()
|
|
if loaded == languages:
|
|
print(f" ✓ Cache loaded correctly: {loaded}")
|
|
else:
|
|
print(f" ✗ Cache mismatch: {loaded}")
|
|
return False
|
|
|
|
# Test expiration
|
|
old_time = time.time() - 7200 # 2 hours old
|
|
os.utime(cache_path, (old_time, old_time))
|
|
expired = _load_languages_cache()
|
|
if expired is None:
|
|
print(f" ✓ Cache correctly expired")
|
|
else:
|
|
print(f" ✗ Cache should be expired: {expired}")
|
|
return False
|
|
|
|
return True
|
|
except Exception as e:
|
|
print(f" ✗ Cache test failed: {e}")
|
|
import traceback
|
|
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
|
|
def test_imports():
|
|
"""Test all public imports"""
|
|
print("\nTesting imports...")
|
|
|
|
functions = [
|
|
"execute_code",
|
|
"execute_async",
|
|
"get_job",
|
|
"wait_for_job",
|
|
"cancel_job",
|
|
"list_jobs",
|
|
"get_languages",
|
|
"detect_language",
|
|
"session_snapshot",
|
|
"service_snapshot",
|
|
"list_snapshots",
|
|
"restore_snapshot",
|
|
"delete_snapshot",
|
|
"CredentialsError",
|
|
]
|
|
|
|
print(f" ✓ Imported {len(functions)} public functions and classes:")
|
|
for func in functions:
|
|
print(f" - {func}")
|
|
|
|
return True
|
|
|
|
|
|
def test_package_structure():
|
|
"""Verify package structure"""
|
|
print("\nVerifying package structure...")
|
|
|
|
required_files = [
|
|
"src/__init__.py",
|
|
"src/un.py",
|
|
"setup.py",
|
|
"README.md",
|
|
"USAGE.md",
|
|
"LICENSE",
|
|
"MANIFEST.in",
|
|
"pytest.ini",
|
|
"tests/__init__.py",
|
|
"tests/test_credentials.py",
|
|
"tests/test_language_detection.py",
|
|
"tests/test_signatures.py",
|
|
"tests/test_caching.py",
|
|
"tests/test_integration_mock.py",
|
|
"tests/test_real_world_scenarios.py",
|
|
]
|
|
|
|
base_dir = Path(__file__).parent
|
|
all_exist = True
|
|
|
|
for filename in required_files:
|
|
filepath = base_dir / filename
|
|
status = "✓" if filepath.exists() else "✗"
|
|
print(f" {status} {filename}")
|
|
if not filepath.exists():
|
|
all_exist = False
|
|
|
|
return all_exist
|
|
|
|
|
|
def test_example_structure():
|
|
"""Verify examples exist"""
|
|
print("\nVerifying examples...")
|
|
|
|
example_files = [
|
|
"examples/hello_world.py",
|
|
"examples/fibonacci.py",
|
|
"examples/hello_world_client.py",
|
|
"examples/fibonacci_client.py",
|
|
"examples/json_processing.py",
|
|
"examples/http_request.py",
|
|
"examples/file_operations.py",
|
|
]
|
|
|
|
base_dir = Path(__file__).parent
|
|
all_exist = True
|
|
|
|
for filename in example_files:
|
|
filepath = base_dir / filename
|
|
status = "✓" if filepath.exists() else "✗"
|
|
print(f" {status} {filename}")
|
|
if not filepath.exists():
|
|
all_exist = False
|
|
|
|
return all_exist
|
|
|
|
|
|
def main():
|
|
"""Run all verification tests"""
|
|
print("=" * 60)
|
|
print("Unsandbox Python SDK (Sync) - Verification")
|
|
print("=" * 60)
|
|
|
|
tests = [
|
|
("Package Structure", test_package_structure),
|
|
("Imports", test_imports),
|
|
("Language Detection", test_language_detection),
|
|
("Request Signing", test_request_signing),
|
|
("Credentials", test_credentials),
|
|
("Caching", test_caching),
|
|
("Examples", test_example_structure),
|
|
]
|
|
|
|
results = []
|
|
for test_name, test_func in tests:
|
|
try:
|
|
result = test_func()
|
|
results.append((test_name, result))
|
|
except Exception as e:
|
|
print(f"\n✗ {test_name} failed with exception: {e}")
|
|
import traceback
|
|
|
|
traceback.print_exc()
|
|
results.append((test_name, False))
|
|
|
|
print("\n" + "=" * 60)
|
|
print("Summary")
|
|
print("=" * 60)
|
|
|
|
passed = sum(1 for _, result in results if result)
|
|
total = len(results)
|
|
|
|
for test_name, result in results:
|
|
status = "✓" if result else "✗"
|
|
print(f"{status} {test_name}")
|
|
|
|
print(f"\nPassed: {passed}/{total}")
|
|
|
|
if passed == total:
|
|
print("\n✓ All verification tests passed!")
|
|
return 0
|
|
else:
|
|
print(f"\n✗ {total - passed} test(s) failed")
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|