# 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: 1. **Library functions** - The core API that can be imported/required by other code 2. **CLI functionality** - Command-line interface for direct code execution 3. **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):** ```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):** ```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:** ```python 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 ```python # 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 ```python # 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 ```python # Get supported languages languages = languages(cache_ttl: int = 3600) -> list[str] # Generate image from text prompt result = image(prompt: str, model: str = None, size: str = "1024x1024") -> dict ``` ### Snapshot Functions ```python # Create session snapshot snap = session_snapshot(session_id: str) -> dict # Create service snapshot snap = service_snapshot(service_id: str) -> dict # List all snapshots snaps = list_snapshots() -> dict # Restore from snapshot result = restore_snapshot(snapshot_id: str) -> dict # Delete snapshot delete_snapshot(snapshot_id: str) -> dict ``` ### Credential Functions ```python # 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 ```python # 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: 1. **Function arguments** - Highest priority (explicit parameter) 2. **Environment variables** - `UNSANDBOX_PUBLIC_KEY` and `UNSANDBOX_SECRET_KEY` 3. **Home directory config** - `~/.unsandbox/accounts.csv` 4. **Local directory config** - `./accounts.csv` in current working directory 5. **Error** - Raise error if no credentials found **Example (Python):** ```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):** ```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:** ```python 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```python # BAD - doesn't test library functions result = subprocess.run(['python', 'un.py', 'code.py']) ``` ### ✅ Correct: Testing both library AND CLI ```python # 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 ```python # BAD - hardcoded credentials def execute(language, code): pk = 'unsb-pk-xxxx' # WRONG! sk = 'unsb-sk-xxxx' # WRONG! ``` ### ✅ Correct: Flexible credential loading ```python # 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 ```python # BAD - always calls API def languages(): return requests.get('/languages').json() ``` ### ✅ Correct: Proper caching with TTL ```python # 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: 1. **Add unit tests** to `test_sdk_library.py`: ```python def test_ruby_sdk(self): """Test Ruby SDK library functions""" # Import, test credentials, HMAC, etc. ``` 2. **Add to test runner** `run_sdk_tests.sh`: ```bash # 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 ``` 3. **Create test file** `tests/test_un_ruby.rb`: ```ruby 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: 1. SDK file (un.py, un.js, etc.) is in parent directory 2. Correct extension (.py for Python, .js for JavaScript) 3. No syntax errors in SDK file ### Test: HMAC Signature Fails ``` FAIL: HMAC-SHA256 signature - invalid signature length ``` **Fix:** Check: 1. Using `hmac` library correctly 2. Using SHA256 hash algorithm 3. Converting result to hex string (64 chars) 4. Secret key is string/bytes, not encoded yet ### Test: API Integration Fails ``` FAIL: execute() function - status 401 ``` **Fix:** 1. Check `UNSANDBOX_API_KEY` is set: `echo $UNSANDBOX_API_KEY` 2. Verify API key is valid and not expired 3. Check credentials are loaded correctly: `_get_credentials()` 4. Verify HMAC signature generation is working ### Test: Language Not Found ``` ⚠ Python 3 not found ``` **Fix:** Install the required interpreter: ```bash # 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: 1. ✅ **All unit tests pass** - Library functions work correctly 2. ✅ **All integration tests pass** - API communication works 3. ✅ **All functional tests pass** - CLI still works 4. ✅ **Examples execute** - Python/JS examples work with new SDK 5. ✅ **Credentials load** - All 4 credential sources work 6. ✅ **Caching works** - Languages cache is created and used 7. ✅ **CLI still works** - Original CLI functionality preserved 8. ✅ **Documentation updated** - Examples reflect new SDK usage ## Example: Complete Ruby SDK Refactoring + Testing ### Step 1: Check Ruby is available ```bash ruby --version ``` ### Step 2: Refactor un.rb with library exports ```ruby # 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 ```python 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 ```ruby #!/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 ```bash ./tests/run_sdk_tests.sh --languages ruby # Or manually ruby tests/test_un_ruby.rb ``` ### Step 6: Validate CLI still works ```bash # 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 ```bash 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: 1. Check the error message - it usually indicates the problem 2. Review the example SDKs (un.py, un.js) for reference 3. Look at existing test files for patterns 4. Run individual tests to isolate issues Good luck with your SDK refactoring!