Files
sase.tr/packages/config/src/index.spec.ts
Fusion 1039812a8f feat(FN-349): add vitest tests for @sase/config and @sase/shared packages
Fusion-Task-Id: FN-349
Fusion-Task-Lineage: 6221f636-1ede-4ebb-b8b2-b3abb3b252c3
2026-05-14 05:21:21 +00:00

393 lines
13 KiB
TypeScript

import { describe, it, expect, beforeEach } from "vitest";
import { envSchema, validateEnv } from "./index.js";
/** All required env vars (no defaults/optional) */
const REQUIRED_ENV = {
DATABASE_URL: "postgresql://user:pass@localhost:5432/sase",
REDIS_PASSWORD: "redis-secret-password",
BETTER_AUTH_SECRET: "x".repeat(32),
BETTER_AUTH_URL: "http://localhost:4000",
MINIO_ENDPOINT: "http://localhost:9000",
MINIO_ACCESS_KEY: "minio-access",
MINIO_SECRET_KEY: "minio-secret",
MINIO_PUBLIC_URL: "http://localhost:9000/sase-schemas",
};
/** Full valid env object */
function validEnv() {
return { ...REQUIRED_ENV };
}
describe("envSchema", () => {
describe("happy path", () => {
it("parses minimal valid env (required only)", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.DATABASE_URL).toBe(REQUIRED_ENV.DATABASE_URL);
expect(result.data.BETTER_AUTH_SECRET).toBe(REQUIRED_ENV.BETTER_AUTH_SECRET);
}
});
it("parses full env with all optional fields set", () => {
const env = {
...REQUIRED_ENV,
NODE_ENV: "production",
GOOGLE_CLIENT_ID: "google-client-id",
GOOGLE_CLIENT_SECRET: "google-client-secret",
STRIPE_SECRET_KEY: "sk_test_...",
STRIPE_PUBLISHABLE_KEY: "pk_test_...",
STRIPE_WEBHOOK_SECRET: "whsec_...",
PL24_BASE_URL: "https://pl24.example.com",
PL24_COMPANY_CODE: "tr-903645",
PL24_USERNAME: "pl24-user",
PL24_PASSWORD: "pl24-pass",
PL24_COMPANY_CODE_2: "de-708171",
PL24_USERNAME_2: "pl24-user-2",
PL24_PASSWORD_2: "pl24-pass-2",
PL24_PROXY_DE: "http://user:pass@gw.dataimpulse.com:10000",
EMEX_USERNAME: "emex-user",
EMEX_PASSWORD: "emex-pass",
PCAT_PROXY_USER: "pcat-user",
PCAT_PROXY_PASS: "pcat-pass",
OPENROUTER_API_KEY: "sk-or-...",
POSTAL_API_URL: "https://postal.example.com",
POSTAL_API_KEY: "postal-key",
OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318",
OTEL_EXPORTER_OTLP_HEADERS: "x-api-key=abc",
POSTHOG_API_KEY: "phc_...",
SENTRY_DSN: "https://abc@sentr.ing/123",
CHANGELOG_AUTOMATION_TOKEN: "y".repeat(32),
};
const result = envSchema.safeParse(env);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.NODE_ENV).toBe("production");
expect(result.data.GOOGLE_CLIENT_ID).toBe("google-client-id");
expect(result.data.STRIPE_SECRET_KEY).toBe("sk_test_...");
expect(result.data.CHANGELOG_AUTOMATION_TOKEN).toBe("y".repeat(32));
}
});
});
describe("missing required fields", () => {
const requiredKeys = Object.keys(REQUIRED_ENV) as (keyof typeof REQUIRED_ENV)[];
for (const key of requiredKeys) {
it(`throws when ${key} is missing`, () => {
const env = { ...REQUIRED_ENV };
delete env[key];
const result = envSchema.safeParse(env);
expect(result.success).toBe(false);
});
}
});
describe("invalid types", () => {
it("rejects non-URL DATABASE_URL", () => {
const result = envSchema.safeParse({ ...REQUIRED_ENV, DATABASE_URL: "not-a-url" });
expect(result.success).toBe(false);
});
it("rejects non-URL BETTER_AUTH_URL", () => {
const result = envSchema.safeParse({ ...REQUIRED_ENV, BETTER_AUTH_URL: "not-a-url" });
expect(result.success).toBe(false);
});
it("rejects too-short BETTER_AUTH_SECRET", () => {
const result = envSchema.safeParse({ ...REQUIRED_ENV, BETTER_AUTH_SECRET: "short" });
expect(result.success).toBe(false);
});
it("rejects out-of-range OTEL_TRACE_SAMPLE_RATE", () => {
const result = envSchema.safeParse({ ...REQUIRED_ENV, OTEL_TRACE_SAMPLE_RATE: "1.5" });
expect(result.success).toBe(false);
});
it("rejects invalid NODE_ENV", () => {
const result = envSchema.safeParse({ ...REQUIRED_ENV, NODE_ENV: "staging" });
expect(result.success).toBe(false);
});
});
describe("coercion and transforms", () => {
it("coerces PORT string to number", () => {
const result = envSchema.safeParse({ ...REQUIRED_ENV, PORT: "8080" });
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.PORT).toBe(8080);
expect(typeof result.data.PORT).toBe("number");
}
});
it("coerces REDIS_PORT string to number", () => {
const result = envSchema.safeParse({ ...REQUIRED_ENV, REDIS_PORT: "6380" });
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.REDIS_PORT).toBe(6380);
}
});
it("transforms MINIO_USE_SSL=true string to boolean true", () => {
const result = envSchema.safeParse({ ...REQUIRED_ENV, MINIO_USE_SSL: "true" });
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.MINIO_USE_SSL).toBe(true);
}
});
it("transforms MINIO_USE_SSL=false string to boolean false", () => {
const result = envSchema.safeParse({ ...REQUIRED_ENV, MINIO_USE_SSL: "false" });
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.MINIO_USE_SSL).toBe(false);
}
});
it("transforms OTEL_ENABLED=true string to boolean true", () => {
const result = envSchema.safeParse({ ...REQUIRED_ENV, OTEL_ENABLED: "true" });
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.OTEL_ENABLED).toBe(true);
}
});
it("transforms OTEL_ENABLED=false string to boolean false", () => {
const result = envSchema.safeParse({ ...REQUIRED_ENV, OTEL_ENABLED: "false" });
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.OTEL_ENABLED).toBe(false);
}
});
it("transforms ML_PREDICTION_ENABLED=true string to boolean true", () => {
const result = envSchema.safeParse({ ...REQUIRED_ENV, ML_PREDICTION_ENABLED: "true" });
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.ML_PREDICTION_ENABLED).toBe(true);
}
});
it("coerces OTEL_TRACE_SAMPLE_RATE string to number", () => {
const result = envSchema.safeParse({ ...REQUIRED_ENV, OTEL_TRACE_SAMPLE_RATE: "0.5" });
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.OTEL_TRACE_SAMPLE_RATE).toBe(0.5);
}
});
});
describe("defaults", () => {
it("defaults NODE_ENV to development", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.NODE_ENV).toBe("development");
}
});
it("defaults PORT to 4000", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.PORT).toBe(4000);
}
});
it("defaults REDIS_HOST to 127.0.0.1", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.REDIS_HOST).toBe("127.0.0.1");
}
});
it("defaults REDIS_PORT to 6379", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.REDIS_PORT).toBe(6379);
}
});
it("defaults MINIO_BUCKET_NAME to sase-schemas", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.MINIO_BUCKET_NAME).toBe("sase-schemas");
}
});
it("defaults MINIO_USE_SSL to false", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.MINIO_USE_SSL).toBe(false);
}
});
it("defaults CORS_ORIGIN to localhost:3000", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.CORS_ORIGIN).toBe("http://localhost:3000");
}
});
it("defaults STRIPE_SUCCESS_URL", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.STRIPE_SUCCESS_URL).toContain("stripe=success");
}
});
it("defaults STRIPE_CANCEL_URL", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.STRIPE_CANCEL_URL).toContain("stripe=cancelled");
}
});
it("defaults PCAT_USE_PROXY to true", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.PCAT_USE_PROXY).toBe("true");
}
});
it("defaults PCAT_PROXY_HOST to gw.dataimpulse.com", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.PCAT_PROXY_HOST).toBe("gw.dataimpulse.com");
}
});
it("defaults ML_PREDICTION_ENABLED to false", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.ML_PREDICTION_ENABLED).toBe(false);
}
});
it("defaults POSTAL_FROM_ADDRESS to noreply@sase.tr", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.POSTAL_FROM_ADDRESS).toBe("noreply@sase.tr");
}
});
it("defaults POSTAL_FROM_NAME to Sase.tr", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.POSTAL_FROM_NAME).toBe("Sase.tr");
}
});
it("defaults OTEL_ENABLED to false", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.OTEL_ENABLED).toBe(false);
}
});
it("defaults OTEL_SERVICE_NAME to sase-api", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.OTEL_SERVICE_NAME).toBe("sase-api");
}
});
it("defaults OTEL_TRACE_SAMPLE_RATE to 1.0", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.OTEL_TRACE_SAMPLE_RATE).toBe(1.0);
}
});
it("defaults POSTHOG_HOST to https://t.sase.tr", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.POSTHOG_HOST).toBe("https://t.sase.tr");
}
});
});
describe("optional fields", () => {
it("returns undefined for GOOGLE_CLIENT_ID when unset", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.GOOGLE_CLIENT_ID).toBeUndefined();
}
});
it("returns undefined for STRIPE_SECRET_KEY when unset", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.STRIPE_SECRET_KEY).toBeUndefined();
}
});
it("returns undefined for SENTRY_DSN when unset", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.SENTRY_DSN).toBeUndefined();
}
});
it("returns undefined for CHANGELOG_AUTOMATION_TOKEN when unset", () => {
const result = envSchema.safeParse(REQUIRED_ENV);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.CHANGELOG_AUTOMATION_TOKEN).toBeUndefined();
}
});
});
});
describe("validateEnv", () => {
const OLD_ENV = { ...process.env };
beforeEach(() => {
process.env = { ...REQUIRED_ENV };
});
afterAll(() => {
process.env = OLD_ENV;
});
it("returns parsed config on success", () => {
const config = validateEnv();
expect(config.DATABASE_URL).toBe(REQUIRED_ENV.DATABASE_URL);
});
it("throws Error when required field is missing", () => {
delete process.env.DATABASE_URL;
expect(() => validateEnv()).toThrow("Invalid environment variables");
});
it("accepts explicit env parameter instead of process.env", () => {
const customEnv = { ...REQUIRED_ENV, PORT: "9999" };
const config = validateEnv(customEnv);
expect(config.PORT).toBe(9999);
});
it("accepts all required env vars as env parameter", () => {
const config = validateEnv(REQUIRED_ENV);
expect(config.DATABASE_URL).toBe(REQUIRED_ENV.DATABASE_URL);
});
});