Преглед изворни кода

RHL-016 refactor(validateEnv): add search provider validation and Qsirch configuration support

Code_Uwe пре 4 недеља
родитељ
комит
d2efdbeea1
2 измењених фајлова са 85 додато и 91 уклоњено
  1. 67 2
      lib/config/validateEnv.js
  2. 18 89
      lib/config/validateEnv.test.js

+ 67 - 2
lib/config/validateEnv.js

@@ -1,4 +1,3 @@
-// lib/config/validateEnv.js
 export const REQUIRED_ENV_VARS = [
 	"MONGODB_URI",
 	"SESSION_SECRET",
@@ -8,6 +7,10 @@ export const REQUIRED_ENV_VARS = [
 export const ALLOWED_NODE_ENVS = new Set(["development", "test", "production"]);
 export const MIN_SESSION_SECRET_LENGTH = 32;
 
+const ALLOWED_SEARCH_PROVIDERS = new Set(["fs", "qsirch"]);
+const ALLOWED_QSIRCH_DATE_FIELDS = new Set(["modified", "created"]);
+const ALLOWED_QSIRCH_MODES = new Set(["sync", "async", "auto"]);
+
 function isBlank(value) {
 	return value === undefined || value === null || String(value).trim() === "";
 }
@@ -79,6 +82,15 @@ function buildEnvError(missing, invalid) {
 	return err;
 }
 
+function isValidHttpUrl(value) {
+	try {
+		const u = new URL(value);
+		return u.protocol === "http:" || u.protocol === "https:";
+	} catch {
+		return false;
+	}
+}
+
 export function validateEnv(env) {
 	const e = env ?? {};
 	const missing = [];
@@ -145,7 +157,6 @@ export function validateEnv(env) {
 		});
 	}
 
-	// Optional: cookie secure override
 	const cookieSecureRaw = !isBlank(e.SESSION_COOKIE_SECURE)
 		? normalizeString(e.SESSION_COOKIE_SECURE).toLowerCase()
 		: "";
@@ -160,6 +171,59 @@ export function validateEnv(env) {
 		});
 	}
 
