e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
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
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
273 lines
8.0 KiB
JavaScript
273 lines
8.0 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readdir, readFile } from "node:fs/promises";
|
|
import test from "node:test";
|
|
|
|
import { EgressAdapter } from "../dist/internal.js";
|
|
|
|
const credential = {
|
|
name: "api-token",
|
|
source: { value: "write-only-value" },
|
|
};
|
|
|
|
const binding = {
|
|
name: "api-binding",
|
|
match: {
|
|
schemes: ["https"],
|
|
hosts: ["api.example.com"],
|
|
methods: ["GET", "POST"],
|
|
paths: ["/v1/*"],
|
|
},
|
|
auth: {
|
|
type: "apiKey",
|
|
name: "X-API-Key",
|
|
credential: "api-token",
|
|
},
|
|
};
|
|
|
|
const sanitizedCredential = {
|
|
name: "api-token",
|
|
sourceType: "inline",
|
|
revision: 7,
|
|
};
|
|
|
|
const sanitizedBinding = {
|
|
name: "api-binding",
|
|
revision: 5,
|
|
match: {
|
|
schemes: ["https"],
|
|
hosts: ["api.example.com"],
|
|
methods: ["GET", "POST"],
|
|
paths: ["/v1/*"],
|
|
},
|
|
auth: {
|
|
type: "apiKey",
|
|
name: "X-API-Key",
|
|
},
|
|
};
|
|
|
|
function stateResponse() {
|
|
return {
|
|
revision: 9,
|
|
credentials: [
|
|
{
|
|
...sanitizedCredential,
|
|
source: { type: "inline", value: "write-only-value" },
|
|
},
|
|
],
|
|
bindings: [
|
|
{
|
|
...sanitizedBinding,
|
|
auth: {
|
|
...sanitizedBinding.auth,
|
|
credential: "api-token",
|
|
headers: [{ name: "X-Extra", credential: "api-token" }],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
function jsonResponse(body, init = {}) {
|
|
return new Response(body == null ? null : JSON.stringify(body), {
|
|
status: init.status ?? 200,
|
|
headers: {
|
|
"content-type": "application/json",
|
|
...(init.headers ?? {}),
|
|
},
|
|
});
|
|
}
|
|
|
|
function createAdapter() {
|
|
const requests = [];
|
|
const fetchImpl = async (input, init = {}) => {
|
|
const url = new URL(String(input));
|
|
const bodyText = typeof init.body === "string" ? init.body : undefined;
|
|
const request = {
|
|
method: init.method ?? "GET",
|
|
url: String(input),
|
|
pathname: url.pathname,
|
|
headers: Object.fromEntries(new Headers(init.headers).entries()),
|
|
body: bodyText ? JSON.parse(bodyText) : undefined,
|
|
};
|
|
requests.push(request);
|
|
|
|
if (request.pathname === "/credential-vault") {
|
|
if (request.method === "DELETE") {
|
|
return new Response(null, { status: 204 });
|
|
}
|
|
return jsonResponse(stateResponse());
|
|
}
|
|
if (request.pathname === "/credential-vault/credentials") {
|
|
return jsonResponse({
|
|
revision: 9,
|
|
credentials: [stateResponse().credentials[0]],
|
|
});
|
|
}
|
|
if (request.pathname === "/credential-vault/credentials/api-token%2Fprimary") {
|
|
return jsonResponse(stateResponse().credentials[0]);
|
|
}
|
|
if (request.pathname === "/credential-vault/bindings") {
|
|
return jsonResponse({
|
|
revision: 9,
|
|
bindings: [stateResponse().bindings[0]],
|
|
});
|
|
}
|
|
if (request.pathname === "/credential-vault/bindings/api%20binding") {
|
|
return jsonResponse(stateResponse().bindings[0]);
|
|
}
|
|
return jsonResponse({ code: "NOT_FOUND", message: "not found" }, { status: 404 });
|
|
};
|
|
|
|
const policyClient = {
|
|
GET() {
|
|
throw new Error("policy GET was not expected");
|
|
},
|
|
PATCH() {
|
|
throw new Error("policy PATCH was not expected");
|
|
},
|
|
DELETE() {
|
|
throw new Error("policy DELETE was not expected");
|
|
},
|
|
};
|
|
|
|
return {
|
|
adapter: new EgressAdapter(policyClient, {
|
|
baseUrl: "https://egress.example",
|
|
fetch: fetchImpl,
|
|
headers: {
|
|
"OPEN-SANDBOX-API-KEY": "sdk-key",
|
|
"x-endpoint-token": "route-token",
|
|
},
|
|
}),
|
|
requests,
|
|
};
|
|
}
|
|
|
|
test("EgressAdapter sends Credential Vault JSON payloads with endpoint headers", async () => {
|
|
const { adapter, requests } = createAdapter();
|
|
|
|
const created = await adapter.create({
|
|
credentials: [credential],
|
|
bindings: [binding],
|
|
});
|
|
|
|
await adapter.patch({
|
|
expectedRevision: 9,
|
|
credentials: {
|
|
add: [credential],
|
|
replace: [credential],
|
|
delete: ["old-token"],
|
|
},
|
|
bindings: {
|
|
add: [binding],
|
|
replace: [binding],
|
|
delete: ["old-binding"],
|
|
},
|
|
});
|
|
const current = await adapter.get();
|
|
const credentials = await adapter.listCredentials();
|
|
const oneCredential = await adapter.getCredential("api-token/primary");
|
|
const bindings = await adapter.listBindings();
|
|
const oneBinding = await adapter.getBinding("api binding");
|
|
await adapter.delete();
|
|
|
|
assert.deepEqual(created, {
|
|
revision: 9,
|
|
credentials: [sanitizedCredential],
|
|
bindings: [sanitizedBinding],
|
|
});
|
|
assert.deepEqual(current, created);
|
|
assert.deepEqual(credentials, [sanitizedCredential]);
|
|
assert.deepEqual(oneCredential, sanitizedCredential);
|
|
assert.deepEqual(bindings, [sanitizedBinding]);
|
|
assert.deepEqual(oneBinding, sanitizedBinding);
|
|
|
|
assert.deepEqual(
|
|
requests.map((request) => [request.method, request.pathname]),
|
|
[
|
|
["POST", "/credential-vault"],
|
|
["PATCH", "/credential-vault"],
|
|
["GET", "/credential-vault"],
|
|
["GET", "/credential-vault/credentials"],
|
|
["GET", "/credential-vault/credentials/api-token%2Fprimary"],
|
|
["GET", "/credential-vault/bindings"],
|
|
["GET", "/credential-vault/bindings/api%20binding"],
|
|
["DELETE", "/credential-vault"],
|
|
],
|
|
);
|
|
assert.equal(requests[0].headers["open-sandbox-api-key"], "sdk-key");
|
|
assert.equal(requests[0].headers["x-endpoint-token"], "route-token");
|
|
assert.equal(requests[0].headers["content-type"], "application/json");
|
|
assert.equal(requests[2].headers["content-type"], undefined);
|
|
assert.deepEqual(requests[0].body, {
|
|
credentials: [credential],
|
|
bindings: [binding],
|
|
});
|
|
assert.deepEqual(requests[1].body, {
|
|
expectedRevision: 9,
|
|
credentials: {
|
|
add: [credential],
|
|
replace: [credential],
|
|
delete: ["old-token"],
|
|
},
|
|
bindings: {
|
|
add: [binding],
|
|
replace: [binding],
|
|
delete: ["old-binding"],
|
|
},
|
|
});
|
|
});
|
|
|
|
test("Credential Vault state omits plaintext secret fields", async () => {
|
|
const { adapter } = createAdapter();
|
|
const state = await adapter.get();
|
|
|
|
assert.equal(Object.hasOwn(state.credentials[0], "source"), false);
|
|
assert.equal(Object.hasOwn(state.bindings[0].auth, "credential"), false);
|
|
assert.equal(Object.hasOwn(state.bindings[0].auth, "headers"), false);
|
|
|
|
const dts = await readDistDeclarations();
|
|
const inlineCredentialSource = declarationBlock(dts, "InlineCredentialSource");
|
|
const credentialMetadata = declarationBlock(dts, "CredentialMetadata");
|
|
const authMetadata = declarationBlock(dts, "CredentialAuthMetadata");
|
|
const bindingMetadata = declarationBlock(dts, "CredentialBindingMetadata");
|
|
|
|
assert.match(inlineCredentialSource, /\btype\?: "inline"/);
|
|
assert.doesNotMatch(credentialMetadata, /extends Record/);
|
|
assert.doesNotMatch(credentialMetadata, /\bsource\s*[?:]/);
|
|
assert.doesNotMatch(authMetadata, /extends Record/);
|
|
assert.doesNotMatch(authMetadata, /\bcredential\s*[?:]/);
|
|
assert.doesNotMatch(authMetadata, /\bheaders\s*[?:]/);
|
|
assert.doesNotMatch(bindingMetadata, /extends Record/);
|
|
assert.match(bindingMetadata, /\bauth\??: CredentialAuthMetadata/);
|
|
});
|
|
|
|
test("Egress declarations keep Credential Vault optional for custom adapters", async () => {
|
|
const dts = await readDistDeclarations();
|
|
const egress = declarationBlock(dts, "Egress");
|
|
const egressStack = declarationBlock(dts, "EgressStack");
|
|
|
|
assert.doesNotMatch(egress, /extends CredentialVault/);
|
|
assert.doesNotMatch(egress, /\bcreate\(/);
|
|
assert.match(egressStack, /\begress: Egress/);
|
|
assert.match(egressStack, /\bcredentialVault\??: CredentialVault/);
|
|
});
|
|
|
|
async function readDistDeclarations() {
|
|
const distDir = new URL("../dist/", import.meta.url);
|
|
const entries = await readdir(distDir);
|
|
const declarations = entries.filter((entry) => entry.endsWith(".d.ts"));
|
|
const contents = await Promise.all(
|
|
declarations.map((entry) => readFile(new URL(entry, distDir), "utf8")),
|
|
);
|
|
return contents.join("\n");
|
|
}
|
|
|
|
function declarationBlock(dts, name) {
|
|
const start = dts.indexOf(`interface ${name}`);
|
|
assert.notEqual(start, -1, `missing ${name} declaration`);
|
|
const end = dts.indexOf("\n}", start);
|
|
assert.notEqual(end, -1, `missing ${name} declaration end`);
|
|
return dts.slice(start, end + 2);
|
|
}
|