Files
frontend/apps/sn/tests/dredging-upload.test.cjs
T
2026-09-15 10:00:42 +09:00

172 lines
5.5 KiB
JavaScript

const assert = require("node:assert/strict");
const { test } = require("node:test");
const { loadTypeScript } = require("./load-typescript.cjs");
const { uploadPhotosWithReferences } = loadTypeScript(
"src/features/dredging/lib/photo-upload-queue.ts",
);
const photos = () =>
["one", "two", "three"].map((key) => ({ key, file: { name: `${key}.jpg` } }));
test("부분 업로드 성공 참조는 실패 후에도 원본 Map에 남고 재시도는 실패 사진부터 진행", async () => {
const references = new Map();
const calls = [];
let failSecond = true;
const upload = async (file) => {
calls.push(file.name);
if (file.name === "two.jpg" && failSecond) {
failSecond = false;
throw new Error("upload failed");
}
return { atflCd: file.name, atflId: `id-${file.name}` };
};
await assert.rejects(
uploadPhotosWithReferences(photos(), references, upload),
);
assert.deepEqual([...references.keys()], ["one"]);
assert.equal(
await uploadPhotosWithReferences(photos(), references, upload),
references,
);
assert.deepEqual(calls, ["one.jpg", "two.jpg", "two.jpg", "three.jpg"]);
assert.deepEqual([...references.keys()], ["one", "two", "three"]);
});
test("기존 성공·빈 목록·같은 사진 키의 중복은 추가 업로드를 발생시키지 않는다", async () => {
let calls = 0;
const references = new Map([["one", { atflCd: "A", atflId: "B" }]]);
const upload = async () => {
calls++;
return { atflCd: "C", atflId: "D" };
};
assert.equal(
await uploadPhotosWithReferences([], references, upload),
references,
);
await uploadPhotosWithReferences(
[photos()[0], photos()[1], photos()[1]],
references,
upload,
);
assert.equal(calls, 1);
assert.deepEqual(references.get("one"), { atflCd: "A", atflId: "B" });
});
/** 실제 브라우저 API 경계만 모의해 TUS callback과 타이머 정리를 검증한다. */
function browserStubs(t) {
const originalWindow = globalThis.window;
const originalTransfer = globalThis.DataTransfer;
const timers = new Map();
let timerId = 0;
globalThis.window = {
setTimeout(callback, delay) {
assert.equal(delay, 90_000);
timers.set(++timerId, callback);
return timerId;
},
clearTimeout(id) {
timers.delete(id);
},
};
globalThis.DataTransfer = class {
constructor() {
this.files = [];
this.items = { add: (file) => this.files.push(file) };
}
};
t.after(() => {
if (originalWindow === undefined) delete globalThis.window;
else globalThis.window = originalWindow;
if (originalTransfer === undefined) delete globalThis.DataTransfer;
else globalThis.DataTransfer = originalTransfer;
});
t.mock.method(console, "warn", () => {});
return timers;
}
test("실제 photo adapter의 TUS 비동기 오류는 즉시 reject하고 성공 참조·재시도를 보존", async (t) => {
const timers = browserStubs(t);
const calls = [];
let failSecond = true;
const { uploadDredgingPhotoDrafts } = loadTypeScript(
"src/features/dredging/api/photo-upload.ts",
{
"@/components/lib/utils": {
handleUpload(files, options) {
const name = files[0].name;
calls.push(name);
queueMicrotask(() => {
if (name === "two.jpg" && failSecond) {
failSecond = false;
options.handleError(new Error("tus failure"));
return;
}
options.handleResponse({
getHeader: () =>
JSON.stringify({ atflCd: name, atflId: `id-${name}` }),
});
});
},
},
},
);
const references = new Map();
await assert.rejects(
uploadDredgingPhotoDrafts(photos(), references),
/준설사진을 업로드하지 못했습니다/,
);
assert.deepEqual([...references.keys()], ["one"]);
assert.equal(timers.size, 0);
assert.equal(
await uploadDredgingPhotoDrafts(photos(), references),
references,
);
assert.deepEqual(calls, ["one.jpg", "two.jpg", "two.jpg", "three.jpg"]);
assert.equal(timers.size, 0);
});
test("잘못된 X-File-Info는 성공 참조를 만들지 않고 타이머를 해제", async (t) => {
const timers = browserStubs(t);
const { uploadDredgingPhotoDrafts } = loadTypeScript(
"src/features/dredging/api/photo-upload.ts",
{
"@/components/lib/utils": {
handleUpload(_files, options) {
options.handleResponse({ getHeader: () => '{"atflCd":"A"}' });
},
},
},
);
const references = new Map();
await assert.rejects(uploadDredgingPhotoDrafts([photos()[0]], references));
assert.equal(references.size, 0);
assert.equal(timers.size, 0);
});
test("FE 선행 모의 모드에서는 TUS를 호출하거나 참조를 추가하지 않는다", async () => {
const previous = process.env.NEXT_PUBLIC_DREDGING_BACKEND_ENABLED;
process.env.NEXT_PUBLIC_DREDGING_BACKEND_ENABLED = "false";
try {
const { uploadDredgingPhotoDrafts } = loadTypeScript(
"src/features/dredging/api/photo-upload.ts",
{
"@/components/lib/utils": {
handleUpload() {
assert.fail("mock mode must not upload");
},
},
},
);
const references = new Map();
assert.equal(
await uploadDredgingPhotoDrafts(photos(), references),
references,
);
assert.equal(references.size, 0);
} finally {
if (previous === undefined)
delete process.env.NEXT_PUBLIC_DREDGING_BACKEND_ENABLED;
else process.env.NEXT_PUBLIC_DREDGING_BACKEND_ENABLED = previous;
}
});