C SDK changes: - Add #include un.h when building as library - Export library functions: version, detect_language, hmac_sign, health_check - Add NULL safety checks to wrapper functions - Rewrite tests to call ACTUAL exported functions (no more local re-implementations) - Tests now verify real HMAC-SHA256 against known test vectors Documentation: - Add SDK Testing Philosophy to CLAUDE.md - Create docs/TESTING.md with unit/integration/functional test definitions - Document the THREE testing levels required for all SDKs - Explicitly forbid mocking and local re-implementation in tests 33 unit tests pass, testing real exported functions.
8.6 KiB
8.6 KiB
SDK Testing Guidelines
This document defines the testing requirements for all un-inception SDK clients.
Core Principle
SDKs are LIBRARIES for embedding in other people's code.
Every SDK must be:
- Importable - Can be used as a library in other projects
- Testable - Exports functions that tests can call directly
- Functional - Actually works against the live API
Three Testing Levels
1. Unit Tests (test-library)
Purpose: Test exported library functions in isolation.
Requirements:
- Test ACTUAL exported functions from the SDK
- NO mocking
- NO re-implementing functions locally
- Tests run without network access
- Tests run without API credentials
What to test:
- HMAC-SHA256 signature generation
- Request building (headers, body formatting)
- Response parsing (JSON → native types)
- Language detection from file extensions
- Error handling and edge cases
- Memory management (for C/C++/Rust)
Example (C):
#include "un.h"
void test_hmac_signature() {
// Test the REAL exported function
char *sig = unsandbox_hmac_sign("secret", "1234567890:POST:/execute:{}");
assert(sig != NULL);
assert(strlen(sig) == 64); // hex-encoded SHA256
free(sig);
}
void test_language_detection() {
assert(strcmp(unsandbox_detect_language("test.py"), "python") == 0);
assert(strcmp(unsandbox_detect_language("main.go"), "go") == 0);
assert(unsandbox_detect_language("unknown.xyz") == NULL);
}
Example (Python):
from un import UnsandboxClient, hmac_sign, detect_language
def test_hmac_signature():
sig = hmac_sign("secret", "1234567890:POST:/execute:{}")
assert len(sig) == 64
assert sig == "expected_hex_value"
def test_language_detection():
assert detect_language("test.py") == "python"
assert detect_language("main.go") == "go"
assert detect_language("unknown.xyz") is None
2. Integration Tests (test-integration)
Purpose: Test that SDK components work together correctly.
Requirements:
- Test internal SDK consistency
- May use test doubles for HTTP layer
- Should NOT call live API
- Tests the full request/response cycle internally
What to test:
- Auth headers are generated correctly together
- Request body is properly formatted
- Response parsing handles all expected formats
- Error responses are properly converted to exceptions/errors
- Async operations work correctly (if applicable)
Example (Python):
def test_auth_headers_integration():
client = UnsandboxClient(public_key="pk", secret_key="sk")
headers = client._build_auth_headers("POST", "/execute", '{"code":"x"}')
assert "Authorization" in headers
assert "X-Timestamp" in headers
assert "X-Signature" in headers
assert headers["Authorization"] == "Bearer pk"
def test_request_building():
client = UnsandboxClient(public_key="pk", secret_key="sk")
req = client._build_execute_request("python", "print(1)")
assert req["method"] == "POST"
assert req["path"] == "/execute"
assert "language" in req["body"]
assert req["body"]["language"] == "python"
3. Functional Tests (test-functional)
Purpose: Test the SDK against the live API.
Requirements:
- Requires
UNSANDBOX_PUBLIC_KEYandUNSANDBOX_SECRET_KEYenvironment variables - Makes REAL API calls to api.unsandbox.com
- Tests the complete lifecycle: execute, sessions, services
- Should be skipped gracefully if credentials not available
What to test:
- Execute code in multiple languages
- Create/list/destroy sessions
- Create/list/destroy services
- Error handling for invalid requests
- Rate limiting behavior
Example (Python):
import os
import pytest
@pytest.fixture
def client():
pk = os.environ.get("UNSANDBOX_PUBLIC_KEY")
sk = os.environ.get("UNSANDBOX_SECRET_KEY")
if not pk or not sk:
pytest.skip("API credentials not set")
return UnsandboxClient(public_key=pk, secret_key=sk)
def test_execute_python(client):
result = client.execute("python", "print(42)")
assert result.exit_code == 0
assert "42" in result.stdout
def test_execute_invalid_language(client):
with pytest.raises(UnsandboxError) as exc:
client.execute("not_a_real_language", "code")
assert "unsupported" in str(exc.value).lower()
def test_session_lifecycle(client):
# Create
session = client.session_create()
assert session.id is not None
# Execute in session
result = client.session_execute(session.id, "echo hello")
assert "hello" in result.stdout
# Destroy
client.session_destroy(session.id)
Test Directory Structure
clients/{language}/
├── src/
│ ├── un.{ext} # Main implementation
│ └── un.h # Header (for C/C++)
├── tests/
│ ├── unit/
│ │ ├── test_hmac.{ext}
│ │ ├── test_language_detection.{ext}
│ │ └── test_request_building.{ext}
│ ├── integration/
│ │ ├── test_auth_flow.{ext}
│ │ └── test_response_parsing.{ext}
│ └── functional/
│ ├── test_execute.{ext}
│ ├── test_sessions.{ext}
│ └── test_services.{ext}
├── Makefile
└── README.md
Makefile Requirements
Every client Makefile MUST implement these targets:
.PHONY: test test-cli test-library test-integration test-functional clean
# Run all tests
test: test-cli test-library test-integration test-functional
@echo "All tests complete"
# Test CLI mode - binary runs, --help works
test-cli:
@echo "Testing CLI mode..."
./un --help >/dev/null
./un --version >/dev/null
# Test library mode - unit tests of exported functions
test-library:
@echo "Testing library exports..."
./run_unit_tests
# Test integration - SDK internal consistency
test-integration:
@echo "Testing SDK integration..."
./run_integration_tests
# Test functional - real API calls
test-functional:
@echo "Testing against live API..."
@if [ -z "$$UNSANDBOX_PUBLIC_KEY" ]; then \
echo " Skipped (no credentials)"; \
else \
./run_functional_tests; \
fi
Anti-Patterns (FORBIDDEN)
1. Re-implementing Functions Locally
// ❌ WRONG - This tests a LOCAL copy, not the SDK
static void local_sha256_transform(...) { ... }
void test_sha256() {
// Testing LOCAL function, not the one in un.c!
local_sha256_transform(...);
}
2. Mocking Everything
# ❌ WRONG - This doesn't test the real SDK behavior
@mock.patch('un.requests.post')
def test_execute(mock_post):
mock_post.return_value = Mock(json=lambda: {"stdout": "42"})
# This tests the mock, not the SDK!
3. Tests Without Assertions
# ❌ WRONG - This doesn't actually verify anything
def test_execute():
client = UnsandboxClient()
client.execute("python", "print(1)")
# No assertion! Test always passes!
4. Skipping Test Levels
# ❌ WRONG - Missing test levels
test: test-functional # Only functional tests? No unit/integration!
Language-Specific Guidelines
C/C++
- Use
#ifndef UNSANDBOX_LIBRARYto guardmain()for library builds - Export functions without
statickeyword when built as library - Tests link against the compiled library, not source directly
- Use assertion macros or a test framework (Unity, CUnit)
Python
- Use pytest for all test levels
- Export functions at module level (not just class methods)
- Use
__all__to define public API
JavaScript/TypeScript
- Use Jest or Mocha for testing
- Export functions via
module.exportsor ES6export - Test both CommonJS and ESM imports if supporting both
Go
- Use standard
testingpackage - Export functions with capital letters
- Tests in
*_test.gofiles
Rust
- Use
#[cfg(test)]modules - Export public API with
pubkeyword - Use
cargo testfor all test levels
CI Integration
Tests are run automatically on push via GitLab CI:
test-{language}:
stage: test
script:
- cd clients/{language}
- make test-cli
- make test-library
- make test-integration
rules:
- changes:
- clients/{language}/**/*
test-{language}-functional:
stage: functional
script:
- cd clients/{language}
- make test-functional
rules:
- changes:
- clients/{language}/**/*
variables:
UNSANDBOX_PUBLIC_KEY: $CI_UNSANDBOX_PUBLIC_KEY
UNSANDBOX_SECRET_KEY: $CI_UNSANDBOX_SECRET_KEY
Summary
- Unit tests - Test exported functions, no mocking
- Integration tests - Test SDK internals work together
- Functional tests - Test against live API
- All three levels are REQUIRED for each SDK
- NO re-implementing functions locally - test the REAL code