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

@ -319,6 +319,401 @@ fn execCurlPut(allocator: std.mem.Allocator, endpoint: []const u8, body: []const
return ret == 0;
}
// ============================================================================
// Library Functions for Zig SDK (matching C reference un.h)
// ============================================================================
pub const SDK_VERSION = "4.2.0";
// Generic API request helper
fn makeApiRequest(allocator: std.mem.Allocator, method: []const u8, path: []const u8, body: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const result = try execCurlWithStatus(allocator, method, path, body, public_key, secret_key, "");
return result.body;
}
// Execute code synchronously
pub fn execute(allocator: std.mem.Allocator, language: []const u8, code: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const body = try std.fmt.allocPrint(allocator, "{{\"language\":\"{s}\",\"code\":\"{s}\"}}", .{ language, code });
defer allocator.free(body);
return try makeApiRequest(allocator, "POST", "/execute", body, public_key, secret_key);
}
// Execute code asynchronously (returns job_id)
pub fn executeAsync(allocator: std.mem.Allocator, language: []const u8, code: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const body = try std.fmt.allocPrint(allocator, "{{\"language\":\"{s}\",\"code\":\"{s}\",\"async\":true}}", .{ language, code });
defer allocator.free(body);
return try makeApiRequest(allocator, "POST", "/execute", body, public_key, secret_key);
}
// Get job status
pub fn getJob(allocator: std.mem.Allocator, job_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/jobs/{s}", .{job_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "GET", path, "", public_key, secret_key);
}
// Wait for job completion
pub fn waitForJob(allocator: std.mem.Allocator, job_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const poll_delays = [_]u64{ 300, 450, 700, 900, 650, 1600, 2000 };
var delay_idx: usize = 0;
while (true) {
const result = try getJob(allocator, job_id, public_key, secret_key);
// Check for terminal states
if (mem.indexOf(u8, result, "\"status\":\"completed\"") != null or
mem.indexOf(u8, result, "\"status\":\"failed\"") != null or
mem.indexOf(u8, result, "\"status\":\"timeout\"") != null or
mem.indexOf(u8, result, "\"status\":\"cancelled\"") != null)
{
return result;
}
allocator.free(result);
std.time.sleep(poll_delays[delay_idx] * std.time.ns_per_ms);
if (delay_idx < poll_delays.len - 1) delay_idx += 1;
}
}
// Cancel a job
pub fn cancelJob(allocator: std.mem.Allocator, job_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/jobs/{s}/cancel", .{job_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "POST", path, "", public_key, secret_key);
}
// List all jobs
pub fn listJobs(allocator: std.mem.Allocator, public_key: []const u8, secret_key: []const u8) ![]const u8 {
return try makeApiRequest(allocator, "GET", "/jobs", "", public_key, secret_key);
}
// Get supported languages
pub fn getLanguages(allocator: std.mem.Allocator, public_key: []const u8, secret_key: []const u8) ![]const u8 {
return try makeApiRequest(allocator, "GET", "/languages", "", public_key, secret_key);
}
// Detect language from filename
pub fn detectLanguage(filename: []const u8) ?[]const u8 {
const lang_map = [_]struct { ext: []const u8, lang: []const u8 }{
.{ .ext = ".py", .lang = "python" },
.{ .ext = ".js", .lang = "javascript" },
.{ .ext = ".ts", .lang = "typescript" },
.{ .ext = ".go", .lang = "go" },
.{ .ext = ".rs", .lang = "rust" },
.{ .ext = ".c", .lang = "c" },
.{ .ext = ".cpp", .lang = "cpp" },
.{ .ext = ".cc", .lang = "cpp" },
.{ .ext = ".d", .lang = "d" },
.{ .ext = ".zig", .lang = "zig" },
.{ .ext = ".rb", .lang = "ruby" },
.{ .ext = ".php", .lang = "php" },
.{ .ext = ".sh", .lang = "bash" },
.{ .ext = ".lua", .lang = "lua" },
.{ .ext = ".nim", .lang = "nim" },
.{ .ext = ".v", .lang = "v" },
};
const idx = mem.lastIndexOfScalar(u8, filename, '.') orelse return null;
const ext = filename[idx..];
for (lang_map) |entry| {
if (mem.eql(u8, ext, entry.ext)) {
return entry.lang;
}
}
return null;
}
// Session functions
pub fn sessionList(allocator: std.mem.Allocator, public_key: []const u8, secret_key: []const u8) ![]const u8 {
return try makeApiRequest(allocator, "GET", "/sessions", "", public_key, secret_key);
}
pub fn sessionGet(allocator: std.mem.Allocator, session_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/sessions/{s}", .{session_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "GET", path, "", public_key, secret_key);
}
pub fn sessionCreate(allocator: std.mem.Allocator, shell: ?[]const u8, network: ?[]const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
var body_buf: [512]u8 = undefined;
var stream = std.io.fixedBufferStream(&body_buf);
const writer = stream.writer();
try writer.print("{{\"shell\":\"{s}\"", .{shell orelse "bash"});
if (network) |n| try writer.print(",\"network\":\"{s}\"", .{n});
try writer.writeAll("}");
const body = stream.getWritten();
return try makeApiRequest(allocator, "POST", "/sessions", body, public_key, secret_key);
}
pub fn sessionDestroy(allocator: std.mem.Allocator, session_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/sessions/{s}", .{session_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "DELETE", path, "", public_key, secret_key);
}
pub fn sessionFreeze(allocator: std.mem.Allocator, session_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/sessions/{s}/freeze", .{session_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "POST", path, "", public_key, secret_key);
}
pub fn sessionUnfreeze(allocator: std.mem.Allocator, session_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/sessions/{s}/unfreeze", .{session_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "POST", path, "", public_key, secret_key);
}
pub fn sessionBoost(allocator: std.mem.Allocator, session_id: []const u8, vcpu: ?u32, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/sessions/{s}/boost", .{session_id});
defer allocator.free(path);
var body: []const u8 = "{}";
if (vcpu) |v| {
body = try std.fmt.allocPrint(allocator, "{{\"vcpu\":{d}}}", .{v});
}
return try makeApiRequest(allocator, "POST", path, body, public_key, secret_key);
}
pub fn sessionUnboost(allocator: std.mem.Allocator, session_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/sessions/{s}/unboost", .{session_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "POST", path, "", public_key, secret_key);
}
pub fn sessionExecute(allocator: std.mem.Allocator, session_id: []const u8, command: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/sessions/{s}/shell", .{session_id});
defer allocator.free(path);
const body = try std.fmt.allocPrint(allocator, "{{\"command\":\"{s}\"}}", .{command});
defer allocator.free(body);
return try makeApiRequest(allocator, "POST", path, body, public_key, secret_key);
}
// Service functions
pub fn serviceList(allocator: std.mem.Allocator, public_key: []const u8, secret_key: []const u8) ![]const u8 {
return try makeApiRequest(allocator, "GET", "/services", "", public_key, secret_key);
}
pub fn serviceGet(allocator: std.mem.Allocator, service_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/services/{s}", .{service_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "GET", path, "", public_key, secret_key);
}
pub fn serviceDestroy(allocator: std.mem.Allocator, service_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/services/{s}", .{service_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "DELETE", path, "", public_key, secret_key);
}
pub fn serviceFreeze(allocator: std.mem.Allocator, service_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/services/{s}/freeze", .{service_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "POST", path, "", public_key, secret_key);
}
pub fn serviceUnfreeze(allocator: std.mem.Allocator, service_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/services/{s}/unfreeze", .{service_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "POST", path, "", public_key, secret_key);
}
pub fn serviceLock(allocator: std.mem.Allocator, service_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/services/{s}/lock", .{service_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "POST", path, "", public_key, secret_key);
}
pub fn serviceUnlock(allocator: std.mem.Allocator, service_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/services/{s}/unlock", .{service_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "POST", path, "", public_key, secret_key);
}
pub fn serviceRedeploy(allocator: std.mem.Allocator, service_id: []const u8, bootstrap: ?[]const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/services/{s}/redeploy", .{service_id});
defer allocator.free(path);
const body = if (bootstrap) |b| try std.fmt.allocPrint(allocator, "{{\"bootstrap\":\"{s}\"}}", .{b}) else try allocator.dupe(u8, "{}");
defer allocator.free(body);
return try makeApiRequest(allocator, "POST", path, body, public_key, secret_key);
}
pub fn serviceLogs(allocator: std.mem.Allocator, service_id: []const u8, all: bool, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = if (all)
try std.fmt.allocPrint(allocator, "/services/{s}/logs?all=true", .{service_id})
else
try std.fmt.allocPrint(allocator, "/services/{s}/logs", .{service_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "GET", path, "", public_key, secret_key);
}
pub fn serviceExecute(allocator: std.mem.Allocator, service_id: []const u8, command: []const u8, timeout_ms: ?u32, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/services/{s}/execute", .{service_id});
defer allocator.free(path);
const body = if (timeout_ms) |t|
try std.fmt.allocPrint(allocator, "{{\"command\":\"{s}\",\"timeout\":{d}}}", .{ command, t })
else
try std.fmt.allocPrint(allocator, "{{\"command\":\"{s}\"}}", .{command});
defer allocator.free(body);
return try makeApiRequest(allocator, "POST", path, body, public_key, secret_key);
}
pub fn serviceResize(allocator: std.mem.Allocator, service_id: []const u8, vcpu: u32, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/services/{s}/resize", .{service_id});
defer allocator.free(path);
const body = try std.fmt.allocPrint(allocator, "{{\"vcpu\":{d}}}", .{vcpu});
defer allocator.free(body);
return try makeApiRequest(allocator, "POST", path, body, public_key, secret_key);
}
// Snapshot functions
pub fn snapshotList(allocator: std.mem.Allocator, public_key: []const u8, secret_key: []const u8) ![]const u8 {
return try makeApiRequest(allocator, "GET", "/snapshots", "", public_key, secret_key);
}
pub fn snapshotGet(allocator: std.mem.Allocator, snapshot_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/snapshots/{s}", .{snapshot_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "GET", path, "", public_key, secret_key);
}
pub fn snapshotRestore(allocator: std.mem.Allocator, snapshot_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/snapshots/{s}/restore", .{snapshot_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "POST", path, "", public_key, secret_key);
}
pub fn snapshotDelete(allocator: std.mem.Allocator, snapshot_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/snapshots/{s}", .{snapshot_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "DELETE", path, "", public_key, secret_key);
}
pub fn snapshotLock(allocator: std.mem.Allocator, snapshot_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/snapshots/{s}/lock", .{snapshot_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "POST", path, "", public_key, secret_key);
}
pub fn snapshotUnlock(allocator: std.mem.Allocator, snapshot_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/snapshots/{s}/unlock", .{snapshot_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "POST", path, "", public_key, secret_key);
}
// Image functions
pub fn imageList(allocator: std.mem.Allocator, filter: ?[]const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = if (filter) |f|
try std.fmt.allocPrint(allocator, "/images?filter={s}", .{f})
else
try allocator.dupe(u8, "/images");
defer allocator.free(path);
return try makeApiRequest(allocator, "GET", path, "", public_key, secret_key);
}
pub fn imageGet(allocator: std.mem.Allocator, image_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/images/{s}", .{image_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "GET", path, "", public_key, secret_key);
}
pub fn imageDelete(allocator: std.mem.Allocator, image_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/images/{s}", .{image_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "DELETE", path, "", public_key, secret_key);
}
pub fn imageLock(allocator: std.mem.Allocator, image_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/images/{s}/lock", .{image_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "POST", path, "", public_key, secret_key);
}
pub fn imageUnlock(allocator: std.mem.Allocator, image_id: []const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
const path = try std.fmt.allocPrint(allocator, "/images/{s}/unlock", .{image_id});
defer allocator.free(path);
return try makeApiRequest(allocator, "POST", path, "", public_key, secret_key);
}
// PaaS Logs functions
pub fn logsFetch(allocator: std.mem.Allocator, source: ?[]const u8, lines: ?u32, since: ?[]const u8, grep: ?[]const u8, public_key: []const u8, secret_key: []const u8) ![]const u8 {
var path_buf: [512]u8 = undefined;
var stream = std.io.fixedBufferStream(&path_buf);
const writer = stream.writer();
try writer.writeAll("/paas/logs?");
var has_param = false;
if (source) |s| {
try writer.print("source={s}", .{s});
has_param = true;
}
if (lines) |l| {
if (has_param) try writer.writeAll("&");
try writer.print("lines={d}", .{l});
has_param = true;
}
if (since) |s| {
if (has_param) try writer.writeAll("&");
try writer.print("since={s}", .{s});
has_param = true;
}
if (grep) |g| {
if (has_param) try writer.writeAll("&");
try writer.print("grep={s}", .{g});
}
const path = stream.getWritten();
return try makeApiRequest(allocator, "GET", path, "", public_key, secret_key);
}
// Key validation
pub fn validateKeys(allocator: std.mem.Allocator, public_key: []const u8, secret_key: []const u8) ![]const u8 {
return try makeApiRequest(allocator, "POST", "/keys/validate", "", public_key, secret_key);
}
// Utility functions
pub fn hmacSign(allocator: std.mem.Allocator, secret_key: []const u8, message: []const u8) ![]const u8 {
const hmac_cmd = try computeHmacCmd(allocator, secret_key, message);
defer allocator.free(hmac_cmd);
const result = try std.process.Child.run(.{
.allocator = allocator,
.argv = &[_][]const u8{ "sh", "-c", hmac_cmd },
});
defer allocator.free(result.stderr);
const trimmed = mem.trim(u8, result.stdout, &std.ascii.whitespace);
const signature = try allocator.dupe(u8, trimmed);
allocator.free(result.stdout);
return signature;
}
pub fn healthCheck(allocator: std.mem.Allocator) !bool {
const cmd = try std.fmt.allocPrint(allocator, "curl -s -o /dev/null -w '%{{http_code}}' '{s}/health' 2>/dev/null", .{API_BASE});
defer allocator.free(cmd);
const result = try std.process.Child.run(.{
.allocator = allocator,
.argv = &[_][]const u8{ "sh", "-c", cmd },
});
defer allocator.free(result.stdout);
defer allocator.free(result.stderr);
const trimmed = mem.trim(u8, result.stdout, &std.ascii.whitespace);
return mem.eql(u8, trimmed, "200");
}
pub fn version() []const u8 {
return SDK_VERSION;
}
var last_error_msg: []const u8 = "";
pub fn setLastError(msg: []const u8) void {
last_error_msg = msg;
}
pub fn lastError() []const u8 {
return last_error_msg;
}
fn cmdServiceEnv(allocator: std.mem.Allocator, action: []const u8, target: []const u8, envs: std.ArrayList([]const u8), env_file: ?[]const u8, public_key: []const u8, secret_key: []const u8) !void {
if (mem.eql(u8, action, "status")) {
const path = try std.fmt.allocPrint(allocator, "/services/{s}/env", .{target});

View file

@ -0,0 +1,213 @@
// Tests for the Zig unsandbox SDK
// Run with: zig test tests/test_un.zig
const std = @import("std");
const un = @import("../src/un.zig");
const testing = std.testing;
const mem = std.mem;
// ============================================================================
// Unit Tests - Test exported library functions
// ============================================================================
test "detectLanguage" {
try testing.expectEqualStrings("python", un.detectLanguage("script.py") orelse "");
try testing.expectEqualStrings("javascript", un.detectLanguage("script.js") orelse "");
try testing.expectEqualStrings("typescript", un.detectLanguage("script.ts") orelse "");
try testing.expectEqualStrings("go", un.detectLanguage("script.go") orelse "");
try testing.expectEqualStrings("rust", un.detectLanguage("script.rs") orelse "");
try testing.expectEqualStrings("c", un.detectLanguage("script.c") orelse "");
try testing.expectEqualStrings("cpp", un.detectLanguage("script.cpp") orelse "");
try testing.expectEqualStrings("d", un.detectLanguage("script.d") orelse "");
try testing.expectEqualStrings("zig", un.detectLanguage("script.zig") orelse "");
try testing.expectEqualStrings("bash", un.detectLanguage("script.sh") orelse "");
try testing.expectEqualStrings("ruby", un.detectLanguage("script.rb") orelse "");
try testing.expectEqualStrings("php", un.detectLanguage("script.php") orelse "");
try testing.expect(un.detectLanguage("script.unknown") == null);
try testing.expect(un.detectLanguage("script") == null);
}
test "version" {
const v = un.version();
try testing.expect(v.len > 0);
// Should be in semver format (at least "0.0.0")
try testing.expect(v.len >= 5);
}
test "lastError" {
// Set an error
un.setLastError("test error message");
// Retrieve it
const err = un.lastError();
try testing.expectEqualStrings("test error message", err);
// Clear it
un.setLastError("");
const err2 = un.lastError();
try testing.expectEqualStrings("", err2);
}
test "SDK_VERSION constant" {
try testing.expect(un.SDK_VERSION.len > 0);
try testing.expect(un.SDK_VERSION.len >= 5);
}
// ============================================================================
// Integration Tests - Test SDK internal consistency
// ============================================================================
test "hmacSign" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const secret_key = "test-secret";
const message = "test-message";
const result = try un.hmacSign(allocator, secret_key, message);
defer allocator.free(result);
// Should return a 64-character hex string
try testing.expectEqual(@as(usize, 64), result.len);
// Should be deterministic
const result2 = try un.hmacSign(allocator, secret_key, message);
defer allocator.free(result2);
try testing.expectEqualStrings(result, result2);
// Different inputs should produce different outputs
const result3 = try un.hmacSign(allocator, secret_key, "different-message");
defer allocator.free(result3);
try testing.expect(!mem.eql(u8, result, result3));
}
// ============================================================================
// Functional Tests - Test against real API (requires credentials)
// These tests are marked as skipped by default since they require credentials
// ============================================================================
fn hasCredentials() bool {
const pk = std.posix.getenv("UNSANDBOX_PUBLIC_KEY") orelse return false;
const sk = std.posix.getenv("UNSANDBOX_SECRET_KEY") orelse return false;
return pk.len > 0 and sk.len > 0;
}
fn getCredentials() ?struct { pk: []const u8, sk: []const u8 } {
const pk = std.posix.getenv("UNSANDBOX_PUBLIC_KEY") orelse return null;
const sk = std.posix.getenv("UNSANDBOX_SECRET_KEY") orelse return null;
if (pk.len == 0 or sk.len == 0) return null;
return .{ .pk = pk, .sk = sk };
}
test "healthCheck functional" {
if (!hasCredentials()) {
std.debug.print("SKIP (no credentials)\n", .{});
return;
}
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const healthy = try un.healthCheck(allocator);
std.debug.print("Health check result: {}\n", .{healthy});
}
test "getLanguages functional" {
const creds = getCredentials() orelse {
std.debug.print("SKIP (no credentials)\n", .{});
return;
};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const result = try un.getLanguages(allocator, creds.pk, creds.sk);
defer allocator.free(result);
try testing.expect(result.len > 0);
// Should contain python
try testing.expect(mem.indexOf(u8, result, "python") != null);
}
test "validateKeys functional" {
const creds = getCredentials() orelse {
std.debug.print("SKIP (no credentials)\n", .{});
return;
};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const result = try un.validateKeys(allocator, creds.pk, creds.sk);
defer allocator.free(result);
try testing.expect(result.len > 0);
}
test "sessionList functional" {
const creds = getCredentials() orelse {
std.debug.print("SKIP (no credentials)\n", .{});
return;
};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const result = try un.sessionList(allocator, creds.pk, creds.sk);
defer allocator.free(result);
try testing.expect(result.len > 0);
}
test "serviceList functional" {
const creds = getCredentials() orelse {
std.debug.print("SKIP (no credentials)\n", .{});
return;
};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const result = try un.serviceList(allocator, creds.pk, creds.sk);
defer allocator.free(result);
try testing.expect(result.len > 0);
}
test "snapshotList functional" {
const creds = getCredentials() orelse {
std.debug.print("SKIP (no credentials)\n", .{});
return;
};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const result = try un.snapshotList(allocator, creds.pk, creds.sk);
defer allocator.free(result);
try testing.expect(result.len > 0);
}
test "imageList functional" {
const creds = getCredentials() orelse {
std.debug.print("SKIP (no credentials)\n", .{});
return;
};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const result = try un.imageList(allocator, null, creds.pk, creds.sk);
defer allocator.free(result);
try testing.expect(result.len > 0);
}