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:
parent
dc69bf4959
commit
59e6d4ebf8
5 changed files with 1874 additions and 0 deletions
262
REFACTORING_CHECKLIST.md
Normal file
262
REFACTORING_CHECKLIST.md
Normal 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.
|
||||
377
tests/AGENT_REFACTORING_INSTRUCTIONS.md
Normal file
377
tests/AGENT_REFACTORING_INSTRUCTIONS.md
Normal 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
605
tests/SDK_TESTING_GUIDE.md
Normal 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
265
tests/run_sdk_tests.sh
Executable 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
365
tests/test_sdk_library.py
Executable 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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue