e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
SDK Tests / Python SDK Tests (sandbox) (push) Waiting to run
SDK Tests / CLI Quality (push) Waiting to run
SDK Tests / CLI Tests (push) Waiting to run
SDK Tests / Python SDK Quality (code-interpreter) (push) Waiting to run
SDK Tests / Python SDK Quality (sandbox) (push) Waiting to run
SDK Tests / Python SDK Tests (code-interpreter) (push) Waiting to run
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Go SDK Quality And Tests (push) Waiting to run
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
446 lines
13 KiB
JavaScript
446 lines
13 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
ConnectionConfig,
|
|
DEFAULT_EGRESS_PORT,
|
|
DEFAULT_EXECD_PORT,
|
|
DEFAULT_TIMEOUT_SECONDS,
|
|
Sandbox,
|
|
} from "../dist/index.js";
|
|
|
|
function createAdapterFactory({ includeCredentialVault = true } = {}) {
|
|
const recordedRequests = [];
|
|
const endpointCalls = [];
|
|
const egressStackCalls = [];
|
|
const policyOnlyEgressService = {
|
|
async getPolicy() {
|
|
return {
|
|
defaultAction: "deny",
|
|
egress: [{ action: "allow", target: "pypi.org" }],
|
|
};
|
|
},
|
|
async patchRules() {},
|
|
async deleteRules() {},
|
|
};
|
|
const credentialVaultService = {
|
|
async create() {
|
|
return { revision: 1, credentials: [], bindings: [] };
|
|
},
|
|
async get() {
|
|
return { revision: 1, credentials: [], bindings: [] };
|
|
},
|
|
async patch() {
|
|
return { revision: 2, credentials: [], bindings: [] };
|
|
},
|
|
async delete() {},
|
|
async listCredentials() {
|
|
return [];
|
|
},
|
|
async getCredential(name) {
|
|
return { name, sourceType: "inline", revision: 1 };
|
|
},
|
|
async listBindings() {
|
|
return [];
|
|
},
|
|
async getBinding(name) {
|
|
return { name, revision: 1 };
|
|
},
|
|
};
|
|
const egressService = includeCredentialVault
|
|
? { ...policyOnlyEgressService, ...credentialVaultService }
|
|
: policyOnlyEgressService;
|
|
const sandboxes = {
|
|
async createSandbox(req) {
|
|
recordedRequests.push(req);
|
|
return { id: "sandbox-test-id", expiresAt: null };
|
|
},
|
|
async getSandbox() {
|
|
throw new Error("not implemented");
|
|
},
|
|
async listSandboxes() {
|
|
throw new Error("not implemented");
|
|
},
|
|
async deleteSandbox() {},
|
|
async pauseSandbox() {},
|
|
async resumeSandbox() {},
|
|
async renewSandboxExpiration() {
|
|
throw new Error("not implemented");
|
|
},
|
|
async getSandboxEndpoint(_sandboxId, port) {
|
|
endpointCalls.push(port);
|
|
return { endpoint: `127.0.0.1:${port}`, headers: { "x-port": String(port) } };
|
|
},
|
|
};
|
|
|
|
const adapterFactory = {
|
|
createLifecycleStack() {
|
|
return { sandboxes };
|
|
},
|
|
createExecdStack() {
|
|
return {
|
|
commands: {},
|
|
files: {},
|
|
health: {},
|
|
metrics: {},
|
|
};
|
|
},
|
|
createEgressStack(opts) {
|
|
egressStackCalls.push(opts);
|
|
return { egress: egressService };
|
|
},
|
|
};
|
|
|
|
return { adapterFactory, recordedRequests, endpointCalls, egressStackCalls };
|
|
}
|
|
|
|
test("Sandbox.create omits timeout when timeoutSeconds is null", async () => {
|
|
const { adapterFactory, recordedRequests } = createAdapterFactory();
|
|
|
|
await Sandbox.create({
|
|
adapterFactory,
|
|
connectionConfig: { domain: "http://127.0.0.1:8080" },
|
|
image: "python:3.12",
|
|
timeoutSeconds: null,
|
|
skipHealthCheck: true,
|
|
});
|
|
|
|
assert.equal(recordedRequests.length, 1);
|
|
assert.ok(!Object.hasOwn(recordedRequests[0], "timeout"));
|
|
});
|
|
|
|
test("Sandbox.create forwards secureAccess", async () => {
|
|
const { adapterFactory, recordedRequests } = createAdapterFactory();
|
|
|
|
await Sandbox.create({
|
|
adapterFactory,
|
|
connectionConfig: { domain: "http://127.0.0.1:8080" },
|
|
image: "python:3.12",
|
|
secureAccess: true,
|
|
skipHealthCheck: true,
|
|
});
|
|
|
|
assert.equal(recordedRequests.length, 1);
|
|
assert.equal(recordedRequests[0].secureAccess, true);
|
|
});
|
|
|
|
test("Sandbox.create forwards credentialProxy", async () => {
|
|
const { adapterFactory, recordedRequests } = createAdapterFactory();
|
|
|
|
await Sandbox.create({
|
|
adapterFactory,
|
|
connectionConfig: { domain: "http://127.0.0.1:8080" },
|
|
image: "python:3.12",
|
|
credentialProxy: { enabled: true },
|
|
skipHealthCheck: true,
|
|
});
|
|
|
|
assert.equal(recordedRequests.length, 1);
|
|
assert.deepEqual(recordedRequests[0].credentialProxy, { enabled: true });
|
|
});
|
|
|
|
test("Sandbox.create forwards windows platform values", async () => {
|
|
const { adapterFactory, recordedRequests } = createAdapterFactory();
|
|
|
|
await Sandbox.create({
|
|
adapterFactory,
|
|
connectionConfig: { domain: "http://127.0.0.1:8080" },
|
|
image: "python:3.12",
|
|
platform: { os: "windows", arch: "amd64" },
|
|
skipHealthCheck: true,
|
|
});
|
|
|
|
assert.equal(recordedRequests.length, 1);
|
|
assert.deepEqual(recordedRequests[0].platform, { os: "windows", arch: "amd64" });
|
|
});
|
|
|
|
test("Sandbox.create floors finite timeoutSeconds", async () => {
|
|
const { adapterFactory, recordedRequests } = createAdapterFactory();
|
|
|
|
await Sandbox.create({
|
|
adapterFactory,
|
|
connectionConfig: { domain: "http://127.0.0.1:8080" },
|
|
image: "python:3.12",
|
|
timeoutSeconds: 61.9,
|
|
skipHealthCheck: true,
|
|
});
|
|
|
|
assert.equal(recordedRequests.length, 1);
|
|
assert.equal(recordedRequests[0].timeout, 61);
|
|
});
|
|
|
|
test("Sandbox.create uses the default timeout when timeoutSeconds is undefined", async () => {
|
|
const { adapterFactory, recordedRequests } = createAdapterFactory();
|
|
|
|
await Sandbox.create({
|
|
adapterFactory,
|
|
connectionConfig: { domain: "http://127.0.0.1:8080" },
|
|
image: "python:3.12",
|
|
skipHealthCheck: true,
|
|
});
|
|
|
|
assert.equal(recordedRequests.length, 1);
|
|
assert.equal(recordedRequests[0].timeout, DEFAULT_TIMEOUT_SECONDS);
|
|
});
|
|
|
|
test("Sandbox.create rejects non-finite timeoutSeconds", async () => {
|
|
for (const timeoutSeconds of [Number.NaN, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY]) {
|
|
const { adapterFactory } = createAdapterFactory();
|
|
await assert.rejects(
|
|
Sandbox.create({
|
|
adapterFactory,
|
|
connectionConfig: { domain: "http://127.0.0.1:8080" },
|
|
image: "python:3.12",
|
|
timeoutSeconds,
|
|
skipHealthCheck: true,
|
|
}),
|
|
/timeoutSeconds must be a finite number/
|
|
);
|
|
}
|
|
});
|
|
|
|
test("Sandbox.create restores from snapshot without entrypoint", async () => {
|
|
const { adapterFactory, recordedRequests } = createAdapterFactory();
|
|
|
|
await Sandbox.create({
|
|
adapterFactory,
|
|
connectionConfig: { domain: "http://127.0.0.1:8080" },
|
|
snapshotId: "snap-123",
|
|
skipHealthCheck: true,
|
|
});
|
|
|
|
assert.equal(recordedRequests.length, 1);
|
|
assert.equal(recordedRequests[0].snapshotId, "snap-123");
|
|
assert.equal(recordedRequests[0].image, undefined);
|
|
assert.deepEqual(recordedRequests[0].entrypoint, ["tail", "-f", "/dev/null"]);
|
|
});
|
|
|
|
test("Sandbox.create restores from snapshot with explicit entrypoint", async () => {
|
|
const { adapterFactory, recordedRequests } = createAdapterFactory();
|
|
|
|
await Sandbox.create({
|
|
adapterFactory,
|
|
connectionConfig: { domain: "http://127.0.0.1:8080" },
|
|
snapshotId: "snap-123",
|
|
entrypoint: ["python", "app.py"],
|
|
skipHealthCheck: true,
|
|
});
|
|
|
|
assert.equal(recordedRequests.length, 1);
|
|
assert.equal(recordedRequests[0].snapshotId, "snap-123");
|
|
assert.deepEqual(recordedRequests[0].entrypoint, ["python", "app.py"]);
|
|
});
|
|
|
|
test("Sandbox.create requires exactly one startup source", async () => {
|
|
const { adapterFactory } = createAdapterFactory();
|
|
|
|
await assert.rejects(
|
|
Sandbox.create({
|
|
adapterFactory,
|
|
connectionConfig: { domain: "http://127.0.0.1:8080" },
|
|
skipHealthCheck: true,
|
|
}),
|
|
/Exactly one of image or snapshotId must be provided/
|
|
);
|
|
|
|
await assert.rejects(
|
|
Sandbox.create({
|
|
adapterFactory,
|
|
connectionConfig: { domain: "http://127.0.0.1:8080" },
|
|
image: "python:3.12",
|
|
snapshotId: "snap-123",
|
|
skipHealthCheck: true,
|
|
}),
|
|
/Exactly one of image or snapshotId must be provided/
|
|
);
|
|
});
|
|
|
|
test("Sandbox creates and reuses egress service during sandbox lifecycle", async () => {
|
|
const { adapterFactory, endpointCalls, egressStackCalls } = createAdapterFactory();
|
|
|
|
const sandbox = await Sandbox.create({
|
|
adapterFactory,
|
|
connectionConfig: { domain: "http://127.0.0.1:8080" },
|
|
image: "python:3.12",
|
|
skipHealthCheck: true,
|
|
});
|
|
|
|
await sandbox.getEgressPolicy();
|
|
await sandbox.patchEgressRules([{ action: "allow", target: "www.github.com" }]);
|
|
const vaultState = await sandbox.credentialVault.get();
|
|
|
|
assert.deepEqual(endpointCalls, [DEFAULT_EXECD_PORT, DEFAULT_EGRESS_PORT]);
|
|
assert.equal(egressStackCalls.length, 1);
|
|
assert.equal(egressStackCalls[0].egressBaseUrl, `http://127.0.0.1:${DEFAULT_EGRESS_PORT}`);
|
|
assert.deepEqual(egressStackCalls[0].endpointHeaders, { "x-port": String(DEFAULT_EGRESS_PORT) });
|
|
assert.deepEqual(vaultState, { revision: 1, credentials: [], bindings: [] });
|
|
});
|
|
|
|
test("Sandbox.create accepts custom egress adapters without Credential Vault methods", async () => {
|
|
const { adapterFactory } = createAdapterFactory({ includeCredentialVault: false });
|
|
|
|
const sandbox = await Sandbox.create({
|
|
adapterFactory,
|
|
connectionConfig: { domain: "http://127.0.0.1:8080" },
|
|
image: "python:3.12",
|
|
skipHealthCheck: true,
|
|
});
|
|
|
|
assert.deepEqual(await sandbox.getEgressPolicy(), {
|
|
defaultAction: "deny",
|
|
egress: [{ action: "allow", target: "pypi.org" }],
|
|
});
|
|
await assert.rejects(
|
|
() => sandbox.credentialVault.get(),
|
|
/Credential Vault is not available/
|
|
);
|
|
});
|
|
|
|
test("Sandbox.create passes OSSFS volume to request", async () => {
|
|
const { adapterFactory, recordedRequests } = createAdapterFactory();
|
|
|
|
await Sandbox.create({
|
|
adapterFactory,
|
|
connectionConfig: { domain: "http://127.0.0.1:8080" },
|
|
image: "python:3.12",
|
|
skipHealthCheck: true,
|
|
volumes: [
|
|
{
|
|
name: "oss-data",
|
|
ossfs: {
|
|
bucket: "my-bucket",
|
|
endpoint: "oss-cn-hangzhou.aliyuncs.com",
|
|
version: "2.0",
|
|
accessKeyId: "ak-id",
|
|
accessKeySecret: "ak-secret",
|
|
},
|
|
mountPath: "/data",
|
|
readOnly: false,
|
|
},
|
|
],
|
|
});
|
|
|
|
assert.equal(recordedRequests.length, 1);
|
|
assert.equal(recordedRequests[0].volumes.length, 1);
|
|
assert.equal(recordedRequests[0].volumes[0].name, "oss-data");
|
|
assert.equal(recordedRequests[0].volumes[0].ossfs.bucket, "my-bucket");
|
|
assert.equal(recordedRequests[0].volumes[0].ossfs.endpoint, "oss-cn-hangzhou.aliyuncs.com");
|
|
});
|
|
|
|
test("Sandbox.create rejects volume with no backend", async () => {
|
|
const { adapterFactory } = createAdapterFactory();
|
|
|
|
await assert.rejects(
|
|
Sandbox.create({
|
|
adapterFactory,
|
|
connectionConfig: { domain: "http://127.0.0.1:8080" },
|
|
image: "python:3.12",
|
|
skipHealthCheck: true,
|
|
volumes: [{ name: "empty", mountPath: "/mnt" }],
|
|
}),
|
|
/must specify exactly one backend \(host, pvc, ossfs\)/
|
|
);
|
|
});
|
|
|
|
test("Sandbox.create rejects volume with multiple backends", async () => {
|
|
const { adapterFactory } = createAdapterFactory();
|
|
|
|
await assert.rejects(
|
|
Sandbox.create({
|
|
adapterFactory,
|
|
connectionConfig: { domain: "http://127.0.0.1:8080" },
|
|
image: "python:3.12",
|
|
skipHealthCheck: true,
|
|
volumes: [
|
|
{
|
|
name: "conflicting",
|
|
host: { path: "/tmp" },
|
|
ossfs: {
|
|
bucket: "b",
|
|
endpoint: "e",
|
|
accessKeyId: "id",
|
|
accessKeySecret: "secret",
|
|
},
|
|
mountPath: "/mnt",
|
|
},
|
|
],
|
|
}),
|
|
/must specify exactly one backend \(host, pvc, ossfs\)/
|
|
);
|
|
});
|
|
|
|
test("Sandbox.create accepts host volume with windows drive path", async () => {
|
|
const { adapterFactory, recordedRequests } = createAdapterFactory();
|
|
|
|
await Sandbox.create({
|
|
adapterFactory,
|
|
connectionConfig: { domain: "http://127.0.0.1:8080" },
|
|
image: "python:3.12",
|
|
skipHealthCheck: true,
|
|
volumes: [{ name: "host-vol", host: { path: "D:/sandbox-mnt/ReMe" }, mountPath: "/mnt" }],
|
|
});
|
|
|
|
assert.equal(recordedRequests.length, 1);
|
|
assert.equal(recordedRequests[0].volumes[0].host.path, "D:/sandbox-mnt/ReMe");
|
|
});
|
|
|
|
test("Sandbox.create rejects host volume with relative path", async () => {
|
|
const { adapterFactory } = createAdapterFactory();
|
|
|
|
await assert.rejects(
|
|
Sandbox.create({
|
|
adapterFactory,
|
|
connectionConfig: { domain: "http://127.0.0.1:8080" },
|
|
image: "python:3.12",
|
|
skipHealthCheck: true,
|
|
volumes: [{ name: "host-vol", host: { path: "relative/path" }, mountPath: "/mnt" }],
|
|
}),
|
|
/Host path must be an absolute path starting with '\/' or a Windows drive letter/
|
|
);
|
|
});
|
|
|
|
test("Sandbox.create validates host path before transport initialization", async () => {
|
|
const { adapterFactory } = createAdapterFactory();
|
|
const connectionConfig = new ConnectionConfig({ domain: "http://127.0.0.1:8080" });
|
|
let transportInitialized = false;
|
|
connectionConfig.withTransportIfMissing = () => {
|
|
transportInitialized = true;
|
|
throw new Error("transport initialized");
|
|
};
|
|
|
|
await assert.rejects(
|
|
Sandbox.create({
|
|
adapterFactory,
|
|
connectionConfig,
|
|
image: "python:3.12",
|
|
skipHealthCheck: true,
|
|
volumes: [{ name: "host-vol", host: { path: "relative/path" }, mountPath: "/mnt" }],
|
|
}),
|
|
/Host path must be an absolute path starting with '\/' or a Windows drive letter/
|
|
);
|
|
assert.equal(transportInitialized, false);
|
|
});
|
|
|
|
test("Sandbox.create treats null backends as absent", async () => {
|
|
const { adapterFactory, recordedRequests } = createAdapterFactory();
|
|
|
|
await Sandbox.create({
|
|
adapterFactory,
|
|
connectionConfig: { domain: "http://127.0.0.1:8080" },
|
|
image: "python:3.12",
|
|
skipHealthCheck: true,
|
|
volumes: [
|
|
{
|
|
name: "host-with-null-ossfs",
|
|
host: { path: "/tmp" },
|
|
ossfs: null,
|
|
pvc: undefined,
|
|
mountPath: "/mnt",
|
|
},
|
|
],
|
|
});
|
|
|
|
assert.equal(recordedRequests.length, 1);
|
|
assert.equal(recordedRequests[0].volumes[0].host.path, "/tmp");
|
|
});
|