fix matchmaking gateway failures
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user