From 816d2da3fa658aa135eb67c33f73ba9f47c1fce0 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 22 Jul 2026 17:49:27 -0700 Subject: [PATCH 1/8] =?UTF-8?q?chore(ci):=20cut=20redundant=20runner=20min?= =?UTF-8?q?utes=20=E2=80=94=20dedup=20promotion-PR=20test=20runs,=20compan?= =?UTF-8?q?ion-pr-check=20concurrency,=20right-size=20trivial=20jobs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ci.yml: new dedup-promotion gate skips the pull_request test-build on staging/main-headed promotion PRs only when the merge tree provably equals the head tree (empty base delta over the merge base) AND the push-event run at the same sha passed its test jobs (polled). Fail-open on any error/ timeout/failure, job-level skip only (skipped job reports Success); verified no required status checks are configured on main/staging rulesets. Measured 39 duplicate PR runs / 5.15 days (~227/mo) at ~7.1 min each on 8vcpu (~57 vcpu-min), probe costs ~9 vcpu-min worst case on 2vcpu. - companion-pr-check.yml: per-PR concurrency group with cancel-in-progress so superseded synchronize/edit runs stop; no paths filter (check depends on PR body + cross-repo state, not changed files). - detect-version and check-docs-changes: 4vcpu -> 2vcpu Blacksmith runners (pure shell / depth-2 checkout + path filter only). --- .github/workflows/ci.yml | 121 ++++++++++++++++++++++- .github/workflows/companion-pr-check.yml | 11 +++ 2 files changed, 129 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0aa7a40ed9e..47159ea76dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,16 +34,129 @@ permissions: contents: read jobs: + # Promotion PRs (staging→main etc.) double-run the test suite: every commit + # on staging/main already gets a push-event run of the exact same test-build, + # and the pull_request "synchronize" run for the open release PR re-runs it on + # the same sha seconds later (~39 duplicate runs / 5 days measured Jul 2026). + # This gate skips the PR run's test-build ONLY when it can prove the identical + # work already passed elsewhere: + # 1. the PR base adds no file changes over the merge base with the head sha + # (compare head...base has an empty diff), so the merge result's tree is + # identical to the head tree the push run tested. Plain ancestry is not + # enough of a check here: main's merge-only ruleset leaves merge commits + # on main that staging lacks, so main...staging is permanently + # "diverged" — but those merge commits carry no tree delta. A real + # hotfix landed directly on the base makes the diff non-empty and we + # run tests here; + # 2. the push-event CI run at the same head sha finished its test jobs with + # conclusion success (polled, since push + PR runs start simultaneously). + # Fail-open by construction: any API error, timeout, missing run, or push-run + # failure leaves covered=false and the PR run tests normally, so the PR check + # is green only if tests passed either here or on the identical tree. Not a + # workflow-level filter on purpose — a job-level skip still reports a + # (successful) check context. NOTE: when test-build is skipped, its nested + # "Test and Build / ..." contexts are not created; verified 2026-07-22 that + # no required status checks are configured on main/staging (rulesets contain + # only pull_request/deletion/non_fast_forward). If required checks are ever + # added, require the caller "Test and Build" context, not the nested ones. + # dev is excluded: push runs on dev skip test-build, so dev-headed PRs have + # no push-run coverage to reuse. + dedup-promotion: + name: Dedup Promotion PR + runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }} + timeout-minutes: 15 + if: >- + github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository && + contains(fromJSON('["main", "staging"]'), github.event.pull_request.head.ref) + permissions: + contents: read + actions: read + outputs: + covered: ${{ steps.probe.outputs.covered }} + steps: + - name: Probe for a passing push run at the same sha + id: probe + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + run: | + COVERED=false + + # (1) Merge-tree equivalence: compare head...base diffs the merge + # base against the base tip. An empty diff means the base contributes + # nothing beyond what head already contains (merge commits only), so + # the PR merge tree equals the head tree the push run tested. Any + # error yields "unknown" and we run the tests. + BASE_DELTA="$(gh api "repos/${REPO}/compare/${HEAD_SHA}...${BASE_SHA}" --jq '.files | length' 2>/dev/null || echo unknown)" + if [ "$BASE_DELTA" != "0" ]; then + echo "Base tip changes ${BASE_DELTA} file(s) over the merge base; merge tree differs from head — running tests in this PR run." + echo "covered=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # (2) Poll the push-event CI run's test jobs (they start seconds after + # this run and take ~4 min). Any conclusion other than success, or + # deadline expiry, falls through to covered=false. + DEADLINE=$((SECONDS + 600)) + while [ "$SECONDS" -lt "$DEADLINE" ]; do + RUN_JSON="$(gh api "repos/${REPO}/actions/workflows/ci.yml/runs?event=push&head_sha=${HEAD_SHA}&per_page=5" 2>/dev/null || echo '')" + RUN_ID="$(printf '%s' "$RUN_JSON" | jq -r '.workflow_runs[0].id // empty' 2>/dev/null || echo '')" + if [ -n "$RUN_ID" ]; then + # Jobs from the reusable test-build workflow are prefixed + # "Test and Build /". If that name ever changes, fall back to the + # overall run conclusion (stricter, still correct). + STATE="$(gh api "repos/${REPO}/actions/runs/${RUN_ID}/jobs?per_page=100" --jq ' + [.jobs[] | select(.name | startswith("Test and Build /"))] as $t | + if ($t | length) == 0 then "nojobs" + elif all($t[]; .conclusion == "success") then "success" + elif any($t[]; .conclusion != null and .conclusion != "success") then "failed" + else "pending" end' 2>/dev/null || echo pending)" + if [ "$STATE" = "nojobs" ]; then + RUN_STATE="$(printf '%s' "$RUN_JSON" | jq -r '.workflow_runs[0] | if .status == "completed" then .conclusion else "pending" end' 2>/dev/null || echo pending)" + if [ "$RUN_STATE" = "success" ]; then + STATE="success" + elif [ "$RUN_STATE" != "pending" ]; then + STATE="failed" + else + STATE="pending" + fi + fi + if [ "$STATE" = "success" ]; then + COVERED=true + echo "Push run ${RUN_ID} passed its test jobs for ${HEAD_SHA} — skipping duplicate test-build." + break + elif [ "$STATE" = "failed" ]; then + echo "Push run ${RUN_ID} did not pass (state: ${STATE}) — running tests in this PR run." + break + fi + fi + sleep 20 + done + + echo "covered=${COVERED}" >> "$GITHUB_OUTPUT" + test-build: name: Test and Build - if: github.ref != 'refs/heads/dev' || github.event_name == 'pull_request' + needs: [dedup-promotion] + # !cancelled(): dedup-promotion is skipped on every non-promotion event and + # a skipped need would otherwise skip this job too. covered != 'true' is + # fail-open — empty (skipped/failed probe) means run the tests. + if: >- + !cancelled() && + (github.ref != 'refs/heads/dev' || github.event_name == 'pull_request') && + needs.dedup-promotion.outputs.covered != 'true' uses: ./.github/workflows/test-build.yml secrets: inherit # Detect if this is a version release commit (e.g., "v0.5.24: ...") + # Smallest runner on purpose: a few seconds of pure shell over the commit + # message, no checkout and no install. detect-version: name: Detect Version - runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} + runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }} timeout-minutes: 5 if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging' || github.ref == 'refs/heads/dev') outputs: @@ -486,9 +599,11 @@ jobs: fi # Check if docs changed + # Smallest runner on purpose: a depth-2 checkout plus a path filter, no + # install and no build. check-docs-changes: name: Check Docs Changes - runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} + runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }} timeout-minutes: 5 if: github.event_name == 'push' && github.ref == 'refs/heads/main' outputs: diff --git a/.github/workflows/companion-pr-check.yml b/.github/workflows/companion-pr-check.yml index 2d149210660..0cf31db934f 100644 --- a/.github/workflows/companion-pr-check.yml +++ b/.github/workflows/companion-pr-check.yml @@ -22,6 +22,17 @@ on: branches: [staging, main] workflow_dispatch: {} +# One live run per PR: a newer opened/edited/synchronize event supersedes the +# previous run's work entirely (the sticky comment/label upsert is idempotent +# and only the latest body matters), so cancel in-flight runs instead of +# letting them race the new one. workflow_dispatch bulk scans get a unique +# group via run_id and are never cancelled. No paths filter on purpose: the +# check reads the PR body and cross-repo PR state, not changed files, so a +# paths filter would both be semantically wrong and stop status refreshes. +concurrency: + group: companion-pr-check-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + permissions: pull-requests: write issues: write From 5ad4e4176931f04fd2b98a7c7ca3afa5d2e931cd Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 22 Jul 2026 17:58:53 -0700 Subject: [PATCH 2/8] improvement(testing): complete stateful shared mocks for env, urls, redis-config, environment-utils Shared mock infrastructure for vitest isolate:false convergence: - packages/testing/src/mocks/env.mock.ts: stateful envMock (live env proxy, setEnv/resetEnvMock, process.env fallback) - packages/testing/src/mocks/urls.mock.ts: complete urlsMock with real-behavior default impls + resetUrlsMock - packages/testing/src/mocks/redis-config.mock.ts: adds getRedisConnectionDefaults + resetRedisConfigMock - packages/testing/src/mocks/environment-utils.mock.ts: new environmentUtilsMock + fns + reset - contract tests: env.mock.test.ts, urls.mock.test.ts, redis-config.mock.test.ts, environment-utils.mock.test.ts - packages/testing/src/mocks/index.ts: barrel exports - apps/sim/vitest.setup.ts: global installs for env, urls, redis, environment/utils - real-module tests unmocked: lib/core/config/env.test.ts, lib/core/config/redis.test.ts, lib/core/utils/urls.test.ts, tools/index.test.ts (urls) - stubEnv/process.env fallout migrated to setEnv: lib/webhooks/providers/{revenuecat,rootly,instantly}.test.ts, app/api/auth/oauth2/authorize/route.test.ts --- .../api/auth/oauth2/authorize/route.test.ts | 17 +- apps/sim/lib/core/config/env.test.ts | 4 +- apps/sim/lib/core/config/redis.test.ts | 1 + apps/sim/lib/core/utils/urls.test.ts | 1 + .../lib/webhooks/providers/instantly.test.ts | 5 +- .../lib/webhooks/providers/revenuecat.test.ts | 5 +- .../sim/lib/webhooks/providers/rootly.test.ts | 5 +- apps/sim/tools/index.test.ts | 3 + apps/sim/vitest.setup.ts | 8 + packages/testing/src/mocks/env.mock.test.ts | 65 ++++++ packages/testing/src/mocks/env.mock.ts | 184 +++++++++++++---- .../src/mocks/environment-utils.mock.test.ts | 44 ++++ .../src/mocks/environment-utils.mock.ts | 79 ++++++++ packages/testing/src/mocks/index.ts | 26 ++- .../src/mocks/redis-config.mock.test.ts | 36 ++++ .../testing/src/mocks/redis-config.mock.ts | 40 +++- packages/testing/src/mocks/urls.mock.test.ts | 66 ++++++ packages/testing/src/mocks/urls.mock.ts | 188 +++++++++++++++++- 18 files changed, 714 insertions(+), 63 deletions(-) create mode 100644 packages/testing/src/mocks/env.mock.test.ts create mode 100644 packages/testing/src/mocks/environment-utils.mock.test.ts create mode 100644 packages/testing/src/mocks/environment-utils.mock.ts create mode 100644 packages/testing/src/mocks/redis-config.mock.test.ts create mode 100644 packages/testing/src/mocks/urls.mock.test.ts diff --git a/apps/sim/app/api/auth/oauth2/authorize/route.test.ts b/apps/sim/app/api/auth/oauth2/authorize/route.test.ts index 894e543c3b2..96b88ccc293 100644 --- a/apps/sim/app/api/auth/oauth2/authorize/route.test.ts +++ b/apps/sim/app/api/auth/oauth2/authorize/route.test.ts @@ -1,8 +1,15 @@ /** * @vitest-environment node */ -import { createMockRequest, dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { + createMockRequest, + dbChainMock, + dbChainMockFns, + resetDbChainMock, + resetEnvMock, + setEnv, +} from '@sim/testing' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockGetSession, @@ -70,10 +77,14 @@ function oauthCredentialActor(overrides: Record = {}) { } describe('OAuth2 authorize route', () => { + afterAll(() => { + resetEnvMock() + }) + beforeEach(() => { vi.clearAllMocks() resetDbChainMock() - process.env.NEXT_PUBLIC_APP_URL = BASE_URL + setEnv({ NEXT_PUBLIC_APP_URL: BASE_URL }) mockGetSession.mockResolvedValue({ user: { id: USER_ID } }) mockCheckWorkspaceAccess.mockResolvedValue({ hasAccess: true, diff --git a/apps/sim/lib/core/config/env.test.ts b/apps/sim/lib/core/config/env.test.ts index 905b4b0b207..ea792ba42d8 100644 --- a/apps/sim/lib/core/config/env.test.ts +++ b/apps/sim/lib/core/config/env.test.ts @@ -1,9 +1,11 @@ /** * @vitest-environment node */ -import { describe, expect, it } from 'vitest' +import { describe, expect, it, vi } from 'vitest' import { envNumber } from '@/lib/core/config/env' +vi.unmock('@/lib/core/config/env') + describe('envNumber', () => { it('can require integer env values for count-like settings', () => { expect(envNumber('5', 1, { min: 1, integer: true })).toBe(5) diff --git a/apps/sim/lib/core/config/redis.test.ts b/apps/sim/lib/core/config/redis.test.ts index 7cd527d8ad0..82a834fc93a 100644 --- a/apps/sim/lib/core/config/redis.test.ts +++ b/apps/sim/lib/core/config/redis.test.ts @@ -14,6 +14,7 @@ MockRedisConstructor.mockImplementation( } ) +vi.unmock('@/lib/core/config/redis') vi.mock('@/lib/core/config/env', () => createEnvMock({ REDIS_URL: 'redis://localhost:6379' })) vi.mock('ioredis', () => ({ default: MockRedisConstructor, diff --git a/apps/sim/lib/core/utils/urls.test.ts b/apps/sim/lib/core/utils/urls.test.ts index 55cc21e71a5..b282a47f832 100644 --- a/apps/sim/lib/core/utils/urls.test.ts +++ b/apps/sim/lib/core/utils/urls.test.ts @@ -7,6 +7,7 @@ const { mockGetEnv } = vi.hoisted(() => ({ mockGetEnv: vi.fn<(key: string) => string | undefined>(), })) +vi.unmock('@/lib/core/utils/urls') vi.mock('@/lib/core/config/env', () => ({ env: {}, getEnv: mockGetEnv, diff --git a/apps/sim/lib/webhooks/providers/instantly.test.ts b/apps/sim/lib/webhooks/providers/instantly.test.ts index 9e3584cde9a..72490858e11 100644 --- a/apps/sim/lib/webhooks/providers/instantly.test.ts +++ b/apps/sim/lib/webhooks/providers/instantly.test.ts @@ -1,3 +1,4 @@ +import { resetEnvMock, setEnv } from '@sim/testing' import { NextRequest } from 'next/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { instantlyHandler } from '@/lib/webhooks/providers/instantly' @@ -242,14 +243,14 @@ describe('Instantly webhook provider', () => { const fetchMock = vi.fn() beforeEach(() => { - vi.stubEnv('NEXT_PUBLIC_APP_URL', 'https://app.test') + setEnv({ NEXT_PUBLIC_APP_URL: 'https://app.test' }) vi.stubGlobal('fetch', fetchMock) fetchMock.mockReset() }) afterEach(() => { vi.unstubAllGlobals() - vi.unstubAllEnvs() + resetEnvMock() }) it('creates an Instantly webhook with the mapped event type', async () => { diff --git a/apps/sim/lib/webhooks/providers/revenuecat.test.ts b/apps/sim/lib/webhooks/providers/revenuecat.test.ts index b34cdfe6d33..0d832733111 100644 --- a/apps/sim/lib/webhooks/providers/revenuecat.test.ts +++ b/apps/sim/lib/webhooks/providers/revenuecat.test.ts @@ -1,3 +1,4 @@ +import { resetEnvMock, setEnv } from '@sim/testing' import { NextRequest } from 'next/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { revenueCatHandler } from '@/lib/webhooks/providers/revenuecat' @@ -164,12 +165,12 @@ describe('RevenueCat webhook provider', () => { beforeEach(() => { vi.restoreAllMocks() - vi.stubEnv('NEXT_PUBLIC_APP_URL', 'https://sim.example.com') + setEnv({ NEXT_PUBLIC_APP_URL: 'https://sim.example.com' }) }) afterEach(() => { vi.restoreAllMocks() - vi.unstubAllEnvs() + resetEnvMock() }) it('creates the integration and returns externalId + generated authHeaderSecret', async () => { diff --git a/apps/sim/lib/webhooks/providers/rootly.test.ts b/apps/sim/lib/webhooks/providers/rootly.test.ts index 59a8e3a90f6..47f8114e262 100644 --- a/apps/sim/lib/webhooks/providers/rootly.test.ts +++ b/apps/sim/lib/webhooks/providers/rootly.test.ts @@ -1,4 +1,5 @@ import crypto from 'node:crypto' +import { resetEnvMock, setEnv } from '@sim/testing' import { NextRequest } from 'next/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { rootlyHandler } from '@/lib/webhooks/providers/rootly' @@ -168,14 +169,14 @@ describe('Rootly webhook provider', () => { const fetchMock = vi.fn() beforeEach(() => { - vi.stubEnv('NEXT_PUBLIC_APP_URL', 'https://app.test') + setEnv({ NEXT_PUBLIC_APP_URL: 'https://app.test' }) vi.stubGlobal('fetch', fetchMock) fetchMock.mockReset() }) afterEach(() => { vi.unstubAllGlobals() - vi.unstubAllEnvs() + resetEnvMock() }) it('creates a Rootly endpoint with a generated secret and the mapped event type', async () => { diff --git a/apps/sim/tools/index.test.ts b/apps/sim/tools/index.test.ts index 929eed52aff..a63205816fe 100644 --- a/apps/sim/tools/index.test.ts +++ b/apps/sim/tools/index.test.ts @@ -53,6 +53,9 @@ const { const mockSecureFetchWithPinnedIP = inputValidationMockFns.mockSecureFetchWithPinnedIP const mockValidateUrlWithDNS = inputValidationMockFns.mockValidateUrlWithDNS +// Use the real urls module so it reads the file-local env mock below +vi.unmock('@/lib/core/utils/urls') + // Mock env config to control hosted key availability vi.mock('@/lib/core/config/env', () => ({ env: new Proxy({} as Record, { diff --git a/apps/sim/vitest.setup.ts b/apps/sim/vitest.setup.ts index 4abf8e34bb2..fe9c7f1439d 100644 --- a/apps/sim/vitest.setup.ts +++ b/apps/sim/vitest.setup.ts @@ -3,13 +3,17 @@ import { databaseMock, drizzleOrmMock, envFlagsMock, + environmentUtilsMock, + envMock, hybridAuthMock, loggerMock, + redisConfigMock, requestUtilsMock, schemaMock, setupGlobalFetchMock, setupGlobalStorageMocks, terminalConsoleMock, + urlsMock, workflowAuthzMock, } from '@sim/testing' import { afterAll, vi } from 'vitest' @@ -27,6 +31,10 @@ vi.mock('@/lib/auth', () => authMock) vi.mock('@/lib/auth/hybrid', () => hybridAuthMock) vi.mock('@/lib/core/utils/request', () => requestUtilsMock) vi.mock('@/lib/core/config/env-flags', () => envFlagsMock) +vi.mock('@/lib/core/config/env', () => envMock) +vi.mock('@/lib/core/utils/urls', () => urlsMock) +vi.mock('@/lib/core/config/redis', () => redisConfigMock) +vi.mock('@/lib/environment/utils', () => environmentUtilsMock) vi.mock('@/stores/console/store', () => ({ useConsoleStore: { diff --git a/packages/testing/src/mocks/env.mock.test.ts b/packages/testing/src/mocks/env.mock.test.ts new file mode 100644 index 00000000000..0270c23220e --- /dev/null +++ b/packages/testing/src/mocks/env.mock.test.ts @@ -0,0 +1,65 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { + defaultMockEnv, + envMock, + envMockFns, + mockEnvObject, + resetEnvMock, + setEnv, +} from './env.mock' + +describe('env mock', () => { + afterEach(() => { + resetEnvMock() + vi.unstubAllEnvs() + }) + + it('exposes the default state through env and getEnv', () => { + expect(envMock.env.NEXT_PUBLIC_APP_URL).toBe(defaultMockEnv.NEXT_PUBLIC_APP_URL) + expect(envMock.getEnv('DATABASE_URL')).toBe(defaultMockEnv.DATABASE_URL) + }) + + it('applies setEnv overrides to live reads', () => { + setEnv({ REDIS_URL: 'redis://localhost:6379' }) + expect(envMock.env.REDIS_URL).toBe('redis://localhost:6379') + expect(envMock.getEnv('REDIS_URL')).toBe('redis://localhost:6379') + }) + + it('supports direct property assignment on the env object', () => { + mockEnvObject.COPILOT_SOURCE_ENV = 'dev' + expect(envMock.env.COPILOT_SOURCE_ENV).toBe('dev') + }) + + it('falls back to process.env for keys not pinned in state', () => { + vi.stubEnv('SOME_UNPINNED_TEST_VAR', 'from-process-env') + expect(envMock.env.SOME_UNPINNED_TEST_VAR).toBe('from-process-env') + expect(envMock.getEnv('SOME_UNPINNED_TEST_VAR')).toBe('from-process-env') + }) + + it('pins explicitly-undefined overrides without process.env fallback', () => { + vi.stubEnv('NEXT_PUBLIC_APP_URL', 'https://shadowed.example.com') + setEnv({ NEXT_PUBLIC_APP_URL: undefined }) + expect(envMock.env.NEXT_PUBLIC_APP_URL).toBeUndefined() + expect(envMock.getEnv('NEXT_PUBLIC_APP_URL')).toBeUndefined() + }) + + it('resetEnvMock restores defaults and removes overrides', () => { + setEnv({ REDIS_URL: 'redis://localhost:6379', NEXT_PUBLIC_APP_URL: 'https://other.test' }) + envMockFns.getEnv.mockReturnValue('overridden') + resetEnvMock() + expect(envMock.env.REDIS_URL).toBeUndefined() + expect(envMock.env.NEXT_PUBLIC_APP_URL).toBe(defaultMockEnv.NEXT_PUBLIC_APP_URL) + expect(envMock.getEnv('NEXT_PUBLIC_APP_URL')).toBe(defaultMockEnv.NEXT_PUBLIC_APP_URL) + }) + + it('coercion helpers mirror the real module', () => { + expect(envMock.isTruthy('true')).toBe(true) + expect(envMock.isTruthy('0')).toBe(false) + expect(envMock.isFalsy('false')).toBe(true) + expect(envMock.isFalsy(undefined)).toBe(false) + expect(envMock.envBoolean('yes')).toBe(true) + expect(envMock.envBoolean('')).toBeUndefined() + expect(envMock.envNumber('5', 1, { min: 1, integer: true })).toBe(5) + expect(envMock.envNumber('5.5', 1, { min: 1, integer: true })).toBe(1) + }) +}) diff --git a/packages/testing/src/mocks/env.mock.ts b/packages/testing/src/mocks/env.mock.ts index e6bbd09f7b4..26368b020f1 100644 --- a/packages/testing/src/mocks/env.mock.ts +++ b/packages/testing/src/mocks/env.mock.ts @@ -1,7 +1,15 @@ import { vi } from 'vitest' /** - * Default mock environment values for testing + * Value type for entries in the mocked env object. The real module runs + * `createEnv` with `skipValidation: true`, so values arrive as raw strings + * (or occasionally booleans/numbers when injected programmatically). + */ +export type EnvMockValue = string | boolean | number | undefined + +/** + * Default mock environment values for testing. These seed the shared stateful + * env mock and are restored by {@link resetEnvMock}. */ export const defaultMockEnv = { // Core @@ -22,23 +30,137 @@ export const defaultMockEnv = { } /** - * Creates a mock getEnv function that returns values from the provided env object + * Mutable state backing the shared env mock. Keys present here (even with an + * `undefined` value) shadow `process.env`; absent keys fall back to + * `process.env` so `vi.stubEnv`-driven tests keep working for variables the + * defaults do not pin. + */ +const envState: Record = { ...defaultMockEnv } + +function readEnvValue(key: string): EnvMockValue { + if (Object.hasOwn(envState, key)) return envState[key] + return process.env[key] +} + +/** + * Live env object for the shared `@/lib/core/config/env` mock. Property reads + * resolve against the mutable mock state first and `process.env` second; + * property writes land in the mock state (mirroring how tests mutate the real + * t3-env object under `skipValidation`). + */ +export const mockEnvObject: Record = new Proxy(envState, { + get: (_target, prop) => (typeof prop === 'string' ? readEnvValue(prop) : undefined), + set: (target, prop, value) => { + if (typeof prop === 'string') target[prop] = value as EnvMockValue + return true + }, + has: (target, prop) => + typeof prop === 'string' ? Object.hasOwn(target, prop) || prop in process.env : false, + deleteProperty: (target, prop) => { + if (typeof prop === 'string') delete target[prop] + return true + }, + ownKeys: (target) => Array.from(new Set([...Object.keys(target), ...Object.keys(process.env)])), + getOwnPropertyDescriptor: (_target, prop) => + typeof prop === 'string' + ? { enumerable: true, configurable: true, value: readEnvValue(prop) } + : undefined, +}) + +/** + * Applies per-test overrides to the shared env mock state. Passing an + * explicitly `undefined` value pins the variable as unset (it will NOT fall + * back to `process.env`). + * + * @example + * ```ts + * beforeEach(() => { + * setEnv({ REDIS_URL: 'redis://localhost:6379', NEXT_PUBLIC_APP_URL: undefined }) + * }) + * afterAll(resetEnvMock) + * ``` + */ +export function setEnv(overrides: Record): void { + Object.assign(envState, overrides) +} + +/** + * Restores the shared env mock to {@link defaultMockEnv} and reinstalls the + * default `getEnv` implementation. + */ +export function resetEnvMock(): void { + for (const key of Object.keys(envState)) delete envState[key] + Object.assign(envState, defaultMockEnv) + envMockFns.getEnv.mockReset().mockImplementation(getEnvDefaultImpl) +} + +function getEnvDefaultImpl(variable: string): string | undefined { + const value = readEnvValue(variable) + return value === undefined ? undefined : String(value) +} + +/** Mirrors the real `isTruthy` from `@/lib/core/config/env`. */ +export const isTruthyImpl = (value: string | boolean | number | undefined): boolean => + typeof value === 'string' ? value.toLowerCase() === 'true' || value === '1' : Boolean(value) + +/** Mirrors the real `isFalsy` from `@/lib/core/config/env`. */ +export const isFalsyImpl = (value: string | boolean | number | undefined): boolean => + typeof value === 'string' ? value.toLowerCase() === 'false' || value === '0' : value === false + +/** Mirrors the real `envBoolean` from `@/lib/core/config/env`. */ +export function envBooleanImpl(value: boolean | string | undefined | null): boolean | undefined { + if (typeof value === 'boolean') return value + if (value === undefined || value === null || value === '') return undefined + const normalized = String(value).trim().toLowerCase() + return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on' +} + +/** Mirrors the real `envNumber` from `@/lib/core/config/env`. */ +export function envNumberImpl( + value: number | string | undefined | null, + fallback: number, + options: { min?: number; integer?: boolean } = {} +): number { + const min = options.min ?? 0 + if ( + typeof value === 'number' && + Number.isFinite(value) && + value >= min && + (!options.integer || Number.isInteger(value)) + ) { + return value + } + if (value === undefined || value === null || value === '') return fallback + const parsed = Number(value) + return Number.isFinite(parsed) && parsed >= min && (!options.integer || Number.isInteger(parsed)) + ? parsed + : fallback +} + +/** + * Controllable mock functions for the function exports of + * `@/lib/core/config/env`. `getEnv` defaults to reading the shared state (with + * `process.env` fallback); override per-test if needed. {@link resetEnvMock} + * restores the default implementation. + */ +export const envMockFns = { + getEnv: vi.fn<(variable: string) => string | undefined>(getEnvDefaultImpl), +} + +/** + * Creates a mock getEnv function that returns values from the provided env object. */ export function createMockGetEnv(envValues: Record = defaultMockEnv) { return vi.fn((key: string) => envValues[key]) } /** - * Creates a complete env mock object for use with vi.doMock + * Creates a standalone (non-shared) env mock module, for file-local factories + * that need a fully isolated env rather than the shared stateful mock. * * @example * ```ts - * vi.doMock('@/lib/core/config/env', () => createEnvMock()) - * - * // With custom values - * vi.doMock('@/lib/core/config/env', () => createEnvMock({ - * NEXT_PUBLIC_APP_URL: 'https://custom.example.com', - * })) + * vi.mock('@/lib/core/config/env', () => createEnvMock({ REDIS_URL: 'redis://localhost:6379' })) * ``` */ export function createEnvMock(overrides: Record = {}) { @@ -47,40 +169,30 @@ export function createEnvMock(overrides: Record = {} return { env: envValues, getEnv: createMockGetEnv(envValues), - isTruthy: (value: string | boolean | number | undefined) => - typeof value === 'string' ? value.toLowerCase() === 'true' || value === '1' : Boolean(value), - isFalsy: (value: string | boolean | number | undefined) => - typeof value === 'string' - ? value.toLowerCase() === 'false' || value === '0' - : value === false, - envBoolean: (value: boolean | string | undefined | null): boolean | undefined => { - if (typeof value === 'boolean') return value - if (value === undefined || value === null || value === '') return undefined - const normalized = String(value).trim().toLowerCase() - return ( - normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on' - ) - }, - envNumber: ( - value: number | string | undefined | null, - fallback: number, - options: { min?: number } = {} - ): number => { - const min = options.min ?? 0 - if (typeof value === 'number' && Number.isFinite(value) && value >= min) return value - if (value === undefined || value === null || value === '') return fallback - const parsed = Number(value) - return Number.isFinite(parsed) && parsed >= min ? parsed : fallback - }, + isTruthy: isTruthyImpl, + isFalsy: isFalsyImpl, + envBoolean: envBooleanImpl, + envNumber: envNumberImpl, } } /** - * Pre-configured env mock for direct use with vi.mock + * Complete, stateful mock module for `@/lib/core/config/env`, installed + * globally in `apps/sim/vitest.setup.ts`. Every export of the real module is + * present. Reads through `env` and `getEnv` are live: override via + * {@link setEnv} (or direct property assignment on `envMock.env`) and restore + * with {@link resetEnvMock}. * * @example * ```ts * vi.mock('@/lib/core/config/env', () => envMock) * ``` */ -export const envMock = createEnvMock() +export const envMock = { + env: mockEnvObject, + getEnv: envMockFns.getEnv, + isTruthy: isTruthyImpl, + isFalsy: isFalsyImpl, + envBoolean: envBooleanImpl, + envNumber: envNumberImpl, +} diff --git a/packages/testing/src/mocks/environment-utils.mock.test.ts b/packages/testing/src/mocks/environment-utils.mock.test.ts new file mode 100644 index 00000000000..389a89fa3f6 --- /dev/null +++ b/packages/testing/src/mocks/environment-utils.mock.test.ts @@ -0,0 +1,44 @@ +import { afterEach, describe, expect, it } from 'vitest' +import { + environmentUtilsMock, + environmentUtilsMockFns, + resetEnvironmentUtilsMock, +} from './environment-utils.mock' + +describe('environment-utils mock', () => { + afterEach(() => { + resetEnvironmentUtilsMock() + }) + + it('defaults model a user with no environment variables', async () => { + await expect(environmentUtilsMock.getEnvironmentVariableKeys('user-1')).resolves.toEqual({ + variableNames: [], + count: 0, + }) + await expect(environmentUtilsMock.getEffectiveDecryptedEnv('user-1')).resolves.toEqual({}) + await expect(environmentUtilsMock.getPersonalAndWorkspaceEnv('user-1')).resolves.toEqual({ + personalEncrypted: {}, + workspaceEncrypted: {}, + personalDecrypted: {}, + workspaceDecrypted: {}, + conflicts: [], + decryptionFailures: [], + }) + await expect(environmentUtilsMock.upsertPersonalEnvVars('user-1', {})).resolves.toEqual({ + added: [], + updated: [], + }) + await expect( + environmentUtilsMock.upsertWorkspaceEnvVars('ws-1', {}, 'user-1') + ).resolves.toEqual([]) + }) + + it('resetEnvironmentUtilsMock restores defaults after overrides', async () => { + environmentUtilsMockFns.mockGetEffectiveDecryptedEnv.mockResolvedValue({ API_KEY: 'k' }) + await expect(environmentUtilsMock.getEffectiveDecryptedEnv('user-1')).resolves.toEqual({ + API_KEY: 'k', + }) + resetEnvironmentUtilsMock() + await expect(environmentUtilsMock.getEffectiveDecryptedEnv('user-1')).resolves.toEqual({}) + }) +}) diff --git a/packages/testing/src/mocks/environment-utils.mock.ts b/packages/testing/src/mocks/environment-utils.mock.ts new file mode 100644 index 00000000000..40c07de5391 --- /dev/null +++ b/packages/testing/src/mocks/environment-utils.mock.ts @@ -0,0 +1,79 @@ +import { vi } from 'vitest' + +function emptyPersonalAndWorkspaceEnv(): { + personalEncrypted: Record + workspaceEncrypted: Record + personalDecrypted: Record + workspaceDecrypted: Record + conflicts: string[] + decryptionFailures: string[] +} { + return { + personalEncrypted: {}, + workspaceEncrypted: {}, + personalDecrypted: {}, + workspaceDecrypted: {}, + conflicts: [], + decryptionFailures: [], + } +} + +/** + * Controllable mock functions for `@/lib/environment/utils`. Defaults model a + * user/workspace with no environment variables. Override per-test and restore + * with {@link resetEnvironmentUtilsMock}. + * + * @example + * ```ts + * import { environmentUtilsMockFns } from '@sim/testing' + * + * environmentUtilsMockFns.mockGetEffectiveDecryptedEnv.mockResolvedValue({ API_KEY: 'k' }) + * ``` + */ +export const environmentUtilsMockFns = { + mockInvalidateEffectiveDecryptedEnvCache: vi.fn(), + mockGetEnvironmentVariableKeys: vi.fn().mockResolvedValue({ variableNames: [], count: 0 }), + mockGetPersonalAndWorkspaceEnv: vi + .fn() + .mockImplementation(async () => emptyPersonalAndWorkspaceEnv()), + mockUpsertPersonalEnvVars: vi.fn().mockResolvedValue({ added: [], updated: [] }), + mockUpsertWorkspaceEnvVars: vi.fn().mockResolvedValue([]), + mockGetEffectiveDecryptedEnv: vi.fn().mockResolvedValue({}), +} + +/** + * Restores every environment-utils mock function to its default behavior. + */ +export function resetEnvironmentUtilsMock(): void { + environmentUtilsMockFns.mockInvalidateEffectiveDecryptedEnvCache.mockReset() + environmentUtilsMockFns.mockGetEnvironmentVariableKeys + .mockReset() + .mockResolvedValue({ variableNames: [], count: 0 }) + environmentUtilsMockFns.mockGetPersonalAndWorkspaceEnv + .mockReset() + .mockImplementation(async () => emptyPersonalAndWorkspaceEnv()) + environmentUtilsMockFns.mockUpsertPersonalEnvVars + .mockReset() + .mockResolvedValue({ added: [], updated: [] }) + environmentUtilsMockFns.mockUpsertWorkspaceEnvVars.mockReset().mockResolvedValue([]) + environmentUtilsMockFns.mockGetEffectiveDecryptedEnv.mockReset().mockResolvedValue({}) +} + +/** + * Complete mock module for `@/lib/environment/utils`, installed globally in + * `apps/sim/vitest.setup.ts`. Every export of the real module is present. + * + * @example + * ```ts + * vi.mock('@/lib/environment/utils', () => environmentUtilsMock) + * ``` + */ +export const environmentUtilsMock = { + invalidateEffectiveDecryptedEnvCache: + environmentUtilsMockFns.mockInvalidateEffectiveDecryptedEnvCache, + getEnvironmentVariableKeys: environmentUtilsMockFns.mockGetEnvironmentVariableKeys, + getPersonalAndWorkspaceEnv: environmentUtilsMockFns.mockGetPersonalAndWorkspaceEnv, + upsertPersonalEnvVars: environmentUtilsMockFns.mockUpsertPersonalEnvVars, + upsertWorkspaceEnvVars: environmentUtilsMockFns.mockUpsertWorkspaceEnvVars, + getEffectiveDecryptedEnv: environmentUtilsMockFns.mockGetEffectiveDecryptedEnv, +} diff --git a/packages/testing/src/mocks/index.ts b/packages/testing/src/mocks/index.ts index e3f6c2d6f62..91705b03f54 100644 --- a/packages/testing/src/mocks/index.ts +++ b/packages/testing/src/mocks/index.ts @@ -51,7 +51,17 @@ export { // Encryption mocks export { encryptionMock, encryptionMockFns } from './encryption.mock' // Env mocks -export { createEnvMock, createMockGetEnv, defaultMockEnv, envMock } from './env.mock' +export { + createEnvMock, + createMockGetEnv, + defaultMockEnv, + type EnvMockValue, + envMock, + envMockFns, + mockEnvObject, + resetEnvMock, + setEnv, +} from './env.mock' // Env flag mocks export { type EnvFlagsMockState, @@ -60,6 +70,12 @@ export { resetEnvFlagsMock, setEnvFlags, } from './env-flags.mock' +// Environment utils mocks (for @/lib/environment/utils) +export { + environmentUtilsMock, + environmentUtilsMockFns, + resetEnvironmentUtilsMock, +} from './environment-utils.mock' // Execution preprocessing mocks (for @/lib/execution/preprocessing) export { executionPreprocessingMock, @@ -104,7 +120,11 @@ export { posthogServerMock, posthogServerMockFns } from './posthog-server.mock' // Redis client mocks (for Redis client objects) export { clearRedisMocks, createMockRedis, type MockRedis } from './redis.mock' // Redis config mocks (for @/lib/core/config/redis) -export { redisConfigMock, redisConfigMockFns } from './redis-config.mock' +export { + redisConfigMock, + redisConfigMockFns, + resetRedisConfigMock, +} from './redis-config.mock' // Request mocks export { createMockFormDataRequest, @@ -142,7 +162,7 @@ export { terminalConsoleMockFns, } from './terminal-console.mock' // URL mocks -export { urlsMock, urlsMockFns } from './urls.mock' +export { LOCALHOST_HOSTNAMES_MOCK, resetUrlsMock, urlsMock, urlsMockFns } from './urls.mock' // Workflow authz package mocks (for @sim/platform-authz/workflow) export { workflowAuthzMock, workflowAuthzMockFns } from './workflow-authz.mock' // Workflows API utils mocks (for @/app/api/workflows/utils) diff --git a/packages/testing/src/mocks/redis-config.mock.test.ts b/packages/testing/src/mocks/redis-config.mock.test.ts new file mode 100644 index 00000000000..65f86acf315 --- /dev/null +++ b/packages/testing/src/mocks/redis-config.mock.test.ts @@ -0,0 +1,36 @@ +import { afterEach, describe, expect, it } from 'vitest' +import { redisConfigMock, redisConfigMockFns, resetRedisConfigMock } from './redis-config.mock' + +describe('redis-config mock', () => { + afterEach(() => { + resetRedisConfigMock() + }) + + it('defaults to the Redis-unavailable behavior of the real module', async () => { + expect(redisConfigMock.getRedisClient()).toBeNull() + await expect(redisConfigMock.acquireLock('k', 'v', 10)).resolves.toBe(true) + await expect(redisConfigMock.releaseLock('k', 'v')).resolves.toBe(true) + await expect(redisConfigMock.extendLock('k', 'v', 10)).resolves.toBe(true) + await expect(redisConfigMock.closeRedisConnection()).resolves.toBeUndefined() + }) + + it('returns the real connection defaults shape', () => { + expect(redisConfigMock.getRedisConnectionDefaults('redis://localhost:6379')).toEqual({ + keepAlive: 1000, + connectTimeout: 10000, + enableOfflineQueue: true, + }) + }) + + it('resetRedisConfigMock restores defaults after overrides', async () => { + const fakeClient = { ping: () => 'PONG' } + redisConfigMockFns.mockGetRedisClient.mockReturnValue(fakeClient) + redisConfigMockFns.mockAcquireLock.mockResolvedValue(false) + expect(redisConfigMock.getRedisClient()).toBe(fakeClient) + await expect(redisConfigMock.acquireLock('k', 'v', 10)).resolves.toBe(false) + + resetRedisConfigMock() + expect(redisConfigMock.getRedisClient()).toBeNull() + await expect(redisConfigMock.acquireLock('k', 'v', 10)).resolves.toBe(true) + }) +}) diff --git a/packages/testing/src/mocks/redis-config.mock.ts b/packages/testing/src/mocks/redis-config.mock.ts index 7d90711a865..a9098b4c653 100644 --- a/packages/testing/src/mocks/redis-config.mock.ts +++ b/packages/testing/src/mocks/redis-config.mock.ts @@ -1,9 +1,24 @@ import { vi } from 'vitest' +function getRedisConnectionDefaultsImpl(_url?: string): { + keepAlive: number + connectTimeout: number + enableOfflineQueue: boolean +} { + return { + keepAlive: 1000, + connectTimeout: 10000, + enableOfflineQueue: true, + } +} + /** * Controllable mock functions for `@/lib/core/config/redis`. - * Default: `getRedisClient` returns `null` (tests that need a client override it). - * `acquireLock` defaults to succeeding (`true`); `releaseLock` defaults to `true`. + * Default: `getRedisClient` returns `null` (tests that need a client override + * it), matching the real module's behavior when `REDIS_URL` is unset. + * `acquireLock`/`releaseLock`/`extendLock` default to succeeding (`true`), + * matching the real module's Redis-unavailable no-op path. + * {@link resetRedisConfigMock} restores the default behaviors. * * @example * ```ts @@ -14,6 +29,7 @@ import { vi } from 'vitest' */ export const redisConfigMockFns = { mockGetRedisClient: vi.fn().mockReturnValue(null), + mockGetRedisConnectionDefaults: vi.fn(getRedisConnectionDefaultsImpl), mockOnRedisReconnect: vi.fn(), mockAcquireLock: vi.fn().mockResolvedValue(true), mockReleaseLock: vi.fn().mockResolvedValue(true), @@ -23,7 +39,24 @@ export const redisConfigMockFns = { } /** - * Static mock module for `@/lib/core/config/redis`. + * Restores every redis-config mock function to its default behavior. + */ +export function resetRedisConfigMock(): void { + redisConfigMockFns.mockGetRedisClient.mockReset().mockReturnValue(null) + redisConfigMockFns.mockGetRedisConnectionDefaults + .mockReset() + .mockImplementation(getRedisConnectionDefaultsImpl) + redisConfigMockFns.mockOnRedisReconnect.mockReset() + redisConfigMockFns.mockAcquireLock.mockReset().mockResolvedValue(true) + redisConfigMockFns.mockReleaseLock.mockReset().mockResolvedValue(true) + redisConfigMockFns.mockExtendLock.mockReset().mockResolvedValue(true) + redisConfigMockFns.mockCloseRedisConnection.mockReset().mockResolvedValue(undefined) + redisConfigMockFns.mockResetForTesting.mockReset() +} + +/** + * Complete mock module for `@/lib/core/config/redis`, installed globally in + * `apps/sim/vitest.setup.ts`. Every export of the real module is present. * * @example * ```ts @@ -32,6 +65,7 @@ export const redisConfigMockFns = { */ export const redisConfigMock = { getRedisClient: redisConfigMockFns.mockGetRedisClient, + getRedisConnectionDefaults: redisConfigMockFns.mockGetRedisConnectionDefaults, onRedisReconnect: redisConfigMockFns.mockOnRedisReconnect, acquireLock: redisConfigMockFns.mockAcquireLock, releaseLock: redisConfigMockFns.mockReleaseLock, diff --git a/packages/testing/src/mocks/urls.mock.test.ts b/packages/testing/src/mocks/urls.mock.test.ts new file mode 100644 index 00000000000..d1e0501eae0 --- /dev/null +++ b/packages/testing/src/mocks/urls.mock.test.ts @@ -0,0 +1,66 @@ +import { afterEach, describe, expect, it } from 'vitest' +import { defaultMockEnv, resetEnvMock, setEnv } from './env.mock' +import { resetUrlsMock, urlsMock, urlsMockFns } from './urls.mock' + +describe('urls mock', () => { + afterEach(() => { + resetUrlsMock() + resetEnvMock() + }) + + it('derives getBaseUrl from the shared env mock state', () => { + expect(urlsMock.getBaseUrl()).toBe(defaultMockEnv.NEXT_PUBLIC_APP_URL) + setEnv({ NEXT_PUBLIC_APP_URL: 'https://custom.example.com' }) + expect(urlsMock.getBaseUrl()).toBe('https://custom.example.com') + }) + + it('throws from getBaseUrl when NEXT_PUBLIC_APP_URL is pinned unset', () => { + setEnv({ NEXT_PUBLIC_APP_URL: undefined }) + expect(() => urlsMock.getBaseUrl()).toThrow('NEXT_PUBLIC_APP_URL must be configured') + }) + + it('getInternalApiBaseUrl prefers INTERNAL_API_BASE_URL and falls back to base URL', () => { + expect(urlsMock.getInternalApiBaseUrl()).toBe(defaultMockEnv.NEXT_PUBLIC_APP_URL) + setEnv({ INTERNAL_API_BASE_URL: 'http://sim-app.default.svc.cluster.local:3000' }) + expect(urlsMock.getInternalApiBaseUrl()).toBe('http://sim-app.default.svc.cluster.local:3000') + }) + + it('ensureAbsoluteUrl prefixes relative paths with the base URL', () => { + expect(urlsMock.ensureAbsoluteUrl('/api/files/serve/x')).toBe( + `${defaultMockEnv.NEXT_PUBLIC_APP_URL}/api/files/serve/x` + ) + expect(urlsMock.ensureAbsoluteUrl('https://a.b/c')).toBe('https://a.b/c') + }) + + it('domain helpers derive from the base URL', () => { + setEnv({ NEXT_PUBLIC_APP_URL: 'https://www.sim.ai' }) + expect(urlsMock.getBaseDomain()).toBe('www.sim.ai') + expect(urlsMock.getEmailDomain()).toBe('sim.ai') + }) + + it('pure helpers behave like the real module', () => { + expect(urlsMock.isLoopbackHostname('localhost')).toBe(true) + expect(urlsMock.isLoopbackHostname('sim.ai')).toBe(false) + expect(urlsMock.isLocalhostUrl('http://127.0.0.1:3000')).toBe(true) + expect(urlsMock.isSafeHttpUrl('javascript:alert(1)')).toBe(false) + expect(urlsMock.isSafeHttpUrl('https://sim.ai')).toBe(true) + expect( + urlsMock.parseOriginList('https://a.example.com/path, https://a.example.com, bad-url') + ).toEqual(['https://a.example.com']) + }) + + it('socket and ollama URLs read env with localhost fallbacks', () => { + expect(urlsMock.getSocketServerUrl()).toBe('http://localhost:3002') + expect(urlsMock.getOllamaUrl()).toBe('http://localhost:11434') + setEnv({ SOCKET_SERVER_URL: 'http://sockets:3002', OLLAMA_URL: 'http://ollama:11434' }) + expect(urlsMock.getSocketServerUrl()).toBe('http://sockets:3002') + expect(urlsMock.getOllamaUrl()).toBe('http://ollama:11434') + }) + + it('resetUrlsMock restores default implementations after overrides', () => { + urlsMockFns.mockGetBaseUrl.mockReturnValue('https://overridden.test') + expect(urlsMock.getBaseUrl()).toBe('https://overridden.test') + resetUrlsMock() + expect(urlsMock.getBaseUrl()).toBe(defaultMockEnv.NEXT_PUBLIC_APP_URL) + }) +}) diff --git a/packages/testing/src/mocks/urls.mock.ts b/packages/testing/src/mocks/urls.mock.ts index 49992cd278e..592b76cff0d 100644 --- a/packages/testing/src/mocks/urls.mock.ts +++ b/packages/testing/src/mocks/urls.mock.ts @@ -1,7 +1,142 @@ import { vi } from 'vitest' +import { envMockFns, mockEnvObject } from './env.mock' + +/** Mirrors the real `LOCALHOST_HOSTNAMES` from `@/lib/core/utils/urls`. */ +export const LOCALHOST_HOSTNAMES_MOCK: ReadonlySet = new Set([ + 'localhost', + '127.0.0.1', + '[::1]', + '::1', +]) + +const DEFAULT_SOCKET_URL = 'http://localhost:3002' +const DEFAULT_OLLAMA_URL = 'http://localhost:11434' + +function readEnv(key: string): string | undefined { + return envMockFns.getEnv(key) +} + +function hasHttpProtocol(url: string): boolean { + return /^https?:\/\//i.test(url) +} + +function getBaseUrlImpl(): string { + const baseUrl = readEnv('NEXT_PUBLIC_APP_URL')?.trim() + if (!baseUrl) { + throw new Error( + 'NEXT_PUBLIC_APP_URL must be configured for webhooks and callbacks to work correctly' + ) + } + return hasHttpProtocol(baseUrl) ? baseUrl : `http://${baseUrl}` +} + +function getInternalApiBaseUrlImpl(): string { + const internalBaseUrl = readEnv('INTERNAL_API_BASE_URL')?.trim() + if (!internalBaseUrl) return getBaseUrlImpl() + if (!hasHttpProtocol(internalBaseUrl)) { + throw new Error( + 'INTERNAL_API_BASE_URL must include protocol (http:// or https://), e.g. http://sim-app.default.svc.cluster.local:3000' + ) + } + return internalBaseUrl +} + +function ensureAbsoluteUrlImpl(pathOrUrl: string): string { + if (!pathOrUrl) throw new Error('URL is required') + return pathOrUrl.startsWith('/') ? `${getBaseUrlImpl()}${pathOrUrl}` : pathOrUrl +} + +function getBaseDomainImpl(): string { + try { + return new URL(getBaseUrlImpl()).host + } catch { + const fallbackUrl = readEnv('NEXT_PUBLIC_APP_URL') || 'http://localhost:3000' + try { + return new URL(fallbackUrl).host + } catch { + return 'localhost:3000' + } + } +} + +function getEmailDomainImpl(): string { + const baseDomain = getBaseDomainImpl() + return baseDomain.startsWith('www.') ? baseDomain.substring(4) : baseDomain +} + +function isLoopbackHostnameImpl(hostname: string): boolean { + return LOCALHOST_HOSTNAMES_MOCK.has(hostname) +} + +function parseOriginListImpl( + raw: string | undefined | null, + onInvalid?: (value: string) => void +): string[] { + if (!raw) return [] + const seen = new Set() + const origins: string[] = [] + for (const candidate of raw.split(',')) { + const trimmed = candidate.trim() + if (!trimmed) continue + try { + const { origin } = new URL(trimmed) + if (!seen.has(origin)) { + seen.add(origin) + origins.push(origin) + } + } catch { + onInvalid?.(trimmed) + } + } + return origins +} + +function isLocalhostUrlImpl(url: string): boolean { + try { + return LOCALHOST_HOSTNAMES_MOCK.has(new URL(url).hostname) + } catch { + return false + } +} + +function getBrowserOriginImpl(): string | null { + return typeof window !== 'undefined' ? window.location.origin : null +} + +function isSafeHttpUrlImpl(url: string): boolean { + try { + const parsed = new URL(url, getBrowserOriginImpl() ?? undefined) + return parsed.protocol === 'http:' || parsed.protocol === 'https:' + } catch { + return false + } +} + +function getSocketServerUrlImpl(): string { + const value = mockEnvObject.SOCKET_SERVER_URL + return (typeof value === 'string' && value) || DEFAULT_SOCKET_URL +} + +function getSocketUrlImpl(): string { + const explicit = readEnv('NEXT_PUBLIC_SOCKET_URL')?.trim() + if (explicit) return explicit + const browserOrigin = getBrowserOriginImpl() + if (browserOrigin && !LOCALHOST_HOSTNAMES_MOCK.has(new URL(browserOrigin).hostname)) { + return browserOrigin + } + return DEFAULT_SOCKET_URL +} + +function getOllamaUrlImpl(): string { + const value = mockEnvObject.OLLAMA_URL + return (typeof value === 'string' && value) || DEFAULT_OLLAMA_URL +} /** - * Controllable mock functions for `@/lib/core/utils/urls`. + * Controllable mock functions for `@/lib/core/utils/urls`. Each defaults to a + * faithful implementation of the real module that reads through the shared env + * mock (so `setEnv({ NEXT_PUBLIC_APP_URL: ... })` changes the derived URLs). + * Override per-test and restore with {@link resetUrlsMock}. * * @example * ```ts @@ -11,19 +146,44 @@ import { vi } from 'vitest' * ``` */ export const urlsMockFns = { - mockGetBaseUrl: vi.fn(), - mockGetInternalApiBaseUrl: vi.fn(), - mockEnsureAbsoluteUrl: vi.fn(), - mockGetBaseDomain: vi.fn(), - mockGetEmailDomain: vi.fn(), - mockGetSocketServerUrl: vi.fn(), - mockGetSocketUrl: vi.fn(), - mockGetOllamaUrl: vi.fn(), + mockGetBaseUrl: vi.fn(getBaseUrlImpl), + mockGetInternalApiBaseUrl: vi.fn(getInternalApiBaseUrlImpl), + mockEnsureAbsoluteUrl: vi.fn(ensureAbsoluteUrlImpl), + mockGetBaseDomain: vi.fn(getBaseDomainImpl), + mockGetEmailDomain: vi.fn(getEmailDomainImpl), + mockIsLoopbackHostname: vi.fn(isLoopbackHostnameImpl), + mockParseOriginList: vi.fn(parseOriginListImpl), + mockIsLocalhostUrl: vi.fn(isLocalhostUrlImpl), + mockGetBrowserOrigin: vi.fn(getBrowserOriginImpl), + mockIsSafeHttpUrl: vi.fn(isSafeHttpUrlImpl), + mockGetSocketServerUrl: vi.fn(getSocketServerUrlImpl), + mockGetSocketUrl: vi.fn(getSocketUrlImpl), + mockGetOllamaUrl: vi.fn(getOllamaUrlImpl), +} + +/** + * Restores every urls mock function to its default (real-behavior) + * implementation. + */ +export function resetUrlsMock(): void { + urlsMockFns.mockGetBaseUrl.mockReset().mockImplementation(getBaseUrlImpl) + urlsMockFns.mockGetInternalApiBaseUrl.mockReset().mockImplementation(getInternalApiBaseUrlImpl) + urlsMockFns.mockEnsureAbsoluteUrl.mockReset().mockImplementation(ensureAbsoluteUrlImpl) + urlsMockFns.mockGetBaseDomain.mockReset().mockImplementation(getBaseDomainImpl) + urlsMockFns.mockGetEmailDomain.mockReset().mockImplementation(getEmailDomainImpl) + urlsMockFns.mockIsLoopbackHostname.mockReset().mockImplementation(isLoopbackHostnameImpl) + urlsMockFns.mockParseOriginList.mockReset().mockImplementation(parseOriginListImpl) + urlsMockFns.mockIsLocalhostUrl.mockReset().mockImplementation(isLocalhostUrlImpl) + urlsMockFns.mockGetBrowserOrigin.mockReset().mockImplementation(getBrowserOriginImpl) + urlsMockFns.mockIsSafeHttpUrl.mockReset().mockImplementation(isSafeHttpUrlImpl) + urlsMockFns.mockGetSocketServerUrl.mockReset().mockImplementation(getSocketServerUrlImpl) + urlsMockFns.mockGetSocketUrl.mockReset().mockImplementation(getSocketUrlImpl) + urlsMockFns.mockGetOllamaUrl.mockReset().mockImplementation(getOllamaUrlImpl) } /** - * Static mock module for `@/lib/core/utils/urls`. - * Functions return sensible localhost defaults. + * Complete mock module for `@/lib/core/utils/urls`, installed globally in + * `apps/sim/vitest.setup.ts`. Every export of the real module is present. * * @example * ```ts @@ -32,11 +192,17 @@ export const urlsMockFns = { */ export const urlsMock = { SITE_URL: 'https://www.sim.ai', + LOCALHOST_HOSTNAMES: LOCALHOST_HOSTNAMES_MOCK, getBaseUrl: urlsMockFns.mockGetBaseUrl, getInternalApiBaseUrl: urlsMockFns.mockGetInternalApiBaseUrl, ensureAbsoluteUrl: urlsMockFns.mockEnsureAbsoluteUrl, getBaseDomain: urlsMockFns.mockGetBaseDomain, getEmailDomain: urlsMockFns.mockGetEmailDomain, + isLoopbackHostname: urlsMockFns.mockIsLoopbackHostname, + parseOriginList: urlsMockFns.mockParseOriginList, + isLocalhostUrl: urlsMockFns.mockIsLocalhostUrl, + getBrowserOrigin: urlsMockFns.mockGetBrowserOrigin, + isSafeHttpUrl: urlsMockFns.mockIsSafeHttpUrl, getSocketServerUrl: urlsMockFns.mockGetSocketServerUrl, getSocketUrl: urlsMockFns.mockGetSocketUrl, getOllamaUrl: urlsMockFns.mockGetOllamaUrl, From cc766c4775f2bc43063d474065dd705c8a53b5b6 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 22 Jul 2026 18:13:46 -0700 Subject: [PATCH 3/8] improvement(tests): drop redundant local mocks in executor/tools/providers and misc dirs (shared-worker readiness) --- .../async-preprocessing-correlation.test.ts | 7 -- apps/sim/background/cleanup-logs.test.ts | 4 +- .../background/cleanup-soft-deletes.test.ts | 2 - apps/sim/background/webhook-execution.test.ts | 2 - .../utils/permission-check.test.ts | 19 +++-- .../lib/copy/cleanup-failed.test.ts | 4 +- .../lib/copy/copy-files.test.ts | 2 - .../lib/copy/copy-resources.test.ts | 2 - .../workspace-forking/lib/create-fork.test.ts | 3 +- .../lib/lineage/unlink.test.ts | 1 - .../handlers/agent/agent-handler.test.ts | 3 - .../handlers/agent/skills-resolver.test.ts | 11 +-- .../handlers/pi/cloud-review-backend.test.ts | 14 ++-- .../workflow/workflow-handler.test.ts | 76 ++++++++++--------- .../providers/azure-anthropic/index.test.ts | 16 ++-- apps/sim/providers/azure-openai/index.test.ts | 22 +++--- apps/sim/providers/litellm/index.test.ts | 11 ++- apps/sim/providers/ollama/index.test.ts | 1 - apps/sim/providers/vllm/index.test.ts | 16 ++-- apps/sim/stores/workflow-diff/store.test.ts | 4 - apps/sim/tools/index.test.ts | 44 +++++------ 21 files changed, 114 insertions(+), 150 deletions(-) diff --git a/apps/sim/background/async-preprocessing-correlation.test.ts b/apps/sim/background/async-preprocessing-correlation.test.ts index 97b9e7900c8..e86d3bd66de 100644 --- a/apps/sim/background/async-preprocessing-correlation.test.ts +++ b/apps/sim/background/async-preprocessing-correlation.test.ts @@ -35,13 +35,6 @@ vi.mock('@sim/db', () => ({ workflowSchedule: {}, })) -vi.mock('drizzle-orm', () => ({ - eq: vi.fn(), - and: vi.fn(), - isNull: vi.fn(), - sql: Object.assign(vi.fn(), { raw: vi.fn() }), -})) - vi.mock('@/lib/execution/preprocessing', () => executionPreprocessingMock) vi.mock('@/lib/logs/execution/logging-session', () => loggingSessionMock) diff --git a/apps/sim/background/cleanup-logs.test.ts b/apps/sim/background/cleanup-logs.test.ts index 1b0f77fce94..9ee5306a39b 100644 --- a/apps/sim/background/cleanup-logs.test.ts +++ b/apps/sim/background/cleanup-logs.test.ts @@ -2,7 +2,7 @@ * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock, schemaMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock, schemaMock } from '@sim/testing' import { drizzleOrmMock } from '@sim/testing/mocks' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' @@ -45,8 +45,6 @@ const { mockTask: vi.fn((config: unknown) => config), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@trigger.dev/sdk', () => ({ task: mockTask })) vi.mock('@/lib/cleanup/batch-delete', () => ({ diff --git a/apps/sim/background/cleanup-soft-deletes.test.ts b/apps/sim/background/cleanup-soft-deletes.test.ts index ea63df6a859..a52108e5d36 100644 --- a/apps/sim/background/cleanup-soft-deletes.test.ts +++ b/apps/sim/background/cleanup-soft-deletes.test.ts @@ -33,8 +33,6 @@ const { mockSelectRowsByIdChunks: vi.fn(async () => [] as unknown[]), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/cleanup/batch-delete', () => ({ batchDeleteByWorkspaceAndTimestamp: mockBatchDeleteByWorkspaceAndTimestamp, chunkedBatchDelete: mockChunkedBatchDelete, diff --git a/apps/sim/background/webhook-execution.test.ts b/apps/sim/background/webhook-execution.test.ts index a075943ea40..fa44602b09b 100644 --- a/apps/sim/background/webhook-execution.test.ts +++ b/apps/sim/background/webhook-execution.test.ts @@ -3,7 +3,6 @@ */ import { - dbChainMock, dbChainMockFns, executionPreprocessingMock, executionPreprocessingMockFns, @@ -44,7 +43,6 @@ vi.mock('@opentelemetry/api', () => ({ trace: { getActiveSpan: mockGetActiveSpan }, })) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@/lib/execution/preprocessing', () => executionPreprocessingMock) vi.mock('@/lib/logs/execution/logging-session', () => loggingSessionMock) diff --git a/apps/sim/ee/access-control/utils/permission-check.test.ts b/apps/sim/ee/access-control/utils/permission-check.test.ts index 2aea7cf535c..c449d3d9608 100644 --- a/apps/sim/ee/access-control/utils/permission-check.test.ts +++ b/apps/sim/ee/access-control/utils/permission-check.test.ts @@ -9,14 +9,14 @@ import { resetEnvFlagsMock, setEnvFlags, } from '@sim/testing' -import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterAll, beforeAll, beforeEach, describe, expect, it, type Mock, vi } from 'vitest' +import { getBlock } from '@/blocks/registry' const { DEFAULT_PERMISSION_GROUP_CONFIG, mockIsOrganizationOnEnterprisePlan, mockGetWorkspaceWithOwner, mockGetProviderFromModel, - mockGetBlock, } = vi.hoisted(() => ({ DEFAULT_PERMISSION_GROUP_CONFIG: { allowedIntegrations: null, @@ -47,7 +47,6 @@ const { mockIsOrganizationOnEnterprisePlan: vi.fn<() => Promise>(), mockGetWorkspaceWithOwner: vi.fn<() => Promise<{ organizationId: string | null } | null>>(), mockGetProviderFromModel: vi.fn<(model: string) => string>(), - mockGetBlock: vi.fn<(type: string) => { hideFromToolbar?: boolean } | undefined>(), })) vi.mock('@/lib/billing', () => ({ @@ -70,11 +69,6 @@ vi.mock('@/providers/utils', () => ({ getProviderFromModel: mockGetProviderFromModel, })) -vi.mock('@/blocks/registry', () => ({ - getBlock: mockGetBlock, - getAllBlocks: vi.fn(() => []), -})) - import { assertPermissionsAllowed, ChatDeployAuthNotAllowedError, @@ -128,6 +122,15 @@ function queueGroupResolution( afterAll(resetDbChainMock) +/** The global registry mock's getBlock, driven per-test in this suite. */ +const mockGetBlock = getBlock as Mock + +const defaultGetBlockImpl = mockGetBlock.getMockImplementation() + +afterAll(() => { + mockGetBlock.mockImplementation(defaultGetBlockImpl as () => unknown) +}) + /** * Default every block to non-legacy. `vi.clearAllMocks()` (used by the * describe-level hooks) keeps implementations, so reset here to stop a legacy diff --git a/apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.test.ts b/apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.test.ts index 907f08c9cf1..c122f181427 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.test.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.test.ts @@ -2,15 +2,13 @@ * @vitest-environment node */ import { knowledgeBase, workflow, workflowBlocks, workflowDeploymentVersion } from '@sim/db/schema' -import { dbChainMock, dbChainMockFns, queueTableRows, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, queueTableRows, resetDbChainMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockInvalidateDeployedStateCache } = vi.hoisted(() => ({ mockInvalidateDeployedStateCache: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/workflows/persistence/utils', () => ({ invalidateDeployedStateCache: mockInvalidateDeployedStateCache, CREDENTIAL_SUBBLOCK_IDS: new Set(['credential', 'manualCredential', 'triggerCredentials']), diff --git a/apps/sim/ee/workspace-forking/lib/copy/copy-files.test.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-files.test.ts index b6a5568e7f5..01b780bcd75 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/copy-files.test.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-files.test.ts @@ -2,7 +2,6 @@ * @vitest-environment node */ import { - dbChainMock, dbChainMockFns, resetDbChainMock, storageServiceMock, @@ -15,7 +14,6 @@ const { mockIncrementStorageUsageInTx, mockResolveStorageBillingContext } = vi.h mockResolveStorageBillingContext: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@/lib/uploads/core/storage-service', () => storageServiceMock) vi.mock('@/lib/billing/storage', () => ({ incrementStorageUsageForBillingContextInTx: mockIncrementStorageUsageInTx, diff --git a/apps/sim/ee/workspace-forking/lib/copy/copy-resources.test.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-resources.test.ts index ac3393c31ef..d3863d3fb6b 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/copy-resources.test.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-resources.test.ts @@ -2,7 +2,6 @@ * @vitest-environment node */ import { - dbChainMock, dbChainMockFns, resetDbChainMock, storageServiceMock, @@ -20,7 +19,6 @@ const { mockResolveStorageBillingContext: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@/lib/uploads/core/storage-service', () => storageServiceMock) vi.mock('@/lib/billing/storage', () => ({ decrementStorageUsageForBillingContextInTx: mockDecrementStorageUsageInTx, diff --git a/apps/sim/ee/workspace-forking/lib/create-fork.test.ts b/apps/sim/ee/workspace-forking/lib/create-fork.test.ts index 6c4a16999c1..9e0c268fffd 100644 --- a/apps/sim/ee/workspace-forking/lib/create-fork.test.ts +++ b/apps/sim/ee/workspace-forking/lib/create-fork.test.ts @@ -1,7 +1,7 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -26,7 +26,6 @@ const { mockSeedEdgeMappings: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@/lib/workflows/defaults', () => ({ buildDefaultWorkflowArtifacts: vi.fn(() => ({ workflowState: {} })), })) diff --git a/apps/sim/ee/workspace-forking/lib/lineage/unlink.test.ts b/apps/sim/ee/workspace-forking/lib/lineage/unlink.test.ts index 8ce5fc2f4d4..9a4affb507e 100644 --- a/apps/sim/ee/workspace-forking/lib/lineage/unlink.test.ts +++ b/apps/sim/ee/workspace-forking/lib/lineage/unlink.test.ts @@ -9,7 +9,6 @@ const { mockSetForkLockTimeout, mockAcquireForkEdgeLock } = vi.hoisted(() => ({ mockAcquireForkEdgeLock: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@/ee/workspace-forking/lib/lineage/lineage', () => ({ setForkLockTimeout: mockSetForkLockTimeout, acquireForkEdgeLock: mockAcquireForkEdgeLock, diff --git a/apps/sim/executor/handlers/agent/agent-handler.test.ts b/apps/sim/executor/handlers/agent/agent-handler.test.ts index 6fc48121454..8ef8c310b16 100644 --- a/apps/sim/executor/handlers/agent/agent-handler.test.ts +++ b/apps/sim/executor/handlers/agent/agent-handler.test.ts @@ -1,5 +1,4 @@ import { - dbChainMock, queueTableRows, resetDbChainMock, resetEnvFlagsMock, @@ -92,8 +91,6 @@ vi.mock('@/executor/utils/http', () => ({ }), })) -vi.mock('@sim/db', () => dbChainMock) - /** Connected MCP servers every workspace-server lookup in this suite resolves. */ const MCP_SERVER_ROWS = [ { id: 'mcp-search-server', connectionStatus: 'connected' }, diff --git a/apps/sim/executor/handlers/agent/skills-resolver.test.ts b/apps/sim/executor/handlers/agent/skills-resolver.test.ts index e562b63b7ec..fb9103da2bc 100644 --- a/apps/sim/executor/handlers/agent/skills-resolver.test.ts +++ b/apps/sim/executor/handlers/agent/skills-resolver.test.ts @@ -1,17 +1,8 @@ /** * @vitest-environment node */ -import { - dbChainMock, - dbChainMockFns, - queueTableRows, - resetDbChainMock, - schemaMock, -} from '@sim/testing' +import { dbChainMockFns, queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' - -vi.mock('@sim/db', () => dbChainMock) - import { resolveSkillContent } from './skills-resolver' // resolveSkillContent is the shared resolver invoked when a workflow agent block diff --git a/apps/sim/executor/handlers/pi/cloud-review-backend.test.ts b/apps/sim/executor/handlers/pi/cloud-review-backend.test.ts index 37d22a178dd..d5131ec0403 100644 --- a/apps/sim/executor/handlers/pi/cloud-review-backend.test.ts +++ b/apps/sim/executor/handlers/pi/cloud-review-backend.test.ts @@ -1,6 +1,7 @@ /** * @vitest-environment node */ +import { createLogger } from '@sim/logger' import { beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -17,7 +18,6 @@ const { mockRemoveRuntimeApiKey, mockCreateSealedResourceLoader, mockCreatePiModelRuntime, - mockLoggerWarn, } = vi.hoisted(() => ({ mockRun: vi.fn(), mockWriteFile: vi.fn(), @@ -32,7 +32,6 @@ const { mockRemoveRuntimeApiKey: vi.fn(), mockCreateSealedResourceLoader: vi.fn(), mockCreatePiModelRuntime: vi.fn(), - mockLoggerWarn: vi.fn(), })) let sessionEventListener: ((raw: unknown) => void) | undefined @@ -59,9 +58,6 @@ const mockModelRuntime = { removeRuntimeApiKey: mockRemoveRuntimeApiKey, } -vi.mock('@sim/logger', () => ({ - createLogger: () => ({ info: vi.fn(), warn: mockLoggerWarn }), -})) vi.mock('@/lib/execution/e2b', () => ({ withPiSandbox: (fn: (runner: unknown) => unknown) => fn({ run: mockRun, writeFile: mockWriteFile }), @@ -95,6 +91,14 @@ vi.mock('@/executor/handlers/pi/pi-sdk', () => ({ import type { PiCloudReviewRunParams } from '@/executor/handlers/pi/backend' import { runCloudReviewPi } from '@/executor/handlers/pi/cloud-review-backend' +/** + * The mock logger instance the global `@sim/logger` mock handed to the module + * under test at import time, captured so the suite can assert on its warns. + */ +const mockLoggerWarn = vi.mocked(createLogger).mock.results[ + vi.mocked(createLogger).mock.calls.findIndex(([name]) => name === 'PiCloudReviewBackend') +].value.warn as ReturnType + const HEAD_SHA = 'a'.repeat(40) const BASE_SHA = 'b'.repeat(40) const REVIEW_TOOL_NAMES = [ diff --git a/apps/sim/executor/handlers/workflow/workflow-handler.test.ts b/apps/sim/executor/handlers/workflow/workflow-handler.test.ts index 50bc0a7abef..4e9b8c9831a 100644 --- a/apps/sim/executor/handlers/workflow/workflow-handler.test.ts +++ b/apps/sim/executor/handlers/workflow/workflow-handler.test.ts @@ -1,4 +1,6 @@ -import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest' +import { environmentUtilsMockFns, resetEnvironmentUtilsMock } from '@sim/testing' +import { afterAll, beforeAll, beforeEach, describe, expect, it, type Mock, vi } from 'vitest' +import { getBlock } from '@/blocks/registry' import { BlockType } from '@/executor/constants' import { findMissingRequiredCustomBlockInputs, @@ -13,7 +15,6 @@ const { mockCreateSnapshot, mockResolveBillingAttribution, mockGetCustomBlockAuthority, - mockGetPersonalAndWorkspaceEnv, mockGetUserEmailById, executorOptions, } = vi.hoisted(() => ({ @@ -21,7 +22,6 @@ const { mockCreateSnapshot: vi.fn(), mockResolveBillingAttribution: vi.fn(), mockGetCustomBlockAuthority: vi.fn(), - mockGetPersonalAndWorkspaceEnv: vi.fn(), mockGetUserEmailById: vi.fn(), executorOptions: [] as Array>, })) @@ -39,9 +39,7 @@ vi.mock('@/lib/billing/core/billing-attribution', () => ({ resolveBillingAttribution: mockResolveBillingAttribution, })) -vi.mock('@/lib/environment/utils', () => ({ - getPersonalAndWorkspaceEnv: mockGetPersonalAndWorkspaceEnv, -})) +const mockGetPersonalAndWorkspaceEnv = environmentUtilsMockFns.mockGetPersonalAndWorkspaceEnv vi.mock('@/lib/workflows/custom-blocks/operations', () => ({ getCustomBlockAuthority: mockGetCustomBlockAuthority, @@ -51,40 +49,50 @@ vi.mock('@/lib/users/queries', () => ({ getUserEmailById: mockGetUserEmailById, })) -// Override the global registry mock so the Serializer can carry the start -// block's runMetadata param through child deployed-state serialization. -vi.mock('@/blocks/registry', () => ({ - getBlock: vi.fn((type: string) => { - if (type === 'start_trigger') { - return { - name: 'Start', - description: 'Unified workflow entry point', - category: 'triggers', - bgColor: '#34B5FF', - icon: () => null, - subBlocks: [ - { id: 'inputFormat', title: 'Inputs', type: 'input-format' }, - { id: 'runMetadata', title: 'Add run metadata', type: 'switch', defaultValue: false }, - ], - inputs: {}, - outputs: {}, - tools: { access: [] }, - triggers: { enabled: true, available: ['chat', 'manual', 'api'] }, - } - } +/** + * Overrides the global registry mock's getBlock so the Serializer can carry the + * start block's runMetadata param through child deployed-state serialization. + */ +function getBlockOverride(type: string) { + if (type === 'start_trigger') { return { - name: 'Mock Block', - description: 'Mock block description', + name: 'Start', + description: 'Unified workflow entry point', + category: 'triggers', + bgColor: '#34B5FF', icon: () => null, - subBlocks: [], + subBlocks: [ + { id: 'inputFormat', title: 'Inputs', type: 'input-format' }, + { id: 'runMetadata', title: 'Add run metadata', type: 'switch', defaultValue: false }, + ], inputs: {}, outputs: {}, tools: { access: [] }, + triggers: { enabled: true, available: ['chat', 'manual', 'api'] }, } - }), - getAllBlocks: vi.fn(() => ({})), - getLatestBlock: vi.fn(() => undefined), -})) + } + return { + name: 'Mock Block', + description: 'Mock block description', + icon: () => null, + subBlocks: [], + inputs: {}, + outputs: {}, + tools: { access: [] }, + } +} + +const mockGetBlock = getBlock as Mock +const defaultGetBlockImpl = mockGetBlock.getMockImplementation() + +beforeAll(() => { + mockGetBlock.mockImplementation(getBlockOverride) +}) + +afterAll(() => { + mockGetBlock.mockImplementation(defaultGetBlockImpl as () => unknown) + resetEnvironmentUtilsMock() +}) vi.mock('@/lib/logs/execution/snapshot/service', () => ({ snapshotService: { createSnapshotWithDeduplication: mockCreateSnapshot }, diff --git a/apps/sim/providers/azure-anthropic/index.test.ts b/apps/sim/providers/azure-anthropic/index.test.ts index d78c9bdac2b..0956aa0dac8 100644 --- a/apps/sim/providers/azure-anthropic/index.test.ts +++ b/apps/sim/providers/azure-anthropic/index.test.ts @@ -1,7 +1,8 @@ /** * @vitest-environment node */ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { resetEnvMock, setEnv } from '@sim/testing' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' import type { ProviderRequest } from '@/providers/types' const { @@ -11,7 +12,6 @@ const { mockCreatePinnedFetch, mockExecuteAnthropic, sentinelFetch, - envState, } = vi.hoisted(() => { const anthropicArgs: Array> = [] const sentinelFetch = vi.fn() @@ -27,15 +27,10 @@ const { mockCreatePinnedFetch: vi.fn(() => sentinelFetch), mockExecuteAnthropic: vi.fn(), sentinelFetch, - envState: { - AZURE_ANTHROPIC_ENDPOINT: undefined as string | undefined, - AZURE_ANTHROPIC_API_VERSION: undefined as string | undefined, - }, } }) vi.mock('@anthropic-ai/sdk', () => ({ default: mockAnthropic })) -vi.mock('@/lib/core/config/env', () => ({ env: envState })) vi.mock('@/lib/core/security/input-validation.server', () => ({ validateUrlWithDNS: mockValidate, createPinnedFetch: mockCreatePinnedFetch, @@ -65,12 +60,13 @@ function buildClientOptions(): Record { return anthropicArgs[0] } +afterAll(resetEnvMock) + describe('azureAnthropicProvider — SSRF pinning', () => { beforeEach(() => { vi.clearAllMocks() anthropicArgs.length = 0 - envState.AZURE_ANTHROPIC_ENDPOINT = undefined - envState.AZURE_ANTHROPIC_API_VERSION = undefined + setEnv({ AZURE_ANTHROPIC_ENDPOINT: undefined, AZURE_ANTHROPIC_API_VERSION: undefined }) mockExecuteAnthropic.mockResolvedValue({ content: 'ok' }) }) @@ -87,7 +83,7 @@ describe('azureAnthropicProvider — SSRF pinning', () => { }) it('does not pin when the endpoint comes from trusted server env', async () => { - envState.AZURE_ANTHROPIC_ENDPOINT = 'https://trusted.services.ai.azure.com' + setEnv({ AZURE_ANTHROPIC_ENDPOINT: 'https://trusted.services.ai.azure.com' }) await azureAnthropicProvider.executeRequest(request({ azureEndpoint: undefined })) diff --git a/apps/sim/providers/azure-openai/index.test.ts b/apps/sim/providers/azure-openai/index.test.ts index 15e4073e8b0..f58c72ddfd6 100644 --- a/apps/sim/providers/azure-openai/index.test.ts +++ b/apps/sim/providers/azure-openai/index.test.ts @@ -1,7 +1,8 @@ /** * @vitest-environment node */ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { resetEnvMock, setEnv } from '@sim/testing' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' import type { ProviderRequest } from '@/providers/types' const { @@ -14,7 +15,6 @@ const { sentinelFetch, mockIsChatCompletionsEndpoint, mockIsResponsesEndpoint, - envState, } = vi.hoisted(() => { const azureOpenAIArgs: Array> = [] const sentinelFetch = vi.fn() @@ -35,15 +35,10 @@ const { sentinelFetch, mockIsChatCompletionsEndpoint: vi.fn(() => false), mockIsResponsesEndpoint: vi.fn(() => false), - envState: { - AZURE_OPENAI_ENDPOINT: undefined as string | undefined, - AZURE_OPENAI_API_VERSION: undefined as string | undefined, - }, } }) vi.mock('openai', () => ({ AzureOpenAI: mockAzureOpenAI })) -vi.mock('@/lib/core/config/env', () => ({ env: envState })) vi.mock('@/providers', () => ({ MAX_TOOL_ITERATIONS: 20 })) vi.mock('@/lib/core/security/input-validation.server', () => ({ validateUrlWithDNS: mockValidate, @@ -96,12 +91,13 @@ function request(overrides: Partial): ProviderRequest { /** Config object passed to the Responses core on the Nth call. */ const responsesConfig = (call = 0) => mockExecuteResponses.mock.calls[call][1] +afterAll(resetEnvMock) + describe('azureOpenAIProvider — SSRF pinning', () => { beforeEach(() => { vi.clearAllMocks() azureOpenAIArgs.length = 0 - envState.AZURE_OPENAI_ENDPOINT = undefined - envState.AZURE_OPENAI_API_VERSION = undefined + setEnv({ AZURE_OPENAI_ENDPOINT: undefined, AZURE_OPENAI_API_VERSION: undefined }) mockIsChatCompletionsEndpoint.mockReturnValue(false) mockIsResponsesEndpoint.mockReturnValue(false) mockExecuteResponses.mockResolvedValue({ content: 'ok' }) @@ -121,7 +117,7 @@ describe('azureOpenAIProvider — SSRF pinning', () => { }) it('passes no custom fetch when the endpoint comes from trusted server env', async () => { - envState.AZURE_OPENAI_ENDPOINT = 'https://trusted.openai.azure.com' + setEnv({ AZURE_OPENAI_ENDPOINT: 'https://trusted.openai.azure.com' }) await azureOpenAIProvider.executeRequest(request({ azureEndpoint: undefined })) @@ -178,8 +174,10 @@ describe('azureOpenAIProvider — SSRF pinning', () => { it('constructs the AzureOpenAI client without a custom fetch for a trusted env endpoint', async () => { mockIsChatCompletionsEndpoint.mockReturnValue(true) - envState.AZURE_OPENAI_ENDPOINT = - 'https://trusted.openai.azure.com/openai/deployments/gpt-4o/chat/completions' + setEnv({ + AZURE_OPENAI_ENDPOINT: + 'https://trusted.openai.azure.com/openai/deployments/gpt-4o/chat/completions', + }) mockChatCreate.mockResolvedValue({ choices: [{ message: { content: 'hi', tool_calls: undefined } }], usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, diff --git a/apps/sim/providers/litellm/index.test.ts b/apps/sim/providers/litellm/index.test.ts index 8a6a2fa011d..04efada3fa3 100644 --- a/apps/sim/providers/litellm/index.test.ts +++ b/apps/sim/providers/litellm/index.test.ts @@ -1,7 +1,8 @@ /** * @vitest-environment node */ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { resetEnvMock, setEnv } from '@sim/testing' +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockCreate, mockExecuteTool } = vi.hoisted(() => ({ mockCreate: vi.fn(), @@ -20,9 +21,11 @@ vi.mock('@/tools', () => ({ executeTool: mockExecuteTool })) vi.mock('@/providers', () => ({ MAX_TOOL_ITERATIONS: 20 })) -vi.mock('@/lib/core/config/env', () => ({ - env: { LITELLM_BASE_URL: 'http://litellm.test', LITELLM_API_KEY: '' }, -})) +beforeAll(() => { + setEnv({ LITELLM_BASE_URL: 'http://litellm.test', LITELLM_API_KEY: '' }) +}) + +afterAll(resetEnvMock) vi.mock('@/stores/providers', () => ({ useProvidersStore: { getState: () => ({ setProviderModels: vi.fn() }) }, diff --git a/apps/sim/providers/ollama/index.test.ts b/apps/sim/providers/ollama/index.test.ts index 3c811906826..00ec2b43c32 100644 --- a/apps/sim/providers/ollama/index.test.ts +++ b/apps/sim/providers/ollama/index.test.ts @@ -38,7 +38,6 @@ vi.mock('openai', () => { return { default: OpenAI } }) -vi.mock('@/lib/core/utils/urls', () => ({ getOllamaUrl: () => 'http://localhost:11434' })) vi.mock('@/providers', () => ({ MAX_TOOL_ITERATIONS: 20 })) vi.mock('@/providers/attachments', () => ({ formatMessagesForProvider: (messages: unknown) => messages, diff --git a/apps/sim/providers/vllm/index.test.ts b/apps/sim/providers/vllm/index.test.ts index 925c61ca2d2..ed6fa3de8f1 100644 --- a/apps/sim/providers/vllm/index.test.ts +++ b/apps/sim/providers/vllm/index.test.ts @@ -1,7 +1,8 @@ /** * @vitest-environment node */ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { resetEnvMock, setEnv } from '@sim/testing' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockCreate, @@ -14,7 +15,6 @@ const { mockValidateUrlWithDNS, mockCreatePinnedFetch, pinnedFetchFn, - envState, } = vi.hoisted(() => { const openAIArgs: Array> = [] const mockCreate = vi.fn() @@ -36,15 +36,10 @@ const { mockValidateUrlWithDNS: vi.fn(), mockCreatePinnedFetch: vi.fn(() => pinnedFetchFn), pinnedFetchFn, - envState: { - VLLM_BASE_URL: 'http://localhost:8000', - VLLM_API_KEY: undefined as string | undefined, - }, } }) vi.mock('openai', () => ({ default: mockOpenAI })) -vi.mock('@/lib/core/config/env', () => ({ env: envState })) vi.mock('@/lib/core/security/input-validation.server', () => ({ validateUrlWithDNS: mockValidateUrlWithDNS, createPinnedFetch: mockCreatePinnedFetch, @@ -115,13 +110,14 @@ const toolCall = (id: string, name: string, args = '{}'): ToolCall => ({ /** Payload passed to the Nth `chat.completions.create` call. */ const createPayload = (callIndex: number) => mockCreate.mock.calls[callIndex][0] +afterAll(resetEnvMock) + describe('vllmProvider', () => { beforeEach(() => { vi.clearAllMocks() clearProviderClientCacheForTests() openAIArgs.length = 0 - envState.VLLM_BASE_URL = 'http://localhost:8000' - envState.VLLM_API_KEY = undefined + setEnv({ VLLM_BASE_URL: 'http://localhost:8000', VLLM_API_KEY: undefined }) mockPrepareTools.mockReturnValue({ tools: [{ type: 'function', function: { name: 'myTool' } }], toolChoice: 'auto', @@ -386,7 +382,7 @@ describe('vllmProvider', () => { }) it('throws when no base URL is configured', async () => { - envState.VLLM_BASE_URL = '' + setEnv({ VLLM_BASE_URL: '' }) await expect( vllmProvider.executeRequest({ diff --git a/apps/sim/stores/workflow-diff/store.test.ts b/apps/sim/stores/workflow-diff/store.test.ts index c9bca038048..7eae99115f9 100644 --- a/apps/sim/stores/workflow-diff/store.test.ts +++ b/apps/sim/stores/workflow-diff/store.test.ts @@ -22,10 +22,6 @@ const { applyWorkflowStateToStores } = vi.hoisted(() => ({ applyWorkflowStateToStores: vi.fn(), })) -vi.mock('@sim/logger', () => ({ - createLogger: () => ({ info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }), -})) - vi.mock('@/lib/workflows/diff', () => ({ WorkflowDiffEngine: class { clearDiff = vi.fn() diff --git a/apps/sim/tools/index.test.ts b/apps/sim/tools/index.test.ts index a63205816fe..7c4ab612bdc 100644 --- a/apps/sim/tools/index.test.ts +++ b/apps/sim/tools/index.test.ts @@ -11,10 +11,15 @@ import { createExecutionContext, createMockFetch, type ExecutionContext, + environmentUtilsMockFns, inputValidationMock, inputValidationMockFns, type MockFetchResponse, resetEnvFlagsMock, + resetEnvironmentUtilsMock, + resetEnvMock, + resetUrlsMock, + setEnv, setEnvFlags, } from '@sim/testing' import { sleep } from '@sim/utils/helpers' @@ -23,7 +28,6 @@ import type { BillingAttributionSnapshot } from '@/lib/billing/core/billing-attr // Hoisted mock state - these are available to vi.mock factories const { - mockEnv, mockGetBYOKKey, mockGetToolAsync, mockRateLimiterFns, @@ -32,9 +36,7 @@ const { mockGetCustomToolByIdOrTitle, mockGenerateInternalToken, mockResolveWorkspaceFileReference, - mockGetEffectiveDecryptedEnv, } = vi.hoisted(() => ({ - mockEnv: { NEXT_PUBLIC_APP_URL: 'http://localhost:3000' } as Record, mockGetBYOKKey: vi.fn(), mockGetToolAsync: vi.fn(), mockRateLimiterFns: { @@ -47,24 +49,11 @@ const { mockGetCustomToolByIdOrTitle: vi.fn(), mockGenerateInternalToken: vi.fn(), mockResolveWorkspaceFileReference: vi.fn(), - mockGetEffectiveDecryptedEnv: vi.fn(), })) const mockSecureFetchWithPinnedIP = inputValidationMockFns.mockSecureFetchWithPinnedIP const mockValidateUrlWithDNS = inputValidationMockFns.mockValidateUrlWithDNS - -// Use the real urls module so it reads the file-local env mock below -vi.unmock('@/lib/core/utils/urls') - -// Mock env config to control hosted key availability -vi.mock('@/lib/core/config/env', () => ({ - env: new Proxy({} as Record, { - get: (_target, prop: string) => mockEnv[prop], - }), - getEnv: (key: string) => mockEnv[key], - isTruthy: (val: unknown) => val === true || val === 'true' || val === '1', - isFalsy: (val: unknown) => val === false || val === 'false' || val === '0', -})) +const mockGetEffectiveDecryptedEnv = environmentUtilsMockFns.mockGetEffectiveDecryptedEnv // Mock getBYOKKey vi.mock('@/lib/api-key/byok', () => ({ @@ -102,10 +91,6 @@ vi.mock('@/lib/core/rate-limiter/hosted-key', () => ({ getHostedKeyRateLimiter: () => mockRateLimiterFns, })) -vi.mock('@/lib/environment/utils', () => ({ - getEffectiveDecryptedEnv: (...args: unknown[]) => mockGetEffectiveDecryptedEnv(...args), -})) - vi.mock('@/lib/uploads/contexts/workspace/workspace-file-manager', () => ({ resolveWorkspaceFileReference: (...args: unknown[]) => mockResolveWorkspaceFileReference(...args), })) @@ -405,10 +390,19 @@ vi.spyOn(getQueryClientModule, 'getQueryClient').mockImplementation(createMockQu beforeEach(() => { vi.spyOn(getQueryClientModule, 'getQueryClient').mockImplementation(createMockQueryClient) + // Suites below call vi.resetAllMocks(), which wipes the shared env/urls mock + // implementations — restore their defaults and re-pin the base URL each test. + resetEnvMock() + resetUrlsMock() + resetEnvironmentUtilsMock() + setEnv({ NEXT_PUBLIC_APP_URL: 'http://localhost:3000' }) }) afterAll(() => { vi.mocked(getQueryClientModule.getQueryClient).mockRestore() + resetEnvMock() + resetUrlsMock() + resetEnvironmentUtilsMock() }) /** @@ -2553,7 +2547,7 @@ describe('Rate Limiting and Retry Logic', () => { }) vi.clearAllMocks() setEnvFlags({ isHosted: true }) - mockEnv.TEST_HOSTED_KEY = 'test-hosted-api-key' + setEnv({ TEST_HOSTED_KEY: 'test-hosted-api-key' }) mockGetBYOKKey.mockResolvedValue(null) // Set up throttler mock defaults mockRateLimiterFns.acquireKey.mockResolvedValue({ @@ -2571,7 +2565,7 @@ describe('Rate Limiting and Retry Logic', () => { vi.resetAllMocks() cleanupEnvVars() setEnvFlags({ isHosted: false }) - mockEnv.TEST_HOSTED_KEY = undefined + setEnv({ TEST_HOSTED_KEY: undefined }) }) it('should retry on 429 rate limit errors with exponential backoff', async () => { @@ -2937,7 +2931,7 @@ describe('Cost Field Handling', () => { }) vi.clearAllMocks() setEnvFlags({ isHosted: true }) - mockEnv.TEST_HOSTED_KEY = 'test-hosted-api-key' + setEnv({ TEST_HOSTED_KEY: 'test-hosted-api-key' }) mockGetBYOKKey.mockResolvedValue(null) // Set up throttler mock defaults mockRateLimiterFns.acquireKey.mockResolvedValue({ @@ -2954,7 +2948,7 @@ describe('Cost Field Handling', () => { vi.resetAllMocks() cleanupEnvVars() setEnvFlags({ isHosted: false }) - mockEnv.TEST_HOSTED_KEY = undefined + setEnv({ TEST_HOSTED_KEY: undefined }) }) it('should add cost to output when using hosted key with per_request pricing', async () => { From 6e9dcb76b85ac58aede5ae4b288e97ffdfa1f5f8 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 22 Jul 2026 18:14:09 -0700 Subject: [PATCH 4/8] improvement(tests): drop redundant local mocks in app routes (shared-worker readiness) --- .../billing/credit-usage/page.test.ts | 27 +++++------- .../app/api/audit-logs/export/route.test.ts | 11 ++--- .../api/auth/forget-password/route.test.ts | 12 +++--- .../api/auth/oauth/connections/route.test.ts | 4 -- .../api/auth/oauth/disconnect/route.test.ts | 3 -- apps/sim/app/api/auth/oauth/utils.test.ts | 4 +- .../api/auth/oauth2/authorize/route.test.ts | 3 -- .../app/api/auth/sso/register/route.test.ts | 7 ++-- .../app/api/billing/invoices/route.test.ts | 14 ++----- apps/sim/app/api/billing/route.test.ts | 13 ++---- .../app/api/billing/switch-plan/route.test.ts | 11 ++--- .../app/api/blocks/visibility/route.test.ts | 16 ++++--- .../api/chat/[identifier]/otp/route.test.ts | 25 +++-------- .../app/api/chat/manage/[id]/route.test.ts | 13 +++--- apps/sim/app/api/chat/route.test.ts | 20 ++++----- apps/sim/app/api/chat/utils.test.ts | 13 ++---- .../app/api/copilot/api-keys/route.test.ts | 11 +++-- .../app/api/copilot/chat/delete/route.test.ts | 4 +- .../app/api/copilot/chat/stop/route.test.ts | 4 +- .../copilot/checkpoints/revert/route.test.ts | 11 ++--- .../app/api/copilot/feedback/route.test.ts | 10 +---- .../cron/renew-subscriptions/route.test.ts | 4 -- .../custom-blocks/[id]/usages/route.test.ts | 24 +++++------ apps/sim/app/api/files/authorization.test.ts | 4 +- apps/sim/app/api/files/delete/route.test.ts | 25 ----------- apps/sim/app/api/files/upload/route.test.ts | 25 ----------- apps/sim/app/api/folders/[id]/route.test.ts | 2 - .../sim/app/api/folders/reorder/route.test.ts | 7 ---- apps/sim/app/api/folders/route.test.ts | 5 --- .../api/guardrails/mask-batch/route.test.ts | 9 ++-- .../api/invitations/[id]/accept/route.test.ts | 11 ++--- apps/sim/app/api/jobs/[jobId]/route.test.ts | 14 ++++--- .../[connectorId]/documents/route.test.ts | 2 - .../connectors/[connectorId]/route.test.ts | 2 - .../[connectorId]/sync/route.test.ts | 2 - .../knowledge/[id]/connectors/route.test.ts | 2 - .../[id]/documents/[documentId]/route.test.ts | 3 -- .../knowledge/[id]/documents/route.test.ts | 3 -- .../[id]/documents/upsert/route.test.ts | 4 -- apps/sim/app/api/knowledge/[id]/route.test.ts | 3 -- apps/sim/app/api/knowledge/route.test.ts | 3 -- .../app/api/knowledge/search/route.test.ts | 22 ++-------- apps/sim/app/api/knowledge/utils.test.ts | 3 -- .../app/api/mcp/oauth/callback/route.test.ts | 9 ---- .../sim/app/api/mcp/oauth/start/route.test.ts | 11 ----- .../api/mcp/serve/[serverId]/route.test.ts | 25 ++++------- .../mcp/servers/[id]/refresh/route.test.ts | 4 +- apps/sim/app/api/mcp/servers/route.test.ts | 4 +- .../chats/[chatId]/fork/route.test.ts | 13 ++++-- .../[id]/invitations/route.test.ts | 15 ++----- .../[memberId]/usage-limit/route.test.ts | 10 ++--- .../[id]/permission-groups/utils.test.ts | 4 +- .../organizations/[id]/roster/route.test.ts | 16 ++----- .../[id]/session-policy/route.test.ts | 13 ++---- apps/sim/app/api/organizations/route.test.ts | 15 ++----- .../providers/baseten/models/route.test.ts | 22 ++++------ .../ollama-cloud/models/route.test.ts | 10 ++--- .../providers/together/models/route.test.ts | 42 ++++++++----------- apps/sim/app/api/resume/poll/route.test.ts | 34 ++++----------- apps/sim/app/api/schedules/[id]/route.test.ts | 6 --- apps/sim/app/api/schedules/route.test.ts | 7 ---- apps/sim/app/api/speech/token/route.test.ts | 16 ++++--- apps/sim/app/api/tools/custom/route.test.ts | 3 -- apps/sim/app/api/usage/route.test.ts | 17 +++----- .../subscription/[id]/transfer/route.test.ts | 5 --- .../app/api/v1/audit-logs/[id]/route.test.ts | 4 +- apps/sim/app/api/v1/audit-logs/auth.test.ts | 10 +---- apps/sim/app/api/v1/audit-logs/query.test.ts | 5 +-- .../webhooks/poll/[provider]/route.test.ts | 4 +- apps/sim/app/api/webhooks/slack/route.test.ts | 12 +++--- .../sim/app/api/webhooks/tiktok/route.test.ts | 21 ++++------ .../workflows/[id]/chat/status/route.test.ts | 8 ---- .../[id]/execute/route.async.test.ts | 17 ++++---- .../[executionId]/stream/route.test.ts | 22 +++------- .../app/api/workflows/[id]/log/route.test.ts | 3 +- .../workflows/[id]/references/route.test.ts | 16 +++---- apps/sim/app/api/workflows/[id]/route.test.ts | 6 --- apps/sim/app/api/workflows/middleware.test.ts | 2 - apps/sim/app/api/workflows/route.test.ts | 3 -- .../api/workspace-events/poll/route.test.ts | 4 +- .../workspaces/[id]/api-keys/route.test.ts | 32 +++++++------- .../workspaces/[id]/byok-keys/route.test.ts | 27 +++++------- .../[id]/credit-availability/route.test.ts | 17 ++++---- .../workspaces/[id]/environment/route.test.ts | 33 +++++---------- .../[id]/files/inline/route.test.ts | 10 ++--- .../fork/excluded-workflows/route.test.ts | 17 +++----- .../[id]/fork/lineage/route.test.ts | 11 ++--- .../[id]/host-context/route.test.ts | 12 ++---- .../workspaces/[id]/usage-gate/route.test.ts | 17 +++----- .../api/workspaces/invitations/route.test.ts | 3 -- .../mention/mention-chip.test.tsx | 3 -- .../workspace/[workspaceId]/layout.test.tsx | 10 ++--- 92 files changed, 310 insertions(+), 735 deletions(-) diff --git a/apps/sim/app/account/settings/billing/credit-usage/page.test.ts b/apps/sim/app/account/settings/billing/credit-usage/page.test.ts index cfa256daba4..0c2781bf469 100644 --- a/apps/sim/app/account/settings/billing/credit-usage/page.test.ts +++ b/apps/sim/app/account/settings/billing/credit-usage/page.test.ts @@ -1,30 +1,21 @@ /** * @vitest-environment node */ +import { authMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { - mockCreditUsageLoading, - mockGetPersonalSubscription, - mockGetSession, - mockIsEnterprise, - mockRedirect, -} = vi.hoisted(() => ({ - mockCreditUsageLoading: vi.fn(() => null), - mockGetPersonalSubscription: vi.fn(), - mockGetSession: vi.fn(), - mockIsEnterprise: vi.fn(), - mockRedirect: vi.fn(), -})) +const { mockCreditUsageLoading, mockGetPersonalSubscription, mockIsEnterprise, mockRedirect } = + vi.hoisted(() => ({ + mockCreditUsageLoading: vi.fn(() => null), + mockGetPersonalSubscription: vi.fn(), + mockIsEnterprise: vi.fn(), + mockRedirect: vi.fn(), + })) vi.mock('next/navigation', () => ({ redirect: mockRedirect, })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - vi.mock('@/lib/billing/core/plan', () => ({ getHighestPriorityPersonalSubscription: mockGetPersonalSubscription, })) @@ -44,6 +35,8 @@ vi.mock('@/app/workspace/[workspaceId]/settings/billing/credit-usage/loading', ( import AccountCreditUsagePage from '@/app/account/settings/billing/credit-usage/page' +const mockGetSession = authMockFns.mockGetSession + describe('AccountCreditUsagePage', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/apps/sim/app/api/audit-logs/export/route.test.ts b/apps/sim/app/api/audit-logs/export/route.test.ts index 712d8db9e81..367c04cb86f 100644 --- a/apps/sim/app/api/audit-logs/export/route.test.ts +++ b/apps/sim/app/api/audit-logs/export/route.test.ts @@ -1,18 +1,16 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { authMockFns, createMockRequest } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { - mockGetSession, mockValidateEnterpriseAuditAccess, mockBuildOrgScopeCondition, mockGetOrgWorkspaceIds, mockQueryAuditLogs, mockBuildFilterConditions, } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), mockValidateEnterpriseAuditAccess: vi.fn(), mockBuildOrgScopeCondition: vi.fn(), mockGetOrgWorkspaceIds: vi.fn(), @@ -20,11 +18,6 @@ const { mockBuildFilterConditions: vi.fn(), })) -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, -})) - vi.mock('@/app/api/v1/audit-logs/auth', () => ({ validateEnterpriseAuditAccess: mockValidateEnterpriseAuditAccess, })) @@ -38,6 +31,8 @@ vi.mock('@/app/api/v1/audit-logs/query', () => ({ import { GET } from '@/app/api/audit-logs/export/route' +const mockGetSession = authMockFns.mockGetSession + const ORG_ID = 'org-1' const MEMBER_IDS = ['admin-1'] const SCOPE_SENTINEL = { type: 'org-scope-sentinel' } diff --git a/apps/sim/app/api/auth/forget-password/route.test.ts b/apps/sim/app/api/auth/forget-password/route.test.ts index e7dc5612554..7b43e1fbc5a 100644 --- a/apps/sim/app/api/auth/forget-password/route.test.ts +++ b/apps/sim/app/api/auth/forget-password/route.test.ts @@ -3,8 +3,8 @@ * * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { createMockRequest, resetEnvMock, setEnv } from '@sim/testing' +import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const { mockRequestPasswordReset, mockLogger } = vi.hoisted(() => { const logger = { @@ -22,9 +22,6 @@ const { mockRequestPasswordReset, mockLogger } = vi.hoisted(() => { } }) -vi.mock('@/lib/core/utils/urls', () => ({ - getBaseUrl: vi.fn(() => 'https://app.example.com'), -})) vi.mock('@/lib/auth', () => ({ auth: { api: { @@ -43,9 +40,14 @@ import { POST } from '@/app/api/auth/forget-password/route' describe('Forget Password API Route', () => { beforeEach(() => { vi.clearAllMocks() + setEnv({ NEXT_PUBLIC_APP_URL: 'https://app.example.com' }) mockRequestPasswordReset.mockResolvedValue(undefined) }) + afterAll(() => { + resetEnvMock() + }) + afterEach(() => { vi.clearAllMocks() }) diff --git a/apps/sim/app/api/auth/oauth/connections/route.test.ts b/apps/sim/app/api/auth/oauth/connections/route.test.ts index e5e5d742711..593079aa20c 100644 --- a/apps/sim/app/api/auth/oauth/connections/route.test.ts +++ b/apps/sim/app/api/auth/oauth/connections/route.test.ts @@ -25,10 +25,6 @@ vi.mock('@sim/db', () => ({ eq: mockEq, })) -vi.mock('drizzle-orm', () => ({ - eq: mockEq, -})) - vi.mock('jose', () => ({ decodeJwt: mockDecodeJwt, })) diff --git a/apps/sim/app/api/auth/oauth/disconnect/route.test.ts b/apps/sim/app/api/auth/oauth/disconnect/route.test.ts index 97215a0d923..757ea76c9df 100644 --- a/apps/sim/app/api/auth/oauth/disconnect/route.test.ts +++ b/apps/sim/app/api/auth/oauth/disconnect/route.test.ts @@ -7,14 +7,11 @@ import { auditMock, authMockFns, createMockRequest, - dbChainMock, dbChainMockFns, resetDbChainMock, } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@sim/audit', () => auditMock) import { POST } from '@/app/api/auth/oauth/disconnect/route' diff --git a/apps/sim/app/api/auth/oauth/utils.test.ts b/apps/sim/app/api/auth/oauth/utils.test.ts index 43ea672f9d1..4a22a97b8fc 100644 --- a/apps/sim/app/api/auth/oauth/utils.test.ts +++ b/apps/sim/app/api/auth/oauth/utils.test.ts @@ -4,7 +4,7 @@ * @vitest-environment node */ -import { redisConfigMock, redisConfigMockFns } from '@sim/testing' +import { redisConfigMockFns } from '@sim/testing' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' vi.mock('@/lib/oauth/oauth', () => ({ @@ -12,8 +12,6 @@ vi.mock('@/lib/oauth/oauth', () => ({ OAUTH_PROVIDERS: {}, })) -vi.mock('@/lib/core/config/redis', () => redisConfigMock) - const { mockDecryptSecret } = vi.hoisted(() => ({ mockDecryptSecret: vi.fn() })) vi.mock('@/lib/core/security/encryption', () => ({ decryptSecret: mockDecryptSecret, diff --git a/apps/sim/app/api/auth/oauth2/authorize/route.test.ts b/apps/sim/app/api/auth/oauth2/authorize/route.test.ts index 96b88ccc293..8b4abab40b0 100644 --- a/apps/sim/app/api/auth/oauth2/authorize/route.test.ts +++ b/apps/sim/app/api/auth/oauth2/authorize/route.test.ts @@ -3,7 +3,6 @@ */ import { createMockRequest, - dbChainMock, dbChainMockFns, resetDbChainMock, resetEnvMock, @@ -23,8 +22,6 @@ const { mockGetCredentialActorContext: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/auth/auth', () => ({ auth: { api: { oAuth2LinkAccount: mockOAuth2LinkAccount } }, getSession: mockGetSession, diff --git a/apps/sim/app/api/auth/sso/register/route.test.ts b/apps/sim/app/api/auth/sso/register/route.test.ts index e3539b969e5..67b4beb8b13 100644 --- a/apps/sim/app/api/auth/sso/register/route.test.ts +++ b/apps/sim/app/api/auth/sso/register/route.test.ts @@ -2,13 +2,14 @@ * @vitest-environment node */ import { - createEnvMock, createMockRequest, dbChainMock, dbChainMockFns, queueTableRows, resetDbChainMock, + resetEnvMock, schemaMock, + setEnv, } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' @@ -64,8 +65,6 @@ vi.mock('@/lib/core/security/input-validation.server', () => ({ secureFetchWithPinnedIP: mockSecureFetchWithPinnedIP, })) -vi.mock('@/lib/core/config/env', () => createEnvMock({ SSO_ENABLED: 'true' })) - import { POST } from '@/app/api/auth/sso/register/route' const OIDC_BODY = { @@ -89,6 +88,7 @@ describe('POST /api/auth/sso/register', () => { beforeEach(() => { vi.clearAllMocks() resetDbChainMock() + setEnv({ SSO_ENABLED: 'true' }) mockGetSession.mockResolvedValue({ user: { id: 'u1' } }) mockHasSSOAccess.mockResolvedValue(true) mockValidateUrlWithDNS.mockResolvedValue({ isValid: true, resolvedIP: '1.2.3.4' }) @@ -98,6 +98,7 @@ describe('POST /api/auth/sso/register', () => { afterAll(() => { resetDbChainMock() + resetEnvMock() }) it('rejects callers without an Enterprise plan', async () => { diff --git a/apps/sim/app/api/billing/invoices/route.test.ts b/apps/sim/app/api/billing/invoices/route.test.ts index 58a95c888ca..db1aa12a181 100644 --- a/apps/sim/app/api/billing/invoices/route.test.ts +++ b/apps/sim/app/api/billing/invoices/route.test.ts @@ -1,29 +1,23 @@ /** * @vitest-environment node */ -import { createMockRequest, dbChainMock, dbChainMockFns } from '@sim/testing' +import { authMockFns, createMockRequest, dbChainMockFns } from '@sim/testing' import { generateShortId } from '@sim/utils/id' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetSession, mockGetStripeClient, mockStripeInvoicesList } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), +const { mockGetStripeClient, mockStripeInvoicesList } = vi.hoisted(() => ({ mockGetStripeClient: vi.fn(), mockStripeInvoicesList: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, -})) - vi.mock('@/lib/billing/stripe-client', () => ({ getStripeClient: mockGetStripeClient, })) import { GET } from '@/app/api/billing/invoices/route' +const mockGetSession = authMockFns.mockGetSession + function makeInvoice(overrides: Record = {}) { return { id: `in_${generateShortId()}`, diff --git a/apps/sim/app/api/billing/route.test.ts b/apps/sim/app/api/billing/route.test.ts index ee339be12c8..68d52a9c13e 100644 --- a/apps/sim/app/api/billing/route.test.ts +++ b/apps/sim/app/api/billing/route.test.ts @@ -1,7 +1,7 @@ /** * @vitest-environment node */ -import { createMockRequest, dbChainMock, dbChainMockFns } from '@sim/testing' +import { authMockFns, createMockRequest, dbChainMock, dbChainMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -9,24 +9,15 @@ const { mockGetOrganizationBillingData, mockGetOrganizationSubscription, mockGetPersonalBillingSummary, - mockGetSession, mockResolveBillingInterval, } = vi.hoisted(() => ({ mockGetCreditBalanceForEntity: vi.fn(), mockGetOrganizationBillingData: vi.fn(), mockGetOrganizationSubscription: vi.fn(), mockGetPersonalBillingSummary: vi.fn(), - mockGetSession: vi.fn(), mockResolveBillingInterval: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, -})) - vi.mock('@/lib/billing/core/billing', () => ({ getOrganizationSubscription: mockGetOrganizationSubscription, getPersonalBillingSummary: mockGetPersonalBillingSummary, @@ -46,6 +37,8 @@ vi.mock('@/lib/billing/credits/balance', () => ({ import { GET } from '@/app/api/billing/route' +const mockGetSession = authMockFns.mockGetSession + const PERSONAL_SUMMARY = { type: 'individual', plan: 'pro_6000', diff --git a/apps/sim/app/api/billing/switch-plan/route.test.ts b/apps/sim/app/api/billing/switch-plan/route.test.ts index 377907a9f8e..6b1b4d7a814 100644 --- a/apps/sim/app/api/billing/switch-plan/route.test.ts +++ b/apps/sim/app/api/billing/switch-plan/route.test.ts @@ -1,28 +1,21 @@ /** * @vitest-environment node */ -import { createMockRequest, resetEnvFlagsMock, setEnvFlags } from '@sim/testing' +import { authMockFns, createMockRequest, resetEnvFlagsMock, setEnvFlags } from '@sim/testing' import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockCanManageWorkspaceBilling, mockGetEffectiveBillingStatus, mockGetOrganizationSubscription, - mockGetSession, mockGetWorkspaceHostContextForViewer, } = vi.hoisted(() => ({ mockCanManageWorkspaceBilling: vi.fn(), mockGetEffectiveBillingStatus: vi.fn(), mockGetOrganizationSubscription: vi.fn(), - mockGetSession: vi.fn(), mockGetWorkspaceHostContextForViewer: vi.fn(), })) -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, -})) - vi.mock('@/lib/billing/core/access', () => ({ getEffectiveBillingStatus: mockGetEffectiveBillingStatus, })) @@ -58,6 +51,8 @@ vi.mock('@/lib/workspaces/host-context', () => ({ import { POST } from '@/app/api/billing/switch-plan/route' +const mockGetSession = authMockFns.mockGetSession + beforeAll(() => { setEnvFlags({ isBillingEnabled: true }) }) diff --git a/apps/sim/app/api/blocks/visibility/route.test.ts b/apps/sim/app/api/blocks/visibility/route.test.ts index 1a59df82aa5..3d14370168a 100644 --- a/apps/sim/app/api/blocks/visibility/route.test.ts +++ b/apps/sim/app/api/blocks/visibility/route.test.ts @@ -1,21 +1,17 @@ /** * @vitest-environment node */ +import { authMockFns } from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetSession, mockCheckWorkspaceAccess, mockIsPlatformAdmin, mockGetBlockVisibility } = - vi.hoisted(() => ({ - mockGetSession: vi.fn(), +const { mockCheckWorkspaceAccess, mockIsPlatformAdmin, mockGetBlockVisibility } = vi.hoisted( + () => ({ mockCheckWorkspaceAccess: vi.fn(), mockIsPlatformAdmin: vi.fn(), mockGetBlockVisibility: vi.fn(), - })) - -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, -})) + }) +) vi.mock('@/lib/workspaces/permissions/utils', () => ({ checkWorkspaceAccess: mockCheckWorkspaceAccess, @@ -31,6 +27,8 @@ vi.mock('@/lib/core/config/block-visibility', () => ({ import { GET } from '@/app/api/blocks/visibility/route' +const mockGetSession = authMockFns.mockGetSession + const WORKSPACE_ID = '11111111-2222-4333-8444-555555555555' function request(workspaceId = WORKSPACE_ID) { diff --git a/apps/sim/app/api/chat/[identifier]/otp/route.test.ts b/apps/sim/app/api/chat/[identifier]/otp/route.test.ts index b67e0942c4c..1c60db08fa3 100644 --- a/apps/sim/app/api/chat/[identifier]/otp/route.test.ts +++ b/apps/sim/app/api/chat/[identifier]/otp/route.test.ts @@ -4,14 +4,15 @@ * @vitest-environment node */ import { - dbChainMock, dbChainMockFns, + envMockFns, queueTableRows, - redisConfigMock, redisConfigMockFns, requestUtilsMockFns, resetDbChainMock, + resetEnvMock, schemaMock, + setEnv, workflowsApiUtilsMock, workflowsApiUtilsMockFns, } from '@sim/testing' @@ -30,7 +31,6 @@ const { mockSetChatAuthCookie, mockGetStorageMethod, mockZodParse, - mockGetEnv, } = vi.hoisted(() => { const mockRedisSet = vi.fn() const mockRedisGet = vi.fn() @@ -49,7 +49,6 @@ const { const mockSetChatAuthCookie = vi.fn() const mockGetStorageMethod = vi.fn() const mockZodParse = vi.fn() - const mockGetEnv = vi.fn() return { mockRedisSet, @@ -63,18 +62,14 @@ const { mockSetChatAuthCookie, mockGetStorageMethod, mockZodParse, - mockGetEnv, } }) const mockGetRedisClient = redisConfigMockFns.mockGetRedisClient +const mockGetEnv = envMockFns.getEnv const mockCreateSuccessResponse = workflowsApiUtilsMockFns.mockCreateSuccessResponse const mockCreateErrorResponse = workflowsApiUtilsMockFns.mockCreateErrorResponse -vi.mock('@/lib/core/config/redis', () => redisConfigMock) - -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/core/storage', () => ({ getStorageMethod: mockGetStorageMethod, })) @@ -115,16 +110,6 @@ vi.mock('@/app/api/chat/utils', () => ({ vi.mock('@/app/api/workflows/utils', () => workflowsApiUtilsMock) -vi.mock('@/lib/core/config/env', () => ({ - env: { - NEXT_PUBLIC_APP_URL: 'http://localhost:3000', - NODE_ENV: 'test', - }, - getEnv: mockGetEnv, - isTruthy: vi.fn().mockReturnValue(false), - isFalsy: vi.fn().mockReturnValue(true), -})) - vi.mock('zod', () => { class ZodError extends Error { errors: Array<{ message: string }> @@ -226,6 +211,7 @@ describe('Chat OTP API Route', () => { mockZodParse.mockImplementation((data: unknown) => data) + setEnv({ NEXT_PUBLIC_APP_URL: 'http://localhost:3000', NODE_ENV: 'test' }) mockGetEnv.mockReturnValue('http://localhost:3000') }) @@ -235,6 +221,7 @@ describe('Chat OTP API Route', () => { afterAll(() => { resetDbChainMock() + resetEnvMock() }) describe('POST - Store OTP (Redis path)', () => { diff --git a/apps/sim/app/api/chat/manage/[id]/route.test.ts b/apps/sim/app/api/chat/manage/[id]/route.test.ts index f883fdaf6c3..bedfbece4a6 100644 --- a/apps/sim/app/api/chat/manage/[id]/route.test.ts +++ b/apps/sim/app/api/chat/manage/[id]/route.test.ts @@ -6,12 +6,13 @@ import { auditMock, authMockFns, - dbChainMock, dbChainMockFns, encryptionMock, encryptionMockFns, resetDbChainMock, resetEnvFlagsMock, + resetEnvMock, + setEnv, setEnvFlags, workflowsApiUtilsMock, workflowsApiUtilsMockFns, @@ -39,12 +40,8 @@ const mockNotifySocketDeploymentChanged = workflowsOrchestrationMockFns.mockNotifySocketDeploymentChanged vi.mock('@sim/audit', () => auditMock) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@/app/api/workflows/utils', () => workflowsApiUtilsMock) vi.mock('@/lib/core/security/encryption', () => encryptionMock) -vi.mock('@/lib/core/utils/urls', () => ({ - getEmailDomain: vi.fn().mockReturnValue('localhost:3000'), -})) vi.mock('@/app/api/chat/utils', () => ({ checkChatAccess: mockCheckChatAccess, })) @@ -65,9 +62,13 @@ import { ChatDeployAuthNotAllowedError } from '@/ee/access-control/utils/permiss beforeAll(() => { setEnvFlags({ isDev: true }) + setEnv({ NEXT_PUBLIC_APP_URL: 'http://localhost:3000' }) }) -afterAll(resetEnvFlagsMock) +afterAll(() => { + resetEnvFlagsMock() + resetEnvMock() +}) describe('Chat Edit API Route', () => { beforeEach(() => { diff --git a/apps/sim/app/api/chat/route.test.ts b/apps/sim/app/api/chat/route.test.ts index d85f62bd2b4..ab4ebded2cf 100644 --- a/apps/sim/app/api/chat/route.test.ts +++ b/apps/sim/app/api/chat/route.test.ts @@ -5,16 +5,16 @@ */ import { authMockFns, - createEnvMock, - dbChainMock, dbChainMockFns, + resetEnvMock, + setEnv, workflowsApiUtilsMock, workflowsApiUtilsMockFns, workflowsOrchestrationMock, workflowsOrchestrationMockFns, } from '@sim/testing' import { NextRequest } from 'next/server' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockCheckWorkflowAccessForChatCreation, mockValidateChatDeployAuth } = vi.hoisted(() => ({ mockCheckWorkflowAccessForChatCreation: vi.fn(), @@ -25,8 +25,6 @@ const mockCreateSuccessResponse = workflowsApiUtilsMockFns.mockCreateSuccessResp const mockCreateErrorResponse = workflowsApiUtilsMockFns.mockCreateErrorResponse const mockPerformChatDeploy = workflowsOrchestrationMockFns.mockPerformChatDeploy -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/app/api/workflows/utils', () => workflowsApiUtilsMock) vi.mock('@/app/api/chat/utils', () => ({ @@ -45,19 +43,17 @@ vi.mock('@/ee/access-control/utils/permission-check', () => { vi.mock('@/lib/workflows/orchestration', () => workflowsOrchestrationMock) -vi.mock('@/lib/core/config/env', () => - createEnvMock({ - NODE_ENV: 'development', - NEXT_PUBLIC_APP_URL: 'http://localhost:3000', - }) -) - import { GET, POST } from '@/app/api/chat/route' import { ChatDeployAuthNotAllowedError } from '@/ee/access-control/utils/permission-check' describe('Chat API Route', () => { + afterAll(() => { + resetEnvMock() + }) + beforeEach(() => { vi.clearAllMocks() + setEnv({ NODE_ENV: 'development', NEXT_PUBLIC_APP_URL: 'http://localhost:3000' }) mockCreateSuccessResponse.mockImplementation((data) => { return new Response(JSON.stringify(data), { diff --git a/apps/sim/app/api/chat/utils.test.ts b/apps/sim/app/api/chat/utils.test.ts index 4e457c1181d..6c41eeb21cc 100644 --- a/apps/sim/app/api/chat/utils.test.ts +++ b/apps/sim/app/api/chat/utils.test.ts @@ -4,7 +4,7 @@ * @vitest-environment node */ import { - dbChainMock, + authMockFns, encryptionMock, encryptionMockFns, loggingSessionMock, @@ -19,7 +19,6 @@ const { mockValidateAuthToken, mockSetDeploymentAuthCookie, mockIsEmailAllowed, - mockGetSession, mockCheckRateLimitDirect, } = vi.hoisted(() => ({ mockMergeSubblockStateWithValues: vi.fn().mockReturnValue({}), @@ -27,23 +26,15 @@ const { mockValidateAuthToken: vi.fn().mockReturnValue(false), mockSetDeploymentAuthCookie: vi.fn(), mockIsEmailAllowed: vi.fn(), - mockGetSession: vi.fn(), mockCheckRateLimitDirect: vi.fn().mockResolvedValue({ allowed: true }), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/core/rate-limiter', () => ({ RateLimiter: class { checkRateLimitDirect = mockCheckRateLimitDirect }, })) -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, -})) - const mockDecryptSecret = encryptionMockFns.mockDecryptSecret vi.mock('@/lib/logs/execution/logging-session', () => loggingSessionMock) @@ -75,6 +66,8 @@ vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) import { decryptSecret } from '@/lib/core/security/encryption' import { setChatAuthCookie, validateChatAuth } from '@/app/api/chat/utils' +const mockGetSession = authMockFns.mockGetSession + describe('Chat API Utils', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/apps/sim/app/api/copilot/api-keys/route.test.ts b/apps/sim/app/api/copilot/api-keys/route.test.ts index e6c4b6cab60..d5bb8b49a1f 100644 --- a/apps/sim/app/api/copilot/api-keys/route.test.ts +++ b/apps/sim/app/api/copilot/api-keys/route.test.ts @@ -3,9 +3,9 @@ * * @vitest-environment node */ -import { authMockFns, createEnvMock } from '@sim/testing' +import { authMockFns, resetEnvMock, setEnv } from '@sim/testing' import { NextRequest } from 'next/server' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockFetch, mockGetMothershipBaseURL } = vi.hoisted(() => ({ mockFetch: vi.fn(), @@ -23,8 +23,6 @@ vi.mock('@/lib/copilot/server/agent-url', () => ({ getMothershipBaseURL: mockGetMothershipBaseURL, })) -vi.mock('@/lib/core/config/env', () => createEnvMock({ COPILOT_API_KEY: 'test-api-key' })) - import { DELETE, GET } from '@/app/api/copilot/api-keys/route' // `fetchGo` reads `response.status` and `response.headers.get('content-length')` @@ -46,10 +44,15 @@ function buildMockResponse(init: { describe('Copilot API Keys API Route', () => { beforeEach(() => { vi.clearAllMocks() + setEnv({ COPILOT_API_KEY: 'test-api-key' }) mockGetMothershipBaseURL.mockResolvedValue('https://agent.sim.example.com') global.fetch = mockFetch }) + afterAll(() => { + resetEnvMock() + }) + describe('GET', () => { it('should return 401 when user is not authenticated', async () => { authMockFns.mockGetSession.mockResolvedValue(null) diff --git a/apps/sim/app/api/copilot/chat/delete/route.test.ts b/apps/sim/app/api/copilot/chat/delete/route.test.ts index df25c952463..6783fdbd265 100644 --- a/apps/sim/app/api/copilot/chat/delete/route.test.ts +++ b/apps/sim/app/api/copilot/chat/delete/route.test.ts @@ -3,7 +3,7 @@ * * @vitest-environment node */ -import { authMockFns, dbChainMock, dbChainMockFns } from '@sim/testing' +import { authMockFns, dbChainMockFns } from '@sim/testing' import { NextRequest } from 'next/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' @@ -19,8 +19,6 @@ const { mockGetAccessibleCopilotChatAuth: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/copilot/chat/lifecycle', () => ({ getAccessibleCopilotChat: mockGetAccessibleCopilotChat, getAccessibleCopilotChatAuth: mockGetAccessibleCopilotChatAuth, diff --git a/apps/sim/app/api/copilot/chat/stop/route.test.ts b/apps/sim/app/api/copilot/chat/stop/route.test.ts index e734cd9cde4..7c35ca8d355 100644 --- a/apps/sim/app/api/copilot/chat/stop/route.test.ts +++ b/apps/sim/app/api/copilot/chat/stop/route.test.ts @@ -1,12 +1,10 @@ /** * @vitest-environment node */ -import { authMockFns, dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { authMockFns, dbChainMockFns, resetDbChainMock } from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) - const { mockAppendCopilotChatMessages, mockPublishStatusChanged } = vi.hoisted(() => ({ mockAppendCopilotChatMessages: vi.fn(), mockPublishStatusChanged: vi.fn(), diff --git a/apps/sim/app/api/copilot/checkpoints/revert/route.test.ts b/apps/sim/app/api/copilot/checkpoints/revert/route.test.ts index 4918057f055..785718e9ba2 100644 --- a/apps/sim/app/api/copilot/checkpoints/revert/route.test.ts +++ b/apps/sim/app/api/copilot/checkpoints/revert/route.test.ts @@ -8,7 +8,9 @@ import { dbChainMockFns, queueTableRows, resetDbChainMock, + resetEnvMock, schemaMock, + setEnv, workflowAuthzMockFns, workflowsUtilsMock, } from '@sim/testing' @@ -19,13 +21,6 @@ const { mockGetAccessibleCopilotChat } = vi.hoisted(() => ({ mockGetAccessibleCopilotChat: vi.fn(), })) -vi.mock('@/lib/core/utils/urls', () => ({ - getBaseUrl: vi.fn(() => 'http://localhost:3000'), - getInternalApiBaseUrl: vi.fn(() => 'http://localhost:3000'), - getBaseDomain: vi.fn(() => 'localhost:3000'), - getEmailDomain: vi.fn(() => 'localhost:3000'), -})) - vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) vi.mock('@/lib/copilot/chat/lifecycle', () => ({ @@ -39,6 +34,7 @@ describe('Copilot Checkpoints Revert API Route', () => { beforeEach(() => { vi.clearAllMocks() resetDbChainMock() + setEnv({ NEXT_PUBLIC_APP_URL: 'http://localhost:3000' }) authMockFns.mockGetSession.mockResolvedValue(null) @@ -79,6 +75,7 @@ describe('Copilot Checkpoints Revert API Route', () => { afterAll(() => { resetDbChainMock() + resetEnvMock() }) /** Helper to set authenticated state */ diff --git a/apps/sim/app/api/copilot/feedback/route.test.ts b/apps/sim/app/api/copilot/feedback/route.test.ts index b1121ee4b8c..910bbed28a2 100644 --- a/apps/sim/app/api/copilot/feedback/route.test.ts +++ b/apps/sim/app/api/copilot/feedback/route.test.ts @@ -3,18 +3,10 @@ * * @vitest-environment node */ -import { - copilotHttpMock, - copilotHttpMockFns, - dbChainMock, - dbChainMockFns, - resetDbChainMock, -} from '@sim/testing' +import { copilotHttpMock, copilotHttpMockFns, dbChainMockFns, resetDbChainMock } from '@sim/testing' import { NextRequest } from 'next/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/copilot/request/http', () => copilotHttpMock) import { GET, POST } from '@/app/api/copilot/feedback/route' diff --git a/apps/sim/app/api/cron/renew-subscriptions/route.test.ts b/apps/sim/app/api/cron/renew-subscriptions/route.test.ts index 1340ec486d7..6bdd19602ac 100644 --- a/apps/sim/app/api/cron/renew-subscriptions/route.test.ts +++ b/apps/sim/app/api/cron/renew-subscriptions/route.test.ts @@ -6,9 +6,7 @@ import { authOAuthUtilsMock, createMockRequest, - dbChainMock, dbChainMockFns, - redisConfigMock, redisConfigMockFns, resetDbChainMock, } from '@sim/testing' @@ -23,8 +21,6 @@ vi.mock('@/lib/auth/internal', () => ({ verifyCronAuth: mockVerifyCronAuth, })) -vi.mock('@/lib/core/config/redis', () => redisConfigMock) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@/app/api/auth/oauth/utils', () => authOAuthUtilsMock) import { GET } from './route' diff --git a/apps/sim/app/api/custom-blocks/[id]/usages/route.test.ts b/apps/sim/app/api/custom-blocks/[id]/usages/route.test.ts index 836f7960969..458e5f220b7 100644 --- a/apps/sim/app/api/custom-blocks/[id]/usages/route.test.ts +++ b/apps/sim/app/api/custom-blocks/[id]/usages/route.test.ts @@ -1,22 +1,16 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { authMockFns, createMockRequest } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetSession, mockIsFeatureEnabled, mockHasWorkspaceAdminAccess, mockOperations } = - vi.hoisted(() => ({ - mockGetSession: vi.fn(), - mockIsFeatureEnabled: vi.fn(), - mockHasWorkspaceAdminAccess: vi.fn(), - mockOperations: { - getCustomBlockManageContext: vi.fn(), - getCustomBlockUsageCounts: vi.fn(), - }, - })) - -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, +const { mockIsFeatureEnabled, mockHasWorkspaceAdminAccess, mockOperations } = vi.hoisted(() => ({ + mockIsFeatureEnabled: vi.fn(), + mockHasWorkspaceAdminAccess: vi.fn(), + mockOperations: { + getCustomBlockManageContext: vi.fn(), + getCustomBlockUsageCounts: vi.fn(), + }, })) vi.mock('@/lib/core/config/feature-flags', () => ({ @@ -31,6 +25,8 @@ vi.mock('@/lib/workflows/custom-blocks/operations', () => mockOperations) import { GET } from '@/app/api/custom-blocks/[id]/usages/route' +const mockGetSession = authMockFns.mockGetSession + const MANAGE_CONTEXT = { organizationId: 'org-1', sourceWorkspaceId: 'ws-1', diff --git a/apps/sim/app/api/files/authorization.test.ts b/apps/sim/app/api/files/authorization.test.ts index e894bb387a0..8cce46882fc 100644 --- a/apps/sim/app/api/files/authorization.test.ts +++ b/apps/sim/app/api/files/authorization.test.ts @@ -9,7 +9,7 @@ * * @vitest-environment node */ -import { dbChainMock, dbChainMockFns } from '@sim/testing' +import { dbChainMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { mockGetFileMetadataByKey, mockGetUserEntityPermissions, mockGetFileMetadata } = vi.hoisted( @@ -20,8 +20,6 @@ const { mockGetFileMetadataByKey, mockGetUserEntityPermissions, mockGetFileMetad }) ) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/uploads', () => ({ getFileMetadata: mockGetFileMetadata, })) diff --git a/apps/sim/app/api/files/delete/route.test.ts b/apps/sim/app/api/files/delete/route.test.ts index d843b573651..7dc868d0767 100644 --- a/apps/sim/app/api/files/delete/route.test.ts +++ b/apps/sim/app/api/files/delete/route.test.ts @@ -23,31 +23,6 @@ const mocks = vi.hoisted(() => { } }) -vi.mock('drizzle-orm', () => ({ - and: vi.fn((...conditions: unknown[]) => ({ conditions, type: 'and' })), - eq: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'eq' })), - or: vi.fn((...conditions: unknown[]) => ({ type: 'or', conditions })), - gte: vi.fn((field: unknown, value: unknown) => ({ type: 'gte', field, value })), - lte: vi.fn((field: unknown, value: unknown) => ({ type: 'lte', field, value })), - gt: vi.fn((field: unknown, value: unknown) => ({ type: 'gt', field, value })), - lt: vi.fn((field: unknown, value: unknown) => ({ type: 'lt', field, value })), - ne: vi.fn((field: unknown, value: unknown) => ({ type: 'ne', field, value })), - asc: vi.fn((field: unknown) => ({ field, type: 'asc' })), - desc: vi.fn((field: unknown) => ({ field, type: 'desc' })), - isNull: vi.fn((field: unknown) => ({ field, type: 'isNull' })), - isNotNull: vi.fn((field: unknown) => ({ field, type: 'isNotNull' })), - inArray: vi.fn((field: unknown, values: unknown) => ({ field, values, type: 'inArray' })), - notInArray: vi.fn((field: unknown, values: unknown) => ({ field, values, type: 'notInArray' })), - like: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'like' })), - ilike: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'ilike' })), - count: vi.fn((field: unknown) => ({ field, type: 'count' })), - sum: vi.fn((field: unknown) => ({ field, type: 'sum' })), - avg: vi.fn((field: unknown) => ({ field, type: 'avg' })), - min: vi.fn((field: unknown) => ({ field, type: 'min' })), - max: vi.fn((field: unknown) => ({ field, type: 'max' })), - sql: vi.fn((strings: unknown, ...values: unknown[]) => ({ type: 'sql', sql: strings, values })), -})) - vi.mock('@sim/utils/id', () => ({ generateId: vi.fn(() => 'test-uuid'), generateShortId: vi.fn(() => 'mock-short-id'), diff --git a/apps/sim/app/api/files/upload/route.test.ts b/apps/sim/app/api/files/upload/route.test.ts index 357f5014b4a..034efb5eb70 100644 --- a/apps/sim/app/api/files/upload/route.test.ts +++ b/apps/sim/app/api/files/upload/route.test.ts @@ -48,31 +48,6 @@ const mocks = vi.hoisted(() => { } }) -vi.mock('drizzle-orm', () => ({ - and: vi.fn((...conditions: unknown[]) => ({ conditions, type: 'and' })), - eq: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'eq' })), - or: vi.fn((...conditions: unknown[]) => ({ type: 'or', conditions })), - gte: vi.fn((field: unknown, value: unknown) => ({ type: 'gte', field, value })), - lte: vi.fn((field: unknown, value: unknown) => ({ type: 'lte', field, value })), - gt: vi.fn((field: unknown, value: unknown) => ({ type: 'gt', field, value })), - lt: vi.fn((field: unknown, value: unknown) => ({ type: 'lt', field, value })), - ne: vi.fn((field: unknown, value: unknown) => ({ type: 'ne', field, value })), - asc: vi.fn((field: unknown) => ({ field, type: 'asc' })), - desc: vi.fn((field: unknown) => ({ field, type: 'desc' })), - isNull: vi.fn((field: unknown) => ({ field, type: 'isNull' })), - isNotNull: vi.fn((field: unknown) => ({ field, type: 'isNotNull' })), - inArray: vi.fn((field: unknown, values: unknown) => ({ field, values, type: 'inArray' })), - notInArray: vi.fn((field: unknown, values: unknown) => ({ field, values, type: 'notInArray' })), - like: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'like' })), - ilike: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'ilike' })), - count: vi.fn((field: unknown) => ({ field, type: 'count' })), - sum: vi.fn((field: unknown) => ({ field, type: 'sum' })), - avg: vi.fn((field: unknown) => ({ field, type: 'avg' })), - min: vi.fn((field: unknown) => ({ field, type: 'min' })), - max: vi.fn((field: unknown) => ({ field, type: 'max' })), - sql: vi.fn((strings: unknown, ...values: unknown[]) => ({ type: 'sql', sql: strings, values })), -})) - vi.mock('@sim/utils/id', () => ({ generateId: vi.fn(() => 'test-uuid'), generateShortId: vi.fn(() => 'mock-short-id'), diff --git a/apps/sim/app/api/folders/[id]/route.test.ts b/apps/sim/app/api/folders/[id]/route.test.ts index bbe7dfeba63..aed839f8743 100644 --- a/apps/sim/app/api/folders/[id]/route.test.ts +++ b/apps/sim/app/api/folders/[id]/route.test.ts @@ -7,7 +7,6 @@ import { auditMock, authMockFns, createMockRequest, - dbChainMock, dbChainMockFns, type MockUser, permissionsMock, @@ -49,7 +48,6 @@ vi.mock('@sim/logger', () => ({ getRequestContext: () => undefined, })) vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@/lib/workflows/orchestration', () => workflowsOrchestrationMock) vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) diff --git a/apps/sim/app/api/folders/reorder/route.test.ts b/apps/sim/app/api/folders/reorder/route.test.ts index 61b56e6fa50..c396bd5e46d 100644 --- a/apps/sim/app/api/folders/reorder/route.test.ts +++ b/apps/sim/app/api/folders/reorder/route.test.ts @@ -4,7 +4,6 @@ * @vitest-environment node */ import { authMockFns, createMockRequest, permissionsMock, permissionsMockFns } from '@sim/testing' -import { drizzleOrmMock } from '@sim/testing/mocks' import { beforeEach, describe, expect, it, vi } from 'vitest' const { mockLogger } = vi.hoisted(() => ({ @@ -21,12 +20,6 @@ const { mockLogger } = vi.hoisted(() => ({ const mockGetUserEntityPermissions = permissionsMockFns.mockGetUserEntityPermissions -vi.mock('drizzle-orm', () => drizzleOrmMock) -vi.mock('@sim/logger', () => ({ - createLogger: vi.fn().mockReturnValue(mockLogger), - runWithRequestContext: (_ctx: unknown, fn: () => T): T => fn(), - getRequestContext: () => undefined, -})) vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) import { db } from '@sim/db' diff --git a/apps/sim/app/api/folders/route.test.ts b/apps/sim/app/api/folders/route.test.ts index f7eb512da68..8c0210e12b9 100644 --- a/apps/sim/app/api/folders/route.test.ts +++ b/apps/sim/app/api/folders/route.test.ts @@ -11,7 +11,6 @@ import { permissionsMockFns, workflowAuthzMockFns, } from '@sim/testing' -import { drizzleOrmMock } from '@sim/testing/mocks' import { beforeEach, describe, expect, it, vi } from 'vitest' const { mockLogger } = vi.hoisted(() => { @@ -32,10 +31,6 @@ const { mockLogger } = vi.hoisted(() => { const mockGetUserEntityPermissions = permissionsMockFns.mockGetUserEntityPermissions vi.mock('@sim/audit', () => auditMock) -vi.mock('drizzle-orm', () => ({ - ...drizzleOrmMock, - min: vi.fn((field) => ({ type: 'min', field })), -})) vi.mock('@sim/logger', () => ({ createLogger: vi.fn().mockReturnValue(mockLogger), runWithRequestContext: (_ctx: unknown, fn: () => T): T => fn(), diff --git a/apps/sim/app/api/guardrails/mask-batch/route.test.ts b/apps/sim/app/api/guardrails/mask-batch/route.test.ts index 976ec1ec80b..6a37e3b9d1b 100644 --- a/apps/sim/app/api/guardrails/mask-batch/route.test.ts +++ b/apps/sim/app/api/guardrails/mask-batch/route.test.ts @@ -1,17 +1,14 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { createMockRequest, hybridAuthMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockCheckInternalAuth, mockMaskPIIBatch } = vi.hoisted(() => ({ - mockCheckInternalAuth: vi.fn(), +const { mockMaskPIIBatch } = vi.hoisted(() => ({ mockMaskPIIBatch: vi.fn(), })) -vi.mock('@/lib/auth/hybrid', () => ({ - checkInternalAuth: mockCheckInternalAuth, -})) +const mockCheckInternalAuth = hybridAuthMockFns.mockCheckInternalAuth vi.mock('@/lib/guardrails/validate_pii', () => ({ maskPIIBatch: mockMaskPIIBatch, diff --git a/apps/sim/app/api/invitations/[id]/accept/route.test.ts b/apps/sim/app/api/invitations/[id]/accept/route.test.ts index dd6f3833f2b..951b769389d 100644 --- a/apps/sim/app/api/invitations/[id]/accept/route.test.ts +++ b/apps/sim/app/api/invitations/[id]/accept/route.test.ts @@ -1,16 +1,11 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { authMockFns, createMockRequest } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockAcceptInvitation, mockGetSession } = vi.hoisted(() => ({ +const { mockAcceptInvitation } = vi.hoisted(() => ({ mockAcceptInvitation: vi.fn(), - mockGetSession: vi.fn(), -})) - -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, })) vi.mock('@/lib/invitations/core', () => ({ @@ -19,6 +14,8 @@ vi.mock('@/lib/invitations/core', () => ({ import { POST } from '@/app/api/invitations/[id]/accept/route' +const mockGetSession = authMockFns.mockGetSession + function createInvitationRequest() { return createMockRequest( 'POST', diff --git a/apps/sim/app/api/jobs/[jobId]/route.test.ts b/apps/sim/app/api/jobs/[jobId]/route.test.ts index 189e03b1052..5fc7de85435 100644 --- a/apps/sim/app/api/jobs/[jobId]/route.test.ts +++ b/apps/sim/app/api/jobs/[jobId]/route.test.ts @@ -1,13 +1,17 @@ /** * @vitest-environment node */ -import { hybridAuthMockFns, workflowsUtilsMock, workflowsUtilsMockFns } from '@sim/testing' +import { + hybridAuthMockFns, + workflowAuthzMockFns, + workflowsUtilsMock, + workflowsUtilsMockFns, +} from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetJobQueue, mockAuthorizeWorkflow, mockGetJob } = vi.hoisted(() => ({ +const { mockGetJobQueue, mockGetJob } = vi.hoisted(() => ({ mockGetJobQueue: vi.fn(), - mockAuthorizeWorkflow: vi.fn(), mockGetJob: vi.fn(), })) @@ -15,9 +19,7 @@ vi.mock('@/lib/core/async-jobs', () => ({ getJobQueue: mockGetJobQueue, })) -vi.mock('@sim/platform-authz/workflow', () => ({ - authorizeWorkflowByWorkspacePermission: mockAuthorizeWorkflow, -})) +const mockAuthorizeWorkflow = workflowAuthzMockFns.mockAuthorizeWorkflowByWorkspacePermission vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) diff --git a/apps/sim/app/api/knowledge/[id]/connectors/[connectorId]/documents/route.test.ts b/apps/sim/app/api/knowledge/[id]/connectors/[connectorId]/documents/route.test.ts index bcbf905bb27..b31ec4e5ea1 100644 --- a/apps/sim/app/api/knowledge/[id]/connectors/[connectorId]/documents/route.test.ts +++ b/apps/sim/app/api/knowledge/[id]/connectors/[connectorId]/documents/route.test.ts @@ -4,7 +4,6 @@ import { auditMock, createMockRequest, - dbChainMock, dbChainMockFns, hybridAuthMockFns, knowledgeApiUtilsMock, @@ -17,7 +16,6 @@ import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const mockCheckAccess = knowledgeApiUtilsMockFns.mockCheckKnowledgeBaseAccess const mockCheckWriteAccess = knowledgeApiUtilsMockFns.mockCheckKnowledgeBaseWriteAccess -vi.mock('@sim/db', () => dbChainMock) vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock) vi.mock('@sim/audit', () => auditMock) diff --git a/apps/sim/app/api/knowledge/[id]/connectors/[connectorId]/route.test.ts b/apps/sim/app/api/knowledge/[id]/connectors/[connectorId]/route.test.ts index 4784c21943a..bf347078d73 100644 --- a/apps/sim/app/api/knowledge/[id]/connectors/[connectorId]/route.test.ts +++ b/apps/sim/app/api/knowledge/[id]/connectors/[connectorId]/route.test.ts @@ -5,7 +5,6 @@ import { auditMock, authOAuthUtilsMock, createMockRequest, - dbChainMock, dbChainMockFns, hybridAuthMockFns, knowledgeApiUtilsMock, @@ -24,7 +23,6 @@ const { mockHasWorkspaceLiveSyncAccess, mockValidateConfig } = vi.hoisted(() => const mockCheckAccess = knowledgeApiUtilsMockFns.mockCheckKnowledgeBaseAccess const mockCheckWriteAccess = knowledgeApiUtilsMockFns.mockCheckKnowledgeBaseWriteAccess -vi.mock('@sim/db', () => dbChainMock) vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock) vi.mock('@/app/api/auth/oauth/utils', () => authOAuthUtilsMock) vi.mock('@/connectors/registry.server', () => ({ diff --git a/apps/sim/app/api/knowledge/[id]/connectors/[connectorId]/sync/route.test.ts b/apps/sim/app/api/knowledge/[id]/connectors/[connectorId]/sync/route.test.ts index 54ef8f3fc4c..c79c85df58a 100644 --- a/apps/sim/app/api/knowledge/[id]/connectors/[connectorId]/sync/route.test.ts +++ b/apps/sim/app/api/knowledge/[id]/connectors/[connectorId]/sync/route.test.ts @@ -4,7 +4,6 @@ import { auditMock, createMockRequest, - dbChainMock, dbChainMockFns, hybridAuthMockFns, knowledgeApiUtilsMock, @@ -21,7 +20,6 @@ const { mockDispatchSync, mockResolveBillingAttribution } = vi.hoisted(() => ({ const mockCheckWriteAccess = knowledgeApiUtilsMockFns.mockCheckKnowledgeBaseWriteAccess -vi.mock('@sim/db', () => dbChainMock) vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock) vi.mock('@/lib/billing/core/billing-attribution', () => ({ requireBillingAttributionHeader: vi.fn(), diff --git a/apps/sim/app/api/knowledge/[id]/connectors/route.test.ts b/apps/sim/app/api/knowledge/[id]/connectors/route.test.ts index d1f9f852ae4..6087572fb40 100644 --- a/apps/sim/app/api/knowledge/[id]/connectors/route.test.ts +++ b/apps/sim/app/api/knowledge/[id]/connectors/route.test.ts @@ -5,7 +5,6 @@ import { auditMock, authOAuthUtilsMock, createMockRequest, - dbChainMock, dbChainMockFns, hybridAuthMockFns, knowledgeApiUtilsMock, @@ -32,7 +31,6 @@ const { const mockCheckWriteAccess = knowledgeApiUtilsMockFns.mockCheckKnowledgeBaseWriteAccess -vi.mock('@sim/db', () => dbChainMock) vi.mock('@sim/audit', () => auditMock) vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock) vi.mock('@/app/api/auth/oauth/utils', () => authOAuthUtilsMock) diff --git a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.test.ts b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.test.ts index a512d203d43..84bdd9736e5 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.test.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.test.ts @@ -7,14 +7,11 @@ import { auditMock, authMockFns, createMockRequest, - dbChainMock, knowledgeApiUtilsMock, resetDbChainMock, } from '@sim/testing' import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock) vi.mock('@/lib/knowledge/documents/service', () => ({ diff --git a/apps/sim/app/api/knowledge/[id]/documents/route.test.ts b/apps/sim/app/api/knowledge/[id]/documents/route.test.ts index 26ec37a6786..971c4a8f28b 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/route.test.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/route.test.ts @@ -7,14 +7,11 @@ import { auditMock, authMockFns, createMockRequest, - dbChainMock, knowledgeApiUtilsMock, resetDbChainMock, } from '@sim/testing' import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock) vi.mock('@/lib/knowledge/documents/service', () => ({ diff --git a/apps/sim/app/api/knowledge/[id]/documents/upsert/route.test.ts b/apps/sim/app/api/knowledge/[id]/documents/upsert/route.test.ts index e48ef65e164..ddd9c80cb7f 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/upsert/route.test.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/upsert/route.test.ts @@ -6,16 +6,12 @@ import { auditMock, createMockRequest, - dbChainMock, - hybridAuthMock, hybridAuthMockFns, knowledgeApiUtilsMock, resetDbChainMock, } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) -vi.mock('@/lib/auth/hybrid', () => hybridAuthMock) vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock) vi.mock('@sim/audit', () => auditMock) diff --git a/apps/sim/app/api/knowledge/[id]/route.test.ts b/apps/sim/app/api/knowledge/[id]/route.test.ts index c146ee9a13c..882a1df6f0d 100644 --- a/apps/sim/app/api/knowledge/[id]/route.test.ts +++ b/apps/sim/app/api/knowledge/[id]/route.test.ts @@ -7,14 +7,11 @@ import { auditMock, authMockFns, createMockRequest, - dbChainMock, knowledgeApiUtilsMock, resetDbChainMock, } from '@sim/testing' import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@sim/audit', () => auditMock) vi.mock('@/lib/knowledge/service', async (importOriginal) => { diff --git a/apps/sim/app/api/knowledge/route.test.ts b/apps/sim/app/api/knowledge/route.test.ts index 499efa0d812..3c8f8083b79 100644 --- a/apps/sim/app/api/knowledge/route.test.ts +++ b/apps/sim/app/api/knowledge/route.test.ts @@ -7,7 +7,6 @@ import { auditMock, authMockFns, createMockRequest, - dbChainMock, dbChainMockFns, permissionsMock, permissionsMockFns, @@ -15,8 +14,6 @@ import { } from '@sim/testing' import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@sim/audit', () => auditMock) vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) diff --git a/apps/sim/app/api/knowledge/search/route.test.ts b/apps/sim/app/api/knowledge/search/route.test.ts index b389990c744..88e379dafe7 100644 --- a/apps/sim/app/api/knowledge/search/route.test.ts +++ b/apps/sim/app/api/knowledge/search/route.test.ts @@ -6,14 +6,14 @@ * @vitest-environment node */ import { - createEnvMock, createMockRequest, - dbChainMock, dbChainMockFns, hybridAuthMockFns, knowledgeApiUtilsMock, knowledgeApiUtilsMockFns, resetDbChainMock, + resetEnvMock, + setEnv, workflowAuthzMockFns, workflowsUtilsMock, } from '@sim/testing' @@ -39,24 +39,8 @@ const { const mockCheckKnowledgeBaseAccess = knowledgeApiUtilsMockFns.mockCheckKnowledgeBaseAccess -vi.mock('drizzle-orm', () => ({ - and: vi.fn().mockImplementation((...args) => ({ and: args })), - eq: vi.fn().mockImplementation((a, b) => ({ eq: [a, b] })), - inArray: vi.fn().mockImplementation((field, values) => ({ inArray: [field, values] })), - isNull: vi.fn().mockImplementation((arg) => ({ isNull: arg })), - sql: vi.fn().mockImplementation((strings, ...values) => ({ - sql: strings, - values, - as: vi.fn().mockReturnValue({ sql: strings, values, alias: 'mocked_alias' }), - })), -})) - -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) -vi.mock('@/lib/core/config/env', () => createEnvMock({ OPENAI_API_KEY: 'test-api-key' })) - vi.mock('@/lib/documents/utils', () => ({ retryWithExponentialBackoff: vi.fn().mockImplementation((fn) => fn()), })) @@ -132,6 +116,7 @@ describe('Knowledge Search API Route', () => { beforeEach(() => { vi.clearAllMocks() resetDbChainMock() + setEnv({ OPENAI_API_KEY: 'test-api-key' }) mockHandleTagOnlySearch.mockClear() mockHandleVectorOnlySearch.mockClear() @@ -173,6 +158,7 @@ describe('Knowledge Search API Route', () => { afterAll(() => { resetDbChainMock() + resetEnvMock() }) describe('POST /api/knowledge/search', () => { diff --git a/apps/sim/app/api/knowledge/utils.test.ts b/apps/sim/app/api/knowledge/utils.test.ts index d61e8273ef6..f7a0297ef52 100644 --- a/apps/sim/app/api/knowledge/utils.test.ts +++ b/apps/sim/app/api/knowledge/utils.test.ts @@ -7,7 +7,6 @@ * including access checks, document processing, and embedding generation. */ import { - dbChainMock, dbChainMockFns, defaultMockEnv, queueTableRows, @@ -126,8 +125,6 @@ function createEmbeddingFetchMock() { vi.stubGlobal('fetch', createEmbeddingFetchMock()) -vi.mock('@sim/db', () => dbChainMock) - import { processDocumentAsync } from '@/lib/knowledge/documents/service' import { generateEmbeddings } from '@/lib/knowledge/embeddings' import { diff --git a/apps/sim/app/api/mcp/oauth/callback/route.test.ts b/apps/sim/app/api/mcp/oauth/callback/route.test.ts index 96a7de727fe..1cad45541bd 100644 --- a/apps/sim/app/api/mcp/oauth/callback/route.test.ts +++ b/apps/sim/app/api/mcp/oauth/callback/route.test.ts @@ -3,12 +3,10 @@ */ import { authMockFns, - dbChainMock, dbChainMockFns, mcpOauthMock, mcpOauthMockFns, resetDbChainMock, - schemaMock, } from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' @@ -17,13 +15,6 @@ const { mockDiscoverServerTools } = vi.hoisted(() => ({ mockDiscoverServerTools: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) -vi.mock('@sim/db/schema', () => schemaMock) -vi.mock('drizzle-orm', () => ({ - and: vi.fn(), - eq: vi.fn(), - isNull: vi.fn(), -})) vi.mock('@/lib/mcp/oauth', () => mcpOauthMock) vi.mock('@/lib/mcp/service', () => ({ mcpService: { discoverServerTools: mockDiscoverServerTools }, diff --git a/apps/sim/app/api/mcp/oauth/start/route.test.ts b/apps/sim/app/api/mcp/oauth/start/route.test.ts index 6b0df17c66e..11bbc1af623 100644 --- a/apps/sim/app/api/mcp/oauth/start/route.test.ts +++ b/apps/sim/app/api/mcp/oauth/start/route.test.ts @@ -2,9 +2,7 @@ * @vitest-environment node */ import { - dbChainMock, dbChainMockFns, - hybridAuthMock, hybridAuthMockFns, McpOauthRedirectRequiredMock, mcpOauthMock, @@ -12,19 +10,10 @@ import { permissionsMock, permissionsMockFns, resetDbChainMock, - schemaMock, } from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) -vi.mock('@sim/db/schema', () => schemaMock) -vi.mock('drizzle-orm', () => ({ - and: vi.fn(), - eq: vi.fn(), - isNull: vi.fn(), -})) -vi.mock('@/lib/auth/hybrid', () => hybridAuthMock) vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) vi.mock('@/lib/mcp/oauth', () => mcpOauthMock) diff --git a/apps/sim/app/api/mcp/serve/[serverId]/route.test.ts b/apps/sim/app/api/mcp/serve/[serverId]/route.test.ts index a67cb62ba37..99bf39603b6 100644 --- a/apps/sim/app/api/mcp/serve/[serverId]/route.test.ts +++ b/apps/sim/app/api/mcp/serve/[serverId]/route.test.ts @@ -4,15 +4,16 @@ * @vitest-environment node */ import { - dbChainMock, dbChainMockFns, hybridAuthMockFns, permissionsMock, permissionsMockFns, resetDbChainMock, + resetEnvMock, + setEnv, } from '@sim/testing' import { NextRequest } from 'next/server' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const { mockAssertBillingAttributionSnapshot, @@ -54,27 +55,12 @@ function createBillingAttribution(actorUserId: string, workspaceId: string) { } } -vi.mock('@sim/db', () => dbChainMock) -vi.mock('drizzle-orm', () => ({ - and: vi.fn(), - asc: vi.fn(), - eq: vi.fn(), - gt: vi.fn(), - isNull: vi.fn(), - sql: vi.fn(), -})) - vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) vi.mock('@/lib/auth/internal', () => ({ generateInternalToken: mockGenerateInternalToken, })) -vi.mock('@/lib/core/utils/urls', () => ({ - getBaseUrl: () => 'http://localhost:3000', - getInternalApiBaseUrl: () => 'http://localhost:3000', -})) - vi.mock('@/lib/core/execution-limits', () => ({ getMaxExecutionTimeout: () => 10_000, })) @@ -82,9 +68,14 @@ vi.mock('@/lib/core/execution-limits', () => ({ import { DELETE, GET, POST } from '@/app/api/mcp/serve/[serverId]/route' describe('MCP Serve Route', () => { + afterAll(() => { + resetEnvMock() + }) + beforeEach(() => { vi.clearAllMocks() resetDbChainMock() + setEnv({ NEXT_PUBLIC_APP_URL: 'http://localhost:3000' }) vi.stubGlobal('fetch', fetchMock) mockResolveBillingAttribution.mockImplementation( ({ actorUserId, workspaceId }: { actorUserId: string; workspaceId: string }) => diff --git a/apps/sim/app/api/mcp/servers/[id]/refresh/route.test.ts b/apps/sim/app/api/mcp/servers/[id]/refresh/route.test.ts index a04dd7edf74..0feba60d055 100644 --- a/apps/sim/app/api/mcp/servers/[id]/refresh/route.test.ts +++ b/apps/sim/app/api/mcp/servers/[id]/refresh/route.test.ts @@ -1,7 +1,7 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import type { NextRequest } from 'next/server' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' @@ -10,8 +10,6 @@ const { mockClearCache, mockDiscoverServerTools } = vi.hoisted(() => ({ mockDiscoverServerTools: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/core/utils/with-route-handler', () => ({ withRouteHandler: (handler: unknown) => handler, })) diff --git a/apps/sim/app/api/mcp/servers/route.test.ts b/apps/sim/app/api/mcp/servers/route.test.ts index 2d397432507..beb8187dd41 100644 --- a/apps/sim/app/api/mcp/servers/route.test.ts +++ b/apps/sim/app/api/mcp/servers/route.test.ts @@ -1,7 +1,7 @@ /** * @vitest-environment node */ -import { dbChainMock, resetDbChainMock } from '@sim/testing' +import { resetDbChainMock } from '@sim/testing' import type { NextRequest } from 'next/server' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' @@ -9,8 +9,6 @@ const { mockPerformDeleteMcpServer } = vi.hoisted(() => ({ mockPerformDeleteMcpServer: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/mcp/middleware', () => ({ getParsedBody: () => undefined, withMcpAuth: diff --git a/apps/sim/app/api/mothership/chats/[chatId]/fork/route.test.ts b/apps/sim/app/api/mothership/chats/[chatId]/fork/route.test.ts index e7ab8202a6e..9b4c80e23e9 100644 --- a/apps/sim/app/api/mothership/chats/[chatId]/fork/route.test.ts +++ b/apps/sim/app/api/mothership/chats/[chatId]/fork/route.test.ts @@ -1,7 +1,14 @@ /** * @vitest-environment node */ -import { copilotHttpMock, copilotHttpMockFns, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { + copilotHttpMock, + copilotHttpMockFns, + dbChainMockFns, + resetDbChainMock, + resetEnvMock, + setEnv, +} from '@sim/testing' import { NextRequest } from 'next/server' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' @@ -70,8 +77,6 @@ vi.mock('@/lib/copilot/server/agent-url', () => ({ getMothershipSourceEnvHeaders: vi.fn().mockReturnValue({}), })) -vi.mock('@/lib/core/config/env', () => ({ env: {} })) - vi.mock('@/lib/posthog/server', () => ({ captureServerEvent: mockCaptureServerEvent, })) @@ -136,6 +141,7 @@ describe('POST /api/mothership/chats/[chatId]/fork', () => { beforeEach(() => { vi.clearAllMocks() resetDbChainMock() + setEnv({ COPILOT_API_KEY: undefined }) copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValue({ userId: 'user-1', isAuthenticated: true, @@ -158,6 +164,7 @@ describe('POST /api/mothership/chats/[chatId]/fork', () => { afterAll(() => { resetDbChainMock() + resetEnvMock() }) it('rejects unauthenticated callers', async () => { diff --git a/apps/sim/app/api/organizations/[id]/invitations/route.test.ts b/apps/sim/app/api/organizations/[id]/invitations/route.test.ts index 1066801bef8..e4592e1318f 100644 --- a/apps/sim/app/api/organizations/[id]/invitations/route.test.ts +++ b/apps/sim/app/api/organizations/[id]/invitations/route.test.ts @@ -4,17 +4,15 @@ import { member, organization, permissions, user, workspace } from '@sim/db/schema' import { auditMock, + authMockFns, createMockRequest, createSession, - dbChainMock, - loggerMock, queueTableRows, resetDbChainMock, } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { - mockGetSession, mockValidateInvitationsAllowed, mockValidateSeatAvailability, mockCreatePendingInvitation, @@ -22,7 +20,6 @@ const { mockCancelPendingInvitation, mockGrantWorkspaceAccessDirectly, } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), mockValidateInvitationsAllowed: vi.fn(), mockValidateSeatAvailability: vi.fn(), mockCreatePendingInvitation: vi.fn(), @@ -31,16 +28,8 @@ const { mockGrantWorkspaceAccessDirectly: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - -vi.mock('@sim/logger', () => loggerMock) - vi.mock('@sim/audit', () => auditMock) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - vi.mock('@/lib/billing/validation/seat-management', () => ({ validateBulkInvitations: vi.fn(), validateSeatAvailability: mockValidateSeatAvailability, @@ -75,6 +64,8 @@ vi.mock('@/ee/access-control/utils/permission-check', () => ({ import { POST } from '@/app/api/organizations/[id]/invitations/route' +const mockGetSession = authMockFns.mockGetSession + /** Queues the caller's admin-role check followed by the org-name lookup. */ function queueOwnerAndOrg() { queueTableRows(member, [{ role: 'owner' }]) diff --git a/apps/sim/app/api/organizations/[id]/members/[memberId]/usage-limit/route.test.ts b/apps/sim/app/api/organizations/[id]/members/[memberId]/usage-limit/route.test.ts index 8d8428f78d1..d2898b60821 100644 --- a/apps/sim/app/api/organizations/[id]/members/[memberId]/usage-limit/route.test.ts +++ b/apps/sim/app/api/organizations/[id]/members/[memberId]/usage-limit/route.test.ts @@ -3,6 +3,7 @@ */ import { auditMock, + authMockFns, createMockRequest, createSession, resetEnvFlagsMock, @@ -11,14 +12,12 @@ import { import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { - mockGetSession, mockIsOrganizationOwnerOrAdmin, mockGetOrgMemberUsageLimit, mockGetOrgMemberUsageForCurrentPeriod, mockSetOrgMemberUsageLimit, mockGetOrganizationSubscription, } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), mockIsOrganizationOwnerOrAdmin: vi.fn(), mockGetOrgMemberUsageLimit: vi.fn(), mockGetOrgMemberUsageForCurrentPeriod: vi.fn(), @@ -26,11 +25,6 @@ const { mockGetOrganizationSubscription: vi.fn(), })) -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, -})) - vi.mock('@sim/audit', () => auditMock) vi.mock('@/lib/billing/core/organization', () => ({ @@ -49,6 +43,8 @@ vi.mock('@/lib/billing/core/billing', () => ({ import { GET, PUT } from '@/app/api/organizations/[id]/members/[memberId]/usage-limit/route' +const mockGetSession = authMockFns.mockGetSession + afterAll(resetEnvFlagsMock) function context() { diff --git a/apps/sim/app/api/organizations/[id]/permission-groups/utils.test.ts b/apps/sim/app/api/organizations/[id]/permission-groups/utils.test.ts index 2e89f6db56f..6e762e3bd2a 100644 --- a/apps/sim/app/api/organizations/[id]/permission-groups/utils.test.ts +++ b/apps/sim/app/api/organizations/[id]/permission-groups/utils.test.ts @@ -2,7 +2,7 @@ * @vitest-environment node */ import { permissionGroup, permissionGroupMember } from '@sim/db/schema' -import { dbChainMock, queueTableRows, resetDbChainMock } from '@sim/testing' +import { queueTableRows, resetDbChainMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockIsOrganizationAdminOrOwner, mockIsOrganizationOnEnterprisePlan } = vi.hoisted(() => ({ @@ -18,8 +18,6 @@ vi.mock('@/lib/workspaces/permissions/utils', () => ({ isOrganizationAdminOrOwner: mockIsOrganizationAdminOrOwner, })) -vi.mock('@sim/db', () => dbChainMock) - import { authorizeOrgAccessControl, findAllMembersWorkspaceConflict, diff --git a/apps/sim/app/api/organizations/[id]/roster/route.test.ts b/apps/sim/app/api/organizations/[id]/roster/route.test.ts index 338e7a5e598..ac698fe3606 100644 --- a/apps/sim/app/api/organizations/[id]/roster/route.test.ts +++ b/apps/sim/app/api/organizations/[id]/roster/route.test.ts @@ -9,38 +9,30 @@ import { workspace, } from '@sim/db/schema' import { + authMockFns, createMockRequest, createSession, - dbChainMock, - loggerMock, queueTableRows, resetDbChainMock, } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' -const { mockExpireStaleInvitations, mockGetSession } = vi.hoisted(() => ({ +const { mockExpireStaleInvitations } = vi.hoisted(() => ({ mockExpireStaleInvitations: vi.fn(), - mockGetSession: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - -vi.mock('@sim/logger', () => loggerMock) - vi.mock('@sim/platform-authz/workspace', () => ({ isOrgAdminRole: (role: string | null | undefined) => role === 'owner' || role === 'admin', })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - vi.mock('@/lib/invitations/core', () => ({ expireStalePendingInvitationsForOrganization: mockExpireStaleInvitations, })) import { GET } from '@/app/api/organizations/[id]/roster/route' +const mockGetSession = authMockFns.mockGetSession + const MEMBER_ROWS = [ { memberId: 'member-admin', diff --git a/apps/sim/app/api/organizations/[id]/session-policy/route.test.ts b/apps/sim/app/api/organizations/[id]/session-policy/route.test.ts index 0e1d39adaaa..c8722a51154 100644 --- a/apps/sim/app/api/organizations/[id]/session-policy/route.test.ts +++ b/apps/sim/app/api/organizations/[id]/session-policy/route.test.ts @@ -3,8 +3,8 @@ */ import { member, organization } from '@sim/db/schema' import { + authMockFns, createMockRequest, - dbChainMock, dbChainMockFns, queueTableRows, resetDbChainMock, @@ -13,19 +13,12 @@ import { } from '@sim/testing' import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetSession, mockIsEnterprise, mockEagerClamp, mockRecordAudit } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), +const { mockIsEnterprise, mockEagerClamp, mockRecordAudit } = vi.hoisted(() => ({ mockIsEnterprise: vi.fn(), mockEagerClamp: vi.fn(), mockRecordAudit: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - vi.mock('@/lib/auth/session-policy', () => ({ eagerClampOrgSessions: mockEagerClamp, invalidateSessionPolicyCache: vi.fn(), @@ -49,6 +42,8 @@ vi.mock('@sim/audit', () => ({ import { GET, PUT } from '@/app/api/organizations/[id]/session-policy/route' +const mockGetSession = authMockFns.mockGetSession + const ORG_ID = 'org-1' const routeContext = { params: Promise.resolve({ id: ORG_ID }) } diff --git a/apps/sim/app/api/organizations/route.test.ts b/apps/sim/app/api/organizations/route.test.ts index 04f0146bff6..bbe8e36990e 100644 --- a/apps/sim/app/api/organizations/route.test.ts +++ b/apps/sim/app/api/organizations/route.test.ts @@ -4,23 +4,20 @@ import { member, subscription } from '@sim/db/schema' import { auditMock, + authMockFns, createSession, - dbChainMock, - loggerMock, queueTableRows, resetDbChainMock, } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { - mockGetSession, mockSetActiveOrganizationForCurrentSession, mockCreateOrganizationForTeamPlan, mockEnsureOrganizationForTeamSubscription, mockAttachOwnedWorkspacesToOrganization, WorkspaceOrganizationMembershipConflictError, } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), mockSetActiveOrganizationForCurrentSession: vi.fn().mockResolvedValue(undefined), mockCreateOrganizationForTeamPlan: vi.fn(), mockEnsureOrganizationForTeamSubscription: vi.fn(), @@ -28,16 +25,8 @@ const { WorkspaceOrganizationMembershipConflictError: class WorkspaceOrganizationMembershipConflictError extends Error {}, })) -vi.mock('@sim/db', () => dbChainMock) - -vi.mock('@sim/logger', () => loggerMock) - vi.mock('@sim/audit', () => auditMock) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - vi.mock('@/lib/auth/active-organization', () => ({ setActiveOrganizationForCurrentSession: mockSetActiveOrganizationForCurrentSession, })) @@ -67,6 +56,8 @@ vi.mock('@/lib/workspaces/organization-workspaces', () => ({ import { POST } from '@/app/api/organizations/route' +const mockGetSession = authMockFns.mockGetSession + afterAll(resetDbChainMock) describe('POST /api/organizations', () => { diff --git a/apps/sim/app/api/providers/baseten/models/route.test.ts b/apps/sim/app/api/providers/baseten/models/route.test.ts index 8ada3c427ea..fe53568ed51 100644 --- a/apps/sim/app/api/providers/baseten/models/route.test.ts +++ b/apps/sim/app/api/providers/baseten/models/route.test.ts @@ -1,27 +1,21 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { authMockFns, createMockRequest, resetEnvMock, setEnv } from '@sim/testing' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockFilterBlacklistedModels, mockIsProviderBlacklisted, mockGetBYOKKey, - mockGetSession, mockGetUserEntityPermissions, - mutableEnv, } = vi.hoisted(() => ({ mockFilterBlacklistedModels: vi.fn(), mockIsProviderBlacklisted: vi.fn(), mockGetBYOKKey: vi.fn(), - mockGetSession: vi.fn(), mockGetUserEntityPermissions: vi.fn(), - mutableEnv: { BASETEN_API_KEY: undefined as string | undefined }, })) -vi.mock('@/lib/core/config/env', () => ({ env: mutableEnv })) - vi.mock('@/providers/utils', () => ({ filterBlacklistedModels: mockFilterBlacklistedModels, isProviderBlacklisted: mockIsProviderBlacklisted, @@ -31,16 +25,14 @@ vi.mock('@/lib/api-key/byok', () => ({ getBYOKKey: mockGetBYOKKey, })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - vi.mock('@/lib/workspaces/permissions/utils', () => ({ getUserEntityPermissions: mockGetUserEntityPermissions, })) import { GET } from '@/app/api/providers/baseten/models/route' +const mockGetSession = authMockFns.mockGetSession + const BASETEN_MODELS_URL = 'https://inference.baseten.co/v1/models' function jsonResponse(body: unknown, init: { ok?: boolean; status?: number } = {}): Response { @@ -55,7 +47,7 @@ function jsonResponse(body: unknown, init: { ok?: boolean; status?: number } = { } function setEnvKey(value: string | undefined): void { - mutableEnv.BASETEN_API_KEY = value + setEnv({ BASETEN_API_KEY: value }) } function authHeaderFromLastFetch(mockFetch: ReturnType): unknown { @@ -80,6 +72,10 @@ describe('GET /api/providers/baseten/models', () => { setEnvKey(undefined) }) + afterAll(() => { + resetEnvMock() + }) + it('returns empty models without fetching when the provider is blacklisted', async () => { mockIsProviderBlacklisted.mockReturnValue(true) setEnvKey('env-key') diff --git a/apps/sim/app/api/providers/ollama-cloud/models/route.test.ts b/apps/sim/app/api/providers/ollama-cloud/models/route.test.ts index fd70d1ae8b1..3b563a8f7cb 100644 --- a/apps/sim/app/api/providers/ollama-cloud/models/route.test.ts +++ b/apps/sim/app/api/providers/ollama-cloud/models/route.test.ts @@ -1,21 +1,19 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { authMockFns, createMockRequest } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { mockFilterBlacklistedModels, mockIsProviderBlacklisted, mockGetBYOKKey, - mockGetSession, mockGetUserEntityPermissions, mockFetch, } = vi.hoisted(() => ({ mockFilterBlacklistedModels: vi.fn(), mockIsProviderBlacklisted: vi.fn(), mockGetBYOKKey: vi.fn(), - mockGetSession: vi.fn(), mockGetUserEntityPermissions: vi.fn(), mockFetch: vi.fn(), })) @@ -29,16 +27,14 @@ vi.mock('@/lib/api-key/byok', () => ({ getBYOKKey: mockGetBYOKKey, })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - vi.mock('@/lib/workspaces/permissions/utils', () => ({ getUserEntityPermissions: mockGetUserEntityPermissions, })) import { GET } from '@/app/api/providers/ollama-cloud/models/route' +const mockGetSession = authMockFns.mockGetSession + const OLLAMA_CLOUD_TAGS_URL = 'https://ollama.com/api/tags' const okResponse = (body: unknown) => ({ diff --git a/apps/sim/app/api/providers/together/models/route.test.ts b/apps/sim/app/api/providers/together/models/route.test.ts index ae801bb7c56..b7516070a0e 100644 --- a/apps/sim/app/api/providers/together/models/route.test.ts +++ b/apps/sim/app/api/providers/together/models/route.test.ts @@ -1,25 +1,21 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { authMockFns, createMockRequest, resetEnvMock, setEnv } from '@sim/testing' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockFilterBlacklistedModels, mockIsProviderBlacklisted, mockGetBYOKKey, - mockGetSession, mockGetUserEntityPermissions, mockFetch, - mutableEnv, } = vi.hoisted(() => ({ mockFilterBlacklistedModels: vi.fn(), mockIsProviderBlacklisted: vi.fn(), mockGetBYOKKey: vi.fn(), - mockGetSession: vi.fn(), mockGetUserEntityPermissions: vi.fn(), mockFetch: vi.fn(), - mutableEnv: { TOGETHER_API_KEY: undefined as string | undefined }, })) vi.mock('@/providers/utils', () => ({ @@ -31,20 +27,14 @@ vi.mock('@/lib/api-key/byok', () => ({ getBYOKKey: mockGetBYOKKey, })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - vi.mock('@/lib/workspaces/permissions/utils', () => ({ getUserEntityPermissions: mockGetUserEntityPermissions, })) -vi.mock('@/lib/core/config/env', () => ({ - env: mutableEnv, -})) - import { GET } from '@/app/api/providers/together/models/route' +const mockGetSession = authMockFns.mockGetSession + const TOGETHER_MODELS_URL = 'https://api.together.ai/v1/models' const okResponse = (body: unknown) => ({ @@ -84,7 +74,7 @@ describe('GET /api/providers/together/models', () => { vi.clearAllMocks() vi.stubGlobal('fetch', mockFetch) - mutableEnv.TOGETHER_API_KEY = undefined + setEnv({ TOGETHER_API_KEY: undefined }) mockIsProviderBlacklisted.mockReturnValue(false) mockFilterBlacklistedModels.mockImplementation((models: string[]) => models) mockGetBYOKKey.mockResolvedValue(null) @@ -92,6 +82,10 @@ describe('GET /api/providers/together/models', () => { mockGetUserEntityPermissions.mockResolvedValue(null) }) + afterAll(() => { + resetEnvMock() + }) + it('returns empty models without calling fetch when the provider is blacklisted', async () => { mockIsProviderBlacklisted.mockReturnValue(true) @@ -111,7 +105,7 @@ describe('GET /api/providers/together/models', () => { }) it('fetches with the env key and prefixes each model id with together/', async () => { - mutableEnv.TOGETHER_API_KEY = 'env-together-key' + setEnv({ TOGETHER_API_KEY: 'env-together-key' }) mockFetch.mockResolvedValue( okResponse([{ id: 'moonshotai/Kimi-K2-Instruct' }, { id: 'Qwen/Qwen2.5-72B-Instruct-Turbo' }]) ) @@ -129,7 +123,7 @@ describe('GET /api/providers/together/models', () => { }) it('uses the BYOK key when a workspace, session, and permission are present', async () => { - mutableEnv.TOGETHER_API_KEY = 'env-together-key' + setEnv({ TOGETHER_API_KEY: 'env-together-key' }) mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) mockGetUserEntityPermissions.mockResolvedValue('admin') mockGetBYOKKey.mockResolvedValue({ apiKey: 'byok-together-key' }) @@ -145,7 +139,7 @@ describe('GET /api/providers/together/models', () => { }) it('falls back to the env key when a workspaceId is given but there is no session', async () => { - mutableEnv.TOGETHER_API_KEY = 'env-together-key' + setEnv({ TOGETHER_API_KEY: 'env-together-key' }) mockGetSession.mockResolvedValue(null) mockFetch.mockResolvedValue(okResponse([{ id: 'moonshotai/Kimi-K2-Instruct' }])) @@ -158,7 +152,7 @@ describe('GET /api/providers/together/models', () => { }) it('falls back to the env key when the session user lacks workspace permission', async () => { - mutableEnv.TOGETHER_API_KEY = 'env-together-key' + setEnv({ TOGETHER_API_KEY: 'env-together-key' }) mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) mockGetUserEntityPermissions.mockResolvedValue(null) mockFetch.mockResolvedValue(okResponse([{ id: 'moonshotai/Kimi-K2-Instruct' }])) @@ -172,7 +166,7 @@ describe('GET /api/providers/together/models', () => { }) it('returns empty models when the upstream fetch responds non-ok', async () => { - mutableEnv.TOGETHER_API_KEY = 'env-together-key' + setEnv({ TOGETHER_API_KEY: 'env-together-key' }) mockFetch.mockResolvedValue(errorResponse(401, 'Unauthorized')) const res = await GET(requestWithWorkspace()) @@ -182,7 +176,7 @@ describe('GET /api/providers/together/models', () => { }) it('returns empty models when the upstream fetch throws', async () => { - mutableEnv.TOGETHER_API_KEY = 'env-together-key' + setEnv({ TOGETHER_API_KEY: 'env-together-key' }) mockFetch.mockRejectedValue(new Error('network down')) const res = await GET(requestWithWorkspace()) @@ -201,7 +195,7 @@ describe('GET /api/providers/together/models', () => { }) it('dedupes duplicate model ids from the upstream array', async () => { - mutableEnv.TOGETHER_API_KEY = 'env-together-key' + setEnv({ TOGETHER_API_KEY: 'env-together-key' }) mockFetch.mockResolvedValue( okResponse([ { id: 'moonshotai/Kimi-K2-Instruct' }, @@ -219,7 +213,7 @@ describe('GET /api/providers/together/models', () => { }) it('applies the blacklist filter to the deduped model list', async () => { - mutableEnv.TOGETHER_API_KEY = 'env-together-key' + setEnv({ TOGETHER_API_KEY: 'env-together-key' }) mockFilterBlacklistedModels.mockImplementation((models: string[]) => models.filter((m) => !m.includes('Qwen')) ) @@ -238,7 +232,7 @@ describe('GET /api/providers/together/models', () => { }) it('filters out non-chat model types (image, embedding, rerank, etc.)', async () => { - mutableEnv.TOGETHER_API_KEY = 'env-together-key' + setEnv({ TOGETHER_API_KEY: 'env-together-key' }) mockFetch.mockResolvedValue( okResponse([ { id: 'meta-llama/Llama-3.3-70B-Instruct-Turbo', type: 'chat' }, diff --git a/apps/sim/app/api/resume/poll/route.test.ts b/apps/sim/app/api/resume/poll/route.test.ts index 19808c6f2eb..8e175aeff29 100644 --- a/apps/sim/app/api/resume/poll/route.test.ts +++ b/apps/sim/app/api/resume/poll/route.test.ts @@ -1,12 +1,16 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { + dbChainMockFns, + redisConfigMockFns, + resetDbChainMock, + resetRedisConfigMock, +} from '@sim/testing' import { NextRequest } from 'next/server' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { - acquireLockMock, assertBillingAttributionSnapshotMock, dueRowsLimitMock, enqueueOrStartResumeMock, @@ -17,12 +21,10 @@ const { lteMock, preprocessExecutionMock, processQueuedResumesMock, - releaseLockMock, setAutomaticResumeWaitingMock, setNextResumeAtMock, sqlMock, } = vi.hoisted(() => ({ - acquireLockMock: vi.fn(), assertBillingAttributionSnapshotMock: vi.fn((value: unknown) => value), dueRowsLimitMock: vi.fn(), enqueueOrStartResumeMock: vi.fn(), @@ -33,7 +35,6 @@ const { lteMock: vi.fn(), preprocessExecutionMock: vi.fn(), processQueuedResumesMock: vi.fn(), - releaseLockMock: vi.fn(), setAutomaticResumeWaitingMock: vi.fn(), setNextResumeAtMock: vi.fn(), sqlMock: vi.fn((strings: TemplateStringsArray) => @@ -41,21 +42,8 @@ const { ), })) -vi.mock('@sim/db', () => dbChainMock) - -vi.mock('@sim/db/schema', () => ({ - pausedExecutions: { - id: 'id', - executionId: 'executionId', - workflowId: 'workflowId', - pausePoints: 'pausePoints', - metadata: 'metadata', - executionSnapshot: 'executionSnapshot', - status: 'status', - nextResumeAt: 'nextResumeAt', - automaticResumeRetryCount: 'automaticResumeRetryCount', - }, -})) +const acquireLockMock = redisConfigMockFns.mockAcquireLock +const releaseLockMock = redisConfigMockFns.mockReleaseLock vi.mock('drizzle-orm', () => ({ and: vi.fn(), @@ -74,11 +62,6 @@ vi.mock('@/lib/billing/core/billing-attribution', () => ({ assertBillingAttributionSnapshot: assertBillingAttributionSnapshotMock, })) -vi.mock('@/lib/core/config/redis', () => ({ - acquireLock: acquireLockMock, - releaseLock: releaseLockMock, -})) - vi.mock('@/lib/execution/preprocessing', () => ({ preprocessExecution: preprocessExecutionMock, })) @@ -217,6 +200,7 @@ describe('time-pause resume admission', () => { afterAll(() => { resetDbChainMock() + resetRedisConfigMock() }) it('keeps a timed pause unclaimed, records why, and schedules automatic retry', async () => { diff --git a/apps/sim/app/api/schedules/[id]/route.test.ts b/apps/sim/app/api/schedules/[id]/route.test.ts index df3407461f1..8522120ad8e 100644 --- a/apps/sim/app/api/schedules/[id]/route.test.ts +++ b/apps/sim/app/api/schedules/[id]/route.test.ts @@ -15,12 +15,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) -vi.mock('drizzle-orm', () => ({ - and: vi.fn(), - eq: vi.fn(), - isNull: vi.fn(), -})) - vi.mock('@sim/audit', () => auditMock) import { PUT } from './route' diff --git a/apps/sim/app/api/schedules/route.test.ts b/apps/sim/app/api/schedules/route.test.ts index 88929342615..0d8f5713ec9 100644 --- a/apps/sim/app/api/schedules/route.test.ts +++ b/apps/sim/app/api/schedules/route.test.ts @@ -9,13 +9,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) -vi.mock('drizzle-orm', () => ({ - eq: vi.fn(), - and: vi.fn(), - or: vi.fn(), - isNull: vi.fn(), -})) - import { GET } from '@/app/api/schedules/route' function createRequest(url: string): NextRequest { diff --git a/apps/sim/app/api/speech/token/route.test.ts b/apps/sim/app/api/speech/token/route.test.ts index ab3fd86ed23..cf599da66c0 100644 --- a/apps/sim/app/api/speech/token/route.test.ts +++ b/apps/sim/app/api/speech/token/route.test.ts @@ -2,16 +2,17 @@ * @vitest-environment node */ import { + authMockFns, createMockRequest, - dbChainMock, queueTableRows, resetDbChainMock, + resetEnvMock, schemaMock, + setEnv, } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { - mockGetSession, mockRecordUsage, mockCheckActorUsageLimits, mockVerifyWorkspaceMembership, @@ -21,7 +22,6 @@ const { mockToBillingContext, mockCheckAndBillPayerOverageThreshold, } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), mockRecordUsage: vi.fn(), mockCheckActorUsageLimits: vi.fn(), mockVerifyWorkspaceMembership: vi.fn(), @@ -45,10 +45,6 @@ const SYSTEM_BILLING_ATTRIBUTION = { payerSubscription: null, } -vi.mock('@sim/db', () => dbChainMock) - -vi.mock('@/lib/auth', () => ({ getSession: mockGetSession })) - vi.mock('@/lib/billing/core/usage-log', () => ({ recordUsage: mockRecordUsage })) vi.mock('@/lib/billing/core/billing-attribution', () => ({ @@ -70,8 +66,6 @@ vi.mock('@/app/api/workflows/utils', () => ({ verifyWorkspaceMembership: mockVerifyWorkspaceMembership, })) -vi.mock('@/lib/core/config/env', () => ({ env: { ELEVENLABS_API_KEY: 'test-key' } })) - vi.mock('@/lib/core/rate-limiter', () => ({ RateLimiter: class { checkRateLimitDirect = vi.fn().mockResolvedValue({ allowed: true }) @@ -82,6 +76,8 @@ vi.mock('@/lib/core/security/deployment', () => ({ validateAuthToken: vi.fn(() = import { POST } from '@/app/api/speech/token/route' +const mockGetSession = authMockFns.mockGetSession + const publicChatRow = { id: 'chat-1', userId: 'owner-1', @@ -94,6 +90,7 @@ const publicChatRow = { beforeEach(() => { vi.clearAllMocks() resetDbChainMock() + setEnv({ ELEVENLABS_API_KEY: 'test-key' }) mockGetSession.mockResolvedValue({ user: { id: 'member-1' } }) mockRecordUsage.mockResolvedValue(undefined) mockCheckActorUsageLimits.mockResolvedValue({ isExceeded: false }) @@ -125,6 +122,7 @@ beforeEach(() => { afterAll(() => { resetDbChainMock() + resetEnvMock() }) describe('POST /api/speech/token — usage attribution', () => { diff --git a/apps/sim/app/api/tools/custom/route.test.ts b/apps/sim/app/api/tools/custom/route.test.ts index d84994ab522..d532688628a 100644 --- a/apps/sim/app/api/tools/custom/route.test.ts +++ b/apps/sim/app/api/tools/custom/route.test.ts @@ -6,7 +6,6 @@ import { authMockFns, createMockRequest, - dbChainMock, dbChainMockFns, hybridAuthMockFns, permissionsMock, @@ -83,8 +82,6 @@ const sampleTools = [ }, ] -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) vi.mock('@/lib/workflows/custom-tools/operations', () => ({ diff --git a/apps/sim/app/api/usage/route.test.ts b/apps/sim/app/api/usage/route.test.ts index ab03f1a5982..8c922f6a170 100644 --- a/apps/sim/app/api/usage/route.test.ts +++ b/apps/sim/app/api/usage/route.test.ts @@ -1,19 +1,12 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { authMockFns, createMockRequest } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetOrganizationBillingData, mockGetSession, mockIsOrganizationOwnerOrAdmin } = - vi.hoisted(() => ({ - mockGetOrganizationBillingData: vi.fn(), - mockGetSession: vi.fn(), - mockIsOrganizationOwnerOrAdmin: vi.fn(), - })) - -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, +const { mockGetOrganizationBillingData, mockIsOrganizationOwnerOrAdmin } = vi.hoisted(() => ({ + mockGetOrganizationBillingData: vi.fn(), + mockIsOrganizationOwnerOrAdmin: vi.fn(), })) vi.mock('@/lib/billing', () => ({ @@ -28,6 +21,8 @@ vi.mock('@/lib/billing/core/organization', () => ({ import { GET } from '@/app/api/usage/route' +const mockGetSession = authMockFns.mockGetSession + describe('GET /api/usage organization context', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/apps/sim/app/api/users/me/subscription/[id]/transfer/route.test.ts b/apps/sim/app/api/users/me/subscription/[id]/transfer/route.test.ts index 4743b35041a..b2b44642e0a 100644 --- a/apps/sim/app/api/users/me/subscription/[id]/transfer/route.test.ts +++ b/apps/sim/app/api/users/me/subscription/[id]/transfer/route.test.ts @@ -2,11 +2,9 @@ * @vitest-environment node */ import { - authMock, authMockFns, createMockRequest, createSession, - dbChainMock, dbChainMockFns, resetDbChainMock, } from '@sim/testing' @@ -18,9 +16,6 @@ const { mockAcquireOrganizationMutationLock, mockAssertNoUnresolvedEnterpriseIss mockAssertNoUnresolvedEnterpriseIssuance: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) -vi.mock('@/lib/auth', () => authMock) - vi.mock('@/lib/billing/enterprise-outbox', () => { class EnterpriseIssuanceInProgressError extends Error {} return { diff --git a/apps/sim/app/api/v1/audit-logs/[id]/route.test.ts b/apps/sim/app/api/v1/audit-logs/[id]/route.test.ts index c44f99f0d25..0424663f9c1 100644 --- a/apps/sim/app/api/v1/audit-logs/[id]/route.test.ts +++ b/apps/sim/app/api/v1/audit-logs/[id]/route.test.ts @@ -4,7 +4,7 @@ * Tests for GET /api/v1/audit-logs/[id] — verifies the lookup is constrained * by the organization scope and 404s for rows outside it. */ -import { createMockRequest, dbChainMock, dbChainMockFns } from '@sim/testing' +import { createMockRequest, dbChainMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -19,8 +19,6 @@ const { mockGetOrgWorkspaceIds: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/app/api/v1/middleware', () => ({ checkRateLimit: mockCheckRateLimit, createRateLimitResponse: vi.fn(), diff --git a/apps/sim/app/api/v1/audit-logs/auth.test.ts b/apps/sim/app/api/v1/audit-logs/auth.test.ts index d7130879a95..a16dee2b0c4 100644 --- a/apps/sim/app/api/v1/audit-logs/auth.test.ts +++ b/apps/sim/app/api/v1/audit-logs/auth.test.ts @@ -1,21 +1,13 @@ /** * @vitest-environment node */ -import { - dbChainMock, - dbChainMockFns, - queueTableRows, - resetDbChainMock, - schemaMock, -} from '@sim/testing' +import { dbChainMockFns, queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockIsOrganizationBillingBlocked } = vi.hoisted(() => ({ mockIsOrganizationBillingBlocked: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/core/access', () => ({ isOrganizationBillingBlocked: mockIsOrganizationBillingBlocked, })) diff --git a/apps/sim/app/api/v1/audit-logs/query.test.ts b/apps/sim/app/api/v1/audit-logs/query.test.ts index c4a4aacecf7..72740ca537f 100644 --- a/apps/sim/app/api/v1/audit-logs/query.test.ts +++ b/apps/sim/app/api/v1/audit-logs/query.test.ts @@ -5,11 +5,8 @@ * mock returns structured operator objects, so these tests assert directly on * the predicate tree. */ -import { dbChainMock, dbChainMockFns } from '@sim/testing' +import { dbChainMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' - -vi.mock('@sim/db', () => dbChainMock) - import { buildOrgScopeCondition, getOrgWorkspaceIds } from '@/app/api/v1/audit-logs/query' const ORG_ID = 'org-1' diff --git a/apps/sim/app/api/webhooks/poll/[provider]/route.test.ts b/apps/sim/app/api/webhooks/poll/[provider]/route.test.ts index 859eba6b73d..432e38bf308 100644 --- a/apps/sim/app/api/webhooks/poll/[provider]/route.test.ts +++ b/apps/sim/app/api/webhooks/poll/[provider]/route.test.ts @@ -3,7 +3,7 @@ * * @vitest-environment node */ -import { createMockRequest, redisConfigMock, redisConfigMockFns } from '@sim/testing' +import { createMockRequest, redisConfigMockFns } from '@sim/testing' import { sleep } from '@sim/utils/helpers' import { beforeEach, describe, expect, it, vi } from 'vitest' @@ -16,8 +16,6 @@ vi.mock('@/lib/auth/internal', () => ({ verifyCronAuth: mockVerifyCronAuth, })) -vi.mock('@/lib/core/config/redis', () => redisConfigMock) - vi.mock('@/lib/webhooks/polling', () => ({ pollProvider: mockPollProvider, VALID_POLLING_PROVIDERS: new Set(['gmail', 'outlook', 'rss']), diff --git a/apps/sim/app/api/webhooks/slack/route.test.ts b/apps/sim/app/api/webhooks/slack/route.test.ts index ac20c7927cf..bbb2b0baf6b 100644 --- a/apps/sim/app/api/webhooks/slack/route.test.ts +++ b/apps/sim/app/api/webhooks/slack/route.test.ts @@ -1,7 +1,8 @@ /** * @vitest-environment node */ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { resetEnvMock, setEnv } from '@sim/testing' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockParseWebhookBody, mockFindWebhooksByRoutingKey, mockDispatchResolvedWebhookTarget } = vi.hoisted(() => ({ @@ -15,10 +16,6 @@ vi.mock('@/lib/core/admission/gate', () => ({ admissionRejectedResponse: () => new Response(null, { status: 503 }), })) -vi.mock('@/lib/core/config/env', () => ({ - env: { SLACK_SIGNING_SECRET: 'test-secret' }, -})) - vi.mock('@/lib/webhooks/processor', () => ({ parseWebhookBody: mockParseWebhookBody, findWebhooksByRoutingKey: mockFindWebhooksByRoutingKey, @@ -56,8 +53,13 @@ const messageBody = { } describe('Slack app webhook route', () => { + afterAll(() => { + resetEnvMock() + }) + beforeEach(() => { vi.clearAllMocks() + setEnv({ SLACK_SIGNING_SECRET: 'test-secret' }) mockFindWebhooksByRoutingKey.mockResolvedValue([webhook('wh1')]) mockDispatchResolvedWebhookTarget.mockResolvedValue({ outcome: 'queued', diff --git a/apps/sim/app/api/webhooks/tiktok/route.test.ts b/apps/sim/app/api/webhooks/tiktok/route.test.ts index 6ad1f0676eb..1bd8d805627 100644 --- a/apps/sim/app/api/webhooks/tiktok/route.test.ts +++ b/apps/sim/app/api/webhooks/tiktok/route.test.ts @@ -3,8 +3,9 @@ */ import crypto from 'node:crypto' +import { requestUtilsMockFns, resetEnvMock, setEnv } from '@sim/testing' import { NextRequest } from 'next/server' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockEnqueueTikTokWebhookIngress, mockRelease } = vi.hoisted(() => ({ mockEnqueueTikTokWebhookIngress: vi.fn(), @@ -20,17 +21,6 @@ vi.mock('@/lib/core/admission/gate', () => ({ tryAdmit: vi.fn(() => ({ release: mockRelease })), })) -vi.mock('@/lib/core/config/env', () => ({ - env: { - TIKTOK_CLIENT_ID: 'client-key', - TIKTOK_CLIENT_SECRET: 'client-secret', - }, -})) - -vi.mock('@/lib/core/utils/request', () => ({ - generateRequestId: vi.fn(() => 'request-1'), -})) - vi.mock('@/lib/core/utils/with-route-handler', () => ({ withRouteHandler: (handler: (request: NextRequest) => Promise) => (request: NextRequest) => @@ -66,9 +56,16 @@ function signedRequest(overrides?: { clientKey?: string }): NextRequest { describe('TikTok webhook ingress route', () => { beforeEach(() => { vi.clearAllMocks() + setEnv({ TIKTOK_CLIENT_ID: 'client-key', TIKTOK_CLIENT_SECRET: 'client-secret' }) + requestUtilsMockFns.mockGenerateRequestId.mockReturnValue('request-1') mockEnqueueTikTokWebhookIngress.mockResolvedValue('ingress-job-1') }) + afterAll(() => { + resetEnvMock() + requestUtilsMockFns.mockGenerateRequestId.mockReset() + }) + it('returns 200 only after the verified delivery is accepted by the job queue', async () => { const response = await POST(signedRequest()) diff --git a/apps/sim/app/api/workflows/[id]/chat/status/route.test.ts b/apps/sim/app/api/workflows/[id]/chat/status/route.test.ts index 188a5a47580..d94b3abcc2a 100644 --- a/apps/sim/app/api/workflows/[id]/chat/status/route.test.ts +++ b/apps/sim/app/api/workflows/[id]/chat/status/route.test.ts @@ -4,7 +4,6 @@ * @vitest-environment node */ import { - dbChainMock, dbChainMockFns, hybridAuthMockFns, resetDbChainMock, @@ -14,13 +13,6 @@ import { import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) -vi.mock('drizzle-orm', () => ({ - and: vi.fn((...args: unknown[]) => ({ type: 'and', args })), - eq: vi.fn(), - isNull: vi.fn((field: unknown) => ({ type: 'isNull', field })), -})) - vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) import { GET } from '@/app/api/workflows/[id]/chat/status/route' diff --git a/apps/sim/app/api/workflows/[id]/execute/route.async.test.ts b/apps/sim/app/api/workflows/[id]/execute/route.async.test.ts index ce55f06ee23..771e7cda706 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.async.test.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.async.test.ts @@ -4,7 +4,6 @@ import { createMockRequest, - dbChainMock, dbChainMockFns, executionPreprocessingMock, executionPreprocessingMockFns, @@ -12,6 +11,8 @@ import { loggingSessionMock, requestUtilsMockFns, resetDbChainMock, + resetEnvMock, + setEnv, workflowAuthzMockFns, workflowsPersistenceUtilsMock, workflowsPersistenceUtilsMockFns, @@ -19,7 +20,7 @@ import { workflowsUtilsMockFns, } from '@sim/testing' import { NextRequest } from 'next/server' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' import { AsyncJobEnqueueError } from '@/lib/core/async-jobs/types' const { @@ -55,8 +56,6 @@ const { mockValidatePublicApiAllowed: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/core/billing-attribution', () => ({ assertBillingAttributionSnapshot: mockAssertBillingAttributionSnapshot, requireBillingAttributionHeader: mockRequireBillingAttributionHeader, @@ -121,11 +120,6 @@ vi.mock('@/lib/core/async-jobs', () => ({ shouldExecuteInline: vi.fn().mockReturnValue(false), })) -vi.mock('@/lib/core/utils/urls', () => ({ - getBaseUrl: vi.fn().mockReturnValue('http://localhost:3000'), - getOllamaUrl: vi.fn().mockReturnValue('http://localhost:11434'), -})) - vi.mock('@/lib/execution/call-chain', () => ({ SIM_VIA_HEADER: 'x-sim-via', parseCallChain: vi.fn().mockReturnValue([]), @@ -280,9 +274,14 @@ function createCallerExecutionRequest( } describe('workflow execute async route', () => { + afterAll(() => { + resetEnvMock() + }) + beforeEach(() => { vi.clearAllMocks() resetDbChainMock() + setEnv({ NEXT_PUBLIC_APP_URL: 'http://localhost:3000' }) mockGenerateId.mockReset().mockReturnValue('execution-123') mockClaimExecutionId.mockImplementation(async (executionId: string) => ({ key: `workflow-execution-id:${executionId}`, diff --git a/apps/sim/app/api/workflows/[id]/executions/[executionId]/stream/route.test.ts b/apps/sim/app/api/workflows/[id]/executions/[executionId]/stream/route.test.ts index 27529007563..5ab5775652b 100644 --- a/apps/sim/app/api/workflows/[id]/executions/[executionId]/stream/route.test.ts +++ b/apps/sim/app/api/workflows/[id]/executions/[executionId]/stream/route.test.ts @@ -1,29 +1,17 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { authMockFns, createMockRequest, workflowAuthzMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' import type { ExecutionEventEntry } from '@/lib/execution/event-buffer' -const { - mockAuthorizeWorkflowByWorkspacePermission, - mockGetSession, - mockReadExecutionEventsState, - mockReadExecutionMetaState, -} = vi.hoisted(() => ({ - mockAuthorizeWorkflowByWorkspacePermission: vi.fn(), - mockGetSession: vi.fn(), +const { mockReadExecutionEventsState, mockReadExecutionMetaState } = vi.hoisted(() => ({ mockReadExecutionEventsState: vi.fn(), mockReadExecutionMetaState: vi.fn(), })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - -vi.mock('@sim/platform-authz/workflow', () => ({ - authorizeWorkflowByWorkspacePermission: mockAuthorizeWorkflowByWorkspacePermission, -})) +const mockAuthorizeWorkflowByWorkspacePermission = + workflowAuthzMockFns.mockAuthorizeWorkflowByWorkspacePermission vi.mock('@/lib/execution/event-buffer', () => ({ readExecutionEventsState: mockReadExecutionEventsState, @@ -32,6 +20,8 @@ vi.mock('@/lib/execution/event-buffer', () => ({ import { GET } from './route' +const mockGetSession = authMockFns.mockGetSession + function completedEntry(eventId: number): ExecutionEventEntry { return { eventId, diff --git a/apps/sim/app/api/workflows/[id]/log/route.test.ts b/apps/sim/app/api/workflows/[id]/log/route.test.ts index b6d0169c1fe..9b6c35578d5 100644 --- a/apps/sim/app/api/workflows/[id]/log/route.test.ts +++ b/apps/sim/app/api/workflows/[id]/log/route.test.ts @@ -1,12 +1,11 @@ /** * @vitest-environment node */ -import { authMockFns, dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { authMockFns, dbChainMockFns, resetDbChainMock } from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' // Override global db mock with the configurable chain mock -vi.mock('@sim/db', () => dbChainMock) const { mockValidateWorkflowAccess, diff --git a/apps/sim/app/api/workflows/[id]/references/route.test.ts b/apps/sim/app/api/workflows/[id]/references/route.test.ts index d6ef6045816..785a1ec2893 100644 --- a/apps/sim/app/api/workflows/[id]/references/route.test.ts +++ b/apps/sim/app/api/workflows/[id]/references/route.test.ts @@ -1,22 +1,14 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { authMockFns, createMockRequest, workflowAuthzMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetSession, mockAuthorizeWorkflow, mockGetWorkflowReferences } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), - mockAuthorizeWorkflow: vi.fn(), +const { mockGetWorkflowReferences } = vi.hoisted(() => ({ mockGetWorkflowReferences: vi.fn(), })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - -vi.mock('@sim/platform-authz/workflow', () => ({ - authorizeWorkflowByWorkspacePermission: mockAuthorizeWorkflow, -})) +const mockAuthorizeWorkflow = workflowAuthzMockFns.mockAuthorizeWorkflowByWorkspacePermission vi.mock('@/lib/workflows/references/operations', () => ({ getWorkflowReferences: mockGetWorkflowReferences, @@ -24,6 +16,8 @@ vi.mock('@/lib/workflows/references/operations', () => ({ import { GET } from '@/app/api/workflows/[id]/references/route' +const mockGetSession = authMockFns.mockGetSession + const REFERENCES = { callers: [{ id: 'b', name: 'B', cycle: false, children: [] }], callees: [], diff --git a/apps/sim/app/api/workflows/[id]/route.test.ts b/apps/sim/app/api/workflows/[id]/route.test.ts index 56b0ffd8b11..9b9be85824c 100644 --- a/apps/sim/app/api/workflows/[id]/route.test.ts +++ b/apps/sim/app/api/workflows/[id]/route.test.ts @@ -7,9 +7,7 @@ import { auditMock, - dbChainMock, dbChainMockFns, - envMock, hybridAuthMockFns, resetDbChainMock, telemetryMock, @@ -52,8 +50,6 @@ function mockGetSession(session: { user: { id: string } } | null) { } } -vi.mock('@/lib/core/config/env', () => envMock) - vi.mock('@/lib/core/telemetry', () => telemetryMock) vi.mock('@sim/audit', () => auditMock) @@ -64,8 +60,6 @@ vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) vi.mock('@/lib/workflows/orchestration', () => workflowsOrchestrationMock) -vi.mock('@sim/db', () => dbChainMock) - import { DELETE, GET, PUT } from './route' describe('Workflow By ID API Route', () => { diff --git a/apps/sim/app/api/workflows/middleware.test.ts b/apps/sim/app/api/workflows/middleware.test.ts index 202326d2c15..e66f552df89 100644 --- a/apps/sim/app/api/workflows/middleware.test.ts +++ b/apps/sim/app/api/workflows/middleware.test.ts @@ -7,7 +7,6 @@ import { hybridAuthMockFns, - workflowAuthzMock, workflowAuthzMockFns, workflowsUtilsMock, workflowsUtilsMockFns, @@ -16,7 +15,6 @@ import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) -vi.mock('@sim/platform-authz/workflow', () => workflowAuthzMock) vi.mock('@/lib/api-key/service', () => ({ authenticateApiKeyFromHeader: vi.fn(), updateApiKeyLastUsed: vi.fn(), diff --git a/apps/sim/app/api/workflows/route.test.ts b/apps/sim/app/api/workflows/route.test.ts index 732d4931200..106b6bf8faf 100644 --- a/apps/sim/app/api/workflows/route.test.ts +++ b/apps/sim/app/api/workflows/route.test.ts @@ -4,7 +4,6 @@ import { auditMock, createMockRequest, - dbChainMock, dbChainMockFns, hybridAuthMockFns, permissionsMock, @@ -25,8 +24,6 @@ const { mockWorkflowCreated } = vi.hoisted(() => ({ const mockGetUserEntityPermissions = permissionsMockFns.mockGetUserEntityPermissions -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@sim/audit', () => auditMock) vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) diff --git a/apps/sim/app/api/workspace-events/poll/route.test.ts b/apps/sim/app/api/workspace-events/poll/route.test.ts index dee80482bc8..96d52e8b3a7 100644 --- a/apps/sim/app/api/workspace-events/poll/route.test.ts +++ b/apps/sim/app/api/workspace-events/poll/route.test.ts @@ -3,7 +3,7 @@ * * @vitest-environment node */ -import { createMockRequest, redisConfigMock, redisConfigMockFns } from '@sim/testing' +import { createMockRequest, redisConfigMockFns } from '@sim/testing' import { sleep } from '@sim/utils/helpers' import { beforeEach, describe, expect, it, vi } from 'vitest' @@ -18,8 +18,6 @@ vi.mock('@/lib/auth/internal', () => ({ verifyCronAuth: mockVerifyCronAuth, })) -vi.mock('@/lib/core/config/redis', () => redisConfigMock) - vi.mock('@/lib/workspace-events/no-activity', () => ({ pollNoActivityEvents: mockPollNoActivityEvents, })) diff --git a/apps/sim/app/api/workspaces/[id]/api-keys/route.test.ts b/apps/sim/app/api/workspaces/[id]/api-keys/route.test.ts index d5d3b874c78..39eb2394e0b 100644 --- a/apps/sim/app/api/workspaces/[id]/api-keys/route.test.ts +++ b/apps/sim/app/api/workspaces/[id]/api-keys/route.test.ts @@ -1,20 +1,21 @@ /** * @vitest-environment node */ -import { createMockRequest, queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' +import { + authMockFns, + createMockRequest, + queueTableRows, + resetDbChainMock, + schemaMock, +} from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' -const { - mockGetApiKeyDisplayFormat, - mockGetSession, - mockGetUserEntityPermissions, - mockGetWorkspaceById, -} = vi.hoisted(() => ({ - mockGetApiKeyDisplayFormat: vi.fn(), - mockGetSession: vi.fn(), - mockGetUserEntityPermissions: vi.fn(), - mockGetWorkspaceById: vi.fn(), -})) +const { mockGetApiKeyDisplayFormat, mockGetUserEntityPermissions, mockGetWorkspaceById } = + vi.hoisted(() => ({ + mockGetApiKeyDisplayFormat: vi.fn(), + mockGetUserEntityPermissions: vi.fn(), + mockGetWorkspaceById: vi.fn(), + })) vi.mock('@/lib/api-key/auth', () => ({ getApiKeyDisplayFormat: mockGetApiKeyDisplayFormat, @@ -24,11 +25,6 @@ vi.mock('@/lib/api-key/orchestration', () => ({ performCreateWorkspaceApiKey: vi.fn(), })) -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, -})) - vi.mock('@/lib/workspaces/permissions/utils', () => ({ getUserEntityPermissions: mockGetUserEntityPermissions, getWorkspaceById: mockGetWorkspaceById, @@ -36,6 +32,8 @@ vi.mock('@/lib/workspaces/permissions/utils', () => ({ import { GET } from '@/app/api/workspaces/[id]/api-keys/route' +const mockGetSession = authMockFns.mockGetSession + describe('GET /api/workspaces/[id]/api-keys', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/apps/sim/app/api/workspaces/[id]/byok-keys/route.test.ts b/apps/sim/app/api/workspaces/[id]/byok-keys/route.test.ts index 89b7f60b7b0..634d5e19137 100644 --- a/apps/sim/app/api/workspaces/[id]/byok-keys/route.test.ts +++ b/apps/sim/app/api/workspaces/[id]/byok-keys/route.test.ts @@ -3,6 +3,7 @@ */ import { auditMock, + authMockFns, createMockRequest, dbChainMockFns, queueTableRows, @@ -11,26 +12,16 @@ import { } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' -const { - mockGetSession, - mockGetUserEntityPermissions, - mockGetWorkspaceById, - mockEncryptSecret, - mockDecryptSecret, -} = vi.hoisted(() => ({ - mockGetSession: vi.fn(), - mockGetUserEntityPermissions: vi.fn(), - mockGetWorkspaceById: vi.fn(), - mockEncryptSecret: vi.fn(), - mockDecryptSecret: vi.fn(), -})) +const { mockGetUserEntityPermissions, mockGetWorkspaceById, mockEncryptSecret, mockDecryptSecret } = + vi.hoisted(() => ({ + mockGetUserEntityPermissions: vi.fn(), + mockGetWorkspaceById: vi.fn(), + mockEncryptSecret: vi.fn(), + mockDecryptSecret: vi.fn(), + })) vi.mock('@sim/audit', () => auditMock) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - vi.mock('@/lib/core/security/encryption', () => ({ encryptSecret: mockEncryptSecret, decryptSecret: mockDecryptSecret, @@ -47,6 +38,8 @@ vi.mock('@/lib/workspaces/permissions/utils', () => ({ import { DELETE, GET, POST } from '@/app/api/workspaces/[id]/byok-keys/route' +const mockGetSession = authMockFns.mockGetSession + const WORKSPACE_ID = 'workspace-1' const routeContext = { params: Promise.resolve({ id: WORKSPACE_ID }) } diff --git a/apps/sim/app/api/workspaces/[id]/credit-availability/route.test.ts b/apps/sim/app/api/workspaces/[id]/credit-availability/route.test.ts index b4d90966f77..637b4e5c0fa 100644 --- a/apps/sim/app/api/workspaces/[id]/credit-availability/route.test.ts +++ b/apps/sim/app/api/workspaces/[id]/credit-availability/route.test.ts @@ -1,20 +1,15 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { authMockFns, createMockRequest } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetSession, mockGetWorkspaceCreditAvailability, mockGetWorkspaceHostContextForViewer } = - vi.hoisted(() => ({ - mockGetSession: vi.fn(), +const { mockGetWorkspaceCreditAvailability, mockGetWorkspaceHostContextForViewer } = vi.hoisted( + () => ({ mockGetWorkspaceCreditAvailability: vi.fn(), mockGetWorkspaceHostContextForViewer: vi.fn(), - })) - -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, -})) + }) +) vi.mock('@/lib/billing/core/workspace-usage-gate', () => ({ getWorkspaceCreditAvailability: mockGetWorkspaceCreditAvailability, @@ -26,6 +21,8 @@ vi.mock('@/lib/workspaces/host-context', () => ({ import { GET } from '@/app/api/workspaces/[id]/credit-availability/route' +const mockGetSession = authMockFns.mockGetSession + const HOST_CONTEXT = { workspace: { id: 'workspace-b', diff --git a/apps/sim/app/api/workspaces/[id]/environment/route.test.ts b/apps/sim/app/api/workspaces/[id]/environment/route.test.ts index 6ad61921591..759abd0b1e5 100644 --- a/apps/sim/app/api/workspaces/[id]/environment/route.test.ts +++ b/apps/sim/app/api/workspaces/[id]/environment/route.test.ts @@ -1,37 +1,22 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { authMockFns, createMockRequest, environmentUtilsMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { - mockGetSession, - mockGetWorkspaceById, - mockGetUserEntityPermissions, - mockGetPersonalAndWorkspaceEnv, - mockGetWorkspaceEnvKeyAdminAccess, -} = vi.hoisted(() => ({ - mockGetSession: vi.fn(), - mockGetWorkspaceById: vi.fn(), - mockGetUserEntityPermissions: vi.fn(), - mockGetPersonalAndWorkspaceEnv: vi.fn(), - mockGetWorkspaceEnvKeyAdminAccess: vi.fn(), -})) - -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, -})) +const { mockGetWorkspaceById, mockGetUserEntityPermissions, mockGetWorkspaceEnvKeyAdminAccess } = + vi.hoisted(() => ({ + mockGetWorkspaceById: vi.fn(), + mockGetUserEntityPermissions: vi.fn(), + mockGetWorkspaceEnvKeyAdminAccess: vi.fn(), + })) vi.mock('@/lib/workspaces/permissions/utils', () => ({ getWorkspaceById: mockGetWorkspaceById, getUserEntityPermissions: mockGetUserEntityPermissions, })) -vi.mock('@/lib/environment/utils', () => ({ - getPersonalAndWorkspaceEnv: mockGetPersonalAndWorkspaceEnv, - invalidateEffectiveDecryptedEnvCache: vi.fn(), -})) +const mockGetPersonalAndWorkspaceEnv = environmentUtilsMockFns.mockGetPersonalAndWorkspaceEnv vi.mock('@/lib/credentials/environment', () => ({ getWorkspaceEnvKeyAdminAccess: mockGetWorkspaceEnvKeyAdminAccess, @@ -41,6 +26,8 @@ vi.mock('@/lib/credentials/environment', () => ({ import { GET } from '@/app/api/workspaces/[id]/environment/route' +const mockGetSession = authMockFns.mockGetSession + const WORKSPACE_ID = 'ws-1' function buildParams() { diff --git a/apps/sim/app/api/workspaces/[id]/files/inline/route.test.ts b/apps/sim/app/api/workspaces/[id]/files/inline/route.test.ts index 3bb2a8a06ba..c57ac919e38 100644 --- a/apps/sim/app/api/workspaces/[id]/files/inline/route.test.ts +++ b/apps/sim/app/api/workspaces/[id]/files/inline/route.test.ts @@ -1,20 +1,16 @@ /** * @vitest-environment node */ +import { authMockFns } from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetSession, mockGetPerms, mockResolveImage, mockDownloadFile } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), +const { mockGetPerms, mockResolveImage, mockDownloadFile } = vi.hoisted(() => ({ mockGetPerms: vi.fn(), mockResolveImage: vi.fn(), mockDownloadFile: vi.fn(), })) -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, -})) vi.mock('@/lib/workspaces/permissions/utils', () => ({ getUserEntityPermissions: mockGetPerms })) vi.mock('@/lib/uploads/server/inline-image', () => ({ resolveWorkspaceInlineImage: mockResolveImage, @@ -23,6 +19,8 @@ vi.mock('@/lib/uploads/core/storage-service', () => ({ downloadFile: mockDownloa import { GET } from '@/app/api/workspaces/[id]/files/inline/route' +const mockGetSession = authMockFns.mockGetSession + const PNG = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00]) const params = { params: Promise.resolve({ id: 'ws-1' }) } const req = (q: string) => new NextRequest(`http://localhost/api/workspaces/ws-1/files/inline?${q}`) diff --git a/apps/sim/app/api/workspaces/[id]/fork/excluded-workflows/route.test.ts b/apps/sim/app/api/workspaces/[id]/fork/excluded-workflows/route.test.ts index a623def8937..f31acbbc923 100644 --- a/apps/sim/app/api/workspaces/[id]/fork/excluded-workflows/route.test.ts +++ b/apps/sim/app/api/workspaces/[id]/fork/excluded-workflows/route.test.ts @@ -4,23 +4,16 @@ import { auditMock, auditMockFns, + authMockFns, createMockRequest, dbChainMockFns, resetDbChainMock, } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetSession, mockAssertWorkspaceAdminAccess, mockCaptureServerEvent } = vi.hoisted( - () => ({ - mockGetSession: vi.fn(), - mockAssertWorkspaceAdminAccess: vi.fn(), - mockCaptureServerEvent: vi.fn(), - }) -) - -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, +const { mockAssertWorkspaceAdminAccess, mockCaptureServerEvent } = vi.hoisted(() => ({ + mockAssertWorkspaceAdminAccess: vi.fn(), + mockCaptureServerEvent: vi.fn(), })) vi.mock('@/ee/workspace-forking/lib/lineage/authz', () => ({ @@ -35,6 +28,8 @@ vi.mock('@/lib/posthog/server', () => ({ import { PUT } from '@/app/api/workspaces/[id]/fork/excluded-workflows/route' +const mockGetSession = authMockFns.mockGetSession + const WORKSPACE_ID = 'workspace-1' const ADMIN_ID = 'user-1' const routeContext = { params: Promise.resolve({ id: WORKSPACE_ID }) } diff --git a/apps/sim/app/api/workspaces/[id]/fork/lineage/route.test.ts b/apps/sim/app/api/workspaces/[id]/fork/lineage/route.test.ts index 1970a50b9e4..c3c265ba4c6 100644 --- a/apps/sim/app/api/workspaces/[id]/fork/lineage/route.test.ts +++ b/apps/sim/app/api/workspaces/[id]/fork/lineage/route.test.ts @@ -1,18 +1,16 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { authMockFns, createMockRequest } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { - mockGetSession, mockAssertWorkspaceAdminAccess, mockGetForkParent, mockGetForkChildren, mockGetUndoableRunForTarget, mockGetEffectiveWorkspacePermission, } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), mockAssertWorkspaceAdminAccess: vi.fn(), mockGetForkParent: vi.fn(), mockGetForkChildren: vi.fn(), @@ -20,11 +18,6 @@ const { mockGetEffectiveWorkspacePermission: vi.fn(), })) -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, -})) - vi.mock('@/ee/workspace-forking/lib/lineage/authz', () => ({ assertWorkspaceAdminAccess: mockAssertWorkspaceAdminAccess, })) @@ -44,6 +37,8 @@ vi.mock('@/lib/workspaces/permissions/utils', () => ({ import { GET } from '@/app/api/workspaces/[id]/fork/lineage/route' +const mockGetSession = authMockFns.mockGetSession + const WORKSPACE_ID = 'workspace-1' const VIEWER_ID = 'user-1' const routeContext = { params: Promise.resolve({ id: WORKSPACE_ID }) } diff --git a/apps/sim/app/api/workspaces/[id]/host-context/route.test.ts b/apps/sim/app/api/workspaces/[id]/host-context/route.test.ts index 15a573533e8..b893936cd63 100644 --- a/apps/sim/app/api/workspaces/[id]/host-context/route.test.ts +++ b/apps/sim/app/api/workspaces/[id]/host-context/route.test.ts @@ -1,25 +1,21 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { authMockFns, createMockRequest } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetSession, mockGetWorkspaceHostContextForViewer } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), +const { mockGetWorkspaceHostContextForViewer } = vi.hoisted(() => ({ mockGetWorkspaceHostContextForViewer: vi.fn(), })) -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, -})) - vi.mock('@/lib/workspaces/host-context', () => ({ getWorkspaceHostContextForViewer: mockGetWorkspaceHostContextForViewer, })) import { GET } from '@/app/api/workspaces/[id]/host-context/route' +const mockGetSession = authMockFns.mockGetSession + const HOST_CONTEXT = { workspace: { id: 'workspace-1', diff --git a/apps/sim/app/api/workspaces/[id]/usage-gate/route.test.ts b/apps/sim/app/api/workspaces/[id]/usage-gate/route.test.ts index a95c44df843..1416eb53e61 100644 --- a/apps/sim/app/api/workspaces/[id]/usage-gate/route.test.ts +++ b/apps/sim/app/api/workspaces/[id]/usage-gate/route.test.ts @@ -1,19 +1,12 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { authMockFns, createMockRequest } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockCheckWorkspaceUsageGate, mockGetSession, mockGetWorkspaceHostContextForViewer } = - vi.hoisted(() => ({ - mockCheckWorkspaceUsageGate: vi.fn(), - mockGetSession: vi.fn(), - mockGetWorkspaceHostContextForViewer: vi.fn(), - })) - -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, +const { mockCheckWorkspaceUsageGate, mockGetWorkspaceHostContextForViewer } = vi.hoisted(() => ({ + mockCheckWorkspaceUsageGate: vi.fn(), + mockGetWorkspaceHostContextForViewer: vi.fn(), })) vi.mock('@/lib/billing/core/workspace-usage-gate', () => ({ @@ -26,6 +19,8 @@ vi.mock('@/lib/workspaces/host-context', () => ({ import { GET } from '@/app/api/workspaces/[id]/usage-gate/route' +const mockGetSession = authMockFns.mockGetSession + const HOST_CONTEXT = { workspace: { id: 'workspace-b', diff --git a/apps/sim/app/api/workspaces/invitations/route.test.ts b/apps/sim/app/api/workspaces/invitations/route.test.ts index d4eefc75af4..2b600626204 100644 --- a/apps/sim/app/api/workspaces/invitations/route.test.ts +++ b/apps/sim/app/api/workspaces/invitations/route.test.ts @@ -3,7 +3,6 @@ */ import { auditMock, - authMock, authMockFns, createMockRequest, permissionsMock, @@ -35,8 +34,6 @@ const { mockFindPendingGrantForWorkspaceEmail: vi.fn(), })) -vi.mock('@/lib/auth', () => authMock) - vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) vi.mock('@/lib/workspaces/policy', () => ({ diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx index e91226d8757..b3e08f18da1 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx @@ -18,9 +18,6 @@ vi.mock('next/navigation', () => ({ })) // Override the global `getAllBlocks: () => ({})` stub — `getIconColorMap` iterates it as an array. -vi.mock('@/blocks/registry', () => ({ - getAllBlocks: () => [], -})) const { MentionChipView } = await import('./mention-chip') diff --git a/apps/sim/app/workspace/[workspaceId]/layout.test.tsx b/apps/sim/app/workspace/[workspaceId]/layout.test.tsx index b8f0399d751..10264ea9276 100644 --- a/apps/sim/app/workspace/[workspaceId]/layout.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/layout.test.tsx @@ -1,20 +1,20 @@ /** * @vitest-environment node */ + import type { ReactNode } from 'react' +import { authMockFns } from '@sim/testing' import { renderToStaticMarkup } from 'react-dom/server' import { beforeEach, describe, expect, it, vi } from 'vitest' const { mockBrandingProvider, mockGetOrgWhitelabelSettings, - mockGetSession, mockPrefetchWorkspaceHostContext, mockPrefetchWorkspaceSidebar, } = vi.hoisted(() => ({ mockBrandingProvider: vi.fn(({ children }: { children: ReactNode }) => children), mockGetOrgWhitelabelSettings: vi.fn(), - mockGetSession: vi.fn(), mockPrefetchWorkspaceHostContext: vi.fn(), mockPrefetchWorkspaceSidebar: vi.fn(), })) @@ -36,10 +36,6 @@ vi.mock('next/navigation', () => ({ redirect: vi.fn(), })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - vi.mock('@/app/_shell/providers/get-query-client', () => ({ getQueryClient: () => ({ setQueryData: vi.fn() }), })) @@ -104,6 +100,8 @@ vi.mock('@/app/workspace/[workspaceId]/providers/workspace-scope-sync', () => ({ import WorkspaceLayout from '@/app/workspace/[workspaceId]/layout' +const mockGetSession = authMockFns.mockGetSession + const HOST_CONTEXT = { workspace: { id: 'workspace-b', From be2582c3e0c804d5c8ac2e8de73919f162abf0b2 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 22 Jul 2026 18:19:04 -0700 Subject: [PATCH 5/8] improvement(tests): drop redundant local mocks in lib (shared-worker readiness) --- .../lib/admin/dashboard-credit-grant.test.ts | 3 +- .../lib/admin/dashboard-organizations.test.ts | 3 +- .../lib/admin/external-collaborators.test.ts | 4 +- apps/sim/lib/api-key/byok.test.ts | 4 +- apps/sim/lib/api-key/crypto.test.ts | 15 +++--- apps/sim/lib/api-key/service.test.ts | 4 +- apps/sim/lib/auth/access-control.test.ts | 48 +++++++++--------- apps/sim/lib/auth/ban.test.ts | 37 +++++++------- apps/sim/lib/auth/hybrid.test.ts | 33 ++++++------- .../sim/lib/auth/stripe-adapter-guard.test.ts | 5 +- apps/sim/lib/billing/authorization.test.ts | 3 +- .../calculations/usage-monitor.test.ts | 10 +--- .../usage-reservation-env.test.ts | 46 +++++++---------- .../calculations/usage-reservation.test.ts | 5 +- .../lib/billing/cleanup-dispatcher.test.ts | 9 +--- .../billing/core/billing-attribution.test.ts | 10 +--- apps/sim/lib/billing/core/billing.test.ts | 2 - .../billing/core/limit-notifications.test.ts | 17 ++++--- apps/sim/lib/billing/core/plan.test.ts | 4 +- .../sim/lib/billing/core/subscription.test.ts | 6 +-- apps/sim/lib/billing/core/usage-log.test.ts | 10 +--- apps/sim/lib/billing/core/usage.test.ts | 4 +- .../lib/billing/credits/daily-refresh.test.ts | 4 +- .../billing/enterprise-provisioning.test.ts | 10 +--- apps/sim/lib/billing/organization.test.ts | 2 - .../organizations/create-organization.test.ts | 4 +- .../billing/organizations/lock-order.test.ts | 4 +- .../organizations/member-limits.test.ts | 4 +- .../pause-pro-for-coverage.test.ts | 3 +- .../organizations/provision-seat.test.ts | 4 +- .../billing/organizations/seat-drift.test.ts | 3 -- .../lib/billing/organizations/seats.test.ts | 3 -- apps/sim/lib/billing/storage/limits.test.ts | 15 ++---- apps/sim/lib/billing/storage/tracking.test.ts | 4 -- .../sim/lib/billing/threshold-billing.test.ts | 15 +----- .../validation/seat-management.test.ts | 10 +--- .../enterprise-reconciliation-lease.test.ts | 5 +- .../sim/lib/billing/webhooks/invoices.test.ts | 8 --- .../billing/webhooks/outbox-handlers.test.ts | 4 +- .../concurrency/__tests__/leader-lock.test.ts | 5 +- .../lib/copilot/async-runs/repository.test.ts | 5 +- apps/sim/lib/copilot/auth/permissions.test.ts | 15 +++--- apps/sim/lib/copilot/chat/lifecycle.test.ts | 22 ++++----- .../lib/copilot/chat/messages-store.test.ts | 5 +- apps/sim/lib/copilot/chat/post.test.ts | 14 ++---- .../lib/copilot/chat/process-contents.test.ts | 4 +- .../lib/copilot/chat/stream-liveness.test.ts | 4 +- .../lib/copilot/chat/terminal-state.test.ts | 4 +- .../lib/copilot/request/lifecycle/run.test.ts | 17 ++++--- .../copilot/request/lifecycle/start.test.ts | 4 +- .../lib/copilot/request/session/abort.test.ts | 3 +- .../copilot/request/session/buffer.test.ts | 4 +- .../request/session/explicit-abort.test.ts | 16 +++--- apps/sim/lib/copilot/server/agent-url.test.ts | 3 +- .../tools/handlers/materialize-file.test.ts | 2 - .../tools/handlers/upload-file-reader.test.ts | 4 +- .../copilot/tools/handlers/vfs-mutate.test.ts | 33 +++++++------ .../tools/handlers/workflow/mutations.test.ts | 32 +++++++----- .../server/files/file-intent-store.test.ts | 5 +- .../server/knowledge/knowledge-base.test.ts | 13 ++--- .../tools/server/user/get-credentials.test.ts | 27 +++++----- .../user/set-environment-variables.test.ts | 32 ++++++------ .../validation/selector-validator.test.ts | 4 +- apps/sim/lib/copilot/vfs/file-reader.test.ts | 2 - .../lib/core/config/block-visibility.test.ts | 25 +++++----- apps/sim/lib/core/idempotency/cleanup.test.ts | 3 +- apps/sim/lib/core/outbox/service.test.ts | 2 - .../rate-limiter/hosted-key/queue.test.ts | 4 +- .../core/rate-limiter/storage/factory.test.ts | 4 +- apps/sim/lib/core/utils.test.ts | 43 ++++++++-------- apps/sim/lib/credentials/environment.test.ts | 5 +- .../validators/trello.test.ts | 17 +++---- apps/sim/lib/data-drains/dispatcher.test.ts | 10 +--- apps/sim/lib/data-drains/service.test.ts | 4 +- apps/sim/lib/execution/cancellation.test.ts | 3 +- apps/sim/lib/execution/e2b.test.ts | 11 ++++- apps/sim/lib/execution/event-buffer.test.ts | 14 +++--- apps/sim/lib/execution/isolated-vm.test.ts | 8 +-- apps/sim/lib/execution/preprocessing.test.ts | 23 ++++----- .../preprocessing.webhook-correlation.test.ts | 24 +++++---- apps/sim/lib/guardrails/mask-client.test.ts | 11 +++-- apps/sim/lib/invitations/core.test.ts | 2 - apps/sim/lib/invitations/direct-grant.test.ts | 9 +--- .../invitations/workspace-invitations.test.ts | 9 +--- .../lib/knowledge/connectors/queue.test.ts | 3 +- .../knowledge/connectors/sync-engine.test.ts | 4 +- .../knowledge/documents/lock-order.test.ts | 4 +- .../documents/processing-queue.test.ts | 2 - .../documents/storage-billing.test.ts | 2 - apps/sim/lib/knowledge/service.test.ts | 9 +--- .../logs/execution/progress-markers.test.ts | 13 ++--- apps/sim/lib/mcp/connection-pool.test.ts | 4 -- apps/sim/lib/mcp/oauth/revoke.test.ts | 3 +- apps/sim/lib/mcp/oauth/storage.test.ts | 21 +++----- .../orchestration/server-lifecycle.test.ts | 4 -- .../workflow-mcp-lifecycle.test.ts | 1 - apps/sim/lib/mcp/service-pool.test.ts | 4 +- apps/sim/lib/mcp/service.test.ts | 4 +- .../messaging/email/providers/gmail.test.ts | 32 ++++++------ .../lib/messaging/email/unsubscribe.test.ts | 13 +++-- apps/sim/lib/messaging/email/utils.test.ts | 16 +++--- .../oauth/__tests__/terminal-errors.test.ts | 5 +- apps/sim/lib/oauth/oauth.test.ts | 12 +++-- .../lib/organizations/settings-access.test.ts | 5 +- .../table/__tests__/find-row-matches.test.ts | 4 +- .../lib/table/__tests__/lock-order.test.ts | 4 +- .../service-filter-threading.test.ts | 4 +- .../lib/table/__tests__/update-row.test.ts | 4 +- apps/sim/lib/table/cell-write.test.ts | 4 +- .../lib/table/dispatch-concurrency.test.ts | 49 +++++++------------ apps/sim/lib/table/events.test.ts | 13 +++-- apps/sim/lib/table/rows/executions.test.ts | 5 +- apps/sim/lib/table/snapshot-cache.test.ts | 3 +- .../workspace/track-chat-upload.test.ts | 4 +- .../workspace-file-manager-errors.test.ts | 5 +- .../workspace-file-storage-accounting.test.ts | 4 +- .../workspace-file-storage-billing.test.ts | 4 +- .../utils/user-file-base64.server.test.ts | 12 ++--- apps/sim/lib/webhooks/env-resolver.test.ts | 11 ++--- .../lib/webhooks/pending-verification.test.ts | 7 +-- apps/sim/lib/webhooks/processor.test.ts | 4 -- .../webhooks/provider-subscriptions.test.ts | 15 +++--- .../lib/workflows/deployment-outbox.test.ts | 13 ----- .../workflows/executor/execution-core.test.ts | 14 +++--- .../executor/execution-id-claim.test.ts | 4 +- .../human-in-the-loop-manager.test.ts | 4 +- apps/sim/lib/workflows/lifecycle.test.ts | 18 +++---- .../workflows/orchestration/deploy.test.ts | 9 ---- .../sanitization/json-sanitizer.test.ts | 11 +++-- apps/sim/lib/workflows/utils.test.ts | 20 +++----- apps/sim/lib/workspace-events/emitter.test.ts | 15 +++--- .../lib/workspace-events/no-activity.test.ts | 4 +- apps/sim/lib/workspace-events/rules.test.ts | 5 +- apps/sim/lib/workspaces/admin-move.test.ts | 6 +-- apps/sim/lib/workspaces/lifecycle.test.ts | 3 -- .../organization-workspaces.test.ts | 10 +--- apps/sim/lib/workspaces/policy.test.ts | 10 +--- apps/sim/lib/workspaces/utils.test.ts | 4 +- 138 files changed, 514 insertions(+), 847 deletions(-) diff --git a/apps/sim/lib/admin/dashboard-credit-grant.test.ts b/apps/sim/lib/admin/dashboard-credit-grant.test.ts index 57e9f4677f8..d4a189418c8 100644 --- a/apps/sim/lib/admin/dashboard-credit-grant.test.ts +++ b/apps/sim/lib/admin/dashboard-credit-grant.test.ts @@ -2,7 +2,7 @@ * @vitest-environment node */ import { member, organization, subscription, user, userStats, workspace } from '@sim/db/schema' -import { dbChainMock, dbChainMockFns, queueTableRows, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, queueTableRows, resetDbChainMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const mocks = vi.hoisted(() => ({ @@ -24,7 +24,6 @@ vi.mock('@sim/audit', () => ({ AuditResourceType: { BILLING: 'billing' }, recordAudit: mocks.recordAudit, })) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@/lib/core/idempotency/transaction', () => ({ executeTransactionallyIdempotent: async ( _tx: unknown, diff --git a/apps/sim/lib/admin/dashboard-organizations.test.ts b/apps/sim/lib/admin/dashboard-organizations.test.ts index 3bf092f9585..78009af4597 100644 --- a/apps/sim/lib/admin/dashboard-organizations.test.ts +++ b/apps/sim/lib/admin/dashboard-organizations.test.ts @@ -1,7 +1,7 @@ /** @vitest-environment node */ import { member, organization, permissions, subscription } from '@sim/db/schema' -import { dbChainMock, dbChainMockFns, queueTableRows, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, queueTableRows, resetDbChainMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' vi.unmock('drizzle-orm') @@ -15,7 +15,6 @@ vi.mock('@sim/audit', () => ({ AuditResourceType: {}, recordAudit: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) /** * Cuts the import chain dashboard.ts -> admin-move.ts -> invitations/core -> * lib/auth/auth.ts. The auth module throws at import time when another suite diff --git a/apps/sim/lib/admin/external-collaborators.test.ts b/apps/sim/lib/admin/external-collaborators.test.ts index 8cdff708d0b..faab5dc0658 100644 --- a/apps/sim/lib/admin/external-collaborators.test.ts +++ b/apps/sim/lib/admin/external-collaborators.test.ts @@ -2,7 +2,7 @@ * @vitest-environment node */ import { member, permissions } from '@sim/db/schema' -import { dbChainMock, queueTableRows, resetDbChainMock } from '@sim/testing' +import { queueTableRows, resetDbChainMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const mocks = vi.hoisted(() => ({ @@ -17,8 +17,6 @@ vi.mock('@sim/audit', () => ({ recordAudit: mocks.recordAudit, })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/organizations/member-limits', () => ({ setOrgMemberUsageLimit: mocks.setLimit, })) diff --git a/apps/sim/lib/api-key/byok.test.ts b/apps/sim/lib/api-key/byok.test.ts index b049ae42738..7bd905b16c5 100644 --- a/apps/sim/lib/api-key/byok.test.ts +++ b/apps/sim/lib/api-key/byok.test.ts @@ -1,15 +1,13 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockDecryptSecret } = vi.hoisted(() => ({ mockDecryptSecret: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/core/security/encryption', () => ({ decryptSecret: mockDecryptSecret, })) diff --git a/apps/sim/lib/api-key/crypto.test.ts b/apps/sim/lib/api-key/crypto.test.ts index f6243b876e4..334a90ac9c7 100644 --- a/apps/sim/lib/api-key/crypto.test.ts +++ b/apps/sim/lib/api-key/crypto.test.ts @@ -9,15 +9,14 @@ * @vitest-environment node */ import { randomBytes } from 'crypto' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { resetEnvMock, setEnv } from '@sim/testing' +import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest' -const { mockEnv } = vi.hoisted(() => ({ - mockEnv: { API_ENCRYPTION_KEY: undefined as string | undefined }, -})) +beforeAll(() => { + setEnv({ API_ENCRYPTION_KEY: undefined }) +}) -vi.mock('@/lib/core/config/env', () => ({ - env: mockEnv, -})) +afterAll(resetEnvMock) import { decryptApiKey, @@ -52,7 +51,7 @@ describe('hashApiKey', () => { describe('backfill idempotency — encrypted round-trip', () => { beforeEach(() => { - mockEnv.API_ENCRYPTION_KEY = FIXED_ENCRYPTION_KEY + setEnv({ API_ENCRYPTION_KEY: FIXED_ENCRYPTION_KEY }) }) it('re-running the backfill on the same row yields the same keyHash', async () => { diff --git a/apps/sim/lib/api-key/service.test.ts b/apps/sim/lib/api-key/service.test.ts index 04011bb46a8..898b4f5ea9e 100644 --- a/apps/sim/lib/api-key/service.test.ts +++ b/apps/sim/lib/api-key/service.test.ts @@ -7,11 +7,9 @@ * * @vitest-environment node */ -import { dbChainMock, dbChainMockFns } from '@sim/testing' +import { dbChainMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) - const { serviceLogger } = vi.hoisted(() => { const logger = { info: vi.fn(), diff --git a/apps/sim/lib/auth/access-control.test.ts b/apps/sim/lib/auth/access-control.test.ts index 38b50e378e8..a58b7df6d33 100644 --- a/apps/sim/lib/auth/access-control.test.ts +++ b/apps/sim/lib/auth/access-control.test.ts @@ -1,33 +1,30 @@ /** * @vitest-environment node */ -import { resetEnvFlagsMock, setEnvFlags } from '@sim/testing' -import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' +import { resetEnvFlagsMock, resetEnvMock, setEnv, setEnvFlags } from '@sim/testing' +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' import type { AccessControlConfig } from '@/lib/auth/access-control' -const { mockFetch, envRef } = vi.hoisted(() => ({ +const { mockFetch } = vi.hoisted(() => ({ mockFetch: vi.fn(), - envRef: { - APPCONFIG_APPLICATION: 'sim-staging' as string | undefined, - APPCONFIG_ENVIRONMENT: 'staging' as string | undefined, - BLOCKED_SIGNUP_DOMAINS: undefined as string | undefined, - BLOCKED_EMAILS: undefined as string | undefined, - ALLOWED_LOGIN_EMAILS: undefined as string | undefined, - ALLOWED_LOGIN_DOMAINS: undefined as string | undefined, - BLOCKED_EMAIL_MX_HOSTS: undefined as string | undefined, - }, })) +beforeAll(() => { + setEnv({ + APPCONFIG_APPLICATION: 'sim-staging', + APPCONFIG_ENVIRONMENT: 'staging', + BLOCKED_SIGNUP_DOMAINS: undefined, + BLOCKED_EMAILS: undefined, + ALLOWED_LOGIN_EMAILS: undefined, + ALLOWED_LOGIN_DOMAINS: undefined, + BLOCKED_EMAIL_MX_HOSTS: undefined, + }) +}) + vi.mock('@/lib/core/config/appconfig', () => ({ fetchAppConfigProfile: mockFetch, })) -vi.mock('@/lib/core/config/env', () => ({ - get env() { - return envRef - }, -})) - import { getAccessControlConfig, isEmailBlockedByAccessControl, @@ -42,13 +39,16 @@ const empty: AccessControlConfig = { blockedEmailMxHosts: [], } -afterAll(resetEnvFlagsMock) +afterAll(() => { + resetEnvFlagsMock() + resetEnvMock() +}) describe('getAccessControlConfig', () => { beforeEach(() => { vi.clearAllMocks() setEnvFlags({ isAppConfigEnabled: false }) - Object.assign(envRef, { + setEnv({ BLOCKED_SIGNUP_DOMAINS: undefined, BLOCKED_EMAILS: undefined, ALLOWED_LOGIN_EMAILS: undefined, @@ -64,9 +64,9 @@ describe('getAccessControlConfig', () => { }) it('parses, trims, lowercases, and dedupes csv env vars', async () => { - envRef.BLOCKED_SIGNUP_DOMAINS = 'Gmail.com, yahoo.com ,gmail.com,' - envRef.BLOCKED_EMAILS = 'Spam@Evil.com, spam@evil.com' - envRef.ALLOWED_LOGIN_DOMAINS = 'Sim.ai' + setEnv({ BLOCKED_SIGNUP_DOMAINS: 'Gmail.com, yahoo.com ,gmail.com,' }) + setEnv({ BLOCKED_EMAILS: 'Spam@Evil.com, spam@evil.com' }) + setEnv({ ALLOWED_LOGIN_DOMAINS: 'Sim.ai' }) const result = await getAccessControlConfig() expect(result.blockedSignupDomains).toEqual(['gmail.com', 'yahoo.com']) expect(result.blockedEmails).toEqual(['spam@evil.com']) @@ -102,7 +102,7 @@ describe('getAccessControlConfig', () => { }) it('falls back to env vars when the fetch yields null', async () => { - envRef.BLOCKED_SIGNUP_DOMAINS = 'spam.example' + setEnv({ BLOCKED_SIGNUP_DOMAINS: 'spam.example' }) mockFetch.mockResolvedValue(null) const result = await getAccessControlConfig() expect(result.blockedSignupDomains).toEqual(['spam.example']) diff --git a/apps/sim/lib/auth/ban.test.ts b/apps/sim/lib/auth/ban.test.ts index a6c81ad4faf..52bedf850f7 100644 --- a/apps/sim/lib/auth/ban.test.ts +++ b/apps/sim/lib/auth/ban.test.ts @@ -7,28 +7,25 @@ import { dbChainMockFns, queueTableRows, resetDbChainMock, + resetEnvMock, schemaMock, + setEnv, } from '@sim/testing' -import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' - -const { envRef } = vi.hoisted(() => ({ - envRef: { - BLOCKED_SIGNUP_DOMAINS: undefined as string | undefined, - BLOCKED_EMAILS: undefined as string | undefined, - }, -})) +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' vi.mock('@sim/db', () => ({ ...dbChainMock, ...schemaMock })) vi.mock('@/lib/core/config/appconfig', () => ({ fetchAppConfigProfile: vi.fn() })) -vi.mock('@/lib/core/config/env', () => ({ - get env() { - return envRef - }, -})) import { getActivelyBannedUserIds, isBanActive, isEmailBlocked } from '@/lib/auth/ban' -afterAll(resetDbChainMock) +beforeAll(() => { + setEnv({ BLOCKED_SIGNUP_DOMAINS: undefined, BLOCKED_EMAILS: undefined }) +}) + +afterAll(() => { + resetDbChainMock() + resetEnvMock() +}) describe('isBanActive', () => { it('returns true for a permanent ban', () => { @@ -53,8 +50,8 @@ describe('isEmailBlocked', () => { beforeEach(() => { vi.clearAllMocks() resetDbChainMock() - envRef.BLOCKED_SIGNUP_DOMAINS = 'bad.com' - envRef.BLOCKED_EMAILS = 'spam@evil.com' + setEnv({ BLOCKED_SIGNUP_DOMAINS: 'bad.com' }) + setEnv({ BLOCKED_EMAILS: 'spam@evil.com' }) }) it('returns true for blocked domains and subdomains without querying users', async () => { @@ -84,8 +81,8 @@ describe('getActivelyBannedUserIds', () => { beforeEach(() => { vi.clearAllMocks() resetDbChainMock() - envRef.BLOCKED_SIGNUP_DOMAINS = undefined - envRef.BLOCKED_EMAILS = undefined + setEnv({ BLOCKED_SIGNUP_DOMAINS: undefined }) + setEnv({ BLOCKED_EMAILS: undefined }) }) it('short-circuits on empty input without querying', async () => { @@ -110,7 +107,7 @@ describe('getActivelyBannedUserIds', () => { }) it('returns ids whose email is individually blocked', async () => { - envRef.BLOCKED_EMAILS = 'spam@evil.com' + setEnv({ BLOCKED_EMAILS: 'spam@evil.com' }) queueTableRows(user, [ { id: 'u1', email: 'spam@evil.com', banned: false, banExpires: null }, { id: 'u2', email: 'ok@evil.com', banned: false, banExpires: null }, @@ -119,7 +116,7 @@ describe('getActivelyBannedUserIds', () => { }) it('returns ids whose email domain is in the blocked-domains list, including subdomains', async () => { - envRef.BLOCKED_SIGNUP_DOMAINS = 'bad.com' + setEnv({ BLOCKED_SIGNUP_DOMAINS: 'bad.com' }) queueTableRows(user, [ { id: 'u1', email: 'a@bad.com', banned: false, banExpires: null }, { id: 'u2', email: 'b@mail.bad.com', banned: false, banExpires: null }, diff --git a/apps/sim/lib/auth/hybrid.test.ts b/apps/sim/lib/auth/hybrid.test.ts index a67bf018ad9..35d37188b90 100644 --- a/apps/sim/lib/auth/hybrid.test.ts +++ b/apps/sim/lib/auth/hybrid.test.ts @@ -1,20 +1,23 @@ /** * @vitest-environment node */ + +import { authMockFns } from '@sim/testing' import { NextRequest } from 'next/server' -import { beforeEach, describe, expect, it, vi } from 'vitest' - -const { - mockAuthenticateApiKeyFromHeader, - mockGetSession, - mockUpdateApiKeyLastUsed, - mockVerifyInternalToken, -} = vi.hoisted(() => ({ - mockAuthenticateApiKeyFromHeader: vi.fn(), - mockGetSession: vi.fn(), - mockUpdateApiKeyLastUsed: vi.fn(), - mockVerifyInternalToken: vi.fn(), -})) +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockAuthenticateApiKeyFromHeader, mockUpdateApiKeyLastUsed, mockVerifyInternalToken } = + vi.hoisted(() => ({ + mockAuthenticateApiKeyFromHeader: vi.fn(), + mockUpdateApiKeyLastUsed: vi.fn(), + mockVerifyInternalToken: vi.fn(), + })) + +const mockGetSession = authMockFns.mockGetSession + +afterAll(() => { + mockGetSession.mockReset() +}) vi.unmock('@/lib/auth/hybrid') @@ -23,10 +26,6 @@ vi.mock('@/lib/api-key/service', () => ({ updateApiKeyLastUsed: mockUpdateApiKeyLastUsed, })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - vi.mock('@/lib/auth/internal', () => ({ verifyInternalToken: mockVerifyInternalToken, })) diff --git a/apps/sim/lib/auth/stripe-adapter-guard.test.ts b/apps/sim/lib/auth/stripe-adapter-guard.test.ts index 8c8521041ee..824921f0a71 100644 --- a/apps/sim/lib/auth/stripe-adapter-guard.test.ts +++ b/apps/sim/lib/auth/stripe-adapter-guard.test.ts @@ -1,11 +1,8 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' - -vi.mock('@sim/db', () => dbChainMock) - import { guardSubscriptionPlanWrites } from '@/lib/auth/stripe-adapter-guard' function createBaseAdapter() { diff --git a/apps/sim/lib/billing/authorization.test.ts b/apps/sim/lib/billing/authorization.test.ts index 457564f13a8..e2843bd47a0 100644 --- a/apps/sim/lib/billing/authorization.test.ts +++ b/apps/sim/lib/billing/authorization.test.ts @@ -1,7 +1,7 @@ /** * @vitest-environment node */ -import { dbChainMock, resetDbChainMock } from '@sim/testing' +import { resetDbChainMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -16,7 +16,6 @@ const { mockGetOrganizationCoverageForMember: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@/lib/billing', () => ({ hasPaidSubscription: mockHasPaidSubscription })) vi.mock('@/lib/billing/core/organization', () => ({ isOrganizationOwnerOrAdmin: mockIsOwnerOrAdmin, diff --git a/apps/sim/lib/billing/calculations/usage-monitor.test.ts b/apps/sim/lib/billing/calculations/usage-monitor.test.ts index ddd9b902474..ef47567b436 100644 --- a/apps/sim/lib/billing/calculations/usage-monitor.test.ts +++ b/apps/sim/lib/billing/calculations/usage-monitor.test.ts @@ -1,13 +1,7 @@ /** * @vitest-environment node */ -import { - dbChainMock, - dbChainMockFns, - resetDbChainMock, - resetEnvFlagsMock, - setEnvFlags, -} from '@sim/testing' +import { dbChainMockFns, resetDbChainMock, resetEnvFlagsMock, setEnvFlags } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -20,8 +14,6 @@ const { mockIsOrganizationBillingBlocked: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/organizations/member-limits', () => ({ getOrgMemberUsageForBillingPeriod: mockGetOrgMemberUsageForBillingPeriod, getOrgMemberUsageLimit: mockGetOrgMemberUsageLimit, diff --git a/apps/sim/lib/billing/calculations/usage-reservation-env.test.ts b/apps/sim/lib/billing/calculations/usage-reservation-env.test.ts index 1a2697253c0..353306d1eaa 100644 --- a/apps/sim/lib/billing/calculations/usage-reservation-env.test.ts +++ b/apps/sim/lib/billing/calculations/usage-reservation-env.test.ts @@ -1,45 +1,35 @@ /** * @vitest-environment node */ -import { redisConfigMock, redisConfigMockFns, resetEnvFlagsMock, setEnvFlags } from '@sim/testing' +import { + redisConfigMockFns, + resetEnvFlagsMock, + resetEnvMock, + setEnv, + setEnvFlags, +} from '@sim/testing' import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' -const { evalMock, mockEnv } = vi.hoisted(() => ({ +const { evalMock } = vi.hoisted(() => ({ evalMock: vi.fn(), - mockEnv: { - BILLING_CONCURRENCY_LIMIT_FREE: '11', - BILLING_CONCURRENCY_LIMIT_PRO: '55', - BILLING_CONCURRENCY_LIMIT_TEAM: '222', - BILLING_CONCURRENCY_LIMIT_ENTERPRISE: '1111', - }, })) -vi.mock('@/lib/core/config/env', () => ({ - env: mockEnv, - envNumber: ( - value: number | string | undefined | null, - fallback: number, - options: { min?: number; integer?: boolean } = {} - ) => { - const parsed = Number(value) - const min = options.min ?? 0 - return Number.isFinite(parsed) && - parsed >= min && - (!options.integer || Number.isInteger(parsed)) - ? parsed - : fallback - }, -})) - -vi.mock('@/lib/core/config/redis', () => redisConfigMock) - import { reserveExecutionSlot } from '@/lib/billing/calculations/usage-reservation' beforeAll(() => { setEnvFlags({ isBillingEnabled: true, isHosted: true }) + setEnv({ + BILLING_CONCURRENCY_LIMIT_FREE: '11', + BILLING_CONCURRENCY_LIMIT_PRO: '55', + BILLING_CONCURRENCY_LIMIT_TEAM: '222', + BILLING_CONCURRENCY_LIMIT_ENTERPRISE: '1111', + }) }) -afterAll(resetEnvFlagsMock) +afterAll(() => { + resetEnvFlagsMock() + resetEnvMock() +}) describe('usage reservation environment overrides', () => { beforeEach(() => { diff --git a/apps/sim/lib/billing/calculations/usage-reservation.test.ts b/apps/sim/lib/billing/calculations/usage-reservation.test.ts index 23615006588..8b45b5aca83 100644 --- a/apps/sim/lib/billing/calculations/usage-reservation.test.ts +++ b/apps/sim/lib/billing/calculations/usage-reservation.test.ts @@ -1,11 +1,8 @@ /** * @vitest-environment node */ -import { redisConfigMock, redisConfigMockFns, resetEnvFlagsMock, setEnvFlags } from '@sim/testing' +import { redisConfigMockFns, resetEnvFlagsMock, setEnvFlags } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' - -vi.mock('@/lib/core/config/redis', () => redisConfigMock) - import { releaseExecutionSlot, reserveExecutionSlot, diff --git a/apps/sim/lib/billing/cleanup-dispatcher.test.ts b/apps/sim/lib/billing/cleanup-dispatcher.test.ts index fccdaf8e6c9..e923da4135a 100644 --- a/apps/sim/lib/billing/cleanup-dispatcher.test.ts +++ b/apps/sim/lib/billing/cleanup-dispatcher.test.ts @@ -1,20 +1,13 @@ /** * @vitest-environment node */ -import { - dbChainMock, - dbChainMockFns, - resetDbChainMock, - resetEnvFlagsMock, - setEnvFlags, -} from '@sim/testing' +import { dbChainMockFns, resetDbChainMock, resetEnvFlagsMock, setEnvFlags } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockIsTriggerAvailable } = vi.hoisted(() => ({ mockIsTriggerAvailable: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@/lib/billing/core/billing', () => ({ getOrganizationSubscription: vi.fn() })) vi.mock('@/lib/billing/core/subscription', () => ({ getHighestPriorityPersonalSubscription: vi.fn(), diff --git a/apps/sim/lib/billing/core/billing-attribution.test.ts b/apps/sim/lib/billing/core/billing-attribution.test.ts index 588a5a985c2..67a3b348092 100644 --- a/apps/sim/lib/billing/core/billing-attribution.test.ts +++ b/apps/sim/lib/billing/core/billing-attribution.test.ts @@ -1,13 +1,7 @@ /** * @vitest-environment node */ -import { - dbChainMock, - dbChainMockFns, - resetDbChainMock, - resetEnvFlagsMock, - setEnvFlags, -} from '@sim/testing' +import { dbChainMockFns, resetDbChainMock, resetEnvFlagsMock, setEnvFlags } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -26,8 +20,6 @@ const { mockGetOrganizationSubscription: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/calculations/usage-monitor', () => ({ checkBillingBlocked: mockCheckBillingBlocked, checkBillingEntityBlocked: mockCheckBillingEntityBlocked, diff --git a/apps/sim/lib/billing/core/billing.test.ts b/apps/sim/lib/billing/core/billing.test.ts index fa35c7a46c2..ce938771089 100644 --- a/apps/sim/lib/billing/core/billing.test.ts +++ b/apps/sim/lib/billing/core/billing.test.ts @@ -20,8 +20,6 @@ const { mockResolveBillingInterval: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/core/subscription', () => ({ getHighestPriorityPersonalSubscription: mockGetHighestPriorityPersonalSubscription, getHighestPrioritySubscription: mockGetHighestPrioritySubscription, diff --git a/apps/sim/lib/billing/core/limit-notifications.test.ts b/apps/sim/lib/billing/core/limit-notifications.test.ts index 37ce9ac295a..1d00608d855 100644 --- a/apps/sim/lib/billing/core/limit-notifications.test.ts +++ b/apps/sim/lib/billing/core/limit-notifications.test.ts @@ -2,15 +2,16 @@ * @vitest-environment node */ import { - dbChainMock, dbChainMockFns, queueTableRows, resetDbChainMock, resetEnvFlagsMock, + resetUrlsMock, schemaMock, setEnvFlags, + urlsMockFns, } from '@sim/testing' -import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' const { sendEmailSpy, getEmailPreferencesMock, renderMock, subjectMock, isOrgAdminRoleMock } = vi.hoisted(() => ({ @@ -21,9 +22,6 @@ const { sendEmailSpy, getEmailPreferencesMock, renderMock, subjectMock, isOrgAdm isOrgAdminRoleMock: vi.fn(() => true), })) -vi.mock('@sim/db', () => dbChainMock) - -vi.mock('@/lib/core/utils/urls', () => ({ getBaseUrl: () => 'https://app.sim.ai' })) vi.mock('@/lib/messaging/email/mailer', () => ({ sendEmail: sendEmailSpy })) vi.mock('@/lib/messaging/email/unsubscribe', () => ({ getEmailPreferences: getEmailPreferencesMock, @@ -47,7 +45,14 @@ const baseUserParams = { userName: 'Ada', } -afterAll(resetEnvFlagsMock) +beforeAll(() => { + urlsMockFns.mockGetBaseUrl.mockReturnValue('https://app.sim.ai') +}) + +afterAll(() => { + resetEnvFlagsMock() + resetUrlsMock() +}) describe('maybeSendLimitThresholdEmail', () => { beforeEach(() => { diff --git a/apps/sim/lib/billing/core/plan.test.ts b/apps/sim/lib/billing/core/plan.test.ts index 9e776770eb9..f3dab53c4fd 100644 --- a/apps/sim/lib/billing/core/plan.test.ts +++ b/apps/sim/lib/billing/core/plan.test.ts @@ -2,11 +2,9 @@ * @vitest-environment node */ import { member, organization, subscription } from '@sim/db/schema' -import { dbChainMock, dbChainMockFns, queueTableRows, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, queueTableRows, resetDbChainMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) - /** * Realistic plan-check predicates so `pickHighestPrioritySubscription` exercises * the real Enterprise > Team > Pro priority ordering over the rows we feed it. diff --git a/apps/sim/lib/billing/core/subscription.test.ts b/apps/sim/lib/billing/core/subscription.test.ts index af08492f9e4..758658d95a6 100644 --- a/apps/sim/lib/billing/core/subscription.test.ts +++ b/apps/sim/lib/billing/core/subscription.test.ts @@ -1,7 +1,7 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetEnvFlagsMock, setEnvFlags, urlsMock } from '@sim/testing' +import { dbChainMockFns, resetEnvFlagsMock, setEnvFlags } from '@sim/testing' import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -20,8 +20,6 @@ const { mockHasUsableSubscriptionAccess: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/core/access', () => ({ getEffectiveBillingStatus: vi.fn(), isOrganizationBillingBlocked: vi.fn(), @@ -55,8 +53,6 @@ vi.mock('@/lib/workspaces/permissions/utils', () => ({ getWorkspaceWithOwner: mockGetWorkspaceWithOwner, })) -vi.mock('@/lib/core/utils/urls', () => urlsMock) - import { getOrganizationCoverageForMember, getOrganizationIdForSubscriptionReference, diff --git a/apps/sim/lib/billing/core/usage-log.test.ts b/apps/sim/lib/billing/core/usage-log.test.ts index b03d8ff569b..a4512999cdb 100644 --- a/apps/sim/lib/billing/core/usage-log.test.ts +++ b/apps/sim/lib/billing/core/usage-log.test.ts @@ -2,13 +2,7 @@ * @vitest-environment node */ import { usageLog } from '@sim/db/schema' -import { - dbChainMock, - dbChainMockFns, - resetDbChainMock, - resetEnvFlagsMock, - setEnvFlags, -} from '@sim/testing' +import { dbChainMockFns, resetDbChainMock, resetEnvFlagsMock, setEnvFlags } from '@sim/testing' import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -31,8 +25,6 @@ const { mockUpdate: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/core/plan', () => ({ getHighestPrioritySubscription: mockGetHighestPrioritySubscription, })) diff --git a/apps/sim/lib/billing/core/usage.test.ts b/apps/sim/lib/billing/core/usage.test.ts index a46caa1517a..c855f49ea94 100644 --- a/apps/sim/lib/billing/core/usage.test.ts +++ b/apps/sim/lib/billing/core/usage.test.ts @@ -8,11 +8,9 @@ * * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) - afterAll(() => { resetDbChainMock() }) diff --git a/apps/sim/lib/billing/credits/daily-refresh.test.ts b/apps/sim/lib/billing/credits/daily-refresh.test.ts index 932f487fe9e..760cc1bb729 100644 --- a/apps/sim/lib/billing/credits/daily-refresh.test.ts +++ b/apps/sim/lib/billing/credits/daily-refresh.test.ts @@ -1,11 +1,9 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, drizzleOrmMock } from '@sim/testing' +import { dbChainMockFns, drizzleOrmMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) - vi.mock('drizzle-orm', () => { const sqlTag = () => { const obj: { as: () => typeof obj } = { as: () => obj } diff --git a/apps/sim/lib/billing/enterprise-provisioning.test.ts b/apps/sim/lib/billing/enterprise-provisioning.test.ts index d50dd0a8088..15cc7b7f0bd 100644 --- a/apps/sim/lib/billing/enterprise-provisioning.test.ts +++ b/apps/sim/lib/billing/enterprise-provisioning.test.ts @@ -1,13 +1,7 @@ /** * @vitest-environment node */ -import { - dbChainMock, - dbChainMockFns, - queueTableRows, - resetDbChainMock, - schemaMock, -} from '@sim/testing' +import { dbChainMockFns, queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const mocks = vi.hoisted(() => ({ @@ -32,8 +26,6 @@ vi.mock('@sim/audit', () => ({ recordAudit: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@sim/utils/id', () => ({ generateId: vi.fn(() => 'generated-id') })) vi.mock('@/lib/billing/organizations/membership', () => ({ acquireOrganizationMutationLock: vi.fn(), diff --git a/apps/sim/lib/billing/organization.test.ts b/apps/sim/lib/billing/organization.test.ts index 00709265f20..8886f7b2975 100644 --- a/apps/sim/lib/billing/organization.test.ts +++ b/apps/sim/lib/billing/organization.test.ts @@ -24,8 +24,6 @@ const { mockIsSubscriptionOrgScoped: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/core/billing', () => ({ getPlanPricing: mockGetPlanPricing, isSubscriptionOrgScoped: mockIsSubscriptionOrgScoped, diff --git a/apps/sim/lib/billing/organizations/create-organization.test.ts b/apps/sim/lib/billing/organizations/create-organization.test.ts index a3fb3bfa74d..31fdef2a5d1 100644 --- a/apps/sim/lib/billing/organizations/create-organization.test.ts +++ b/apps/sim/lib/billing/organizations/create-organization.test.ts @@ -1,15 +1,13 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns } from '@sim/testing' +import { dbChainMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { mockGenerateId } = vi.hoisted(() => ({ mockGenerateId: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@sim/utils/id', () => ({ generateId: mockGenerateId, generateShortId: vi.fn(() => 'short-id'), diff --git a/apps/sim/lib/billing/organizations/lock-order.test.ts b/apps/sim/lib/billing/organizations/lock-order.test.ts index f06493cdcab..453828e0c0d 100644 --- a/apps/sim/lib/billing/organizations/lock-order.test.ts +++ b/apps/sim/lib/billing/organizations/lock-order.test.ts @@ -18,7 +18,7 @@ import { userStats, workspace, } from '@sim/db/schema' -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { mockChangeOrganizationWorkspaceBilledAccountsInTx, mockChangeWorkspaceStoragePayersInTx } = @@ -42,8 +42,6 @@ import { import type { DbOrTx } from '@/lib/db/types' import { attachOwnedWorkspacesToOrganizationTx } from '@/lib/workspaces/organization-workspaces' -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/core/outbox/service', () => ({ enqueueOutboxEvent: vi.fn(), })) diff --git a/apps/sim/lib/billing/organizations/member-limits.test.ts b/apps/sim/lib/billing/organizations/member-limits.test.ts index 3558a65337f..0367b73c80f 100644 --- a/apps/sim/lib/billing/organizations/member-limits.test.ts +++ b/apps/sim/lib/billing/organizations/member-limits.test.ts @@ -1,7 +1,7 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, queueTableRows, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, queueTableRows, resetDbChainMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -48,8 +48,6 @@ const { mockGetOrganizationSubscription: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@sim/db/schema', () => schemaTables) vi.mock('drizzle-orm', () => ({ diff --git a/apps/sim/lib/billing/organizations/pause-pro-for-coverage.test.ts b/apps/sim/lib/billing/organizations/pause-pro-for-coverage.test.ts index 4c8e50dedea..6731dfe1a32 100644 --- a/apps/sim/lib/billing/organizations/pause-pro-for-coverage.test.ts +++ b/apps/sim/lib/billing/organizations/pause-pro-for-coverage.test.ts @@ -1,14 +1,13 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { mockEnqueueOutboxEvent } = vi.hoisted(() => ({ mockEnqueueOutboxEvent: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@/lib/billing/storage/payer-transfer', () => ({ changeOrganizationWorkspaceBilledAccountsInTx: vi.fn(), changeWorkspaceStoragePayerInTx: vi.fn(), diff --git a/apps/sim/lib/billing/organizations/provision-seat.test.ts b/apps/sim/lib/billing/organizations/provision-seat.test.ts index 638d84897d4..a5881054627 100644 --- a/apps/sim/lib/billing/organizations/provision-seat.test.ts +++ b/apps/sim/lib/billing/organizations/provision-seat.test.ts @@ -1,7 +1,7 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -24,8 +24,6 @@ const { updateCalls: { value: [] as Array> }, })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/core/billing', () => ({ getOrganizationSubscription: mockGetOrganizationSubscription, })) diff --git a/apps/sim/lib/billing/organizations/seat-drift.test.ts b/apps/sim/lib/billing/organizations/seat-drift.test.ts index 4b36402bdaf..cb5b0bcb2e4 100644 --- a/apps/sim/lib/billing/organizations/seat-drift.test.ts +++ b/apps/sim/lib/billing/organizations/seat-drift.test.ts @@ -2,7 +2,6 @@ * @vitest-environment node */ import { - dbChainMock, queueTableRows, resetDbChainMock, resetEnvFlagsMock, @@ -15,8 +14,6 @@ const { mockReconcileOrganizationSeats } = vi.hoisted(() => ({ mockReconcileOrganizationSeats: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/organizations/seats', () => ({ reconcileOrganizationSeats: mockReconcileOrganizationSeats, })) diff --git a/apps/sim/lib/billing/organizations/seats.test.ts b/apps/sim/lib/billing/organizations/seats.test.ts index fbfac95f4ab..e27e8bf9b80 100644 --- a/apps/sim/lib/billing/organizations/seats.test.ts +++ b/apps/sim/lib/billing/organizations/seats.test.ts @@ -3,7 +3,6 @@ */ import { auditMock, - dbChainMock, dbChainMockFns, queueTableRows, resetDbChainMock, @@ -18,8 +17,6 @@ const { mockSyncSubscriptionUsageLimits, enqueueMock } = vi.hoisted(() => ({ enqueueMock: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/organization', () => ({ syncSubscriptionUsageLimits: mockSyncSubscriptionUsageLimits, })) diff --git a/apps/sim/lib/billing/storage/limits.test.ts b/apps/sim/lib/billing/storage/limits.test.ts index 117dd1d2a58..4b9ffb21fd1 100644 --- a/apps/sim/lib/billing/storage/limits.test.ts +++ b/apps/sim/lib/billing/storage/limits.test.ts @@ -2,12 +2,15 @@ * @vitest-environment node */ import { - dbChainMock, dbChainMockFns, + envMockFns, resetDbChainMock, resetEnvFlagsMock, setEnvFlags, } from '@sim/testing' + +const mockGetEnv = envMockFns.getEnv + import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockEq, mockGetHighestPrioritySubscription } = vi.hoisted(() => ({ @@ -15,8 +18,6 @@ const { mockEq, mockGetHighestPrioritySubscription } = vi.hoisted(() => ({ mockGetHighestPrioritySubscription: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@sim/db/schema', () => ({ organization: { id: 'organization.id', @@ -36,14 +37,6 @@ vi.mock('@/lib/billing/core/subscription', () => ({ getHighestPrioritySubscription: mockGetHighestPrioritySubscription, })) -const { mockGetEnv } = vi.hoisted(() => ({ - mockGetEnv: vi.fn((_variable: string): string | undefined => undefined), -})) - -vi.mock('@/lib/core/config/env', () => ({ - getEnv: mockGetEnv, -})) - import type { StorageBillingContext } from '@/lib/billing/storage/context' import { checkStorageQuota, diff --git a/apps/sim/lib/billing/storage/tracking.test.ts b/apps/sim/lib/billing/storage/tracking.test.ts index 33709cbbcf3..d994395a2ad 100644 --- a/apps/sim/lib/billing/storage/tracking.test.ts +++ b/apps/sim/lib/billing/storage/tracking.test.ts @@ -94,10 +94,6 @@ vi.mock('@/lib/billing/storage/limits', () => ({ isStorageEnforcementEnabled: () => envFlagsMock.isBillingEnabled, })) -vi.mock('@/lib/core/config/env', () => ({ - getEnv: vi.fn(() => undefined), -})) - vi.mock('@sim/logger', () => ({ createLogger: () => ({ error: mockLoggerError, diff --git a/apps/sim/lib/billing/threshold-billing.test.ts b/apps/sim/lib/billing/threshold-billing.test.ts index 63838978487..9e1276b278f 100644 --- a/apps/sim/lib/billing/threshold-billing.test.ts +++ b/apps/sim/lib/billing/threshold-billing.test.ts @@ -1,13 +1,7 @@ /** * @vitest-environment node */ -import { - dbChainMock, - dbChainMockFns, - queueTableRows, - resetDbChainMock, - schemaMock, -} from '@sim/testing' +import { dbChainMockFns, queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -48,8 +42,6 @@ vi.mock('@sim/audit', () => ({ recordAudit: mockRecordAudit, })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/core/access', () => ({ getEffectiveBillingStatus: mockGetEffectiveBillingStatus, isOrganizationBillingBlocked: mockIsOrganizationBillingBlocked, @@ -85,11 +77,6 @@ vi.mock('@/lib/billing/webhooks/outbox-handlers', () => ({ }, })) -vi.mock('@/lib/core/config/env', () => ({ - env: {}, - envNumber: vi.fn((_value: string | undefined, fallback: number) => fallback), -})) - vi.mock('@/lib/core/outbox/service', () => ({ enqueueOutboxEvent: mockEnqueueOutboxEvent, })) diff --git a/apps/sim/lib/billing/validation/seat-management.test.ts b/apps/sim/lib/billing/validation/seat-management.test.ts index 61194fca2ed..a38880a6609 100644 --- a/apps/sim/lib/billing/validation/seat-management.test.ts +++ b/apps/sim/lib/billing/validation/seat-management.test.ts @@ -1,13 +1,7 @@ /** * @vitest-environment node */ -import { - dbChainMock, - dbChainMockFns, - resetDbChainMock, - resetEnvFlagsMock, - setEnvFlags, -} from '@sim/testing' +import { dbChainMockFns, resetDbChainMock, resetEnvFlagsMock, setEnvFlags } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockGetOrganizationSubscription, mockHasInflightOutboxEvent } = vi.hoisted(() => ({ @@ -15,8 +9,6 @@ const { mockGetOrganizationSubscription, mockHasInflightOutboxEvent } = vi.hoist mockHasInflightOutboxEvent: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/core/outbox/service', () => ({ hasInflightOutboxEvent: mockHasInflightOutboxEvent, })) diff --git a/apps/sim/lib/billing/webhooks/enterprise-reconciliation-lease.test.ts b/apps/sim/lib/billing/webhooks/enterprise-reconciliation-lease.test.ts index 8fcff1ee94e..81d65b00f0f 100644 --- a/apps/sim/lib/billing/webhooks/enterprise-reconciliation-lease.test.ts +++ b/apps/sim/lib/billing/webhooks/enterprise-reconciliation-lease.test.ts @@ -1,11 +1,8 @@ /** * @vitest-environment node */ -import { dbChainMock, resetDbChainMock } from '@sim/testing' +import { resetDbChainMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' - -vi.mock('@sim/db', () => dbChainMock) - import { assertEnterpriseReconciliationLeaseHeld, type EnterpriseReconciliationLease, diff --git a/apps/sim/lib/billing/webhooks/invoices.test.ts b/apps/sim/lib/billing/webhooks/invoices.test.ts index 360b954b1c0..8dcdcd52cdd 100644 --- a/apps/sim/lib/billing/webhooks/invoices.test.ts +++ b/apps/sim/lib/billing/webhooks/invoices.test.ts @@ -3,13 +3,10 @@ */ import { createMockStripeEvent, - dbChainMock, dbChainMockFns, - drizzleOrmMock, resetDbChainMock, stripeClientMock, stripePaymentMethodMock, - urlsMock, urlsMockFns, } from '@sim/testing' import type Stripe from 'stripe' @@ -20,9 +17,6 @@ const { mockBlockOrgMembers, mockUnblockOrgMembers } = vi.hoisted(() => ({ mockUnblockOrgMembers: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) -vi.mock('drizzle-orm', () => drizzleOrmMock) - vi.mock('@/components/emails', () => ({ PaymentFailedEmail: vi.fn(), getEmailSubject: vi.fn(), @@ -83,8 +77,6 @@ vi.mock('@/lib/billing/webhooks/idempotency', () => ({ }, })) -vi.mock('@/lib/core/utils/urls', () => urlsMock) - vi.mock('@/lib/messaging/email/mailer', () => ({ sendEmail: vi.fn(), })) diff --git a/apps/sim/lib/billing/webhooks/outbox-handlers.test.ts b/apps/sim/lib/billing/webhooks/outbox-handlers.test.ts index 5cf73d5cb85..1f03bd7a164 100644 --- a/apps/sim/lib/billing/webhooks/outbox-handlers.test.ts +++ b/apps/sim/lib/billing/webhooks/outbox-handlers.test.ts @@ -1,7 +1,7 @@ /** * @vitest-environment node */ -import { dbChainMock, queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' +import { queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockGetPlanByName, mockResolveDefaultPaymentMethod, stripeMock } = vi.hoisted(() => { @@ -18,8 +18,6 @@ const { mockGetPlanByName, mockResolveDefaultPaymentMethod, stripeMock } = vi.ho } }) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/stripe-client', () => ({ requireStripeClient: () => stripeMock, })) diff --git a/apps/sim/lib/concurrency/__tests__/leader-lock.test.ts b/apps/sim/lib/concurrency/__tests__/leader-lock.test.ts index bd0a11c548b..1314056e921 100644 --- a/apps/sim/lib/concurrency/__tests__/leader-lock.test.ts +++ b/apps/sim/lib/concurrency/__tests__/leader-lock.test.ts @@ -1,12 +1,9 @@ /** * @vitest-environment node */ -import { redisConfigMock, redisConfigMockFns } from '@sim/testing' +import { redisConfigMockFns } from '@sim/testing' import { sleep } from '@sim/utils/helpers' import { beforeEach, describe, expect, it, vi } from 'vitest' - -vi.mock('@/lib/core/config/redis', () => redisConfigMock) - import { withLeaderLock } from '@/lib/concurrency/leader-lock' beforeEach(() => { diff --git a/apps/sim/lib/copilot/async-runs/repository.test.ts b/apps/sim/lib/copilot/async-runs/repository.test.ts index 9dd30c73673..a66fab12218 100644 --- a/apps/sim/lib/copilot/async-runs/repository.test.ts +++ b/apps/sim/lib/copilot/async-runs/repository.test.ts @@ -2,11 +2,8 @@ * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' - -vi.mock('@sim/db', () => dbChainMock) - import { claimCompletedAsyncToolCall, completeAsyncToolCall, diff --git a/apps/sim/lib/copilot/auth/permissions.test.ts b/apps/sim/lib/copilot/auth/permissions.test.ts index 93a62fc3171..605f82c0ae9 100644 --- a/apps/sim/lib/copilot/auth/permissions.test.ts +++ b/apps/sim/lib/copilot/auth/permissions.test.ts @@ -1,18 +1,17 @@ /** * @vitest-environment node */ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { workflowAuthzMockFns } from '@sim/testing' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' -const { mockAuthorizeWorkflowByWorkspacePermission } = vi.hoisted(() => ({ - mockAuthorizeWorkflowByWorkspacePermission: vi.fn(), -})) - -vi.mock('@sim/platform-authz/workflow', () => ({ - authorizeWorkflowByWorkspacePermission: mockAuthorizeWorkflowByWorkspacePermission, -})) +const { mockAuthorizeWorkflowByWorkspacePermission } = workflowAuthzMockFns import { createPermissionError, verifyWorkflowAccess } from '@/lib/copilot/auth/permissions' +afterAll(() => { + mockAuthorizeWorkflowByWorkspacePermission.mockReset() +}) + describe('Copilot Auth Permissions', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/apps/sim/lib/copilot/chat/lifecycle.test.ts b/apps/sim/lib/copilot/chat/lifecycle.test.ts index 38dbe31de34..4e214ef22fa 100644 --- a/apps/sim/lib/copilot/chat/lifecycle.test.ts +++ b/apps/sim/lib/copilot/chat/lifecycle.test.ts @@ -1,20 +1,18 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { dbChainMockFns, resetDbChainMock, workflowAuthzMockFns } from '@sim/testing' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) +const { + mockAuthorizeWorkflowByWorkspacePermission: mockAuthorizeWorkflow, + mockGetActiveWorkflowRecord: mockGetActiveWorkflow, +} = workflowAuthzMockFns -const { mockAuthorizeWorkflow, mockGetActiveWorkflow } = vi.hoisted(() => ({ - mockAuthorizeWorkflow: vi.fn(), - mockGetActiveWorkflow: vi.fn(), -})) - -vi.mock('@sim/platform-authz/workflow', () => ({ - authorizeWorkflowByWorkspacePermission: mockAuthorizeWorkflow, - getActiveWorkflowRecord: mockGetActiveWorkflow, -})) +afterAll(() => { + mockAuthorizeWorkflow.mockReset() + mockGetActiveWorkflow.mockReset() +}) vi.mock('@/lib/workspaces/permissions/utils', () => ({ assertActiveWorkspaceAccess: vi.fn(), diff --git a/apps/sim/lib/copilot/chat/messages-store.test.ts b/apps/sim/lib/copilot/chat/messages-store.test.ts index 3ca49834423..5a8b1155c4b 100644 --- a/apps/sim/lib/copilot/chat/messages-store.test.ts +++ b/apps/sim/lib/copilot/chat/messages-store.test.ts @@ -1,11 +1,8 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' - -vi.mock('@sim/db', () => dbChainMock) - import { appendCopilotChatMessages, replaceCopilotChatMessages, diff --git a/apps/sim/lib/copilot/chat/post.test.ts b/apps/sim/lib/copilot/chat/post.test.ts index bace6856104..ba72294c37b 100644 --- a/apps/sim/lib/copilot/chat/post.test.ts +++ b/apps/sim/lib/copilot/chat/post.test.ts @@ -4,10 +4,11 @@ import { authMockFns, - dbChainMock, + environmentUtilsMockFns, permissionsMock, permissionsMockFns, resetDbChainMock, + resetEnvironmentUtilsMock, workflowsUtilsMock, workflowsUtilsMockFns, } from '@sim/testing' @@ -17,8 +18,9 @@ import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const resolveWorkflowIdForUser = workflowsUtilsMockFns.mockResolveWorkflowIdForUser const getUserEntityPermissions = permissionsMockFns.mockGetUserEntityPermissions +const getEffectiveDecryptedEnv = environmentUtilsMockFns.mockGetEffectiveDecryptedEnv + const { - getEffectiveDecryptedEnv, generateWorkspaceSnapshot, processContextsServer, resolveActiveResourceContext, @@ -33,7 +35,6 @@ const { appendCopilotChatMessages, mockPublishStatusChanged, } = vi.hoisted(() => ({ - getEffectiveDecryptedEnv: vi.fn(), generateWorkspaceSnapshot: vi.fn(), processContextsServer: vi.fn(), resolveActiveResourceContext: vi.fn(), @@ -71,10 +72,6 @@ vi.mock('@/lib/billing/core/billing-attribution', () => ({ resolveBillingAttribution, })) -vi.mock('@/lib/environment/utils', () => ({ - getEffectiveDecryptedEnv, -})) - vi.mock('@/lib/copilot/chat/workspace-context', () => ({ generateWorkspaceSnapshot, })) @@ -117,13 +114,12 @@ vi.mock('@/lib/copilot/chat-status', () => ({ }, })) -vi.mock('@sim/db', () => dbChainMock) - import { handleUnifiedChatPost } from './post' describe('handleUnifiedChatPost', () => { afterAll(() => { resetDbChainMock() + resetEnvironmentUtilsMock() }) beforeEach(() => { diff --git a/apps/sim/lib/copilot/chat/process-contents.test.ts b/apps/sim/lib/copilot/chat/process-contents.test.ts index 970e1a9a531..bb2172e71f3 100644 --- a/apps/sim/lib/copilot/chat/process-contents.test.ts +++ b/apps/sim/lib/copilot/chat/process-contents.test.ts @@ -2,7 +2,7 @@ * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, workflowAuthzMockFns } from '@sim/testing' +import { dbChainMockFns, workflowAuthzMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' import type { ChatContext } from '@/stores/panel' @@ -13,11 +13,11 @@ const { discoverServerTools, getSkillById } = vi.hoisted(() => ({ vi.mock('@/lib/workflows/skills/operations', () => ({ getSkillById })) vi.mock('@/lib/mcp/service', () => ({ mcpService: { discoverServerTools } })) + /** * Overrides the global `@sim/db` mock: the logs-context tests below need * controllable row data, which the stable `dbChainMockFns.limit` provides. */ -vi.mock('@sim/db', () => dbChainMock) import { processContextsServer } from './process-contents' diff --git a/apps/sim/lib/copilot/chat/stream-liveness.test.ts b/apps/sim/lib/copilot/chat/stream-liveness.test.ts index 27a17434878..f821be4cc8e 100644 --- a/apps/sim/lib/copilot/chat/stream-liveness.test.ts +++ b/apps/sim/lib/copilot/chat/stream-liveness.test.ts @@ -2,12 +2,10 @@ * @vitest-environment node */ import { copilotChats } from '@sim/db/schema' -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { and, eq } from 'drizzle-orm' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) - const { mockGetChatStreamLockOwners } = vi.hoisted(() => ({ mockGetChatStreamLockOwners: vi.fn(), })) diff --git a/apps/sim/lib/copilot/chat/terminal-state.test.ts b/apps/sim/lib/copilot/chat/terminal-state.test.ts index 7adb8e42ae1..7f211472ce1 100644 --- a/apps/sim/lib/copilot/chat/terminal-state.test.ts +++ b/apps/sim/lib/copilot/chat/terminal-state.test.ts @@ -3,12 +3,10 @@ */ import { copilotChats } from '@sim/db/schema' -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { eq } from 'drizzle-orm' import { beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) - const { mockAppendCopilotChatMessages } = vi.hoisted(() => ({ mockAppendCopilotChatMessages: vi.fn(), })) diff --git a/apps/sim/lib/copilot/request/lifecycle/run.test.ts b/apps/sim/lib/copilot/request/lifecycle/run.test.ts index ad2551c7ce8..89d63bfb61a 100644 --- a/apps/sim/lib/copilot/request/lifecycle/run.test.ts +++ b/apps/sim/lib/copilot/request/lifecycle/run.test.ts @@ -2,14 +2,22 @@ * @vitest-environment node */ -import { resetEnvFlagsMock, setEnvFlags } from '@sim/testing' +import { + environmentUtilsMockFns, + resetEnvFlagsMock, + resetEnvironmentUtilsMock, + setEnvFlags, +} from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' import type { ExecutionContext, StreamingContext } from '@/lib/copilot/request/types' +const mockGetEffectiveDecryptedEnv = environmentUtilsMockFns.mockGetEffectiveDecryptedEnv + +afterAll(resetEnvironmentUtilsMock) + const { mockCreateRunSegment, mockForceFailHungToolCall, - mockGetEffectiveDecryptedEnv, mockGetMothershipBaseURL, mockGetMothershipSourceEnvHeaders, mockPrepareExecutionContext, @@ -20,7 +28,6 @@ const { } = vi.hoisted(() => ({ mockCreateRunSegment: vi.fn(), mockForceFailHungToolCall: vi.fn(), - mockGetEffectiveDecryptedEnv: vi.fn(), mockGetMothershipBaseURL: vi.fn(), mockGetMothershipSourceEnvHeaders: vi.fn(), mockPrepareExecutionContext: vi.fn(), @@ -77,10 +84,6 @@ vi.mock('@/lib/core/config/env', () => ({ isFalsy: vi.fn((value: string | undefined) => value === 'false'), })) -vi.mock('@/lib/environment/utils', () => ({ - getEffectiveDecryptedEnv: mockGetEffectiveDecryptedEnv, -})) - vi.mock('@/lib/copilot/tools/handlers/context', () => ({ prepareExecutionContext: mockPrepareExecutionContext, })) diff --git a/apps/sim/lib/copilot/request/lifecycle/start.test.ts b/apps/sim/lib/copilot/request/lifecycle/start.test.ts index 30733864ff7..1a8541c811b 100644 --- a/apps/sim/lib/copilot/request/lifecycle/start.test.ts +++ b/apps/sim/lib/copilot/request/lifecycle/start.test.ts @@ -5,7 +5,7 @@ import { propagation, trace } from '@opentelemetry/api' import { W3CTraceContextPropagator } from '@opentelemetry/core' import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base' -import { dbChainMock, resetDbChainMock, resetEnvFlagsMock, setEnvFlags } from '@sim/testing' +import { resetDbChainMock, resetEnvFlagsMock, setEnvFlags } from '@sim/testing' import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { MothershipStreamV1CompletionStatus, @@ -113,8 +113,6 @@ vi.mock('@/lib/copilot/request/session/sse', () => ({ SSE_RESPONSE_HEADERS: {}, })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/copilot/chat-status', () => ({ chatPubSub: null, })) diff --git a/apps/sim/lib/copilot/request/session/abort.test.ts b/apps/sim/lib/copilot/request/session/abort.test.ts index 9e8e2feae82..2404b12dc5c 100644 --- a/apps/sim/lib/copilot/request/session/abort.test.ts +++ b/apps/sim/lib/copilot/request/session/abort.test.ts @@ -2,7 +2,7 @@ * @vitest-environment node */ -import { redisConfigMock, redisConfigMockFns } from '@sim/testing' +import { redisConfigMockFns } from '@sim/testing' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const { mockHasAbortMarker, mockClearAbortMarker, mockWriteAbortMarker } = vi.hoisted(() => ({ @@ -11,7 +11,6 @@ const { mockHasAbortMarker, mockClearAbortMarker, mockWriteAbortMarker } = vi.ho mockWriteAbortMarker: vi.fn().mockResolvedValue(undefined), })) -vi.mock('@/lib/core/config/redis', () => redisConfigMock) vi.mock('@/lib/copilot/request/session/buffer', () => ({ hasAbortMarker: mockHasAbortMarker, clearAbortMarker: mockClearAbortMarker, diff --git a/apps/sim/lib/copilot/request/session/buffer.test.ts b/apps/sim/lib/copilot/request/session/buffer.test.ts index 1556ed7c3d5..e0fec738227 100644 --- a/apps/sim/lib/copilot/request/session/buffer.test.ts +++ b/apps/sim/lib/copilot/request/session/buffer.test.ts @@ -2,7 +2,7 @@ * @vitest-environment node */ -import { redisConfigMock, redisConfigMockFns } from '@sim/testing' +import { redisConfigMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' import { MothershipStreamV1EventType, @@ -100,8 +100,6 @@ const createRedisStub = () => { let mockRedis: ReturnType -vi.mock('@/lib/core/config/redis', () => redisConfigMock) - import { allocateCursor, appendEvent, diff --git a/apps/sim/lib/copilot/request/session/explicit-abort.test.ts b/apps/sim/lib/copilot/request/session/explicit-abort.test.ts index 8c267692cf9..5cfcd9efadf 100644 --- a/apps/sim/lib/copilot/request/session/explicit-abort.test.ts +++ b/apps/sim/lib/copilot/request/session/explicit-abort.test.ts @@ -1,7 +1,14 @@ /** * @vitest-environment node */ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { resetEnvMock, setEnv } from '@sim/testing' +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' + +beforeAll(() => { + setEnv({ COPILOT_API_KEY: 'sim-agent-key' }) +}) + +afterAll(resetEnvMock) const { mockFetchGo } = vi.hoisted(() => ({ mockFetchGo: vi.fn(), @@ -16,13 +23,6 @@ vi.mock('@/lib/copilot/server/agent-url', () => ({ getMothershipSourceEnvHeaders: vi.fn().mockReturnValue({ 'X-Sim-Source-Env': 'test' }), })) -vi.mock('@/lib/core/config/env', () => ({ - env: { COPILOT_API_KEY: 'sim-agent-key' }, - getEnv: vi.fn().mockReturnValue(undefined), - isTruthy: (value: unknown) => Boolean(value), - isFalsy: (value: unknown) => value === false, -})) - import { requestExplicitStreamAbort } from '@/lib/copilot/request/session/explicit-abort' describe('requestExplicitStreamAbort', () => { diff --git a/apps/sim/lib/copilot/server/agent-url.test.ts b/apps/sim/lib/copilot/server/agent-url.test.ts index dae4021ad0a..5cdc9ce2f91 100644 --- a/apps/sim/lib/copilot/server/agent-url.test.ts +++ b/apps/sim/lib/copilot/server/agent-url.test.ts @@ -1,5 +1,5 @@ import { user } from '@sim/db/schema' -import { dbChainMock, queueTableRows, resetDbChainMock } from '@sim/testing' +import { queueTableRows, resetDbChainMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' import { getMothershipBaseURL, @@ -16,7 +16,6 @@ const { envMock } = vi.hoisted(() => ({ }, })) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@/lib/api/contracts', () => ({ mothershipEnvironmentSchema: { safeParse: (value: unknown) => diff --git a/apps/sim/lib/copilot/tools/handlers/materialize-file.test.ts b/apps/sim/lib/copilot/tools/handlers/materialize-file.test.ts index d81b81bc17c..d0e2cbc18cd 100644 --- a/apps/sim/lib/copilot/tools/handlers/materialize-file.test.ts +++ b/apps/sim/lib/copilot/tools/handlers/materialize-file.test.ts @@ -28,8 +28,6 @@ const { mockResolveStorageBillingContext: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/copilot/tools/handlers/upload-file-reader', () => ({ findMothershipUploadRowByChatAndName: mockFindUpload, })) diff --git a/apps/sim/lib/copilot/tools/handlers/upload-file-reader.test.ts b/apps/sim/lib/copilot/tools/handlers/upload-file-reader.test.ts index 2f78577deef..2aa574c6a69 100644 --- a/apps/sim/lib/copilot/tools/handlers/upload-file-reader.test.ts +++ b/apps/sim/lib/copilot/tools/handlers/upload-file-reader.test.ts @@ -2,11 +2,9 @@ * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) - const { mockReadFileRecord, mockFetchBuffer } = vi.hoisted(() => ({ mockReadFileRecord: vi.fn(), mockFetchBuffer: vi.fn(), diff --git a/apps/sim/lib/copilot/tools/handlers/vfs-mutate.test.ts b/apps/sim/lib/copilot/tools/handlers/vfs-mutate.test.ts index 1b7860da1df..f14a565c292 100644 --- a/apps/sim/lib/copilot/tools/handlers/vfs-mutate.test.ts +++ b/apps/sim/lib/copilot/tools/handlers/vfs-mutate.test.ts @@ -1,15 +1,19 @@ /** * @vitest-environment node */ -import { dbChainMock, queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' +import { + dbChainMock, + queueTableRows, + resetDbChainMock, + schemaMock, + workflowAuthzMockFns, +} from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const mocks = vi.hoisted(() => ({ ensureWorkspaceAccess: vi.fn(), ensureWorkflowAccess: vi.fn(), getDefaultWorkspaceId: vi.fn(), - assertFolderMutable: vi.fn(), - assertWorkflowMutable: vi.fn(), getWorkspaceFileByName: vi.fn(), findWorkspaceFileFolderIdByPath: vi.fn(), ensureWorkspaceFileFolderPath: vi.fn(), @@ -30,11 +34,6 @@ const mocks = vi.hoisted(() => ({ vi.mock('@sim/db', () => ({ ...dbChainMock, ...schemaMock })) -vi.mock('@sim/platform-authz/workflow', () => ({ - assertFolderMutable: mocks.assertFolderMutable, - assertWorkflowMutable: mocks.assertWorkflowMutable, -})) - vi.mock('@/lib/copilot/tools/handlers/access', () => ({ ensureWorkspaceAccess: mocks.ensureWorkspaceAccess, ensureWorkflowAccess: mocks.ensureWorkflowAccess, @@ -96,8 +95,8 @@ describe('vfs mv/cp', () => { resetDbChainMock() mocks.ensureWorkspaceAccess.mockResolvedValue(undefined) mocks.ensureWorkflowAccess.mockResolvedValue({ workspaceId: 'ws-1', workflow: {} }) - mocks.assertFolderMutable.mockResolvedValue(undefined) - mocks.assertWorkflowMutable.mockResolvedValue(undefined) + workflowAuthzMockFns.mockAssertFolderMutable.mockResolvedValue(undefined) + workflowAuthzMockFns.mockAssertWorkflowMutable.mockResolvedValue(undefined) mocks.verifyFolderWorkspace.mockResolvedValue(true) mocks.listFolders.mockResolvedValue([]) mocks.getWorkspaceFileByName.mockResolvedValue(null) @@ -107,6 +106,8 @@ describe('vfs mv/cp', () => { afterAll(() => { resetDbChainMock() + workflowAuthzMockFns.mockAssertFolderMutable.mockReset().mockResolvedValue(undefined) + workflowAuthzMockFns.mockAssertWorkflowMutable.mockReset().mockResolvedValue(undefined) }) describe('category rules', () => { @@ -289,7 +290,7 @@ describe('vfs mv/cp', () => { context ) - expect(mocks.assertWorkflowMutable).toHaveBeenCalledWith('wf-1') + expect(workflowAuthzMockFns.mockAssertWorkflowMutable).toHaveBeenCalledWith('wf-1') expect(mocks.performUpdateWorkflow).toHaveBeenCalledWith( expect.objectContaining({ workflowId: 'wf-1', name: 'New Name', folderId: null }) ) @@ -309,7 +310,7 @@ describe('vfs mv/cp', () => { context ) - expect(mocks.assertFolderMutable).toHaveBeenCalledWith('fold-1') + expect(workflowAuthzMockFns.mockAssertFolderMutable).toHaveBeenCalledWith('fold-1') expect(mocks.performUpdateWorkflow).toHaveBeenCalledWith( expect.objectContaining({ workflowId: 'wf-1', name: undefined, folderId: 'fold-1' }) ) @@ -318,7 +319,9 @@ describe('vfs mv/cp', () => { it('surfaces locked-workflow rejections per item', async () => { queueTableRows(schemaMock.workflow, [{ id: 'wf-1', name: 'Locked One', folderId: null }]) - mocks.assertWorkflowMutable.mockRejectedValue(new Error('Workflow is locked')) + workflowAuthzMockFns.mockAssertWorkflowMutable.mockRejectedValue( + new Error('Workflow is locked') + ) const result = await executeVfsMv( { sources: ['workflows/Locked%20One'], destination: 'workflows/Renamed' }, @@ -338,7 +341,7 @@ describe('vfs mv/cp', () => { context ) - expect(mocks.assertWorkflowMutable).not.toHaveBeenCalled() + expect(workflowAuthzMockFns.mockAssertWorkflowMutable).not.toHaveBeenCalled() expect(mocks.duplicateWorkflow).toHaveBeenCalledWith( expect.objectContaining({ sourceWorkflowId: 'wf-1', @@ -429,7 +432,7 @@ describe('vfs mv/cp', () => { it('rejects creation inside a locked workflow folder', async () => { mocks.listFolders.mockResolvedValue([]) - mocks.assertFolderMutable.mockRejectedValue(new Error('Folder is locked')) + workflowAuthzMockFns.mockAssertFolderMutable.mockRejectedValue(new Error('Folder is locked')) const result = await executeVfsMkdir({ paths: ['workflows/Locked/Sub'] }, context) diff --git a/apps/sim/lib/copilot/tools/handlers/workflow/mutations.test.ts b/apps/sim/lib/copilot/tools/handlers/workflow/mutations.test.ts index 41e3c6c922e..37d2ee42a92 100644 --- a/apps/sim/lib/copilot/tools/handlers/workflow/mutations.test.ts +++ b/apps/sim/lib/copilot/tools/handlers/workflow/mutations.test.ts @@ -1,8 +1,26 @@ /** * @vitest-environment node */ -import { createEnvMock, dbChainMock, schemaMock, workflowAuthzMockFns } from '@sim/testing' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { + dbChainMock, + requestUtilsMockFns, + resetEnvMock, + schemaMock, + setEnv, + workflowAuthzMockFns, +} from '@sim/testing' +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' + +beforeAll(() => { + setEnv({ INTERNAL_API_SECRET: 'secret', SOCKET_SERVER_URL: 'http://socket.test' }) + requestUtilsMockFns.mockGenerateRequestId.mockReturnValue('request-1') +}) + +afterAll(() => { + resetEnvMock() + requestUtilsMockFns.mockGenerateRequestId.mockReset() +}) + import type { BillingAttributionSnapshot } from '@/lib/billing/core/billing-attribution' import type { ExecutionContext } from '@/lib/copilot/request/types' @@ -61,16 +79,6 @@ vi.mock('@/lib/billing/calculations/usage-reservation', () => ({ UsageReservationUnavailableError: class UsageReservationUnavailableError extends Error {}, })) -vi.mock('@/lib/core/config/env', () => createEnvMock({ INTERNAL_API_SECRET: 'secret' })) - -vi.mock('@/lib/core/utils/request', () => ({ - generateRequestId: () => 'request-1', -})) - -vi.mock('@/lib/core/utils/urls', () => ({ - getSocketServerUrl: () => 'http://socket.test', -})) - vi.mock('@/lib/workflows/executor/execute-workflow', () => ({ executeWorkflow: executeWorkflowMock, })) diff --git a/apps/sim/lib/copilot/tools/server/files/file-intent-store.test.ts b/apps/sim/lib/copilot/tools/server/files/file-intent-store.test.ts index be6852763c3..af26937c4b7 100644 --- a/apps/sim/lib/copilot/tools/server/files/file-intent-store.test.ts +++ b/apps/sim/lib/copilot/tools/server/files/file-intent-store.test.ts @@ -1,14 +1,11 @@ import { generateShortId } from '@sim/utils/id' -import { describe, expect, it, vi } from 'vitest' +import { describe, expect, it } from 'vitest' import { consumeLatestFileIntent, type PendingFileIntent, storeFileIntent, } from './file-intent-store' -// Force the in-memory store path so the test is deterministic and Redis-free. -vi.mock('@/lib/core/config/redis', () => ({ getRedisClient: () => null })) - function makeIntent(overrides: Partial): PendingFileIntent { return { operation: 'update', diff --git a/apps/sim/lib/copilot/tools/server/knowledge/knowledge-base.test.ts b/apps/sim/lib/copilot/tools/server/knowledge/knowledge-base.test.ts index aa0dd96c9f6..af3374b925b 100644 --- a/apps/sim/lib/copilot/tools/server/knowledge/knowledge-base.test.ts +++ b/apps/sim/lib/copilot/tools/server/knowledge/knowledge-base.test.ts @@ -2,8 +2,8 @@ * @vitest-environment node */ import { knowledgeConnector } from '@sim/db/schema' -import { dbChainMock, queueTableRows, resetDbChainMock } from '@sim/testing' -import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' +import { queueTableRows, resetDbChainMock, resetUrlsMock, urlsMockFns } from '@sim/testing' +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockAssertBillingAttributionSnapshot, @@ -19,7 +19,6 @@ const { mockSerializeBillingAttributionHeader: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@/lib/auth/internal', () => ({ generateInternalToken: mockGenerateInternalToken, })) @@ -38,9 +37,11 @@ vi.mock('@/lib/copilot/generated/tool-catalog-v1', () => ({ vi.mock('@/lib/copilot/tools/server/base-tool', () => ({ assertServerToolNotAborted: vi.fn(), })) -vi.mock('@/lib/core/utils/urls', () => ({ - getInternalApiBaseUrl: vi.fn(() => 'http://internal.test'), -})) +beforeAll(() => { + urlsMockFns.mockGetInternalApiBaseUrl.mockReturnValue('http://internal.test') +}) + +afterAll(resetUrlsMock) vi.mock('@/lib/knowledge/documents/service', () => ({ createSingleDocument: vi.fn(), deleteDocument: vi.fn(), diff --git a/apps/sim/lib/copilot/tools/server/user/get-credentials.test.ts b/apps/sim/lib/copilot/tools/server/user/get-credentials.test.ts index 716612512a7..a57b7f4699c 100644 --- a/apps/sim/lib/copilot/tools/server/user/get-credentials.test.ts +++ b/apps/sim/lib/copilot/tools/server/user/get-credentials.test.ts @@ -6,29 +6,30 @@ */ import { account, user } from '@sim/db/schema' -import { dbChainMock, dbChainMockFns, queueTableRows, resetDbChainMock } from '@sim/testing' +import { + dbChainMockFns, + environmentUtilsMockFns, + queueTableRows, + resetDbChainMock, + resetEnvironmentUtilsMock, +} from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const SECRET_ACCESS_TOKEN = 'ya29.a0SECRET_GOOGLE_BEARER_TOKEN_DO_NOT_LEAK' -const { getAllOAuthServicesMock, getPersonalAndWorkspaceEnvMock, decodeJwtMock } = vi.hoisted( - () => ({ - getAllOAuthServicesMock: vi.fn(), - getPersonalAndWorkspaceEnvMock: vi.fn(), - decodeJwtMock: vi.fn(), - }) -) +const { getAllOAuthServicesMock, decodeJwtMock } = vi.hoisted(() => ({ + getAllOAuthServicesMock: vi.fn(), + decodeJwtMock: vi.fn(), +})) + +const getPersonalAndWorkspaceEnvMock = environmentUtilsMockFns.mockGetPersonalAndWorkspaceEnv -vi.mock('@sim/db', () => dbChainMock) +afterAll(resetEnvironmentUtilsMock) vi.mock('@/lib/oauth', () => ({ getAllOAuthServices: getAllOAuthServicesMock, })) -vi.mock('@/lib/environment/utils', () => ({ - getPersonalAndWorkspaceEnv: getPersonalAndWorkspaceEnvMock, -})) - vi.mock('jose', () => ({ decodeJwt: decodeJwtMock, })) diff --git a/apps/sim/lib/copilot/tools/server/user/set-environment-variables.test.ts b/apps/sim/lib/copilot/tools/server/user/set-environment-variables.test.ts index 38813e2383a..e6b159a3da4 100644 --- a/apps/sim/lib/copilot/tools/server/user/set-environment-variables.test.ts +++ b/apps/sim/lib/copilot/tools/server/user/set-environment-variables.test.ts @@ -2,21 +2,22 @@ * @vitest-environment node */ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { environmentUtilsMockFns, resetEnvironmentUtilsMock } from '@sim/testing' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { - ensureWorkflowAccessMock, - ensureWorkspaceAccessMock, - getDefaultWorkspaceIdMock, - upsertPersonalEnvVarsMock, - upsertWorkspaceEnvVarsMock, -} = vi.hoisted(() => ({ - ensureWorkflowAccessMock: vi.fn(), - ensureWorkspaceAccessMock: vi.fn(), - getDefaultWorkspaceIdMock: vi.fn(), - upsertPersonalEnvVarsMock: vi.fn(), - upsertWorkspaceEnvVarsMock: vi.fn(), -})) + mockUpsertPersonalEnvVars: upsertPersonalEnvVarsMock, + mockUpsertWorkspaceEnvVars: upsertWorkspaceEnvVarsMock, +} = environmentUtilsMockFns + +afterAll(resetEnvironmentUtilsMock) + +const { ensureWorkflowAccessMock, ensureWorkspaceAccessMock, getDefaultWorkspaceIdMock } = + vi.hoisted(() => ({ + ensureWorkflowAccessMock: vi.fn(), + ensureWorkspaceAccessMock: vi.fn(), + getDefaultWorkspaceIdMock: vi.fn(), + })) vi.mock('@/lib/copilot/tools/handlers/access', () => ({ ensureWorkflowAccess: ensureWorkflowAccessMock, @@ -24,11 +25,6 @@ vi.mock('@/lib/copilot/tools/handlers/access', () => ({ getDefaultWorkspaceId: getDefaultWorkspaceIdMock, })) -vi.mock('@/lib/environment/utils', () => ({ - upsertPersonalEnvVars: upsertPersonalEnvVarsMock, - upsertWorkspaceEnvVars: upsertWorkspaceEnvVarsMock, -})) - import { setEnvironmentVariablesServerTool } from './set-environment-variables' describe('setEnvironmentVariablesServerTool', () => { diff --git a/apps/sim/lib/copilot/validation/selector-validator.test.ts b/apps/sim/lib/copilot/validation/selector-validator.test.ts index 7d068c03b20..6dbb96d81b1 100644 --- a/apps/sim/lib/copilot/validation/selector-validator.test.ts +++ b/apps/sim/lib/copilot/validation/selector-validator.test.ts @@ -1,15 +1,13 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { mockCheckWorkspaceAccess } = vi.hoisted(() => ({ mockCheckWorkspaceAccess: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/workspaces/permissions/utils', () => ({ checkWorkspaceAccess: mockCheckWorkspaceAccess, })) diff --git a/apps/sim/lib/copilot/vfs/file-reader.test.ts b/apps/sim/lib/copilot/vfs/file-reader.test.ts index f4326b32035..5f070a01c06 100644 --- a/apps/sim/lib/copilot/vfs/file-reader.test.ts +++ b/apps/sim/lib/copilot/vfs/file-reader.test.ts @@ -3,14 +3,12 @@ */ import { randomFillSync } from 'node:crypto' -import { loggerMock } from '@sim/testing' import { describe, expect, it, vi } from 'vitest' const { fetchWorkspaceFileBuffer } = vi.hoisted(() => ({ fetchWorkspaceFileBuffer: vi.fn(), })) -vi.mock('@sim/logger', () => loggerMock) vi.mock('@/lib/uploads/contexts/workspace/workspace-file-manager', () => ({ fetchWorkspaceFileBuffer, })) diff --git a/apps/sim/lib/core/config/block-visibility.test.ts b/apps/sim/lib/core/config/block-visibility.test.ts index 55fabe28702..775a0990510 100644 --- a/apps/sim/lib/core/config/block-visibility.test.ts +++ b/apps/sim/lib/core/config/block-visibility.test.ts @@ -1,28 +1,22 @@ /** * @vitest-environment node */ -import { envFlagsMockFns, resetEnvFlagsMock, setEnvFlags } from '@sim/testing' -import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' +import { envFlagsMockFns, resetEnvFlagsMock, resetEnvMock, setEnv, setEnvFlags } from '@sim/testing' +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' -const { mockFetch, mockIsPlatformAdmin, envRef } = vi.hoisted(() => ({ +const { mockFetch, mockIsPlatformAdmin } = vi.hoisted(() => ({ mockFetch: vi.fn(), mockIsPlatformAdmin: vi.fn(), - envRef: { - APPCONFIG_APPLICATION: 'sim-staging' as string | undefined, - APPCONFIG_ENVIRONMENT: 'staging' as string | undefined, - }, })) +beforeAll(() => { + setEnv({ APPCONFIG_APPLICATION: 'sim-staging', APPCONFIG_ENVIRONMENT: 'staging' }) +}) + vi.mock('@/lib/core/config/appconfig', () => ({ fetchAppConfigProfile: mockFetch, })) -vi.mock('@/lib/core/config/env', () => ({ - get env() { - return envRef - }, -})) - vi.mock('@/lib/permissions/super-user', () => ({ isPlatformAdmin: mockIsPlatformAdmin, })) @@ -35,7 +29,10 @@ function withAppConfig(doc: unknown) { mockFetch.mockImplementation((_ids, parse) => Promise.resolve(parse(doc))) } -afterAll(resetEnvFlagsMock) +afterAll(() => { + resetEnvFlagsMock() + resetEnvMock() +}) describe('getBlockVisibility', () => { beforeEach(() => { diff --git a/apps/sim/lib/core/idempotency/cleanup.test.ts b/apps/sim/lib/core/idempotency/cleanup.test.ts index 3e460083168..1777b8b1ea9 100644 --- a/apps/sim/lib/core/idempotency/cleanup.test.ts +++ b/apps/sim/lib/core/idempotency/cleanup.test.ts @@ -2,11 +2,10 @@ * @vitest-environment node */ import { idempotencyKey } from '@sim/db/schema' -import { dbChainMock, resetDbChainMock } from '@sim/testing' +import { resetDbChainMock } from '@sim/testing' import { like, notLike } from 'drizzle-orm' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) vi.mock('@sim/utils/helpers', () => ({ sleep: vi.fn() })) import { cleanupExpiredIdempotencyKeys } from '@/lib/core/idempotency/cleanup' diff --git a/apps/sim/lib/core/outbox/service.test.ts b/apps/sim/lib/core/outbox/service.test.ts index 0c3390fbf95..7502ed5b8e8 100644 --- a/apps/sim/lib/core/outbox/service.test.ts +++ b/apps/sim/lib/core/outbox/service.test.ts @@ -20,8 +20,6 @@ type OutboxRow = { processedAt: Date | null } -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@sim/utils/id', () => ({ generateId: vi.fn(() => 'test-event-id'), })) diff --git a/apps/sim/lib/core/rate-limiter/hosted-key/queue.test.ts b/apps/sim/lib/core/rate-limiter/hosted-key/queue.test.ts index d3d997d215a..0883618bebe 100644 --- a/apps/sim/lib/core/rate-limiter/hosted-key/queue.test.ts +++ b/apps/sim/lib/core/rate-limiter/hosted-key/queue.test.ts @@ -1,9 +1,7 @@ -import { redisConfigMock, redisConfigMockFns } from '@sim/testing' +import { redisConfigMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest' import { HostedKeyQueue } from './queue' -vi.mock('@/lib/core/config/redis', () => redisConfigMock) - interface MockPipeline { rpush: Mock expire: Mock diff --git a/apps/sim/lib/core/rate-limiter/storage/factory.test.ts b/apps/sim/lib/core/rate-limiter/storage/factory.test.ts index 4827d142d9a..60507e1235d 100644 --- a/apps/sim/lib/core/rate-limiter/storage/factory.test.ts +++ b/apps/sim/lib/core/rate-limiter/storage/factory.test.ts @@ -1,4 +1,4 @@ -import { redisConfigMock, redisConfigMockFns } from '@sim/testing' +import { redisConfigMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { mockGetStorageMethod, reconnectCallbacks } = vi.hoisted(() => { @@ -12,8 +12,6 @@ const { mockGetStorageMethod, reconnectCallbacks } = vi.hoisted(() => { const mockGetRedisClient = redisConfigMockFns.mockGetRedisClient const mockOnRedisReconnect = redisConfigMockFns.mockOnRedisReconnect -vi.mock('@/lib/core/config/redis', () => redisConfigMock) - vi.mock('@/lib/core/storage', () => ({ getStorageMethod: mockGetStorageMethod, })) diff --git a/apps/sim/lib/core/utils.test.ts b/apps/sim/lib/core/utils.test.ts index 7ccb5dc75cf..62753f61204 100644 --- a/apps/sim/lib/core/utils.test.ts +++ b/apps/sim/lib/core/utils.test.ts @@ -1,5 +1,5 @@ import { cn } from '@sim/emcn' -import { createEnvMock } from '@sim/testing' +import { resetEnvMock, setEnv } from '@sim/testing' import { formatDate, formatDateTime, @@ -7,7 +7,28 @@ import { formatTime, getTimezoneAbbreviation, } from '@sim/utils/formatting' -import { afterEach, describe, expect, it, vi } from 'vitest' +import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest' + +beforeAll(() => { + setEnv({ + ENCRYPTION_KEY: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + OPENAI_API_KEY_1: 'test-openai-key-1', + OPENAI_API_KEY_2: 'test-openai-key-2', + OPENAI_API_KEY_3: 'test-openai-key-3', + ANTHROPIC_API_KEY_1: 'test-anthropic-key-1', + ANTHROPIC_API_KEY_2: 'test-anthropic-key-2', + ANTHROPIC_API_KEY_3: 'test-anthropic-key-3', + GEMINI_API_KEY_1: 'test-gemini-key-1', + GEMINI_API_KEY_2: 'test-gemini-key-2', + GEMINI_API_KEY_3: 'test-gemini-key-3', + XAI_API_KEY_1: 'test-xai-key-1', + XAI_API_KEY_2: 'test-xai-key-2', + XAI_API_KEY_3: 'test-xai-key-3', + }) +}) + +afterAll(resetEnvMock) + import { getRotatingApiKey } from '@/lib/core/config/api-keys' import { decryptSecret, encryptSecret } from '@/lib/core/security/encryption' import { convertScheduleOptionsToCron } from '@/lib/core/utils/scheduling' @@ -31,24 +52,6 @@ vi.mock('crypto', () => ({ }), })) -vi.mock('@/lib/core/config/env', () => - createEnvMock({ - ENCRYPTION_KEY: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', - OPENAI_API_KEY_1: 'test-openai-key-1', - OPENAI_API_KEY_2: 'test-openai-key-2', - OPENAI_API_KEY_3: 'test-openai-key-3', - ANTHROPIC_API_KEY_1: 'test-anthropic-key-1', - ANTHROPIC_API_KEY_2: 'test-anthropic-key-2', - ANTHROPIC_API_KEY_3: 'test-anthropic-key-3', - GEMINI_API_KEY_1: 'test-gemini-key-1', - GEMINI_API_KEY_2: 'test-gemini-key-2', - GEMINI_API_KEY_3: 'test-gemini-key-3', - XAI_API_KEY_1: 'test-xai-key-1', - XAI_API_KEY_2: 'test-xai-key-2', - XAI_API_KEY_3: 'test-xai-key-3', - }) -) - afterEach(() => { vi.clearAllMocks() }) diff --git a/apps/sim/lib/credentials/environment.test.ts b/apps/sim/lib/credentials/environment.test.ts index be6a158982e..93a5eae03ac 100644 --- a/apps/sim/lib/credentials/environment.test.ts +++ b/apps/sim/lib/credentials/environment.test.ts @@ -1,11 +1,8 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' - -vi.mock('@sim/db', () => dbChainMock) - import { getWorkspaceEnvKeyAdminAccess } from '@/lib/credentials/environment' describe('getWorkspaceEnvKeyAdminAccess', () => { diff --git a/apps/sim/lib/credentials/token-service-accounts/validators/trello.test.ts b/apps/sim/lib/credentials/token-service-accounts/validators/trello.test.ts index 5e78de2f1d6..314da932186 100644 --- a/apps/sim/lib/credentials/token-service-accounts/validators/trello.test.ts +++ b/apps/sim/lib/credentials/token-service-accounts/validators/trello.test.ts @@ -1,15 +1,14 @@ /** * @vitest-environment node */ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { resetEnvMock, setEnv } from '@sim/testing' +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' -const { mockEnv } = vi.hoisted(() => ({ - mockEnv: { TRELLO_API_KEY: undefined as string | undefined }, -})) +beforeAll(() => { + setEnv({ TRELLO_API_KEY: undefined }) +}) -vi.mock('@/lib/core/config/env', () => ({ - env: mockEnv, -})) +afterAll(resetEnvMock) import { validateTrelloServiceAccount } from '@/lib/credentials/token-service-accounts/validators/trello' @@ -31,7 +30,7 @@ describe('validateTrelloServiceAccount', () => { beforeEach(() => { vi.clearAllMocks() vi.stubGlobal('fetch', mockFetch) - mockEnv.TRELLO_API_KEY = 'sim-api-key' + setEnv({ TRELLO_API_KEY: 'sim-api-key' }) }) afterEach(() => { @@ -119,7 +118,7 @@ describe('validateTrelloServiceAccount', () => { }) it('throws provider_unavailable without fetching when the API key is not configured', async () => { - mockEnv.TRELLO_API_KEY = undefined + setEnv({ TRELLO_API_KEY: undefined }) await expect(validateTrelloServiceAccount(FIELDS)).rejects.toMatchObject({ name: 'TokenServiceAccountValidationError', diff --git a/apps/sim/lib/data-drains/dispatcher.test.ts b/apps/sim/lib/data-drains/dispatcher.test.ts index 17edc85de08..cb51659b0a5 100644 --- a/apps/sim/lib/data-drains/dispatcher.test.ts +++ b/apps/sim/lib/data-drains/dispatcher.test.ts @@ -1,17 +1,9 @@ /** * @vitest-environment node */ -import { - dbChainMock, - dbChainMockFns, - resetDbChainMock, - resetEnvFlagsMock, - setEnvFlags, -} from '@sim/testing' +import { dbChainMockFns, resetDbChainMock, resetEnvFlagsMock, setEnvFlags } from '@sim/testing' import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) - const { mockIsEnterprise, mockEnqueue, mockGetJobQueue } = vi.hoisted(() => { const mockEnqueue = vi.fn(async () => 'job-id') return { diff --git a/apps/sim/lib/data-drains/service.test.ts b/apps/sim/lib/data-drains/service.test.ts index 394b2d6d9f1..328ea105462 100644 --- a/apps/sim/lib/data-drains/service.test.ts +++ b/apps/sim/lib/data-drains/service.test.ts @@ -1,11 +1,9 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) - const { mockGetSource, mockGetDestination, mockDecryptCredentials } = vi.hoisted(() => ({ mockGetSource: vi.fn(), mockGetDestination: vi.fn(), diff --git a/apps/sim/lib/execution/cancellation.test.ts b/apps/sim/lib/execution/cancellation.test.ts index eb440b2985e..2a59904326c 100644 --- a/apps/sim/lib/execution/cancellation.test.ts +++ b/apps/sim/lib/execution/cancellation.test.ts @@ -1,4 +1,4 @@ -import { redisConfigMock, redisConfigMockFns } from '@sim/testing' +import { redisConfigMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { mockRedisSet, mockPublish, mockSubscribe } = vi.hoisted(() => ({ @@ -9,7 +9,6 @@ const { mockRedisSet, mockPublish, mockSubscribe } = vi.hoisted(() => ({ const mockGetRedisClient = redisConfigMockFns.mockGetRedisClient -vi.mock('@/lib/core/config/redis', () => redisConfigMock) vi.mock('@/lib/events/pubsub', () => ({ createPubSubChannel: () => ({ publish: mockPublish, diff --git a/apps/sim/lib/execution/e2b.test.ts b/apps/sim/lib/execution/e2b.test.ts index 0bc92f095f2..d5d45fbd03a 100644 --- a/apps/sim/lib/execution/e2b.test.ts +++ b/apps/sim/lib/execution/e2b.test.ts @@ -1,7 +1,15 @@ /** * @vitest-environment node */ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { resetEnvMock, setEnv } from '@sim/testing' +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' + +beforeAll(() => { + setEnv({ E2B_API_KEY: 'test-key' }) +}) + +afterAll(resetEnvMock) + import { CodeLanguage } from '@/lib/execution/languages' const { mockCreate, mockRunCode, mockCommandsRun, mockFilesWrite, mockKill } = vi.hoisted(() => ({ @@ -13,7 +21,6 @@ const { mockCreate, mockRunCode, mockCommandsRun, mockFilesWrite, mockKill } = v })) vi.mock('@e2b/code-interpreter', () => ({ Sandbox: { create: mockCreate } })) -vi.mock('@/lib/core/config/env', () => ({ env: { E2B_API_KEY: 'test-key' } })) import { executeInE2B, executeShellInE2B } from '@/lib/execution/e2b' diff --git a/apps/sim/lib/execution/event-buffer.test.ts b/apps/sim/lib/execution/event-buffer.test.ts index cd1753570ec..93dbd3bff07 100644 --- a/apps/sim/lib/execution/event-buffer.test.ts +++ b/apps/sim/lib/execution/event-buffer.test.ts @@ -1,11 +1,12 @@ /** * @vitest-environment node */ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { redisConfigMockFns, resetRedisConfigMock } from '@sim/testing' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' import type { ExecutionEventEntry } from '@/lib/execution/event-buffer' import type { ExecutionEvent } from '@/lib/workflows/executor/execution-events' -const { mockGetRedisClient, mockRedis, persistedEntries } = vi.hoisted(() => { +const { mockRedis, persistedEntries } = vi.hoisted(() => { const persistedEntries: ExecutionEventEntry[] = [] const mockRedis = { get: vi.fn(), @@ -18,13 +19,12 @@ const { mockGetRedisClient, mockRedis, persistedEntries } = vi.hoisted(() => { pipeline: vi.fn(), eval: vi.fn(), } - const mockGetRedisClient = vi.fn(() => mockRedis) - return { mockGetRedisClient, mockRedis, persistedEntries } + return { mockRedis, persistedEntries } }) -vi.mock('@/lib/core/config/redis', () => ({ - getRedisClient: mockGetRedisClient, -})) +const mockGetRedisClient = redisConfigMockFns.mockGetRedisClient + +afterAll(resetRedisConfigMock) import { createExecutionEventWriter, diff --git a/apps/sim/lib/execution/isolated-vm.test.ts b/apps/sim/lib/execution/isolated-vm.test.ts index 85c479b40b4..46ca743ae7f 100644 --- a/apps/sim/lib/execution/isolated-vm.test.ts +++ b/apps/sim/lib/execution/isolated-vm.test.ts @@ -2,12 +2,7 @@ * @vitest-environment node */ import { EventEmitter } from 'node:events' -import { - inputValidationMock, - inputValidationMockFns, - redisConfigMock, - redisConfigMockFns, -} from '@sim/testing' +import { inputValidationMock, inputValidationMockFns, redisConfigMockFns } from '@sim/testing' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' type MockProc = EventEmitter & { @@ -204,7 +199,6 @@ vi.mock('@/lib/core/utils/logging', () => ({ vi.mock('@/lib/core/config/env', () => ({ env: mockEnv, })) -vi.mock('@/lib/core/config/redis', () => redisConfigMock) vi.mock('node:child_process', () => ({ execSync: mockExecSync, spawn: mockSpawn, diff --git a/apps/sim/lib/execution/preprocessing.test.ts b/apps/sim/lib/execution/preprocessing.test.ts index 6aff30c4ea0..3443e102939 100644 --- a/apps/sim/lib/execution/preprocessing.test.ts +++ b/apps/sim/lib/execution/preprocessing.test.ts @@ -2,8 +2,8 @@ * @vitest-environment node */ -import { loggingSessionMock } from '@sim/testing' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { loggingSessionMock, workflowAuthzMockFns } from '@sim/testing' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' import { ADMISSION_ERROR_CODE } from '@/lib/core/admission/transient-failure' const { @@ -55,15 +55,6 @@ vi.mock('@/lib/core/rate-limiter/rate-limiter', () => ({ })) vi.mock('@/lib/logs/execution/logging-session', () => loggingSessionMock) -vi.mock('@sim/platform-authz/workflow', () => ({ - getActiveWorkflowRecord: vi.fn().mockResolvedValue({ - id: 'workflow-1', - userId: 'creator-1', - workspaceId: 'workspace-1', - isDeployed: true, - }), -})) - import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription' import { preprocessExecution } from './preprocessing' @@ -88,7 +79,17 @@ const ORGANIZATION_ATTRIBUTION = { workspaceId: 'workspace-1', } +afterAll(() => { + workflowAuthzMockFns.mockGetActiveWorkflowRecord.mockReset() +}) + beforeEach(() => { + workflowAuthzMockFns.mockGetActiveWorkflowRecord.mockResolvedValue({ + id: 'workflow-1', + userId: 'creator-1', + workspaceId: 'workspace-1', + isDeployed: true, + }) mockResolveBillingAttribution.mockImplementation( ({ actorUserId, workspaceId }: { actorUserId: string; workspaceId: string }) => ({ ...ORGANIZATION_ATTRIBUTION, diff --git a/apps/sim/lib/execution/preprocessing.webhook-correlation.test.ts b/apps/sim/lib/execution/preprocessing.webhook-correlation.test.ts index 4dc1b8bf9c0..fcbf013a774 100644 --- a/apps/sim/lib/execution/preprocessing.webhook-correlation.test.ts +++ b/apps/sim/lib/execution/preprocessing.webhook-correlation.test.ts @@ -2,8 +2,8 @@ * @vitest-environment node */ -import { loggingSessionMock } from '@sim/testing' -import { describe, expect, it, vi } from 'vitest' +import { loggingSessionMock, workflowAuthzMockFns } from '@sim/testing' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockResolveSystemBillingAttribution } = vi.hoisted(() => ({ mockResolveSystemBillingAttribution: vi.fn(), @@ -29,17 +29,21 @@ vi.mock('@/lib/core/rate-limiter/rate-limiter', () => ({ })) vi.mock('@/lib/logs/execution/logging-session', () => loggingSessionMock) -vi.mock('@sim/platform-authz/workflow', () => ({ - getActiveWorkflowRecord: vi.fn().mockResolvedValue({ - id: 'workflow-1', - workspaceId: 'workspace-1', - isDeployed: true, - }), -})) - import { preprocessExecution } from './preprocessing' describe('preprocessExecution webhook correlation logging', () => { + beforeEach(() => { + workflowAuthzMockFns.mockGetActiveWorkflowRecord.mockResolvedValue({ + id: 'workflow-1', + workspaceId: 'workspace-1', + isDeployed: true, + }) + }) + + afterAll(() => { + workflowAuthzMockFns.mockGetActiveWorkflowRecord.mockReset() + }) + it('preserves webhook correlation when logging preprocessing failures', async () => { mockResolveSystemBillingAttribution.mockRejectedValueOnce( new Error('Unable to resolve billing payer') diff --git a/apps/sim/lib/guardrails/mask-client.test.ts b/apps/sim/lib/guardrails/mask-client.test.ts index f8ee031545c..354adbefc0a 100644 --- a/apps/sim/lib/guardrails/mask-client.test.ts +++ b/apps/sim/lib/guardrails/mask-client.test.ts @@ -1,16 +1,19 @@ /** * @vitest-environment node */ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { resetUrlsMock, urlsMockFns } from '@sim/testing' +import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -const { mockToken, mockBaseUrl, mockSleep } = vi.hoisted(() => ({ +afterAll(resetUrlsMock) + +const { mockToken, mockSleep } = vi.hoisted(() => ({ mockToken: vi.fn(), - mockBaseUrl: vi.fn(), mockSleep: vi.fn(), })) +const mockBaseUrl = urlsMockFns.mockGetInternalApiBaseUrl + vi.mock('@/lib/auth/internal', () => ({ generateInternalToken: mockToken })) -vi.mock('@/lib/core/utils/urls', () => ({ getInternalApiBaseUrl: mockBaseUrl })) vi.mock('@sim/utils/helpers', () => ({ sleep: mockSleep })) import { maskPIIBatchViaHttp } from '@/lib/guardrails/mask-client' diff --git a/apps/sim/lib/invitations/core.test.ts b/apps/sim/lib/invitations/core.test.ts index a3f258e0a02..0535f6632cd 100644 --- a/apps/sim/lib/invitations/core.test.ts +++ b/apps/sim/lib/invitations/core.test.ts @@ -37,8 +37,6 @@ const { mockIsWorkspaceOnEnterprisePlan: vi.fn(async () => true), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/organizations/membership', () => ({ ensureUserInOrganizationTx: mockEnsureUserInOrganization, getUserOrganization: mockGetUserOrganization, diff --git a/apps/sim/lib/invitations/direct-grant.test.ts b/apps/sim/lib/invitations/direct-grant.test.ts index 9eba697ffff..db987ac4603 100644 --- a/apps/sim/lib/invitations/direct-grant.test.ts +++ b/apps/sim/lib/invitations/direct-grant.test.ts @@ -1,13 +1,7 @@ /** * @vitest-environment node */ -import { - auditMock, - auditMockFns, - dbChainMock, - dbChainMockFns, - resetDbChainMock, -} from '@sim/testing' +import { auditMock, auditMockFns, dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -26,7 +20,6 @@ const { mockWorkspaceMemberAdded: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@sim/audit', () => auditMock) vi.mock('@/lib/billing/organizations/membership', () => ({ diff --git a/apps/sim/lib/invitations/workspace-invitations.test.ts b/apps/sim/lib/invitations/workspace-invitations.test.ts index a63fc27447a..819337b96e2 100644 --- a/apps/sim/lib/invitations/workspace-invitations.test.ts +++ b/apps/sim/lib/invitations/workspace-invitations.test.ts @@ -1,13 +1,7 @@ /** * @vitest-environment node */ -import { - auditMock, - createMockRequest, - dbChainMock, - dbChainMockFns, - resetDbChainMock, -} from '@sim/testing' +import { auditMock, createMockRequest, dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -32,7 +26,6 @@ const { mockCaptureServerEvent: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@sim/audit', () => auditMock) vi.mock('@/lib/billing/organizations/membership', () => ({ diff --git a/apps/sim/lib/knowledge/connectors/queue.test.ts b/apps/sim/lib/knowledge/connectors/queue.test.ts index 81c5627e456..9ad039247b1 100644 --- a/apps/sim/lib/knowledge/connectors/queue.test.ts +++ b/apps/sim/lib/knowledge/connectors/queue.test.ts @@ -1,7 +1,7 @@ /** * @vitest-environment node */ -import { dbChainMock, queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' +import { queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockExecuteSync, mockIsTriggerAvailable, mockResolveTriggerRegion, mockTrigger } = @@ -12,7 +12,6 @@ const { mockExecuteSync, mockIsTriggerAvailable, mockResolveTriggerRegion, mockT mockTrigger: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@trigger.dev/sdk', () => ({ tasks: { trigger: mockTrigger } })) vi.mock('@/lib/core/async-jobs/region', () => ({ resolveTriggerRegion: mockResolveTriggerRegion, diff --git a/apps/sim/lib/knowledge/connectors/sync-engine.test.ts b/apps/sim/lib/knowledge/connectors/sync-engine.test.ts index faa7fca4717..83a91635bab 100644 --- a/apps/sim/lib/knowledge/connectors/sync-engine.test.ts +++ b/apps/sim/lib/knowledge/connectors/sync-engine.test.ts @@ -1,11 +1,10 @@ /** * @vitest-environment node */ -import { authOAuthUtilsMock, dbChainMock, urlsMock } from '@sim/testing' +import { authOAuthUtilsMock } from '@sim/testing' import { generateShortId } from '@sim/utils/id' import { beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => dbChainMock) vi.mock('drizzle-orm', () => ({ and: vi.fn(), eq: vi.fn(), @@ -13,7 +12,6 @@ vi.mock('drizzle-orm', () => ({ isNull: vi.fn(), ne: vi.fn(), })) -vi.mock('@/lib/core/utils/urls', () => urlsMock) vi.mock('@/lib/knowledge/documents/service', () => ({ hardDeleteDocuments: vi.fn(), isTriggerAvailable: vi.fn(), diff --git a/apps/sim/lib/knowledge/documents/lock-order.test.ts b/apps/sim/lib/knowledge/documents/lock-order.test.ts index 92ecb99c566..27c6796dd4c 100644 --- a/apps/sim/lib/knowledge/documents/lock-order.test.ts +++ b/apps/sim/lib/knowledge/documents/lock-order.test.ts @@ -8,12 +8,10 @@ * a concurrent chunk edit of the same document. */ import { document, embedding } from '@sim/db/schema' -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' import { updateDocument } from '@/lib/knowledge/documents/service' -vi.mock('@sim/db', () => dbChainMock) - /** invocationCallOrder of the first `tx.update(table)` call. */ function updateOrderForTable(table: unknown): number { const { calls, invocationCallOrder } = dbChainMockFns.update.mock diff --git a/apps/sim/lib/knowledge/documents/processing-queue.test.ts b/apps/sim/lib/knowledge/documents/processing-queue.test.ts index 62de6d9d01d..5e090255f0a 100644 --- a/apps/sim/lib/knowledge/documents/processing-queue.test.ts +++ b/apps/sim/lib/knowledge/documents/processing-queue.test.ts @@ -2,7 +2,6 @@ * @vitest-environment node */ import { - dbChainMock, dbChainMockFns, defaultMockEnv, resetDbChainMock, @@ -17,7 +16,6 @@ const { mockBatchTrigger } = vi.hoisted(() => ({ mockBatchTrigger: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@trigger.dev/sdk', () => ({ tasks: { batchTrigger: mockBatchTrigger, diff --git a/apps/sim/lib/knowledge/documents/storage-billing.test.ts b/apps/sim/lib/knowledge/documents/storage-billing.test.ts index 6ff2a882f1c..f2e5e5b8a9a 100644 --- a/apps/sim/lib/knowledge/documents/storage-billing.test.ts +++ b/apps/sim/lib/knowledge/documents/storage-billing.test.ts @@ -24,8 +24,6 @@ const { mockGetFileMetadataByKeys: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/storage', () => ({ applyStorageUsageDeltasInTx: mockApplyStorageUsageDeltasInTx, checkStorageQuota: mockCheckStorageQuota, diff --git a/apps/sim/lib/knowledge/service.test.ts b/apps/sim/lib/knowledge/service.test.ts index 8b825f0592e..8b9ca673f2d 100644 --- a/apps/sim/lib/knowledge/service.test.ts +++ b/apps/sim/lib/knowledge/service.test.ts @@ -1,13 +1,7 @@ /** * @vitest-environment node */ -import { - dbChainMock, - dbChainMockFns, - permissionsMock, - permissionsMockFns, - resetDbChainMock, -} from '@sim/testing' +import { dbChainMockFns, permissionsMock, permissionsMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -24,7 +18,6 @@ const { mockResolveStorageBillingContext: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) vi.mock('@/lib/billing/storage', () => ({ applyStorageUsageDeltasInTx: mockApplyStorageUsageDeltasInTx, diff --git a/apps/sim/lib/logs/execution/progress-markers.test.ts b/apps/sim/lib/logs/execution/progress-markers.test.ts index 2fc17b45d93..f04945d029f 100644 --- a/apps/sim/lib/logs/execution/progress-markers.test.ts +++ b/apps/sim/lib/logs/execution/progress-markers.test.ts @@ -1,21 +1,22 @@ /** * @vitest-environment node */ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { redisConfigMockFns, resetRedisConfigMock } from '@sim/testing' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' import type { ExecutionLastCompletedBlock, ExecutionLastStartedBlock } from '@/lib/logs/types' -const { mockGetRedisClient, mockRedis } = vi.hoisted(() => { +const { mockRedis } = vi.hoisted(() => { const mockRedis = { eval: vi.fn(), hgetall: vi.fn(), del: vi.fn(), } - return { mockGetRedisClient: vi.fn<[], typeof mockRedis | null>(() => mockRedis), mockRedis } + return { mockRedis } }) -vi.mock('@/lib/core/config/redis', () => ({ - getRedisClient: mockGetRedisClient, -})) +const mockGetRedisClient = redisConfigMockFns.mockGetRedisClient + +afterAll(resetRedisConfigMock) vi.mock('@/lib/core/execution-limits', () => ({ getExecutionReservationTtlMs: () => 5_460_000, diff --git a/apps/sim/lib/mcp/connection-pool.test.ts b/apps/sim/lib/mcp/connection-pool.test.ts index 9359eceb92e..4683ce23d23 100644 --- a/apps/sim/lib/mcp/connection-pool.test.ts +++ b/apps/sim/lib/mcp/connection-pool.test.ts @@ -5,10 +5,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { McpClient } from '@/lib/mcp/client' import { type AcquireParams, McpConnectionPool } from '@/lib/mcp/connection-pool' -vi.mock('@sim/logger', () => ({ - createLogger: () => ({ info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }), -})) - interface FakeClient extends McpClient { __fireClose(): void __setConnected(connected: boolean): void diff --git a/apps/sim/lib/mcp/oauth/revoke.test.ts b/apps/sim/lib/mcp/oauth/revoke.test.ts index 73583eb5926..046671b269e 100644 --- a/apps/sim/lib/mcp/oauth/revoke.test.ts +++ b/apps/sim/lib/mcp/oauth/revoke.test.ts @@ -8,7 +8,7 @@ * raw `fetch`. */ -import { dbChainMock, queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' +import { queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const BLOCKED_ENDPOINT = 'http://169.254.170.2/v2/credentials/' @@ -49,7 +49,6 @@ vi.mock('@/lib/mcp/oauth/storage', () => ({ vi.mock('@/lib/core/security/encryption', () => ({ decryptSecret: mockDecryptSecret, })) -vi.mock('@sim/db', () => dbChainMock) import { revokeMcpOauthTokens } from './revoke' diff --git a/apps/sim/lib/mcp/oauth/storage.test.ts b/apps/sim/lib/mcp/oauth/storage.test.ts index b9d07e779f7..ace0c16430f 100644 --- a/apps/sim/lib/mcp/oauth/storage.test.ts +++ b/apps/sim/lib/mcp/oauth/storage.test.ts @@ -2,29 +2,20 @@ * @vitest-environment node */ import { - dbChainMock, dbChainMockFns, encryptionMock, encryptionMockFns, + redisConfigMockFns, resetDbChainMock, - schemaMock, + resetRedisConfigMock, } from '@sim/testing' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' -const { mockAcquireLock, mockReleaseLock, mockExtendLock } = vi.hoisted(() => ({ - mockAcquireLock: vi.fn(), - mockReleaseLock: vi.fn(), - mockExtendLock: vi.fn(), -})) +const { mockAcquireLock, mockReleaseLock, mockExtendLock } = redisConfigMockFns + +afterAll(resetRedisConfigMock) -vi.mock('@sim/db', () => dbChainMock) -vi.mock('@sim/db/schema', () => schemaMock) vi.mock('@/lib/core/security/encryption', () => encryptionMock) -vi.mock('@/lib/core/config/redis', () => ({ - acquireLock: mockAcquireLock, - releaseLock: mockReleaseLock, - extendLock: mockExtendLock, -})) import { getOrCreateOauthRow, diff --git a/apps/sim/lib/mcp/orchestration/server-lifecycle.test.ts b/apps/sim/lib/mcp/orchestration/server-lifecycle.test.ts index 8b11a5ec4f7..29d746f3c7b 100644 --- a/apps/sim/lib/mcp/orchestration/server-lifecycle.test.ts +++ b/apps/sim/lib/mcp/orchestration/server-lifecycle.test.ts @@ -5,9 +5,7 @@ import { auditMock, dbChainMock, dbChainMockFns, - drizzleOrmMock, encryptionMock, - loggerMock, posthogServerMock, resetDbChainMock, schemaMock, @@ -36,9 +34,7 @@ vi.mock('@sim/db', () => ({ vi.mock('@sim/db/schema', () => ({ mcpServerOauth: schemaMock.mcpServerOauth, })) -vi.mock('@sim/logger', () => loggerMock) vi.mock('@sim/utils/id', () => ({ generateId: vi.fn() })) -vi.mock('drizzle-orm', () => drizzleOrmMock) vi.mock('@/lib/core/security/encryption', () => encryptionMock) vi.mock('@/lib/mcp/domain-check', () => ({ McpDnsResolutionError: class extends Error {}, diff --git a/apps/sim/lib/mcp/orchestration/workflow-mcp-lifecycle.test.ts b/apps/sim/lib/mcp/orchestration/workflow-mcp-lifecycle.test.ts index d2bbe589b79..f6437a111e8 100644 --- a/apps/sim/lib/mcp/orchestration/workflow-mcp-lifecycle.test.ts +++ b/apps/sim/lib/mcp/orchestration/workflow-mcp-lifecycle.test.ts @@ -21,7 +21,6 @@ vi.mock('@sim/db', () => ({ workflowMcpServer: schemaMock.workflowMcpServer, workflowMcpTool: schemaMock.workflowMcpTool, })) -vi.mock('@sim/db/schema', () => schemaMock) vi.mock('drizzle-orm', () => ({ and: vi.fn(), asc: vi.fn(), diff --git a/apps/sim/lib/mcp/service-pool.test.ts b/apps/sim/lib/mcp/service-pool.test.ts index ac334e22b41..f499ba99111 100644 --- a/apps/sim/lib/mcp/service-pool.test.ts +++ b/apps/sim/lib/mcp/service-pool.test.ts @@ -9,7 +9,7 @@ */ import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js' import { StreamableHTTPError } from '@modelcontextprotocol/sdk/client/streamableHttp.js' -import { dbChainMock, dbChainMockFns, loggerMock, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -60,7 +60,6 @@ const { } }) -vi.mock('@sim/logger', () => loggerMock) vi.mock('@/lib/mcp/connection-pool', () => ({ mcpConnectionPool: { acquire: mockAcquire, evictServer: vi.fn() }, })) @@ -87,7 +86,6 @@ const SERVER_ROW = { updatedAt: new Date('2026-01-01T00:00:00Z'), } -vi.mock('@sim/db', () => dbChainMock) vi.mock('@/lib/mcp/domain-check', () => ({ isMcpDomainAllowed: () => true, validateMcpDomain: () => {}, diff --git a/apps/sim/lib/mcp/service.test.ts b/apps/sim/lib/mcp/service.test.ts index 04f33b2b4ff..d1cbb4bb538 100644 --- a/apps/sim/lib/mcp/service.test.ts +++ b/apps/sim/lib/mcp/service.test.ts @@ -3,7 +3,7 @@ */ import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js' -import { dbChainMock, dbChainMockFns, loggerMock, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, loggerMock, resetDbChainMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -73,8 +73,6 @@ const { } }) -vi.mock('@sim/db', () => dbChainMock) - /** * Routes every select chain to `mockGetWorkspaceServersRows`: `where(...)` * resolves the workspace's rows AND exposes `.limit()` for chains like diff --git a/apps/sim/lib/messaging/email/providers/gmail.test.ts b/apps/sim/lib/messaging/email/providers/gmail.test.ts index f7c79e0de5a..8147ab1e350 100644 --- a/apps/sim/lib/messaging/email/providers/gmail.test.ts +++ b/apps/sim/lib/messaging/email/providers/gmail.test.ts @@ -3,12 +3,14 @@ * * @vitest-environment node */ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { resetEnvMock, setEnv } from '@sim/testing' +import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -const { mockJwtConstructor, mockGetAccessToken, mockEnv } = vi.hoisted(() => { +afterAll(resetEnvMock) + +const { mockJwtConstructor, mockGetAccessToken } = vi.hoisted(() => { const mockGetAccessToken = vi.fn() const jwtInstance = { getAccessToken: mockGetAccessToken } - const mockEnv: Record = {} return { mockJwtConstructor: vi.fn().mockImplementation( class { @@ -19,7 +21,6 @@ const { mockJwtConstructor, mockGetAccessToken, mockEnv } = vi.hoisted(() => { } ), mockGetAccessToken, - mockEnv, } }) @@ -27,11 +28,6 @@ vi.mock('google-auth-library', () => ({ JWT: mockJwtConstructor, })) -vi.mock('@/lib/core/config/env', () => ({ - env: mockEnv, - getEnv: (key: string) => mockEnv[key], -})) - import { createGmailProvider } from '@/lib/messaging/email/providers/gmail' import type { ProcessedEmailData } from '@/lib/messaging/email/types' @@ -54,8 +50,8 @@ describe('Gmail mail provider', () => { beforeEach(() => { vi.clearAllMocks() vi.stubGlobal('fetch', mockFetch) - mockEnv.GMAIL_SENDER = 'noreply@sim.example' - mockEnv.GMAIL_CREDENTIALS_JSON = VALID_CREDENTIALS + setEnv({ GMAIL_SENDER: 'noreply@sim.example' }) + setEnv({ GMAIL_CREDENTIALS_JSON: VALID_CREDENTIALS }) mockGetAccessToken.mockResolvedValue({ token: 'test-token' }) }) @@ -66,26 +62,26 @@ describe('Gmail mail provider', () => { describe('createGmailProvider', () => { it('returns null when neither GMAIL_SENDER nor GMAIL_CREDENTIALS_JSON is set', () => { - mockEnv.GMAIL_SENDER = undefined - mockEnv.GMAIL_CREDENTIALS_JSON = undefined + setEnv({ GMAIL_SENDER: undefined }) + setEnv({ GMAIL_CREDENTIALS_JSON: undefined }) expect(createGmailProvider()).toBeNull() }) it('returns null when only one of the two variables is set', () => { - mockEnv.GMAIL_CREDENTIALS_JSON = undefined + setEnv({ GMAIL_CREDENTIALS_JSON: undefined }) expect(createGmailProvider()).toBeNull() - mockEnv.GMAIL_CREDENTIALS_JSON = VALID_CREDENTIALS - mockEnv.GMAIL_SENDER = undefined + setEnv({ GMAIL_CREDENTIALS_JSON: VALID_CREDENTIALS }) + setEnv({ GMAIL_SENDER: undefined }) expect(createGmailProvider()).toBeNull() }) it('returns null for invalid or incomplete credentials JSON', () => { - mockEnv.GMAIL_CREDENTIALS_JSON = 'not-json' + setEnv({ GMAIL_CREDENTIALS_JSON: 'not-json' }) expect(createGmailProvider()).toBeNull() - mockEnv.GMAIL_CREDENTIALS_JSON = JSON.stringify({ client_email: 'x@y.iam' }) + setEnv({ GMAIL_CREDENTIALS_JSON: JSON.stringify({ client_email: 'x@y.iam' }) }) expect(createGmailProvider()).toBeNull() }) diff --git a/apps/sim/lib/messaging/email/unsubscribe.test.ts b/apps/sim/lib/messaging/email/unsubscribe.test.ts index d0b65c2fa26..f2eb98e8f67 100644 --- a/apps/sim/lib/messaging/email/unsubscribe.test.ts +++ b/apps/sim/lib/messaging/email/unsubscribe.test.ts @@ -1,5 +1,12 @@ -import { createEnvMock, databaseMock } from '@sim/testing' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { databaseMock, resetEnvMock, setEnv } from '@sim/testing' +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' + +beforeAll(() => { + setEnv({ BETTER_AUTH_SECRET: 'test-secret-key' }) +}) + +afterAll(resetEnvMock) + import type { EmailType } from '@/lib/messaging/email/mailer' vi.mock('drizzle-orm', () => ({ @@ -8,8 +15,6 @@ vi.mock('drizzle-orm', () => ({ const mockDb = databaseMock.db as Record> -vi.mock('@/lib/core/config/env', () => createEnvMock({ BETTER_AUTH_SECRET: 'test-secret-key' })) - import { generateUnsubscribeToken, getEmailPreferences, diff --git a/apps/sim/lib/messaging/email/utils.test.ts b/apps/sim/lib/messaging/email/utils.test.ts index c261c8966d0..c8ca121c837 100644 --- a/apps/sim/lib/messaging/email/utils.test.ts +++ b/apps/sim/lib/messaging/email/utils.test.ts @@ -1,5 +1,5 @@ -import { createEnvMock, urlsMock, urlsMockFns } from '@sim/testing' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { resetEnvMock, resetUrlsMock, setEnv, urlsMockFns } from '@sim/testing' +import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest' import { EMAIL_HEADER_CONTROL_CHARS_REGEX, getFromEmailAddress, @@ -14,15 +14,17 @@ import { * environment configurations for email addresses. */ -// Set up mocks at module level - these will be used for all tests in this file -vi.mock('@/lib/core/config/env', () => - createEnvMock({ +beforeAll(() => { + setEnv({ FROM_EMAIL_ADDRESS: 'Sim ', EMAIL_DOMAIN: 'example.com', }) -) +}) -vi.mock('@/lib/core/utils/urls', () => urlsMock) +afterAll(() => { + resetEnvMock() + resetUrlsMock() +}) beforeEach(() => { urlsMockFns.mockGetEmailDomain.mockReturnValue('fallback.com') diff --git a/apps/sim/lib/oauth/__tests__/terminal-errors.test.ts b/apps/sim/lib/oauth/__tests__/terminal-errors.test.ts index 3fd2787a1ea..9755c64b2d7 100644 --- a/apps/sim/lib/oauth/__tests__/terminal-errors.test.ts +++ b/apps/sim/lib/oauth/__tests__/terminal-errors.test.ts @@ -1,11 +1,8 @@ /** * @vitest-environment node */ -import { redisConfigMock, redisConfigMockFns } from '@sim/testing' +import { redisConfigMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' - -vi.mock('@/lib/core/config/redis', () => redisConfigMock) - import { clearDeadFlag, getRecentTerminalError, diff --git a/apps/sim/lib/oauth/oauth.test.ts b/apps/sim/lib/oauth/oauth.test.ts index 339b1a7694a..d6273e5af40 100644 --- a/apps/sim/lib/oauth/oauth.test.ts +++ b/apps/sim/lib/oauth/oauth.test.ts @@ -1,8 +1,8 @@ -import { createEnvMock, createMockFetch } from '@sim/testing' -import { describe, expect, it, vi } from 'vitest' +import { createMockFetch, resetEnvMock, setEnv } from '@sim/testing' +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' -vi.mock('@/lib/core/config/env', () => - createEnvMock({ +beforeAll(() => { + setEnv({ GOOGLE_CLIENT_ID: 'google_client_id', GOOGLE_CLIENT_SECRET: 'google_client_secret', GITHUB_CLIENT_ID: 'github_client_id', @@ -54,7 +54,9 @@ vi.mock('@/lib/core/config/env', () => SPOTIFY_CLIENT_ID: 'spotify_client_id', SPOTIFY_CLIENT_SECRET: 'spotify_client_secret', }) -) +}) + +afterAll(resetEnvMock) import { DEFAULT_MAX_ERROR_BODY_BYTES } from '@/lib/core/utils/stream-limits' import { refreshOAuthToken } from '@/lib/oauth' diff --git a/apps/sim/lib/organizations/settings-access.test.ts b/apps/sim/lib/organizations/settings-access.test.ts index 616eaae5f00..3ac741ff69d 100644 --- a/apps/sim/lib/organizations/settings-access.test.ts +++ b/apps/sim/lib/organizations/settings-access.test.ts @@ -2,11 +2,8 @@ * @vitest-environment node */ import { member } from '@sim/db/schema' -import { dbChainMock, dbChainMockFns, queueTableRows, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, queueTableRows, resetDbChainMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' - -vi.mock('@sim/db', () => dbChainMock) - import { canOpenOrganizationSettingsSection, getOrganizationSettingsAccess, diff --git a/apps/sim/lib/table/__tests__/find-row-matches.test.ts b/apps/sim/lib/table/__tests__/find-row-matches.test.ts index 076a50686df..a0db90a5f82 100644 --- a/apps/sim/lib/table/__tests__/find-row-matches.test.ts +++ b/apps/sim/lib/table/__tests__/find-row-matches.test.ts @@ -6,13 +6,11 @@ * JS-side shaping (ordinal coercion, column rename, LIMIT+1 truncation), not * the query semantics — those need a real Postgres. */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { sql } from 'drizzle-orm' import { beforeEach, describe, expect, it, vi } from 'vitest' import type { ColumnDefinition, TableDefinition } from '@/lib/table/types' -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/table/sql', () => ({ buildFilterClause: vi.fn(() => sql`true`), buildSortClause: vi.fn(() => sql`true`), diff --git a/apps/sim/lib/table/__tests__/lock-order.test.ts b/apps/sim/lib/table/__tests__/lock-order.test.ts index cca52fadcee..580584d438c 100644 --- a/apps/sim/lib/table/__tests__/lock-order.test.ts +++ b/apps/sim/lib/table/__tests__/lock-order.test.ts @@ -8,13 +8,11 @@ * concurrent inserts on the same table. */ import { userTableDefinitions } from '@sim/db/schema' -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' import { importAppendRows } from '@/lib/table/import-data' import type { TableDefinition } from '@/lib/table/types' -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/core/config/feature-flags', () => ({ isFeatureEnabled: vi.fn().mockResolvedValue(false), })) diff --git a/apps/sim/lib/table/__tests__/service-filter-threading.test.ts b/apps/sim/lib/table/__tests__/service-filter-threading.test.ts index c0ade663506..8e19a08c84d 100644 --- a/apps/sim/lib/table/__tests__/service-filter-threading.test.ts +++ b/apps/sim/lib/table/__tests__/service-filter-threading.test.ts @@ -7,14 +7,12 @@ * timestamp for dates) are always available at the SQL builder layer — the * latent bug that PR #4657 was originally fixing. */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { sql } from 'drizzle-orm' import { beforeEach, describe, expect, it, vi } from 'vitest' import { buildFilterClause, buildSortClause } from '@/lib/table/sql' import type { ColumnDefinition, TableDefinition } from '@/lib/table/types' -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/table/sql', () => ({ buildFilterClause: vi.fn(() => sql`true`), buildSortClause: vi.fn(() => sql`true`), diff --git a/apps/sim/lib/table/__tests__/update-row.test.ts b/apps/sim/lib/table/__tests__/update-row.test.ts index b516a7e0c5a..e606333026d 100644 --- a/apps/sim/lib/table/__tests__/update-row.test.ts +++ b/apps/sim/lib/table/__tests__/update-row.test.ts @@ -1,7 +1,7 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' import { deleteColumn, renameColumn } from '@/lib/table/columns/service' import { @@ -14,8 +14,6 @@ import { import type { TableDefinition } from '@/lib/table/types' import { getUniqueColumns } from '@/lib/table/validation' -vi.mock('@sim/db', () => dbChainMock) - // Capacity is exercised in billing.test.ts; here it's a no-op so the timeout-scaling // suites can use large synthetic row counts without tripping the plan limit. vi.mock('@/lib/table/billing', () => ({ diff --git a/apps/sim/lib/table/cell-write.test.ts b/apps/sim/lib/table/cell-write.test.ts index fab52b73106..0f68850a39c 100644 --- a/apps/sim/lib/table/cell-write.test.ts +++ b/apps/sim/lib/table/cell-write.test.ts @@ -1,7 +1,7 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' import type { RowExecutionMetadata, TableDefinition, WorkflowGroup } from '@/lib/table/types' @@ -11,8 +11,6 @@ const { mockAppendTableEvent, mockUpdateRow, mockWriteExecutionsPatch } = vi.hoi mockWriteExecutionsPatch: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/table/events', () => ({ appendTableEvent: mockAppendTableEvent, })) diff --git a/apps/sim/lib/table/dispatch-concurrency.test.ts b/apps/sim/lib/table/dispatch-concurrency.test.ts index 770be25bb1b..562ff0b0949 100644 --- a/apps/sim/lib/table/dispatch-concurrency.test.ts +++ b/apps/sim/lib/table/dispatch-concurrency.test.ts @@ -1,40 +1,24 @@ /** * @vitest-environment node */ -import { resetEnvFlagsMock, setEnvFlags } from '@sim/testing' -import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' - -const { mockEnv } = vi.hoisted(() => ({ - mockEnv: {} as Record, -})) - -vi.mock('@/lib/core/config/env', () => ({ - env: mockEnv, - envNumber: ( - value: number | string | undefined | null, - fallback: number, - options: { min?: number; integer?: boolean } = {} - ) => { - const parsed = Number(value) - const min = options.min ?? 0 - return Number.isFinite(parsed) && - parsed >= min && - (!options.integer || Number.isInteger(parsed)) - ? parsed - : fallback - }, -})) - +import { resetEnvFlagsMock, resetEnvMock, setEnv, setEnvFlags } from '@sim/testing' +import { afterAll, beforeEach, describe, expect, it } from 'vitest' import { getMaxTableDispatchConcurrency, getTableDispatchConcurrency, } from '@/lib/table/dispatch-concurrency' -afterAll(resetEnvFlagsMock) +afterAll(() => { + resetEnvFlagsMock() + resetEnvMock() +}) describe('getTableDispatchConcurrency', () => { beforeEach(() => { - for (const key of Object.keys(mockEnv)) delete mockEnv[key] + setEnv({ + TABLE_DISPATCH_CONCURRENCY_FREE: undefined, + TABLE_DISPATCH_CONCURRENCY_PAID: undefined, + }) setEnvFlags({ isBillingEnabled: true }) }) @@ -47,8 +31,8 @@ describe('getTableDispatchConcurrency', () => { }) it('applies env overrides', () => { - mockEnv.TABLE_DISPATCH_CONCURRENCY_FREE = '5' - mockEnv.TABLE_DISPATCH_CONCURRENCY_PAID = '200' + setEnv({ TABLE_DISPATCH_CONCURRENCY_FREE: '5' }) + setEnv({ TABLE_DISPATCH_CONCURRENCY_PAID: '200' }) expect(getTableDispatchConcurrency('free')).toBe(5) expect(getTableDispatchConcurrency('pro_6000')).toBe(200) @@ -59,20 +43,23 @@ describe('getTableDispatchConcurrency', () => { setEnvFlags({ isBillingEnabled: false }) expect(getTableDispatchConcurrency(null)).toBe(50) - mockEnv.TABLE_DISPATCH_CONCURRENCY_PAID = '120' + setEnv({ TABLE_DISPATCH_CONCURRENCY_PAID: '120' }) expect(getTableDispatchConcurrency(null)).toBe(120) }) }) describe('getMaxTableDispatchConcurrency', () => { beforeEach(() => { - for (const key of Object.keys(mockEnv)) delete mockEnv[key] + setEnv({ + TABLE_DISPATCH_CONCURRENCY_FREE: undefined, + TABLE_DISPATCH_CONCURRENCY_PAID: undefined, + }) }) it('returns the highest configured value', () => { expect(getMaxTableDispatchConcurrency()).toBe(50) - mockEnv.TABLE_DISPATCH_CONCURRENCY_FREE = '80' + setEnv({ TABLE_DISPATCH_CONCURRENCY_FREE: '80' }) expect(getMaxTableDispatchConcurrency()).toBe(80) }) }) diff --git a/apps/sim/lib/table/events.test.ts b/apps/sim/lib/table/events.test.ts index d3bdb6deca3..48c8a7bd61d 100644 --- a/apps/sim/lib/table/events.test.ts +++ b/apps/sim/lib/table/events.test.ts @@ -1,15 +1,14 @@ /** * @vitest-environment node */ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { resetEnvMock, setEnv } from '@sim/testing' +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@/lib/core/config/redis', () => ({ - getRedisClient: () => null, -})) +beforeAll(() => { + setEnv({ REDIS_URL: undefined }) +}) -vi.mock('@/lib/core/config/env', () => ({ - env: { REDIS_URL: undefined }, -})) +afterAll(resetEnvMock) import type { TableEvent } from '@/lib/table/events' import { appendTableEvent, getLatestTableEventId, readTableEventsSince } from '@/lib/table/events' diff --git a/apps/sim/lib/table/rows/executions.test.ts b/apps/sim/lib/table/rows/executions.test.ts index 57dd8128a53..ebfc2c0e008 100644 --- a/apps/sim/lib/table/rows/executions.test.ts +++ b/apps/sim/lib/table/rows/executions.test.ts @@ -3,11 +3,8 @@ */ import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { RowExecutionMetadata } from '@/lib/table/types' - -vi.mock('@sim/db', () => dbChainMock) - import { writeExecutionsPatch } from '@/lib/table/rows/executions' +import type { RowExecutionMetadata } from '@/lib/table/types' const EXECUTION_STATE: RowExecutionMetadata = { status: 'running', diff --git a/apps/sim/lib/table/snapshot-cache.test.ts b/apps/sim/lib/table/snapshot-cache.test.ts index 35f5b4d1310..032970857ba 100644 --- a/apps/sim/lib/table/snapshot-cache.test.ts +++ b/apps/sim/lib/table/snapshot-cache.test.ts @@ -1,7 +1,7 @@ /** * @vitest-environment node */ -import { dbChainMock, queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' +import { queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockSelectExportRowPage, mockCreateMultipartUpload, mockHeadObject, mockDeleteFile } = @@ -12,7 +12,6 @@ const { mockSelectExportRowPage, mockCreateMultipartUpload, mockHeadObject, mock mockDeleteFile: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@/lib/table/jobs/service', () => ({ selectExportRowPage: mockSelectExportRowPage })) vi.mock('@/lib/uploads/core/storage-service', () => ({ createMultipartUpload: mockCreateMultipartUpload, diff --git a/apps/sim/lib/uploads/contexts/workspace/track-chat-upload.test.ts b/apps/sim/lib/uploads/contexts/workspace/track-chat-upload.test.ts index 5c658928b55..ca103139be3 100644 --- a/apps/sim/lib/uploads/contexts/workspace/track-chat-upload.test.ts +++ b/apps/sim/lib/uploads/contexts/workspace/track-chat-upload.test.ts @@ -2,7 +2,7 @@ * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -17,8 +17,6 @@ const { mockResolveStorageBillingContext: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/storage', () => ({ checkStorageQuotaForBillingContext: mockCheckStorageQuotaForBillingContext, decrementStorageUsageForBillingContext: mockDecrementStorageUsageForBillingContext, diff --git a/apps/sim/lib/uploads/contexts/workspace/workspace-file-manager-errors.test.ts b/apps/sim/lib/uploads/contexts/workspace/workspace-file-manager-errors.test.ts index b106ce0aa54..1f51abd4b37 100644 --- a/apps/sim/lib/uploads/contexts/workspace/workspace-file-manager-errors.test.ts +++ b/apps/sim/lib/uploads/contexts/workspace/workspace-file-manager-errors.test.ts @@ -1,11 +1,8 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' - -vi.mock('@sim/db', () => dbChainMock) - import { listWorkspaceFiles } from './workspace-file-manager' afterAll(resetDbChainMock) diff --git a/apps/sim/lib/uploads/contexts/workspace/workspace-file-storage-accounting.test.ts b/apps/sim/lib/uploads/contexts/workspace/workspace-file-storage-accounting.test.ts index 13b2f30a628..9ac40d5587f 100644 --- a/apps/sim/lib/uploads/contexts/workspace/workspace-file-storage-accounting.test.ts +++ b/apps/sim/lib/uploads/contexts/workspace/workspace-file-storage-accounting.test.ts @@ -1,7 +1,7 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -26,8 +26,6 @@ const { mockUploadFile: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/storage', () => ({ decrementStorageUsageForBillingContextInTx: mockDecrementStorageUsageForBillingContextInTx, incrementStorageUsageForBillingContextInTx: mockIncrementStorageUsageForBillingContextInTx, diff --git a/apps/sim/lib/uploads/contexts/workspace/workspace-file-storage-billing.test.ts b/apps/sim/lib/uploads/contexts/workspace/workspace-file-storage-billing.test.ts index 8d016c7174c..eaf463774bc 100644 --- a/apps/sim/lib/uploads/contexts/workspace/workspace-file-storage-billing.test.ts +++ b/apps/sim/lib/uploads/contexts/workspace/workspace-file-storage-billing.test.ts @@ -1,7 +1,7 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -16,8 +16,6 @@ const { mockUploadFile: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/storage', () => ({ decrementStorageUsageForBillingContextInTx: vi.fn(), incrementStorageUsageForBillingContextInTx: mockIncrementStorageUsageForBillingContextInTx, diff --git a/apps/sim/lib/uploads/utils/user-file-base64.server.test.ts b/apps/sim/lib/uploads/utils/user-file-base64.server.test.ts index 4e497204463..6c6cc70611a 100644 --- a/apps/sim/lib/uploads/utils/user-file-base64.server.test.ts +++ b/apps/sim/lib/uploads/utils/user-file-base64.server.test.ts @@ -1,14 +1,15 @@ /** * @vitest-environment node */ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { redisConfigMockFns, resetRedisConfigMock } from '@sim/testing' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' import { cleanupExecutionBase64Cache, hydrateUserFilesWithBase64, } from '@/lib/uploads/utils/user-file-base64.server' import type { UserFile } from '@/executor/types' -const { mockDownloadFile, mockGetRedisClient, mockRedis, mockVerifyFileAccess } = vi.hoisted(() => { +const { mockDownloadFile, mockRedis, mockVerifyFileAccess } = vi.hoisted(() => { const mockRedis = { get: vi.fn(), set: vi.fn(), @@ -22,15 +23,14 @@ const { mockDownloadFile, mockGetRedisClient, mockRedis, mockVerifyFileAccess } } return { mockDownloadFile: vi.fn(), - mockGetRedisClient: vi.fn(), mockRedis, mockVerifyFileAccess: vi.fn(), } }) -vi.mock('@/lib/core/config/redis', () => ({ - getRedisClient: mockGetRedisClient, -})) +const mockGetRedisClient = redisConfigMockFns.mockGetRedisClient + +afterAll(resetRedisConfigMock) vi.mock('@/lib/uploads', () => ({ StorageService: { diff --git a/apps/sim/lib/webhooks/env-resolver.test.ts b/apps/sim/lib/webhooks/env-resolver.test.ts index 6da44a32fb3..fcbdb139daa 100644 --- a/apps/sim/lib/webhooks/env-resolver.test.ts +++ b/apps/sim/lib/webhooks/env-resolver.test.ts @@ -2,15 +2,12 @@ * @vitest-environment node */ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { environmentUtilsMockFns, resetEnvironmentUtilsMock } from '@sim/testing' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetEffectiveDecryptedEnv } = vi.hoisted(() => ({ - mockGetEffectiveDecryptedEnv: vi.fn(), -})) +const { mockGetEffectiveDecryptedEnv } = environmentUtilsMockFns -vi.mock('@/lib/environment/utils', () => ({ - getEffectiveDecryptedEnv: mockGetEffectiveDecryptedEnv, -})) +afterAll(resetEnvironmentUtilsMock) import { resolveWebhookProviderConfig, diff --git a/apps/sim/lib/webhooks/pending-verification.test.ts b/apps/sim/lib/webhooks/pending-verification.test.ts index d282aebb90d..370cd18b0a1 100644 --- a/apps/sim/lib/webhooks/pending-verification.test.ts +++ b/apps/sim/lib/webhooks/pending-verification.test.ts @@ -1,11 +1,8 @@ /** * @vitest-environment node */ -import { redisConfigMock, redisConfigMockFns } from '@sim/testing' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -vi.mock('@/lib/core/config/redis', () => redisConfigMock) - +import { redisConfigMockFns } from '@sim/testing' +import { afterEach, beforeEach, describe, expect, it } from 'vitest' import { clearPendingWebhookVerification, getPendingWebhookVerification, diff --git a/apps/sim/lib/webhooks/processor.test.ts b/apps/sim/lib/webhooks/processor.test.ts index 9218ab35403..d189730573f 100644 --- a/apps/sim/lib/webhooks/processor.test.ts +++ b/apps/sim/lib/webhooks/processor.test.ts @@ -81,10 +81,6 @@ vi.mock('@sim/security/compare', () => ({ safeCompare: vi.fn().mockReturnValue(true), })) -vi.mock('@/lib/environment/utils', () => ({ - getEffectiveDecryptedEnv: vi.fn().mockResolvedValue({}), -})) - vi.mock('@/lib/execution/preprocessing', () => executionPreprocessingMock) vi.mock('@/lib/workflows/persistence/utils', () => workflowsPersistenceUtilsMock) diff --git a/apps/sim/lib/webhooks/provider-subscriptions.test.ts b/apps/sim/lib/webhooks/provider-subscriptions.test.ts index 336dc8ed3da..cdccc03b4a7 100644 --- a/apps/sim/lib/webhooks/provider-subscriptions.test.ts +++ b/apps/sim/lib/webhooks/provider-subscriptions.test.ts @@ -1,16 +1,17 @@ /** * @vitest-environment node */ + +import { environmentUtilsMockFns, resetEnvironmentUtilsMock } from '@sim/testing' import type { NextRequest } from 'next/server' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetEffectiveDecryptedEnv, mockGetProviderHandler } = vi.hoisted(() => ({ - mockGetEffectiveDecryptedEnv: vi.fn(), - mockGetProviderHandler: vi.fn(), -})) +const { mockGetEffectiveDecryptedEnv } = environmentUtilsMockFns -vi.mock('@/lib/environment/utils', () => ({ - getEffectiveDecryptedEnv: mockGetEffectiveDecryptedEnv, +afterAll(resetEnvironmentUtilsMock) + +const { mockGetProviderHandler } = vi.hoisted(() => ({ + mockGetProviderHandler: vi.fn(), })) vi.mock('@/lib/webhooks/providers', () => ({ diff --git a/apps/sim/lib/workflows/deployment-outbox.test.ts b/apps/sim/lib/workflows/deployment-outbox.test.ts index 164538fb65d..b08db0ce3e9 100644 --- a/apps/sim/lib/workflows/deployment-outbox.test.ts +++ b/apps/sim/lib/workflows/deployment-outbox.test.ts @@ -65,24 +65,11 @@ vi.mock('@sim/audit', () => ({ vi.mock('@sim/db', () => ({ ...dbChainMock, ...schemaMock })) -vi.mock('@/lib/core/config/env', () => ({ - env: { INTERNAL_API_SECRET: 'secret' }, -})) - vi.mock('@/lib/core/outbox/service', () => ({ enqueueOutboxEvent: vi.fn(), processOutboxEventById: vi.fn(), })) -vi.mock('@/lib/core/utils/request', () => ({ - generateRequestId: () => 'request-generated', -})) - -vi.mock('@/lib/core/utils/urls', () => ({ - getBaseUrl: () => 'http://localhost:3000', - getSocketServerUrl: () => 'http://localhost:3002', -})) - vi.mock('@/lib/mcp/server-locks', () => ({ setWorkflowMcpTransactionLockTimeout: mockSetWorkflowMcpTransactionLockTimeout, })) diff --git a/apps/sim/lib/workflows/executor/execution-core.test.ts b/apps/sim/lib/workflows/executor/execution-core.test.ts index b5e2f77429a..a5f255bcf33 100644 --- a/apps/sim/lib/workflows/executor/execution-core.test.ts +++ b/apps/sim/lib/workflows/executor/execution-core.test.ts @@ -1,13 +1,14 @@ import { + environmentUtilsMockFns, + resetEnvironmentUtilsMock, workflowsPersistenceUtilsMock, workflowsPersistenceUtilsMockFns, workflowsUtilsMock, workflowsUtilsMockFns, } from '@sim/testing' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { - getPersonalAndWorkspaceEnvMock, mergeSubblockStateWithValuesMock, safeStartMock, safeCompleteMock, @@ -23,7 +24,6 @@ const { executorConstructorMock, findStartBlockMock, } = vi.hoisted(() => ({ - getPersonalAndWorkspaceEnvMock: vi.fn(), mergeSubblockStateWithValuesMock: vi.fn(), safeStartMock: vi.fn(), safeCompleteMock: vi.fn(), @@ -40,15 +40,15 @@ const { findStartBlockMock: vi.fn(), })) +const getPersonalAndWorkspaceEnvMock = environmentUtilsMockFns.mockGetPersonalAndWorkspaceEnv + +afterAll(resetEnvironmentUtilsMock) + const loadWorkflowFromNormalizedTablesMock = workflowsPersistenceUtilsMockFns.mockLoadWorkflowFromNormalizedTables const loadDeployedWorkflowStateMock = workflowsPersistenceUtilsMockFns.mockLoadDeployedWorkflowState const updateWorkflowRunCountsMock = workflowsUtilsMockFns.mockUpdateWorkflowRunCounts -vi.mock('@/lib/environment/utils', () => ({ - getPersonalAndWorkspaceEnv: getPersonalAndWorkspaceEnvMock, -})) - vi.mock('@/lib/execution/cancellation', () => ({ clearExecutionCancellation: clearExecutionCancellationMock, })) diff --git a/apps/sim/lib/workflows/executor/execution-id-claim.test.ts b/apps/sim/lib/workflows/executor/execution-id-claim.test.ts index c62b261df59..c442bf9a2f0 100644 --- a/apps/sim/lib/workflows/executor/execution-id-claim.test.ts +++ b/apps/sim/lib/workflows/executor/execution-id-claim.test.ts @@ -1,15 +1,13 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { mockGenerateId } = vi.hoisted(() => ({ mockGenerateId: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@sim/utils/id', () => ({ generateId: mockGenerateId, })) diff --git a/apps/sim/lib/workflows/executor/human-in-the-loop-manager.test.ts b/apps/sim/lib/workflows/executor/human-in-the-loop-manager.test.ts index de567e13371..43403742eb0 100644 --- a/apps/sim/lib/workflows/executor/human-in-the-loop-manager.test.ts +++ b/apps/sim/lib/workflows/executor/human-in-the-loop-manager.test.ts @@ -1,7 +1,7 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { mockReleaseExecutionSlot, mockReplaceLargeValueReferenceKeysWithClient } = vi.hoisted( @@ -11,8 +11,6 @@ const { mockReleaseExecutionSlot, mockReplaceLargeValueReferenceKeysWithClient } }) ) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/calculations/usage-reservation', () => ({ releaseExecutionSlot: mockReleaseExecutionSlot, })) diff --git a/apps/sim/lib/workflows/lifecycle.test.ts b/apps/sim/lib/workflows/lifecycle.test.ts index 5749f6408c6..624880ba592 100644 --- a/apps/sim/lib/workflows/lifecycle.test.ts +++ b/apps/sim/lib/workflows/lifecycle.test.ts @@ -2,17 +2,23 @@ * @vitest-environment node */ import { - createEnvMock, dbChainMock, dbChainMockFns, resetDbChainMock, + resetEnvMock, schemaMock, - urlsMock, + setEnv, urlsMockFns, workflowsUtilsMock, workflowsUtilsMockFns, } from '@sim/testing' -import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' + +beforeAll(() => { + setEnv({ SOCKET_SERVER_URL: 'http://socket.test', INTERNAL_API_SECRET: 'secret' }) +}) + +afterAll(resetEnvMock) const { mockCleanupExternalWebhook, mockWorkflowDeleted } = vi.hoisted(() => ({ mockCleanupExternalWebhook: vi.fn(), @@ -29,12 +35,6 @@ vi.mock('@/lib/webhooks/provider-subscriptions', () => ({ cleanupExternalWebhook: (...args: unknown[]) => mockCleanupExternalWebhook(...args), })) -vi.mock('@/lib/core/config/env', () => - createEnvMock({ SOCKET_SERVER_URL: 'http://socket.test', INTERNAL_API_SECRET: 'secret' }) -) - -vi.mock('@/lib/core/utils/urls', () => urlsMock) - vi.mock('@/lib/core/telemetry', () => ({ PlatformEvents: { workflowDeleted: (...args: unknown[]) => mockWorkflowDeleted(...args), diff --git a/apps/sim/lib/workflows/orchestration/deploy.test.ts b/apps/sim/lib/workflows/orchestration/deploy.test.ts index b9afcdd9fa7..e428dc4b48b 100644 --- a/apps/sim/lib/workflows/orchestration/deploy.test.ts +++ b/apps/sim/lib/workflows/orchestration/deploy.test.ts @@ -79,15 +79,6 @@ vi.mock('@/lib/workspace-events/emitter', () => ({ emitWorkflowUndeployedEvent: vi.fn(), })) -vi.mock('@/lib/core/config/env', () => ({ - env: { INTERNAL_API_SECRET: 'secret' }, -})) - -vi.mock('@/lib/core/utils/urls', () => ({ - getBaseUrl: () => 'http://localhost:3000', - getSocketServerUrl: () => 'http://localhost:3002', -})) - vi.mock('@/lib/posthog/server', () => ({ captureServerEvent: mockCaptureServerEvent, })) diff --git a/apps/sim/lib/workflows/sanitization/json-sanitizer.test.ts b/apps/sim/lib/workflows/sanitization/json-sanitizer.test.ts index 683a973714c..404af18c04f 100644 --- a/apps/sim/lib/workflows/sanitization/json-sanitizer.test.ts +++ b/apps/sim/lib/workflows/sanitization/json-sanitizer.test.ts @@ -1,14 +1,17 @@ /** * @vitest-environment node */ -import { describe, expect, it, vi } from 'vitest' +import { resetUrlsMock, urlsMockFns } from '@sim/testing' +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' import { sanitizeForCopilot } from '@/lib/workflows/sanitization/json-sanitizer' import type { WorkflowState } from '@/stores/workflows/workflow/types' import { TRIGGER_WEBHOOK_URL_FIELD } from '@/triggers/constants' -vi.mock('@/lib/core/utils/urls', () => ({ - getBaseUrl: () => 'https://sim.test', -})) +beforeAll(() => { + urlsMockFns.mockGetBaseUrl.mockReturnValue('https://sim.test') +}) + +afterAll(resetUrlsMock) const genericWebhookConfig = { type: 'generic_webhook', diff --git a/apps/sim/lib/workflows/utils.test.ts b/apps/sim/lib/workflows/utils.test.ts index 0936d440680..d0c8b88d474 100644 --- a/apps/sim/lib/workflows/utils.test.ts +++ b/apps/sim/lib/workflows/utils.test.ts @@ -13,19 +13,15 @@ import { createWorkflowRecord, expectWorkflowAccessDenied, expectWorkflowAccessGranted, + workflowAuthzMockFns, } from '@sim/testing' -import { beforeEach, describe, expect, it, vi } from 'vitest' - -const { mockAuthorizeWorkflow } = vi.hoisted(() => ({ - mockAuthorizeWorkflow: vi.fn(), -})) - -vi.mock('@sim/platform-authz/workflow', () => ({ - authorizeWorkflowByWorkspacePermission: mockAuthorizeWorkflow, - getActiveWorkflowContext: vi.fn(), - getActiveWorkflowRecord: vi.fn(), - assertActiveWorkflowContext: vi.fn(), -})) +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockAuthorizeWorkflowByWorkspacePermission: mockAuthorizeWorkflow } = workflowAuthzMockFns + +afterAll(() => { + mockAuthorizeWorkflow.mockReset() +}) import { createHttpResponseFromBlock, validateWorkflowPermissions } from '@/lib/workflows/utils' diff --git a/apps/sim/lib/workspace-events/emitter.test.ts b/apps/sim/lib/workspace-events/emitter.test.ts index 096c79fb4d8..3072ece6ac7 100644 --- a/apps/sim/lib/workspace-events/emitter.test.ts +++ b/apps/sim/lib/workspace-events/emitter.test.ts @@ -1,17 +1,22 @@ /** * @vitest-environment node */ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { workflowAuthzMockFns } from '@sim/testing' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockGetActiveWorkflowContext } = workflowAuthzMockFns + +afterAll(() => { + mockGetActiveWorkflowContext.mockReset() +}) const { - mockGetActiveWorkflowContext, mockFetchSubscriptions, mockEvaluateRule, mockReadLastFiredAt, mockClaimCooldown, mockProcessPolledWebhookEvent, } = vi.hoisted(() => ({ - mockGetActiveWorkflowContext: vi.fn(), mockFetchSubscriptions: vi.fn(), mockEvaluateRule: vi.fn(), mockReadLastFiredAt: vi.fn(), @@ -19,10 +24,6 @@ const { mockProcessPolledWebhookEvent: vi.fn(), })) -vi.mock('@sim/platform-authz/workflow', () => ({ - getActiveWorkflowContext: mockGetActiveWorkflowContext, -})) - vi.mock('@/lib/workspace-events/subscriptions', () => ({ fetchSimTriggerSubscriptions: mockFetchSubscriptions, parseSubscriptionConfig: vi.fn((providerConfig: unknown) => providerConfig), diff --git a/apps/sim/lib/workspace-events/no-activity.test.ts b/apps/sim/lib/workspace-events/no-activity.test.ts index bee1fec1bf2..220f0978cfa 100644 --- a/apps/sim/lib/workspace-events/no-activity.test.ts +++ b/apps/sim/lib/workspace-events/no-activity.test.ts @@ -1,7 +1,7 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns } from '@sim/testing' +import { dbChainMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { mockDispatchSimEvent, mockReadLastFiredAt, mockClaimCooldown } = vi.hoisted(() => ({ @@ -10,8 +10,6 @@ const { mockDispatchSimEvent, mockReadLastFiredAt, mockClaimCooldown } = vi.hois mockClaimCooldown: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/workspace-events/emitter', () => ({ dispatchSimEvent: mockDispatchSimEvent, })) diff --git a/apps/sim/lib/workspace-events/rules.test.ts b/apps/sim/lib/workspace-events/rules.test.ts index 5350c25c079..cfb3caea534 100644 --- a/apps/sim/lib/workspace-events/rules.test.ts +++ b/apps/sim/lib/workspace-events/rules.test.ts @@ -1,11 +1,8 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns } from '@sim/testing' +import { dbChainMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' - -vi.mock('@sim/db', () => dbChainMock) - import { evaluateRule, excludeSimExecutionsCondition } from '@/lib/workspace-events/rules' import type { ExecutionEventContext, SimSubscriptionConfig } from '@/lib/workspace-events/types' diff --git a/apps/sim/lib/workspaces/admin-move.test.ts b/apps/sim/lib/workspaces/admin-move.test.ts index e77473ffa3a..d1e29fffbdc 100644 --- a/apps/sim/lib/workspaces/admin-move.test.ts +++ b/apps/sim/lib/workspaces/admin-move.test.ts @@ -1,7 +1,7 @@ /** @vitest-environment node */ import { organization, workspace } from '@sim/db/schema' -import { dbChainMock, dbChainMockFns, queueTableRows, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, queueTableRows, resetDbChainMock } from '@sim/testing' import { PgDialect } from 'drizzle-orm/pg-core' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' import type { WorkspaceMoveError } from '@/lib/workspaces/admin-move' @@ -26,15 +26,11 @@ const { changeWorkspaceStoragePayerInTx: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) vi.mock('@sim/audit', () => ({ AuditAction: { WORKSPACE_UPDATED: 'workspace.updated', INVITATION_UPDATED: 'invitation.updated' }, AuditResourceType: { WORKSPACE: 'workspace' }, recordAudit, })) -vi.mock('@sim/logger', () => ({ - createLogger: () => ({ info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }), -})) vi.mock('@/lib/billing/organizations/membership', () => ({ acquireOrganizationMutationLock: vi.fn(), })) diff --git a/apps/sim/lib/workspaces/lifecycle.test.ts b/apps/sim/lib/workspaces/lifecycle.test.ts index 671e6c73df6..2102e84ae97 100644 --- a/apps/sim/lib/workspaces/lifecycle.test.ts +++ b/apps/sim/lib/workspaces/lifecycle.test.ts @@ -2,7 +2,6 @@ * @vitest-environment node */ import { - dbChainMock, dbChainMockFns, permissionsMock, permissionsMockFns, @@ -18,8 +17,6 @@ const { mockArchiveWorkflowsForWorkspace } = vi.hoisted(() => ({ const mockGetWorkspaceWithOwner = permissionsMockFns.mockGetWorkspaceWithOwner -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/workflows/lifecycle', () => ({ archiveWorkflowsForWorkspace: (...args: unknown[]) => mockArchiveWorkflowsForWorkspace(...args), })) diff --git a/apps/sim/lib/workspaces/organization-workspaces.test.ts b/apps/sim/lib/workspaces/organization-workspaces.test.ts index d84c3571cf7..2dd1bcb4800 100644 --- a/apps/sim/lib/workspaces/organization-workspaces.test.ts +++ b/apps/sim/lib/workspaces/organization-workspaces.test.ts @@ -1,13 +1,7 @@ /** * @vitest-environment node */ -import { - dbChainMock, - dbChainMockFns, - queueTableRows, - resetDbChainMock, - schemaMock, -} from '@sim/testing' +import { dbChainMockFns, queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -26,8 +20,6 @@ const { mockChangeWorkspaceStoragePayersInTx: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/organizations/membership', () => ({ acquireOrganizationMutationLock: mockAcquireOrganizationMutationLock, ensureUserInOrganizationTx: mockEnsureUserInOrganizationTx, diff --git a/apps/sim/lib/workspaces/policy.test.ts b/apps/sim/lib/workspaces/policy.test.ts index fc5f2f86b0e..0a3ca8d6e55 100644 --- a/apps/sim/lib/workspaces/policy.test.ts +++ b/apps/sim/lib/workspaces/policy.test.ts @@ -2,13 +2,7 @@ * @vitest-environment node */ import { member, workspace } from '@sim/db/schema' -import { - dbChainMock, - queueTableRows, - resetDbChainMock, - resetEnvFlagsMock, - setEnvFlags, -} from '@sim/testing' +import { queueTableRows, resetDbChainMock, resetEnvFlagsMock, setEnvFlags } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -21,8 +15,6 @@ const { mockGetHighestPrioritySubscription: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/organizations/membership', () => ({ getUserOrganization: mockGetUserOrganization, })) diff --git a/apps/sim/lib/workspaces/utils.test.ts b/apps/sim/lib/workspaces/utils.test.ts index a1cd55d2ad7..33d9eec1d0d 100644 --- a/apps/sim/lib/workspaces/utils.test.ts +++ b/apps/sim/lib/workspaces/utils.test.ts @@ -1,15 +1,13 @@ /** * @vitest-environment node */ -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' const { mockChangeWorkspaceStoragePayerInTx } = vi.hoisted(() => ({ mockChangeWorkspaceStoragePayerInTx: vi.fn(), })) -vi.mock('@sim/db', () => dbChainMock) - vi.mock('@/lib/billing/storage/payer-transfer', () => ({ changeWorkspaceStoragePayerInTx: mockChangeWorkspaceStoragePayerInTx, })) From 142d7081ba1402a41799e056967959302fe54686 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 22 Jul 2026 18:38:53 -0700 Subject: [PATCH 6/8] fix(ci+testing): live base-tip recheck before dedup skip; prod-aware urls mock fallbacks - the dedup gate re-verifies merge-tree equivalence against the LIVE base tip at decision time, closing the window where the base branch gains real commits during the poll (frozen BASE_SHA check alone was stale) - the urls mock's getBaseUrl protocol prefix and getBaseDomain parse fallback now follow the shared isProd flag, mirroring the real module --- .github/workflows/ci.yml | 15 +++++++++++++-- packages/testing/src/mocks/urls.mock.ts | 8 ++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47159ea76dc..3cd3472cc7b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,6 +82,7 @@ jobs: REPO: ${{ github.repository }} HEAD_SHA: ${{ github.event.pull_request.head.sha }} BASE_SHA: ${{ github.event.pull_request.base.sha }} + BASE_REF: ${{ github.event.pull_request.base.ref }} run: | COVERED=false @@ -125,8 +126,18 @@ jobs: fi fi if [ "$STATE" = "success" ]; then - COVERED=true - echo "Push run ${RUN_ID} passed its test jobs for ${HEAD_SHA} — skipping duplicate test-build." + # (3) Re-verify merge-tree equivalence against the LIVE base + # tip at decision time: the base branch may have gained real + # commits during the poll, in which case the frozen BASE_SHA + # check from step (1) is stale and the skip would be unsound. + # Any error yields "unknown" and we run the tests. + LIVE_DELTA="$(gh api "repos/${REPO}/compare/${HEAD_SHA}...heads/${BASE_REF}" --jq '.files | length' 2>/dev/null || echo unknown)" + if [ "$LIVE_DELTA" = "0" ]; then + COVERED=true + echo "Push run ${RUN_ID} passed its test jobs for ${HEAD_SHA} and the live base tip still adds no file changes — skipping duplicate test-build." + else + echo "Base branch moved during the poll (live delta: ${LIVE_DELTA}) — running tests in this PR run." + fi break elif [ "$STATE" = "failed" ]; then echo "Push run ${RUN_ID} did not pass (state: ${STATE}) — running tests in this PR run." diff --git a/packages/testing/src/mocks/urls.mock.ts b/packages/testing/src/mocks/urls.mock.ts index 592b76cff0d..ab58ae5804a 100644 --- a/packages/testing/src/mocks/urls.mock.ts +++ b/packages/testing/src/mocks/urls.mock.ts @@ -1,5 +1,6 @@ import { vi } from 'vitest' import { envMockFns, mockEnvObject } from './env.mock' +import { envFlagsMock } from './env-flags.mock' /** Mirrors the real `LOCALHOST_HOSTNAMES` from `@/lib/core/utils/urls`. */ export const LOCALHOST_HOSTNAMES_MOCK: ReadonlySet = new Set([ @@ -27,7 +28,9 @@ function getBaseUrlImpl(): string { 'NEXT_PUBLIC_APP_URL must be configured for webhooks and callbacks to work correctly' ) } - return hasHttpProtocol(baseUrl) ? baseUrl : `http://${baseUrl}` + // Mirrors the real module: protocol-less values get https:// under isProd. + const protocol = envFlagsMock.isProd ? 'https://' : 'http://' + return hasHttpProtocol(baseUrl) ? baseUrl : `${protocol}${baseUrl}` } function getInternalApiBaseUrlImpl(): string { @@ -54,7 +57,8 @@ function getBaseDomainImpl(): string { try { return new URL(fallbackUrl).host } catch { - return 'localhost:3000' + // Mirrors the real module's unparseable-URL fallback per environment. + return envFlagsMock.isProd ? 'sim.ai' : 'localhost:3000' } } } From 3e63380de74d2ee4dbbb9f9ccd2e31f42dd26b40 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 22 Jul 2026 18:48:20 -0700 Subject: [PATCH 7/8] fix(ci+testing): fail-closed nojobs fallback in dedup gate; TLS-aware redis defaults mock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - the dedup gate no longer infers coverage from overall run conclusion when no 'Test and Build /' jobs match — a renamed or skipped test job now runs the tests instead of skipping them - the shared getRedisConnectionDefaults mock mirrors the real TLS resolution (rediss:// to a raw IP requires REDIS_TLS_SERVERNAME and yields tls.servername) --- .github/workflows/ci.yml | 14 ++++---- .../testing/src/mocks/redis-config.mock.ts | 33 ++++++++++++++++++- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3cd3472cc7b..14538d29370 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,14 +116,12 @@ jobs: elif any($t[]; .conclusion != null and .conclusion != "success") then "failed" else "pending" end' 2>/dev/null || echo pending)" if [ "$STATE" = "nojobs" ]; then - RUN_STATE="$(printf '%s' "$RUN_JSON" | jq -r '.workflow_runs[0] | if .status == "completed" then .conclusion else "pending" end' 2>/dev/null || echo pending)" - if [ "$RUN_STATE" = "success" ]; then - STATE="success" - elif [ "$RUN_STATE" != "pending" ]; then - STATE="failed" - else - STATE="pending" - fi + # Fail closed: no jobs matching the "Test and Build /" prefix + # means the reusable-workflow job name changed (or tests were + # skipped) — never infer coverage from the overall run + # conclusion. Update the prefix here if test-build is renamed. + echo "Push run ${RUN_ID} has no 'Test and Build /' jobs — running tests in this PR run (update the prefix if the job was renamed)." + break fi if [ "$STATE" = "success" ]; then # (3) Re-verify merge-tree equivalence against the LIVE base diff --git a/packages/testing/src/mocks/redis-config.mock.ts b/packages/testing/src/mocks/redis-config.mock.ts index a9098b4c653..48d4fc9e01e 100644 --- a/packages/testing/src/mocks/redis-config.mock.ts +++ b/packages/testing/src/mocks/redis-config.mock.ts @@ -1,14 +1,45 @@ import { vi } from 'vitest' +import { envMockFns } from './env.mock' -function getRedisConnectionDefaultsImpl(_url?: string): { +/** + * Mirrors the real `resolveRedisTlsOptions`: `rediss://` URLs targeting a raw + * IPv4 host require `REDIS_TLS_SERVERNAME` (cert hostname verification cannot + * match an IP) and yield a `tls.servername`; DNS hosts and plain `redis://` + * URLs add no TLS options. + */ +function resolveTlsOptionsImpl(url: string | undefined): { servername: string } | undefined { + if (!url) return undefined + let parsed: URL + try { + parsed = new URL(url) + } catch { + return undefined + } + if (parsed.protocol !== 'rediss:') return undefined + if (!/^\d{1,3}(\.\d{1,3}){3}$/.test(parsed.hostname)) return undefined + const servername = envMockFns.getEnv('REDIS_TLS_SERVERNAME') + if (!servername) { + throw new Error( + 'REDIS_TLS_SERVERNAME must be set when REDIS_URL targets an IP over rediss://. ' + + 'TLS cert hostname verification cannot match an IP — set REDIS_TLS_SERVERNAME ' + + 'to the DNS name the cert was issued for (the ElastiCache primary endpoint).' + ) + } + return { servername } +} + +function getRedisConnectionDefaultsImpl(url?: string): { keepAlive: number connectTimeout: number enableOfflineQueue: boolean + tls?: { servername: string } } { + const tls = resolveTlsOptionsImpl(url) return { keepAlive: 1000, connectTimeout: 10000, enableOfflineQueue: true, + ...(tls ? { tls } : {}), } } From 5547b91ffd351b5a267cc308893a5dadca41a717 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 22 Jul 2026 18:56:37 -0700 Subject: [PATCH 8/8] fix(ci): keep polling while nested test jobs have not appeared yet An in-progress push run lists its reusable-workflow jobs only after the caller starts; nojobs is now terminal (fail closed) only once the run has completed without them. --- .github/workflows/ci.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14538d29370..1f0dc4a2cbf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,12 +116,18 @@ jobs: elif any($t[]; .conclusion != null and .conclusion != "success") then "failed" else "pending" end' 2>/dev/null || echo pending)" if [ "$STATE" = "nojobs" ]; then - # Fail closed: no jobs matching the "Test and Build /" prefix - # means the reusable-workflow job name changed (or tests were - # skipped) — never infer coverage from the overall run - # conclusion. Update the prefix here if test-build is renamed. - echo "Push run ${RUN_ID} has no 'Test and Build /' jobs — running tests in this PR run (update the prefix if the job was renamed)." - break + # Nested reusable-workflow jobs appear only after the caller + # starts, so an in-progress run with no "Test and Build /" + # jobs yet just needs another poll. Once the run has + # COMPLETED without them, fail closed: the job was renamed or + # tests were skipped — never infer coverage from the overall + # run conclusion. Update the prefix here on a rename. + RUN_STATUS="$(printf '%s' "$RUN_JSON" | jq -r '.workflow_runs[0].status // "unknown"' 2>/dev/null || echo unknown)" + if [ "$RUN_STATUS" = "completed" ]; then + echo "Push run ${RUN_ID} completed with no 'Test and Build /' jobs — running tests in this PR run (update the prefix if the job was renamed)." + break + fi + STATE="pending" fi if [ "$STATE" = "success" ]; then # (3) Re-verify merge-tree equivalence against the LIVE base