Files
diegosouzapw--omniroute/tests/unit/antigravity-retry-hook.test.ts
2026-07-13 13:39:12 +08:00

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}`);
});