fix matchmaking gateway failures

This commit is contained in:
Sai-karthik
2026-07-17 07:40:07 +00:00
parent 35e29316df
commit 6d2eb6e22a
2 changed files with 52 additions and 12 deletions

View File

@@ -1,11 +1,15 @@
import assert from "node:assert/strict";
import { serviceRoutes } from "../src/routes/services.js";
import { config } from "../src/config.js";
const originalFetch = globalThis.fetch;
const calls: Array<{ url: string; body: Record<string, unknown> }> = [];
const calls: Array<{ url: string; body: Record<string, unknown>; headers: Headers }> = [];
globalThis.fetch = (async (input, init) => {
const body = JSON.parse(String(init?.body ?? "{}")) as Record<string, unknown>;
calls.push({ url: String(input), body });
calls.push({ url: String(input), body, headers: new Headers(init?.headers) });
if (body.action === "force_forbidden") {
return new Response(JSON.stringify({ detail: "Invalid A2A auth token" }), { status: 403, headers: { "content-type": "application/json" } });
}
return new Response(JSON.stringify({ task_id: "a2a-task", status: "completed", messages: [] }), { status: 200, headers: { "content-type": "application/json" } });
}) as typeof fetch;
const app = serviceRoutes({ skipAuth: true });
@@ -24,6 +28,11 @@ try {
const matchmakingResponse = await app.request("http://backend.test/matchmaking/a2a", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action: "mark_applied", params: { opportunity_id: "job-1" } }) });
assert.equal(matchmakingResponse.status, 200);
assert.ok(calls[0]?.url.endsWith("/a2a/tasks"));
assert.equal(calls[0]?.headers.get("authorization"), config.a2aAllowedKey ? `Bearer ${config.a2aAllowedKey}` : null);
const forbiddenResponse = await app.request("http://backend.test/matchmaking/a2a", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action: "force_forbidden" }) });
assert.equal(forbiddenResponse.status, 502);
assert.match(await forbiddenResponse.text(), /matchmaking service authentication failed/);
} finally {
globalThis.fetch = originalFetch;
}

View File

@@ -389,17 +389,48 @@ async function callMatchmakingA2a(body: Record<string, unknown>, userId: string)
action: action === "get_feed" ? "get_scout_feed" : action,
user_id: getString(body.user_id) ?? userId,
};
const res = await fetch(target, {
method: "POST",
headers: {
"content-type": "application/json",
...(config.a2aAllowedKey ? { authorization: `Bearer ${config.a2aAllowedKey}` } : {}),
},
body: JSON.stringify(payload),
});
const startedAt = Date.now();
let res: Response;
try {
res = await fetch(target, {
method: "POST",
headers: {
"content-type": "application/json",
...(config.a2aAllowedKey ? { authorization: `Bearer ${config.a2aAllowedKey}` } : {}),
},
body: JSON.stringify(payload),
});
} catch (err) {
log.warn({ err, service: "matchmaking", action, userId, durationMs: Date.now() - startedAt }, "matchmaking gateway request failed");
throw new HTTPException(502, { message: "matchmaking service is unavailable" });
}
const text = await res.text();
const result = text ? JSON.parse(text) as Record<string, unknown> : {};
if (!res.ok) throw new HTTPException(res.status as never, { message: text || "matchmaking request failed" });
let result: Record<string, unknown> = {};
if (text) {
try {
const parsed = JSON.parse(text);
result = isRecord(parsed) ? parsed : { value: parsed };
} catch {
result = { detail: text };
}
}
if (!res.ok) {
const upstreamAuthFailure = res.status === 401 || res.status === 403;
log.warn({
service: "matchmaking",
action,
userId,
upstreamStatus: res.status,
durationMs: Date.now() - startedAt,
}, "matchmaking gateway upstream rejected request");
// A downstream service credential failure is infrastructure, not the
// browser user's auth. Returning 502 prevents an unnecessary Clerk token
// refresh/retry that would simply repeat the same rejected request.
throw new HTTPException((upstreamAuthFailure ? 502 : res.status) as never, {
message: upstreamAuthFailure ? "matchmaking service authentication failed" : text || "matchmaking request failed",
});
}
log.info({ service: "matchmaking", action, userId, upstreamStatus: res.status, durationMs: Date.now() - startedAt }, "matchmaking gateway request completed");
return { action: String(payload.action || action), result };
}