un-inception/clients/python/sync/verify_sdk.py
russell@unturf.com 1e01d09883 feat: per-client Makefile infrastructure for 4-mode testing
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.
2026-01-15 16:39:56 -05:00

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())