172 lines
5.5 KiB
JavaScript
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;
|
|
}
|
|
});
|