Keep Kakao waypoint validation at the proxy boundary

Constraint: Kakao Mobility waypoint coordinates share the same x,y shape as origin and destination.\nRejected: Letting out-of-range waypoints reach upstream | it spends quota on a deterministic bad request.\nConfidence: high\nScope-risk: narrow\nDirective: Keep Kakao Mobility coordinate validation local before cache lookup or upstream fetch.\nTested: node --test packages/k-skill-proxy/test/server.test.js; npm test --workspace k-skill-proxy; npm run lint --workspace k-skill-proxy; node --test scripts/skill-docs.test.js; bash scripts/validate-skills.sh; manual Fastify inject invalid waypoint 400/0 upstream calls and valid waypoint 200/1 upstream call.\nNot-tested: npm run ci full root pipeline, because prior PR validation documented a local Python 3.14 pyexpat/pip install environment blocker.
This commit is contained in:
Jeffrey (Dongkyu) Kim 2026-05-23 21:56:35 +09:00
commit 51ea778a2d
2 changed files with 32 additions and 1 deletions

View file

@ -251,9 +251,17 @@ function normalizeKakaoMobilityDirectionsQuery(query) {
}
for (const [index, entry] of entries.entries()) {
const parts = entry.split(",").map((p) => p.trim());
if (parts.length !== 2 || !Number.isFinite(parseFloatOrNaN(parts[0])) || !Number.isFinite(parseFloatOrNaN(parts[1]))) {
if (parts.length !== 2) {
throw new Error(`Provide waypoint[${index}] as numeric 'x,y'.`);
}
const x = parseFloatOrNaN(parts[0]);
const y = parseFloatOrNaN(parts[1]);
if (!Number.isFinite(x) || !Number.isFinite(y)) {
throw new Error(`Provide waypoint[${index}] as numeric 'x,y'.`);
}
if (x < -180 || x > 180 || y < -90 || y > 90) {
throw new Error(`Provide valid waypoint[${index}] coordinates.`);
}
}
waypoints = entries.join("|");
}

View file

@ -1064,6 +1064,29 @@ test("Kakao Mobility directions endpoint validates coordinate, priority, and way
assert.equal(tooManyWaypoints.statusCode, 400);
});
test("Kakao Mobility directions rejects out-of-range waypoints before upstream", async (t) => {
const originalFetch = global.fetch;
let upstreamCalls = 0;
global.fetch = async () => {
upstreamCalls += 1;
throw new Error("Unexpected upstream call for invalid waypoint.");
};
const app = buildServer({ env: { KAKAO_REST_API_KEY: "k" } });
t.after(async () => {
global.fetch = originalFetch;
await app.close();
});
const response = await app.inject({
method: "GET",
url: "/v1/kakao-mobility/directions?origin=127.0,37.5&destination=127.1,37.6&waypoints=181,37.55"
});
assert.equal(response.statusCode, 400);
assert.match(response.json().message, /waypoint\[0\]/);
assert.equal(upstreamCalls, 0);
});
test("Kakao Mobility directions surfaces routes[0].result_code != 0 as 502 and does not cache", async (t) => {
const originalFetch = global.fetch;
let upstreamCalls = 0;