142 lines
5.5 KiB
TypeScript
142 lines
5.5 KiB
TypeScript
/**
|
|
* TDD tests for port of upstream PR #2054 (decolua/9router):
|
|
* fix(antigravity): retry transient upstream failures
|
|
*
|
|
* Covers:
|
|
* 1. isTransientAntigravityError classifies 5xx + body patterns correctly
|
|
* 2. extractErrorMessage extracts message from various JSON shapes
|
|
* 3. Tool-name deduplication in buildGeminiTools (via geminiToolsSanitizer)
|
|
*/
|
|
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import { AntigravityExecutor } from "../../open-sse/executors/antigravity.ts";
|
|
import { buildGeminiTools } from "../../open-sse/translator/helpers/geminiToolsSanitizer.ts";
|
|
|
|
// ── isTransientAntigravityError ────────────────────────────────────────────────
|
|
|
|
test("isTransientAntigravityError: 503 → true (transient status)", () => {
|
|
const ex = new AntigravityExecutor();
|
|
assert.equal(ex.isTransientAntigravityError(503, ""), true);
|
|
});
|
|
|
|
test("isTransientAntigravityError: 500 + 'Agent execution terminated due to error' body → true", () => {
|
|
const ex = new AntigravityExecutor();
|
|
assert.equal(
|
|
ex.isTransientAntigravityError(500, "Agent execution terminated due to error"),
|
|
true
|
|
);
|
|
});
|
|
|
|
test("isTransientAntigravityError: 500 + 'high traffic' body → true", () => {
|
|
const ex = new AntigravityExecutor();
|
|
assert.equal(ex.isTransientAntigravityError(500, "high traffic"), true);
|
|
});
|
|
|
|
test("isTransientAntigravityError: 500 + 'capacity' body → true", () => {
|
|
const ex = new AntigravityExecutor();
|
|
assert.equal(ex.isTransientAntigravityError(500, "service is at capacity"), true);
|
|
});
|
|
|
|
test("isTransientAntigravityError: 400 + 'Invalid request' body → false (veto)", () => {
|
|
const ex = new AntigravityExecutor();
|
|
assert.equal(ex.isTransientAntigravityError(400, "Invalid request"), false);
|
|
});
|
|
|
|
test("isTransientAntigravityError: 404 → false", () => {
|
|
const ex = new AntigravityExecutor();
|
|
assert.equal(ex.isTransientAntigravityError(404, "not found"), false);
|
|
});
|
|
|
|
test("isTransientAntigravityError: 429 → true (rate limited is transient)", () => {
|
|
const ex = new AntigravityExecutor();
|
|
assert.equal(ex.isTransientAntigravityError(429, ""), true);
|
|
});
|
|
|
|
test("isTransientAntigravityError: 502 → true (bad gateway is transient)", () => {
|
|
const ex = new AntigravityExecutor();
|
|
assert.equal(ex.isTransientAntigravityError(502, ""), true);
|
|
});
|
|
|
|
test("isTransientAntigravityError: 504 → true (gateway timeout is transient)", () => {
|
|
const ex = new AntigravityExecutor();
|
|
assert.equal(ex.isTransientAntigravityError(504, ""), true);
|
|
});
|
|
|
|
// ── extractErrorMessage ────────────────────────────────────────────────────────
|
|
|
|
test("extractErrorMessage: extracts error.message from standard JSON shape", () => {
|
|
const ex = new AntigravityExecutor();
|
|
const json = { error: { message: "quota exceeded" } };
|
|
const msg = ex.extractErrorMessage(json, "");
|
|
assert.ok(msg.includes("quota exceeded"), `expected 'quota exceeded' in: ${msg}`);
|
|
});
|
|
|
|
test("extractErrorMessage: falls back to top-level message field", () => {
|
|
const ex = new AntigravityExecutor();
|
|
const json = { message: "service unavailable" };
|
|
const msg = ex.extractErrorMessage(json, "");
|
|
assert.ok(msg.includes("service unavailable"), `expected 'service unavailable' in: ${msg}`);
|
|
});
|
|
|
|
test("extractErrorMessage: falls back to bodyText when json has no message", () => {
|
|
const ex = new AntigravityExecutor();
|
|
const msg = ex.extractErrorMessage(null, "raw error body text");
|
|
assert.ok(msg.includes("raw error body text"), `expected body text in: ${msg}`);
|
|
});
|
|
|
|
// ── Tool-name deduplication ────────────────────────────────────────────────────
|
|
|
|
test("buildGeminiTools: deduplicates tool names that sanitize to the same value", () => {
|
|
// "read/file" and "read/file" appear twice → only one after dedup
|
|
const tools = [
|
|
{
|
|
functionDeclarations: [
|
|
{
|
|
name: "read/file",
|
|
description: "Read a file",
|
|
parameters: { type: "object", properties: {} },
|
|
},
|
|
{
|
|
name: "read/file",
|
|
description: "Read a file (dup)",
|
|
parameters: { type: "object", properties: {} },
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
const result = buildGeminiTools(tools);
|
|
assert.ok(Array.isArray(result), "should return array");
|
|
const decls = result![0]?.functionDeclarations ?? [];
|
|
// After sanitizing "read/file" → "read_file", both resolve to same name; only 1 should remain
|
|
const names = decls.map((d) => d.name);
|
|
assert.equal(new Set(names).size, names.length, `Duplicate names found: ${names}`);
|
|
assert.equal(names.length, 1, `Expected 1 declaration, got ${names.length}: ${names}`);
|
|
});
|
|
|
|
test("buildGeminiTools: tools with different sanitized names are both kept", () => {
|
|
const tools = [
|
|
{
|
|
functionDeclarations: [
|
|
{
|
|
name: "read_file",
|
|
description: "Read a file",
|
|
parameters: { type: "object", properties: {} },
|
|
},
|
|
{
|
|
name: "write_file",
|
|
description: "Write a file",
|
|
parameters: { type: "object", properties: {} },
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
const result = buildGeminiTools(tools);
|
|
assert.ok(Array.isArray(result), "should return array");
|
|
const decls = result![0]?.functionDeclarations ?? [];
|
|
assert.equal(decls.length, 2, `Expected 2 declarations, got ${decls.length}`);
|
|
});
|