Files
2026-07-13 13:39:12 +08:00

257 lines
8.0 KiB
TypeScript

import { describe, it } from "node:test";
import assert from "node:assert/strict";
const mod = await import("../../open-sse/executors/doubao-web.ts");
describe("DoubaoWebExecutor", () => {
it("can be instantiated", () => {
const executor = new mod.DoubaoWebExecutor();
assert.ok(executor);
});
it("builds Dola cookie headers from JSON token extraction data", () => {
const cookie = mod.buildDolaCookieHeader(
JSON.stringify({
sessionid: "sid",
ttwid: "tt",
s_v_web_id: "verify_abc",
})
);
assert.equal(cookie, "sessionid=sid; ttwid=tt; s_v_web_id=verify_abc");
});
it("strips Cookie prefix from full Dola cookie headers", () => {
const cookie = mod.buildDolaCookieHeader(
"Cookie: sessionid=sid; ttwid=tt; s_v_web_id=verify_abc"
);
assert.equal(cookie, "sessionid=sid; ttwid=tt; s_v_web_id=verify_abc");
});
it("builds Dola query params without browser-only anti-bot signature params", () => {
const params = mod.buildDolaQueryParams("sessionid=sid; ttwid=tt; s_v_web_id=verify_abc", {
device_id: "1234567890123456789",
});
assert.equal(params.get("aid"), "495671");
assert.equal(params.get("device_id"), "1234567890123456789");
assert.equal(params.get("fp"), "verify_abc");
assert.equal(params.has("msToken"), false);
assert.equal(params.has("a_bogus"), false);
});
it("prefers s_v_web_id over fp when both fingerprint values exist", () => {
const cookie = "sessionid=sid; ttwid=tt; s_v_web_id=verify_cookie; fp=verify_fp";
const params = mod.buildDolaQueryParams(cookie, { fp: "verify_data" });
assert.equal(mod.resolveDolaFingerprint(cookie, { fp: "verify_data" }), "verify_cookie");
assert.equal(params.get("fp"), "verify_cookie");
});
it("accepts fp from the pasted credential when s_v_web_id is absent", () => {
const credential = "sessionid=sid; ttwid=tt; fp=verify_from_url";
const cookie = mod.buildDolaCookieHeader(credential);
const params = mod.buildDolaQueryParams(cookie, undefined, credential);
assert.equal(mod.resolveDolaFingerprint(cookie, undefined, credential), "verify_from_url");
assert.equal(params.get("fp"), "verify_from_url");
});
it("builds the Dola new-conversation payload shape", () => {
const payload = mod.buildDolaPayload(
"user: hello",
"dola-speed",
"sessionid=sid; ttwid=tt; s_v_web_id=verify_abc"
);
const clientMeta = payload.client_meta as Record<string, unknown>;
const option = payload.option as Record<string, unknown>;
const ext = payload.ext as Record<string, unknown>;
const messages = payload.messages as Array<Record<string, unknown>>;
assert.equal(clientMeta.conversation_id, "");
assert.equal(clientMeta.bot_id, "7339470689562525703");
assert.equal(option.need_create_conversation, true);
assert.deepEqual(option.conversation_init_option, { need_ack_conversation: true });
assert.equal(option.need_deep_think, 0);
assert.equal(ext.fp, "verify_abc");
assert.equal(ext.use_deep_think, "0");
assert.equal(ext.conversation_init_option, '{"need_ack_conversation":true}');
assert.equal(messages.length, 1);
});
it("maps Dola Pro to deep-think value 3", () => {
const payload = mod.buildDolaPayload(
"user: hello",
"dola-pro",
"sessionid=sid; ttwid=tt; s_v_web_id=verify_abc"
);
const option = payload.option as Record<string, unknown>;
const ext = payload.ext as Record<string, unknown>;
assert.equal(option.need_deep_think, 3);
assert.equal(ext.use_deep_think, "3");
});
it("extracts text deltas from Dola STREAM_MSG_NOTIFY and STREAM_CHUNK events", () => {
const initial = mod.extractDolaTextDeltas({
content: {
content_block: [
{
content: {
text_block: {
text: "hello",
},
},
},
],
},
});
const deltas = mod.extractDolaTextDeltas({
message_id: "1154824514127377",
patch_op: [
{
patch_object: 1,
patch_type: 1,
patch_value: {
content_block: [
{
block_type: 10000,
content: {
text_block: {
text: " world",
},
},
is_finish: false,
patch_type: 1,
},
],
},
},
],
});
assert.deepEqual(initial, ["hello"]);
assert.deepEqual(deltas, [" world"]);
});
it("defers Dola Pro reasoning text until the answer boundary", () => {
const state = { deferUntilAnswer: true, answerStarted: false, bufferedDeltas: [] as string[] };
const reasoning = mod.extractDolaTextDeltas(
{
patch_op: [
{
patch_value: {
content_block: [
{
block_type: 10000,
content: { text_block: { text: "reasoning" } },
is_finish: false,
},
],
},
},
],
},
state
);
const answer = mod.extractDolaTextDeltas(
{
patch_op: [
{
patch_value: {
content_block: [
{ block_type: 10040, content: { text_block: {} }, is_finish: true },
{ block_type: 10000, content: { text_block: { text: "2" } }, is_finish: false },
],
},
},
],
},
state
);
assert.deepEqual(reasoning, []);
assert.deepEqual(answer, ["2"]);
});
it("detects Dola busy messages", () => {
assert.equal(
mod.isDolaBusyMessage("A lot of people are using the app right now. Please try again later."),
true
);
assert.equal(mod.isDolaBusyMessage("2"), false);
});
it("returns a credential error when sessionid is missing", async () => {
const executor = new mod.DoubaoWebExecutor();
const result = await executor.execute({
model: "dola-speed",
body: { messages: [{ role: "user", content: "hi" }] },
stream: false,
credentials: { apiKey: "" },
signal: null,
});
assert.equal(result.response.status, 401);
assert.equal(new URL(result.url).hostname, "www.dola.com");
});
it("returns a credential error when browser fingerprint is missing", async () => {
const executor = new mod.DoubaoWebExecutor();
const result = await executor.execute({
model: "dola-speed",
body: { messages: [{ role: "user", content: "hi" }] },
stream: false,
credentials: { apiKey: "sessionid=sid; ttwid=tt" },
signal: null,
});
const text = await result.response.text();
assert.equal(result.response.status, 401);
assert.match(text, /s_v_web_id|fp=verify/);
});
it("classifies Dola busy SSE text as a rate limit error", async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = (async () =>
new Response(
[
"event: STREAM_MSG_NOTIFY",
`data: ${JSON.stringify({
content: {
content_block: [
{
content: {
text_block: {
text: "A lot of people are using the app right now. Please try again later.",
},
},
},
],
},
})}`,
"",
"",
].join("\n"),
{ headers: { "Content-Type": "text/event-stream" } }
)) as typeof fetch;
try {
const executor = new mod.DoubaoWebExecutor();
const result = await executor.execute({
model: "dola-speed",
body: { messages: [{ role: "user", content: "hi" }] },
stream: false,
credentials: { apiKey: "sessionid=sid; ttwid=tt; s_v_web_id=verify_abc" },
signal: null,
});
const text = await result.response.text();
assert.equal(result.response.status, 429);
assert.match(text, /temporarily busy/i);
} finally {
globalThis.fetch = originalFetch;
}
});
});