feat: full feature parity for all 42 SDKs + comprehensive tests

All SDKs now implement 58+ functions matching C reference (un.h):
- Execution (8): execute, execute_async, wait_job, get_job, cancel_job, list_jobs, get_languages, detect_language
- Sessions (9): list, get, create, destroy, freeze, unfreeze, boost, unboost, execute
- Services (17): list, get, create, destroy, freeze, unfreeze, lock, unlock, set_unfreeze_on_demand, redeploy, logs, execute, env_get/set/delete/export, resize
- Snapshots (9): list, get, session, service, restore, delete, lock, unlock, clone
- Images (13): list, get, publish, delete, lock, unlock, set_visibility, grant/revoke_access, list_trusted, transfer, spawn, clone
- PaaS Logs (2): fetch, stream
- Utilities (5): validate_keys, hmac_sign, health_check, version, last_error

Test suites created for all SDKs with unit, integration, and functional tests.

Languages: AWK, Bash, C++, C#, Clojure, COBOL, Crystal, D, Dart, .NET, Elixir, Erlang, F#, Forth, Fortran, Go, Groovy, Haskell, Java, JavaScript, Julia, Kotlin, Lisp, Lua, Nim, Objective-C, OCaml, Perl, PHP, PowerShell, Prolog, Python, R, Raku, Ruby, Rust, Scheme, Swift, Tcl, TypeScript, V, Zig
This commit is contained in:
russell@unturf.com 2026-02-05 16:45:02 -05:00
parent a5155aed4f
commit 6e746ace44
90 changed files with 28955 additions and 3028 deletions

View file

