diff --git a/clients/go/Makefile b/clients/go/Makefile index ff873a3..637f44c 100644 --- a/clients/go/Makefile +++ b/clients/go/Makefile @@ -204,8 +204,10 @@ test-functional: check-go echo " $(YELLOW)⊘$(NC) Skipping (no API credentials)"; \ else \ echo " Running functional tests..."; \ - if [ -d "$(SYNC_DIR)/tests" ]; then \ - cd $(SYNC_DIR) && $(GO) test -v -run TestFunctional ./tests/ 2>&1 || true; \ + if [ -d "$(SYNC_DIR)/tests" ] && [ -d "$(SYNC_DIR)/src" ]; then \ + cp $(SYNC_DIR)/tests/functional_test.go $(SYNC_DIR)/src/ 2>/dev/null; \ + cd $(SYNC_DIR)/src && $(GO) test -v -run TestFunctional . 2>&1; \ + rm -f $(SYNC_DIR)/src/functional_test.go; \ fi; \ fi diff --git a/clients/go/sync/tests/functional_test.go b/clients/go/sync/tests/functional_test.go new file mode 100644 index 0000000..f27b60e --- /dev/null +++ b/clients/go/sync/tests/functional_test.go @@ -0,0 +1,181 @@ +// This is free software for the public good of a permacomputer hosted at +// permacomputer.com, an always-on computer by the people, for the people. +// One which is durable, easy to repair, & distributed like tap water +// for machine learning intelligence. +// +// The permacomputer is community-owned infrastructure optimized around +// four values: +// +// TRUTH First principles, math & science, open source code freely distributed +// FREEDOM Voluntary partnerships, freedom from tyranny & corporate control +// HARMONY Minimal waste, self-renewing systems with diverse thriving connections +// LOVE Be yourself without hurting others, cooperation through natural law +// +// This software contributes to that vision by enabling code execution across 42+ programming languages through a unified interface, accessible to all. +// Code is seeds to sprout on any abandoned technology. + +// UN Go SDK - Functional Tests +// +// Tests library functions against real API. +// Requires: UNSANDBOX_PUBLIC_KEY, UNSANDBOX_SECRET_KEY +// +// Usage: +// Copy to sync/src/ then: go test -v -run TestFunctional +package un + +import ( + "os" + "strings" + "testing" +) + +func skipIfNoCreds(t *testing.T) *Credentials { + t.Helper() + pk := os.Getenv("UNSANDBOX_PUBLIC_KEY") + sk := os.Getenv("UNSANDBOX_SECRET_KEY") + if pk == "" || sk == "" { + t.Skip("UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY required") + } + return &Credentials{PublicKey: pk, SecretKey: sk} +} + +func TestFunctionalHealthCheck(t *testing.T) { + _ = skipIfNoCreds(t) + result := HealthCheck() + // HealthCheck returns a bool - just verify it runs without panic + t.Logf("HealthCheck: %v", result) +} + +func TestFunctionalValidateKeys(t *testing.T) { + creds := skipIfNoCreds(t) + info, err := ValidateKeys(creds) + if err != nil { + t.Fatalf("ValidateKeys error: %v", err) + } + if info == nil { + t.Fatal("ValidateKeys returned nil") + } + valid, ok := info["valid"] + if !ok { + t.Fatal("ValidateKeys result missing 'valid' key") + } + if valid != true { + t.Errorf("Keys should be valid, got: %v", valid) + } +} + +func TestFunctionalGetLanguages(t *testing.T) { + creds := skipIfNoCreds(t) + langs, err := GetLanguages(creds) + if err != nil { + t.Fatalf("GetLanguages error: %v", err) + } + if len(langs) == 0 { + t.Fatal("GetLanguages returned empty list") + } + foundPython := false + for _, l := range langs { + if l == "python" { + foundPython = true + break + } + } + if !foundPython { + t.Error("python not found in languages list") + } + t.Logf("Found %d languages", len(langs)) +} + +func TestFunctionalExecute(t *testing.T) { + creds := skipIfNoCreds(t) + result, err := ExecuteCode(creds, "python", "print('hello from Go SDK')") + if err != nil { + t.Fatalf("ExecuteCode error: %v", err) + } + if result == nil { + t.Fatal("ExecuteCode returned nil") + } + stdout, _ := result["stdout"].(string) + if !strings.Contains(stdout, "hello from Go SDK") { + t.Errorf("stdout should contain 'hello from Go SDK', got: %s", stdout) + } + exitCode, _ := result["exit_code"].(float64) + if exitCode != 0 { + t.Errorf("exit_code should be 0, got: %v", exitCode) + } +} + +func TestFunctionalExecuteError(t *testing.T) { + creds := skipIfNoCreds(t) + result, err := ExecuteCode(creds, "python", "import sys; sys.exit(1)") + if err != nil { + t.Fatalf("ExecuteCode error: %v", err) + } + if result == nil { + t.Fatal("ExecuteCode returned nil") + } + exitCode, _ := result["exit_code"].(float64) + if exitCode != 1 { + t.Errorf("exit_code should be 1, got: %v", exitCode) + } +} + +func TestFunctionalSessionList(t *testing.T) { + creds := skipIfNoCreds(t) + sessions, err := ListSessions(creds) + if err != nil { + t.Fatalf("ListSessions error: %v", err) + } + t.Logf("Found %d sessions", len(sessions)) +} + +func TestFunctionalSessionLifecycle(t *testing.T) { + creds := skipIfNoCreds(t) + + // Create + session, err := CreateSession(creds, nil) + if err != nil { + t.Fatalf("CreateSession error: %v", err) + } + if session == nil { + t.Fatal("CreateSession returned nil") + } + sessionID, _ := session["id"].(string) + if sessionID == "" { + t.Fatal("Session missing id") + } + t.Logf("Created session: %s", sessionID) + + // Destroy + _, err = DeleteSession(creds, sessionID) + if err != nil { + t.Errorf("DeleteSession error: %v", err) + } +} + +func TestFunctionalServiceList(t *testing.T) { + creds := skipIfNoCreds(t) + services, err := ListServices(creds) + if err != nil { + t.Fatalf("ListServices error: %v", err) + } + t.Logf("Found %d services", len(services)) +} + +func TestFunctionalSnapshotList(t *testing.T) { + creds := skipIfNoCreds(t) + snapshots, err := ListSnapshots(creds) + if err != nil { + t.Fatalf("ListSnapshots error: %v", err) + } + t.Logf("Found %d snapshots", len(snapshots)) +} + +func TestFunctionalImageList(t *testing.T) { + creds := skipIfNoCreds(t) + images, err := ListImages(creds, "") + if err != nil { + t.Fatalf("ListImages error: %v", err) + } + t.Logf("Found %d images", len(images)) +} diff --git a/clients/java/Makefile b/clients/java/Makefile index 4915796..d1456bd 100644 --- a/clients/java/Makefile +++ b/clients/java/Makefile @@ -196,7 +196,12 @@ test-functional: echo " $(YELLOW)⊘$(NC) Skipping (no API credentials)"; \ else \ echo " Running functional tests..."; \ - echo " $(YELLOW)⊘$(NC) Functional: SDK not yet implemented"; \ + if [ -f "$(SYNC_DIR)/tests/TestFunctional.java" ] && [ -f "$(SYNC_DIR)/src/Un.java" ]; then \ + $(JAVAC) -cp $(SYNC_DIR)/src $(SYNC_DIR)/tests/TestFunctional.java -d /tmp/un-java-test 2>&1 && \ + $(JAVA) -cp /tmp/un-java-test:$(SYNC_DIR)/src TestFunctional 2>&1 && \ + echo " $(GREEN)✓$(NC) Functional: All tests passed" || echo " $(RED)✗$(NC) Functional: Tests failed"; \ + rm -rf /tmp/un-java-test; \ + fi; \ fi # ============================================================================ diff --git a/clients/java/sync/tests/TestFunctional.java b/clients/java/sync/tests/TestFunctional.java new file mode 100644 index 0000000..fe6c4b3 --- /dev/null +++ b/clients/java/sync/tests/TestFunctional.java @@ -0,0 +1,186 @@ +/** + * This is free software for the public good of a permacomputer hosted at + * permacomputer.com, an always-on computer by the people, for the people. + * One which is durable, easy to repair, & distributed like tap water + * for machine learning intelligence. + * + * The permacomputer is community-owned infrastructure optimized around + * four values: + * + * TRUTH First principles, math & science, open source code freely distributed + * FREEDOM Voluntary partnerships, freedom from tyranny & corporate control + * HARMONY Minimal waste, self-renewing systems with diverse thriving connections + * LOVE Be yourself without hurting others, cooperation through natural law + * + * This software contributes to that vision by enabling code execution across 42+ programming languages through a unified interface, accessible to all. + * Code is seeds to sprout on any abandoned technology. + * + * UN Java SDK - Functional Tests + * + * Tests library functions against real API. + * Requires: UNSANDBOX_PUBLIC_KEY, UNSANDBOX_SECRET_KEY + * + * Usage: + * javac -cp src tests/TestFunctional.java && java -cp src:tests TestFunctional + */ + +import java.io.IOException; +import java.util.*; + +public class TestFunctional { + + private static int passed = 0; + private static int failed = 0; + + private static final String GREEN = "\033[32m"; + private static final String RED = "\033[31m"; + private static final String NC = "\033[0m"; + + private static void check(boolean condition, String msg) { + if (condition) { + System.out.println(" " + GREEN + "✓" + NC + " " + msg); + passed++; + } else { + System.out.println(" " + RED + "✗" + NC + " " + msg); + failed++; + } + } + + private static void testHealthCheck() { + System.out.println("\nTesting healthCheck()..."); + boolean result = Un.healthCheck(); + check(true, "healthCheck completed without exception"); + } + + private static void testValidateKeys() throws IOException { + System.out.println("\nTesting validateKeys()..."); + Map info = Un.validateKeys(null, null); + check(info != null, "validateKeys returns non-null"); + if (info != null) { + check(info.containsKey("valid"), "result has 'valid' key"); + check(Boolean.TRUE.equals(info.get("valid")), "keys are valid"); + Object tier = info.get("tier"); + if (tier != null) System.out.println(" tier: " + tier); + } + } + + private static void testGetLanguages() throws IOException { + System.out.println("\nTesting getLanguages()..."); + List langs = Un.getLanguages(null, null); + check(langs != null, "getLanguages returns non-null"); + if (langs != null) { + check(!langs.isEmpty(), "at least one language returned"); + check(langs.contains("python"), "python is in languages list"); + System.out.println(" Found " + langs.size() + " languages"); + } + } + + private static void testExecute() throws IOException { + System.out.println("\nTesting executeCode()..."); + Map result = Un.executeCode("python", "print('hello from Java SDK')", null, null); + check(result != null, "execute returns non-null"); + if (result != null) { + String stdout = (String) result.get("stdout"); + check(stdout != null && stdout.contains("hello from Java SDK"), "stdout contains expected output"); + Object exitCode = result.get("exit_code"); + check(exitCode != null && ((Number) exitCode).intValue() == 0, "exit code is 0"); + } + } + + private static void testExecuteError() throws IOException { + System.out.println("\nTesting executeCode() with error..."); + Map result = Un.executeCode("python", "import sys; sys.exit(1)", null, null); + check(result != null, "execute returns non-null"); + if (result != null) { + Object exitCode = result.get("exit_code"); + check(exitCode != null && ((Number) exitCode).intValue() == 1, "exit code is 1"); + } + } + + private static void testSessionList() throws IOException { + System.out.println("\nTesting listSessions()..."); + List> sessions = Un.listSessions(null, null); + check(sessions != null, "listSessions returns non-null"); + if (sessions != null) { + System.out.println(" Found " + sessions.size() + " sessions"); + } + } + + private static void testSessionLifecycle() throws IOException { + System.out.println("\nTesting session lifecycle (create, destroy)..."); + Map session = Un.createSession("python", null, null, null); + check(session != null, "createSession returns non-null"); + if (session != null) { + String sessionId = (String) session.get("id"); + check(sessionId != null, "session has id"); + System.out.println(" session_id: " + sessionId); + + if (sessionId != null) { + Un.deleteSession(sessionId, null, null); + check(true, "deleteSession completed"); + } + } + } + + private static void testServiceList() throws IOException { + System.out.println("\nTesting listServices()..."); + List> services = Un.listServices(null, null); + check(services != null, "listServices returns non-null"); + if (services != null) { + System.out.println(" Found " + services.size() + " services"); + } + } + + private static void testSnapshotList() throws IOException { + System.out.println("\nTesting listSnapshots()..."); + List> snapshots = Un.listSnapshots(null, null); + check(snapshots != null, "listSnapshots returns non-null"); + if (snapshots != null) { + System.out.println(" Found " + snapshots.size() + " snapshots"); + } + } + + private static void testImageList() throws IOException { + System.out.println("\nTesting listImages()..."); + List> images = Un.listImages(null, null, null); + check(images != null, "listImages returns non-null"); + if (images != null) { + System.out.println(" Found " + images.size() + " images"); + } + } + + public static void main(String[] args) { + System.out.println("====================================="); + System.out.println("UN Java SDK - Functional Tests"); + System.out.println("Testing against real API"); + System.out.println("====================================="); + + String pk = System.getenv("UNSANDBOX_PUBLIC_KEY"); + String sk = System.getenv("UNSANDBOX_SECRET_KEY"); + + if (pk == null || sk == null || pk.isEmpty() || sk.isEmpty()) { + System.out.println("\n\033[33mSKIP: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY not set\033[0m"); + System.exit(0); + } + + try { testHealthCheck(); } catch (Exception e) { System.out.println(" " + RED + "✗ " + e.getMessage() + NC); failed++; } + try { testValidateKeys(); } catch (Exception e) { System.out.println(" " + RED + "✗ " + e.getMessage() + NC); failed++; } + try { testGetLanguages(); } catch (Exception e) { System.out.println(" " + RED + "✗ " + e.getMessage() + NC); failed++; } + try { testExecute(); } catch (Exception e) { System.out.println(" " + RED + "✗ " + e.getMessage() + NC); failed++; } + try { testExecuteError(); } catch (Exception e) { System.out.println(" " + RED + "✗ " + e.getMessage() + NC); failed++; } + try { testSessionList(); } catch (Exception e) { System.out.println(" " + RED + "✗ " + e.getMessage() + NC); failed++; } + try { testSessionLifecycle(); } catch (Exception e) { System.out.println(" " + RED + "✗ " + e.getMessage() + NC); failed++; } + try { testServiceList(); } catch (Exception e) { System.out.println(" " + RED + "✗ " + e.getMessage() + NC); failed++; } + try { testSnapshotList(); } catch (Exception e) { System.out.println(" " + RED + "✗ " + e.getMessage() + NC); failed++; } + try { testImageList(); } catch (Exception e) { System.out.println(" " + RED + "✗ " + e.getMessage() + NC); failed++; } + + System.out.println("\n====================================="); + System.out.println("Test Summary"); + System.out.println("====================================="); + System.out.println("Passed: " + GREEN + passed + NC); + System.out.println("Failed: " + RED + failed + NC); + System.out.println("====================================="); + + System.exit(failed > 0 ? 1 : 0); + } +} diff --git a/clients/javascript/Makefile b/clients/javascript/Makefile index 61beb44..3b619b8 100644 --- a/clients/javascript/Makefile +++ b/clients/javascript/Makefile @@ -157,8 +157,8 @@ test-functional: echo " $(YELLOW)⊘$(NC) Skipping (no API credentials)"; \ else \ echo " Running functional tests..."; \ - if [ -f "$(SYNC_DIR)/src/un.js" ]; then \ - node --input-type=module -e "const un = await import('./$(SYNC_DIR)/src/un.js'); const r = await un.executeCode('python', 'def fib(n): return n if n<2 else fib(n-1)+fib(n-2); print(fib(10))'); if(r.stdout && r.stdout.includes('55')) console.log(' ✓ Functional: Fibonacci'); else console.log(' ✗ Functional: Check output');" 2>/dev/null || echo " $(RED)✗$(NC) Functional: SDK error"; \ + if [ -f "$(SYNC_DIR)/tests/test_functional.mjs" ]; then \ + node $(SYNC_DIR)/tests/test_functional.mjs 2>&1 && echo " $(GREEN)✓$(NC) Functional: All tests passed" || echo " $(RED)✗$(NC) Functional: Tests failed"; \ fi; \ fi diff --git a/clients/javascript/sync/tests/test_functional.mjs b/clients/javascript/sync/tests/test_functional.mjs new file mode 100644 index 0000000..bb682de --- /dev/null +++ b/clients/javascript/sync/tests/test_functional.mjs @@ -0,0 +1,184 @@ +// This is free software for the public good of a permacomputer hosted at +// permacomputer.com, an always-on computer by the people, for the people. +// One which is durable, easy to repair, & distributed like tap water +// for machine learning intelligence. +// +// The permacomputer is community-owned infrastructure optimized around +// four values: +// +// TRUTH First principles, math & science, open source code freely distributed +// FREEDOM Voluntary partnerships, freedom from tyranny & corporate control +// HARMONY Minimal waste, self-renewing systems with diverse thriving connections +// LOVE Be yourself without hurting others, cooperation through natural law +// +// This software contributes to that vision by enabling code execution across 42+ programming languages through a unified interface, accessible to all. +// Code is seeds to sprout on any abandoned technology. + +/** + * UN JavaScript SDK - Functional Tests + * + * Tests library functions against real API. + * Requires: UNSANDBOX_PUBLIC_KEY, UNSANDBOX_SECRET_KEY + * + * Usage: + * node clients/javascript/sync/tests/test_functional.mjs + */ + +import { + executeCode, + getLanguages, + listSessions, + createSession, + deleteSession, + listServices, + listSnapshots, + listImages, + validateKeys, + healthCheck, +} from '../src/un.js'; + +const GREEN = '\x1b[32m'; +const RED = '\x1b[31m'; +const BLUE = '\x1b[34m'; +const YELLOW = '\x1b[33m'; +const NC = '\x1b[0m'; + +let passed = 0; +let failed = 0; + +function check(condition, msg) { + if (condition) { + console.log(` ${GREEN}✓${NC} ${msg}`); + passed++; + } else { + console.log(` ${RED}✗${NC} ${msg}`); + failed++; + } +} + +async function testHealthCheck() { + console.log('\nTesting healthCheck()...'); + const result = await healthCheck(); + check(typeof result === 'boolean', 'healthCheck returns boolean'); +} + +async function testValidateKeys() { + console.log('\nTesting validateKeys()...'); + const info = await validateKeys(); + check(info != null, 'validateKeys returns non-null'); + check(info.valid === true, 'keys are valid'); + if (info.tier) console.log(` tier: ${info.tier}`); +} + +async function testGetLanguages() { + console.log('\nTesting getLanguages()...'); + const langs = await getLanguages(); + check(Array.isArray(langs), 'getLanguages returns array'); + check(langs.length > 0, 'at least one language returned'); + check(langs.includes('python'), 'python is in languages list'); + console.log(` Found ${langs.length} languages`); +} + +async function testExecute() { + console.log('\nTesting executeCode()...'); + const result = await executeCode('python', "print('hello from JS SDK')"); + check(result != null, 'execute returns non-null'); + check(result.stdout && result.stdout.includes('hello from JS SDK'), 'stdout contains expected output'); + check(result.exit_code === 0, 'exit code is 0'); +} + +async function testExecuteError() { + console.log('\nTesting executeCode() with error...'); + const result = await executeCode('python', 'import sys; sys.exit(1)'); + check(result != null, 'execute returns non-null'); + check(result.exit_code === 1, 'exit code is 1'); +} + +async function testSessionList() { + console.log('\nTesting listSessions()...'); + const sessions = await listSessions(); + check(Array.isArray(sessions), 'listSessions returns array'); + console.log(` Found ${sessions.length} sessions`); +} + +async function testSessionLifecycle() { + console.log('\nTesting session lifecycle (create, destroy)...'); + + const session = await createSession('python'); + check(session != null, 'createSession returns non-null'); + const sessionId = session.session_id || session.id; + check(sessionId != null, 'session has id'); + console.log(` session_id: ${sessionId}`); + + if (sessionId) { + const destroyed = await deleteSession(sessionId); + check(destroyed != null, 'deleteSession returns non-null'); + } +} + +async function testServiceList() { + console.log('\nTesting listServices()...'); + const services = await listServices(); + check(Array.isArray(services), 'listServices returns array'); + console.log(` Found ${services.length} services`); +} + +async function testSnapshotList() { + console.log('\nTesting listSnapshots()...'); + const snapshots = await listSnapshots(); + check(Array.isArray(snapshots), 'listSnapshots returns array'); + console.log(` Found ${snapshots.length} snapshots`); +} + +async function testImageList() { + console.log('\nTesting listImages()...'); + const images = await listImages(); + check(Array.isArray(images), 'listImages returns array'); + console.log(` Found ${images.length} images`); +} + +// Main +async function main() { + console.log('====================================='); + console.log('UN JavaScript SDK - Functional Tests'); + console.log('Testing against real API'); + console.log('====================================='); + + if (!process.env.UNSANDBOX_PUBLIC_KEY || !process.env.UNSANDBOX_SECRET_KEY) { + console.log(`\n${YELLOW}SKIP: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY not set${NC}`); + process.exit(0); + } + + const tests = [ + testHealthCheck, + testValidateKeys, + testGetLanguages, + testExecute, + testExecuteError, + testSessionList, + testSessionLifecycle, + testServiceList, + testSnapshotList, + testImageList, + ]; + + for (const test of tests) { + try { + await test(); + } catch (err) { + console.log(` ${RED}✗${NC} ${test.name}: ${err.message}`); + failed++; + } + } + + console.log('\n====================================='); + console.log('Test Summary'); + console.log('====================================='); + console.log(`Passed: ${GREEN}${passed}${NC}`); + console.log(`Failed: ${RED}${failed}${NC}`); + console.log('====================================='); + + process.exit(failed > 0 ? 1 : 0); +} + +main(); diff --git a/clients/php/Makefile b/clients/php/Makefile index 29b37f8..5284246 100644 --- a/clients/php/Makefile +++ b/clients/php/Makefile @@ -163,7 +163,9 @@ test-functional: echo " $(YELLOW)⊘$(NC) Skipping (no API credentials)"; \ else \ echo " Running functional tests..."; \ - echo " $(YELLOW)⊘$(NC) Functional: SDK not yet implemented"; \ + if [ -f "$(SYNC_DIR)/tests/FunctionalTest.php" ]; then \ + cd $(SYNC_DIR) && php vendor/bin/phpunit tests/FunctionalTest.php 2>&1 && echo " $(GREEN)✓$(NC) Functional: All tests passed" || echo " $(RED)✗$(NC) Functional: Tests failed"; \ + fi; \ fi # ============================================================================ diff --git a/clients/php/sync/tests/FunctionalTest.php b/clients/php/sync/tests/FunctionalTest.php new file mode 100644 index 0000000..9ef1246 --- /dev/null +++ b/clients/php/sync/tests/FunctionalTest.php @@ -0,0 +1,120 @@ +markTestSkipped('UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY required'); + } + $this->client = new Unsandbox(); + } + + public function testHealthCheck(): void + { + $result = Unsandbox::healthCheck(); + $this->assertIsBool($result); + } + + public function testValidateKeys(): void + { + $info = $this->client->validateKeys(); + $this->assertIsArray($info); + $this->assertArrayHasKey('valid', $info); + $this->assertTrue($info['valid']); + } + + public function testGetLanguages(): void + { + $langs = $this->client->getLanguages(); + $this->assertIsArray($langs); + $this->assertNotEmpty($langs); + $this->assertContains('python', $langs); + } + + public function testExecute(): void + { + $result = $this->client->executeCode('python', "print('hello from PHP SDK')"); + $this->assertIsArray($result); + $this->assertStringContainsString('hello from PHP SDK', $result['stdout'] ?? ''); + $this->assertEquals(0, $result['exit_code'] ?? -1); + } + + public function testExecuteError(): void + { + $result = $this->client->executeCode('python', 'import sys; sys.exit(1)'); + $this->assertIsArray($result); + $this->assertEquals(1, $result['exit_code'] ?? -1); + } + + public function testSessionList(): void + { + $sessions = $this->client->listSessions(); + $this->assertIsArray($sessions); + } + + public function testSessionLifecycle(): void + { + $session = $this->client->createSession('python'); + $this->assertIsArray($session); + $this->assertArrayHasKey('id', $session); + $sessionId = $session['id']; + + $this->client->deleteSession($sessionId); + } + + public function testServiceList(): void + { + $services = $this->client->listServices(); + $this->assertIsArray($services); + } + + public function testSnapshotList(): void + { + $snapshots = $this->client->listSnapshots(); + $this->assertIsArray($snapshots); + } + + public function testImageList(): void + { + $images = $this->client->listImages(); + $this->assertIsArray($images); + } +} diff --git a/clients/python/Makefile b/clients/python/Makefile index 74fb3a3..64fdfe0 100644 --- a/clients/python/Makefile +++ b/clients/python/Makefile @@ -170,9 +170,7 @@ test-functional: $(VENV)/.deps-installed echo " $(YELLOW)⊘$(NC) Skipping (no API credentials)"; \ else \ echo " Running functional tests..."; \ - if [ -f "$(SYNC_DIR)/verify_sdk.py" ]; then \ - cd $(SYNC_DIR) && ../$(PYTHON) verify_sdk.py 2>/dev/null && echo " $(GREEN)✓$(NC) Functional: Sync SDK verified" || echo " $(RED)✗$(NC) Functional: Sync verification failed"; \ - fi; \ + cd $(SYNC_DIR) && PYTHONPATH=src ../$(PYTEST) tests/test_functional.py -v 2>&1 && echo " $(GREEN)✓$(NC) Functional: Sync SDK verified" || echo " $(RED)✗$(NC) Functional: Sync verification failed"; \ fi # ============================================================================ diff --git a/clients/python/sync/tests/test_functional.py b/clients/python/sync/tests/test_functional.py new file mode 100644 index 0000000..3bd86a9 --- /dev/null +++ b/clients/python/sync/tests/test_functional.py @@ -0,0 +1,118 @@ +# This is free software for the public good of a permacomputer hosted at +# permacomputer.com, an always-on computer by the people, for the people. +# One which is durable, easy to repair, & distributed like tap water +# for machine learning intelligence. +# +# The permacomputer is community-owned infrastructure optimized around +# four values: +# +# TRUTH First principles, math & science, open source code freely distributed +# FREEDOM Voluntary partnerships, freedom from tyranny & corporate control +# HARMONY Minimal waste, self-renewing systems with diverse thriving connections +# LOVE Be yourself without hurting others, cooperation through natural law +# +# This software contributes to that vision by enabling code execution across 42+ programming languages through a unified interface, accessible to all. +# Code is seeds to sprout on any abandoned technology. + +""" +UN Python SDK - Functional Tests + +Tests library functions against real API. +Requires: UNSANDBOX_PUBLIC_KEY, UNSANDBOX_SECRET_KEY + +Usage: + cd clients/python/sync && PYTHONPATH=src pytest tests/test_functional.py -v +""" + +import os +import sys +import pytest + +# Import SDK functions +from un import ( + execute_code, + get_languages, + list_sessions, + list_services, + list_snapshots, + list_images, + validate_keys, + health_check, +) + + +# Skip all tests if no credentials +pytestmark = pytest.mark.skipif( + not os.environ.get("UNSANDBOX_PUBLIC_KEY") or not os.environ.get("UNSANDBOX_SECRET_KEY"), + reason="UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY required", +) + + +class TestFunctional: + """Functional tests against real API (matches C SDK test_functional.c)""" + + def test_health_check(self): + """API health check returns boolean""" + result = health_check() + assert isinstance(result, bool) + + def test_validate_keys(self): + """API keys validate successfully""" + info = validate_keys() + assert isinstance(info, dict) + assert "valid" in info + assert info["valid"] is True + + def test_get_languages(self): + """Languages endpoint returns list with python""" + langs = get_languages() + assert isinstance(langs, list) + assert len(langs) > 0 + assert "python" in langs + + def test_execute(self): + """Execute python code and verify stdout""" + result = execute_code("python", "print('hello from Python SDK')") + assert isinstance(result, dict) + assert result.get("success") is True + assert "hello from Python SDK" in result.get("stdout", "") + assert result.get("exit_code") == 0 + + def test_execute_error(self): + """Execute code that exits with error""" + result = execute_code("python", "import sys; sys.exit(1)") + assert isinstance(result, dict) + assert result.get("success") is False + assert result.get("exit_code") == 1 + + def test_session_list(self): + """List sessions returns list""" + sessions = list_sessions() + assert isinstance(sessions, list) + + def test_session_lifecycle(self): + """Create and destroy a session""" + from un import create_session, delete_session + + session = create_session("python") + assert isinstance(session, dict) + assert "id" in session + session_id = session["id"] + + destroyed = delete_session(session_id) + assert destroyed is not None + + def test_service_list(self): + """List services returns list""" + services = list_services() + assert isinstance(services, list) + + def test_snapshot_list(self): + """List snapshots returns list""" + snapshots = list_snapshots() + assert isinstance(snapshots, list) + + def test_image_list(self): + """List images returns list""" + images = list_images() + assert isinstance(images, list) diff --git a/clients/ruby/Makefile b/clients/ruby/Makefile index 6657e0a..fff91db 100644 --- a/clients/ruby/Makefile +++ b/clients/ruby/Makefile @@ -162,7 +162,9 @@ test-functional: echo " $(YELLOW)⊘$(NC) Skipping (no API credentials)"; \ else \ echo " Running functional tests..."; \ - echo " $(YELLOW)⊘$(NC) Functional: SDK not yet implemented"; \ + if [ -f "$(SYNC_DIR)/test/test_functional.rb" ]; then \ + cd $(SYNC_DIR) && ruby test/test_functional.rb 2>&1 && echo " $(GREEN)✓$(NC) Functional: All tests passed" || echo " $(RED)✗$(NC) Functional: Tests failed"; \ + fi; \ fi # ============================================================================ diff --git a/clients/ruby/sync/test/test_functional.rb b/clients/ruby/sync/test/test_functional.rb new file mode 100644 index 0000000..dab2cc0 --- /dev/null +++ b/clients/ruby/sync/test/test_functional.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +# This is free software for the public good of a permacomputer hosted at +# permacomputer.com, an always-on computer by the people, for the people. +# One which is durable, easy to repair, & distributed like tap water +# for machine learning intelligence. +# +# The permacomputer is community-owned infrastructure optimized around +# four values: +# +# TRUTH First principles, math & science, open source code freely distributed +# FREEDOM Voluntary partnerships, freedom from tyranny & corporate control +# HARMONY Minimal waste, self-renewing systems with diverse thriving connections +# LOVE Be yourself without hurting others, cooperation through natural law +# +# This software contributes to that vision by enabling code execution across 42+ programming languages through a unified interface, accessible to all. +# Code is seeds to sprout on any abandoned technology. + +# UN Ruby SDK - Functional Tests +# +# Tests library functions against real API. +# Requires: UNSANDBOX_PUBLIC_KEY, UNSANDBOX_SECRET_KEY +# +# Usage: +# cd clients/ruby/sync && ruby test/test_functional.rb + +$LOAD_PATH.unshift File.expand_path('../src', __dir__) + +require 'minitest/autorun' +require 'un' + +class TestFunctional < Minitest::Test + def setup + skip 'UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY required' unless credentials? + end + + def test_health_check + result = Un.health_check + assert [true, false].include?(result), 'health_check should return boolean' + end + + def test_validate_keys + info = Un.validate_keys + assert_kind_of Hash, info + assert info.key?('valid') || info.key?(:valid), 'should have valid key' + assert_equal true, (info['valid'] || info[:valid]) + end + + def test_get_languages + langs = Un.get_languages + assert_kind_of Array, langs + refute_empty langs + assert_includes langs, 'python' + end + + def test_execute + result = Un.execute_code('python', "print('hello from Ruby SDK')") + assert_kind_of Hash, result + stdout = result['stdout'] || result[:stdout] || '' + assert_includes stdout, 'hello from Ruby SDK' + exit_code = result['exit_code'] || result[:exit_code] + assert_equal 0, exit_code + end + + def test_execute_error + result = Un.execute_code('python', 'import sys; sys.exit(1)') + assert_kind_of Hash, result + exit_code = result['exit_code'] || result[:exit_code] + assert_equal 1, exit_code + end + + def test_session_list + sessions = Un.list_sessions + assert_kind_of Array, sessions + end + + def test_session_lifecycle + session = Un.create_session('python') + assert_kind_of Hash, session + session_id = session['id'] || session[:id] + refute_nil session_id, 'session should have id' + + Un.delete_session(session_id) + end + + def test_service_list + services = Un.list_services + assert_kind_of Array, services + end + + def test_snapshot_list + snapshots = Un.list_snapshots + assert_kind_of Array, snapshots + end + + def test_image_list + images = Un.list_images + assert_kind_of Array, images + end + + private + + def credentials? + ENV['UNSANDBOX_PUBLIC_KEY'] && ENV['UNSANDBOX_SECRET_KEY'] && + !ENV['UNSANDBOX_PUBLIC_KEY'].empty? && !ENV['UNSANDBOX_SECRET_KEY'].empty? + end +end diff --git a/clients/rust/Makefile b/clients/rust/Makefile index 1134b09..ff05378 100644 --- a/clients/rust/Makefile +++ b/clients/rust/Makefile @@ -187,7 +187,7 @@ test-functional: else \ echo " Running functional tests..."; \ if [ -f "$(SYNC_DIR)/Cargo.toml" ]; then \ - cd $(SYNC_DIR) && cargo run --release -- -s python -c 'def fib(n): return n if n<2 else fib(n-1)+fib(n-2); print(fib(10))' 2>&1 | grep -q "55" && echo " $(GREEN)✓$(NC) Functional: Fibonacci" || echo " $(YELLOW)⊘$(NC) Functional: Fibonacci (check output)"; \ + cd $(SYNC_DIR) && cargo test --test functional_test -- --nocapture 2>&1 && echo " $(GREEN)✓$(NC) Functional: All tests passed" || echo " $(RED)✗$(NC) Functional: Tests failed"; \ fi; \ fi diff --git a/clients/rust/sync/tests/functional_test.rs b/clients/rust/sync/tests/functional_test.rs new file mode 100644 index 0000000..f84bd67 --- /dev/null +++ b/clients/rust/sync/tests/functional_test.rs @@ -0,0 +1,130 @@ +// This is free software for the public good of a permacomputer hosted at +// permacomputer.com, an always-on computer by the people, for the people. +// One which is durable, easy to repair, & distributed like tap water +// for machine learning intelligence. +// +// The permacomputer is community-owned infrastructure optimized around +// four values: +// +// TRUTH First principles, math & science, open source code freely distributed +// FREEDOM Voluntary partnerships, freedom from tyranny & corporate control +// HARMONY Minimal waste, self-renewing systems with diverse thriving connections +// LOVE Be yourself without hurting others, cooperation through natural law +// +// This software contributes to that vision by enabling code execution across 42+ programming languages through a unified interface, accessible to all. +// Code is seeds to sprout on any abandoned technology. + +//! UN Rust SDK - Functional Tests +//! +//! Tests library functions against real API. +//! Requires: UNSANDBOX_PUBLIC_KEY, UNSANDBOX_SECRET_KEY +//! +//! Usage: +//! cd clients/rust/sync && cargo test --test functional_test + +use un::*; +use std::env; + +fn get_creds() -> Option { + let pk = env::var("UNSANDBOX_PUBLIC_KEY").ok()?; + let sk = env::var("UNSANDBOX_SECRET_KEY").ok()?; + if pk.is_empty() || sk.is_empty() { + return None; + } + Some(Credentials::new(pk, sk)) +} + +macro_rules! skip_no_creds { + () => { + match get_creds() { + Some(c) => c, + None => { + eprintln!("SKIP: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY required"); + return; + } + } + }; +} + +#[test] +fn functional_health_check() { + let _ = skip_no_creds!(); + let result = health_check(); + // Just verify it returns without panic + eprintln!("health_check: {}", result); +} + +#[test] +fn functional_validate_keys() { + let creds = skip_no_creds!(); + let info = validate_keys(&creds).expect("validate_keys should succeed"); + assert!(info.valid, "keys should be valid"); +} + +#[test] +fn functional_get_languages() { + let creds = skip_no_creds!(); + let langs = get_languages(&creds).expect("get_languages should succeed"); + assert!(!langs.is_empty(), "should have at least one language"); + assert!(langs.contains(&"python".to_string()), "python should be in languages list"); + eprintln!("Found {} languages", langs.len()); +} + +#[test] +fn functional_execute() { + let creds = skip_no_creds!(); + let result = execute_code("python", "print('hello from Rust SDK')", &creds) + .expect("execute should succeed"); + assert!(result.stdout.contains("hello from Rust SDK"), "stdout should contain expected output"); + assert_eq!(result.exit_code, 0, "exit code should be 0"); +} + +#[test] +fn functional_execute_error() { + let creds = skip_no_creds!(); + let result = execute_code("python", "import sys; sys.exit(1)", &creds) + .expect("execute should return result even on error"); + assert_eq!(result.exit_code, 1, "exit code should be 1"); +} + +#[test] +fn functional_session_list() { + let creds = skip_no_creds!(); + let sessions = list_sessions(&creds).expect("list_sessions should succeed"); + eprintln!("Found {} sessions", sessions.len()); +} + +#[test] +fn functional_session_lifecycle() { + let creds = skip_no_creds!(); + + // Create + let session = create_session("python", &creds, None) + .expect("create_session should succeed"); + assert!(!session.id.is_empty(), "session should have id"); + eprintln!("Created session: {}", session.id); + + // Destroy + delete_session(&session.id, &creds).expect("delete_session should succeed"); +} + +#[test] +fn functional_service_list() { + let creds = skip_no_creds!(); + let services = list_services(&creds).expect("list_services should succeed"); + eprintln!("Found {} services", services.len()); +} + +#[test] +fn functional_snapshot_list() { + let creds = skip_no_creds!(); + let snapshots = list_snapshots(&creds).expect("list_snapshots should succeed"); + eprintln!("Found {} snapshots", snapshots.len()); +} + +#[test] +fn functional_image_list() { + let creds = skip_no_creds!(); + let images = list_images(None, &creds).expect("list_images should succeed"); + eprintln!("Found {} images", images.len()); +}