convertor/tests/e2e/api.e2e.test.ts

250 lines
8.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* API E2E 測試
*
* 這些測試使用 Elysia 的測試工具直接測試 HTTP API。
* 不需要啟動獨立的伺服器。
*
* 執行方式:
* bun test tests/e2e/api.e2e.test.ts
*
* 測試覆蓋:
* - Healthcheck API
* - 檔案上傳流程
* - 轉換工作流程
* - 結果下載流程
*/
import { describe, test, expect, beforeAll, afterAll } from "bun:test";
import { Elysia } from "elysia";
import { existsSync } from "node:fs";
import { healthcheck } from "../../src/pages/healthcheck";
import { setupOutputDir } from "./helpers";
beforeAll(() => {
setupOutputDir("api");
});
afterAll(() => {
// 保留輸出目錄以便檢查
});
// ============================================================================
// Healthcheck API 測試
// ============================================================================
describe("Healthcheck API", () => {
const app = new Elysia().use(healthcheck);
test("GET /healthcheck 應返回 200 和 ok 狀態", async () => {
const response = await app.handle(new Request("http://localhost/healthcheck"));
expect(response.status).toBe(200);
const body = await response.json();
expect(body).toEqual({ status: "ok" });
});
test("Healthcheck 應該快速響應", async () => {
const startTime = Date.now();
const response = await app.handle(new Request("http://localhost/healthcheck"));
const duration = Date.now() - startTime;
expect(response.status).toBe(200);
expect(duration).toBeLessThan(100); // 應該在 100ms 內響應
});
});
// ============================================================================
// 靜態資源測試
// ============================================================================
describe("Static Resources", () => {
test("robots.txt 應該存在", () => {
expect(existsSync("public/robots.txt")).toBe(true);
});
test("site.webmanifest 應該存在", () => {
expect(existsSync("public/site.webmanifest")).toBe(true);
});
});
// ============================================================================
// API 結構測試
// ============================================================================
describe("API Structure", () => {
test("轉換器模組應該能正確載入", async () => {
const converters = await import("../../src/converters/main");
expect(converters).toBeDefined();
expect(converters.handleConvert).toBeInstanceOf(Function);
});
test("資料庫模組應該能正確載入", async () => {
const db = await import("../../src/db/db");
expect(db).toBeDefined();
expect(db.default).toBeDefined();
});
test("healthcheck 模組應該能正確載入", async () => {
const module = await import("../../src/pages/healthcheck");
expect(module).toBeDefined();
expect(module.healthcheck).toBeDefined();
});
});
// ============================================================================
// 轉換器清單測試
// ============================================================================
describe("Converter List", () => {
test("應該有正確的轉換器屬性結構", async () => {
const { properties } = await import("../../src/converters/inkscape");
expect(properties).toHaveProperty("from");
expect(properties).toHaveProperty("to");
expect(properties.from).toHaveProperty("images");
expect(properties.to).toHaveProperty("images");
expect(Array.isArray(properties.from.images)).toBe(true);
expect(Array.isArray(properties.to.images)).toBe(true);
});
test("Inkscape 應支援 SVG 輸入", async () => {
const { properties } = await import("../../src/converters/inkscape");
expect(properties.from.images).toContain("svg");
});
test("Inkscape 應支援 PNG 輸出", async () => {
const { properties } = await import("../../src/converters/inkscape");
expect(properties.to.images).toContain("png");
});
test("Inkscape 應支援 PDF 輸出", async () => {
const { properties } = await import("../../src/converters/inkscape");
expect(properties.to.images).toContain("pdf");
});
});
// ============================================================================
// 檔案類型正規化測試
// ============================================================================
describe("File Type Normalization", () => {
test("normalizeFiletype 應該正確處理常見格式", async () => {
const { normalizeFiletype } = await import("../../src/helpers/normalizeFiletype");
// jpg/jfif 會被正規化為 jpeg
expect(normalizeFiletype("jpg")).toBe("jpeg");
expect(normalizeFiletype("jfif")).toBe("jpeg");
expect(normalizeFiletype("JPG")).toBe("jpeg");
// 其他格式保持原樣(小寫)
expect(normalizeFiletype("PNG")).toBe("png");
expect(normalizeFiletype("svg")).toBe("svg");
});
test("normalizeOutputFiletype 應該正確處理輸出格式", async () => {
const { normalizeOutputFiletype } = await import("../../src/helpers/normalizeFiletype");
// jpeg 輸出會轉為 jpg
expect(normalizeOutputFiletype("jpeg")).toBe("jpg");
expect(normalizeOutputFiletype("png")).toBe("png");
expect(normalizeOutputFiletype("pdf")).toBe("pdf");
});
});
// ============================================================================
// 環境變數測試
// ============================================================================
describe("Environment Variables", () => {
test("環境變數模組應該能正確載入", async () => {
const env = await import("../../src/helpers/env");
expect(env).toHaveProperty("WEBROOT");
expect(env).toHaveProperty("AUTO_DELETE_EVERY_N_HOURS");
expect(env).toHaveProperty("MAX_CONVERT_PROCESS");
});
test("WEBROOT 應該是字串", async () => {
const { WEBROOT } = await import("../../src/helpers/env");
expect(typeof WEBROOT).toBe("string");
});
});
// ============================================================================
// i18n 測試
// ============================================================================
describe("i18n Module", () => {
test("i18n 服務應該能正確載入", async () => {
const i18n = await import("../../src/i18n/service");
expect(i18n).toBeDefined();
});
test("預設語言檔案應該存在", () => {
expect(existsSync("src/locales/en.json")).toBe(true);
expect(existsSync("src/locales/zh-TW.json")).toBe(true);
expect(existsSync("src/locales/zh-CN.json")).toBe(true);
});
test("語言檔案應該是有效的 JSON", async () => {
const enContent = await Bun.file("src/locales/en.json").text();
const zhTWContent = await Bun.file("src/locales/zh-TW.json").text();
expect(() => JSON.parse(enContent)).not.toThrow();
expect(() => JSON.parse(zhTWContent)).not.toThrow();
});
});
// ============================================================================
// Transfer 模組測試
// ============================================================================
describe("Transfer Module", () => {
test("Transfer 常數應該正確定義", async () => {
const { CHUNK_THRESHOLD_BYTES, CHUNK_SIZE_BYTES } =
await import("../../src/transfer/constants");
expect(CHUNK_THRESHOLD_BYTES).toBe(10 * 1024 * 1024); // 10MB
expect(CHUNK_SIZE_BYTES).toBe(5 * 1024 * 1024); // 5MB
});
test("允許的封裝格式應該是 .tar", async () => {
const { ALLOWED_ARCHIVE_FORMAT, FORBIDDEN_ARCHIVE_FORMATS } =
await import("../../src/transfer/constants");
expect(ALLOWED_ARCHIVE_FORMAT).toBe(".tar");
expect(FORBIDDEN_ARCHIVE_FORMATS).toContain(".tar.gz");
expect(FORBIDDEN_ARCHIVE_FORMATS).toContain(".zip");
});
test("Upload Manager 應該能正確載入", async () => {
const uploadManager = await import("../../src/transfer/uploadManager");
expect(uploadManager).toBeDefined();
});
test("Download Manager 應該能正確載入", async () => {
const downloadManager = await import("../../src/transfer/downloadManager");
expect(downloadManager).toBeDefined();
});
});
// ============================================================================
// 資料庫結構測試
// ============================================================================
describe("Database Structure", () => {
test("資料庫類型定義應該正確", async () => {
const types = await import("../../src/db/types");
// 這些是 class檢查它們是否可以被建構
expect(types.Jobs).toBeDefined();
expect(types.Filename).toBeDefined();
expect(types.User).toBeDefined();
// 驗證可以創建實例
const job = new types.Jobs();
expect(job).toBeInstanceOf(types.Jobs);
});
});