- test_sdk_library.py: Python test framework for validating library functions
- run_sdk_tests.sh: Master test runner for all language SDKs
- SDK_TESTING_GUIDE.md: Detailed guide on test structure and patterns
- AGENT_REFACTORING_INSTRUCTIONS.md: Step-by-step refactoring walkthrough
- REFACTORING_CHECKLIST.md: Comprehensive checklist for SDK refactoring
Agents can now validate their refactoring work independently with:
python3 tests/test_sdk_library.py --languages {language}
./tests/run_sdk_tests.sh --languages {language}
Framework tests:
- Unit tests: credential loading, HMAC signing, function signatures
- Integration tests: real API calls (requires UNSANDBOX_API_KEY)
- Functional tests: end-to-end CLI execution
This enables parallel refactoring with automatic validation.
15 KiB
SDK Testing Guide for Refactoring
This guide explains how to test UN SDK library implementations during refactoring. It enables agents to validate their own work independently.
Overview
Each SDK (un.py, un.js, un.go, etc.) must provide:
- Library functions - The core API that can be imported/required by other code
- CLI functionality - Command-line interface for direct code execution
- Tests - Unit, integration, and functional tests to validate behavior
Test Categories
1. Unit Tests (No API Key Required)
Focus on library function behavior without external API calls:
- Credential loading - Functions correctly parse credentials from env vars, files, and arguments
- HMAC signature generation - Signatures match the expected format (64 hex chars)
- Cache checking - Languages cache exists and can be read
- Extension detection - File extensions correctly map to language identifiers
- Function signatures - All expected functions exist and are callable
- Error handling - Invalid inputs produce appropriate errors
Example Unit Test (Python):
def test_hmac_signature():
"""Test HMAC-SHA256 signature generation"""
sig = un._sign_request(
'test-secret-key',
'1704067200', # timestamp
'POST', # method
'/execute', # endpoint
'{}' # body
)
assert len(sig) == 64, f"Invalid signature length: {len(sig)}"
assert all(c in '0123456789abcdef' for c in sig), "Invalid hex characters"
2. Integration Tests (Requires API Key)
Test actual API communication with real infrastructure:
- execute() - Synchronous code execution and result retrieval
- executeAsync() - Asynchronous job submission and status polling
- wait() - Job polling with exponential backoff
- languages() - Language list retrieval with caching
- get_job() - Job status retrieval
- cancel_job() - Job cancellation
Example Integration Test (Python):
def test_execute_integration():
"""Test execute function with real API"""
result = un.execute(
'python',
'print("hello world")',
network_mode='zerotrust'
)
assert result['exit_code'] == 0
assert 'hello world' in result['stdout']
3. Functional Tests (Requires API Key)
End-to-end workflow validation:
- CLI execution - Run code file through CLI interface
- Output parsing - Correctly display results to user
- Error handling - Handle API errors gracefully
- Input/output files - Process input files and write outputs
Example Functional Test:
def test_cli_execution():
"""Test CLI end-to-end"""
import subprocess
result = subprocess.run(
['python', 'un.py', 'test_script.py'],
capture_output=True,
text=True
)
assert result.returncode == 0
assert 'expected output' in result.stdout
SDK Library Requirements
Every SDK must implement these functions:
Core Execution Functions
# Synchronous execution
result = execute(
language: str,
code: str,
network_mode: str = 'zerotrust',
ttl: int = 60,
env: dict = None,
files: list = None,
input_file: str = None
) -> dict
# Asynchronous execution
job = executeAsync(
language: str,
code: str,
network_mode: str = 'zerotrust',
ttl: int = 60,
env: dict = None,
files: list = None
) -> dict
# Wait for job completion
result = wait(job_id: str, timeout: int = 3600) -> dict
Job Management Functions
# Get job status
job = get_job(job_id: str) -> dict
# Cancel job
cancel_job(job_id: str) -> dict
# List jobs
jobs = list_jobs(limit: int = 100) -> list[dict]
Utility Functions
# Get supported languages
languages = languages(cache_ttl: int = 3600) -> list[str]
# Get language info
info = language_info(language: str) -> dict
Credential Functions
# Load credentials from multiple sources
public_key, secret_key = _get_credentials(
public_key: str = None,
secret_key: str = None
) -> tuple[str, str]
# Load accounts.csv
accounts = _load_accounts_csv(path: str = None) -> list[tuple[str, str]]
Signature Functions
# Generate HMAC-SHA256 signature
signature = _sign_request(
secret_key: str,
timestamp: str,
method: str,
endpoint: str,
body: str
) -> str
Credential Priority System
SDKs must check credentials in this order:
- Function arguments - Highest priority (explicit parameter)
- Environment variables -
UNSANDBOX_PUBLIC_KEYandUNSANDBOX_SECRET_KEY - Home directory config -
~/.unsandbox/accounts.csv - Local directory config -
./accounts.csvin current working directory - Error - Raise error if no credentials found
Example (Python):
def _get_credentials(public_key=None, secret_key=None):
"""Load credentials with 4-tier fallback system"""
# Tier 1: Function arguments
if public_key and secret_key:
return public_key, secret_key
# Tier 2: Environment variables
pk = os.environ.get('UNSANDBOX_PUBLIC_KEY')
sk = os.environ.get('UNSANDBOX_SECRET_KEY')
if pk and sk:
return pk, sk
# Tier 3: Home directory
home_file = Path.home() / '.unsandbox' / 'accounts.csv'
if home_file.exists():
accounts = _load_accounts_csv(str(home_file))
if accounts:
return accounts[0] # Use first account
# Tier 4: Local directory
local_file = Path('./accounts.csv')
if local_file.exists():
accounts = _load_accounts_csv(str(local_file))
if accounts:
return accounts[0]
raise AuthenticationError("No credentials found")
Caching Requirements
Languages Cache
- Location:
~/.unsandbox/languages.json - TTL: 1 hour (3600 seconds)
- Format: JSON array of language strings
- Update: Only update if cache is older than TTL AND API call succeeds
Example (Python):
def languages(cache_ttl=3600):
"""Get supported languages with 1-hour cache"""
cache_path = Path.home() / '.unsandbox' / 'languages.json'
# Check if cache exists and is fresh
if cache_path.exists():
age = time.time() - cache_path.stat().st_mtime
if age < cache_ttl:
return json.load(open(cache_path))
# Fetch from API
response = _make_request('GET', '/languages')
langs = response['languages']
# Update cache (create directory if needed)
cache_path.parent.mkdir(parents=True, exist_ok=True)
cache_path.write_text(json.dumps(langs))
return langs
HMAC Signature Format
All API requests must include proper HMAC signatures:
Signature Input:
{timestamp}:{method}:{endpoint}:{body}
Example:
1704067200:POST:/execute:{"language":"python","code":"print(1)"}
Signature Calculation:
import hmac
import hashlib
signature = hmac.new(
secret_key.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
Request Headers:
Authorization: Bearer {public_key}
X-Timestamp: {timestamp}
X-Signature: {signature}
Content-Type: application/json
Testing Workflow for Agents
1. Before Refactoring
# Create a checkpoint of the original SDK
cd /home/fox/git/un-inception
git stash
# Run baseline tests
./tests/run_sdk_tests.sh --unit
2. During Refactoring
# As you implement library functions, add tests
# Edit tests/test_sdk_library.py to add tests for your language
# Periodically validate unit tests pass
python3 tests/test_sdk_library.py --languages your_language
3. After Refactoring
# Run full test suite
./tests/run_sdk_tests.sh --unit
# If API key available, test integration
export UNSANDBOX_API_KEY='your-key'
./tests/run_sdk_tests.sh --integration
# Test CLI still works
python un.py examples/hello.py
node un.js examples/hello.js
go run un.go examples/hello.go
4. Validation Before Commit
# Run all tests
./tests/run_sdk_tests.sh --all
# Verify both library AND CLI work
node un.js -s javascript 'console.log("hello from cli")'
python un.py -s python 'print("hello from cli")'
go run un.go -s go 'fmt.Println("hello from cli")'
# Commit when all tests pass
git add un.py un.js un.go
git commit -m "Refactor SDK: add library exports + HMAC auth + caching"
Common Testing Mistakes to Avoid
❌ Wrong: Only testing CLI functionality
# BAD - doesn't test library functions
result = subprocess.run(['python', 'un.py', 'code.py'])
✅ Correct: Testing both library AND CLI
# GOOD - test library functions exist and work
from un import execute, executeAsync
# AND test CLI
result = subprocess.run(['python', 'un.py', 'code.py'])
❌ Wrong: Ignoring credential loading
# BAD - hardcoded credentials
def execute(language, code):
pk = 'unsb-pk-xxxx' # WRONG!
sk = 'unsb-sk-xxxx' # WRONG!
✅ Correct: Flexible credential loading
# GOOD - respect credential sources
def execute(language, code, public_key=None, secret_key=None):
pk, sk = _get_credentials(public_key, secret_key)
# Now use pk, sk
❌ Wrong: Ignoring cache TTL
# BAD - always calls API
def languages():
return requests.get('/languages').json()
✅ Correct: Proper caching with TTL
# GOOD - cache with 1-hour TTL
def languages(cache_ttl=3600):
cache_file = Path.home() / '.unsandbox' / 'languages.json'
if cache_file.exists():
age = time.time() - cache_file.stat().st_mtime
if age < cache_ttl:
return json.load(open(cache_file))
# Fetch, cache, return
Adding Tests for a New Language
When refactoring a new SDK:
- Add unit tests to
test_sdk_library.py:
def test_ruby_sdk(self):
"""Test Ruby SDK library functions"""
# Import, test credentials, HMAC, etc.
- Add to test runner
run_sdk_tests.sh:
# Run Ruby SDK tests
echo -e "${BLUE}Running Ruby SDK tests...${RESET}"
if command -v ruby &> /dev/null; then
ruby "$SCRIPT_DIR/test_un_ruby.rb" 2>&1 || true
else
echo -e "${YELLOW}⚠ Ruby not found${RESET}"
fi
- Create test file
tests/test_un_ruby.rb:
require_relative '../un.rb'
# Test HMAC signature
sig = Un._sign_request('test-sk', '1704067200', 'POST', '/execute', '{}')
if sig.length == 64 && sig.match?(/^[0-9a-f]+$/)
puts "✓ PASS: HMAC-SHA256 signature generation"
else
puts "✗ FAIL: HMAC-SHA256 signature generation"
end
# Test execute function exists
if Un.respond_to?(:execute)
puts "✓ PASS: execute function exists"
else
puts "✗ FAIL: execute function exists"
end
Troubleshooting Tests
Test: Import Error
Error: Cannot import un module
Fix: Ensure:
- SDK file (un.py, un.js, etc.) is in parent directory
- Correct extension (.py for Python, .js for JavaScript)
- No syntax errors in SDK file
Test: HMAC Signature Fails
FAIL: HMAC-SHA256 signature - invalid signature length
Fix: Check:
- Using
hmaclibrary correctly - Using SHA256 hash algorithm
- Converting result to hex string (64 chars)
- Secret key is string/bytes, not encoded yet
Test: API Integration Fails
FAIL: execute() function - status 401
Fix:
- Check
UNSANDBOX_API_KEYis set:echo $UNSANDBOX_API_KEY - Verify API key is valid and not expired
- Check credentials are loaded correctly:
_get_credentials() - Verify HMAC signature generation is working
Test: Language Not Found
⚠ Python 3 not found
Fix: Install the required interpreter:
# Ubuntu/Debian
sudo apt-get install python3 nodejs golang-go ruby perl php
# macOS
brew install python node go ruby perl php
Success Criteria
An SDK is ready for production when:
- ✅ All unit tests pass - Library functions work correctly
- ✅ All integration tests pass - API communication works
- ✅ All functional tests pass - CLI still works
- ✅ Examples execute - Python/JS examples work with new SDK
- ✅ Credentials load - All 4 credential sources work
- ✅ Caching works - Languages cache is created and used
- ✅ CLI still works - Original CLI functionality preserved
- ✅ Documentation updated - Examples reflect new SDK usage
Example: Complete Ruby SDK Refactoring + Testing
Step 1: Check Ruby is available
ruby --version
Step 2: Refactor un.rb with library exports
# un.rb - Ruby SDK with library exports
require 'net/http'
require 'json'
require 'openssl'
require 'base64'
require 'time'
module Un
def self.execute(language, code, opts = {})
# Implementation
end
def self.executeAsync(language, code, opts = {})
# Implementation
end
private
def self._sign_request(secret, timestamp, method, endpoint, body)
message = "#{timestamp}:#{method}:#{endpoint}:#{body}"
OpenSSL::HMAC.hexdigest('SHA256', secret, message)
end
end
# CLI entry point
if __FILE__ == $0
# Parse args and call Un.execute
end
Step 3: Add unit tests to test_sdk_library.py
def test_ruby_sdk(self):
"""Test Ruby SDK library functions"""
language = 'ruby'
try:
# Create test script
test_code = '''
require_relative '../un.rb'
sig = Un._sign_request('test-sk', '1704067200', 'POST', '/execute', '{}')
if sig.length == 64
puts "✓ PASS: HMAC signature"
else
puts "✗ FAIL: HMAC signature"
end
'''
# Run test and parse output
except Exception as e:
self.results.add_result(language, 'unit', 'SDK import', False, str(e))
Step 4: Create test_un_ruby.rb
#!/usr/bin/env ruby
require_relative '../un.rb'
puts "Testing Ruby SDK..."
# Test 1: HMAC signature
sig = Un._sign_request('test-sk', '1704067200', 'POST', '/execute', '{}')
if sig && sig.length == 64 && sig.match?(/^[0-9a-f]+$/)
puts "✓ PASS: HMAC-SHA256 signature"
else
puts "✗ FAIL: HMAC-SHA256 signature"
end
# Test 2: execute function
if Un.respond_to?(:execute)
puts "✓ PASS: execute function exists"
else
puts "✗ FAIL: execute function exists"
end
Step 5: Run tests
./tests/run_sdk_tests.sh --languages ruby
# Or manually
ruby tests/test_un_ruby.rb
Step 6: Validate CLI still works
# Test CLI with inline code
ruby un.rb -s ruby 'puts "hello from ruby"'
# Test CLI with file
echo 'puts "hello from file"' > test.rb
ruby un.rb test.rb
Step 7: Commit when all tests pass
git add un.rb tests/test_un_ruby.rb tests/test_sdk_library.py
git commit -m "Refactor Ruby SDK: add library exports + HMAC auth + caching
- Implement execute, executeAsync, wait, get_job, cancel_job, list_jobs
- Add 4-tier credential system (args > env > home > local)
- Add 1-hour cache for languages list
- Implement HMAC-SHA256 request signing
- Preserve original CLI functionality
- All unit tests passing"
Questions?
If tests fail or you need clarification:
- Check the error message - it usually indicates the problem
- Review the example SDKs (un.py, un.js) for reference
- Look at existing test files for patterns
- Run individual tests to isolate issues
Good luck with your SDK refactoring!