un-inception/clients/go/async/tests/un_async_test.go

396 lines
11 KiB
Go

// 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.
/*
Tests for the unsandbox Go SDK (Asynchronous)
Run tests:
cd /home/fox/git/un-inception/clients/go/async
go test ./tests/...
Or run with verbose output:
go test -v ./tests/...
*/
package tests
import (
"os"
"path/filepath"
"testing"
"time"
un_async "github.com/unsandbox/un-go-async/src"
)
// TestDetectLanguage tests language detection from filenames
func TestDetectLanguage(t *testing.T) {
tests := []struct {
filename string
expected string
}{
{"hello.py", "python"},
{"script.js", "javascript"},
{"main.go", "go"},
{"test.rs", "rust"},
{"program.c", "c"},
{"app.rb", "ruby"},
{"test.php", "php"},
{"script.sh", "bash"},
{"data.R", "r"}, // Uppercase R
{"data.r", "r"}, // Lowercase r
{"unknown", ""}, // No extension
{"no_ext", ""}, // No extension
{"file.xyz", ""}, // Unknown extension
{"test.ts", "typescript"},
{"code.java", "java"},
{"test.kt", "kotlin"},
{"app.ex", "elixir"},
{"prog.erl", "erlang"},
{"script.lua", "lua"},
{"test.nim", "nim"},
}
for _, tc := range tests {
t.Run(tc.filename, func(t *testing.T) {
result := un_async.DetectLanguage(tc.filename)
if result != tc.expected {
t.Errorf("DetectLanguage(%q) = %q, want %q", tc.filename, result, tc.expected)
}
})
}
}
// TestSignRequest tests HMAC-SHA256 signature generation
func TestSignRequest(t *testing.T) {
// This is a basic test to ensure signature generation works
// The actual signature validation would need to match server-side implementation
secretKey := "test-secret-key"
timestamp := int64(1234567890)
method := "POST"
path := "/execute"
body := []byte(`{"language":"python","code":"print(42)"}`)
// We test that signRequest returns a non-empty 64-character hex string
// Note: signRequest is not exported, so we test via LanguageMap as a proxy
// for now we just verify the LanguageMap is properly defined
if len(un_async.LanguageMap) == 0 {
t.Error("LanguageMap should not be empty")
}
// Verify constants are defined
if un_async.APIBase != "https://api.unsandbox.com" {
t.Errorf("APIBase = %q, want %q", un_async.APIBase, "https://api.unsandbox.com")
}
if un_async.LanguagesCacheTTL != 3600 {
t.Errorf("LanguagesCacheTTL = %d, want %d", un_async.LanguagesCacheTTL, 3600)
}
// Verify poll delays are defined
if len(un_async.PollDelaysMs) == 0 {
t.Error("PollDelaysMs should not be empty")
}
// Use the variables to avoid unused variable warnings
_ = secretKey
_ = timestamp
_ = method
_ = path
_ = body
}
// TestCredentialsError tests the CredentialsError type
func TestCredentialsError(t *testing.T) {
err := &un_async.CredentialsError{Message: "test error"}
if err.Error() != "test error" {
t.Errorf("CredentialsError.Error() = %q, want %q", err.Error(), "test error")
}
}
// TestResolveCredentialsFromArgs tests credential resolution from arguments
func TestResolveCredentialsFromArgs(t *testing.T) {
creds, err := un_async.ResolveCredentials("test-pk", "test-sk")
if err != nil {
t.Fatalf("ResolveCredentials failed: %v", err)
}
if creds.PublicKey != "test-pk" {
t.Errorf("PublicKey = %q, want %q", creds.PublicKey, "test-pk")
}
if creds.SecretKey != "test-sk" {
t.Errorf("SecretKey = %q, want %q", creds.SecretKey, "test-sk")
}
}
// TestResolveCredentialsFromEnv tests credential resolution from environment
func TestResolveCredentialsFromEnv(t *testing.T) {
// Save original values
origPK := os.Getenv("UNSANDBOX_PUBLIC_KEY")
origSK := os.Getenv("UNSANDBOX_SECRET_KEY")
// Set test values
os.Setenv("UNSANDBOX_PUBLIC_KEY", "env-pk")
os.Setenv("UNSANDBOX_SECRET_KEY", "env-sk")
// Restore after test
defer func() {
if origPK != "" {
os.Setenv("UNSANDBOX_PUBLIC_KEY", origPK)
} else {
os.Unsetenv("UNSANDBOX_PUBLIC_KEY")
}
if origSK != "" {
os.Setenv("UNSANDBOX_SECRET_KEY", origSK)
} else {
os.Unsetenv("UNSANDBOX_SECRET_KEY")
}
}()
creds, err := un_async.ResolveCredentials("", "")
if err != nil {
t.Fatalf("ResolveCredentials failed: %v", err)
}
if creds.PublicKey != "env-pk" {
t.Errorf("PublicKey = %q, want %q", creds.PublicKey, "env-pk")
}
if creds.SecretKey != "env-sk" {
t.Errorf("SecretKey = %q, want %q", creds.SecretKey, "env-sk")
}
}
// TestResolveCredentialsArgsOverrideEnv tests that args take priority over env
func TestResolveCredentialsArgsOverrideEnv(t *testing.T) {
// Save original values
origPK := os.Getenv("UNSANDBOX_PUBLIC_KEY")
origSK := os.Getenv("UNSANDBOX_SECRET_KEY")
// Set env values
os.Setenv("UNSANDBOX_PUBLIC_KEY", "env-pk")
os.Setenv("UNSANDBOX_SECRET_KEY", "env-sk")
// Restore after test
defer func() {
if origPK != "" {
os.Setenv("UNSANDBOX_PUBLIC_KEY", origPK)
} else {
os.Unsetenv("UNSANDBOX_PUBLIC_KEY")
}
if origSK != "" {
os.Setenv("UNSANDBOX_SECRET_KEY", origSK)
} else {
os.Unsetenv("UNSANDBOX_SECRET_KEY")
}
}()
// Args should override env
creds, err := un_async.ResolveCredentials("arg-pk", "arg-sk")
if err != nil {
t.Fatalf("ResolveCredentials failed: %v", err)
}
if creds.PublicKey != "arg-pk" {
t.Errorf("PublicKey = %q, want %q", creds.PublicKey, "arg-pk")
}
if creds.SecretKey != "arg-sk" {
t.Errorf("SecretKey = %q, want %q", creds.SecretKey, "arg-sk")
}
}
// TestResolveCredentialsNoSourcesError tests error when no credentials found
func TestResolveCredentialsNoSourcesError(t *testing.T) {
// Save original values
origPK := os.Getenv("UNSANDBOX_PUBLIC_KEY")
origSK := os.Getenv("UNSANDBOX_SECRET_KEY")
// Clear env values
os.Unsetenv("UNSANDBOX_PUBLIC_KEY")
os.Unsetenv("UNSANDBOX_SECRET_KEY")
// Restore after test
defer func() {
if origPK != "" {
os.Setenv("UNSANDBOX_PUBLIC_KEY", origPK)
}
if origSK != "" {
os.Setenv("UNSANDBOX_SECRET_KEY", origSK)
}
}()
// Should fail if no CSV files exist
_, err := un_async.ResolveCredentials("", "")
if err == nil {
t.Log("Note: ResolveCredentials succeeded - CSV file may exist in test environment")
return
}
credErr, ok := err.(*un_async.CredentialsError)
if !ok {
t.Errorf("Expected CredentialsError, got %T", err)
return
}
if credErr.Message == "" {
t.Error("CredentialsError.Message should not be empty")
}
}
// TestCSVCredentials tests loading credentials from CSV file
func TestCSVCredentials(t *testing.T) {
// Create temp directory
tmpDir, err := os.MkdirTemp("", "unsandbox-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
// Create test CSV file
csvPath := filepath.Join(tmpDir, "accounts.csv")
csvContent := "public_key_1,secret_key_1\n# comment line\npublic_key_2,secret_key_2\n"
if err := os.WriteFile(csvPath, []byte(csvContent), 0600); err != nil {
t.Fatalf("Failed to create CSV file: %v", err)
}
// Change to temp dir to test ./accounts.csv loading
origDir, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get working dir: %v", err)
}
defer os.Chdir(origDir)
if err := os.Chdir(tmpDir); err != nil {
t.Fatalf("Failed to change to temp dir: %v", err)
}
// Clear env vars
origPK := os.Getenv("UNSANDBOX_PUBLIC_KEY")
origSK := os.Getenv("UNSANDBOX_SECRET_KEY")
os.Unsetenv("UNSANDBOX_PUBLIC_KEY")
os.Unsetenv("UNSANDBOX_SECRET_KEY")
defer func() {
if origPK != "" {
os.Setenv("UNSANDBOX_PUBLIC_KEY", origPK)
}
if origSK != "" {
os.Setenv("UNSANDBOX_SECRET_KEY", origSK)
}
}()
// Test loading first account (index 0)
creds, err := un_async.ResolveCredentials("", "")
if err != nil {
t.Fatalf("ResolveCredentials failed: %v", err)
}
if creds.PublicKey != "public_key_1" {
t.Errorf("PublicKey = %q, want %q", creds.PublicKey, "public_key_1")
}
if creds.SecretKey != "secret_key_1" {
t.Errorf("SecretKey = %q, want %q", creds.SecretKey, "secret_key_1")
}
// Test loading second account (index 1)
os.Setenv("UNSANDBOX_ACCOUNT", "1")
defer os.Unsetenv("UNSANDBOX_ACCOUNT")
creds, err = un_async.ResolveCredentials("", "")
if err != nil {
t.Fatalf("ResolveCredentials failed: %v", err)
}
if creds.PublicKey != "public_key_2" {
t.Errorf("PublicKey = %q, want %q", creds.PublicKey, "public_key_2")
}
if creds.SecretKey != "secret_key_2" {
t.Errorf("SecretKey = %q, want %q", creds.SecretKey, "secret_key_2")
}
}
// TestLanguageMapCompleteness tests that common languages are mapped
func TestLanguageMapCompleteness(t *testing.T) {
requiredMappings := map[string]string{
"py": "python",
"js": "javascript",
"ts": "typescript",
"rb": "ruby",
"php": "php",
"go": "go",
"rs": "rust",
"c": "c",
"cpp": "cpp",
"java": "java",
"sh": "bash",
}
for ext, expected := range requiredMappings {
if lang, ok := un_async.LanguageMap[ext]; !ok {
t.Errorf("LanguageMap missing extension %q", ext)
} else if lang != expected {
t.Errorf("LanguageMap[%q] = %q, want %q", ext, lang, expected)
}
}
}
// TestPollDelays tests that poll delays are reasonable
func TestPollDelays(t *testing.T) {
delays := un_async.PollDelaysMs
if len(delays) < 5 {
t.Errorf("PollDelaysMs has %d elements, want at least 5", len(delays))
}
// First delay should be small (for quick jobs)
if delays[0] > 500 {
t.Errorf("First poll delay %d ms too large, should be < 500ms", delays[0])
}
// Last delay should be reasonable (not too long)
lastDelay := delays[len(delays)-1]
if lastDelay > 5000 {
t.Errorf("Last poll delay %d ms too large, should be < 5000ms", lastDelay)
}
}
// TestAsyncChannelBehavior tests that async functions return properly buffered channels
func TestAsyncChannelBehavior(t *testing.T) {
// Create test credentials
creds := &un_async.Credentials{
PublicKey: "test-pk",
SecretKey: "test-sk",
}
// Test that ExecuteCode returns a channel (even if request fails)
resultChan := un_async.ExecuteCode(creds, "python", "print(1)")
if resultChan == nil {
t.Error("ExecuteCode returned nil channel")
}
// The channel should be buffered and eventually close
select {
case result := <-resultChan:
// We expect an error since we're using test credentials
if result.Err == nil {
t.Log("Note: ExecuteCode succeeded - may have valid credentials")
}
case <-time.After(30 * time.Second):
t.Error("ExecuteCode channel did not receive result within timeout")
}
}