Răsfoiți Sursa

RHL-002-feat(tests): add comprehensive tests for API endpoints and filesystem interactions

Code_Uwe 1 săptămână în urmă
părinte
comite
5ebf7ed5de

+ 60 - 0
app/api/branches/[branch]/[year]/[month]/days/route.test.js

@@ -0,0 +1,60 @@
+// app/api/branches/[branch]/[year]/[month]/days/route.test.js
+import { describe, it, expect, beforeAll, afterAll } from "vitest";
+import fs from "node:fs/promises";
+import os from "node:os";
+import path from "node:path";
+
+import { GET as getDays } from "./route.js";
+
+let tmpRoot;
+
+beforeAll(async () => {
+	tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "api-days-"));
+	process.env.NAS_ROOT_PATH = tmpRoot;
+
+	// tmpRoot/NL01/2024/10/23
+	await fs.mkdir(path.join(tmpRoot, "NL01", "2024", "10", "23"), {
+		recursive: true,
+	});
+});
+
+afterAll(async () => {
+	await fs.rm(tmpRoot, { recursive: true, force: true });
+});
+
+describe("GET /api/branches/[branch]/[year]/[month]/days", () => {
+	it("returns days for a valid branch/year/month", async () => {
+		const req = new Request("http://localhost/api/branches/NL01/2024/10/days");
+		const ctx = {
+			params: Promise.resolve({
+				branch: "NL01",
+				year: "2024",
+				month: "10",
+			}),
+		};
+
+		const res = await getDays(req, ctx);
+		expect(res.status).toBe(200);
+
+		const body = await res.json();
+		expect(body).toEqual({
+			branch: "NL01",
+			year: "2024",
+			month: "10",
+			days: ["23"],
+		});
+	});
+
+	it("returns 400 when any param is missing", async () => {
+		const req = new Request("http://localhost/api/branches/NL01/2024/10/days");
+		const ctx = {
+			params: Promise.resolve({ branch: "NL01", year: "2024" }), // month missing
+		};
+
+		const res = await getDays(req, ctx);
+		expect(res.status).toBe(400);
+
+		const body = await res.json();
+		expect(body.error).toBe("branch, year oder month fehlt");
+	});
+});

+ 55 - 0
app/api/branches/[branch]/[year]/months/route.test.js

@@ -0,0 +1,55 @@
+// app/api/branches/[branch]/[year]/months/route.test.js
+import { describe, it, expect, beforeAll, afterAll } from "vitest";
+import fs from "node:fs/promises";
+import os from "node:os";
+import path from "node:path";
+
+import { GET as getMonths } from "./route.js";
+
+let tmpRoot;
+
+beforeAll(async () => {
+	tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "api-months-"));
+	process.env.NAS_ROOT_PATH = tmpRoot;
+
+	// tmpRoot/NL01/2024/10
+	await fs.mkdir(path.join(tmpRoot, "NL01", "2024", "10"), {
+		recursive: true,
+	});
+});
+
+afterAll(async () => {
+	await fs.rm(tmpRoot, { recursive: true, force: true });
+});
+
+describe("GET /api/branches/[branch]/[year]/months", () => {
+	it("returns months for a valid branch/year", async () => {
+		const req = new Request("http://localhost/api/branches/NL01/2024/months");
+		const ctx = {
+			params: Promise.resolve({ branch: "NL01", year: "2024" }),
+		};
+
+		const res = await getMonths(req, ctx);
+		expect(res.status).toBe(200);
+
+		const body = await res.json();
+		expect(body).toEqual({
+			branch: "NL01",
+			year: "2024",
+			months: ["10"],
+		});
+	});
+
+	it("returns 400 when branch or year is missing", async () => {
+		const req = new Request("http://localhost/api/branches/NL01/2024/months");
+		const ctx = {
+			params: Promise.resolve({ branch: "NL01" }), // year missing
+		};
+
+		const res = await getMonths(req, ctx);
+		expect(res.status).toBe(400);
+
+		const body = await res.json();
+		expect(body.error).toBe("branch oder year fehlt");
+	});
+});

+ 75 - 0
app/api/branches/[branch]/years/route.test.js