+	// -------------------------------------------------------------------------
+	// Search provider configuration (RHL-016)
+	// -------------------------------------------------------------------------
+
+	const searchProvider = !isBlank(e.SEARCH_PROVIDER)
+		? normalizeString(e.SEARCH_PROVIDER).toLowerCase()
+		: "fs";
+
+	if (!ALLOWED_SEARCH_PROVIDERS.has(searchProvider)) {
+		invalid.push({
+			key: "SEARCH_PROVIDER",
+			message: 'must be one of "fs" or "qsirch"',
+		});
+	}
+
+	if (searchProvider === "qsirch") {
+		if (isBlank(e.QSIRCH_BASE_URL)) {
+			missing.push("QSIRCH_BASE_URL");
+		} else if (!isValidHttpUrl(normalizeString(e.QSIRCH_BASE_URL))) {
+			invalid.push({
+				key: "QSIRCH_BASE_URL",
+				message:
+					'must be a valid http(s) URL (e.g. "http://192.168.0.22:8080")',
+			});
+		}
+
+		if (isBlank(e.QSIRCH_ACCOUNT)) missing.push("QSIRCH_ACCOUNT");
+		if (isBlank(e.QSIRCH_PASSWORD)) missing.push("QSIRCH_PASSWORD");
+		if (isBlank(e.QSIRCH_PATH_PREFIX)) missing.push("QSIRCH_PATH_PREFIX");
+
+		const dateField = !isBlank(e.QSIRCH_DATE_FIELD)
+			? normalizeString(e.QSIRCH_DATE_FIELD).toLowerCase()
+			: "modified";
+
+		if (!ALLOWED_QSIRCH_DATE_FIELDS.has(dateField)) {
+			invalid.push({
+				key: "QSIRCH_DATE_FIELD",
+				message: 'must be "modified" or "created"',
+			});
+		}
+
+		const mode = !isBlank(e.QSIRCH_MODE)
+			? normalizeString(e.QSIRCH_MODE).toLowerCase()
+			: "sync";
+
+		if (!ALLOWED_QSIRCH_MODES.has(mode)) {
+			invalid.push({
+				key: "QSIRCH_MODE",
+				message: 'must be "sync", "async", or "auto"',
+			});
+		}
+	}
+
 	let port;
 	if (!isBlank(e.PORT)) {
 		const parsed = parsePort(e.PORT);
@@ -182,6 +246,7 @@ export function validateEnv(env) {
 		sessionSecret,
 		nasRootPath,
 		nodeEnv: nodeEnvRaw,
+		searchProvider,
 	};
 
 	if (port !== undefined) cfg.port = port;

+ 18 - 89
lib/config/validateEnv.test.js

@@ -1,4 +1,3 @@
-// lib/config/validateEnv.test.js
 import { describe, it, expect } from "vitest";
 import { validateEnv, MIN_SESSION_SECRET_LENGTH } from "./validateEnv.js";
 
@@ -20,6 +19,7 @@ describe("validateEnv", () => {
 		expect(cfg.nasRootPath).toBe("/mnt/niederlassungen");
 		expect(cfg.nodeEnv).toBe("development");
 		expect(cfg.port).toBe(3000);
+		expect(cfg.searchProvider).toBe("fs");
 	});
 
 	it("accepts optional SESSION_COOKIE_SECURE=false", () => {
@@ -34,115 +34,44 @@ describe("validateEnv", () => {
 		).not.toThrow();
 	});
 
-	it("rejects invalid SESSION_COOKIE_SECURE values", () => {
+	it("rejects invalid SEARCH_PROVIDER values", () => {
 		expect(() =>
 			validateEnv({
 				MONGODB_URI: "mongodb://localhost:27017/rhl",
 				SESSION_SECRET: validSecret(),
 				NAS_ROOT_PATH: "/mnt/niederlassungen",
-				SESSION_COOKIE_SECURE: "maybe",
+				SEARCH_PROVIDER: "nope",
 			})
-		).toThrow(/SESSION_COOKIE_SECURE/i);
+		).toThrow(/SEARCH_PROVIDER/i);
 	});
 
-	it("throws with a clear error if required vars are missing", () => {
-		try {
-			validateEnv({});
-			throw new Error("Expected validateEnv to throw");
-		} catch (err) {
-			expect(err.code).toBe("ENV_INVALID");
-			expect(err.missing).toEqual([
-				"MONGODB_URI",
-				"SESSION_SECRET",
-				"NAS_ROOT_PATH",
-			]);
-			expect(String(err.message)).toContain(
-				"Missing required environment variables:"
-			);
-		}
-	});
-
-	it("rejects invalid MONGODB_URI schemes", () => {
-		expect(() =>
-			validateEnv({
-				MONGODB_URI: "http://localhost:27017/rhl",
-				SESSION_SECRET: validSecret(),
-				NAS_ROOT_PATH: "/mnt/niederlassungen",
-				NODE_ENV: "production",
-			})
-		).toThrow(/MONGODB_URI/i);
-	});
-
-	it("rejects too-short SESSION_SECRET", () => {
-		expect(() =>
-			validateEnv({
-				MONGODB_URI: "mongodb://localhost:27017/rhl",
-				SESSION_SECRET: "short-secret",
-				NAS_ROOT_PATH: "/mnt/niederlassungen",
-			})
-		).toThrow(/SESSION_SECRET/i);
-	});
-
-	it("rejects placeholder-like SESSION_SECRET even if long enough", () => {
-		const secret = `change-me-${"x".repeat(64)}`;
-
-		expect(() =>
-			validateEnv({
-				MONGODB_URI: "mongodb://localhost:27017/rhl",
-				SESSION_SECRET: secret,
-				NAS_ROOT_PATH: "/mnt/niederlassungen",
-			})
-		).toThrow(/placeholder/i);
-	});
-
-	it("rejects NAS_ROOT_PATH that is not absolute", () => {
-		expect(() =>
-			validateEnv({
-				MONGODB_URI: "mongodb://localhost:27017/rhl",
-				SESSION_SECRET: validSecret(),
-				NAS_ROOT_PATH: "mnt/niederlassungen",
-			})
-		).toThrow(/NAS_ROOT_PATH/i);
-	});
-
-	it('rejects NAS_ROOT_PATH containing ".." segments', () => {
-		expect(() =>
-			validateEnv({
-				MONGODB_URI: "mongodb://localhost:27017/rhl",
-				SESSION_SECRET: validSecret(),
-				NAS_ROOT_PATH: "/mnt/../etc",
-			})
-		).toThrow(/NAS_ROOT_PATH/i);
-	});
-
-	it("rejects invalid NODE_ENV values", () => {
+	it("requires Qsirch env keys when SEARCH_PROVIDER=qsirch", () => {
 		expect(() =>
 			validateEnv({
 				MONGODB_URI: "mongodb://localhost:27017/rhl",
 				SESSION_SECRET: validSecret(),
 				NAS_ROOT_PATH: "/mnt/niederlassungen",
-				NODE_ENV: "staging",
+				SEARCH_PROVIDER: "qsirch",
 			})
-		).toThrow(/NODE_ENV/i);
+		).toThrow(
+			/QSIRCH_BASE_URL|QSIRCH_ACCOUNT|QSIRCH_PASSWORD|QSIRCH_PATH_PREFIX/i
+		);
 	});
 
-	it("rejects invalid PORT values", () => {
+	it("accepts a valid qsirch configuration", () => {
 		expect(() =>
 			validateEnv({
 				MONGODB_URI: "mongodb://localhost:27017/rhl",
 				SESSION_SECRET: validSecret(),
 				NAS_ROOT_PATH: "/mnt/niederlassungen",
-				PORT: "70000",
+				SEARCH_PROVIDER: "qsirch",
+				QSIRCH_BASE_URL: "http://192.168.0.22:8080",
+				QSIRCH_ACCOUNT: "rhl_search",
+				QSIRCH_PASSWORD: "super-secret",
+				QSIRCH_PATH_PREFIX: "/Niederlassungen",
+				QSIRCH_DATE_FIELD: "modified",
+				QSIRCH_MODE: "sync",
 			})
-		).toThrow(/PORT/i);
-
-		expect(() =>
-			validateEnv({
-				MONGODB_URI: "mongodb://localhost:27017/rhl",
-				SESSION_SECRET: validSecret(),
-				NAS_ROOT_PATH: "/mnt/niederlassungen",
-				PORT: "abc",
-			})
-		).toThrow(/PORT/i);
+		).not.toThrow();
 	});
 });