diff --git a/REFACTORING_CHECKLIST.md b/REFACTORING_CHECKLIST.md new file mode 100644 index 0000000..adfa990 --- /dev/null +++ b/REFACTORING_CHECKLIST.md @@ -0,0 +1,262 @@ +# SDK Refactoring Checklist + +Use this checklist when refactoring a UN SDK to ensure nothing is missed. + +## Pre-Refactoring + +- [ ] Read `tests/AGENT_REFACTORING_INSTRUCTIONS.md` +- [ ] Study Python SDK (`un.py`) as reference +- [ ] Study JavaScript SDK (`un.js`) as reference +- [ ] Create test file `tests/test_un_{language}.{ext}` +- [ ] Run baseline tests: `python3 tests/test_sdk_library.py --languages {lang}` + +## Core Library Functions + +### Execution Functions +- [ ] `execute(language, code, opts)` - Sync execution +- [ ] `executeAsync(language, code, opts)` - Async execution +- [ ] `run(file, opts)` - Run file sync +- [ ] `runAsync(file, opts)` - Run file async + +### Job Management +- [ ] `wait(job_id, timeout)` - Poll for completion +- [ ] `getJob(job_id)` - Get job status +- [ ] `cancelJob(job_id)` - Cancel running job +- [ ] `listJobs(limit)` - List active jobs + +### Utilities +- [ ] `languages(cache_ttl)` - Get supported languages +- [ ] `languageInfo(language)` - Get language details +- [ ] `detectLanguage(filename)` - Detect from extension +- [ ] `image(code, format)` - Generate images + +## Credential System (4-tier) + +- [ ] Load from function arguments (highest priority) +- [ ] Load from env vars: `UNSANDBOX_PUBLIC_KEY` + `UNSANDBOX_SECRET_KEY` +- [ ] Load from `~/.unsandbox/accounts.csv` +- [ ] Load from `./accounts.csv` (local directory) +- [ ] Raise error if no credentials found +- [ ] Test all 4 credential sources work + +## HMAC Signature + +- [ ] Implement `_sign_request(secret, timestamp, method, endpoint, body)` +- [ ] Generate signature: `HMAC-SHA256(secret, "{timestamp}:{method}:{endpoint}:{body}")` +- [ ] Signature format: 64 lowercase hexadecimal characters +- [ ] Include headers in requests: + - [ ] `Authorization: Bearer {public_key}` + - [ ] `X-Timestamp: {unix_timestamp}` + - [ ] `X-Signature: {signature}` + - [ ] `Content-Type: application/json` +- [ ] Test signature generation produces correct format + +## Languages Cache + +- [ ] Create cache directory: `~/.unsandbox/` +- [ ] Cache location: `~/.unsandbox/languages.json` +- [ ] Cache TTL: 3600 seconds (1 hour) +- [ ] Check cache exists AND is fresh before API call +- [ ] Only update cache on successful API response +- [ ] Create directory if needed before writing +- [ ] Test cache is used (don't call API twice within 1 hour) + +## Error Handling + +Define exception classes: +- [ ] `UnsandboxError` - Base exception +- [ ] `AuthenticationError` - Invalid credentials +- [ ] `ExecutionError` - Code execution failed +- [ ] `APIError` - API communication error +- [ ] `TimeoutError` - Job polling timeout + +Implement proper error handling: +- [ ] Handle network errors gracefully +- [ ] Handle invalid credentials +- [ ] Handle API errors (4xx, 5xx) +- [ ] Handle malformed responses +- [ ] Handle job timeout scenarios + +## Code Quality + +### Docstrings (Language-Appropriate) +- [ ] Python: `""" """` triple quotes +- [ ] JavaScript: `/** */` JSDoc format with @param @returns +- [ ] Go: `//` line comments before functions +- [ ] Ruby: `#` line comments before methods +- [ ] PHP: `/** */` doc blocks +- [ ] Java: `/** */` Javadoc format +- [ ] C/C++: `// //` or `/* */` comments +- [ ] All public functions documented +- [ ] All parameters documented +- [ ] Return types documented +- [ ] Examples in docstrings + +### Code Style +- [ ] Follow language conventions +- [ ] Use language-idiomatic patterns +- [ ] Consistent naming (snake_case, camelCase, PascalCase as appropriate) +- [ ] Proper error handling throughout +- [ ] No hardcoded credentials +- [ ] No debugging print statements +- [ ] No commented-out code + +### License Header +- [ ] Full permacomputer public domain notice (35+ lines) +- [ ] Placed at very top of file +- [ ] Followed by blank line +- [ ] All license text preserved exactly + +## CLI Functionality + +- [ ] Original CLI behavior preserved +- [ ] Can execute code files: `un {file}` +- [ ] Can execute inline code: `un -s language 'code'` +- [ ] Proper usage/help output +- [ ] Correct exit codes +- [ ] Error messages to stderr +- [ ] Output to stdout +- [ ] Support for all original flags/options + +### CLI Entry Point +- [ ] Python: `if __name__ == '__main__':` +- [ ] JavaScript: `if (require.main === module):` +- [ ] Go: Separate main() function +- [ ] Ruby: `if __FILE__ == $0:` +- [ ] Other languages: Language-appropriate + +## Testing + +### Unit Tests +- [ ] Create `tests/test_un_{language}.{ext}` +- [ ] Test credential loading (all 4 sources) +- [ ] Test HMAC signature generation +- [ ] Test function existence and signatures +- [ ] Test error handling for invalid inputs +- [ ] Tests pass: `python3 tests/test_sdk_library.py --languages {lang}` + +### Manual Testing +- [ ] Can import/require library: `import un` or `require('./un')` +- [ ] Can call each function without errors +- [ ] Credentials load correctly +- [ ] HMAC signatures generate correctly +- [ ] Cache is created and used +- [ ] CLI still works: `un.py test.py` or `node un.js test.js` + +### Integration Testing (if API key available) +- [ ] `execute()` works with real API +- [ ] `executeAsync()` works with real API +- [ ] `wait()` polls correctly +- [ ] Examples execute successfully + +## Documentation + +### Example Files +- [ ] Update `unsandbox.com/priv/static/docs/examples/{language}/execute.txt` +- [ ] Update `unsandbox.com/priv/static/docs/examples/{language}/execute_async.txt` +- [ ] Show library import/require +- [ ] Show credential setup (env vars) +- [ ] Show proper HMAC authentication +- [ ] Show error handling +- [ ] Code runs successfully + +### Comments in Code +- [ ] Explain complex logic +- [ ] Document public API +- [ ] Note any language-specific workarounds +- [ ] Explain credential priority system +- [ ] Explain cache invalidation + +## Git Workflow + +- [ ] Create feature branch (optional): `git checkout -b refactor/{language}` +- [ ] Implement and test incrementally +- [ ] Commit with clear message explaining changes +- [ ] Include test file in commit +- [ ] Run all tests before committing +- [ ] Push to main (or PR if using branches) + +### Commit Message Template +``` +Refactor {Language} SDK: add library exports + HMAC auth + caching + +- Implement execute, executeAsync, wait, getJob, cancelJob, listJobs +- 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 +- Language-specific docstrings +- Full test coverage +``` + +## Final Validation + +Before declaring complete: + +- [ ] `git status` is clean (all changes committed) +- [ ] Unit tests pass: `python3 tests/test_sdk_library.py --languages {lang}` +- [ ] Master test runner passes: `./tests/run_sdk_tests.sh --languages {lang}` +- [ ] CLI still works: `un.{ext} test.{ext}` +- [ ] Library imports work: `from un import execute` or `const un = require('./un')` +- [ ] Examples in docs can be copied and executed +- [ ] No syntax errors in any files +- [ ] No hardcoded credentials +- [ ] No debug code or print statements +- [ ] Git history is clean and meaningful + +## Special Cases + +### Compiled Languages (C, Go, Rust, etc.) +- [ ] Build works without errors +- [ ] No build artifacts committed +- [ ] Can be run after compilation +- [ ] Tests include compilation step +- [ ] Documentation mentions compilation requirement + +### JVM Languages (Java, Kotlin, Scala, etc.) +- [ ] No .class or compiled files committed +- [ ] Build files present if needed +- [ ] Tests work with build system +- [ ] Documentation mentions build tool + +### Functional Languages (Haskell, OCaml, etc.) +- [ ] Pure functions for calculations +- [ ] Monads for I/O and side effects +- [ ] Tests use language-specific patterns +- [ ] Documentation explains functional patterns + +### Web Languages (PHP, JSP, etc.) +- [ ] Can run as CLI (not just web server) +- [ ] No web server dependency for CLI mode +- [ ] Tests work without web server + +## Estimated Language Groupings + +**Fast (< 2 hours)** +- Ruby, Perl, PHP, Lua, TCL, Shell/Bash, Deno + +**Medium (2-4 hours)** +- Go, TypeScript, Crystal, Dart, Elixir, Scala, Kotlin + +**Slower (4+ hours)** +- Rust (borrow checker learning curve), C++, Java, Fortran, COBOL + +## Help & Resources + +- **AGENT_REFACTORING_INSTRUCTIONS.md** - Detailed walkthrough +- **SDK_TESTING_GUIDE.md** - Testing patterns and examples +- **un.py** - Python reference implementation (canonical) +- **un.js** - JavaScript reference implementation +- **tests/test_sdk_library.py** - Python test framework +- **tests/run_sdk_tests.sh** - Master test runner +- **tests/test_un_{lang}.{ext}** - Language-specific test examples + +## Success = Complete Testing Pass + +Your SDK refactoring is done when: + +``` +✓ 8/8 passed (0 failed) +``` + +All unit tests pass, no failures, all functions tested. diff --git a/tests/AGENT_REFACTORING_INSTRUCTIONS.md b/tests/AGENT_REFACTORING_INSTRUCTIONS.md new file mode 100644 index 0000000..312a5f6 --- /dev/null +++ b/tests/AGENT_REFACTORING_INSTRUCTIONS.md @@ -0,0 +1,377 @@ +# SDK Refactoring Instructions for Agents + +This document explains how to refactor UN SDK implementations with proper testing and validation. + +## Quick Start + +### Step 1: Understand the Reference Implementations +Study these complete, working SDK implementations first: +- **Python**: `/home/fox/git/un-inception/un.py` (DONE - use as reference) +- **JavaScript**: `/home/fox/git/un-inception/un.js` (DONE - use as reference) +- **Go**: Check if exists; if incomplete, use Python/JS as pattern + +### Step 2: Apply the Pattern to Your Language +Every refactored SDK needs: + +1. **Library functions** (can be imported/required) + ``` + execute(language, code, opts) + executeAsync(language, code, opts) + run(file, opts) + runAsync(file, opts) + wait(job_id) + getJob(job_id) + cancelJob(job_id) + listJobs() + languages() + detectLanguage(filename) + image(code, output_format) + ``` + +2. **Credential system** (4-tier with fallback) + ``` + Priority: args > env vars > ~/.unsandbox/accounts.csv > ./accounts.csv + ``` + +3. **HMAC-SHA256 request signing** + ``` + signature = HMAC(secret, "timestamp:METHOD:endpoint:body") + ``` + +4. **1-hour cache** for `/languages` API endpoint + ``` + ~/.unsandbox/languages.json with TTL check + ``` + +5. **CLI functionality** (preserve original behavior) + ``` + Command-line interface for executing code files + ``` + +6. **Proper docstrings** (language-appropriate style) + ``` + Python: """ """ + JavaScript: /** */ + Go: // line comments + Ruby: # line comments + PHP: /** */ + Etc. + ``` + +7. **Complete license header** (permacomputer public domain) + ``` + Must include full 35+ line license notice + ``` + +### Step 3: Test Your Work + +Use the provided test framework to validate: + +```bash +# Run unit tests (no API key required) +cd /home/fox/git/un-inception +python3 tests/test_sdk_library.py --languages YOUR_LANGUAGE + +# Or run the master test runner +./tests/run_sdk_tests.sh --languages YOUR_LANGUAGE +``` + +Expected output: +``` +SDK LIBRARY TEST SUMMARY +======================================== +✓ your_language 3/ 4 passed (1 skipped) +======================================== +Total: 3 passed, 0 failed, 1 skipped +``` + +### Step 4: Validate Examples Still Work + +After refactoring, ensure the example files execute properly: + +```bash +# Set credentials (if available) +export UNSANDBOX_API_KEY='your-test-key' + +# Test CLI still works +python un.py examples/hello.py +node un.js examples/hello.js +ruby un.rb examples/hello.rb +# etc. + +# Test library imports work +python3 -c "import un; result = un.execute('python', 'print(1)')" +node -e "const un = require('./un.js'); un.execute('javascript', 'console.log(1)')" +``` + +### Step 5: Update Documentation Examples + +Once your SDK is refactored, update the example files: + +``` +/home/fox/git/unsandbox.com/priv/static/docs/examples/{language}/execute.txt +/home/fox/git/unsandbox.com/priv/static/docs/examples/{language}/execute_async.txt +``` + +Pattern to follow (from Python/JavaScript): +- Show SDK library import/require +- Demonstrate credential setup +- Show execute() call with proper options +- Show executeAsync() + wait() pattern +- Include error handling + +### Step 6: Commit Your Work + +```bash +git add un.{ext} tests/test_un_{lang}.{ext} +git commit -m "Refactor {Language} SDK: add library exports + HMAC auth + caching + +- Implement execute, executeAsync, wait, getJob, cancelJob, listJobs +- Add 4-tier credential system (args > env > home > local) +- Add 1-hour cache for languages list in ~/.unsandbox/ +- Implement HMAC-SHA256 request signing +- Preserve original CLI functionality +- Proper docstrings for {Language} +- Full test coverage with unit + integration tests" +``` + +## Key Implementation Details + +### Credential Loading (4-tier system) + +Priority order (stop at first match): + +1. **Function arguments** - explicit parameters to execute() + ```python + execute('python', code, public_key='...', secret_key='...') + ``` + +2. **Environment variables** + ```bash + export UNSANDBOX_PUBLIC_KEY='unsb-pk-xxxx' + export UNSANDBOX_SECRET_KEY='unsb-sk-xxxx' + ``` + +3. **Home directory** (default location) + ``` + ~/.unsandbox/accounts.csv + Format: public_key,secret_key (one per line) + ``` + +4. **Local directory** (current working dir) + ``` + ./accounts.csv + Format: public_key,secret_key (one per line) + ``` + +### HMAC Signature Format + +Every API request must include: + +``` +Authorization: Bearer {public_key} +X-Timestamp: {unix_timestamp} +X-Signature: {hmac_sha256} +``` + +Where signature is: +``` +HMAC-SHA256( + secret_key, + "{timestamp}:{METHOD}:{endpoint}:{json_body}" +) +``` + +Example with Python: +```python +import hmac +import hashlib +import json +import time + +secret = 'unsb-sk-...' +timestamp = str(int(time.time())) +method = 'POST' +endpoint = '/execute' +body = json.dumps({'language': 'python', 'code': 'print(1)'}) + +message = f"{timestamp}:{method}:{endpoint}:{body}" +signature = hmac.new( + secret.encode(), + message.encode(), + hashlib.sha256 +).hexdigest() +``` + +### Languages Cache (1-hour TTL) + +Implement caching to avoid repeated API calls: + +1. **Cache location**: `~/.unsandbox/languages.json` +2. **TTL**: 3600 seconds (1 hour) +3. **Check logic**: + - If file exists AND age < TTL → return cached + - Otherwise → fetch from API AND update cache + +Example with Python: +```python +from pathlib import Path +import json +import time + +def languages(cache_ttl=3600): + cache_path = Path.home() / '.unsandbox' / 'languages.json' + + # Check cache + if cache_path.exists(): + age = time.time() - cache_path.stat().st_mtime + if age < cache_ttl: + return json.loads(cache_path.read_text()) + + # Fetch from API + result = api_request('GET', '/languages') + langs = result['languages'] + + # Update cache + cache_path.parent.mkdir(parents=True, exist_ok=True) + cache_path.write_text(json.dumps(langs)) + + return langs +``` + +## Common Patterns + +### Exception Hierarchy +Every SDK should define: +```python +class UnsandboxError(Exception): pass +class AuthenticationError(UnsandboxError): pass +class ExecutionError(UnsandboxError): pass +class APIError(UnsandboxError): pass +class TimeoutError(UnsandboxError): pass +``` + +### Exponential Backoff Polling +When waiting for async jobs: +``` +Initial delays (ms): [300, 450, 700, 900, 650, 1600, 2000, ...] +Pattern: Increases generally but with variation to avoid thundering herd +Max polls: Usually around 120 (2+ hours for patience) +``` + +### CLI Entry Point +Preserve CLI functionality: +```python +# Python: at end of file +if __name__ == '__main__': + import sys + from pathlib import Path + # Parse args, call execute/run, handle errors +``` + +```javascript +// JavaScript: at end of file +if (require.main === module) { + cliMain().catch(e => { + console.error(e.message); + process.exit(1); + }); +} +``` + +```go +// Go: in main() +if len(os.Args) < 2 { + // Show usage +} +// Parse args, call Execute/Run, handle errors +``` + +## Testing Checklist + +Before committing, verify ALL of these: + +- [ ] **Unit tests pass** - Library functions work +- [ ] **Library functions exist** - execute, executeAsync, wait, etc. +- [ ] **Credentials load** - All 4 sources (args, env, home, local) +- [ ] **HMAC signature** - Correct format and length (64 hex chars) +- [ ] **Cache works** - languages.json created and used +- [ ] **CLI still works** - Command-line execution preserved +- [ ] **Error handling** - Appropriate exceptions for errors +- [ ] **Docstrings** - Language-appropriate comment style +- [ ] **License header** - Full permacomputer notice at top +- [ ] **No hardcoded credentials** - All removed +- [ ] **Examples updated** - execute.txt and execute_async.txt +- [ ] **Git status clean** - All files committed + +## Troubleshooting + +### Problem: "Cannot import/require un module" +**Solution:** +1. Check file is in correct location: `/home/fox/git/un-inception/un.{ext}` +2. Check filename matches language (un.py, un.js, un.go, etc.) +3. Check for syntax errors: try running file directly + +### Problem: "HMAC signature invalid" +**Solution:** +1. Verify message format: `"{timestamp}:{METHOD}:{endpoint}:{body}"` +2. Verify secret key encoding: bytes, not string +3. Verify hexdigest: should be 64 lowercase hex characters +4. Verify HMAC algorithm: must be SHA256, not SHA1 or other + +### Problem: "Credentials not found" +**Solution:** +1. Check env vars: `echo $UNSANDBOX_PUBLIC_KEY` +2. Check files exist: `ls ~/.unsandbox/accounts.csv` +3. Check file format: one credential pair per line +4. Check loading order: function args, env, home, local + +### Problem: "Cache not working" +**Solution:** +1. Check directory exists: `mkdir -p ~/.unsandbox/` +2. Check file created: `ls -la ~/.unsandbox/languages.json` +3. Check TTL logic: age < 3600 seconds +4. Check mtime: `stat ~/.unsandbox/languages.json` + +## Examples of Refactored SDKs + +To understand the pattern better, study: + +1. **Python (un.py)** - Complete, well-commented implementation + - Shows all library functions + - Demonstrates credential system + - Shows HMAC signing + - Shows caching + - Preserves CLI + +2. **JavaScript (un.js)** - Node.js implementation + - Shows async/await pattern + - Shows Client class wrapper + - Shows error handling + - Shows function validation + +3. **Go** (partially done) - if available + - Shows goroutine patterns + - Shows error handling + - Shows HTTP client usage + +## Questions or Blockers? + +If you're stuck: + +1. **Check the guide**: `/home/fox/git/un-inception/tests/SDK_TESTING_GUIDE.md` +2. **Review Python SDK**: `un.py` is the canonical reference +3. **Look at examples**: `priv/static/docs/examples/python/` and `javascript/` +4. **Run tests**: `python3 tests/test_sdk_library.py --languages YOUR_LANGUAGE` +5. **Check git history**: `git log --oneline un.py` to see refactoring commits + +## Final Notes + +- **Don't rush** - Quality over speed. Tests will catch errors. +- **Test frequently** - Run tests after each function you implement +- **Copy patterns** - Use Python/JavaScript as templates exactly +- **Preserve functionality** - Original CLI must still work +- **Document clearly** - Use language-appropriate docstrings +- **Commit atomically** - Each language in separate commit + +Good luck! The test framework is there to help you validate your work. Use it! diff --git a/tests/SDK_TESTING_GUIDE.md b/tests/SDK_TESTING_GUIDE.md new file mode 100644 index 0000000..81ba776 --- /dev/null +++ b/tests/SDK_TESTING_GUIDE.md @@ -0,0 +1,605 @@ +# 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] + +# Get language info +info = language_info(language: 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! diff --git a/tests/run_sdk_tests.sh b/tests/run_sdk_tests.sh new file mode 100755 index 0000000..440b81a --- /dev/null +++ b/tests/run_sdk_tests.sh @@ -0,0 +1,265 @@ +#!/bin/bash +# +# Master test runner for SDK library validation +# Allows agents to validate their SDK refactoring work +# +# Usage: +# ./run_sdk_tests.sh # Run all SDK tests +# ./run_sdk_tests.sh --unit # Run only unit tests +# ./run_sdk_tests.sh --integration # Run only integration tests +# ./run_sdk_tests.sh --languages python javascript go +# UNSANDBOX_API_KEY=xxx ./run_sdk_tests.sh --all +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" + +# Color codes +GREEN='\033[92m' +RED='\033[91m' +YELLOW='\033[93m' +BLUE='\033[94m' +RESET='\033[0m' + +# Test configuration +TEST_TYPE="all" # unit, integration, all +LANGUAGES="" +API_KEY="${UNSANDBOX_API_KEY:-}" +API_URL="${UNSANDBOX_API_URL:-https://api.unsandbox.com}" + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --unit) + TEST_TYPE="unit" + shift + ;; + --integration) + TEST_TYPE="integration" + shift + ;; + --functional) + TEST_TYPE="functional" + shift + ;; + --languages) + shift + LANGUAGES="$@" + break + ;; + --api-key) + API_KEY="$2" + shift 2 + ;; + --api-url) + API_URL="$2" + shift 2 + ;; + --help|-h) + echo "UN SDK Test Runner" + echo "" + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --unit Run only unit tests (no API key required)" + echo " --integration Run only integration tests (requires API key)" + echo " --functional Run only functional tests (requires API key)" + echo " --languages L1 L2 Test specific languages (default: all)" + echo " --api-key KEY Set API key for integration tests" + echo " --api-url URL Set API URL (default: https://api.unsandbox.com)" + echo " --help Show this help message" + echo "" + echo "Environment Variables:" + echo " UNSANDBOX_API_KEY API key for integration tests" + echo " UNSANDBOX_API_URL API URL for testing" + echo "" + exit 0 + ;; + *) + LANGUAGES="$LANGUAGES $1" + shift + ;; + esac +done + +# Export for subprocesses +export UNSANDBOX_API_KEY="$API_KEY" +export UNSANDBOX_API_URL="$API_URL" + +# Print header +echo -e "${BLUE}" +echo "==========================================" +echo "UN SDK Library Test Runner" +echo "==========================================" +echo -e "${RESET}" + +echo "Test Type: $TEST_TYPE" +if [ -z "$API_KEY" ]; then + echo "Integration Tests: ${YELLOW}SKIPPED${RESET} (no API key)" +else + echo "Integration Tests: ${GREEN}ENABLED${RESET}" +fi +echo "API URL: $API_URL" +echo "" + +# Run Python SDK tests +echo -e "${BLUE}Running Python SDK tests...${RESET}" +if command -v python3 &> /dev/null; then + if [ "$TEST_TYPE" = "all" ] || [ "$TEST_TYPE" = "unit" ]; then + python3 "$SCRIPT_DIR/test_sdk_library.py" --languages python 2>&1 || true + fi +else + echo -e "${YELLOW}⚠ Python 3 not found${RESET}" +fi + +# Run JavaScript SDK tests +echo -e "${BLUE}Running JavaScript SDK tests...${RESET}" +if command -v node &> /dev/null; then + # Create test script for JavaScript + JS_TEST_SCRIPT="$SCRIPT_DIR/test_sdk_library.js" + cat > "$JS_TEST_SCRIPT" << 'EOJS' +#!/usr/bin/env node +/** + * JavaScript SDK library tests + * Tests verify SDK functions work correctly + */ + +const un = require('../un.js'); +const crypto = require('crypto'); +const fs = require('fs'); + +class TestResults { + constructor() { + this.passed = 0; + this.failed = 0; + this.skipped = 0; + } + + pass(name) { + console.log(`✓ PASS: ${name}`); + this.passed++; + } + + fail(name, error) { + console.log(`✗ FAIL: ${name} - ${error}`); + this.failed++; + } + + skip(name, reason) { + console.log(`⊘ SKIP: ${name} - ${reason}`); + this.skipped++; + } + + summary() { + console.log(`\n✓ ${this.passed} passed, ✗ ${this.failed} failed, ⊘ ${this.skipped} skipped\n`); + return this.failed === 0; + } +} + +const results = new TestResults(); + +// Test 1: HMAC signature generation +try { + const sig = un._signRequest('test-sk', '1704067200', 'POST', '/execute', '{}'); + if (sig && sig.length === 64 && /^[0-9a-f]+$/.test(sig)) { + results.pass('HMAC-SHA256 signature generation'); + } else { + results.fail('HMAC-SHA256 signature generation', `Invalid signature: ${sig}`); + } +} catch (e) { + results.fail('HMAC-SHA256 signature generation', e.message); +} + +// Test 2: Client class exists +try { + if (typeof un.Client === 'function') { + results.pass('Client class exists'); + } else { + results.fail('Client class exists', 'Not a function'); + } +} catch (e) { + results.fail('Client class exists', e.message); +} + +// Test 3: execute function exists +try { + if (typeof un.execute === 'function') { + results.pass('execute function exists'); + } else { + results.fail('execute function exists', 'Not a function'); + } +} catch (e) { + results.fail('execute function exists', e.message); +} + +// Test 4: executeAsync function exists +try { + if (typeof un.executeAsync === 'function') { + results.pass('executeAsync function exists'); + } else { + results.fail('executeAsync function exists', 'Not a function'); + } +} catch (e) { + results.fail('executeAsync function exists', e.message); +} + +// Print summary +const success = results.summary(); +process.exit(success ? 0 : 1); +EOJS + + chmod +x "$JS_TEST_SCRIPT" 2>/dev/null || true + node "$JS_TEST_SCRIPT" 2>&1 || true + rm -f "$JS_TEST_SCRIPT" +else + echo -e "${YELLOW}⚠ Node.js not found${RESET}" +fi + +# Run Go SDK tests +echo -e "${BLUE}Running Go SDK tests...${RESET}" +if command -v go &> /dev/null; then + GO_TEST_FILE="$SCRIPT_DIR/test_go_sdk.go" + cat > "$GO_TEST_FILE" << 'EOGO' +package main + +import ( + "fmt" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" +) + +func main() { + // Test 1: HMAC signature + key := []byte("test-sk") + message := "1704067200:POST:/execute:{}" + h := hmac.New(sha256.New, key) + h.Write([]byte(message)) + sig := hex.EncodeToString(h.Sum(nil)) + + if len(sig) == 64 { + fmt.Println("✓ PASS: HMAC-SHA256 signature generation") + } else { + fmt.Printf("✗ FAIL: HMAC-SHA256 signature generation - invalid length %d\n", len(sig)) + } +} +EOGO + + go run "$GO_TEST_FILE" 2>&1 || true + rm -f "$GO_TEST_FILE" +else + echo -e "${YELLOW}⚠ Go not found${RESET}" +fi + +# Print final summary +echo "" +echo -e "${BLUE}==========================================" +echo "Test run complete" +echo "==========================================${RESET}" +echo "" +echo "To run integration tests with real API calls:" +echo " export UNSANDBOX_API_KEY='your-key-here'" +echo " $0 --integration" +echo "" diff --git a/tests/test_sdk_library.py b/tests/test_sdk_library.py new file mode 100755 index 0000000..06079c7 --- /dev/null +++ b/tests/test_sdk_library.py @@ -0,0 +1,365 @@ +#!/usr/bin/env python3 +""" +Comprehensive test suite for UN SDK library functions across all languages. + +Tests validate: +1. Unit tests - Library function signatures and basic behavior +2. Integration tests - API communication with real credentials +3. Functional tests - End-to-end execution with input/output handling + +This allows agents to validate their SDK refactoring work independently. +""" + +import os +import sys +import json +import hmac +import hashlib +import tempfile +import subprocess +import time +from pathlib import Path +from typing import Dict, List, Tuple, Optional, Any + +# ANSI color codes +GREEN = '\033[92m' +RED = '\033[91m' +YELLOW = '\033[93m' +BLUE = '\033[94m' +RESET = '\033[0m' + + +class SDKTestResults: + """Track test results across all languages and test types.""" + + def __init__(self): + self.results = {} # language -> {'unit': [...], 'integration': [...], 'functional': [...]} + self.summary = {'passed': 0, 'failed': 0, 'skipped': 0} + + def add_result(self, language: str, test_type: str, name: str, passed: bool, message: str = ""): + """Record test result.""" + if language not in self.results: + self.results[language] = {'unit': [], 'integration': [], 'functional': []} + + self.results[language][test_type].append({ + 'name': name, + 'passed': passed, + 'message': message + }) + + if passed: + self.summary['passed'] += 1 + else: + self.summary['failed'] += 1 + + def skip_result(self, language: str, test_type: str, name: str, reason: str): + """Record skipped test.""" + if language not in self.results: + self.results[language] = {'unit': [], 'integration': [], 'functional': []} + + self.results[language][test_type].append({ + 'name': name, + 'passed': None, # None indicates skipped + 'message': reason + }) + + self.summary['skipped'] += 1 + + def print_summary(self): + """Print test summary.""" + print(f"\n{'='*70}") + print(f"SDK LIBRARY TEST SUMMARY") + print(f"{'='*70}") + + for language, test_types in sorted(self.results.items()): + lang_passed = sum(1 for t in test_types.values() for r in t if r['passed'] is True) + lang_failed = sum(1 for t in test_types.values() for r in t if r['passed'] is False) + lang_skipped = sum(1 for t in test_types.values() for r in t if r['passed'] is None) + lang_total = lang_passed + lang_failed + lang_skipped + + status = f"{GREEN}✓{RESET}" if lang_failed == 0 else f"{RED}✗{RESET}" + print(f"{status} {language:15} {lang_passed:3}/{lang_total:3} passed" + + (f" ({lang_skipped} skipped)" if lang_skipped > 0 else "")) + + # Show failed tests + for test_type, tests in test_types.items(): + for test in tests: + if test['passed'] is False: + print(f" {RED}✗{RESET} {test['name']}: {test['message'][:60]}") + + print(f"{'='*70}") + print(f"Total: {GREEN}{self.summary['passed']} passed{RESET}, " + + f"{RED}{self.summary['failed']} failed{RESET}, " + + f"{YELLOW}{self.summary['skipped']} skipped{RESET}") + print(f"{'='*70}\n") + + return self.summary['failed'] == 0 + + +class SDKLibraryTester: + """Test SDK library implementations.""" + + def __init__(self, sdk_dir: str = "/home/fox/git/un-inception"): + self.sdk_dir = Path(sdk_dir) + self.results = SDKTestResults() + self.api_key = os.environ.get('UNSANDBOX_API_KEY', '') + self.api_url = os.environ.get('UNSANDBOX_API_URL', 'https://api.unsandbox.com') + + def test_python_sdk(self): + """Test Python SDK library functions.""" + language = 'python' + + try: + # Import un module + sys.path.insert(0, str(self.sdk_dir)) + import un + + # Unit test: credential loading + try: + # Test loading from env vars + os.environ['UNSANDBOX_PUBLIC_KEY'] = 'unsb-pk-test-1234' + os.environ['UNSANDBOX_SECRET_KEY'] = 'unsb-sk-test-5678' + + pk, sk = un._get_credentials() + + if pk == 'unsb-pk-test-1234' and sk == 'unsb-sk-test-5678': + self.results.add_result(language, 'unit', 'Credential loading from env', True) + else: + self.results.add_result(language, 'unit', 'Credential loading from env', False, + f"Got pk={pk}, sk={sk}") + except Exception as e: + self.results.add_result(language, 'unit', 'Credential loading from env', False, str(e)) + + # Unit test: HMAC signature generation + try: + sig = un._sign_request('unsb-sk-test-5678', '1704067200', 'POST', '/execute', '{}') + if len(sig) == 64 and all(c in '0123456789abcdef' for c in sig): + self.results.add_result(language, 'unit', 'HMAC-SHA256 signature generation', True) + else: + self.results.add_result(language, 'unit', 'HMAC-SHA256 signature generation', False, + f"Invalid signature: {sig}") + except Exception as e: + self.results.add_result(language, 'unit', 'HMAC-SHA256 signature generation', False, str(e)) + + # Unit test: languages cache + try: + # Create temp cache dir + cache_dir = Path(tempfile.gettempdir()) / 'unsandbox_test_cache' + cache_dir.mkdir(exist_ok=True) + cache_file = cache_dir / 'languages.json' + + # Clean up old cache + if cache_file.exists(): + cache_file.unlink() + + # Test that function returns list (without calling API during unit test) + # Just verify the function exists and has correct signature + if callable(un.languages): + self.results.add_result(language, 'unit', 'Languages function signature', True) + else: + self.results.add_result(language, 'unit', 'Languages function signature', False, + "languages function not callable") + except Exception as e: + self.results.add_result(language, 'unit', 'Languages function signature', False, str(e)) + + # Integration test: execute function (if API key available) + if self.api_key: + try: + result = un.execute( + 'python', + 'print("hello from sdk")', + network_mode='zerotrust', + ttl=60 + ) + + if result.get('exit_code') == 0 and 'hello from sdk' in result.get('stdout', ''): + self.results.add_result(language, 'integration', 'execute() function', True) + else: + self.results.add_result(language, 'integration', 'execute() function', False, + f"exit_code={result.get('exit_code')}, stdout={result.get('stdout')}") + except Exception as e: + self.results.add_result(language, 'integration', 'execute() function', False, str(e)[:100]) + else: + self.results.skip_result(language, 'integration', 'execute() function', 'No API key') + + except ImportError as e: + self.results.add_result(language, 'unit', 'SDK import', False, f"Cannot import un: {e}") + except Exception as e: + self.results.add_result(language, 'unit', 'SDK import', False, str(e)) + + def test_javascript_sdk(self): + """Test JavaScript SDK library functions.""" + language = 'javascript' + + try: + # Check if Node.js is available + subprocess.run(['node', '--version'], capture_output=True, check=True) + + # Create test script + test_script = ''' +const un = require('./un.js'); + +// Test 1: HMAC signature +try { + const sig = un._signRequest('test-sk', '1704067200', 'POST', '/execute', '{}'); + if (sig && sig.length === 64) { + console.log('PASS: HMAC signature'); + } else { + console.log('FAIL: HMAC signature - invalid length'); + } +} catch (e) { + console.log('FAIL: HMAC signature - ' + e.message); +} + +// Test 2: Client class exists +try { + if (typeof un.Client === 'function') { + console.log('PASS: Client class'); + } else { + console.log('FAIL: Client class - not a function'); + } +} catch (e) { + console.log('FAIL: Client class - ' + e.message); +} +''' + + with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False) as f: + f.write(test_script) + test_file = f.name + + try: + result = subprocess.run( + ['node', test_file], + cwd=str(self.sdk_dir), + capture_output=True, + text=True, + timeout=10 + ) + + output = result.stdout + result.stderr + if 'PASS: HMAC signature' in output: + self.results.add_result(language, 'unit', 'HMAC-SHA256 signature generation', True) + else: + self.results.add_result(language, 'unit', 'HMAC-SHA256 signature generation', False, + output[:100]) + + if 'PASS: Client class' in output: + self.results.add_result(language, 'unit', 'Client class exists', True) + else: + self.results.add_result(language, 'unit', 'Client class exists', False, + output[:100]) + finally: + os.unlink(test_file) + + except subprocess.CalledProcessError: + self.results.skip_result(language, 'unit', 'JavaScript SDK tests', 'Node.js not available') + except Exception as e: + self.results.skip_result(language, 'unit', 'JavaScript SDK tests', str(e)) + + def test_go_sdk(self): + """Test Go SDK library functions.""" + language = 'go' + + try: + # Check if Go is available + subprocess.run(['go', 'version'], capture_output=True, check=True) + + # Create test program + test_code = ''' +package main + +import ( + "fmt" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" +) + +func main() { + // Test HMAC signature + key := []byte("test-sk") + message := "1704067200:POST:/execute:{}" + h := hmac.New(sha256.New, key) + h.Write([]byte(message)) + sig := hex.EncodeToString(h.Sum(nil)) + + if len(sig) == 64 { + fmt.Println("PASS: HMAC signature") + } else { + fmt.Printf("FAIL: HMAC signature - invalid length %d\\n", len(sig)) + } +} +''' + + with tempfile.TemporaryDirectory() as tmpdir: + test_file = Path(tmpdir) / 'test.go' + test_file.write_text(test_code) + + result = subprocess.run( + ['go', 'run', str(test_file)], + capture_output=True, + text=True, + timeout=15 + ) + + if 'PASS: HMAC signature' in result.stdout: + self.results.add_result(language, 'unit', 'HMAC-SHA256 signature generation', True) + else: + self.results.add_result(language, 'unit', 'HMAC-SHA256 signature generation', False, + (result.stdout + result.stderr)[:100]) + + except subprocess.CalledProcessError: + self.results.skip_result(language, 'unit', 'Go SDK tests', 'Go not available') + except Exception as e: + self.results.skip_result(language, 'unit', 'Go SDK tests', str(e)) + + def run_all_tests(self, languages: Optional[List[str]] = None): + """Run all SDK tests.""" + print(f"{BLUE}Testing SDK Library Implementations{RESET}\n") + + if languages is None: + languages = ['python', 'javascript', 'go'] + + if 'python' in languages: + print(f"{BLUE}Testing Python SDK...{RESET}") + self.test_python_sdk() + + if 'javascript' in languages: + print(f"{BLUE}Testing JavaScript SDK...{RESET}") + self.test_javascript_sdk() + + if 'go' in languages: + print(f"{BLUE}Testing Go SDK...{RESET}") + self.test_go_sdk() + + return self.results.print_summary() + + +def main(): + """Main entry point.""" + import argparse + + parser = argparse.ArgumentParser(description='Test UN SDK library implementations') + parser.add_argument('--languages', nargs='+', default=None, + help='Languages to test (default: all)') + parser.add_argument('--api-key', default=None, + help='API key for integration tests (or set UNSANDBOX_API_KEY env var)') + parser.add_argument('--api-url', default='https://api.unsandbox.com', + help='API URL for testing') + + args = parser.parse_args() + + if args.api_key: + os.environ['UNSANDBOX_API_KEY'] = args.api_key + + if args.api_url: + os.environ['UNSANDBOX_API_URL'] = args.api_url + + tester = SDKLibraryTester() + success = tester.run_all_tests(languages=args.languages) + + sys.exit(0 if success else 1) + + +if __name__ == '__main__': + main()