Create comprehensive SDK testing and refactoring framework for agents

- 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.
This commit is contained in:
russell@unturf.com 2026-01-15 14:51:32 -05:00
parent dc69bf4959
commit 59e6d4ebf8
5 changed files with 1874 additions and 0 deletions

262
REFACTORING_CHECKLIST.md Normal file
View file

@ -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.

View file

@ -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!

605
tests/SDK_TESTING_GUIDE.md Normal file
View file

@ -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!

265
tests/run_sdk_tests.sh Executable file
View file

@ -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 ""

365
tests/test_sdk_library.py Executable file
View file

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