190 lines
6.1 KiB
JavaScript
190 lines
6.1 KiB
JavaScript
const assert = require("node:assert/strict");
|
|
const { test } = require("node:test");
|
|
const { loadTypeScript } = require("./load-typescript.cjs");
|
|
|
|
// Legacy facade를 실제로 실행하고 인증 외부 의존성만 고정한다.
|
|
const transport = loadTypeScript("src/shared/api/http-transport.ts");
|
|
const api = loadTypeScript("app/components/lib/api/index.ts", {
|
|
"@/shared/api/http-transport": transport,
|
|
"@/shared/config": { SITE_ID: "SN-TEST", CLAIM_NAME: "Authorization" },
|
|
"@/entities/auth/lib/token-manager": { tokenManager: {} },
|
|
"@common/auth": { getRemainingMs: () => 0 },
|
|
});
|
|
|
|
for (const method of [
|
|
"getData",
|
|
"postData",
|
|
"putData",
|
|
"deleteData",
|
|
"getRawJson",
|
|
"postRawJson",
|
|
]) {
|
|
test(`${method}: HTTP 500 JSON 오류를 status/body와 함께 reject`, async (t) => {
|
|
const body = { message: "저장 실패", data: null };
|
|
t.mock.method(globalThis, "fetch", async () =>
|
|
Response.json(body, { status: 500 }),
|
|
);
|
|
await assert.rejects(api[method]("v1/test", {}), (error) => {
|
|
assert.equal(error.status, 500);
|
|
assert.equal(error.message, "저장 실패");
|
|
assert.deepEqual(error.body, body);
|
|
return true;
|
|
});
|
|
});
|
|
}
|
|
|
|
test("HTML 502 오류가 JSON 파싱 오류로 바뀌지 않는다", async (t) => {
|
|
t.mock.method(
|
|
globalThis,
|
|
"fetch",
|
|
async () => new Response("<html>Bad Gateway</html>", { status: 502 }),
|
|
);
|
|
await assert.rejects(api.postRawJson("v1/test", {}), (error) => {
|
|
assert.equal(error.status, 502);
|
|
assert.equal(error.body, "<html>Bad Gateway</html>");
|
|
assert.match(error.message, /502/);
|
|
assert.doesNotMatch(error.message, /<html>/);
|
|
return true;
|
|
});
|
|
});
|
|
|
|
test("nested 오류 메시지와 비어 있는 오류 응답", async (t) => {
|
|
const fetchMock = t.mock.method(globalThis, "fetch", async () =>
|
|
Response.json({ error: { message: "권한 없음" } }, { status: 403 }),
|
|
);
|
|
await assert.rejects(api.deleteData("v1/test"), {
|
|
message: "권한 없음",
|
|
status: 403,
|
|
});
|
|
fetchMock.mock.mockImplementation(
|
|
async () => new Response(null, { status: 503 }),
|
|
);
|
|
await assert.rejects(api.postData("v1/test", {}), {
|
|
status: 503,
|
|
body: null,
|
|
});
|
|
});
|
|
|
|
test("POST 성공 래퍼를 해제하고 URL·사이트 헤더·JSON body 유지", async (t) => {
|
|
const calls = [];
|
|
t.mock.method(globalThis, "fetch", async (url, init) => {
|
|
calls.push({ url, init });
|
|
return Response.json({ status: 201, data: { id: 9 } }, { status: 201 });
|
|
});
|
|
assert.deepEqual(await api.postData("v1/test", { fcltMngNo: "P-1" }), {
|
|
id: 9,
|
|
});
|
|
assert.equal(calls[0].url, "/sewerage-service/api/v1/test");
|
|
assert.equal(calls[0].init.headers.get("X-Site-Id"), "SN-TEST");
|
|
assert.equal(calls[0].init.headers.get("Content-Type"), "application/json");
|
|
assert.deepEqual(JSON.parse(calls[0].init.body), { fcltMngNo: "P-1" });
|
|
await api.postData("/attach-service/custom", {});
|
|
assert.equal(calls[1].url, "/attach-service/custom");
|
|
});
|
|
|
|
test("204 정상: JSON 함수 null, DELETE true", async (t) => {
|
|
t.mock.method(
|
|
globalThis,
|
|
"fetch",
|
|
async () => new Response(null, { status: 204 }),
|
|
);
|
|
for (const method of [
|
|
"getData",
|
|
"postData",
|
|
"putData",
|
|
"getRawJson",
|
|
"postRawJson",
|
|
]) {
|
|
assert.equal(await api[method]("v1/test", {}), null);
|
|
}
|
|
assert.equal(await api.deleteData("v1/test"), true);
|
|
});
|
|
|
|
test("DELETE 200 성공 Response는 본문을 읽지 않은 채 반환", async (t) => {
|
|
t.mock.method(globalThis, "fetch", async () =>
|
|
Response.json({ deleted: true }),
|
|
);
|
|
const result = await api.deleteData("v1/test");
|
|
assert.ok(result instanceof Response);
|
|
assert.equal(result.status, 200);
|
|
assert.equal(result.bodyUsed, false);
|
|
assert.deepEqual(await result.json(), { deleted: true });
|
|
});
|
|
|
|
test("실패한 삭제 이후 성공 알림·두 번째 삭제를 실행하지 않는다", async (t) => {
|
|
let calls = 0;
|
|
let success = false;
|
|
t.mock.method(globalThis, "fetch", async () => {
|
|
calls++;
|
|
return new Response(null, { status: 500 });
|
|
});
|
|
await assert.rejects(async () => {
|
|
await api.deleteData("v1/first");
|
|
await api.deleteData("v1/second");
|
|
success = true;
|
|
});
|
|
assert.equal(calls, 1);
|
|
assert.equal(success, false);
|
|
});
|
|
|
|
test("raw GeoJSON·필드 이름 및 PUT raw/wrapper 계약 유지", async (t) => {
|
|
const geojson = {
|
|
type: "FeatureCollection",
|
|
features: [],
|
|
fclt_mng_no: "P",
|
|
fcltMngNo: "Q",
|
|
};
|
|
const fetchMock = t.mock.method(globalThis, "fetch", async () =>
|
|
Response.json(geojson),
|
|
);
|
|
assert.deepEqual(await api.getRawJson("/geoserver/test"), geojson);
|
|
assert.deepEqual(await api.putData("v1/test", {}), geojson);
|
|
fetchMock.mock.mockImplementation(async () => Response.json({ data: null }));
|
|
assert.equal(await api.putData("v1/test", {}), null);
|
|
});
|
|
|
|
test("정상 HTTP에서 HTML 본문은 성공 데이터로 취급하지 않는다", async (t) => {
|
|
t.mock.method(
|
|
globalThis,
|
|
"fetch",
|
|
async () => new Response("<html>login</html>"),
|
|
);
|
|
await assert.rejects(api.getData("v1/test"), {
|
|
status: 200,
|
|
body: "<html>login</html>",
|
|
});
|
|
});
|
|
|
|
test("Headers 객체, 사이트 헤더 및 AbortSignal을 보존하고 취소 예외를 유지", async (t) => {
|
|
const controller = new AbortController();
|
|
const abort = new DOMException("cancelled", "AbortError");
|
|
t.mock.method(globalThis, "fetch", async (_url, init) => {
|
|
assert.equal(init.signal, controller.signal);
|
|
assert.equal(init.headers.get("X-Custom"), "retained");
|
|
assert.equal(init.headers.get("X-Site-Id"), "SN-TEST");
|
|
throw abort;
|
|
});
|
|
await assert.rejects(
|
|
api.getRawJson("v1/test", {
|
|
signal: controller.signal,
|
|
headers: new Headers({ "X-Custom": "retained" }),
|
|
}),
|
|
(error) => error === abort,
|
|
);
|
|
});
|
|
|
|
test("401에서 주입한 인증 callback을 한 번 실행한 뒤 reject", async (t) => {
|
|
let count = 0;
|
|
const client = transport.createHttpTransport({
|
|
baseUrl: "/api",
|
|
onUnauthorized: () => count++,
|
|
});
|
|
t.mock.method(
|
|
globalThis,
|
|
"fetch",
|
|
async () => new Response(null, { status: 401 }),
|
|
);
|
|
await assert.rejects(client.postData("test", {}), { status: 401 });
|
|
assert.equal(count, 1);
|
|
});
|