Files
OmniRoute/tests/unit/sse-error-passthrough-3324.test.ts
backryun acc066db3f [v3.8.50] feat(devin-desktop): replace public Windsurf provider (#8228)
* feat(devin-desktop): replace public Windsurf provider

* fix(migrations): renumber Devin Desktop migration to 151 (avoid 147 collision)

147_windsurf_to_devin_desktop.sql collided with the released
147_api_keys_model_access_mode.sql — getMigrationFiles throws
"Migration version collision detected" on every DB start. Base occupies
slots up to 150, so renumber the new migration to 151 and point the
windsurf→devin RENAMED_MIGRATION_COMPATIBILITY entries (and tests) at it.
147 is freed in KNOWN_GAPS since 147_api_keys now owns the slot.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-12 08:40:18 -03:00

160 lines
6.3 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";
const { parseSSEToOpenAIResponse, extractSSEErrorMessage } =
await import("../../open-sse/handlers/sseParser.ts");
const GENERIC = "Invalid SSE response for non-streaming request";
// ─── PART 2: error-only SSE passthrough ───────────────────────────────────────
test("extractSSEErrorMessage surfaces the error message from an error-only SSE chunk", () => {
const rawSSE = [
'data: {"error":{"message":"Devin CLI not found on PATH"}}',
"",
"data: [DONE]",
"",
].join("\n");
const surfaced = extractSSEErrorMessage(rawSSE);
assert.ok(surfaced, "expected a surfaced error message, got null");
assert.ok(
surfaced.includes("Devin CLI not found on PATH"),
`expected surfaced message to contain the real Devin error, got: ${surfaced}`
);
assert.notEqual(surfaced, GENERIC);
});
test("extractSSEErrorMessage surfaces the real Devin spawn error shape (type+code)", () => {
const rawSSE = [
'data: {"error":{"message":"Devin CLI not found: devin. Install via https://cli.devin.ai or set CLI_DEVIN_BIN env var.","type":"devin_cli_error","code":"spawn_failed"}}',
"",
"data: [DONE]",
"",
].join("\n");
const surfaced = extractSSEErrorMessage(rawSSE);
assert.ok(surfaced);
assert.ok(surfaced.includes("Devin CLI not found"));
});
test("extractSSEErrorMessage accepts a top-level string error field", () => {
const rawSSE = ['data: {"error":"upstream blew up"}', "", "data: [DONE]"].join("\n");
const surfaced = extractSSEErrorMessage(rawSSE);
assert.ok(surfaced);
assert.ok(surfaced.includes("upstream blew up"));
});
test("extractSSEErrorMessage returns null when a valid choices chunk is present (no false positive)", () => {
const rawSSE = [
'data: {"id":"chatcmpl_ok","choices":[{"index":0,"delta":{"content":"hi"},"finish_reason":"stop"}]}',
"data: [DONE]",
].join("\n");
assert.equal(extractSSEErrorMessage(rawSSE), null);
});
test("extractSSEErrorMessage returns null for a stream with no error and no choices", () => {
const rawSSE = ['data: {"foo":"bar"}', "data: [DONE]"].join("\n");
assert.equal(extractSSEErrorMessage(rawSSE), null);
});
test("extractSSEErrorMessage sanitizes stack-trace-like error messages (no `at /` leak)", () => {
// Genuine V8 stack shape: real newline between the message and the frames, so
// a JSON.stringify of a real Error.stack-style string round-trips with the
// newline intact (sanitizeErrorMessage drops everything after the first line).
const stacky =
"ENOENT: spawn devin\n at ChildProcess._handle.onexit (/home/me/app/open-sse/executors/devin-cli.ts:170:5)";
// A bare absolute source path on the surviving first line must also be redacted.
const bareInline = "Devin failed loading /home/me/app/open-sse/executors/devin-cli.ts module";
const rawSSE = [
`data: ${JSON.stringify({ error: { message: stacky } })}`,
"",
`data: ${JSON.stringify({ error: { message: bareInline } })}`,
"",
"data: [DONE]",
].join("\n");
const surfaced = extractSSEErrorMessage(rawSSE);
assert.ok(surfaced, "expected a sanitized error message");
// The first error-only chunk wins; its stack tail is dropped at the newline.
assert.equal(surfaced, "ENOENT: spawn devin");
assert.ok(!surfaced.includes("at /"), `surfaced message leaked a stack frame path: ${surfaced}`);
assert.ok(
!/\/[^\s]+\.ts/.test(surfaced),
`surfaced message leaked an absolute source path: ${surfaced}`
);
// A bare absolute source path on a single line is redacted to <path>.
const bareSurfaced = extractSSEErrorMessage(
[`data: ${JSON.stringify({ error: { message: bareInline } })}`, "", "data: [DONE]"].join("\n")
);
assert.ok(bareSurfaced);
assert.ok(
bareSurfaced.includes("<path>"),
`expected the bare absolute path to be redacted, got: ${bareSurfaced}`
);
assert.ok(!/\/[^\s]+\.ts/.test(bareSurfaced), `bare-path message leaked: ${bareSurfaced}`);
});
// ─── Regression: the normal valid-SSE parse path still works ───────────────────
test("parseSSEToOpenAIResponse still parses a normal valid SSE (no regression)", () => {
const rawSSE = [
'data: {"id":"chatcmpl_ok","model":"gpt-4o-mini","choices":[{"index":0,"delta":{"content":"hello"},"finish_reason":"stop"}]}',
"data: [DONE]",
].join("\n");
const parsed = parseSSEToOpenAIResponse(rawSSE, "fallback-model");
assert.ok(parsed);
assert.equal(parsed.choices[0].message.content, "hello");
});
test("parseSSEToOpenAIResponse still returns null for an error-only SSE (boundary owns the error path)", () => {
const rawSSE = ['data: {"error":{"message":"Devin CLI not found on PATH"}}', "data: [DONE]"].join(
"\n"
);
// The valid-SSE parser intentionally returns null here (no `choices`); the
// error surfacing is the job of extractSSEErrorMessage at the boundary.
assert.equal(parseSSEToOpenAIResponse(rawSSE, "fallback-model"), null);
});
// ─── PART 1: Devin Desktop instruction text is version-honest ─────────────────
test("PART 1: Devin Desktop authHint describes importing an existing key", () => {
const here = path.dirname(fileURLToPath(import.meta.url));
// After the providers.ts oauth-constants split, the Devin Desktop authHint moved to
// src/shared/constants/providers/oauth.ts.
const providers = readFileSync(
path.join(here, "../../src/shared/constants/providers/oauth.ts"),
"utf8"
);
assert.match(providers, /Paste an existing Devin API key/);
assert.match(providers, /vary by Devin version and account/);
assert.doesNotMatch(providers, /Devin: Copy API Key to Clipboard/);
});
test("PART 1: oauth route 410 errors avoid promising a universal copy command", () => {
const here = path.dirname(fileURLToPath(import.meta.url));
const route = readFileSync(
path.join(here, "../../src/app/api/oauth/[provider]/[action]/route.ts"),
"utf8"
);
assert.match(route, /Paste an existing Devin API key/);
assert.match(route, /vary by Devin version and account/);
assert.doesNotMatch(route, /Devin: Copy API Key to Clipboard/);
});