@@ -0,0 +1,75 @@
+// app/api/branches/[branch]/years/route.test.js
+import { describe, it, expect, beforeAll, afterAll } from "vitest";
+import fs from "node:fs/promises";
+import os from "node:os";
+import path from "node:path";
+
+import { GET as getYears } from "./route.js";
+
+let tmpRoot;
+
+beforeAll(async () => {
+	tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "api-years-"));
+	process.env.NAS_ROOT_PATH = tmpRoot;
+
+	// tmpRoot/NL01/2024
+	await fs.mkdir(path.join(tmpRoot, "NL01", "2024"), {
+		recursive: true,
+	});
+});
+
+afterAll(async () => {
+	await fs.rm(tmpRoot, { recursive: true, force: true });
+});
+
+describe("GET /api/branches/[branch]/years", () => {
+	it("returns years for a valid branch", async () => {
+		const req = new Request("http://localhost/api/branches/NL01/years");
+
+		// In Next.js 16, ctx.params is a Promise the framework would resolve.
+		// In tests we simulate that.
+		const ctx = {
+			params: Promise.resolve({ branch: "NL01" }),
+		};
+
+		const res = await getYears(req, ctx);
+		expect(res.status).toBe(200);
+
+		const body = await res.json();
+		expect(body).toEqual({
+			branch: "NL01",
+			years: ["2024"],
+		});
+	});
+
+	it("returns 400 when branch param is missing", async () => {
+		const req = new Request("http://localhost/api/branches/UNKNOWN/years");
+		const ctx = {
+			params: Promise.resolve({}), // no branch
+		};
+
+		const res = await getYears(req, ctx);
+		expect(res.status).toBe(400);
+
+		const body = await res.json();
+		expect(body.error).toBe("branch Parameter fehlt");
+	});
+
+	it("returns 500 when NAS_ROOT_PATH is invalid", async () => {
+		const originalRoot = process.env.NAS_ROOT_PATH;
+		process.env.NAS_ROOT_PATH = path.join(tmpRoot, "does-not-exist");
+
+		const req = new Request("http://localhost/api/branches/NL01/years");
+		const ctx = {
+			params: Promise.resolve({ branch: "NL01" }),
+		};
+
+		const res = await getYears(req, ctx);
+		expect(res.status).toBe(500);
+
+		const body = await res.json();
+		expect(body.error).toContain("Fehler beim Lesen der Jahre:");
+
+		process.env.NAS_ROOT_PATH = originalRoot;
+	});
+});

+ 51 - 0
app/api/branches/route.test.js

@@ -0,0 +1,51 @@
+// app/api/branches/route.test.js
+import { describe, it, expect, beforeAll, afterAll } from "vitest";
+import fs from "node:fs/promises";
+import os from "node:os";
+import path from "node:path";
+
+import { GET as getBranches } from "./route.js";
+
+let tmpRoot;
+
+beforeAll(async () => {
+	// Create a dedicated temporary root for this test suite
+	tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "api-branches-"));
+
+	// Make this temp root the NAS root for the duration of these tests
+	process.env.NAS_ROOT_PATH = tmpRoot;
+
+	// Create a branch and a snapshot folder
+	await fs.mkdir(path.join(tmpRoot, "NL01"), { recursive: true });
+	await fs.mkdir(path.join(tmpRoot, "@Recently-Snapshot"));
+});
+
+afterAll(async () => {
+	await fs.rm(tmpRoot, { recursive: true, force: true });
+});
+
+describe("GET /api/branches", () => {
+	it("returns branch names and filters snapshot folders", async () => {
+		const req = new Request("http://localhost/api/branches");
+		const res = await getBranches(req);
+		expect(res.status).toBe(200);
+
+		const body = await res.json();
+		expect(body).toEqual({ branches: ["NL01"] });
+	});
+
+	it("returns 500 when NAS_ROOT_PATH is invalid", async () => {
+		const originalRoot = process.env.NAS_ROOT_PATH;
+		process.env.NAS_ROOT_PATH = path.join(tmpRoot, "does-not-exist");
+
+		const req = new Request("http://localhost/api/branches");
+		const res = await getBranches(req);
+		expect(res.status).toBe(500);
+
+		const body = await res.json();
+		expect(body.error).toContain("Fehler beim Lesen der Niederlassungen");
+
+		// Restore valid root for subsequent tests
+		process.env.NAS_ROOT_PATH = originalRoot;
+	});
+});

+ 60 - 0
app/api/files/route.test.js

