From b17bb88daa56e690be3056a7bca0e2c497fd0c1f Mon Sep 17 00:00:00 2001 From: Bhagya Amarasinghe Date: Thu, 30 Apr 2026 00:30:11 +0530 Subject: [PATCH] fix: require cube env vars in app config --- .github/actions/cache-build-web/action.yml | 8 ++--- .github/workflows/e2e.yml | 10 ++---- .github/workflows/lint.yml | 11 ++---- .github/workflows/sonarqube.yml | 10 ++---- .github/workflows/test.yml | 10 ++---- apps/web/lib/env.test.ts | 26 ++++++++++---- apps/web/lib/env.ts | 4 +-- .../ee/analysis/api/lib/cube-client.test.ts | 12 ++++--- .../ee/analysis/api/lib/cube-config.test.ts | 36 +++++++------------ 9 files changed, 57 insertions(+), 70 deletions(-) diff --git a/.github/actions/cache-build-web/action.yml b/.github/actions/cache-build-web/action.yml index 4db8d682c6..fe461383c8 100644 --- a/.github/actions/cache-build-web/action.yml +++ b/.github/actions/cache-build-web/action.yml @@ -57,16 +57,14 @@ runs: if: steps.cache-build.outputs.cache-hit != 'true' shell: bash - - name: create .env - run: cp .env.example .env + - name: Create .env + run: pnpm dev:setup shell: bash - - name: Fill ENCRYPTION_KEY, ENTERPRISE_LICENSE_KEY and E2E_TESTING in .env + - name: Fill E2E_TESTING in .env env: E2E_TESTING_MODE: ${{ inputs.e2e_testing_mode }} run: | - RANDOM_KEY=$(openssl rand -hex 32) - sed -i "s/ENCRYPTION_KEY=.*/ENCRYPTION_KEY=${RANDOM_KEY}/" .env echo "E2E_TESTING=$E2E_TESTING_MODE" >> .env shell: bash diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index bf12561d94..482fdc9bf4 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -68,16 +68,12 @@ jobs: run: pnpm install --config.platform=linux --config.architecture=x64 shell: bash - - name: create .env - run: cp .env.example .env + - name: Create .env + run: pnpm dev:setup shell: bash - - name: Fill ENCRYPTION_KEY, ENTERPRISE_LICENSE_KEY and E2E_TESTING in .env + - name: Fill ENTERPRISE_LICENSE_KEY and E2E_TESTING in .env run: | - RANDOM_KEY=$(openssl rand -hex 32) - sed -i "s/ENCRYPTION_KEY=.*/ENCRYPTION_KEY=${RANDOM_KEY}/" .env - sed -i "s/CRON_SECRET=.*/CRON_SECRET=${RANDOM_KEY}/" .env - sed -i "s/NEXTAUTH_SECRET=.*/NEXTAUTH_SECRET=${RANDOM_KEY}/" .env sed -i "s/ENTERPRISE_LICENSE_KEY=.*/ENTERPRISE_LICENSE_KEY=${{ secrets.ENTERPRISE_LICENSE_KEY }}/" .env sed -i "s|REDIS_URL=.*|REDIS_URL=redis://localhost:6379|" .env echo "" >> .env diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f751ac4155..c777cf48d3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,15 +31,8 @@ jobs: - name: Install dependencies run: pnpm install --config.platform=linux --config.architecture=x64 - - name: create .env - run: cp .env.example .env - - - name: Generate Random ENCRYPTION_KEY, CRON_SECRET & NEXTAUTH_SECRET and fill in .env - run: | - RANDOM_KEY=$(openssl rand -hex 32) - sed -i "s/ENCRYPTION_KEY=.*/ENCRYPTION_KEY=${RANDOM_KEY}/" .env - sed -i "s/CRON_SECRET=.*/CRON_SECRET=${RANDOM_KEY}/" .env - sed -i "s/NEXTAUTH_SECRET=.*/NEXTAUTH_SECRET=${RANDOM_KEY}/" .env + - name: Create .env + run: pnpm dev:setup - name: Lint run: pnpm lint diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 9f0a1fc2fa..f30e027a01 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -35,15 +35,11 @@ jobs: - name: Install dependencies run: pnpm install --config.platform=linux --config.architecture=x64 - - name: create .env - run: cp .env.example .env + - name: Create .env + run: pnpm dev:setup - - name: Generate Random ENCRYPTION_KEY, CRON_SECRET & NEXTAUTH_SECRET and fill in .env + - name: Adjust CI-specific env values run: | - RANDOM_KEY=$(openssl rand -hex 32) - sed -i "s/ENCRYPTION_KEY=.*/ENCRYPTION_KEY=${RANDOM_KEY}/" .env - sed -i "s/CRON_SECRET=.*/CRON_SECRET=${RANDOM_KEY}/" .env - sed -i "s/NEXTAUTH_SECRET=.*/NEXTAUTH_SECRET=${RANDOM_KEY}/" .env sed -i "s|REDIS_URL=.*|REDIS_URL=|" .env - name: Run tests with coverage diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2af0cda7ee..e48593a92e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,15 +32,11 @@ jobs: - name: Install dependencies run: pnpm install --config.platform=linux --config.architecture=x64 - - name: create .env - run: cp .env.example .env + - name: Create .env + run: pnpm dev:setup - - name: Generate Random ENCRYPTION_KEY, CRON_SECRET & NEXTAUTH_SECRET and fill in .env + - name: Adjust CI-specific env values run: | - RANDOM_KEY=$(openssl rand -hex 32) - sed -i "s/ENCRYPTION_KEY=.*/ENCRYPTION_KEY=${RANDOM_KEY}/" .env - sed -i "s/CRON_SECRET=.*/CRON_SECRET=${RANDOM_KEY}/" .env - sed -i "s/NEXTAUTH_SECRET=.*/NEXTAUTH_SECRET=${RANDOM_KEY}/" .env sed -i "s|REDIS_URL=.*|REDIS_URL=|" .env - name: Test diff --git a/apps/web/lib/env.test.ts b/apps/web/lib/env.test.ts index 717f781e3a..daa7b0e77f 100644 --- a/apps/web/lib/env.test.ts +++ b/apps/web/lib/env.test.ts @@ -9,6 +9,8 @@ const setTestEnv = (overrides: Record = {}) => { DATABASE_URL: "https://example.com/db", ENCRYPTION_KEY: "12345678901234567890123456789012", HUB_API_URL: "https://hub.formbricks.local", + CUBEJS_API_URL: "https://cube.formbricks.local", + CUBEJS_API_SECRET: "cube-secret", ...overrides, }; }; @@ -77,18 +79,30 @@ describe("env", () => { expect(env.DEBUG_SHOW_RESET_LINK).toBe("1"); }); - test("uses the configured Cube environment variables when provided", async () => { - setTestEnv({ - CUBEJS_API_URL: "https://cube.formbricks.local", - CUBEJS_API_SECRET: "cube-secret", - }); - + test("uses the configured Cube environment variables", async () => { + setTestEnv(); const { env } = await import("./env"); expect(env.CUBEJS_API_URL).toBe("https://cube.formbricks.local"); expect(env.CUBEJS_API_SECRET).toBe("cube-secret"); }); + test("fails to load when the Cube API secret is missing", async () => { + setTestEnv({ + CUBEJS_API_SECRET: undefined, + }); + + await expect(import("./env")).rejects.toThrow("Invalid environment variables"); + }); + + test("fails to load when the Cube API URL is missing", async () => { + setTestEnv({ + CUBEJS_API_URL: undefined, + }); + + await expect(import("./env")).rejects.toThrow("Invalid environment variables"); + }); + test("fails to load when the Cube API URL is invalid", async () => { setTestEnv({ CUBEJS_API_URL: "not-a-url", diff --git a/apps/web/lib/env.ts b/apps/web/lib/env.ts index 2cb942b450..3c2d6ad63d 100644 --- a/apps/web/lib/env.ts +++ b/apps/web/lib/env.ts @@ -194,8 +194,8 @@ const parsedEnv = createEnv({ AI_AZURE_API_KEY: z.string().optional(), AI_AZURE_API_VERSION: z.string().optional(), AI_AZURE_RESOURCE_NAME: z.string().optional(), - CUBEJS_API_SECRET: z.string().optional(), - CUBEJS_API_URL: z.url().optional(), + CUBEJS_API_SECRET: z.string().trim().min(1), + CUBEJS_API_URL: z.url(), HTTP_PROXY: z.url().optional(), HTTPS_PROXY: z.url().optional(), HUB_API_URL: z.url(), diff --git a/apps/web/modules/ee/analysis/api/lib/cube-client.test.ts b/apps/web/modules/ee/analysis/api/lib/cube-client.test.ts index 1a4062cbc8..e1dcbf9a35 100644 --- a/apps/web/modules/ee/analysis/api/lib/cube-client.test.ts +++ b/apps/web/modules/ee/analysis/api/lib/cube-client.test.ts @@ -15,6 +15,7 @@ describe("executeQuery", () => { beforeEach(() => { vi.clearAllMocks(); vi.resetModules(); + vi.doUnmock("@/lib/env"); vi.stubEnv("NODE_ENV", "test"); vi.stubEnv("DATABASE_URL", "postgresql://postgres:postgres@localhost:5432/formbricks?schema=public"); vi.stubEnv("ENCRYPTION_KEY", "12345678901234567890123456789012"); @@ -54,11 +55,14 @@ describe("executeQuery", () => { }); test("throws a configuration error when Cube env is missing", async () => { + vi.resetModules(); vi.unstubAllEnvs(); - vi.stubEnv("NODE_ENV", "test"); - vi.stubEnv("DATABASE_URL", "postgresql://postgres:postgres@localhost:5432/formbricks?schema=public"); - vi.stubEnv("ENCRYPTION_KEY", "12345678901234567890123456789012"); - vi.stubEnv("HUB_API_URL", "https://hub.formbricks.local"); + vi.doMock("@/lib/env", () => ({ + env: { + CUBEJS_API_URL: undefined, + CUBEJS_API_SECRET: undefined, + }, + })); const { CUBE_CONFIGURATION_ERROR_MESSAGE } = await import("./cube-config"); const { executeQuery } = await import("./cube-client"); diff --git a/apps/web/modules/ee/analysis/api/lib/cube-config.test.ts b/apps/web/modules/ee/analysis/api/lib/cube-config.test.ts index d6f70af6d7..02b36fbcf1 100644 --- a/apps/web/modules/ee/analysis/api/lib/cube-config.test.ts +++ b/apps/web/modules/ee/analysis/api/lib/cube-config.test.ts @@ -1,21 +1,16 @@ import jwt from "jsonwebtoken"; -import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; +import { beforeEach, describe, expect, test, vi } from "vitest"; vi.mock("server-only", () => ({})); -const ORIGINAL_ENV = process.env; - -const setTestEnv = (overrides: Record = {}) => { - process.env = { - ...ORIGINAL_ENV, - NODE_ENV: "test", - DATABASE_URL: "https://example.com/db", - ENCRYPTION_KEY: "12345678901234567890123456789012", - HUB_API_URL: "https://hub.formbricks.local", - CUBEJS_API_URL: "https://cube.formbricks.local", - CUBEJS_API_SECRET: "cube-secret", - ...overrides, - }; +const mockCubeEnv = (overrides: { CUBEJS_API_URL?: string; CUBEJS_API_SECRET?: string } = {}) => { + vi.doMock("@/lib/env", () => ({ + env: { + CUBEJS_API_URL: "https://cube.formbricks.local", + CUBEJS_API_SECRET: "cube-secret", + ...overrides, + }, + })); }; describe("cube-config", () => { @@ -23,13 +18,8 @@ describe("cube-config", () => { vi.resetModules(); }); - afterEach(() => { - process.env = ORIGINAL_ENV; - vi.unstubAllEnvs(); - }); - test("normalizes the Cube API URL and signs a JWT from CUBEJS_API_SECRET", async () => { - setTestEnv(); + mockCubeEnv(); const { getCubeApiConfig } = await import("./cube-config"); @@ -42,7 +32,7 @@ describe("cube-config", () => { }); test("preserves a full Cube API URL when it already contains /cubejs-api/v1", async () => { - setTestEnv({ + mockCubeEnv({ CUBEJS_API_URL: "https://cube.formbricks.local/cubejs-api/v1", }); @@ -52,7 +42,7 @@ describe("cube-config", () => { }); test("throws a configuration error when CUBEJS_API_URL is missing", async () => { - setTestEnv({ + mockCubeEnv({ CUBEJS_API_URL: undefined, }); @@ -62,7 +52,7 @@ describe("cube-config", () => { }); test("throws a configuration error when CUBEJS_API_SECRET is missing", async () => { - setTestEnv({ + mockCubeEnv({ CUBEJS_API_SECRET: undefined, });