@ -922,6 +922,597 @@ def languages(Map options = [:]) {
return result
}
// ============================================================================
// Utility Functions
// ============================================================================
/**
* Get SDK version string.
*/
def version() {
return "4.2.0"
}
/**
* Check API health status.
*/
def healthCheck() {
try {
def url = new URL("${API_BASE}/health")
def connection = url.openConnection() as java.net.HttpURLConnection
connection.requestMethod = "GET"
connection.connectTimeout = 5000
connection.readTimeout = 5000
return connection.responseCode == 200
} catch (Exception e) {
return false
}
}
/**
* Generate HMAC-SHA256 signature.
*/
def hmacSign(String secretKey, String message) {
def mac = Mac.getInstance("HmacSHA256")
mac.init(new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA256"))
return mac.doFinal(message.getBytes("UTF-8")).encodeHex().toString()
}
// ============================================================================
// Session Functions
// ============================================================================
/**
* List all sessions.
*/
def sessionList(Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
def result = apiRequest('/sessions', 'GET', null, publicKey, secretKey)
return result.sessions ?: []
}
/**
* Get session details.
*/
def sessionGet(String sessionId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/sessions/${sessionId}", 'GET', null, publicKey, secretKey)
}
/**
* Create a new session.
*/
def sessionCreate(Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
def payload = [
network_mode: options.networkMode ?: 'zerotrust',
shell: options.shell ?: 'bash'
]
if (options.vcpu) payload.vcpu = options.vcpu
return apiRequest('/sessions', 'POST', payload, publicKey, secretKey)
}
/**
* Destroy a session.
*/
def sessionDestroy(String sessionId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/sessions/${sessionId}", 'DELETE', null, publicKey, secretKey)
}
/**
* Freeze a session.
*/
def sessionFreeze(String sessionId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/sessions/${sessionId}/freeze", 'POST', null, publicKey, secretKey)
}
/**
* Unfreeze a session.
*/
def sessionUnfreeze(String sessionId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/sessions/${sessionId}/unfreeze", 'POST', null, publicKey, secretKey)
}
/**
* Boost a session.
*/
def sessionBoost(String sessionId, int vcpu = 2, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/sessions/${sessionId}/boost", 'POST', [vcpu: vcpu], publicKey, secretKey)
}
/**
* Unboost a session.
*/
def sessionUnboost(String sessionId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/sessions/${sessionId}/unboost", 'POST', null, publicKey, secretKey)
}
/**
* Execute command in a session.
*/
def sessionExecute(String sessionId, String command, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/sessions/${sessionId}/shell", 'POST', [command: command], publicKey, secretKey)
}
// ============================================================================
// Service Functions
// ============================================================================
/**
* List all services.
*/
def serviceList(Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
def result = apiRequest('/services', 'GET', null, publicKey, secretKey)
return result.services ?: []
}
/**
* Get service details.
*/
def serviceGet(String serviceId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/services/${serviceId}", 'GET', null, publicKey, secretKey)
}
/**
* Create a new service.
*/
def serviceCreate(String name, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
def payload = [name: name]
if (options.ports) payload.ports = options.ports.split(',').collect { it.trim().toInteger() }
if (options.domains) payload.domains = options.domains
if (options.bootstrap) payload.bootstrap = options.bootstrap
if (options.networkMode) payload.network_mode = options.networkMode
def result = apiRequest('/services', 'POST', payload, publicKey, secretKey)
return result.id
}
/**
* Destroy a service.
*/
def serviceDestroy(String serviceId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return executeDestructive("/services/${serviceId}", 'DELETE', null, publicKey, secretKey)
}
/**
* Freeze a service.
*/
def serviceFreeze(String serviceId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/services/${serviceId}/freeze", 'POST', null, publicKey, secretKey)
}
/**
* Unfreeze a service.
*/
def serviceUnfreeze(String serviceId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/services/${serviceId}/unfreeze", 'POST', null, publicKey, secretKey)
}
/**
* Lock a service.
*/
def serviceLock(String serviceId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/services/${serviceId}/lock", 'POST', null, publicKey, secretKey)
}
/**
* Unlock a service.
*/
def serviceUnlock(String serviceId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return executeDestructive("/services/${serviceId}/unlock", 'POST', null, publicKey, secretKey)
}
/**
* Set unfreeze on demand for a service.
*/
def serviceSetUnfreezeOnDemand(String serviceId, boolean enabled, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequestPatch("/services/${serviceId}", [unfreeze_on_demand: enabled], publicKey, secretKey)
}
/**
* Redeploy a service.
*/
def serviceRedeploy(String serviceId, String bootstrap = null, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
def payload = bootstrap ? [bootstrap: bootstrap] : [:]
return apiRequest("/services/${serviceId}/redeploy", 'POST', payload, publicKey, secretKey)
}
/**
* Get service logs.
*/
def serviceLogs(String serviceId, boolean allLogs = false, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
def path = allLogs ? "/services/${serviceId}/logs?all=true" : "/services/${serviceId}/logs"
def result = apiRequest(path, 'GET', null, publicKey, secretKey)
return result.logs
}
/**
* Execute command in a service.
*/
def serviceExecute(String serviceId, String command, int timeoutMs = 0, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
def payload = [command: command]
if (timeoutMs > 0) payload.timeout = timeoutMs
return apiRequest("/services/${serviceId}/execute", 'POST', payload, publicKey, secretKey)
}
/**
* Get service environment vault status.
*/
def serviceEnvGet(String serviceId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/services/${serviceId}/env", 'GET', null, publicKey, secretKey)
}
/**
* Set service environment vault.
*/
def serviceEnvSet(String serviceId, String envContent, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequestText("/services/${serviceId}/env", 'PUT', envContent, publicKey, secretKey)
}
/**
* Delete service environment vault.
*/
def serviceEnvDelete(String serviceId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/services/${serviceId}/env", 'DELETE', null, publicKey, secretKey)
}
/**
* Export service environment vault.
*/
def serviceEnvExport(String serviceId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/services/${serviceId}/env/export", 'POST', [:], publicKey, secretKey)
}
/**
* Resize a service.
*/
def serviceResize(String serviceId, int vcpu, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequestPatch("/services/${serviceId}", [vcpu: vcpu], publicKey, secretKey)
}
// ============================================================================
// Snapshot Functions
// ============================================================================
/**
* List all snapshots.
*/
def snapshotList(Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
def result = apiRequest('/snapshots', 'GET', null, publicKey, secretKey)
return result.snapshots ?: []
}
/**
* Get snapshot details.
*/
def snapshotGet(String snapshotId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/snapshots/${snapshotId}", 'GET', null, publicKey, secretKey)
}
/**
* Create snapshot from session.
*/
def snapshotSession(String sessionId, String name = null, boolean hot = false, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
def payload = [session_id: sessionId, hot: hot]
if (name) payload.name = name
def result = apiRequest('/snapshots', 'POST', payload, publicKey, secretKey)
return result.snapshot_id
}
/**
* Create snapshot from service.
*/
def snapshotService(String serviceId, String name = null, boolean hot = false, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
def payload = [service_id: serviceId, hot: hot]
if (name) payload.name = name
def result = apiRequest('/snapshots', 'POST', payload, publicKey, secretKey)
return result.snapshot_id
}
/**
* Restore a snapshot.
*/
def snapshotRestore(String snapshotId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/snapshots/${snapshotId}/restore", 'POST', [:], publicKey, secretKey)
}
/**
* Delete a snapshot.
*/
def snapshotDelete(String snapshotId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return executeDestructive("/snapshots/${snapshotId}", 'DELETE', null, publicKey, secretKey)
}
/**
* Lock a snapshot.
*/
def snapshotLock(String snapshotId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/snapshots/${snapshotId}/lock", 'POST', null, publicKey, secretKey)
}
/**
* Unlock a snapshot.
*/
def snapshotUnlock(String snapshotId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return executeDestructive("/snapshots/${snapshotId}/unlock", 'POST', null, publicKey, secretKey)
}
/**
* Clone a snapshot.
*/
def snapshotClone(String snapshotId, String cloneType, String name = null, String ports = null, String shell = null, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
def payload = [type: cloneType]
if (name) payload.name = name
if (ports) payload.ports = ports.split(',').collect { it.trim().toInteger() }
if (shell) payload.shell = shell
def result = apiRequest("/snapshots/${snapshotId}/clone", 'POST', payload, publicKey, secretKey)
return result.session_id ?: result.service_id
}
// ============================================================================
// Image Functions
// ============================================================================
/**
* List all images.
*/
def imageList(String filter = null, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
def path = filter ? "/images/${filter}" : '/images'
def result = apiRequest(path, 'GET', null, publicKey, secretKey)
return result.images ?: []
}
/**
* Get image details.
*/
def imageGet(String imageId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/images/${imageId}", 'GET', null, publicKey, secretKey)
}
/**
* Publish an image.
*/
def imagePublish(String sourceType, String sourceId, String name = null, String description = null, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
def payload = [source_type: sourceType, source_id: sourceId]
if (name) payload.name = name
if (description) payload.description = description
def result = apiRequest('/images', 'POST', payload, publicKey, secretKey)
return result.image_id
}
/**
* Delete an image.
*/
def imageDelete(String imageId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return executeDestructive("/images/${imageId}", 'DELETE', null, publicKey, secretKey)
}
/**
* Lock an image.
*/
def imageLock(String imageId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/images/${imageId}/lock", 'POST', null, publicKey, secretKey)
}
/**
* Unlock an image.
*/
def imageUnlock(String imageId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return executeDestructive("/images/${imageId}/unlock", 'POST', null, publicKey, secretKey)
}
/**
* Set image visibility.
*/
def imageSetVisibility(String imageId, String visibility, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/images/${imageId}/visibility", 'POST', [visibility: visibility], publicKey, secretKey)
}
/**
* Grant access to an image.
*/
def imageGrantAccess(String imageId, String trustedApiKey, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/images/${imageId}/grant", 'POST', [trusted_api_key: trustedApiKey], publicKey, secretKey)
}
/**
* Revoke access to an image.
*/
def imageRevokeAccess(String imageId, String trustedApiKey, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/images/${imageId}/revoke", 'POST', [trusted_api_key: trustedApiKey], publicKey, secretKey)
}
/**
* List trusted keys for an image.
*/
def imageListTrusted(String imageId, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
def result = apiRequest("/images/${imageId}/trusted", 'GET', null, publicKey, secretKey)
return result.trusted ?: []
}
/**
* Transfer image ownership.
*/
def imageTransfer(String imageId, String toApiKey, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
return apiRequest("/images/${imageId}/transfer", 'POST', [to_api_key: toApiKey], publicKey, secretKey)
}
/**
* Spawn a service from an image.
*/
def imageSpawn(String imageId, String name = null, String ports = null, String bootstrap = null, String networkMode = null, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
def payload = [:]
if (name) payload.name = name
if (ports) payload.ports = ports.split(',').collect { it.trim().toInteger() }
if (bootstrap) payload.bootstrap = bootstrap
if (networkMode) payload.network_mode = networkMode
def result = apiRequest("/images/${imageId}/spawn", 'POST', payload, publicKey, secretKey)
return result.service_id
}
/**
* Clone an image.
*/
def imageClone(String imageId, String name = null, String description = null, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
def payload = [:]
if (name) payload.name = name
if (description) payload.description = description
def result = apiRequest("/images/${imageId}/clone", 'POST', payload, publicKey, secretKey)
return result.image_id
}
// ============================================================================
// PaaS Logs Functions
// ============================================================================
/**
* Fetch batch logs.
*/
def logsFetch(String source = 'all', int lines = 100, String since = null, String grep = null, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
def params = ["source=${source}", "lines=${lines}"]
if (since) params << "since=${since}"
if (grep) params << "grep=${URLEncoder.encode(grep, 'UTF-8')}"
return apiRequest("/paas/logs?${params.join('&')}", 'GET', null, publicKey, secretKey)
}
/**
* Callback interface for log streaming.
*/
interface LogCallback {
void onLogLine(String source, String line)
}
/**
* Stream logs via SSE. Blocks until interrupted or server closes.
*
* @param source Log source ('all', 'api', 'portal', 'pool/cammy', 'pool/ai')
* @param grep Optional filter pattern
* @param callback Callback for each log line
* @param options Optional parameters (publicKey, secretKey)
* @return true on clean shutdown, false on error
*/
def logsStream(String source = 'all', String grep = null, LogCallback callback, Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
def path = "/paas/logs/stream?source=${source ?: 'all'}"
if (grep) {
path += "&grep=${URLEncoder.encode(grep, 'UTF-8')}"
}
def timestamp = (System.currentTimeMillis() / 1000) as long
def signature = signRequest(secretKey, timestamp, 'GET', path, '')
def url = new URL("${API_BASE}${path}")
def connection = url.openConnection() as java.net.HttpURLConnection
connection.requestMethod = 'GET'
connection.setRequestProperty('Authorization', "Bearer ${publicKey}")
connection.setRequestProperty('X-Timestamp', timestamp.toString())
connection.setRequestProperty('X-Signature', signature)
connection.setRequestProperty('Accept', 'text/event-stream')
connection.connectTimeout = 30000
connection.readTimeout = 0 // No timeout for streaming
if (connection.responseCode != 200) {
return false
}
try {
def reader = new BufferedReader(new InputStreamReader(connection.inputStream, 'UTF-8'))
def currentSource = source ?: 'all'
def line
while ((line = reader.readLine()) != null) {
if (line.startsWith('data: ')) {
def data = line.substring(6)
if (callback) {
callback.onLogLine(currentSource, data)
}
} else if (line.startsWith('event: ')) {
currentSource = line.substring(7)
}
}
return true
} catch (Exception e) {
return false
}
}
/**
* Validate API keys.
*/
def validateKeys(Map options = [:]) {
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
def timestamp = (System.currentTimeMillis() / 1000) as long
def message = "${timestamp}:POST:/keys/validate:{}"
def signature = signRequest(secretKey, timestamp, 'POST', '/keys/validate', '{}')
def url = new URL("${PORTAL_BASE}/keys/validate")
def connection = url.openConnection() as java.net.HttpURLConnection
connection.requestMethod = 'POST'
connection.setRequestProperty('Authorization', "Bearer ${publicKey}")
connection.setRequestProperty('X-Timestamp', timestamp.toString())
connection.setRequestProperty('X-Signature', signature)
connection.setRequestProperty('Content-Type', 'application/json')
connection.connectTimeout = 30000
connection.readTimeout = 30000
connection.doOutput = true
connection.outputStream.withWriter { it.write('{}') }
if (connection.responseCode !in 200..299) {
throw new APIError("HTTP ${connection.responseCode}")
}
return new JsonSlurper().parseText(connection.inputStream.text)
}
/**
* Detect programming language from file extension or shebang.
*

View file

@ -0,0 +1,453 @@
#!/usr/bin/env groovy
// PUBLIC DOMAIN - NO LICENSE, NO WARRANTY
// Unit tests for Un SDK - Groovy Synchronous client
import groovy.test.GroovyTestCase
/**
* Test suite for the Unsandbox Groovy SDK.
*
* Run with: groovy UnTest.groovy
*
* Integration tests require UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY
* environment variables to be set.
*/
class UnTest extends GroovyTestCase {
// Load the SDK
static {
def sdkPath = new File(UnTest.class.protectionDomain.codeSource.location.path).parentFile.parentFile
evaluate(new File(sdkPath, 'src/un.groovy'))
}
// ========================================================================
// Language Detection Tests
// ========================================================================
void testDetectPython() {
assertEquals("python", detectLanguage("script.py"))
assertEquals("python", detectLanguage("path/to/script.py"))
}
void testDetectJavaScript() {
assertEquals("javascript", detectLanguage("app.js"))
}
void testDetectTypeScript() {
assertEquals("typescript", detectLanguage("app.ts"))
}
void testDetectGo() {
assertEquals("go", detectLanguage("main.go"))
}
void testDetectRust() {
assertEquals("rust", detectLanguage("lib.rs"))
}
void testDetectJava() {
assertEquals("java", detectLanguage("Main.java"))
}
void testDetectKotlin() {
assertEquals("kotlin", detectLanguage("Main.kt"))
}
void testDetectGroovy() {
assertEquals("groovy", detectLanguage("script.groovy"))
}
void testDetectCpp() {
assertEquals("cpp", detectLanguage("main.cpp"))
}
void testDetectC() {
assertEquals("c", detectLanguage("main.c"))
}
void testDetectRuby() {
assertEquals("ruby", detectLanguage("script.rb"))
}
void testDetectPhp() {
assertEquals("php", detectLanguage("index.php"))
}
void testDetectUnknown() {
assertNull(detectLanguage("file.unknown"))
assertNull(detectLanguage("noextension"))
}
// ========================================================================
// Utility Function Tests
// ========================================================================
void testVersionString() {
def ver = version()
assertNotNull(ver)
assertTrue("Version should be in X.Y.Z format", ver ==~ /\d+\.\d+\.\d+/)
}
void testHmacSignature() {
def signature = hmacSign("secret", "message")
assertNotNull(signature)
assertEquals("HMAC-SHA256 should produce 64 hex chars", 64, signature.length())
assertTrue("Signature should be lowercase hex", signature ==~ /[0-9a-f]+/)
}
void testHmacConsistent() {
def sig1 = hmacSign("key", "data")
def sig2 = hmacSign("key", "data")
assertEquals("Same inputs should produce same signature", sig1, sig2)
}
void testHmacDifferent() {
def sig1 = hmacSign("key1", "data")
def sig2 = hmacSign("key2", "data")
assertFalse("Different keys should produce different signatures", sig1 == sig2)
}
void testHmacKnownValue() {
// HMAC-SHA256("key", "The quick brown fox jumps over the lazy dog")
// Known value from various implementations
def signature = hmacSign("key", "The quick brown fox jumps over the lazy dog")
assertEquals("f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8", signature)
}
// ========================================================================
// Health Check Tests
// ========================================================================
void testHealthCheckReturnsBoolean() {
def healthy = healthCheck()
// We just verify it returns a boolean without throwing
assertTrue(healthy instanceof Boolean)
}
// ========================================================================
// Exception Tests
// ========================================================================
void testUnsandboxError() {
def error = new UnsandboxError("Test message")
assertEquals("Test message", error.message)
}
void testAuthenticationError() {
def error = new AuthenticationError("Auth failed")
assertEquals("Auth failed", error.message)
assertTrue(error instanceof UnsandboxError)
}
void testExecutionError() {
def error = new ExecutionError("Exec failed", 1, "stderr output")
assertEquals("Exec failed", error.message)
assertEquals(1, error.exitCode)
assertEquals("stderr output", error.stderr)
}
void testAPIError() {
def error = new APIError("API failed", 500, '{"error": "internal"}')
assertEquals("API failed", error.message)
assertEquals(500, error.statusCode)
assertEquals('{"error": "internal"}', error.response)
}
void testTimeoutError() {
def error = new TimeoutError("Operation timed out")
assertEquals("Operation timed out", error.message)
assertTrue(error instanceof UnsandboxError)
}
void testSudoChallengeError() {
def error = new SudoChallengeError("challenge-123", '{"challenge_id": "challenge-123"}')
assertEquals("challenge-123", error.challengeId)
assertEquals('{"challenge_id": "challenge-123"}', error.responseBody)
}
// ========================================================================
// Extension Map Tests
// ========================================================================
void testExtensionMapComplete() {
// Verify the EXT_MAP has all expected extensions
assertNotNull(EXT_MAP['.py'])
assertNotNull(EXT_MAP['.js'])
assertNotNull(EXT_MAP['.ts'])
assertNotNull(EXT_MAP['.go'])
assertNotNull(EXT_MAP['.rs'])
assertNotNull(EXT_MAP['.java'])
assertNotNull(EXT_MAP['.kt'])
assertNotNull(EXT_MAP['.groovy'])
assertNotNull(EXT_MAP['.rb'])
assertNotNull(EXT_MAP['.php'])
assertNotNull(EXT_MAP['.c'])
assertNotNull(EXT_MAP['.cpp'])
assertNotNull(EXT_MAP['.sh'])
assertNotNull(EXT_MAP['.lua'])
assertNotNull(EXT_MAP['.pl'])
}
// ========================================================================
// Integration Tests (requires credentials)
// ========================================================================
void testExecutePythonCode() {
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
if (!publicKey || !secretKey) {
println "Skipping integration test - credentials not set"
return
}
def result = execute("python", 'print("Hello, World!")', [
publicKey: publicKey,
secretKey: secretKey
])
assertNotNull(result)
assertTrue(result.stdout?.contains("Hello, World!") ?: false)
}
void testExecuteJavaScriptCode() {
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
if (!publicKey || !secretKey) {
println "Skipping integration test - credentials not set"
return
}
def result = execute("javascript", 'console.log("Hello from JS")', [
publicKey: publicKey,
secretKey: secretKey
])
assertNotNull(result)
assertTrue(result.stdout?.contains("Hello from JS") ?: false)
}
void testGetLanguages() {
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
if (!publicKey || !secretKey) {
println "Skipping integration test - credentials not set"
return
}
def result = languages([
publicKey: publicKey,
secretKey: secretKey,
forceRefresh: true
])
assertNotNull(result)
assertNotNull(result.languages)
assertTrue(result.languages.size() > 0)
assertTrue(result.languages.contains("python"))
assertTrue(result.languages.contains("javascript"))
}
void testListJobs() {
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
if (!publicKey || !secretKey) {
println "Skipping integration test - credentials not set"
return
}
def jobs = listJobs([
publicKey: publicKey,
secretKey: secretKey
])
assertNotNull(jobs)
// Jobs list can be empty if no jobs are running
}
void testValidateKeys() {
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
if (!publicKey || !secretKey) {
println "Skipping integration test - credentials not set"
return
}
def result = validateKeys([
publicKey: publicKey,
secretKey: secretKey
])
assertNotNull(result)
}
void testListSessions() {
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
if (!publicKey || !secretKey) {
println "Skipping integration test - credentials not set"
return
}
def sessions = sessionList([
publicKey: publicKey,
secretKey: secretKey
])
assertNotNull(sessions)
}
void testListServices() {
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
if (!publicKey || !secretKey) {
println "Skipping integration test - credentials not set"
return
}
def services = serviceList([
publicKey: publicKey,
secretKey: secretKey
])
assertNotNull(services)
}
void testListSnapshots() {
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
if (!publicKey || !secretKey) {
println "Skipping integration test - credentials not set"
return
}
def snapshots = snapshotList([
publicKey: publicKey,
secretKey: secretKey
])
assertNotNull(snapshots)
}
void testListImages() {
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
if (!publicKey || !secretKey) {
println "Skipping integration test - credentials not set"
return
}
def images = imageList(null, [
publicKey: publicKey,
secretKey: secretKey
])
assertNotNull(images)
}
void testAsyncExecution() {
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
if (!publicKey || !secretKey) {
println "Skipping integration test - credentials not set"
return
}
def job = executeAsync("python", 'print("Async test")', [
publicKey: publicKey,
secretKey: secretKey
])
assertNotNull(job)
assertNotNull(job.job_id)
// Wait for completion
def result = wait(job.job_id, [
publicKey: publicKey,
secretKey: secretKey,
maxPolls: 30
])
assertNotNull(result)
assertTrue(result.stdout?.contains("Async test") ?: (result.result?.stdout?.contains("Async test") ?: false))
}
void testLogsFetch() {
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
if (!publicKey || !secretKey) {
println "Skipping integration test - credentials not set"
return
}
def result = logsFetch('all', 10, null, null, [
publicKey: publicKey,
secretKey: secretKey
])
assertNotNull(result)
}
void testLogCallbackInterface() {
// Verify LogCallback interface exists and can be implemented
def callback = { source, line ->
assertNotNull(source)
assertNotNull(line)
} as LogCallback
assertNotNull(callback)
}
// ========================================================================
// Run all tests
// ========================================================================
static void main(String[] args) {
println "Running Unsandbox Groovy SDK Tests..."
println "=" * 60
def test = new UnTest()
def methods = UnTest.class.declaredMethods.findAll {
it.name.startsWith('test') && it.parameterCount == 0
}
int passed = 0
int failed = 0
int skipped = 0
methods.each { method ->
print "Testing ${method.name}... "
try {
method.invoke(test)
println "PASS"
passed++
} catch (Exception e) {
def cause = e.cause ?: e
if (cause.message?.contains("Skipping")) {
println "SKIP"
skipped++
} else {
println "FAIL: ${cause.message}"
failed++
}
}
}
println "=" * 60
println "Results: ${passed} passed, ${failed} failed, ${skipped} skipped"
if (failed > 0) {
System.exit(1)
}
}
}