fix: resolve lint issues (XSS K601, knip unused exports, eslint errors)
- Fix XSS warnings in user.tsx by adding safe attribute to elements - Remove unused exports: ChunkUploadRequest, getArchiveInfo, getFileForDirectDownload, createDirectDownloadHeaders, createConverterArchive - Fix eslint no-unused-vars errors across multiple files - Fix pdfmathtranslate async Promise executor issue - Update tests to use toThrow instead of toMatch for Error objects - Apply prettier formatting
This commit is contained in:
parent
db5f47586e
commit
79fc6f7067
21 changed files with 439 additions and 439 deletions
|
|
@ -31,7 +31,7 @@ describe("MinerU converter properties", () => {
|
|||
|
||||
describe("MinerU converter md-t mode", () => {
|
||||
const testDir = "./test-output-mineru-t";
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
if (!existsSync(testDir)) {
|
||||
mkdirSync(testDir, { recursive: true });
|
||||
|
|
@ -46,12 +46,11 @@ describe("MinerU converter md-t mode", () => {
|
|||
|
||||
test("should call mineru with markdown table mode for md-t", async () => {
|
||||
let mineruArgs: string[] = [];
|
||||
|
||||
|
||||
const mockExecFile: ExecFileFn = (
|
||||
cmd: string,
|
||||
args: string[],
|
||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
options?: any,
|
||||
) => {
|
||||
if (cmd === "mineru") {
|
||||
mineruArgs = args;
|
||||
|
|
@ -64,7 +63,10 @@ describe("MinerU converter md-t mode", () => {
|
|||
if (!existsSync(autoDir)) {
|
||||
mkdirSync(autoDir, { recursive: true });
|
||||
}
|
||||
writeFileSync(join(autoDir, "output.md"), "# Test\n\n| Col1 | Col2 |\n|---|---|\n| A | B |");
|
||||
writeFileSync(
|
||||
join(autoDir, "output.md"),
|
||||
"# Test\n\n| Col1 | Col2 |\n|---|---|\n| A | B |",
|
||||
);
|
||||
callback(null, "MinerU conversion complete", "");
|
||||
} else if (cmd === "tar") {
|
||||
// Simulate .tar creation (no compression)
|
||||
|
|
@ -83,7 +85,7 @@ describe("MinerU converter md-t mode", () => {
|
|||
|
||||
describe("MinerU converter md-i mode", () => {
|
||||
const testDir = "./test-output-mineru-i";
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
if (!existsSync(testDir)) {
|
||||
mkdirSync(testDir, { recursive: true });
|
||||
|
|
@ -98,12 +100,11 @@ describe("MinerU converter md-i mode", () => {
|
|||
|
||||
test("should call mineru with image table mode for md-i", async () => {
|
||||
let capturedArgs: string[] = [];
|
||||
|
||||
|
||||
const mockExecFile: ExecFileFn = (
|
||||
cmd: string,
|
||||
args: string[],
|
||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
options?: any,
|
||||
) => {
|
||||
if (cmd === "mineru") {
|
||||
capturedArgs = args;
|
||||
|
|
@ -135,7 +136,7 @@ describe("MinerU converter md-i mode", () => {
|
|||
|
||||
describe("MinerU converter .tar output (no compression)", () => {
|
||||
const testDir = "./test-output-mineru-tar";
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
if (!existsSync(testDir)) {
|
||||
mkdirSync(testDir, { recursive: true });
|
||||
|
|
@ -151,12 +152,11 @@ describe("MinerU converter .tar output (no compression)", () => {
|
|||
test("should create .tar archive from output (without gzip)", async () => {
|
||||
let tarCalled = false;
|
||||
let tarArgs: string[] = [];
|
||||
|
||||
|
||||
const mockExecFile: ExecFileFn = (
|
||||
cmd: string,
|
||||
args: string[],
|
||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
options?: any,
|
||||
) => {
|
||||
if (cmd === "mineru") {
|
||||
// Simulate MinerU creating output
|
||||
|
|
@ -194,9 +194,8 @@ describe("MinerU converter .tar output (no compression)", () => {
|
|||
test("should reject on mineru error", async () => {
|
||||
const mockExecFile: ExecFileFn = (
|
||||
cmd: string,
|
||||
args: string[],
|
||||
_args: string[],
|
||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
options?: any,
|
||||
) => {
|
||||
if (cmd === "mineru") {
|
||||
callback(new Error("MinerU failed") as ExecFileException, "", "Error processing file");
|
||||
|
|
@ -204,18 +203,18 @@ describe("MinerU converter .tar output (no compression)", () => {
|
|||
};
|
||||
|
||||
const targetPath = join(testDir, "output.tar");
|
||||
expect(convert("test.pdf", "pdf", "md-t", targetPath, undefined, mockExecFile))
|
||||
.rejects.toMatch(/mineru error/);
|
||||
expect(convert("test.pdf", "pdf", "md-t", targetPath, undefined, mockExecFile)).rejects.toMatch(
|
||||
/mineru error/,
|
||||
);
|
||||
});
|
||||
|
||||
test("should use correct tar arguments without compression", async () => {
|
||||
let tarArgs: string[] = [];
|
||||
|
||||
|
||||
const mockExecFile: ExecFileFn = (
|
||||
cmd: string,
|
||||
args: string[],
|
||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
options?: any,
|
||||
) => {
|
||||
if (cmd === "mineru") {
|
||||
const outputDir = args[3];
|
||||
|
|
@ -248,12 +247,11 @@ describe("MinerU converter .tar output (no compression)", () => {
|
|||
|
||||
test("should convert .tar.gz target path to .tar", async () => {
|
||||
let tarOutputPath = "";
|
||||
|
||||
|
||||
const mockExecFile: ExecFileFn = (
|
||||
cmd: string,
|
||||
args: string[],
|
||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
options?: any,
|
||||
) => {
|
||||
if (cmd === "mineru") {
|
||||
const outputDir = args[3];
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { test, expect, describe, beforeEach, afterEach } from "bun:test";
|
|||
import { convert, properties } from "../../src/converters/pdfmathtranslate";
|
||||
import type { ExecFileException } from "node:child_process";
|
||||
import { ExecFileFn } from "../../src/converters/types";
|
||||
import { mkdirSync, existsSync, writeFileSync, rmSync, readFileSync } from "node:fs";
|
||||
import { mkdirSync, existsSync, writeFileSync, rmSync, readdirSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
// Skip common tests as PDFMathTranslate has different behavior (archive output)
|
||||
|
|
@ -31,7 +31,7 @@ describe("PDFMathTranslate converter properties", () => {
|
|||
describe("PDFMathTranslate converter - Chinese translation", () => {
|
||||
const testDir = "./test-output-pdfmathtranslate-zh";
|
||||
const testInputFile = join(testDir, "input.pdf");
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
if (!existsSync(testDir)) {
|
||||
mkdirSync(testDir, { recursive: true });
|
||||
|
|
@ -49,17 +49,16 @@ describe("PDFMathTranslate converter - Chinese translation", () => {
|
|||
test("should call pdf2zh with correct language argument for zh", async () => {
|
||||
let pdf2zhArgs: string[] = [];
|
||||
let pdf2zhCalled = false;
|
||||
|
||||
|
||||
const mockExecFile: ExecFileFn = (
|
||||
cmd: string,
|
||||
args: string[],
|
||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
options?: any,
|
||||
) => {
|
||||
if (cmd === "pdf2zh") {
|
||||
pdf2zhCalled = true;
|
||||
pdf2zhArgs = args;
|
||||
|
||||
|
||||
// Simulate pdf2zh creating output files
|
||||
const outputDirIndex = args.indexOf("-o");
|
||||
if (outputDirIndex !== -1 && args[outputDirIndex + 1]) {
|
||||
|
|
@ -95,7 +94,7 @@ describe("PDFMathTranslate converter - Chinese translation", () => {
|
|||
describe("PDFMathTranslate converter - English translation", () => {
|
||||
const testDir = "./test-output-pdfmathtranslate-en";
|
||||
const testInputFile = join(testDir, "input.pdf");
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
if (!existsSync(testDir)) {
|
||||
mkdirSync(testDir, { recursive: true });
|
||||
|
|
@ -111,12 +110,11 @@ describe("PDFMathTranslate converter - English translation", () => {
|
|||
|
||||
test("should call pdf2zh with correct language argument for en", async () => {
|
||||
let pdf2zhArgs: string[] = [];
|
||||
|
||||
|
||||
const mockExecFile: ExecFileFn = (
|
||||
cmd: string,
|
||||
args: string[],
|
||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
options?: any,
|
||||
) => {
|
||||
if (cmd === "pdf2zh") {
|
||||
pdf2zhArgs = args;
|
||||
|
|
@ -145,7 +143,7 @@ describe("PDFMathTranslate converter - English translation", () => {
|
|||
describe("PDFMathTranslate converter - Japanese translation", () => {
|
||||
const testDir = "./test-output-pdfmathtranslate-ja";
|
||||
const testInputFile = join(testDir, "input.pdf");
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
if (!existsSync(testDir)) {
|
||||
mkdirSync(testDir, { recursive: true });
|
||||
|
|
@ -161,12 +159,11 @@ describe("PDFMathTranslate converter - Japanese translation", () => {
|
|||
|
||||
test("should call pdf2zh with correct language argument for ja", async () => {
|
||||
let pdf2zhArgs: string[] = [];
|
||||
|
||||
|
||||
const mockExecFile: ExecFileFn = (
|
||||
cmd: string,
|
||||
args: string[],
|
||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
options?: any,
|
||||
) => {
|
||||
if (cmd === "pdf2zh") {
|
||||
pdf2zhArgs = args;
|
||||
|
|
@ -195,7 +192,7 @@ describe("PDFMathTranslate converter - Japanese translation", () => {
|
|||
describe("PDFMathTranslate converter - Traditional Chinese", () => {
|
||||
const testDir = "./test-output-pdfmathtranslate-zhtw";
|
||||
const testInputFile = join(testDir, "input.pdf");
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
if (!existsSync(testDir)) {
|
||||
mkdirSync(testDir, { recursive: true });
|
||||
|
|
@ -211,12 +208,11 @@ describe("PDFMathTranslate converter - Traditional Chinese", () => {
|
|||
|
||||
test("should call pdf2zh with correct language argument for zh-TW", async () => {
|
||||
let pdf2zhArgs: string[] = [];
|
||||
|
||||
|
||||
const mockExecFile: ExecFileFn = (
|
||||
cmd: string,
|
||||
args: string[],
|
||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
options?: any,
|
||||
) => {
|
||||
if (cmd === "pdf2zh") {
|
||||
pdf2zhArgs = args;
|
||||
|
|
@ -245,7 +241,7 @@ describe("PDFMathTranslate converter - Traditional Chinese", () => {
|
|||
describe("PDFMathTranslate converter - Output structure", () => {
|
||||
const testDir = "./test-output-pdfmathtranslate-structure";
|
||||
const testInputFile = join(testDir, "input.pdf");
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
if (!existsSync(testDir)) {
|
||||
mkdirSync(testDir, { recursive: true });
|
||||
|
|
@ -262,12 +258,11 @@ describe("PDFMathTranslate converter - Output structure", () => {
|
|||
test("should create archive with original.pdf and translated-<lang>.pdf", async () => {
|
||||
let tarSourceDir = "";
|
||||
let archiveContents: string[] = [];
|
||||
|
||||
|
||||
const mockExecFile: ExecFileFn = (
|
||||
cmd: string,
|
||||
args: string[],
|
||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
options?: any,
|
||||
) => {
|
||||
if (cmd === "pdf2zh") {
|
||||
const outputDirIndex = args.indexOf("-o");
|
||||
|
|
@ -286,7 +281,6 @@ describe("PDFMathTranslate converter - Output structure", () => {
|
|||
tarSourceDir = args[cIndex + 1];
|
||||
// Read the files in the archive source directory
|
||||
if (existsSync(tarSourceDir)) {
|
||||
const { readdirSync } = require("node:fs");
|
||||
archiveContents = readdirSync(tarSourceDir);
|
||||
}
|
||||
}
|
||||
|
|
@ -304,12 +298,11 @@ describe("PDFMathTranslate converter - Output structure", () => {
|
|||
|
||||
test("should only use .tar format, not .tar.gz", async () => {
|
||||
let tarArgs: string[] = [];
|
||||
|
||||
|
||||
const mockExecFile: ExecFileFn = (
|
||||
cmd: string,
|
||||
args: string[],
|
||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
options?: any,
|
||||
) => {
|
||||
if (cmd === "pdf2zh") {
|
||||
const outputDirIndex = args.indexOf("-o");
|
||||
|
|
@ -333,7 +326,7 @@ describe("PDFMathTranslate converter - Output structure", () => {
|
|||
// Verify tar is called with -cf (not -czf for gzip compression)
|
||||
expect(tarArgs[0]).toBe("-cf");
|
||||
expect(tarArgs[0]).not.toBe("-czf");
|
||||
|
||||
|
||||
// Verify output path ends with .tar not .tar.gz
|
||||
const outputTar = tarArgs[1];
|
||||
expect(outputTar).toMatch(/\.tar$/);
|
||||
|
|
@ -345,7 +338,7 @@ describe("PDFMathTranslate converter - Output structure", () => {
|
|||
describe("PDFMathTranslate converter - Error handling", () => {
|
||||
const testDir = "./test-output-pdfmathtranslate-error";
|
||||
const testInputFile = join(testDir, "input.pdf");
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
if (!existsSync(testDir)) {
|
||||
mkdirSync(testDir, { recursive: true });
|
||||
|
|
@ -362,9 +355,8 @@ describe("PDFMathTranslate converter - Error handling", () => {
|
|||
test("should reject with error when pdf2zh fails", async () => {
|
||||
const mockExecFile: ExecFileFn = (
|
||||
cmd: string,
|
||||
args: string[],
|
||||
_args: string[],
|
||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
options?: any,
|
||||
) => {
|
||||
if (cmd === "pdf2zh") {
|
||||
const error = new Error("Translation failed") as ExecFileException;
|
||||
|
|
@ -373,18 +365,17 @@ describe("PDFMathTranslate converter - Error handling", () => {
|
|||
};
|
||||
|
||||
const targetPath = join(testDir, "output.tar");
|
||||
|
||||
|
||||
await expect(
|
||||
convert(testInputFile, "pdf", "pdf-zh", targetPath, undefined, mockExecFile)
|
||||
).rejects.toMatch(/pdf2zh error/);
|
||||
convert(testInputFile, "pdf", "pdf-zh", targetPath, undefined, mockExecFile),
|
||||
).rejects.toThrow(/pdf2zh error|PDFMathTranslate error/);
|
||||
});
|
||||
|
||||
test("should reject when no output PDF is generated", async () => {
|
||||
const mockExecFile: ExecFileFn = (
|
||||
cmd: string,
|
||||
args: string[],
|
||||
_args: string[],
|
||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
options?: any,
|
||||
) => {
|
||||
if (cmd === "pdf2zh") {
|
||||
// Don't create any output files
|
||||
|
|
@ -395,9 +386,9 @@ describe("PDFMathTranslate converter - Error handling", () => {
|
|||
};
|
||||
|
||||
const targetPath = join(testDir, "output.tar");
|
||||
|
||||
|
||||
await expect(
|
||||
convert(testInputFile, "pdf", "pdf-zh", targetPath, undefined, mockExecFile)
|
||||
).rejects.toMatch(/No.*PDF.*found/);
|
||||
convert(testInputFile, "pdf", "pdf-zh", targetPath, undefined, mockExecFile),
|
||||
).rejects.toThrow(/No.*PDF.*found|No translated PDF/);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* Contents.CN Chunk 下載整合測試
|
||||
*
|
||||
*
|
||||
* 測試大檔 chunk 下載的完整流程
|
||||
*/
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ import {
|
|||
getChunk,
|
||||
createChunkDownloadHeaders,
|
||||
} from "../../src/transfer/downloadManager";
|
||||
import { CHUNK_SIZE_BYTES, CHUNK_THRESHOLD_BYTES } from "../../src/transfer/constants";
|
||||
import { CHUNK_SIZE_BYTES } from "../../src/transfer/constants";
|
||||
|
||||
const testDir = "./test-output-chunk-download";
|
||||
|
||||
|
|
@ -32,8 +32,7 @@ describe("Chunk 下載資訊測試", () => {
|
|||
|
||||
test("getChunkDownloadInfo 應返回正確的下載資訊", () => {
|
||||
const testFile = join(testDir, "large-file.bin");
|
||||
const fileSize = 25 * 1024 * 1024; // 25MB
|
||||
|
||||
|
||||
// 建立一個假檔案(只寫入少量資料模擬)
|
||||
const content = Buffer.alloc(1024, "X"); // 1KB
|
||||
writeFileSync(testFile, content);
|
||||
|
|
@ -68,7 +67,7 @@ describe("Chunk 讀取測試", () => {
|
|||
|
||||
test("getChunk 應正確讀取指定的 chunk", async () => {
|
||||
const testFile = join(testDir, "chunked-file.bin");
|
||||
|
||||
|
||||
// 建立測試檔案:20 bytes,分成 4 個 5-byte chunks
|
||||
const content = "AAAAABBBBBCCCCCDDDD"; // 19 bytes
|
||||
writeFileSync(testFile, content);
|
||||
|
|
@ -159,7 +158,7 @@ describe("傳輸模式判斷測試", () => {
|
|||
test("小檔(≤10MB)不應使用 chunk 下載", () => {
|
||||
const smallFile = join(testDir, "small.txt");
|
||||
writeFileSync(smallFile, "Small file content");
|
||||
|
||||
|
||||
expect(shouldUseChunkedDownload(smallFile)).toBe(false);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,13 @@
|
|||
/**
|
||||
* Contents.CN Chunk 上傳整合測試
|
||||
*
|
||||
*
|
||||
* 測試大檔 chunk 上傳的完整流程
|
||||
*/
|
||||
|
||||
import { test, expect, describe, beforeEach, afterEach } from "bun:test";
|
||||
import { mkdirSync, existsSync, writeFileSync, rmSync, readFileSync, statSync } from "node:fs";
|
||||
import { mkdirSync, existsSync, rmSync, readFileSync, statSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
handleChunkUpload,
|
||||
uploadSessionManager,
|
||||
calculateChunkCount,
|
||||
} from "../../src/transfer/uploadManager";
|
||||
import { handleChunkUpload, calculateChunkCount } from "../../src/transfer/uploadManager";
|
||||
import { CHUNK_SIZE_BYTES } from "../../src/transfer/constants";
|
||||
|
||||
const testDir = "./test-output-chunk-upload";
|
||||
|
|
@ -34,7 +30,7 @@ describe("Chunk 上傳整合測試", () => {
|
|||
const fileName = "large-file.bin";
|
||||
const userId = "test-user";
|
||||
const jobId = "test-job";
|
||||
|
||||
|
||||
// 建立測試資料(模擬 15MB 檔案,分 3 個 chunks)
|
||||
const chunkSize = 5 * 1024; // 使用較小的 chunk 以便測試
|
||||
const chunk1 = Buffer.alloc(chunkSize, "A");
|
||||
|
|
@ -42,7 +38,7 @@ describe("Chunk 上傳整合測試", () => {
|
|||
const chunk3 = Buffer.alloc(chunkSize, "C");
|
||||
const totalSize = chunkSize * 3;
|
||||
const totalChunks = 3;
|
||||
|
||||
|
||||
const baseTempDir = join(testDir, "temp");
|
||||
const targetDir = join(testDir, "uploads");
|
||||
mkdirSync(baseTempDir, { recursive: true });
|
||||
|
|
@ -50,8 +46,16 @@ describe("Chunk 上傳整合測試", () => {
|
|||
|
||||
// 上傳 chunk 1
|
||||
const result1 = await handleChunkUpload(
|
||||
uploadId, 0, totalChunks, chunk1, fileName, totalSize,
|
||||
userId, jobId, baseTempDir, targetDir
|
||||
uploadId,
|
||||
0,
|
||||
totalChunks,
|
||||
chunk1,
|
||||
fileName,
|
||||
totalSize,
|
||||
userId,
|
||||
jobId,
|
||||
baseTempDir,
|
||||
targetDir,
|
||||
);
|
||||
expect(result1.success).toBe(true);
|
||||
expect(result1.completed).toBe(false);
|
||||
|
|
@ -59,16 +63,32 @@ describe("Chunk 上傳整合測試", () => {
|
|||
|
||||
// 上傳 chunk 2
|
||||
const result2 = await handleChunkUpload(
|
||||
uploadId, 1, totalChunks, chunk2, fileName, totalSize,
|
||||
userId, jobId, baseTempDir, targetDir
|
||||
uploadId,
|
||||
1,
|
||||
totalChunks,
|
||||
chunk2,
|
||||
fileName,
|
||||
totalSize,
|
||||
userId,
|
||||
jobId,
|
||||
baseTempDir,
|
||||
targetDir,
|
||||
);
|
||||
expect(result2.success).toBe(true);
|
||||
expect(result2.completed).toBe(false);
|
||||
|
||||
// 上傳 chunk 3(最後一個)
|
||||
const result3 = await handleChunkUpload(
|
||||
uploadId, 2, totalChunks, chunk3, fileName, totalSize,
|
||||
userId, jobId, baseTempDir, targetDir
|
||||
uploadId,
|
||||
2,
|
||||
totalChunks,
|
||||
chunk3,
|
||||
fileName,
|
||||
totalSize,
|
||||
userId,
|
||||
jobId,
|
||||
baseTempDir,
|
||||
targetDir,
|
||||
);
|
||||
expect(result3.success).toBe(true);
|
||||
expect(result3.completed).toBe(true);
|
||||
|
|
@ -77,10 +97,10 @@ describe("Chunk 上傳整合測試", () => {
|
|||
// 驗證合併後的檔案
|
||||
const mergedFile = join(targetDir, fileName);
|
||||
expect(existsSync(mergedFile)).toBe(true);
|
||||
|
||||
|
||||
const mergedContent = readFileSync(mergedFile);
|
||||
expect(mergedContent.length).toBe(totalSize);
|
||||
|
||||
|
||||
// 驗證內容正確性
|
||||
const expectedContent = Buffer.concat([chunk1, chunk2, chunk3]);
|
||||
expect(mergedContent.equals(expectedContent)).toBe(true);
|
||||
|
|
@ -91,7 +111,7 @@ describe("Chunk 上傳整合測試", () => {
|
|||
const fileName = "out-of-order.bin";
|
||||
const userId = "test-user";
|
||||
const jobId = "test-job";
|
||||
|
||||
|
||||
const chunkSize = 1024;
|
||||
const chunk0 = Buffer.alloc(chunkSize, "0");
|
||||
const chunk1 = Buffer.alloc(chunkSize, "1");
|
||||
|
|
@ -99,17 +119,61 @@ describe("Chunk 上傳整合測試", () => {
|
|||
const chunk3 = Buffer.alloc(chunkSize, "3");
|
||||
const totalSize = chunkSize * 4;
|
||||
const totalChunks = 4;
|
||||
|
||||
|
||||
const baseTempDir = join(testDir, "temp2");
|
||||
const targetDir = join(testDir, "uploads2");
|
||||
mkdirSync(baseTempDir, { recursive: true });
|
||||
mkdirSync(targetDir, { recursive: true });
|
||||
|
||||
// 亂序上傳:3, 1, 0, 2
|
||||
await handleChunkUpload(uploadId, 3, totalChunks, chunk3, fileName, totalSize, userId, jobId, baseTempDir, targetDir);
|
||||
await handleChunkUpload(uploadId, 1, totalChunks, chunk1, fileName, totalSize, userId, jobId, baseTempDir, targetDir);
|
||||
await handleChunkUpload(uploadId, 0, totalChunks, chunk0, fileName, totalSize, userId, jobId, baseTempDir, targetDir);
|
||||
const finalResult = await handleChunkUpload(uploadId, 2, totalChunks, chunk2, fileName, totalSize, userId, jobId, baseTempDir, targetDir);
|
||||
await handleChunkUpload(
|
||||
uploadId,
|
||||
3,
|
||||
totalChunks,
|
||||
chunk3,
|
||||
fileName,
|
||||
totalSize,
|
||||
userId,
|
||||
jobId,
|
||||
baseTempDir,
|
||||
targetDir,
|
||||
);
|
||||
await handleChunkUpload(
|
||||
uploadId,
|
||||
1,
|
||||
totalChunks,
|
||||
chunk1,
|
||||
fileName,
|
||||
totalSize,
|
||||
userId,
|
||||
jobId,
|
||||
baseTempDir,
|
||||
targetDir,
|
||||
);
|
||||
await handleChunkUpload(
|
||||
uploadId,
|
||||
0,
|
||||
totalChunks,
|
||||
chunk0,
|
||||
fileName,
|
||||
totalSize,
|
||||
userId,
|
||||
jobId,
|
||||
baseTempDir,
|
||||
targetDir,
|
||||
);
|
||||
const finalResult = await handleChunkUpload(
|
||||
uploadId,
|
||||
2,
|
||||
totalChunks,
|
||||
chunk2,
|
||||
fileName,
|
||||
totalSize,
|
||||
userId,
|
||||
jobId,
|
||||
baseTempDir,
|
||||
targetDir,
|
||||
);
|
||||
|
||||
expect(finalResult.success).toBe(true);
|
||||
expect(finalResult.completed).toBe(true);
|
||||
|
|
@ -117,7 +181,7 @@ describe("Chunk 上傳整合測試", () => {
|
|||
// 驗證檔案順序正確
|
||||
const mergedFile = join(targetDir, fileName);
|
||||
const mergedContent = readFileSync(mergedFile);
|
||||
|
||||
|
||||
// 即使亂序上傳,合併後應該按正確順序
|
||||
const expectedContent = Buffer.concat([chunk0, chunk1, chunk2, chunk3]);
|
||||
expect(mergedContent.equals(expectedContent)).toBe(true);
|
||||
|
|
@ -128,26 +192,59 @@ describe("Chunk 上傳整合測試", () => {
|
|||
const fileName = "duplicate-chunk.bin";
|
||||
const userId = "test-user";
|
||||
const jobId = "test-job";
|
||||
|
||||
|
||||
const chunkSize = 512;
|
||||
const chunk0 = Buffer.alloc(chunkSize, "X");
|
||||
const chunk1 = Buffer.alloc(chunkSize, "Y");
|
||||
const totalSize = chunkSize * 2;
|
||||
const totalChunks = 2;
|
||||
|
||||
|
||||
const baseTempDir = join(testDir, "temp3");
|
||||
const targetDir = join(testDir, "uploads3");
|
||||
mkdirSync(baseTempDir, { recursive: true });
|
||||
mkdirSync(targetDir, { recursive: true });
|
||||
|
||||
// 上傳 chunk 0
|
||||
await handleChunkUpload(uploadId, 0, totalChunks, chunk0, fileName, totalSize, userId, jobId, baseTempDir, targetDir);
|
||||
|
||||
await handleChunkUpload(
|
||||
uploadId,
|
||||
0,
|
||||
totalChunks,
|
||||
chunk0,
|
||||
fileName,
|
||||
totalSize,
|
||||
userId,
|
||||
jobId,
|
||||
baseTempDir,
|
||||
targetDir,
|
||||
);
|
||||
|
||||
// 重複上傳 chunk 0
|
||||
await handleChunkUpload(uploadId, 0, totalChunks, chunk0, fileName, totalSize, userId, jobId, baseTempDir, targetDir);
|
||||
|
||||
await handleChunkUpload(
|
||||
uploadId,
|
||||
0,
|
||||
totalChunks,
|
||||
chunk0,
|
||||
fileName,
|
||||
totalSize,
|
||||
userId,
|
||||
jobId,
|
||||
baseTempDir,
|
||||
targetDir,
|
||||
);
|
||||
|
||||
// 上傳 chunk 1
|
||||
const result = await handleChunkUpload(uploadId, 1, totalChunks, chunk1, fileName, totalSize, userId, jobId, baseTempDir, targetDir);
|
||||
const result = await handleChunkUpload(
|
||||
uploadId,
|
||||
1,
|
||||
totalChunks,
|
||||
chunk1,
|
||||
fileName,
|
||||
totalSize,
|
||||
userId,
|
||||
jobId,
|
||||
baseTempDir,
|
||||
targetDir,
|
||||
);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.completed).toBe(true);
|
||||
|
|
@ -162,16 +259,16 @@ describe("Chunk 上傳整合測試", () => {
|
|||
describe("Chunk 數量計算測試", () => {
|
||||
test("應正確計算不同大小檔案的 chunk 數量", () => {
|
||||
const chunkSize = CHUNK_SIZE_BYTES;
|
||||
|
||||
|
||||
// 剛好 1 個 chunk
|
||||
expect(calculateChunkCount(chunkSize)).toBe(1);
|
||||
|
||||
|
||||
// 多 1 byte 需要 2 個 chunks
|
||||
expect(calculateChunkCount(chunkSize + 1)).toBe(2);
|
||||
|
||||
|
||||
// 50MB 需要 10 個 chunks
|
||||
expect(calculateChunkCount(50 * 1024 * 1024)).toBe(10);
|
||||
|
||||
|
||||
// 1 byte 需要 1 個 chunk
|
||||
expect(calculateChunkCount(1)).toBe(1);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* Contents.CN 全域檔案傳輸機制測試
|
||||
*
|
||||
*
|
||||
* 測試項目:
|
||||
* 1. 小檔直傳測試(≤10MB)
|
||||
* 2. 大檔 chunk 上傳測試(>10MB)
|
||||
|
|
@ -16,7 +16,6 @@ import {
|
|||
shouldUseChunkedUpload,
|
||||
calculateChunkCount,
|
||||
handleDirectUpload,
|
||||
handleChunkUpload,
|
||||
uploadSessionManager,
|
||||
} from "../../src/transfer/uploadManager";
|
||||
import {
|
||||
|
|
@ -84,13 +83,13 @@ describe("Chunk 數量計算測試", () => {
|
|||
test("計算 chunk 數量應正確", () => {
|
||||
// 10MB / 5MB = 2 chunks
|
||||
expect(calculateChunkCount(10 * 1024 * 1024)).toBe(2);
|
||||
|
||||
|
||||
// 15MB / 5MB = 3 chunks
|
||||
expect(calculateChunkCount(15 * 1024 * 1024)).toBe(3);
|
||||
|
||||
|
||||
// 1 byte 也需要 1 chunk
|
||||
expect(calculateChunkCount(1)).toBe(1);
|
||||
|
||||
|
||||
// 5MB + 1 byte = 2 chunks
|
||||
expect(calculateChunkCount(5 * 1024 * 1024 + 1)).toBe(2);
|
||||
});
|
||||
|
|
@ -101,7 +100,7 @@ describe("封裝格式驗證測試", () => {
|
|||
// 允許的格式
|
||||
expect(validateArchiveFormat("output.tar")).toBe(true);
|
||||
expect(validateArchiveFormat("my_archive.tar")).toBe(true);
|
||||
|
||||
|
||||
// 禁止的格式
|
||||
expect(validateArchiveFormat("output.tar.gz")).toBe(false);
|
||||
expect(validateArchiveFormat("output.tgz")).toBe(false);
|
||||
|
|
@ -112,19 +111,19 @@ describe("封裝格式驗證測試", () => {
|
|||
test("getArchiveFileName 應強制轉換為 .tar", () => {
|
||||
// 已經是 .tar
|
||||
expect(getArchiveFileName("output.tar")).toBe("output.tar");
|
||||
|
||||
|
||||
// 轉換 .tar.gz -> .tar
|
||||
expect(getArchiveFileName("output.tar.gz")).toBe("output.tar");
|
||||
|
||||
|
||||
// 轉換 .tgz -> .tar
|
||||
expect(getArchiveFileName("output.tgz")).toBe("output.tar");
|
||||
|
||||
|
||||
// 無副檔名 -> 加上 .tar
|
||||
expect(getArchiveFileName("output")).toBe("output.tar");
|
||||
|
||||
|
||||
// .zip 也是禁止的格式,會被移除並加上 .tar
|
||||
expect(getArchiveFileName("output.zip")).toBe("output.tar");
|
||||
|
||||
|
||||
// .gz 也是禁止的格式
|
||||
expect(getArchiveFileName("output.gz")).toBe("output.tar");
|
||||
});
|
||||
|
|
@ -148,7 +147,7 @@ describe("上傳管理測試", () => {
|
|||
const testContent = "Hello, World!";
|
||||
const file = new File([testContent], "test.txt", { type: "text/plain" });
|
||||
|
||||
const result = await handleDirectUpload(file as any, targetDir, "test.txt");
|
||||
const result = await handleDirectUpload(file, targetDir, "test.txt");
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(existsSync(join(targetDir, "test.txt"))).toBe(true);
|
||||
|
|
@ -172,7 +171,7 @@ describe("下載管理測試", () => {
|
|||
test("小檔不應使用 chunk 下載", () => {
|
||||
const smallFile = join(testDir, "small.txt");
|
||||
writeFileSync(smallFile, "Small content");
|
||||
|
||||
|
||||
expect(shouldUseChunkedDownload(smallFile)).toBe(false);
|
||||
});
|
||||
|
||||
|
|
@ -196,7 +195,7 @@ describe("下載管理測試", () => {
|
|||
|
||||
// 讀取第一個 chunk(整個檔案,因為小於 chunk size)
|
||||
const chunk = await getChunk(testFile, 0, 5);
|
||||
|
||||
|
||||
expect(chunk).not.toBeNull();
|
||||
expect(chunk!.toString()).toBe("ABCDE");
|
||||
|
||||
|
|
@ -222,7 +221,7 @@ describe(".tar 封裝測試", () => {
|
|||
test("createTarArchive 應建立 .tar 檔案", async () => {
|
||||
const sourceDir = join(testDir, "source");
|
||||
const outputTar = join(testDir, "output.tar");
|
||||
|
||||
|
||||
mkdirSync(sourceDir, { recursive: true });
|
||||
writeFileSync(join(sourceDir, "file1.txt"), "Content 1");
|
||||
writeFileSync(join(sourceDir, "file2.txt"), "Content 2");
|
||||
|
|
@ -238,7 +237,7 @@ describe(".tar 封裝測試", () => {
|
|||
const sourceDir = join(testDir, "source2");
|
||||
// 即使傳入 .tar.gz,也應該輸出 .tar
|
||||
const outputTar = join(testDir, "output.tar.gz");
|
||||
|
||||
|
||||
mkdirSync(sourceDir, { recursive: true });
|
||||
writeFileSync(join(sourceDir, "file.txt"), "Content");
|
||||
|
||||
|
|
@ -309,7 +308,7 @@ describe("上傳會話管理測試", () => {
|
|||
"large-file.pdf",
|
||||
50 * 1024 * 1024, // 50MB
|
||||
10, // 10 chunks
|
||||
testDir
|
||||
testDir,
|
||||
);
|
||||
|
||||
expect(session.upload_id).toBe(uploadId);
|
||||
|
|
@ -319,7 +318,7 @@ describe("上傳會話管理測試", () => {
|
|||
// 標記已接收 chunks
|
||||
uploadSessionManager.markChunkReceived(uploadId, 0);
|
||||
uploadSessionManager.markChunkReceived(uploadId, 1);
|
||||
|
||||
|
||||
const updatedSession = uploadSessionManager.getSession(uploadId);
|
||||
expect(updatedSession!.received_chunks.size).toBe(2);
|
||||
expect(uploadSessionManager.isComplete(uploadId)).toBe(false);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue