342 lines
11 KiB
TypeScript
342 lines
11 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const { BlackboxWebExecutor, normalizeBlackboxCookieHeader } =
|
|
await import("../../open-sse/executors/blackbox-web.ts");
|
|
const { getExecutor, hasSpecializedExecutor } = await import("../../open-sse/executors/index.ts");
|
|
|
|
function mockTextStream(text: string) {
|
|
const encoder = new TextEncoder();
|
|
return new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue(encoder.encode(text));
|
|
controller.close();
|
|
},
|
|
});
|
|
}
|
|
|
|
function mockFetch(status: number, text: string) {
|
|
const original = globalThis.fetch;
|
|
globalThis.fetch = async () =>
|
|
new Response(mockTextStream(text), {
|
|
status,
|
|
headers: { "Content-Type": "text/plain; charset=utf-8" },
|
|
});
|
|
return () => {
|
|
globalThis.fetch = original;
|
|
};
|
|
}
|
|
|
|
function mockFetchCapture(status = 200, text = "Hello from Blackbox") {
|
|
const original = globalThis.fetch;
|
|
let capturedUrl: string | null = null;
|
|
let capturedHeaders: Record<string, string> = {};
|
|
let capturedBody: Record<string, unknown> = {};
|
|
|
|
globalThis.fetch = async (url: any, opts: any) => {
|
|
capturedUrl = String(url);
|
|
capturedHeaders = opts?.headers || {};
|
|
capturedBody = JSON.parse(opts?.body || "{}");
|
|
return new Response(mockTextStream(text), {
|
|
status,
|
|
headers: { "Content-Type": "text/plain; charset=utf-8" },
|
|
});
|
|
};
|
|
|
|
return {
|
|
restore: () => {
|
|
globalThis.fetch = original;
|
|
},
|
|
get url() {
|
|
return capturedUrl;
|
|
},
|
|
get headers() {
|
|
return capturedHeaders;
|
|
},
|
|
get body() {
|
|
return capturedBody;
|
|
},
|
|
};
|
|
}
|
|
|
|
test("BlackboxWebExecutor is registered in executor index", () => {
|
|
assert.ok(hasSpecializedExecutor("blackbox-web"));
|
|
assert.ok(hasSpecializedExecutor("bb-web"));
|
|
const executor = getExecutor("blackbox-web");
|
|
const alias = getExecutor("bb-web");
|
|
assert.ok(executor instanceof BlackboxWebExecutor);
|
|
assert.ok(alias instanceof BlackboxWebExecutor);
|
|
});
|
|
|
|
test("BlackboxWebExecutor sets correct provider name", () => {
|
|
const executor = new BlackboxWebExecutor();
|
|
assert.equal(executor.getProvider(), "blackbox-web");
|
|
});
|
|
|
|
test("Non-streaming: plain text response becomes OpenAI completion", async () => {
|
|
const restore = mockFetch(200, "Hello from Blackbox");
|
|
try {
|
|
const executor = new BlackboxWebExecutor();
|
|
const result = await executor.execute({
|
|
model: "openai/gpt-5.4",
|
|
body: { messages: [{ role: "user", content: "hi" }], stream: false },
|
|
stream: false,
|
|
credentials: { apiKey: "bb-session-token" },
|
|
signal: AbortSignal.timeout(10000),
|
|
log: null,
|
|
});
|
|
|
|
assert.equal(result.response.status, 200);
|
|
const json = (await result.response.json()) as any;
|
|
assert.equal(json.object, "chat.completion");
|
|
assert.equal(json.choices[0].message.role, "assistant");
|
|
assert.equal(json.choices[0].message.content, "Hello from Blackbox");
|
|
assert.equal(json.choices[0].finish_reason, "stop");
|
|
assert.ok(json.id.startsWith("chatcmpl-blackbox-"));
|
|
assert.ok(json.usage.total_tokens > 0);
|
|
} finally {
|
|
restore();
|
|
}
|
|
});
|
|
|
|
test("Streaming: produces valid SSE chunks", async () => {
|
|
const restore = mockFetch(200, "streamed answer");
|
|
try {
|
|
const executor = new BlackboxWebExecutor();
|
|
const result = await executor.execute({
|
|
model: "openai/gpt-5.4",
|
|
body: { messages: [{ role: "user", content: "hello" }], stream: true },
|
|
stream: true,
|
|
credentials: { apiKey: "bb-session-token" },
|
|
signal: AbortSignal.timeout(10000),
|
|
log: null,
|
|
});
|
|
|
|
assert.equal(result.response.status, 200);
|
|
assert.equal(result.response.headers.get("Content-Type"), "text/event-stream");
|
|
|
|
const text = await result.response.text();
|
|
const lines = text.split("\n").filter((line) => line.startsWith("data: "));
|
|
assert.ok(lines.length >= 3, `Expected at least 3 SSE data lines, got ${lines.length}`);
|
|
|
|
const first = JSON.parse(lines[0].slice(6));
|
|
assert.equal(first.choices[0].delta.role, "assistant");
|
|
|
|
const second = JSON.parse(lines[1].slice(6));
|
|
assert.equal(second.choices[0].delta.content, "streamed answer");
|
|
|
|
const lastLine = text.trim().split("\n").filter(Boolean).pop();
|
|
assert.equal(lastLine, "data: [DONE]");
|
|
} finally {
|
|
restore();
|
|
}
|
|
});
|
|
|
|
test("Error: 401 returns auth error", async () => {
|
|
const restore = mockFetch(401, "unauthorized");
|
|
try {
|
|
const executor = new BlackboxWebExecutor();
|
|
const result = await executor.execute({
|
|
model: "openai/gpt-5.4",
|
|
body: { messages: [{ role: "user", content: "hi" }] },
|
|
stream: false,
|
|
credentials: { apiKey: "expired-cookie" },
|
|
signal: AbortSignal.timeout(10000),
|
|
log: null,
|
|
});
|
|
|
|
assert.equal(result.response.status, 401);
|
|
const json = (await result.response.json()) as any;
|
|
assert.match(json.error.message, /auth failed/i);
|
|
assert.match(json.error.message, /session/i);
|
|
} finally {
|
|
restore();
|
|
}
|
|
});
|
|
|
|
test("In-band subscription error in response body returns 402", async () => {
|
|
const upgradeMessage =
|
|
"You have not upgraded your account. " +
|
|
"[Please upgrade to a premium plan to continue](https://app.blackbox.ai/pricing?ref=upgrade-required).";
|
|
const restore = mockFetch(200, upgradeMessage);
|
|
try {
|
|
const executor = new BlackboxWebExecutor();
|
|
const result = await executor.execute({
|
|
model: "anthropic/claude-sonnet-4",
|
|
body: { messages: [{ role: "user", content: "hi" }], stream: false },
|
|
stream: false,
|
|
credentials: { apiKey: "bb-session-token" },
|
|
signal: AbortSignal.timeout(10000),
|
|
log: null,
|
|
});
|
|
|
|
assert.equal(result.response.status, 402);
|
|
const json = (await result.response.json()) as any;
|
|
assert.equal(json.error.code, "BLACKBOX_SUBSCRIPTION_REQUIRED");
|
|
assert.match(json.error.message, /premium subscription/i);
|
|
} finally {
|
|
restore();
|
|
}
|
|
});
|
|
|
|
test("In-band auth error in response body returns 401", async () => {
|
|
const restore = mockFetch(200, "Please login to continue.");
|
|
try {
|
|
const executor = new BlackboxWebExecutor();
|
|
const result = await executor.execute({
|
|
model: "openai/gpt-5.4",
|
|
body: { messages: [{ role: "user", content: "hi" }], stream: false },
|
|
stream: false,
|
|
credentials: { apiKey: "bb-session-token" },
|
|
signal: AbortSignal.timeout(10000),
|
|
log: null,
|
|
});
|
|
|
|
assert.equal(result.response.status, 401);
|
|
const json = (await result.response.json()) as any;
|
|
assert.equal(json.error.code, "BLACKBOX_AUTH_REQUIRED");
|
|
assert.match(json.error.message, /session/i);
|
|
} finally {
|
|
restore();
|
|
}
|
|
});
|
|
|
|
test("In-band rate limit error in response body returns 429", async () => {
|
|
const restore = mockFetch(200, "Rate limit exceeded. Try again later.");
|
|
try {
|
|
const executor = new BlackboxWebExecutor();
|
|
const result = await executor.execute({
|
|
model: "openai/gpt-5.4",
|
|
body: { messages: [{ role: "user", content: "hi" }], stream: false },
|
|
stream: false,
|
|
credentials: { apiKey: "bb-session-token" },
|
|
signal: AbortSignal.timeout(10000),
|
|
log: null,
|
|
});
|
|
|
|
assert.equal(result.response.status, 429);
|
|
const json = (await result.response.json()) as any;
|
|
assert.equal(json.error.code, "BLACKBOX_RATE_LIMIT");
|
|
assert.match(json.error.message, /rate limited/i);
|
|
} finally {
|
|
restore();
|
|
}
|
|
});
|
|
|
|
test("Error: 429 returns rate limit message", async () => {
|
|
const restore = mockFetch(429, "rate limited");
|
|
try {
|
|
const executor = new BlackboxWebExecutor();
|
|
const result = await executor.execute({
|
|
model: "openai/gpt-5.4",
|
|
body: { messages: [{ role: "user", content: "hi" }] },
|
|
stream: false,
|
|
credentials: { apiKey: "bb-session-token" },
|
|
signal: AbortSignal.timeout(10000),
|
|
log: null,
|
|
});
|
|
|
|
assert.equal(result.response.status, 429);
|
|
const json = (await result.response.json()) as any;
|
|
assert.match(json.error.message, /rate limited/i);
|
|
} finally {
|
|
restore();
|
|
}
|
|
});
|
|
|
|
test("Error: empty messages returns 400", async () => {
|
|
const executor = new BlackboxWebExecutor();
|
|
const result = await executor.execute({
|
|
model: "openai/gpt-5.4",
|
|
body: { messages: [] },
|
|
stream: false,
|
|
credentials: { apiKey: "bb-session-token" },
|
|
signal: AbortSignal.timeout(10000),
|
|
log: null,
|
|
});
|
|
|
|
assert.equal(result.response.status, 400);
|
|
});
|
|
|
|
test("Cookie normalization supports raw tokens, prefixed tokens and full headers", () => {
|
|
assert.equal(
|
|
normalizeBlackboxCookieHeader("raw-session-token"),
|
|
"next-auth.session-token=raw-session-token"
|
|
);
|
|
assert.equal(
|
|
normalizeBlackboxCookieHeader("cookie:raw-session-token"),
|
|
"next-auth.session-token=raw-session-token"
|
|
);
|
|
assert.equal(
|
|
normalizeBlackboxCookieHeader("__Secure-authjs.session-token=token; other=value"),
|
|
"__Secure-authjs.session-token=token; other=value"
|
|
);
|
|
});
|
|
|
|
test("Request: posts to correct Blackbox endpoint with normalized cookie", async () => {
|
|
const cap = mockFetchCapture();
|
|
try {
|
|
const executor = new BlackboxWebExecutor();
|
|
await executor.execute({
|
|
model: "openai/gpt-5.4",
|
|
body: { messages: [{ role: "user", content: "test" }], stream: false },
|
|
stream: false,
|
|
credentials: { apiKey: "raw-session-token" },
|
|
signal: AbortSignal.timeout(10000),
|
|
log: null,
|
|
});
|
|
|
|
assert.equal(cap.url, "https://app.blackbox.ai/api/chat");
|
|
assert.equal(cap.headers.Cookie, "next-auth.session-token=raw-session-token");
|
|
assert.equal(cap.headers.Origin, "https://app.blackbox.ai");
|
|
assert.match(String(cap.headers.Referer), /^https:\/\/app\.blackbox\.ai\/chat\//);
|
|
} finally {
|
|
cap.restore();
|
|
}
|
|
});
|
|
|
|
test("Request: payload carries model selection and web app defaults", async () => {
|
|
const cap = mockFetchCapture();
|
|
try {
|
|
const executor = new BlackboxWebExecutor();
|
|
await executor.execute({
|
|
model: "anthropic/claude-opus-4.7",
|
|
body: {
|
|
messages: [
|
|
{ role: "system", content: "Be concise" },
|
|
{ role: "user", content: "Implement this" },
|
|
],
|
|
max_tokens: 2048,
|
|
stream: false,
|
|
},
|
|
stream: false,
|
|
credentials: { apiKey: "__Secure-authjs.session-token=token" },
|
|
signal: AbortSignal.timeout(10000),
|
|
log: null,
|
|
});
|
|
|
|
assert.equal(cap.body.userSelectedModel, "anthropic/claude-opus-4.7");
|
|
assert.equal(cap.body.userSelectedAgent, "VscodeAgent");
|
|
assert.equal(cap.body.codeModelMode, true);
|
|
assert.equal(cap.body.maxTokens, 2048);
|
|
assert.equal(Array.isArray(cap.body.messages), true);
|
|
assert.match(String((cap.body.messages as any[])[0].content), /System instructions:/);
|
|
} finally {
|
|
cap.restore();
|
|
}
|
|
});
|
|
|
|
test("Provider registry: blackbox-web models are exposed", async () => {
|
|
const { getModelsByProviderId } = await import("../../open-sse/config/providerModels.ts");
|
|
const models = getModelsByProviderId("blackbox-web");
|
|
// blackbox-web was removed from the registry in v3.7.0 merge
|
|
// If it gets re-added, also add: assert.equal(PROVIDER_ID_TO_ALIAS["blackbox-web"], "bb-web");
|
|
if (models && models.length > 0) {
|
|
const ids = models.map((model: any) => model.id);
|
|
assert.ok(ids.includes("gpt-4"));
|
|
assert.ok(ids.includes("claude-3-opus"));
|
|
}
|
|
// If not present, skip assertions - provider may have been temporarily removed
|
|
});
|
|
|