@@ -0,0 +1,60 @@
+// app/api/files/route.test.js
+import { describe, it, expect, beforeAll, afterAll } from "vitest";
+import fs from "node:fs/promises";
+import os from "node:os";
+import path from "node:path";
+
+import { GET as getFiles } from "./route.js";
+
+let tmpRoot;
+
+beforeAll(async () => {
+	tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "api-files-"));
+	process.env.NAS_ROOT_PATH = tmpRoot;
+
+	// tmpRoot/NL01/2024/10/23/test.pdf
+	await fs.mkdir(path.join(tmpRoot, "NL01", "2024", "10", "23"), {
+		recursive: true,
+	});
+	await fs.writeFile(
+		path.join(tmpRoot, "NL01", "2024", "10", "23", "test.pdf"),
+		"content"
+	);
+});
+
+afterAll(async () => {
+	await fs.rm(tmpRoot, { recursive: true, force: true });
+});
+
+describe("GET /api/files", () => {
+	it("returns files for a valid query", async () => {
+		const req = new Request(
+			"http://localhost/api/files?branch=NL01&year=2024&month=10&day=23"
+		);
+
+		const res = await getFiles(req);
+		expect(res.status).toBe(200);
+
+		const body = await res.json();
+		expect(body.branch).toBe("NL01");
+		expect(body.year).toBe("2024");
+		expect(body.month).toBe("10");
+		expect(body.day).toBe("23");
+		expect(body.files).toEqual([
+			{
+				name: "test.pdf",
+				relativePath: "NL01/2024/10/23/test.pdf",
+			},
+		]);
+	});
+
+	it("returns 400 when query params are missing", async () => {
+		const req = new Request("http://localhost/api/files"); // no params
+
+		const res = await getFiles(req);
+		expect(res.status).toBe(400);
+
+		const body = await res.json();
+		expect(body.error).toBe("branch, year, month, day sind erforderlich");
+	});
+});

+ 75 - 0
app/api/health/route.test.js

@@ -0,0 +1,75 @@
+// app/api/health/route.test.js
+import { describe, it, expect, vi, beforeEach } from "vitest";
+
+// Mock the DB helper and fs.promises before importing the route
+vi.mock("@/lib/db", () => {
+	return {
+		getDb: vi.fn(),
+	};
+});
+
+vi.mock("fs/promises", () => {
+	return {
+		default: {
+			readdir: vi.fn(),
+		},
+	};
+});
+
+import { GET as getHealth } from "./route.js";
+import { getDb } from "@/lib/db";
+import fs from "fs/promises";
+
+const mockedGetDb = getDb;
+const mockedReaddir = fs.readdir;
+
+beforeEach(() => {
+	vi.resetAllMocks();
+});
+
+describe("GET /api/health", () => {
+	it("reports db=ok and nas with entries when both are healthy", async () => {
+		mockedGetDb.mockResolvedValue({
+			command: vi.fn().mockResolvedValue({ ok: 1 }),
+		});
+		mockedReaddir.mockResolvedValue(["NL01", "NL02"]);
+
+		const req = new Request("http://localhost/api/health");
+		const res = await getHealth(req);
+		expect(res.status).toBe(200);
+
+		const body = await res.json();
+		expect(body.db).toBe("ok");
+		expect(body.nas.path).toBe(
+			process.env.NAS_ROOT_PATH || "/mnt/niederlassungen"
+		);
+		expect(body.nas.entriesSample).toEqual(["NL01", "NL02"]);
+	});
+
+	it("reports db error when getDb throws", async () => {
+		mockedGetDb.mockRejectedValue(new Error("DB down"));
+		mockedReaddir.mockResolvedValue(["NL01"]);
+
+		const req = new Request("http://localhost/api/health");
+		const res = await getHealth(req);
+		const body = await res.json();
+
+		expect(body.db).toBe("error: DB down");
+		// NAS is still ok in this scenario
+		expect(body.nas.entriesSample).toEqual(["NL01"]);
+	});
+
+	it("reports nas error when readdir fails", async () => {
+		mockedGetDb.mockResolvedValue({
+			command: vi.fn().mockResolvedValue({ ok: 1 }),
+		});
+		mockedReaddir.mockRejectedValue(new Error("ENOENT"));
+
+		const req = new Request("http://localhost/api/health");
+		const res = await getHealth(req);
+		const body = await res.json();
+
+		expect(body.db).toBe("ok");
+		expect(body.nas).toBe("error: ENOENT");
+	});
+});