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:
parent
a5155aed4f
commit
6e746ace44
90 changed files with 28955 additions and 3028 deletions
7
clients/javascript/sync/jest.config.js
Normal file
7
clients/javascript/sync/jest.config.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export default {
|
||||
testEnvironment: 'node',
|
||||
transform: {},
|
||||
testMatch: ['**/tests/**/*.test.js'],
|
||||
moduleFileExtensions: ['js', 'mjs'],
|
||||
verbose: true,
|
||||
};
|
||||
|
|
@ -5,9 +5,12 @@
|
|||
"type": "module",
|
||||
"main": "src/un.js",
|
||||
"scripts": {
|
||||
"test": "echo 'No tests configured yet'"
|
||||
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
|
||||
},
|
||||
"keywords": ["unsandbox", "code-execution", "sandbox", "api"],
|
||||
"author": "unsandbox.com",
|
||||
"license": "Unlicense"
|
||||
"license": "Unlicense",
|
||||
"devDependencies": {
|
||||
"jest": "^29.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -933,7 +933,7 @@ async function serviceSnapshot(serviceId, publicKey, secretKey, name, hot = fals
|
|||
}
|
||||
|
||||
/**
|
||||
* List all snapshots (NEW).
|
||||
* List all snapshots.
|
||||
*
|
||||
* Returns: Promise<Array> (list of snapshot dicts)
|
||||
*/
|
||||
|
|
@ -944,7 +944,20 @@ async function listSnapshots(publicKey, secretKey) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Restore a snapshot (NEW).
|
||||
* Get details of a specific snapshot.
|
||||
*
|
||||
* Args:
|
||||
* snapshotId: Snapshot ID to get details for
|
||||
*
|
||||
* Returns: Promise<Object> (snapshot details with id, name, type, source_id, etc.)
|
||||
*/
|
||||
async function getSnapshot(snapshotId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('GET', `/snapshots/${snapshotId}`, publicKey, secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a snapshot.
|
||||
*
|
||||
* Returns: Promise<Object> (response with restored resource info)
|
||||
*/
|
||||
|
|
@ -1410,6 +1423,20 @@ async function executeInService(serviceId, command, timeout = 30000, publicKey,
|
|||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize a service's vCPU allocation.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID to resize
|
||||
* vcpu: Number of vCPUs (1-8 typically)
|
||||
*
|
||||
* Returns: Promise<Object> (updated service info)
|
||||
*/
|
||||
async function resizeService(serviceId, vcpu, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('PATCH', `/services/${serviceId}`, publicKey, secretKey, { vcpu });
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Additional Snapshot Functions
|
||||
// ============================================================================
|
||||
|
|
@ -1753,9 +1780,198 @@ async function image(prompt, options = {}) {
|
|||
return makeRequest('POST', '/image', pk, sk, payload);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PaaS Logs Functions
|
||||
// ============================================================================
|
||||
|
||||
let _lastError = null;
|
||||
|
||||
/**
|
||||
* Fetch batch logs from the PaaS platform.
|
||||
*
|
||||
* Args:
|
||||
* source: Log source - "all", "api", "portal", "pool/cammy", "pool/ai"
|
||||
* lines: Number of lines to fetch (1-10000)
|
||||
* since: Time window - "1m", "5m", "1h", "1d"
|
||||
* grep: Optional filter pattern
|
||||
* publicKey: API public key
|
||||
* secretKey: API secret key
|
||||
*
|
||||
* Returns: Promise<Object> (log entries)
|
||||
*/
|
||||
async function logsFetch(source = 'all', lines = 100, since = '5m', grep = null, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
let urlPath = `/logs?source=${source}&lines=${lines}&since=${since}`;
|
||||
if (grep) urlPath += `&grep=${encodeURIComponent(grep)}`;
|
||||
return makeRequest('GET', urlPath, publicKey, secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream logs via Server-Sent Events.
|
||||
*
|
||||
* Args:
|
||||
* source: Log source - "all", "api", "portal", "pool/cammy", "pool/ai"
|
||||
* grep: Optional filter pattern
|
||||
* callback: Function called for each log line (signature: callback(source, line))
|
||||
* publicKey: API public key
|
||||
* secretKey: API secret key
|
||||
*
|
||||
* Returns: Promise<void> (blocks until interrupted or server closes)
|
||||
*/
|
||||
async function logsStream(source = 'all', grep = null, callback = null, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
|
||||
let urlPath = `/logs/stream?source=${source}`;
|
||||
if (grep) urlPath += `&grep=${encodeURIComponent(grep)}`;
|
||||
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
const signature = await signRequest(secretKey, timestamp, 'GET', urlPath, null);
|
||||
|
||||
const url = `${API_BASE}${urlPath}`;
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${publicKey}`,
|
||||
'X-Timestamp': timestamp.toString(),
|
||||
'X-Signature': signature,
|
||||
'Accept': 'text/event-stream',
|
||||
};
|
||||
|
||||
// Node.js SSE streaming
|
||||
if (IS_NODE) {
|
||||
const https = await import('https');
|
||||
return new Promise((resolve, reject) => {
|
||||
const urlObj = new URL(url);
|
||||
const options = {
|
||||
hostname: urlObj.hostname,
|
||||
path: urlObj.pathname + urlObj.search,
|
||||
method: 'GET',
|
||||
headers,
|
||||
};
|
||||
|
||||
const req = https.default.request(options, (res) => {
|
||||
res.on('data', (chunk) => {
|
||||
const lines = chunk.toString().split('\n');
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const data = line.substring(6);
|
||||
try {
|
||||
const entry = JSON.parse(data);
|
||||
if (callback) {
|
||||
callback(entry.source || source, entry.line || data);
|
||||
} else {
|
||||
console.log(`[${entry.source || source}] ${entry.line || data}`);
|
||||
}
|
||||
} catch (e) {
|
||||
if (callback) {
|
||||
callback(source, data);
|
||||
} else {
|
||||
console.log(`[${source}] ${data}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
res.on('end', resolve);
|
||||
res.on('error', reject);
|
||||
});
|
||||
|
||||
req.on('error', reject);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// Browser EventSource not directly supported with custom headers
|
||||
throw new Error('logsStream is only supported in Node.js');
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Utility Functions
|
||||
// ============================================================================
|
||||
|
||||
const SDK_VERSION = '4.2.0';
|
||||
|
||||
/**
|
||||
* Get the SDK version string.
|
||||
*
|
||||
* Returns: string (e.g., "4.2.0")
|
||||
*/
|
||||
function sdkVersion() {
|
||||
return SDK_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the API is healthy and responding.
|
||||
*
|
||||
* Returns: Promise<boolean>
|
||||
*/
|
||||
async function healthCheck() {
|
||||
try {
|
||||
if (IS_NODE) {
|
||||
const https = await import('https');
|
||||
return new Promise((resolve) => {
|
||||
const req = https.default.get(`${API_BASE}/health`, (res) => {
|
||||
resolve(res.statusCode === 200);
|
||||
});
|
||||
req.on('error', () => {
|
||||
_lastError = 'Health check failed: network error';
|
||||
resolve(false);
|
||||
});
|
||||
req.setTimeout(10000, () => {
|
||||
_lastError = 'Health check failed: timeout';
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const response = await fetch(`${API_BASE}/health`);
|
||||
return response.status === 200;
|
||||
}
|
||||
} catch (e) {
|
||||
_lastError = `Health check failed: ${e.message}`;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last error message.
|
||||
*
|
||||
* Returns: string|null
|
||||
*/
|
||||
function lastError() {
|
||||
return _lastError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a message using HMAC-SHA256.
|
||||
*
|
||||
* This is the underlying signing function used for request authentication.
|
||||
* Exposed for testing and debugging purposes.
|
||||
*
|
||||
* Args:
|
||||
* secretKey: The secret key for signing
|
||||
* message: The message to sign
|
||||
*
|
||||
* Returns: Promise<string> (64-character lowercase hex string)
|
||||
*/
|
||||
async function hmacSign(secretKey, message) {
|
||||
if (IS_NODE) {
|
||||
return crypto.createHmac('sha256', secretKey).update(message).digest('hex');
|
||||
} else {
|
||||
// Browser Web Crypto API
|
||||
const encoder = new TextEncoder();
|
||||
const keyData = encoder.encode(secretKey);
|
||||
const msgData = encoder.encode(message);
|
||||
const cryptoKey = await window.crypto.subtle.importKey(
|
||||
'raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']
|
||||
);
|
||||
const signature = await window.crypto.subtle.sign('HMAC', cryptoKey, msgData);
|
||||
return Array.from(new Uint8Array(signature))
|
||||
.map(b => b.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
}
|
||||
}
|
||||
|
||||
// ES Module exports
|
||||
export {
|
||||
// Code execution
|
||||
// Code execution (8)
|
||||
executeCode,
|
||||
executeAsync,
|
||||
getJob,
|
||||
|
|
@ -1764,7 +1980,7 @@ export {
|
|||
listJobs,
|
||||
getLanguages,
|
||||
detectLanguage,
|
||||
// Session management
|
||||
// Session management (9)
|
||||
listSessions,
|
||||
getSession,
|
||||
createSession,
|
||||
|
|
@ -1774,7 +1990,7 @@ export {
|
|||
boostSession,
|
||||
unboostSession,
|
||||
shellSession,
|
||||
// Service management
|
||||
// Service management (17)
|
||||
listServices,
|
||||
createService,
|
||||
getService,
|
||||
|
|
@ -1793,16 +2009,18 @@ export {
|
|||
exportServiceEnv,
|
||||
redeployService,
|
||||
executeInService,
|
||||
// Snapshot management
|
||||
resizeService,
|
||||
// Snapshot management (9)
|
||||
sessionSnapshot,
|
||||
serviceSnapshot,
|
||||
listSnapshots,
|
||||
getSnapshot,
|
||||
restoreSnapshot,
|
||||
deleteSnapshot,
|
||||
lockSnapshot,
|
||||
unlockSnapshot,
|
||||
cloneSnapshot,
|
||||
// Images API (LXD container images)
|
||||
// Images API (13)
|
||||
imagePublish,
|
||||
listImages,
|
||||
getImage,
|
||||
|
|
@ -1816,9 +2034,17 @@ export {
|
|||
transferImage,
|
||||
spawnFromImage,
|
||||
cloneImage,
|
||||
// PaaS Logs (2)
|
||||
logsFetch,
|
||||
logsStream,
|
||||
// Key validation
|
||||
validateKeys,
|
||||
// Image generation
|
||||
// Utilities
|
||||
sdkVersion,
|
||||
healthCheck,
|
||||
lastError,
|
||||
hmacSign,
|
||||
// Image generation (AI)
|
||||
image,
|
||||
// Errors
|
||||
CredentialsError,
|
||||
|
|
@ -1830,7 +2056,7 @@ export {
|
|||
|
||||
// Default export for convenience
|
||||
export default {
|
||||
// Code execution
|
||||
// Code execution (8)
|
||||
executeCode,
|
||||
executeAsync,
|
||||
getJob,
|
||||
|
|
@ -1839,7 +2065,7 @@ export default {
|
|||
listJobs,
|
||||
getLanguages,
|
||||
detectLanguage,
|
||||
// Session management
|
||||
// Session management (9)
|
||||
listSessions,
|
||||
getSession,
|
||||
createSession,
|
||||
|
|
@ -1849,7 +2075,7 @@ export default {
|
|||
boostSession,
|
||||
unboostSession,
|
||||
shellSession,
|
||||
// Service management
|
||||
// Service management (17)
|
||||
listServices,
|
||||
createService,
|
||||
getService,
|
||||
|
|
@ -1860,6 +2086,7 @@ export default {
|
|||
lockService,
|
||||
unlockService,
|
||||
setUnfreezeOnDemand,
|
||||
setShowFreezePage,
|
||||
getServiceLogs,
|
||||
getServiceEnv,
|
||||
setServiceEnv,
|
||||
|
|
@ -1867,16 +2094,18 @@ export default {
|
|||
exportServiceEnv,
|
||||
redeployService,
|
||||
executeInService,
|
||||
// Snapshot management
|
||||
resizeService,
|
||||
// Snapshot management (9)
|
||||
sessionSnapshot,
|
||||
serviceSnapshot,
|
||||
listSnapshots,
|
||||
getSnapshot,
|
||||
restoreSnapshot,
|
||||
deleteSnapshot,
|
||||
lockSnapshot,
|
||||
unlockSnapshot,
|
||||
cloneSnapshot,
|
||||
// Images API (LXD container images)
|
||||
// Images API (13)
|
||||
imagePublish,
|
||||
listImages,
|
||||
getImage,
|
||||
|
|
@ -1890,9 +2119,17 @@ export default {
|
|||
transferImage,
|
||||
spawnFromImage,
|
||||
cloneImage,
|
||||
// PaaS Logs (2)
|
||||
logsFetch,
|
||||
logsStream,
|
||||
// Key validation
|
||||
validateKeys,
|
||||
// Image generation
|
||||
// Utilities
|
||||
sdkVersion,
|
||||
healthCheck,
|
||||
lastError,
|
||||
hmacSign,
|
||||
// Image generation (AI)
|
||||
image,
|
||||
// Errors
|
||||
CredentialsError,
|
||||
|
|
|
|||
421
clients/javascript/sync/tests/new_functions.test.js
Normal file
421
clients/javascript/sync/tests/new_functions.test.js
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
/**
|
||||
* Tests for new SDK functions (feature parity with C implementation)
|
||||
*/
|
||||
|
||||
import { createRequire } from 'module';
|
||||
import crypto from 'crypto';
|
||||
|
||||
// Since un.js uses top-level await, we need to import dynamically
|
||||
let un;
|
||||
|
||||
beforeAll(async () => {
|
||||
un = await import('../src/un.js');
|
||||
});
|
||||
|
||||
describe('Utility Functions', () => {
|
||||
describe('sdkVersion', () => {
|
||||
test('should return a string', () => {
|
||||
const v = un.sdkVersion();
|
||||
expect(typeof v).toBe('string');
|
||||
expect(v.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('should be semantic version format', () => {
|
||||
const v = un.sdkVersion();
|
||||
const parts = v.split('.');
|
||||
expect(parts.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hmacSign', () => {
|
||||
test('should produce 64-character hex string', async () => {
|
||||
const signature = await un.hmacSign('secret_key', 'message_to_sign');
|
||||
expect(typeof signature).toBe('string');
|
||||
expect(signature.length).toBe(64);
|
||||
// Should be lowercase hex
|
||||
expect(/^[0-9a-f]+$/.test(signature)).toBe(true);
|
||||
});
|
||||
|
||||
test('should be deterministic', async () => {
|
||||
const sig1 = await un.hmacSign('secret', 'message');
|
||||
const sig2 = await un.hmacSign('secret', 'message');
|
||||
expect(sig1).toBe(sig2);
|
||||
});
|
||||
|
||||
test('should produce different signatures for different secrets', async () => {
|
||||
const sig1 = await un.hmacSign('secret1', 'message');
|
||||
const sig2 = await un.hmacSign('secret2', 'message');
|
||||
expect(sig1).not.toBe(sig2);
|
||||
});
|
||||
|
||||
test('should produce different signatures for different messages', async () => {
|
||||
const sig1 = await un.hmacSign('secret', 'message1');
|
||||
const sig2 = await un.hmacSign('secret', 'message2');
|
||||
expect(sig1).not.toBe(sig2);
|
||||
});
|
||||
|
||||
test('should match Node.js crypto HMAC', async () => {
|
||||
const secret = 'test_secret';
|
||||
const message = '1234567890:POST:/execute:';
|
||||
const signature = await un.hmacSign(secret, message);
|
||||
const expected = crypto.createHmac('sha256', secret).update(message).digest('hex');
|
||||
expect(signature).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('lastError', () => {
|
||||
test('should return null or string', () => {
|
||||
const error = un.lastError();
|
||||
expect(error === null || typeof error === 'string').toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Function Exports', () => {
|
||||
describe('Execution functions (8)', () => {
|
||||
test('executeCode is exported', () => {
|
||||
expect(typeof un.executeCode).toBe('function');
|
||||
});
|
||||
|
||||
test('executeAsync is exported', () => {
|
||||
expect(typeof un.executeAsync).toBe('function');
|
||||
});
|
||||
|
||||
test('getJob is exported', () => {
|
||||
expect(typeof un.getJob).toBe('function');
|
||||
});
|
||||
|
||||
test('waitForJob is exported', () => {
|
||||
expect(typeof un.waitForJob).toBe('function');
|
||||
});
|
||||
|
||||
test('cancelJob is exported', () => {
|
||||
expect(typeof un.cancelJob).toBe('function');
|
||||
});
|
||||
|
||||
test('listJobs is exported', () => {
|
||||
expect(typeof un.listJobs).toBe('function');
|
||||
});
|
||||
|
||||
test('getLanguages is exported', () => {
|
||||
expect(typeof un.getLanguages).toBe('function');
|
||||
});
|
||||
|
||||
test('detectLanguage is exported', () => {
|
||||
expect(typeof un.detectLanguage).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Session functions (9)', () => {
|
||||
test('listSessions is exported', () => {
|
||||
expect(typeof un.listSessions).toBe('function');
|
||||
});
|
||||
|
||||
test('getSession is exported', () => {
|
||||
expect(typeof un.getSession).toBe('function');
|
||||
});
|
||||
|
||||
test('createSession is exported', () => {
|
||||
expect(typeof un.createSession).toBe('function');
|
||||
});
|
||||
|
||||
test('deleteSession is exported', () => {
|
||||
expect(typeof un.deleteSession).toBe('function');
|
||||
});
|
||||
|
||||
test('freezeSession is exported', () => {
|
||||
expect(typeof un.freezeSession).toBe('function');
|
||||
});
|
||||
|
||||
test('unfreezeSession is exported', () => {
|
||||
expect(typeof un.unfreezeSession).toBe('function');
|
||||
});
|
||||
|
||||
test('boostSession is exported', () => {
|
||||
expect(typeof un.boostSession).toBe('function');
|
||||
});
|
||||
|
||||
test('unboostSession is exported', () => {
|
||||
expect(typeof un.unboostSession).toBe('function');
|
||||
});
|
||||
|
||||
test('shellSession is exported', () => {
|
||||
expect(typeof un.shellSession).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Service functions (17)', () => {
|
||||
test('listServices is exported', () => {
|
||||
expect(typeof un.listServices).toBe('function');
|
||||
});
|
||||
|
||||
test('createService is exported', () => {
|
||||
expect(typeof un.createService).toBe('function');
|
||||
});
|
||||
|
||||
test('getService is exported', () => {
|
||||
expect(typeof un.getService).toBe('function');
|
||||
});
|
||||
|
||||
test('updateService is exported', () => {
|
||||
expect(typeof un.updateService).toBe('function');
|
||||
});
|
||||
|
||||
test('deleteService is exported', () => {
|
||||
expect(typeof un.deleteService).toBe('function');
|
||||
});
|
||||
|
||||
test('freezeService is exported', () => {
|
||||
expect(typeof un.freezeService).toBe('function');
|
||||
});
|
||||
|
||||
test('unfreezeService is exported', () => {
|
||||
expect(typeof un.unfreezeService).toBe('function');
|
||||
});
|
||||
|
||||
test('lockService is exported', () => {
|
||||
expect(typeof un.lockService).toBe('function');
|
||||
});
|
||||
|
||||
test('unlockService is exported', () => {
|
||||
expect(typeof un.unlockService).toBe('function');
|
||||
});
|
||||
|
||||
test('setUnfreezeOnDemand is exported', () => {
|
||||
expect(typeof un.setUnfreezeOnDemand).toBe('function');
|
||||
});
|
||||
|
||||
test('getServiceLogs is exported', () => {
|
||||
expect(typeof un.getServiceLogs).toBe('function');
|
||||
});
|
||||
|
||||
test('getServiceEnv is exported', () => {
|
||||
expect(typeof un.getServiceEnv).toBe('function');
|
||||
});
|
||||
|
||||
test('setServiceEnv is exported', () => {
|
||||
expect(typeof un.setServiceEnv).toBe('function');
|
||||
});
|
||||
|
||||
test('deleteServiceEnv is exported', () => {
|
||||
expect(typeof un.deleteServiceEnv).toBe('function');
|
||||
});
|
||||
|
||||
test('exportServiceEnv is exported', () => {
|
||||
expect(typeof un.exportServiceEnv).toBe('function');
|
||||
});
|
||||
|
||||
test('redeployService is exported', () => {
|
||||
expect(typeof un.redeployService).toBe('function');
|
||||
});
|
||||
|
||||
test('executeInService is exported', () => {
|
||||
expect(typeof un.executeInService).toBe('function');
|
||||
});
|
||||
|
||||
test('resizeService is exported (NEW)', () => {
|
||||
expect(typeof un.resizeService).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Snapshot functions (9)', () => {
|
||||
test('sessionSnapshot is exported', () => {
|
||||
expect(typeof un.sessionSnapshot).toBe('function');
|
||||
});
|
||||
|
||||
test('serviceSnapshot is exported', () => {
|
||||
expect(typeof un.serviceSnapshot).toBe('function');
|
||||
});
|
||||
|
||||
test('listSnapshots is exported', () => {
|
||||
expect(typeof un.listSnapshots).toBe('function');
|
||||
});
|
||||
|
||||
test('getSnapshot is exported (NEW)', () => {
|
||||
expect(typeof un.getSnapshot).toBe('function');
|
||||
});
|
||||
|
||||
test('restoreSnapshot is exported', () => {
|
||||
expect(typeof un.restoreSnapshot).toBe('function');
|
||||
});
|
||||
|
||||
test('deleteSnapshot is exported', () => {
|
||||
expect(typeof un.deleteSnapshot).toBe('function');
|
||||
});
|
||||
|
||||
test('lockSnapshot is exported', () => {
|
||||
expect(typeof un.lockSnapshot).toBe('function');
|
||||
});
|
||||
|
||||
test('unlockSnapshot is exported', () => {
|
||||
expect(typeof un.unlockSnapshot).toBe('function');
|
||||
});
|
||||
|
||||
test('cloneSnapshot is exported', () => {
|
||||
expect(typeof un.cloneSnapshot).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Image functions (13)', () => {
|
||||
test('imagePublish is exported', () => {
|
||||
expect(typeof un.imagePublish).toBe('function');
|
||||
});
|
||||
|
||||
test('listImages is exported', () => {
|
||||
expect(typeof un.listImages).toBe('function');
|
||||
});
|
||||
|
||||
test('getImage is exported', () => {
|
||||
expect(typeof un.getImage).toBe('function');
|
||||
});
|
||||
|
||||
test('deleteImage is exported', () => {
|
||||
expect(typeof un.deleteImage).toBe('function');
|
||||
});
|
||||
|
||||
test('lockImage is exported', () => {
|
||||
expect(typeof un.lockImage).toBe('function');
|
||||
});
|
||||
|
||||
test('unlockImage is exported', () => {
|
||||
expect(typeof un.unlockImage).toBe('function');
|
||||
});
|
||||
|
||||
test('setImageVisibility is exported', () => {
|
||||
expect(typeof un.setImageVisibility).toBe('function');
|
||||
});
|
||||
|
||||
test('grantImageAccess is exported', () => {
|
||||
expect(typeof un.grantImageAccess).toBe('function');
|
||||
});
|
||||
|
||||
test('revokeImageAccess is exported', () => {
|
||||
expect(typeof un.revokeImageAccess).toBe('function');
|
||||
});
|
||||
|
||||
test('listImageTrusted is exported', () => {
|
||||
expect(typeof un.listImageTrusted).toBe('function');
|
||||
});
|
||||
|
||||
test('transferImage is exported', () => {
|
||||
expect(typeof un.transferImage).toBe('function');
|
||||
});
|
||||
|
||||
test('spawnFromImage is exported', () => {
|
||||
expect(typeof un.spawnFromImage).toBe('function');
|
||||
});
|
||||
|
||||
test('cloneImage is exported', () => {
|
||||
expect(typeof un.cloneImage).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('PaaS Logs functions (2)', () => {
|
||||
test('logsFetch is exported (NEW)', () => {
|
||||
expect(typeof un.logsFetch).toBe('function');
|
||||
});
|
||||
|
||||
test('logsStream is exported (NEW)', () => {
|
||||
expect(typeof un.logsStream).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Utility functions', () => {
|
||||
test('validateKeys is exported', () => {
|
||||
expect(typeof un.validateKeys).toBe('function');
|
||||
});
|
||||
|
||||
test('sdkVersion is exported (NEW)', () => {
|
||||
expect(typeof un.sdkVersion).toBe('function');
|
||||
});
|
||||
|
||||
test('healthCheck is exported (NEW)', () => {
|
||||
expect(typeof un.healthCheck).toBe('function');
|
||||
});
|
||||
|
||||
test('lastError is exported (NEW)', () => {
|
||||
expect(typeof un.lastError).toBe('function');
|
||||
});
|
||||
|
||||
test('hmacSign is exported (NEW)', () => {
|
||||
expect(typeof un.hmacSign).toBe('function');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Language Detection', () => {
|
||||
test('detects Python files', () => {
|
||||
expect(un.detectLanguage('test.py')).toBe('python');
|
||||
});
|
||||
|
||||
test('detects JavaScript files', () => {
|
||||
expect(un.detectLanguage('test.js')).toBe('javascript');
|
||||
});
|
||||
|
||||
test('detects TypeScript files', () => {
|
||||
expect(un.detectLanguage('test.ts')).toBe('typescript');
|
||||
});
|
||||
|
||||
test('detects Ruby files', () => {
|
||||
expect(un.detectLanguage('test.rb')).toBe('ruby');
|
||||
});
|
||||
|
||||
test('detects Go files', () => {
|
||||
expect(un.detectLanguage('test.go')).toBe('go');
|
||||
});
|
||||
|
||||
test('detects Rust files', () => {
|
||||
expect(un.detectLanguage('test.rs')).toBe('rust');
|
||||
});
|
||||
|
||||
test('returns null for unknown extensions', () => {
|
||||
expect(un.detectLanguage('test.unknown')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// Functional tests require API credentials
|
||||
const hasCredentials = process.env.UNSANDBOX_PUBLIC_KEY && process.env.UNSANDBOX_SECRET_KEY;
|
||||
|
||||
(hasCredentials ? describe : describe.skip)('Functional API Tests', () => {
|
||||
test('healthCheck returns boolean', async () => {
|
||||
const result = await un.healthCheck();
|
||||
expect(typeof result).toBe('boolean');
|
||||
});
|
||||
|
||||
test('validateKeys returns object', async () => {
|
||||
const result = await un.validateKeys();
|
||||
expect(typeof result).toBe('object');
|
||||
});
|
||||
|
||||
test('getLanguages returns array with python', async () => {
|
||||
const languages = await un.getLanguages();
|
||||
expect(Array.isArray(languages)).toBe(true);
|
||||
expect(languages).toContain('python');
|
||||
});
|
||||
|
||||
test('listSessions returns array', async () => {
|
||||
const sessions = await un.listSessions();
|
||||
expect(Array.isArray(sessions)).toBe(true);
|
||||
});
|
||||
|
||||
test('listServices returns array', async () => {
|
||||
const services = await un.listServices();
|
||||
expect(Array.isArray(services)).toBe(true);
|
||||
});
|
||||
|
||||
test('listSnapshots returns array', async () => {
|
||||
const snapshots = await un.listSnapshots();
|
||||
expect(Array.isArray(snapshots)).toBe(true);
|
||||
});
|
||||
|
||||
test('listImages returns array', async () => {
|
||||
const images = await un.listImages();
|
||||
expect(Array.isArray(images)).toBe(true);
|
||||
});
|
||||
|
||||
test('executeCode returns result', async () => {
|
||||
const result = await un.executeCode('python', 'print("hello")');
|
||||
expect(typeof result).toBe('object');
|
||||
expect(['completed', 'pending']).toContain(result.status);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue