mirror of
https://github.com/NomaDamas/k-skill.git
synced 2026-06-24 02:04:11 +00:00
fix(household-waste-info): force returnType=json, add proxy tests, fix SKILL.md newline
- Drop user-supplied returnType and force "json" upstream so the cache key (which omits returnType) cannot be poisoned by alternate response shapes. - Add server tests covering: missing SGG_NM (400), missing API key (503), serviceKey injection + cache hit on second call, returnType=xml override ignored, upstream non-200 surfaced as 502. - Add trailing newline to household-waste-info/SKILL.md. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bcd32ac7cc
commit
baea3f2c24
3 changed files with 151 additions and 3 deletions
|
|
@ -123,4 +123,4 @@ curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/household-waste/info' \
|
|||
- 사용자 측에 `DATA_GO_KR_API_KEY`를 저장하지 않고 proxy 서버에서만 관리한다.
|
||||
- API raw payload를 그대로 노출하지 말고 사용자 친화적으로 요약한다.
|
||||
- 응답이 여러 건이면 최신 `DAT_UPDT_PNT` 기준으로 우선 정렬해 보여준다.
|
||||
- 공식 데이터 출처: 공공데이터포털 (`https://www.data.go.kr`)
|
||||
- 공식 데이터 출처: 공공데이터포털 (`https://www.data.go.kr`)
|
||||
|
|
|
|||
|
|
@ -1007,7 +1007,6 @@ function buildServer({ env = process.env, provider = null } = {}) {
|
|||
|
||||
const pageNo = query.pageNo || "1";
|
||||
const numOfRows = query.numOfRows || "20";
|
||||
const returnType = query.returnType || "json";
|
||||
|
||||
const cacheKey = makeCacheKey({
|
||||
route: "household-waste-info",
|
||||
|
|
@ -1039,7 +1038,7 @@ function buildServer({ env = process.env, provider = null } = {}) {
|
|||
url.searchParams.set("serviceKey", config.molitApiKey);
|
||||
url.searchParams.set("pageNo", pageNo);
|
||||
url.searchParams.set("numOfRows", numOfRows);
|
||||
url.searchParams.set("returnType", returnType);
|
||||
url.searchParams.set("returnType", "json");
|
||||
url.searchParams.set("cond[SGG_NM::LIKE]", sggNm.trim());
|
||||
|
||||
let upstreamData;
|
||||
|
|
|
|||
|
|
@ -791,3 +791,152 @@ test("health endpoint reports molitConfigured status", async (t) => {
|
|||
|
||||
assert.equal(response.json().upstreams.molitConfigured, true);
|
||||
});
|
||||
|
||||
test("household waste info endpoint requires SGG_NM filter", async (t) => {
|
||||
const app = buildServer({
|
||||
env: { DATA_GO_KR_API_KEY: "test-key" }
|
||||
});
|
||||
|
||||
t.after(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
const response = await app.inject({
|
||||
method: "GET",
|
||||
url: "/v1/household-waste/info"
|
||||
});
|
||||
|
||||
assert.equal(response.statusCode, 400);
|
||||
assert.equal(response.json().error, "bad_request");
|
||||
});
|
||||
|
||||
test("household waste info endpoint reports 503 when DATA_GO_KR_API_KEY is missing", async (t) => {
|
||||
const app = buildServer();
|
||||
|
||||
t.after(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
const response = await app.inject({
|
||||
method: "GET",
|
||||
url: "/v1/household-waste/info?cond%5BSGG_NM%3A%3ALIKE%5D=%EA%B0%95%EB%82%A8%EA%B5%AC"
|
||||
});
|
||||
|
||||
assert.equal(response.statusCode, 503);
|
||||
assert.equal(response.json().error, "upstream_not_configured");
|
||||
});
|
||||
|
||||
test("household waste info endpoint injects serviceKey, forces returnType=json, and caches", async (t) => {
|
||||
const originalFetch = global.fetch;
|
||||
const fetchCalls = [];
|
||||
global.fetch = async (url) => {
|
||||
fetchCalls.push(String(url));
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
response: {
|
||||
body: {
|
||||
items: [
|
||||
{
|
||||
SGG_NM: "강남구",
|
||||
MNG_ZONE_NM: "역삼1동",
|
||||
EMSN_PLC: "지정장소",
|
||||
LF_WST_EMSN_DOW: "월,수,금",
|
||||
LF_WST_EMSN_BGNG_TM: "18:00",
|
||||
LF_WST_EMSN_END_TM: "23:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}),
|
||||
{ status: 200, headers: { "content-type": "application/json" } }
|
||||
);
|
||||
};
|
||||
|
||||
const app = buildServer({
|
||||
env: {
|
||||
DATA_GO_KR_API_KEY: "test-key",
|
||||
KSKILL_PROXY_CACHE_TTL_MS: "60000"
|
||||
}
|
||||
});
|
||||
|
||||
t.after(async () => {
|
||||
global.fetch = originalFetch;
|
||||
await app.close();
|
||||
});
|
||||
|
||||
const url = "/v1/household-waste/info?cond%5BSGG_NM%3A%3ALIKE%5D=%EA%B0%95%EB%82%A8%EA%B5%AC&pageNo=1&numOfRows=20";
|
||||
|
||||
const first = await app.inject({ method: "GET", url });
|
||||
assert.equal(first.statusCode, 200);
|
||||
const firstBody = first.json();
|
||||
assert.equal(firstBody.proxy.cache.hit, false);
|
||||
assert.equal(firstBody.query.sgg_nm, "강남구");
|
||||
assert.equal(firstBody.query.page_no, "1");
|
||||
assert.equal(firstBody.query.num_of_rows, "20");
|
||||
assert.equal(firstBody.response.body.items[0].SGG_NM, "강남구");
|
||||
|
||||
assert.equal(fetchCalls.length, 1);
|
||||
const upstream = new URL(fetchCalls[0]);
|
||||
assert.equal(upstream.origin + upstream.pathname, "https://apis.data.go.kr/1741000/household_waste_info/info");
|
||||
assert.equal(upstream.searchParams.get("serviceKey"), "test-key");
|
||||
assert.equal(upstream.searchParams.get("returnType"), "json");
|
||||
assert.equal(upstream.searchParams.get("pageNo"), "1");
|
||||
assert.equal(upstream.searchParams.get("numOfRows"), "20");
|
||||
assert.equal(upstream.searchParams.get("cond[SGG_NM::LIKE]"), "강남구");
|
||||
|
||||
const second = await app.inject({ method: "GET", url });
|
||||
assert.equal(second.statusCode, 200);
|
||||
assert.equal(second.json().proxy.cache.hit, true);
|
||||
assert.equal(fetchCalls.length, 1);
|
||||
});
|
||||
|
||||
test("household waste info endpoint ignores user-supplied returnType override", async (t) => {
|
||||
const originalFetch = global.fetch;
|
||||
let capturedUrl = "";
|
||||
global.fetch = async (url) => {
|
||||
capturedUrl = String(url);
|
||||
return new Response(JSON.stringify({ response: { body: { items: [] } } }), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" }
|
||||
});
|
||||
};
|
||||
|
||||
const app = buildServer({
|
||||
env: { DATA_GO_KR_API_KEY: "test-key" }
|
||||
});
|
||||
|
||||
t.after(async () => {
|
||||
global.fetch = originalFetch;
|
||||
await app.close();
|
||||
});
|
||||
|
||||
const response = await app.inject({
|
||||
method: "GET",
|
||||
url: "/v1/household-waste/info?cond%5BSGG_NM%3A%3ALIKE%5D=%EC%88%98%EC%9B%90%EC%8B%9C&returnType=xml"
|
||||
});
|
||||
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert.equal(new URL(capturedUrl).searchParams.get("returnType"), "json");
|
||||
});
|
||||
|
||||
test("household waste info endpoint surfaces upstream non-200 as 502", async (t) => {
|
||||
const originalFetch = global.fetch;
|
||||
global.fetch = async () => new Response("oops", { status: 500 });
|
||||
|
||||
const app = buildServer({
|
||||
env: { DATA_GO_KR_API_KEY: "test-key" }
|
||||
});
|
||||
|
||||
t.after(async () => {
|
||||
global.fetch = originalFetch;
|
||||
await app.close();
|
||||
});
|
||||
|
||||
const response = await app.inject({
|
||||
method: "GET",
|
||||
url: "/v1/household-waste/info?cond%5BSGG_NM%3A%3ALIKE%5D=%EA%B0%95%EB%82%A8%EA%B5%AC"
|
||||
});
|
||||
|
||||
assert.equal(response.statusCode, 502);
|
||||
assert.equal(response.json().error, "upstream_error");
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue