diff --git a/.claude/rules/global.md b/.claude/rules/global.md index 4c98edc16cb..cfcb4c7cc29 100644 --- a/.claude/rules/global.md +++ b/.claude/rules/global.md @@ -10,7 +10,7 @@ Use TSDoc for documentation. No `====` separators. No non-TSDoc comments. Never update global styles. Keep all styling local to components. ## ID Generation -Never use `crypto.randomUUID()`, `nanoid`, or the `uuid` package directly. Use the utilities from `@/lib/core/utils/uuid`: +Never use `crypto.randomUUID()`, `nanoid`, or the `uuid` package directly. Use the utilities from `@sim/utils/id`: - `generateId()` — UUID v4, use by default - `generateShortId(size?)` — short URL-safe ID (default 21 chars), for compact identifiers @@ -24,14 +24,14 @@ import { v4 as uuidv4 } from 'uuid' const id = crypto.randomUUID() // ✓ Good -import { generateId, generateShortId } from '@/lib/core/utils/uuid' +import { generateId, generateShortId } from '@sim/utils/id' const uuid = generateId() const shortId = generateShortId() const tiny = generateShortId(8) ``` ## Common Utilities -Use shared helpers from `@/lib/core/utils/helpers` instead of writing inline implementations: +Use shared helpers from `@sim/utils` instead of writing inline implementations: - `sleep(ms)` — async delay. Never write `new Promise(resolve => setTimeout(resolve, ms))` - `toError(value)` — normalize unknown caught values to `Error`. Never write `e instanceof Error ? e : new Error(String(e))` @@ -44,7 +44,8 @@ const msg = error instanceof Error ? error.message : String(error) const err = error instanceof Error ? error : new Error(String(error)) // ✓ Good -import { sleep, toError } from '@/lib/core/utils/helpers' +import { sleep } from '@sim/utils/helpers' +import { toError } from '@sim/utils/errors' await sleep(1000) const msg = toError(error).message const err = toError(error) diff --git a/.claude/rules/sim-testing.md b/.claude/rules/sim-testing.md index 85a7554637b..36b19dc0d64 100644 --- a/.claude/rules/sim-testing.md +++ b/.claude/rules/sim-testing.md @@ -13,8 +13,12 @@ Use Vitest. Test files: `feature.ts` → `feature.test.ts` These modules are mocked globally — do NOT re-mock them in test files unless you need to override behavior: - `@sim/db` → `databaseMock` +- `@sim/db/schema` → `schemaMock` - `drizzle-orm` → `drizzleOrmMock` - `@sim/logger` → `loggerMock` +- `@/lib/auth` → `authMock` +- `@/lib/auth/hybrid` → `hybridAuthMock` (with default session-delegating behavior) +- `@/lib/core/utils/request` → `requestUtilsMock` - `@/stores/console/store`, `@/stores/terminal`, `@/stores/execution/store` - `@/blocks/registry` - `@trigger.dev/sdk` @@ -102,10 +106,6 @@ vi.mock('@/lib/workspaces/utils', () => ({ })) ``` -### NEVER use `mockAuth()`, `mockConsoleLogger()`, or `setupCommonApiMocks()` from `@sim/testing` - -These helpers internally use `vi.doMock()` which is slow. Use direct `vi.hoisted()` + `vi.mock()` instead. - ### Mock heavy transitive dependencies If a module under test imports `@/blocks` (200+ files), `@/tools/registry`, or other heavy modules, mock them: @@ -135,83 +135,122 @@ await new Promise(r => setTimeout(r, 1)) vi.useFakeTimers() ``` -## Mock Pattern Reference +## Centralized Mocks (prefer over local declarations) + +`@sim/testing` exports ready-to-use mock modules for common dependencies. Import and pass directly to `vi.mock()` — no `vi.hoisted()` boilerplate needed. Each paired `*MockFns` object exposes the underlying `vi.fn()`s for per-test overrides. + +| Module mocked | Import | Factory form | +|---|---|---| +| `@/app/api/auth/oauth/utils` | `authOAuthUtilsMock`, `authOAuthUtilsMockFns` | `vi.mock('@/app/api/auth/oauth/utils', () => authOAuthUtilsMock)` | +| `@/app/api/knowledge/utils` | `knowledgeApiUtilsMock`, `knowledgeApiUtilsMockFns` | `vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock)` | +| `@/app/api/workflows/utils` | `workflowsApiUtilsMock`, `workflowsApiUtilsMockFns` | `vi.mock('@/app/api/workflows/utils', () => workflowsApiUtilsMock)` | +| `@/lib/audit/log` | `auditMock`, `auditMockFns` | `vi.mock('@/lib/audit/log', () => auditMock)` | +| `@/lib/auth` | `authMock`, `authMockFns` | `vi.mock('@/lib/auth', () => authMock)` | +| `@/lib/auth/hybrid` | `hybridAuthMock`, `hybridAuthMockFns` | `vi.mock('@/lib/auth/hybrid', () => hybridAuthMock)` | +| `@/lib/copilot/request/http` | `copilotHttpMock`, `copilotHttpMockFns` | `vi.mock('@/lib/copilot/request/http', () => copilotHttpMock)` | +| `@/lib/core/config/env` | `envMock`, `createEnvMock(overrides)` | `vi.mock('@/lib/core/config/env', () => envMock)` | +| `@/lib/core/config/feature-flags` | `featureFlagsMock` | `vi.mock('@/lib/core/config/feature-flags', () => featureFlagsMock)` | +| `@/lib/core/config/redis` | `redisConfigMock`, `redisConfigMockFns` | `vi.mock('@/lib/core/config/redis', () => redisConfigMock)` | +| `@/lib/core/security/encryption` | `encryptionMock`, `encryptionMockFns` | `vi.mock('@/lib/core/security/encryption', () => encryptionMock)` | +| `@/lib/core/security/input-validation.server` | `inputValidationMock`, `inputValidationMockFns` | `vi.mock('@/lib/core/security/input-validation.server', () => inputValidationMock)` | +| `@/lib/core/utils/request` | `requestUtilsMock`, `requestUtilsMockFns` | `vi.mock('@/lib/core/utils/request', () => requestUtilsMock)` | +| `@/lib/core/utils/urls` | `urlsMock`, `urlsMockFns` | `vi.mock('@/lib/core/utils/urls', () => urlsMock)` | +| `@/lib/execution/preprocessing` | `executionPreprocessingMock`, `executionPreprocessingMockFns` | `vi.mock('@/lib/execution/preprocessing', () => executionPreprocessingMock)` | +| `@/lib/logs/execution/logging-session` | `loggingSessionMock`, `loggingSessionMockFns`, `LoggingSessionMock` | `vi.mock('@/lib/logs/execution/logging-session', () => loggingSessionMock)` | +| `@/lib/workflows/orchestration` | `workflowsOrchestrationMock`, `workflowsOrchestrationMockFns` | `vi.mock('@/lib/workflows/orchestration', () => workflowsOrchestrationMock)` | +| `@/lib/workflows/persistence/utils` | `workflowsPersistenceUtilsMock`, `workflowsPersistenceUtilsMockFns` | `vi.mock('@/lib/workflows/persistence/utils', () => workflowsPersistenceUtilsMock)` | +| `@/lib/workflows/utils` | `workflowsUtilsMock`, `workflowsUtilsMockFns` | `vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock)` | +| `@/lib/workspaces/permissions/utils` | `permissionsMock`, `permissionsMockFns` | `vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock)` | +| `@sim/db/schema` | `schemaMock` | `vi.mock('@sim/db/schema', () => schemaMock)` | ### Auth mocking (API routes) ```typescript -const { mockGetSession } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), -})) +import { authMock, authMockFns } from '@sim/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, -})) +vi.mock('@/lib/auth', () => authMock) -// In tests: -mockGetSession.mockResolvedValue({ user: { id: 'user-1', email: 'test@example.com' } }) -mockGetSession.mockResolvedValue(null) // unauthenticated +import { GET } from '@/app/api/my-route/route' + +beforeEach(() => { + vi.clearAllMocks() + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) +}) ``` +Only define a local `vi.mock('@/lib/auth', ...)` if the module under test consumes exports outside the centralized shape (e.g., `auth.api.verifyOneTimeToken`, `auth.api.resetPassword`). + ### Hybrid auth mocking ```typescript -const { mockCheckSessionOrInternalAuth } = vi.hoisted(() => ({ - mockCheckSessionOrInternalAuth: vi.fn(), -})) +import { hybridAuthMock, hybridAuthMockFns } from '@sim/testing' -vi.mock('@/lib/auth/hybrid', () => ({ - checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, -})) +vi.mock('@/lib/auth/hybrid', () => hybridAuthMock) // In tests: -mockCheckSessionOrInternalAuth.mockResolvedValue({ +hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ success: true, userId: 'user-1', authType: 'session', }) ``` ### Database chain mocking +Use the centralized `dbChainMock` + `dbChainMockFns` helpers — no `vi.hoisted()` or chain-wiring boilerplate needed. + ```typescript -const { mockSelect, mockFrom, mockWhere } = vi.hoisted(() => ({ - mockSelect: vi.fn(), - mockFrom: vi.fn(), - mockWhere: vi.fn(), -})) +import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' -vi.mock('@sim/db', () => ({ - db: { select: mockSelect }, -})) +vi.mock('@sim/db', () => dbChainMock) +// Spread for custom exports: vi.mock('@sim/db', () => ({ ...dbChainMock, myTable: {...} })) beforeEach(() => { - mockSelect.mockReturnValue({ from: mockFrom }) - mockFrom.mockReturnValue({ where: mockWhere }) - mockWhere.mockResolvedValue([{ id: '1', name: 'test' }]) + vi.clearAllMocks() + resetDbChainMock() // only needed if tests use permanent (non-`Once`) overrides +}) + +it('reads a row', async () => { + dbChainMockFns.limit.mockResolvedValueOnce([{ id: '1', name: 'test' }]) + // exercise code that hits db.select().from().where().limit() + expect(dbChainMockFns.where).toHaveBeenCalled() }) ``` +**Default chains supported:** +- `select()/selectDistinct()/selectDistinctOn() → from() → where()/innerJoin()/leftJoin() → where() → limit()/orderBy()/returning()/groupBy()` +- `insert() → values() → returning()/onConflictDoUpdate()/onConflictDoNothing()` +- `update() → set() → where() → limit()/orderBy()/returning()` +- `delete() → where() → limit()/orderBy()/returning()` +- `db.execute()` resolves `[]` +- `db.transaction(cb)` calls cb with `dbChainMock.db` + +All terminals default to `Promise.resolve([])`. Override per-test with `dbChainMockFns..mockResolvedValueOnce(...)`. + +Use `resetDbChainMock()` in `beforeEach` only when tests replace wiring with `.mockReturnValue` / `.mockResolvedValue` (permanent). Tests using only `...Once` variants don't need it. + ## @sim/testing Package Always prefer over local test data. | Category | Utilities | |----------|-----------| -| **Mocks** | `loggerMock`, `databaseMock`, `drizzleOrmMock`, `setupGlobalFetchMock()` | +| **Module mocks** | See "Centralized Mocks" table above | +| **Logger helpers** | `loggerMock`, `createMockLogger()`, `getLoggerCalls()`, `clearLoggerMocks()` | +| **Database helpers** | `databaseMock`, `drizzleOrmMock`, `createMockDb()`, `createMockSql()`, `createMockSqlOperators()` | +| **Fetch helpers** | `setupGlobalFetchMock()`, `createMockFetch()`, `createMockResponse()`, `mockFetchError()` | | **Factories** | `createSession()`, `createWorkflowRecord()`, `createBlock()`, `createExecutionContext()` | | **Builders** | `WorkflowBuilder`, `ExecutionContextBuilder` | | **Assertions** | `expectWorkflowAccessGranted()`, `expectBlockExecuted()` | -| **Requests** | `createMockRequest()`, `createEnvMock()` | +| **Requests** | `createMockRequest()`, `createMockFormDataRequest()` | ## Rules Summary 1. `@vitest-environment node` unless DOM is required -2. `vi.hoisted()` + `vi.mock()` + static imports — never `vi.resetModules()` + `vi.doMock()` + dynamic imports -3. `vi.mock()` calls before importing mocked modules -4. `@sim/testing` utilities over local mocks +2. Prefer centralized mocks from `@sim/testing` (see table above) over local `vi.hoisted()` + `vi.mock()` boilerplate +3. `vi.hoisted()` + `vi.mock()` + static imports — never `vi.resetModules()` + `vi.doMock()` + dynamic imports +4. `vi.mock()` calls before importing mocked modules 5. `beforeEach(() => vi.clearAllMocks())` to reset state — no redundant `afterEach` 6. No `vi.importActual()` — mock everything explicitly -7. No `mockAuth()`, `mockConsoleLogger()`, `setupCommonApiMocks()` — use direct mocks -8. Mock heavy deps (`@/blocks`, `@/tools/registry`, `@/triggers`) in tests that don't need them -9. Use absolute imports in test files -10. Avoid real timers — use 1ms delays or `vi.useFakeTimers()` +7. Mock heavy deps (`@/blocks`, `@/tools/registry`, `@/triggers`) in tests that don't need them +8. Use absolute imports in test files +9. Avoid real timers — use 1ms delays or `vi.useFakeTimers()` diff --git a/.cursor/rules/global.mdc b/.cursor/rules/global.mdc index 991244503f3..78f0cb106b3 100644 --- a/.cursor/rules/global.mdc +++ b/.cursor/rules/global.mdc @@ -17,7 +17,7 @@ Use TSDoc for documentation. No `====` separators. No non-TSDoc comments. Never update global styles. Keep all styling local to components. ## ID Generation -Never use `crypto.randomUUID()`, `nanoid`, or the `uuid` package directly. Use the utilities from `@/lib/core/utils/uuid`: +Never use `crypto.randomUUID()`, `nanoid`, or the `uuid` package directly. Use the utilities from `@sim/utils/id`: - `generateId()` — UUID v4, use by default - `generateShortId(size?)` — short URL-safe ID (default 21 chars), for compact identifiers @@ -31,14 +31,14 @@ import { v4 as uuidv4 } from 'uuid' const id = crypto.randomUUID() // ✓ Good -import { generateId, generateShortId } from '@/lib/core/utils/uuid' +import { generateId, generateShortId } from '@sim/utils/id' const uuid = generateId() const shortId = generateShortId() const tiny = generateShortId(8) ``` ## Common Utilities -Use shared helpers from `@/lib/core/utils/helpers` instead of writing inline implementations: +Use shared helpers from `@sim/utils` instead of writing inline implementations: - `sleep(ms)` — async delay. Never write `new Promise(resolve => setTimeout(resolve, ms))` - `toError(value)` — normalize unknown caught values to `Error`. Never write `e instanceof Error ? e : new Error(String(e))` @@ -51,7 +51,8 @@ const msg = error instanceof Error ? error.message : String(error) const err = error instanceof Error ? error : new Error(String(error)) // ✓ Good -import { sleep, toError } from '@/lib/core/utils/helpers' +import { sleep } from '@sim/utils/helpers' +import { toError } from '@sim/utils/errors' await sleep(1000) const msg = toError(error).message const err = toError(error) diff --git a/.cursor/rules/sim-testing.mdc b/.cursor/rules/sim-testing.mdc index ec140388e84..7e3e4806645 100644 --- a/.cursor/rules/sim-testing.mdc +++ b/.cursor/rules/sim-testing.mdc @@ -3,6 +3,7 @@ description: Testing patterns with Vitest and @sim/testing globs: ["apps/sim/**/*.test.ts", "apps/sim/**/*.test.tsx"] --- + # Testing Patterns Use Vitest. Test files: `feature.ts` → `feature.test.ts` @@ -12,8 +13,12 @@ Use Vitest. Test files: `feature.ts` → `feature.test.ts` These modules are mocked globally — do NOT re-mock them in test files unless you need to override behavior: - `@sim/db` → `databaseMock` +- `@sim/db/schema` → `schemaMock` - `drizzle-orm` → `drizzleOrmMock` - `@sim/logger` → `loggerMock` +- `@/lib/auth` → `authMock` +- `@/lib/auth/hybrid` → `hybridAuthMock` (with default session-delegating behavior) +- `@/lib/core/utils/request` → `requestUtilsMock` - `@/stores/console/store`, `@/stores/terminal`, `@/stores/execution/store` - `@/blocks/registry` - `@trigger.dev/sdk` @@ -101,10 +106,6 @@ vi.mock('@/lib/workspaces/utils', () => ({ })) ``` -### NEVER use `mockAuth()`, `mockConsoleLogger()`, or `setupCommonApiMocks()` from `@sim/testing` - -These helpers internally use `vi.doMock()` which is slow. Use direct `vi.hoisted()` + `vi.mock()` instead. - ### Mock heavy transitive dependencies If a module under test imports `@/blocks` (200+ files), `@/tools/registry`, or other heavy modules, mock them: @@ -134,38 +135,61 @@ await new Promise(r => setTimeout(r, 1)) vi.useFakeTimers() ``` -## Mock Pattern Reference +## Centralized Mocks (prefer over local declarations) + +`@sim/testing` exports ready-to-use mock modules for common dependencies. Import and pass directly to `vi.mock()` — no `vi.hoisted()` boilerplate needed. Each paired `*MockFns` object exposes the underlying `vi.fn()`s for per-test overrides. + +| Module mocked | Import | Factory form | +|---|---|---| +| `@/app/api/auth/oauth/utils` | `authOAuthUtilsMock`, `authOAuthUtilsMockFns` | `vi.mock('@/app/api/auth/oauth/utils', () => authOAuthUtilsMock)` | +| `@/app/api/knowledge/utils` | `knowledgeApiUtilsMock`, `knowledgeApiUtilsMockFns` | `vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock)` | +| `@/app/api/workflows/utils` | `workflowsApiUtilsMock`, `workflowsApiUtilsMockFns` | `vi.mock('@/app/api/workflows/utils', () => workflowsApiUtilsMock)` | +| `@/lib/audit/log` | `auditMock`, `auditMockFns` | `vi.mock('@/lib/audit/log', () => auditMock)` | +| `@/lib/auth` | `authMock`, `authMockFns` | `vi.mock('@/lib/auth', () => authMock)` | +| `@/lib/auth/hybrid` | `hybridAuthMock`, `hybridAuthMockFns` | `vi.mock('@/lib/auth/hybrid', () => hybridAuthMock)` | +| `@/lib/copilot/request/http` | `copilotHttpMock`, `copilotHttpMockFns` | `vi.mock('@/lib/copilot/request/http', () => copilotHttpMock)` | +| `@/lib/core/config/env` | `envMock`, `createEnvMock(overrides)` | `vi.mock('@/lib/core/config/env', () => envMock)` | +| `@/lib/core/config/feature-flags` | `featureFlagsMock` | `vi.mock('@/lib/core/config/feature-flags', () => featureFlagsMock)` | +| `@/lib/core/config/redis` | `redisConfigMock`, `redisConfigMockFns` | `vi.mock('@/lib/core/config/redis', () => redisConfigMock)` | +| `@/lib/core/security/encryption` | `encryptionMock`, `encryptionMockFns` | `vi.mock('@/lib/core/security/encryption', () => encryptionMock)` | +| `@/lib/core/security/input-validation.server` | `inputValidationMock`, `inputValidationMockFns` | `vi.mock('@/lib/core/security/input-validation.server', () => inputValidationMock)` | +| `@/lib/core/utils/request` | `requestUtilsMock`, `requestUtilsMockFns` | `vi.mock('@/lib/core/utils/request', () => requestUtilsMock)` | +| `@/lib/core/utils/urls` | `urlsMock`, `urlsMockFns` | `vi.mock('@/lib/core/utils/urls', () => urlsMock)` | +| `@/lib/execution/preprocessing` | `executionPreprocessingMock`, `executionPreprocessingMockFns` | `vi.mock('@/lib/execution/preprocessing', () => executionPreprocessingMock)` | +| `@/lib/logs/execution/logging-session` | `loggingSessionMock`, `loggingSessionMockFns`, `LoggingSessionMock` | `vi.mock('@/lib/logs/execution/logging-session', () => loggingSessionMock)` | +| `@/lib/workflows/orchestration` | `workflowsOrchestrationMock`, `workflowsOrchestrationMockFns` | `vi.mock('@/lib/workflows/orchestration', () => workflowsOrchestrationMock)` | +| `@/lib/workflows/persistence/utils` | `workflowsPersistenceUtilsMock`, `workflowsPersistenceUtilsMockFns` | `vi.mock('@/lib/workflows/persistence/utils', () => workflowsPersistenceUtilsMock)` | +| `@/lib/workflows/utils` | `workflowsUtilsMock`, `workflowsUtilsMockFns` | `vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock)` | +| `@/lib/workspaces/permissions/utils` | `permissionsMock`, `permissionsMockFns` | `vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock)` | +| `@sim/db/schema` | `schemaMock` | `vi.mock('@sim/db/schema', () => schemaMock)` | ### Auth mocking (API routes) ```typescript -const { mockGetSession } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), -})) +import { authMock, authMockFns } from '@sim/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, -})) +vi.mock('@/lib/auth', () => authMock) -// In tests: -mockGetSession.mockResolvedValue({ user: { id: 'user-1', email: 'test@example.com' } }) -mockGetSession.mockResolvedValue(null) // unauthenticated +import { GET } from '@/app/api/my-route/route' + +beforeEach(() => { + vi.clearAllMocks() + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) +}) ``` +Only define a local `vi.mock('@/lib/auth', ...)` if the module under test consumes exports outside the centralized shape (e.g., `auth.api.verifyOneTimeToken`, `auth.api.resetPassword`). + ### Hybrid auth mocking ```typescript -const { mockCheckSessionOrInternalAuth } = vi.hoisted(() => ({ - mockCheckSessionOrInternalAuth: vi.fn(), -})) +import { hybridAuthMock, hybridAuthMockFns } from '@sim/testing' -vi.mock('@/lib/auth/hybrid', () => ({ - checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, -})) +vi.mock('@/lib/auth/hybrid', () => hybridAuthMock) // In tests: -mockCheckSessionOrInternalAuth.mockResolvedValue({ +hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ success: true, userId: 'user-1', authType: 'session', }) ``` @@ -196,21 +220,23 @@ Always prefer over local test data. | Category | Utilities | |----------|-----------| -| **Mocks** | `loggerMock`, `databaseMock`, `drizzleOrmMock`, `setupGlobalFetchMock()` | +| **Module mocks** | See "Centralized Mocks" table above | +| **Logger helpers** | `loggerMock`, `createMockLogger()`, `getLoggerCalls()`, `clearLoggerMocks()` | +| **Database helpers** | `databaseMock`, `drizzleOrmMock`, `createMockDb()`, `createMockSql()`, `createMockSqlOperators()` | +| **Fetch helpers** | `setupGlobalFetchMock()`, `createMockFetch()`, `createMockResponse()`, `mockFetchError()` | | **Factories** | `createSession()`, `createWorkflowRecord()`, `createBlock()`, `createExecutionContext()` | | **Builders** | `WorkflowBuilder`, `ExecutionContextBuilder` | | **Assertions** | `expectWorkflowAccessGranted()`, `expectBlockExecuted()` | -| **Requests** | `createMockRequest()`, `createEnvMock()` | +| **Requests** | `createMockRequest()`, `createMockFormDataRequest()` | ## Rules Summary 1. `@vitest-environment node` unless DOM is required -2. `vi.hoisted()` + `vi.mock()` + static imports — never `vi.resetModules()` + `vi.doMock()` + dynamic imports -3. `vi.mock()` calls before importing mocked modules -4. `@sim/testing` utilities over local mocks +2. Prefer centralized mocks from `@sim/testing` (see table above) over local `vi.hoisted()` + `vi.mock()` boilerplate +3. `vi.hoisted()` + `vi.mock()` + static imports — never `vi.resetModules()` + `vi.doMock()` + dynamic imports +4. `vi.mock()` calls before importing mocked modules 5. `beforeEach(() => vi.clearAllMocks())` to reset state — no redundant `afterEach` 6. No `vi.importActual()` — mock everything explicitly -7. No `mockAuth()`, `mockConsoleLogger()`, `setupCommonApiMocks()` — use direct mocks -8. Mock heavy deps (`@/blocks`, `@/tools/registry`, `@/triggers`) in tests that don't need them -9. Use absolute imports in test files -10. Avoid real timers — use 1ms delays or `vi.useFakeTimers()` +7. Mock heavy deps (`@/blocks`, `@/tools/registry`, `@/triggers`) in tests that don't need them +8. Use absolute imports in test files +9. Avoid real timers — use 1ms delays or `vi.useFakeTimers()` diff --git a/AGENTS.md b/AGENTS.md index bc54c6f912c..5e21c7e009c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,7 +7,7 @@ You are a professional software engineer. All code must follow best practices: a - **Logging**: Import `createLogger` from `@sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log` - **Comments**: Use TSDoc for documentation. No `====` separators. No non-TSDoc comments - **Styling**: Never update global styles. Keep all styling local to components -- **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@/lib/core/utils/uuid` +- **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@sim/utils/id` - **Package Manager**: Use `bun` and `bunx`, not `npm` and `npx` ## Architecture diff --git a/CLAUDE.md b/CLAUDE.md index 965ae7fd7b3..1ca9bf41a25 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,8 +7,8 @@ You are a professional software engineer. All code must follow best practices: a - **Logging**: Import `createLogger` from `@sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log` - **Comments**: Use TSDoc for documentation. No `====` separators. No non-TSDoc comments - **Styling**: Never update global styles. Keep all styling local to components -- **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@/lib/core/utils/uuid` -- **Common Utilities**: Use shared helpers from `@/lib/core/utils/helpers` instead of inline implementations. `sleep(ms)` for delays, `toError(e)` to normalize caught values. +- **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@sim/utils/id` +- **Common Utilities**: Use shared helpers from `@sim/utils` instead of inline implementations. `sleep(ms)` from `@sim/utils/helpers` for delays, `toError(e)` from `@sim/utils/errors` to normalize caught values. - **Package Manager**: Use `bun` and `bunx`, not `npm` and `npx` ## Architecture diff --git a/apps/sim/app/(landing)/actions/github.ts b/apps/sim/app/(landing)/actions/github.ts deleted file mode 100644 index c5785f0e9ce..00000000000 --- a/apps/sim/app/(landing)/actions/github.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { createLogger } from '@sim/logger' - -const DEFAULT_STARS = '19.4k' - -const logger = createLogger('GitHubStars') - -export async function getFormattedGitHubStars(): Promise { - try { - const response = await fetch('/api/stars', { - headers: { - 'Cache-Control': 'max-age=3600', // Cache for 1 hour - }, - }) - - if (!response.ok) { - logger.warn('Failed to fetch GitHub stars from API') - return DEFAULT_STARS - } - - const data = await response.json() - return data.stars || DEFAULT_STARS - } catch (error) { - logger.warn('Error fetching GitHub stars:', error) - return DEFAULT_STARS - } -} diff --git a/apps/sim/app/(landing)/components/navbar/components/github-stars.tsx b/apps/sim/app/(landing)/components/navbar/components/github-stars.tsx index a8aafdc45a3..83a4db820f4 100644 --- a/apps/sim/app/(landing)/components/navbar/components/github-stars.tsx +++ b/apps/sim/app/(landing)/components/navbar/components/github-stars.tsx @@ -1,13 +1,9 @@ 'use client' -import { useEffect, useState } from 'react' -import { createLogger } from '@sim/logger' import { GithubOutlineIcon } from '@/components/icons' -import { getFormattedGitHubStars } from '@/app/(landing)/actions/github' +import { useGitHubStars } from '@/hooks/queries/github-stars' -const logger = createLogger('github-stars') - -const INITIAL_STARS = '27.7k' +const INITIAL_STARS = '27.8k' /** * Client component that displays GitHub stars count. @@ -16,15 +12,8 @@ const INITIAL_STARS = '27.7k' * a Server Component for optimal SEO/GEO crawlability. */ export function GitHubStars() { - const [stars, setStars] = useState(INITIAL_STARS) - - useEffect(() => { - getFormattedGitHubStars() - .then(setStars) - .catch((error) => { - logger.warn('Failed to fetch GitHub stars', error) - }) - }, []) + const { data: stars } = useGitHubStars() + const displayStars = stars ?? INITIAL_STARS return ( - {stars} + {displayStars} ) } diff --git a/apps/sim/app/academy/components/sandbox-canvas-provider.tsx b/apps/sim/app/academy/components/sandbox-canvas-provider.tsx index 893bb1eaf03..1a176e9ac9b 100644 --- a/apps/sim/app/academy/components/sandbox-canvas-provider.tsx +++ b/apps/sim/app/academy/components/sandbox-canvas-provider.tsx @@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react' import { createLogger } from '@sim/logger' +import { sleep } from '@sim/utils/helpers' import type { Edge } from 'reactflow' import { buildMockExecutionPlan } from '@/lib/academy/mock-execution' import type { @@ -12,7 +13,6 @@ import type { } from '@/lib/academy/types' import { validateExercise } from '@/lib/academy/validation' import { cn } from '@/lib/core/utils/cn' -import { sleep } from '@/lib/core/utils/helpers' import { getEffectiveBlockOutputs } from '@/lib/workflows/blocks/block-outputs' import { getQueryClient } from '@/app/_shell/providers/get-query-client' import { GlobalCommandsProvider } from '@/app/workspace/[workspaceId]/providers/global-commands-provider' diff --git a/apps/sim/app/api/a2a/agents/route.ts b/apps/sim/app/api/a2a/agents/route.ts index e1f82e81047..0ff2d6e017d 100644 --- a/apps/sim/app/api/a2a/agents/route.ts +++ b/apps/sim/app/api/a2a/agents/route.ts @@ -7,13 +7,13 @@ import { db } from '@sim/db' import { a2aAgent, workflow } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, isNull, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { generateSkillsFromWorkflow } from '@/lib/a2a/agent-card' import { A2A_DEFAULT_CAPABILITIES } from '@/lib/a2a/constants' import { sanitizeAgentName } from '@/lib/a2a/utils' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { captureServerEvent } from '@/lib/posthog/server' import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils' import { hasValidStartBlockInState } from '@/lib/workflows/triggers/trigger-utils' diff --git a/apps/sim/app/api/a2a/serve/[agentId]/route.ts b/apps/sim/app/api/a2a/serve/[agentId]/route.ts index 911884c1d12..768017e4f4b 100644 --- a/apps/sim/app/api/a2a/serve/[agentId]/route.ts +++ b/apps/sim/app/api/a2a/serve/[agentId]/route.ts @@ -2,6 +2,7 @@ import type { Artifact, Message, PushNotificationConfig, TaskState } from '@a2a- import { db } from '@sim/db' import { a2aAgent, a2aPushNotificationConfig, a2aTask, workflow } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, isNull } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { A2A_DEFAULT_TIMEOUT, A2A_MAX_HISTORY_LENGTH } from '@/lib/a2a/constants' @@ -18,7 +19,6 @@ import { validateUrlWithDNS } from '@/lib/core/security/input-validation.server' import { getClientIp } from '@/lib/core/utils/request' import { SSE_HEADERS } from '@/lib/core/utils/sse' import { getBaseUrl } from '@/lib/core/utils/urls' -import { generateId } from '@/lib/core/utils/uuid' import { markExecutionCancelled } from '@/lib/execution/cancellation' import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils' import { getWorkspaceBilledAccountUserId } from '@/lib/workspaces/utils' diff --git a/apps/sim/app/api/a2a/serve/[agentId]/utils.ts b/apps/sim/app/api/a2a/serve/[agentId]/utils.ts index e3fdd88f5ca..46425475d67 100644 --- a/apps/sim/app/api/a2a/serve/[agentId]/utils.ts +++ b/apps/sim/app/api/a2a/serve/[agentId]/utils.ts @@ -1,7 +1,7 @@ import type { Artifact, Message, PushNotificationConfig, Task, TaskState } from '@a2a-js/sdk' +import { generateId } from '@sim/utils/id' import { generateInternalToken } from '@/lib/auth/internal' import { getInternalApiBaseUrl } from '@/lib/core/utils/urls' -import { generateId } from '@/lib/core/utils/uuid' /** A2A v0.3 JSON-RPC method names */ export const A2A_METHODS = { diff --git a/apps/sim/app/api/academy/certificates/route.ts b/apps/sim/app/api/academy/certificates/route.ts index 0164e1424e3..aefca60a9ee 100644 --- a/apps/sim/app/api/academy/certificates/route.ts +++ b/apps/sim/app/api/academy/certificates/route.ts @@ -1,6 +1,7 @@ import { db } from '@sim/db' import { academyCertificate, user } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateShortId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -9,7 +10,6 @@ import type { CertificateMetadata } from '@/lib/academy/types' import { getSession } from '@/lib/auth' import type { TokenBucketConfig } from '@/lib/core/rate-limiter' import { RateLimiter } from '@/lib/core/rate-limiter' -import { generateShortId } from '@/lib/core/utils/uuid' const logger = createLogger('AcademyCertificatesAPI') 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 dc6e165a261..92046b9efc1 100644 --- a/apps/sim/app/api/auth/oauth/connections/route.test.ts +++ b/apps/sim/app/api/auth/oauth/connections/route.test.ts @@ -3,43 +3,23 @@ * * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { + authMockFns, + createMockRequest, + dbChainMock, + dbChainMockFns, + resetDbChainMock, +} from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetSession, mockDb, mockLogger, mockParseProvider, mockJwtDecode, mockEq } = vi.hoisted( - () => { - const db = { - select: vi.fn().mockReturnThis(), - from: vi.fn().mockReturnThis(), - where: vi.fn().mockReturnThis(), - limit: vi.fn(), - } - const logger = { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - trace: vi.fn(), - fatal: vi.fn(), - child: vi.fn(), - } - return { - mockGetSession: vi.fn(), - mockDb: db, - mockLogger: logger, - mockParseProvider: vi.fn(), - mockJwtDecode: vi.fn(), - mockEq: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'eq' })), - } - } -) - -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, +const { mockParseProvider, mockJwtDecode, mockEq } = vi.hoisted(() => ({ + mockParseProvider: vi.fn(), + mockJwtDecode: vi.fn(), + mockEq: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'eq' })), })) vi.mock('@sim/db', () => ({ - db: mockDb, + ...dbChainMock, account: { userId: 'userId', providerId: 'providerId' }, user: { email: 'email', id: 'id' }, eq: mockEq, @@ -53,10 +33,6 @@ vi.mock('jwt-decode', () => ({ jwtDecode: mockJwtDecode, })) -vi.mock('@sim/logger', () => ({ - createLogger: vi.fn().mockReturnValue(mockLogger), -})) - vi.mock('@/lib/oauth/utils', () => ({ parseProvider: mockParseProvider, })) @@ -66,10 +42,7 @@ import { GET } from '@/app/api/auth/oauth/connections/route' describe('OAuth Connections API Route', () => { beforeEach(() => { vi.clearAllMocks() - - mockDb.select.mockReturnThis() - mockDb.from.mockReturnThis() - mockDb.where.mockReturnThis() + resetDbChainMock() mockParseProvider.mockImplementation((providerId: string) => ({ baseProvider: providerId.split('-')[0] || providerId, @@ -78,7 +51,7 @@ describe('OAuth Connections API Route', () => { }) it('should return connections successfully', async () => { - mockGetSession.mockResolvedValueOnce({ + authMockFns.mockGetSession.mockResolvedValueOnce({ user: { id: 'user-123' }, }) @@ -103,14 +76,8 @@ describe('OAuth Connections API Route', () => { const mockUserRecord = [{ email: 'user@example.com' }] - mockDb.select.mockReturnValueOnce(mockDb) - mockDb.from.mockReturnValueOnce(mockDb) - mockDb.where.mockResolvedValueOnce(mockAccounts) - - mockDb.select.mockReturnValueOnce(mockDb) - mockDb.from.mockReturnValueOnce(mockDb) - mockDb.where.mockReturnValueOnce(mockDb) - mockDb.limit.mockResolvedValueOnce(mockUserRecord) + dbChainMockFns.where.mockResolvedValueOnce(mockAccounts) + dbChainMockFns.limit.mockResolvedValueOnce(mockUserRecord) const req = createMockRequest('GET') @@ -134,7 +101,7 @@ describe('OAuth Connections API Route', () => { }) it('should handle unauthenticated user', async () => { - mockGetSession.mockResolvedValueOnce(null) + authMockFns.mockGetSession.mockResolvedValueOnce(null) const req = createMockRequest('GET') @@ -143,22 +110,15 @@ describe('OAuth Connections API Route', () => { expect(response.status).toBe(401) expect(data.error).toBe('User not authenticated') - expect(mockLogger.warn).toHaveBeenCalled() }) it('should handle user with no connections', async () => { - mockGetSession.mockResolvedValueOnce({ + authMockFns.mockGetSession.mockResolvedValueOnce({ user: { id: 'user-123' }, }) - mockDb.select.mockReturnValueOnce(mockDb) - mockDb.from.mockReturnValueOnce(mockDb) - mockDb.where.mockResolvedValueOnce([]) - - mockDb.select.mockReturnValueOnce(mockDb) - mockDb.from.mockReturnValueOnce(mockDb) - mockDb.where.mockReturnValueOnce(mockDb) - mockDb.limit.mockResolvedValueOnce([]) + dbChainMockFns.where.mockResolvedValueOnce([]) + dbChainMockFns.limit.mockResolvedValueOnce([]) const req = createMockRequest('GET') @@ -170,13 +130,11 @@ describe('OAuth Connections API Route', () => { }) it('should handle database error', async () => { - mockGetSession.mockResolvedValueOnce({ + authMockFns.mockGetSession.mockResolvedValueOnce({ user: { id: 'user-123' }, }) - mockDb.select.mockReturnValueOnce(mockDb) - mockDb.from.mockReturnValueOnce(mockDb) - mockDb.where.mockRejectedValueOnce(new Error('Database error')) + dbChainMockFns.where.mockRejectedValueOnce(new Error('Database error')) const req = createMockRequest('GET') @@ -185,11 +143,10 @@ describe('OAuth Connections API Route', () => { expect(response.status).toBe(500) expect(data.error).toBe('Internal server error') - expect(mockLogger.error).toHaveBeenCalled() }) it('should decode ID token for display name', async () => { - mockGetSession.mockResolvedValueOnce({ + authMockFns.mockGetSession.mockResolvedValueOnce({ user: { id: 'user-123' }, }) @@ -209,14 +166,8 @@ describe('OAuth Connections API Route', () => { name: 'Decoded User', }) - mockDb.select.mockReturnValueOnce(mockDb) - mockDb.from.mockReturnValueOnce(mockDb) - mockDb.where.mockResolvedValueOnce(mockAccounts) - - mockDb.select.mockReturnValueOnce(mockDb) - mockDb.from.mockReturnValueOnce(mockDb) - mockDb.where.mockReturnValueOnce(mockDb) - mockDb.limit.mockResolvedValueOnce([]) + dbChainMockFns.where.mockResolvedValueOnce(mockAccounts) + dbChainMockFns.limit.mockResolvedValueOnce([]) const req = createMockRequest('GET') diff --git a/apps/sim/app/api/auth/oauth/credentials/route.test.ts b/apps/sim/app/api/auth/oauth/credentials/route.test.ts index bb303924f01..fd87f08f5e9 100644 --- a/apps/sim/app/api/auth/oauth/credentials/route.test.ts +++ b/apps/sim/app/api/auth/oauth/credentials/route.test.ts @@ -4,74 +4,17 @@ * @vitest-environment node */ +import { hybridAuthMockFns, permissionsMock, workflowsUtilsMock } from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockCheckSessionOrInternalAuth, mockLogger } = vi.hoisted(() => { - const logger = { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - trace: vi.fn(), - fatal: vi.fn(), - child: vi.fn(), - } - return { - mockCheckSessionOrInternalAuth: vi.fn(), - mockLogger: logger, - } -}) - -vi.mock('@/lib/auth/hybrid', () => ({ - AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, - checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, -})) - -vi.mock('@/lib/core/utils/request', () => ({ - generateRequestId: vi.fn().mockReturnValue('mock-request-id'), -})) - vi.mock('@/lib/credentials/oauth', () => ({ syncWorkspaceOAuthCredentialsForUser: vi.fn(), })) -vi.mock('@/lib/workflows/utils', () => ({ - authorizeWorkflowByWorkspacePermission: vi.fn(), -})) - -vi.mock('@/lib/workspaces/permissions/utils', () => ({ - checkWorkspaceAccess: vi.fn(), -})) +vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) -vi.mock('@sim/db/schema', () => ({ - account: { - userId: 'userId', - providerId: 'providerId', - id: 'id', - scope: 'scope', - updatedAt: 'updatedAt', - }, - credential: { - id: 'id', - workspaceId: 'workspaceId', - type: 'type', - displayName: 'displayName', - providerId: 'providerId', - accountId: 'accountId', - }, - credentialMember: { - id: 'id', - credentialId: 'credentialId', - userId: 'userId', - status: 'status', - }, - user: { email: 'email', id: 'id' }, -})) - -vi.mock('@sim/logger', () => ({ - createLogger: vi.fn().mockReturnValue(mockLogger), -})) +vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) import { GET } from '@/app/api/auth/oauth/credentials/route' @@ -86,7 +29,7 @@ describe('OAuth Credentials API Route', () => { }) it('should handle unauthenticated user', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: false, error: 'Authentication required', }) @@ -98,11 +41,10 @@ describe('OAuth Credentials API Route', () => { expect(response.status).toBe(401) expect(data.error).toBe('User not authenticated') - expect(mockLogger.warn).toHaveBeenCalled() }) it('should handle missing provider parameter', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, userId: 'user-123', authType: 'session', @@ -115,11 +57,10 @@ describe('OAuth Credentials API Route', () => { expect(response.status).toBe(400) expect(data.error).toBe('Provider or credentialId is required') - expect(mockLogger.warn).toHaveBeenCalled() }) it('should handle no credentials found', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, userId: 'user-123', authType: 'session', @@ -135,7 +76,7 @@ describe('OAuth Credentials API Route', () => { }) it('should return empty credentials when no workspace context', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, userId: 'user-123', authType: 'session', 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 35bec861936..13a3b4bc7e9 100644 --- a/apps/sim/app/api/auth/oauth/disconnect/route.test.ts +++ b/apps/sim/app/api/auth/oauth/disconnect/route.test.ts @@ -3,112 +3,42 @@ * * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { + auditMock, + authMockFns, + createMockRequest, + dbChainMock, + dbChainMockFns, + resetDbChainMock, +} from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetSession, mockDb, mockSelectChain, mockLogger, mockSyncAllWebhooksForCredentialSet } = - vi.hoisted(() => { - const selectChain = { - from: vi.fn().mockReturnThis(), - innerJoin: vi.fn().mockReturnThis(), - where: vi.fn().mockResolvedValue([]), - } - const db = { - delete: vi.fn().mockReturnThis(), - where: vi.fn(), - select: vi.fn().mockReturnValue(selectChain), - } - const logger = { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - trace: vi.fn(), - fatal: vi.fn(), - child: vi.fn(), - } - return { - mockGetSession: vi.fn(), - mockDb: db, - mockSelectChain: selectChain, - mockLogger: logger, - mockSyncAllWebhooksForCredentialSet: vi.fn().mockResolvedValue({}), - } - }) - -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - -vi.mock('@sim/db', () => ({ - db: mockDb, +const { mockSyncAllWebhooksForCredentialSet } = vi.hoisted(() => ({ + mockSyncAllWebhooksForCredentialSet: vi.fn().mockResolvedValue({}), })) -vi.mock('@sim/db/schema', () => ({ - account: { userId: 'userId', providerId: 'providerId' }, - credentialSetMember: { - id: 'id', - credentialSetId: 'credentialSetId', - userId: 'userId', - status: 'status', - }, - credentialSet: { id: 'id', providerId: 'providerId' }, -})) - -vi.mock('drizzle-orm', () => ({ - and: vi.fn((...conditions: unknown[]) => ({ conditions, type: 'and' })), - eq: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'eq' })), - like: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'like' })), - or: vi.fn((...conditions: unknown[]) => ({ conditions, type: 'or' })), -})) - -vi.mock('@sim/logger', () => ({ - createLogger: vi.fn().mockReturnValue(mockLogger), -})) - -vi.mock('@/lib/core/utils/request', () => ({ - generateRequestId: vi.fn().mockReturnValue('test-request-id'), -})) +vi.mock('@sim/db', () => dbChainMock) vi.mock('@/lib/webhooks/utils.server', () => ({ syncAllWebhooksForCredentialSet: mockSyncAllWebhooksForCredentialSet, })) -vi.mock('@/lib/audit/log', () => ({ - recordAudit: vi.fn(), - AuditAction: { - CREDENTIAL_SET_CREATED: 'credential_set.created', - CREDENTIAL_SET_UPDATED: 'credential_set.updated', - CREDENTIAL_SET_DELETED: 'credential_set.deleted', - OAUTH_CONNECTED: 'oauth.connected', - OAUTH_DISCONNECTED: 'oauth.disconnected', - }, - AuditResourceType: { - CREDENTIAL_SET: 'credential_set', - OAUTH_CONNECTION: 'oauth_connection', - }, -})) +vi.mock('@/lib/audit/log', () => auditMock) import { POST } from '@/app/api/auth/oauth/disconnect/route' describe('OAuth Disconnect API Route', () => { beforeEach(() => { vi.clearAllMocks() - - mockDb.delete.mockReturnThis() - mockSelectChain.from.mockReturnThis() - mockSelectChain.innerJoin.mockReturnThis() - mockSelectChain.where.mockResolvedValue([]) + resetDbChainMock() + dbChainMockFns.where.mockResolvedValue([]) }) it('should disconnect provider successfully', async () => { - mockGetSession.mockResolvedValueOnce({ + authMockFns.mockGetSession.mockResolvedValueOnce({ user: { id: 'user-123' }, }) - mockDb.delete.mockReturnValueOnce(mockDb) - mockDb.where.mockResolvedValueOnce(undefined) - const req = createMockRequest('POST', { provider: 'google', }) @@ -118,17 +48,13 @@ describe('OAuth Disconnect API Route', () => { expect(response.status).toBe(200) expect(data.success).toBe(true) - expect(mockLogger.info).toHaveBeenCalled() }) it('should disconnect specific provider ID successfully', async () => { - mockGetSession.mockResolvedValueOnce({ + authMockFns.mockGetSession.mockResolvedValueOnce({ user: { id: 'user-123' }, }) - mockDb.delete.mockReturnValueOnce(mockDb) - mockDb.where.mockResolvedValueOnce(undefined) - const req = createMockRequest('POST', { provider: 'google', providerId: 'google-email', @@ -139,11 +65,10 @@ describe('OAuth Disconnect API Route', () => { expect(response.status).toBe(200) expect(data.success).toBe(true) - expect(mockLogger.info).toHaveBeenCalled() }) it('should handle unauthenticated user', async () => { - mockGetSession.mockResolvedValueOnce(null) + authMockFns.mockGetSession.mockResolvedValueOnce(null) const req = createMockRequest('POST', { provider: 'google', @@ -154,11 +79,10 @@ describe('OAuth Disconnect API Route', () => { expect(response.status).toBe(401) expect(data.error).toBe('User not authenticated') - expect(mockLogger.warn).toHaveBeenCalled() }) it('should handle missing provider', async () => { - mockGetSession.mockResolvedValueOnce({ + authMockFns.mockGetSession.mockResolvedValueOnce({ user: { id: 'user-123' }, }) @@ -169,16 +93,14 @@ describe('OAuth Disconnect API Route', () => { expect(response.status).toBe(400) expect(data.error).toBe('Provider is required') - expect(mockLogger.warn).toHaveBeenCalled() }) it('should handle database error', async () => { - mockGetSession.mockResolvedValueOnce({ + authMockFns.mockGetSession.mockResolvedValueOnce({ user: { id: 'user-123' }, }) - mockDb.delete.mockReturnValueOnce(mockDb) - mockDb.where.mockRejectedValueOnce(new Error('Database error')) + dbChainMockFns.where.mockRejectedValueOnce(new Error('Database error')) const req = createMockRequest('POST', { provider: 'google', @@ -189,6 +111,5 @@ describe('OAuth Disconnect API Route', () => { expect(response.status).toBe(500) expect(data.error).toBe('Internal server error') - expect(mockLogger.error).toHaveBeenCalled() }) }) diff --git a/apps/sim/app/api/auth/oauth/token/route.test.ts b/apps/sim/app/api/auth/oauth/token/route.test.ts index 7970a9b5180..73091ae8b04 100644 --- a/apps/sim/app/api/auth/oauth/token/route.test.ts +++ b/apps/sim/app/api/auth/oauth/token/route.test.ts @@ -3,76 +3,30 @@ * * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { + authOAuthUtilsMock, + authOAuthUtilsMockFns, + createMockRequest, + hybridAuthMockFns, +} from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { - mockGetUserId, - mockGetCredential, - mockRefreshTokenIfNeeded, - mockGetOAuthToken, - mockResolveOAuthAccountId, - mockGetServiceAccountToken, - mockAuthorizeCredentialUse, - mockCheckSessionOrInternalAuth, - mockLogger, -} = vi.hoisted(() => { - const logger = { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - trace: vi.fn(), - fatal: vi.fn(), - child: vi.fn(), - } - return { - mockGetUserId: vi.fn(), - mockGetCredential: vi.fn(), - mockRefreshTokenIfNeeded: vi.fn(), - mockGetOAuthToken: vi.fn(), - mockResolveOAuthAccountId: vi.fn(), - mockGetServiceAccountToken: vi.fn(), - mockAuthorizeCredentialUse: vi.fn(), - mockCheckSessionOrInternalAuth: vi.fn(), - mockLogger: logger, - } -}) - -vi.mock('@/app/api/auth/oauth/utils', () => ({ - getUserId: mockGetUserId, - getCredential: mockGetCredential, - refreshTokenIfNeeded: mockRefreshTokenIfNeeded, - getOAuthToken: mockGetOAuthToken, - resolveOAuthAccountId: mockResolveOAuthAccountId, - getServiceAccountToken: mockGetServiceAccountToken, +const { mockAuthorizeCredentialUse } = vi.hoisted(() => ({ + mockAuthorizeCredentialUse: vi.fn(), })) -vi.mock('@sim/logger', () => ({ - createLogger: vi.fn().mockReturnValue(mockLogger), -})) +vi.mock('@/app/api/auth/oauth/utils', () => authOAuthUtilsMock) vi.mock('@/lib/auth/credential-access', () => ({ authorizeCredentialUse: mockAuthorizeCredentialUse, })) -vi.mock('@/lib/core/utils/request', () => ({ - generateRequestId: vi.fn().mockReturnValue('test-request-id'), -})) - -vi.mock('@/lib/auth/hybrid', () => ({ - AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, - checkHybridAuth: vi.fn(), - checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, - checkInternalAuth: vi.fn(), -})) - import { GET, POST } from '@/app/api/auth/oauth/token/route' describe('OAuth Token API Routes', () => { beforeEach(() => { vi.clearAllMocks() - mockResolveOAuthAccountId.mockResolvedValue(null) + authOAuthUtilsMockFns.mockResolveOAuthAccountId.mockResolvedValue(null) }) /** @@ -86,14 +40,14 @@ describe('OAuth Token API Routes', () => { requesterUserId: 'test-user-id', credentialOwnerUserId: 'owner-user-id', }) - mockGetCredential.mockResolvedValueOnce({ + authOAuthUtilsMockFns.mockGetCredential.mockResolvedValueOnce({ id: 'credential-id', accessToken: 'test-token', refreshToken: 'refresh-token', accessTokenExpiresAt: new Date(Date.now() + 3600 * 1000), providerId: 'google', }) - mockRefreshTokenIfNeeded.mockResolvedValueOnce({ + authOAuthUtilsMockFns.mockRefreshTokenIfNeeded.mockResolvedValueOnce({ accessToken: 'fresh-token', refreshed: false, }) @@ -109,8 +63,8 @@ describe('OAuth Token API Routes', () => { expect(data).toHaveProperty('accessToken', 'fresh-token') expect(mockAuthorizeCredentialUse).toHaveBeenCalled() - expect(mockGetCredential).toHaveBeenCalled() - expect(mockRefreshTokenIfNeeded).toHaveBeenCalled() + expect(authOAuthUtilsMockFns.mockGetCredential).toHaveBeenCalled() + expect(authOAuthUtilsMockFns.mockRefreshTokenIfNeeded).toHaveBeenCalled() }) it('should handle workflowId for server-side authentication', async () => { @@ -120,14 +74,14 @@ describe('OAuth Token API Routes', () => { requesterUserId: 'workflow-owner-id', credentialOwnerUserId: 'workflow-owner-id', }) - mockGetCredential.mockResolvedValueOnce({ + authOAuthUtilsMockFns.mockGetCredential.mockResolvedValueOnce({ id: 'credential-id', accessToken: 'test-token', refreshToken: 'refresh-token', accessTokenExpiresAt: new Date(Date.now() + 3600 * 1000), providerId: 'google', }) - mockRefreshTokenIfNeeded.mockResolvedValueOnce({ + authOAuthUtilsMockFns.mockRefreshTokenIfNeeded.mockResolvedValueOnce({ accessToken: 'fresh-token', refreshed: false, }) @@ -144,7 +98,7 @@ describe('OAuth Token API Routes', () => { expect(data).toHaveProperty('accessToken', 'fresh-token') expect(mockAuthorizeCredentialUse).toHaveBeenCalled() - expect(mockGetCredential).toHaveBeenCalled() + expect(authOAuthUtilsMockFns.mockGetCredential).toHaveBeenCalled() }) it('should handle missing credentialId', async () => { @@ -158,7 +112,6 @@ describe('OAuth Token API Routes', () => { 'error', 'Either credentialId or (credentialAccountUserId + providerId) is required' ) - expect(mockLogger.warn).toHaveBeenCalled() }) it('should handle authentication failure', async () => { @@ -199,7 +152,7 @@ describe('OAuth Token API Routes', () => { requesterUserId: 'test-user-id', credentialOwnerUserId: 'owner-user-id', }) - mockGetCredential.mockResolvedValueOnce(undefined) + authOAuthUtilsMockFns.mockGetCredential.mockResolvedValueOnce(undefined) const req = createMockRequest('POST', { credentialId: 'nonexistent-credential-id', @@ -219,14 +172,16 @@ describe('OAuth Token API Routes', () => { requesterUserId: 'test-user-id', credentialOwnerUserId: 'owner-user-id', }) - mockGetCredential.mockResolvedValueOnce({ + authOAuthUtilsMockFns.mockGetCredential.mockResolvedValueOnce({ id: 'credential-id', accessToken: 'test-token', refreshToken: 'refresh-token', accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000), // Expired providerId: 'google', }) - mockRefreshTokenIfNeeded.mockRejectedValueOnce(new Error('Refresh failure')) + authOAuthUtilsMockFns.mockRefreshTokenIfNeeded.mockRejectedValueOnce( + new Error('Refresh failure') + ) const req = createMockRequest('POST', { credentialId: 'credential-id', @@ -241,7 +196,7 @@ describe('OAuth Token API Routes', () => { describe('credentialAccountUserId + providerId path', () => { it('should reject unauthenticated requests', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: false, error: 'Authentication required', }) @@ -256,11 +211,11 @@ describe('OAuth Token API Routes', () => { expect(response.status).toBe(401) expect(data).toHaveProperty('error', 'User not authenticated') - expect(mockGetOAuthToken).not.toHaveBeenCalled() + expect(authOAuthUtilsMockFns.mockGetOAuthToken).not.toHaveBeenCalled() }) it('should reject internal JWT authentication', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, authType: 'internal_jwt', userId: 'test-user-id', @@ -276,11 +231,11 @@ describe('OAuth Token API Routes', () => { expect(response.status).toBe(401) expect(data).toHaveProperty('error', 'User not authenticated') - expect(mockGetOAuthToken).not.toHaveBeenCalled() + expect(authOAuthUtilsMockFns.mockGetOAuthToken).not.toHaveBeenCalled() }) it('should reject requests for other users credentials', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, authType: 'session', userId: 'attacker-user-id', @@ -296,16 +251,16 @@ describe('OAuth Token API Routes', () => { expect(response.status).toBe(403) expect(data).toHaveProperty('error', 'Unauthorized') - expect(mockGetOAuthToken).not.toHaveBeenCalled() + expect(authOAuthUtilsMockFns.mockGetOAuthToken).not.toHaveBeenCalled() }) it('should allow session-authenticated users to access their own credentials', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, authType: 'session', userId: 'test-user-id', }) - mockGetOAuthToken.mockResolvedValueOnce('valid-access-token') + authOAuthUtilsMockFns.mockGetOAuthToken.mockResolvedValueOnce('valid-access-token') const req = createMockRequest('POST', { credentialAccountUserId: 'test-user-id', @@ -317,16 +272,19 @@ describe('OAuth Token API Routes', () => { expect(response.status).toBe(200) expect(data).toHaveProperty('accessToken', 'valid-access-token') - expect(mockGetOAuthToken).toHaveBeenCalledWith('test-user-id', 'google') + expect(authOAuthUtilsMockFns.mockGetOAuthToken).toHaveBeenCalledWith( + 'test-user-id', + 'google' + ) }) it('should return 404 when credential not found for user', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, authType: 'session', userId: 'test-user-id', }) - mockGetOAuthToken.mockResolvedValueOnce(null) + authOAuthUtilsMockFns.mockGetOAuthToken.mockResolvedValueOnce(null) const req = createMockRequest('POST', { credentialAccountUserId: 'test-user-id', @@ -353,14 +311,14 @@ describe('OAuth Token API Routes', () => { requesterUserId: 'test-user-id', credentialOwnerUserId: 'test-user-id', }) - mockGetCredential.mockResolvedValueOnce({ + authOAuthUtilsMockFns.mockGetCredential.mockResolvedValueOnce({ id: 'credential-id', accessToken: 'test-token', refreshToken: 'refresh-token', accessTokenExpiresAt: new Date(Date.now() + 3600 * 1000), providerId: 'google', }) - mockRefreshTokenIfNeeded.mockResolvedValueOnce({ + authOAuthUtilsMockFns.mockRefreshTokenIfNeeded.mockResolvedValueOnce({ accessToken: 'fresh-token', refreshed: false, }) @@ -376,8 +334,8 @@ describe('OAuth Token API Routes', () => { expect(data).toHaveProperty('accessToken', 'fresh-token') expect(mockAuthorizeCredentialUse).toHaveBeenCalled() - expect(mockGetCredential).toHaveBeenCalled() - expect(mockRefreshTokenIfNeeded).toHaveBeenCalled() + expect(authOAuthUtilsMockFns.mockGetCredential).toHaveBeenCalled() + expect(authOAuthUtilsMockFns.mockRefreshTokenIfNeeded).toHaveBeenCalled() }) it('should handle missing credentialId', async () => { @@ -388,7 +346,6 @@ describe('OAuth Token API Routes', () => { expect(response.status).toBe(400) expect(data).toHaveProperty('error', 'Credential ID is required') - expect(mockLogger.warn).toHaveBeenCalled() }) it('should handle authentication failure', async () => { @@ -415,7 +372,7 @@ describe('OAuth Token API Routes', () => { requesterUserId: 'test-user-id', credentialOwnerUserId: 'test-user-id', }) - mockGetCredential.mockResolvedValueOnce(undefined) + authOAuthUtilsMockFns.mockGetCredential.mockResolvedValueOnce(undefined) const req = new Request( 'http://localhost:3000/api/auth/oauth/token?credentialId=nonexistent-credential-id' @@ -435,7 +392,7 @@ describe('OAuth Token API Routes', () => { requesterUserId: 'test-user-id', credentialOwnerUserId: 'test-user-id', }) - mockGetCredential.mockResolvedValueOnce({ + authOAuthUtilsMockFns.mockGetCredential.mockResolvedValueOnce({ id: 'credential-id', accessToken: null, refreshToken: 'refresh-token', @@ -460,14 +417,16 @@ describe('OAuth Token API Routes', () => { requesterUserId: 'test-user-id', credentialOwnerUserId: 'test-user-id', }) - mockGetCredential.mockResolvedValueOnce({ + authOAuthUtilsMockFns.mockGetCredential.mockResolvedValueOnce({ id: 'credential-id', accessToken: 'test-token', refreshToken: 'refresh-token', accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000), // Expired providerId: 'google', }) - mockRefreshTokenIfNeeded.mockRejectedValueOnce(new Error('Refresh failure')) + authOAuthUtilsMockFns.mockRefreshTokenIfNeeded.mockRejectedValueOnce( + new Error('Refresh failure') + ) const req = new Request( 'http://localhost:3000/api/auth/oauth/token?credentialId=credential-id' diff --git a/apps/sim/app/api/auth/oauth/utils.test.ts b/apps/sim/app/api/auth/oauth/utils.test.ts index 2e102c2f915..7f67d37673a 100644 --- a/apps/sim/app/api/auth/oauth/utils.test.ts +++ b/apps/sim/app/api/auth/oauth/utils.test.ts @@ -4,18 +4,13 @@ * @vitest-environment node */ -import { databaseMock, loggerMock } from '@sim/testing' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@sim/db', () => databaseMock) - vi.mock('@/lib/oauth/oauth', () => ({ refreshOAuthToken: vi.fn(), OAUTH_PROVIDERS: {}, })) -vi.mock('@sim/logger', () => loggerMock) - import { db } from '@sim/db' import { refreshOAuthToken } from '@/lib/oauth' import { diff --git a/apps/sim/app/api/auth/oauth/utils.ts b/apps/sim/app/api/auth/oauth/utils.ts index 920c969b939..38b84a59777 100644 --- a/apps/sim/app/api/auth/oauth/utils.ts +++ b/apps/sim/app/api/auth/oauth/utils.ts @@ -2,9 +2,9 @@ import { createSign } from 'crypto' import { db } from '@sim/db' import { account, credential, credentialSetMember } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { and, desc, eq, inArray } from 'drizzle-orm' import { decryptSecret } from '@/lib/core/security/encryption' -import { toError } from '@/lib/core/utils/helpers' import { refreshOAuthToken } from '@/lib/oauth' import { getMicrosoftRefreshTokenExpiry, diff --git a/apps/sim/app/api/auth/shopify/authorize/route.ts b/apps/sim/app/api/auth/shopify/authorize/route.ts index 0fec2c90c00..518a99e1e12 100644 --- a/apps/sim/app/api/auth/shopify/authorize/route.ts +++ b/apps/sim/app/api/auth/shopify/authorize/route.ts @@ -1,9 +1,9 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { env } from '@/lib/core/config/env' import { getBaseUrl } from '@/lib/core/utils/urls' -import { generateId } from '@/lib/core/utils/uuid' import { isSameOrigin } from '@/lib/core/utils/validation' import { getScopesForService } from '@/lib/oauth/utils' diff --git a/apps/sim/app/api/auth/socket-token/route.ts b/apps/sim/app/api/auth/socket-token/route.ts index 0228fe58c30..2eccea62193 100644 --- a/apps/sim/app/api/auth/socket-token/route.ts +++ b/apps/sim/app/api/auth/socket-token/route.ts @@ -1,9 +1,9 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { headers } from 'next/headers' import { NextResponse } from 'next/server' import { auth } from '@/lib/auth' import { isAuthDisabled } from '@/lib/core/config/feature-flags' -import { toError } from '@/lib/core/utils/helpers' const logger = createLogger('SocketTokenAPI') diff --git a/apps/sim/app/api/billing/switch-plan/route.ts b/apps/sim/app/api/billing/switch-plan/route.ts index 4c763caa8c9..ba3f3c0df55 100644 --- a/apps/sim/app/api/billing/switch-plan/route.ts +++ b/apps/sim/app/api/billing/switch-plan/route.ts @@ -1,6 +1,7 @@ import { db } from '@sim/db' import { subscription as subscriptionTable } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -18,7 +19,6 @@ import { isOrgScopedSubscription, } from '@/lib/billing/subscriptions/utils' import { isBillingEnabled } from '@/lib/core/config/feature-flags' -import { toError } from '@/lib/core/utils/helpers' import { captureServerEvent } from '@/lib/posthog/server' const logger = createLogger('SwitchPlan') diff --git a/apps/sim/app/api/billing/update-cost/route.ts b/apps/sim/app/api/billing/update-cost/route.ts index 733be3edd6a..cd3097bda4e 100644 --- a/apps/sim/app/api/billing/update-cost/route.ts +++ b/apps/sim/app/api/billing/update-cost/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -7,7 +8,6 @@ import { checkAndBillOverageThreshold } from '@/lib/billing/threshold-billing' import { checkInternalApiKey } from '@/lib/copilot/request/http' import { isBillingEnabled } from '@/lib/core/config/feature-flags' import { type AtomicClaimResult, billingIdempotency } from '@/lib/core/idempotency/service' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' const logger = createLogger('BillingUpdateCostAPI') 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 7904dc1c3ab..8069757ea79 100644 --- a/apps/sim/app/api/chat/[identifier]/otp/route.test.ts +++ b/apps/sim/app/api/chat/[identifier]/otp/route.test.ts @@ -3,6 +3,13 @@ * * @vitest-environment node */ +import { + redisConfigMock, + redisConfigMockFns, + requestUtilsMockFns, + workflowsApiUtilsMock, + workflowsApiUtilsMockFns, +} from '@sim/testing' import { NextRequest } from 'next/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' @@ -12,7 +19,6 @@ const { mockRedisDel, mockRedisTtl, mockRedisEval, - mockGetRedisClient, mockRedisClient, mockDbSelect, mockDbInsert, @@ -21,10 +27,7 @@ const { mockSendEmail, mockRenderOTPEmail, mockAddCorsHeaders, - mockCreateSuccessResponse, - mockCreateErrorResponse, mockSetChatAuthCookie, - mockGenerateRequestId, mockGetStorageMethod, mockZodParse, mockGetEnv, @@ -41,7 +44,6 @@ const { ttl: mockRedisTtl, eval: mockRedisEval, } - const mockGetRedisClient = vi.fn() const mockDbSelect = vi.fn() const mockDbInsert = vi.fn() const mockDbDelete = vi.fn() @@ -49,10 +51,7 @@ const { const mockSendEmail = vi.fn() const mockRenderOTPEmail = vi.fn() const mockAddCorsHeaders = vi.fn() - const mockCreateSuccessResponse = vi.fn() - const mockCreateErrorResponse = vi.fn() const mockSetChatAuthCookie = vi.fn() - const mockGenerateRequestId = vi.fn() const mockGetStorageMethod = vi.fn() const mockZodParse = vi.fn() const mockGetEnv = vi.fn() @@ -63,7 +62,6 @@ const { mockRedisDel, mockRedisTtl, mockRedisEval, - mockGetRedisClient, mockRedisClient, mockDbSelect, mockDbInsert, @@ -72,19 +70,18 @@ const { mockSendEmail, mockRenderOTPEmail, mockAddCorsHeaders, - mockCreateSuccessResponse, - mockCreateErrorResponse, mockSetChatAuthCookie, - mockGenerateRequestId, mockGetStorageMethod, mockZodParse, mockGetEnv, } }) -vi.mock('@/lib/core/config/redis', () => ({ - getRedisClient: mockGetRedisClient, -})) +const mockGetRedisClient = redisConfigMockFns.mockGetRedisClient +const mockCreateSuccessResponse = workflowsApiUtilsMockFns.mockCreateSuccessResponse +const mockCreateErrorResponse = workflowsApiUtilsMockFns.mockCreateErrorResponse + +vi.mock('@/lib/core/config/redis', () => redisConfigMock) vi.mock('@sim/db', () => ({ db: { @@ -103,26 +100,6 @@ vi.mock('@sim/db', () => ({ }, })) -vi.mock('@sim/db/schema', () => ({ - chat: { - id: 'id', - identifier: 'identifier', - authType: 'authType', - allowedEmails: 'allowedEmails', - title: 'title', - isActive: 'isActive', - archivedAt: 'archivedAt', - }, - verification: { - id: 'id', - identifier: 'identifier', - value: 'value', - expiresAt: 'expiresAt', - createdAt: 'createdAt', - updatedAt: 'updatedAt', - }, -})) - vi.mock('drizzle-orm', () => ({ eq: vi.fn((field: string, value: string) => ({ field, value, type: 'eq' })), and: vi.fn((...conditions: unknown[]) => ({ conditions, type: 'and' })), @@ -160,19 +137,7 @@ vi.mock('@/app/api/chat/utils', () => ({ setChatAuthCookie: mockSetChatAuthCookie, })) -vi.mock('@/app/api/workflows/utils', () => ({ - createSuccessResponse: mockCreateSuccessResponse, - createErrorResponse: mockCreateErrorResponse, -})) - -vi.mock('@sim/logger', () => ({ - createLogger: vi.fn().mockReturnValue({ - info: vi.fn(), - error: vi.fn(), - warn: vi.fn(), - debug: vi.fn(), - }), -})) +vi.mock('@/app/api/workflows/utils', () => workflowsApiUtilsMock) vi.mock('@/lib/core/config/env', () => ({ env: { @@ -207,10 +172,6 @@ vi.mock('zod', () => { } }) -vi.mock('@/lib/core/utils/request', () => ({ - generateRequestId: mockGenerateRequestId, -})) - import { POST, PUT } from './route' describe('Chat OTP API Route', () => { @@ -272,7 +233,7 @@ describe('Chat OTP API Route', () => { status, })) - mockGenerateRequestId.mockReturnValue('req-123') + requestUtilsMockFns.mockGenerateRequestId.mockReturnValue('req-123') mockZodParse.mockImplementation((data: unknown) => data) diff --git a/apps/sim/app/api/chat/[identifier]/otp/route.ts b/apps/sim/app/api/chat/[identifier]/otp/route.ts index 2c0ccde08ee..6039f407156 100644 --- a/apps/sim/app/api/chat/[identifier]/otp/route.ts +++ b/apps/sim/app/api/chat/[identifier]/otp/route.ts @@ -2,6 +2,7 @@ import { randomInt } from 'crypto' import { db } from '@sim/db' import { chat, verification } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, gt, isNull } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { z } from 'zod' @@ -10,7 +11,6 @@ import { getRedisClient } from '@/lib/core/config/redis' import { addCorsHeaders, isEmailAllowed } from '@/lib/core/security/deployment' import { getStorageMethod } from '@/lib/core/storage' import { generateRequestId } from '@/lib/core/utils/request' -import { generateId } from '@/lib/core/utils/uuid' import { sendEmail } from '@/lib/messaging/email/mailer' import { setChatAuthCookie } from '@/app/api/chat/utils' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' @@ -303,8 +303,12 @@ export async function PUT( const deploymentResult = await db .select({ id: chat.id, + title: chat.title, + description: chat.description, + customizations: chat.customizations, authType: chat.authType, password: chat.password, + outputConfigs: chat.outputConfigs, }) .from(chat) .where(and(eq(chat.identifier, identifier), eq(chat.isActive, true), isNull(chat.archivedAt))) @@ -350,7 +354,17 @@ export async function PUT( await deleteOTP(email, deployment.id) - const response = addCorsHeaders(createSuccessResponse({ authenticated: true }), request) + const response = addCorsHeaders( + createSuccessResponse({ + id: deployment.id, + title: deployment.title, + description: deployment.description, + customizations: deployment.customizations, + authType: deployment.authType, + outputConfigs: deployment.outputConfigs, + }), + request + ) setChatAuthCookie(response, deployment.id, deployment.authType, deployment.password) return response diff --git a/apps/sim/app/api/chat/[identifier]/route.test.ts b/apps/sim/app/api/chat/[identifier]/route.test.ts index dac5048fc86..bddc7d99dde 100644 --- a/apps/sim/app/api/chat/[identifier]/route.test.ts +++ b/apps/sim/app/api/chat/[identifier]/route.test.ts @@ -3,8 +3,17 @@ * * @vitest-environment node */ -import { loggerMock, requestUtilsMock } from '@sim/testing' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { + dbChainMock, + dbChainMockFns, + encryptionMock, + executionPreprocessingMock, + executionPreprocessingMockFns, + loggingSessionMock, + workflowsApiUtilsMock, + workflowsApiUtilsMockFns, +} from '@sim/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' /** * Creates a mock NextRequest with cookies support for testing. @@ -51,38 +60,19 @@ const createMockStream = () => { }) } -const { - mockDbSelect, - mockAddCorsHeaders, - mockValidateChatAuth, - mockSetChatAuthCookie, - mockValidateAuthToken, - mockCreateErrorResponse, - mockCreateSuccessResponse, -} = vi.hoisted(() => ({ - mockDbSelect: vi.fn(), - mockAddCorsHeaders: vi.fn().mockImplementation((response: Response) => response), - mockValidateChatAuth: vi.fn().mockResolvedValue({ authorized: true }), - mockSetChatAuthCookie: vi.fn(), - mockValidateAuthToken: vi.fn().mockReturnValue(false), - mockCreateErrorResponse: vi - .fn() - .mockImplementation((message: string, status: number, code?: string) => { - return new Response( - JSON.stringify({ - error: code || 'Error', - message, - }), - { status } - ) - }), - mockCreateSuccessResponse: vi.fn().mockImplementation((data: unknown) => { - return new Response(JSON.stringify(data), { status: 200 }) - }), -})) +const { mockAddCorsHeaders, mockValidateChatAuth, mockSetChatAuthCookie, mockValidateAuthToken } = + vi.hoisted(() => ({ + mockAddCorsHeaders: vi.fn().mockImplementation((response: Response) => response), + mockValidateChatAuth: vi.fn().mockResolvedValue({ authorized: true }), + mockSetChatAuthCookie: vi.fn(), + mockValidateAuthToken: vi.fn().mockReturnValue(false), + })) + +const mockCreateErrorResponse = workflowsApiUtilsMockFns.mockCreateErrorResponse +const mockCreateSuccessResponse = workflowsApiUtilsMockFns.mockCreateSuccessResponse vi.mock('@sim/db', () => ({ - db: { select: mockDbSelect }, + ...dbChainMock, chat: {}, workflow: {}, })) @@ -99,42 +89,11 @@ vi.mock('@/app/api/chat/utils', () => ({ setChatAuthCookie: mockSetChatAuthCookie, })) -vi.mock('@sim/logger', () => loggerMock) +vi.mock('@/app/api/workflows/utils', () => workflowsApiUtilsMock) -vi.mock('@/app/api/workflows/utils', () => ({ - createErrorResponse: mockCreateErrorResponse, - createSuccessResponse: mockCreateSuccessResponse, -})) +vi.mock('@/lib/execution/preprocessing', () => executionPreprocessingMock) -vi.mock('@/lib/execution/preprocessing', () => ({ - preprocessExecution: vi.fn().mockResolvedValue({ - success: true, - actorUserId: 'test-user-id', - workflowRecord: { - id: 'test-workflow-id', - userId: 'test-user-id', - isDeployed: true, - workspaceId: 'test-workspace-id', - variables: {}, - }, - userSubscription: { - plan: 'pro', - status: 'active', - }, - rateLimitInfo: { - allowed: true, - remaining: 100, - resetAt: new Date(), - }, - }), -})) - -vi.mock('@/lib/logs/execution/logging-session', () => ({ - LoggingSession: vi.fn().mockImplementation(() => ({ - safeStart: vi.fn().mockResolvedValue(undefined), - safeCompleteWithError: vi.fn().mockResolvedValue(undefined), - })), -})) +vi.mock('@/lib/logs/execution/logging-session', () => loggingSessionMock) vi.mock('@/lib/workflows/streaming/streaming', () => ({ createStreamingResponse: vi.fn().mockImplementation(async () => createMockStream()), @@ -153,11 +112,7 @@ vi.mock('@/lib/core/utils/sse', () => ({ }, })) -vi.mock('@/lib/core/utils/request', () => requestUtilsMock) - -vi.mock('@/lib/core/security/encryption', () => ({ - decryptSecret: vi.fn().mockResolvedValue({ decrypted: 'test-password' }), -})) +vi.mock('@/lib/core/security/encryption', () => encryptionMock) import { preprocessExecution } from '@/lib/execution/preprocessing' import { createStreamingResponse } from '@/lib/workflows/streaming/streaming' @@ -202,6 +157,27 @@ describe('Chat Identifier API Route', () => { beforeEach(() => { vi.clearAllMocks() + executionPreprocessingMockFns.mockPreprocessExecution.mockResolvedValue({ + success: true, + actorUserId: 'test-user-id', + workflowRecord: { + id: 'test-workflow-id', + userId: 'test-user-id', + isDeployed: true, + workspaceId: 'test-workspace-id', + variables: {}, + }, + userSubscription: { + plan: 'pro', + status: 'active', + }, + rateLimitInfo: { + allowed: true, + remaining: 100, + resetAt: new Date(), + }, + }) + mockAddCorsHeaders.mockImplementation((response: Response) => response) mockValidateChatAuth.mockResolvedValue({ authorized: true }) mockValidateAuthToken.mockReturnValue(false) @@ -218,7 +194,7 @@ describe('Chat Identifier API Route', () => { return new Response(JSON.stringify(data), { status: 200 }) }) - mockDbSelect.mockImplementation((fields: Record) => { + dbChainMockFns.select.mockImplementation((fields: Record) => { if (fields && fields.isDeployed !== undefined) { return { from: vi.fn().mockReturnValue({ @@ -238,10 +214,6 @@ describe('Chat Identifier API Route', () => { }) }) - afterEach(() => { - vi.clearAllMocks() - }) - describe('GET endpoint', () => { it('should return chat info for a valid identifier', async () => { const req = createMockNextRequest('GET') @@ -260,7 +232,7 @@ describe('Chat Identifier API Route', () => { }) it('should return 404 for non-existent identifier', async () => { - mockDbSelect.mockImplementation(() => { + dbChainMockFns.select.mockImplementation(() => { return { from: vi.fn().mockReturnValue({ where: vi.fn().mockReturnValue({ @@ -283,7 +255,7 @@ describe('Chat Identifier API Route', () => { }) it('should return 403 for inactive chat', async () => { - mockDbSelect.mockImplementation(() => { + dbChainMockFns.select.mockImplementation(() => { return { from: vi.fn().mockReturnValue({ where: vi.fn().mockReturnValue({ @@ -331,7 +303,7 @@ describe('Chat Identifier API Route', () => { }) describe('POST endpoint', () => { - it('should handle authentication requests without input', async () => { + it('should return chat config on successful authentication', async () => { const req = createMockNextRequest('POST', { password: 'test-password' }) const params = Promise.resolve({ identifier: 'password-protected-chat' }) @@ -340,7 +312,10 @@ describe('Chat Identifier API Route', () => { expect(response.status).toBe(200) const data = await response.json() - expect(data).toHaveProperty('authenticated', true) + expect(data).toHaveProperty('id', 'chat-id') + expect(data).toHaveProperty('title', 'Test Chat') + expect(data).toHaveProperty('customizations') + expect(data.customizations).toHaveProperty('welcomeMessage', 'Welcome to the test chat') expect(mockSetChatAuthCookie).toHaveBeenCalled() }) diff --git a/apps/sim/app/api/chat/[identifier]/route.ts b/apps/sim/app/api/chat/[identifier]/route.ts index de826d8c7e9..8abc55835ed 100644 --- a/apps/sim/app/api/chat/[identifier]/route.ts +++ b/apps/sim/app/api/chat/[identifier]/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { chat, workflow } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, isNull } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { addCorsHeaders, validateAuthToken } from '@/lib/core/security/deployment' import { generateRequestId } from '@/lib/core/utils/request' -import { generateId } from '@/lib/core/utils/uuid' import { preprocessExecution } from '@/lib/execution/preprocessing' import { LoggingSession } from '@/lib/logs/execution/logging-session' import { ChatFiles } from '@/lib/uploads' @@ -15,6 +15,26 @@ import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/ const logger = createLogger('ChatIdentifierAPI') +interface ChatConfigSource { + id: string + title: string + description: string | null + customizations: unknown + authType: string | null + outputConfigs: unknown +} + +function toChatConfigResponse(deployment: ChatConfigSource) { + return { + id: deployment.id, + title: deployment.title, + description: deployment.description, + customizations: deployment.customizations, + authType: deployment.authType, + outputConfigs: deployment.outputConfigs, + } +} + const chatFileSchema = z.object({ name: z.string().min(1, 'File name is required'), type: z.string().min(1, 'File type is required'), @@ -66,6 +86,9 @@ export async function POST( const deploymentResult = await db .select({ id: chat.id, + title: chat.title, + description: chat.description, + customizations: chat.customizations, workflowId: chat.workflowId, userId: chat.userId, isActive: chat.isActive, @@ -139,7 +162,10 @@ export async function POST( const { input, password, email, conversationId, files } = parsedBody if ((password || email) && !input) { - const response = addCorsHeaders(createSuccessResponse({ authenticated: true }), request) + const response = addCorsHeaders( + createSuccessResponse(toChatConfigResponse(deployment)), + request + ) setChatAuthCookie(response, deployment.id, deployment.authType, deployment.password) @@ -346,17 +372,7 @@ export async function GET( authCookie && validateAuthToken(authCookie.value, deployment.id, deployment.password) ) { - return addCorsHeaders( - createSuccessResponse({ - id: deployment.id, - title: deployment.title, - description: deployment.description, - customizations: deployment.customizations, - authType: deployment.authType, - outputConfigs: deployment.outputConfigs, - }), - request - ) + return addCorsHeaders(createSuccessResponse(toChatConfigResponse(deployment)), request) } const authResult = await validateChatAuth(requestId, deployment, request) @@ -370,17 +386,7 @@ export async function GET( ) } - return addCorsHeaders( - createSuccessResponse({ - id: deployment.id, - title: deployment.title, - description: deployment.description, - customizations: deployment.customizations, - authType: deployment.authType, - outputConfigs: deployment.outputConfigs, - }), - request - ) + return addCorsHeaders(createSuccessResponse(toChatConfigResponse(deployment)), request) } catch (error: any) { logger.error(`[${requestId}] Error fetching chat info:`, error) return addCorsHeaders( 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 63fad11f3a6..81c04439e37 100644 --- a/apps/sim/app/api/chat/manage/[id]/route.test.ts +++ b/apps/sim/app/api/chat/manage/[id]/route.test.ts @@ -3,52 +3,35 @@ * * @vitest-environment node */ -import { auditMock } from '@sim/testing' +import { + auditMock, + authMockFns, + dbChainMock, + dbChainMockFns, + encryptionMock, + encryptionMockFns, + resetDbChainMock, + workflowsApiUtilsMock, + workflowsApiUtilsMockFns, + workflowsOrchestrationMock, + workflowsOrchestrationMockFns, + workflowsPersistenceUtilsMock, + workflowsPersistenceUtilsMockFns, +} from '@sim/testing' import { NextRequest } from 'next/server' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -const { - mockGetSession, - mockSelect, - mockFrom, - mockWhere, - mockLimit, - mockUpdate, - mockSet, - mockCreateSuccessResponse, - mockCreateErrorResponse, - mockEncryptSecret, - mockCheckChatAccess, - mockDeployWorkflow, - mockPerformChatUndeploy, - mockLogger, -} = vi.hoisted(() => { - const logger = { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - trace: vi.fn(), - fatal: vi.fn(), - child: vi.fn(), - } - return { - mockGetSession: vi.fn(), - mockSelect: vi.fn(), - mockFrom: vi.fn(), - mockWhere: vi.fn(), - mockLimit: vi.fn(), - mockUpdate: vi.fn(), - mockSet: vi.fn(), - mockCreateSuccessResponse: vi.fn(), - mockCreateErrorResponse: vi.fn(), - mockEncryptSecret: vi.fn(), - mockCheckChatAccess: vi.fn(), - mockDeployWorkflow: vi.fn(), - mockPerformChatUndeploy: vi.fn(), - mockLogger: logger, - } -}) +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockCheckChatAccess } = vi.hoisted(() => ({ + mockCheckChatAccess: vi.fn(), +})) + +const mockCreateSuccessResponse = workflowsApiUtilsMockFns.mockCreateSuccessResponse +const mockCreateErrorResponse = workflowsApiUtilsMockFns.mockCreateErrorResponse +const mockEncryptSecret = encryptionMockFns.mockEncryptSecret +const mockDeployWorkflow = workflowsPersistenceUtilsMockFns.mockDeployWorkflow +const mockPerformChatUndeploy = workflowsOrchestrationMockFns.mockPerformChatUndeploy +const mockNotifySocketDeploymentChanged = + workflowsOrchestrationMockFns.mockNotifySocketDeploymentChanged vi.mock('@/lib/audit/log', () => auditMock) vi.mock('@/lib/core/config/feature-flags', () => ({ @@ -56,59 +39,24 @@ vi.mock('@/lib/core/config/feature-flags', () => ({ isHosted: false, isProd: false, })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) -vi.mock('@sim/logger', () => ({ - createLogger: vi.fn().mockReturnValue(mockLogger), -})) -vi.mock('@sim/db', () => ({ - db: { - select: mockSelect, - update: mockUpdate, - }, -})) -vi.mock('@sim/db/schema', () => ({ - chat: { id: 'id', identifier: 'identifier', userId: 'userId', archivedAt: 'archivedAt' }, -})) -vi.mock('@/app/api/workflows/utils', () => ({ - createSuccessResponse: mockCreateSuccessResponse, - createErrorResponse: mockCreateErrorResponse, -})) -vi.mock('@/lib/core/security/encryption', () => ({ - encryptSecret: mockEncryptSecret, -})) +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, })) -vi.mock('@/lib/workflows/persistence/utils', () => ({ - deployWorkflow: mockDeployWorkflow, -})) -vi.mock('@/lib/workflows/orchestration', () => ({ - performChatUndeploy: mockPerformChatUndeploy, - notifySocketDeploymentChanged: vi.fn().mockResolvedValue(undefined), -})) -vi.mock('drizzle-orm', () => ({ - and: vi.fn((...conditions: unknown[]) => ({ type: 'and', conditions })), - eq: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'eq' })), - isNull: vi.fn((field: unknown) => ({ type: 'isNull', field })), -})) +vi.mock('@/lib/workflows/persistence/utils', () => workflowsPersistenceUtilsMock) +vi.mock('@/lib/workflows/orchestration', () => workflowsOrchestrationMock) import { DELETE, GET, PATCH } from '@/app/api/chat/manage/[id]/route' describe('Chat Edit API Route', () => { beforeEach(() => { vi.clearAllMocks() - - mockLimit.mockResolvedValue([]) - mockSelect.mockReturnValue({ from: mockFrom }) - mockFrom.mockReturnValue({ where: mockWhere }) - mockWhere.mockReturnValue({ limit: mockLimit }) - mockUpdate.mockReturnValue({ set: mockSet }) - mockSet.mockReturnValue({ where: mockWhere }) + resetDbChainMock() mockPerformChatUndeploy.mockResolvedValue({ success: true }) mockCreateSuccessResponse.mockImplementation((data) => { @@ -126,15 +74,12 @@ describe('Chat Edit API Route', () => { mockEncryptSecret.mockResolvedValue({ encrypted: 'encrypted-password' }) mockDeployWorkflow.mockResolvedValue({ success: true, version: 1 }) - }) - - afterEach(() => { - vi.clearAllMocks() + mockNotifySocketDeploymentChanged.mockResolvedValue(undefined) }) describe('GET', () => { it('should return 401 when user is not authenticated', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123') const response = await GET(req, { params: Promise.resolve({ id: 'chat-123' }) }) @@ -145,7 +90,7 @@ describe('Chat Edit API Route', () => { }) it('should return 404 when chat not found or access denied', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-id' }, }) @@ -161,7 +106,7 @@ describe('Chat Edit API Route', () => { }) it('should return chat details when user has access', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-id' }, }) @@ -191,7 +136,7 @@ describe('Chat Edit API Route', () => { describe('PATCH', () => { it('should return 401 when user is not authenticated', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123', { method: 'PATCH', @@ -205,7 +150,7 @@ describe('Chat Edit API Route', () => { }) it('should return 404 when chat not found or access denied', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-id' }, }) @@ -224,7 +169,7 @@ describe('Chat Edit API Route', () => { }) it('should update chat when user has access', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-id' }, }) @@ -249,7 +194,7 @@ describe('Chat Edit API Route', () => { const response = await PATCH(req, { params: Promise.resolve({ id: 'chat-123' }) }) expect(response.status).toBe(200) - expect(mockUpdate).toHaveBeenCalled() + expect(dbChainMockFns.update).toHaveBeenCalled() const data = await response.json() expect(data.id).toBe('chat-123') expect(data.chatUrl).toBe('http://localhost:3000/chat/test-chat') @@ -257,7 +202,7 @@ describe('Chat Edit API Route', () => { }) it('should handle identifier conflicts', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-id' }, }) @@ -270,9 +215,9 @@ describe('Chat Edit API Route', () => { mockCheckChatAccess.mockResolvedValue({ hasAccess: true, chat: mockChat }) - mockLimit.mockReset() - mockLimit.mockResolvedValue([{ id: 'other-chat-id', identifier: 'new-identifier' }]) - mockWhere.mockReturnValue({ limit: mockLimit }) + dbChainMockFns.limit.mockResolvedValueOnce([ + { id: 'other-chat-id', identifier: 'new-identifier' }, + ]) const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123', { method: 'PATCH', @@ -286,7 +231,7 @@ describe('Chat Edit API Route', () => { }) it('should validate password requirement for password auth', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-id' }, }) @@ -313,7 +258,7 @@ describe('Chat Edit API Route', () => { }) it('should keep the existing password when updating a password-protected chat', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-id' }, }) @@ -340,7 +285,7 @@ describe('Chat Edit API Route', () => { expect(response.status).toBe(200) expect(mockEncryptSecret).not.toHaveBeenCalled() - expect(mockSet).toHaveBeenCalledWith( + expect(dbChainMockFns.set).toHaveBeenCalledWith( expect.objectContaining({ authType: 'password', allowedEmails: [], @@ -348,12 +293,12 @@ describe('Chat Edit API Route', () => { }) ) - const updatePayload = mockSet.mock.calls[0]?.[0] + const updatePayload = dbChainMockFns.set.mock.calls[0]?.[0] expect(updatePayload.password).toBeUndefined() }) it('should allow access when user has workspace admin permission', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'admin-user-id' }, }) @@ -384,7 +329,7 @@ describe('Chat Edit API Route', () => { describe('DELETE', () => { it('should return 401 when user is not authenticated', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123', { method: 'DELETE', @@ -397,7 +342,7 @@ describe('Chat Edit API Route', () => { }) it('should return 404 when chat not found or access denied', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-id' }, }) @@ -415,7 +360,7 @@ describe('Chat Edit API Route', () => { }) it('should delete chat when user has access', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-id' }, }) @@ -441,7 +386,7 @@ describe('Chat Edit API Route', () => { }) it('should allow deletion when user has workspace admin permission', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'admin-user-id' }, }) diff --git a/apps/sim/app/api/chat/route.test.ts b/apps/sim/app/api/chat/route.test.ts index 88fbe2d22e1..59d6a72b0e4 100644 --- a/apps/sim/app/api/chat/route.test.ts +++ b/apps/sim/app/api/chat/route.test.ts @@ -3,65 +3,36 @@ * * @vitest-environment node */ -import { createEnvMock } from '@sim/testing' +import { + authMockFns, + createEnvMock, + dbChainMock, + dbChainMockFns, + workflowsApiUtilsMock, + workflowsApiUtilsMockFns, + workflowsOrchestrationMock, + workflowsOrchestrationMockFns, +} from '@sim/testing' import { NextRequest } from 'next/server' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -const { - mockSelect, - mockFrom, - mockWhere, - mockLimit, - mockCreateSuccessResponse, - mockCreateErrorResponse, - mockCheckWorkflowAccessForChatCreation, - mockPerformChatDeploy, - mockGetSession, -} = vi.hoisted(() => ({ - mockSelect: vi.fn(), - mockFrom: vi.fn(), - mockWhere: vi.fn(), - mockLimit: vi.fn(), - mockCreateSuccessResponse: vi.fn(), - mockCreateErrorResponse: vi.fn(), +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockCheckWorkflowAccessForChatCreation } = vi.hoisted(() => ({ mockCheckWorkflowAccessForChatCreation: vi.fn(), - mockPerformChatDeploy: vi.fn(), - mockGetSession: vi.fn(), })) -vi.mock('@sim/db', () => ({ - db: { - select: mockSelect, - }, -})) +const mockCreateSuccessResponse = workflowsApiUtilsMockFns.mockCreateSuccessResponse +const mockCreateErrorResponse = workflowsApiUtilsMockFns.mockCreateErrorResponse +const mockPerformChatDeploy = workflowsOrchestrationMockFns.mockPerformChatDeploy -vi.mock('@sim/db/schema', () => ({ - chat: { userId: 'userId', identifier: 'identifier', archivedAt: 'archivedAt' }, - workflow: { id: 'id', userId: 'userId', isDeployed: 'isDeployed' }, -})) +vi.mock('@sim/db', () => dbChainMock) -vi.mock('drizzle-orm', () => ({ - and: vi.fn((...conditions: unknown[]) => ({ type: 'and', conditions })), - eq: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'eq' })), - isNull: vi.fn((field: unknown) => ({ type: 'isNull', field })), -})) - -vi.mock('@/app/api/workflows/utils', () => ({ - createSuccessResponse: mockCreateSuccessResponse, - createErrorResponse: mockCreateErrorResponse, -})) +vi.mock('@/app/api/workflows/utils', () => workflowsApiUtilsMock) vi.mock('@/app/api/chat/utils', () => ({ checkWorkflowAccessForChatCreation: mockCheckWorkflowAccessForChatCreation, })) -vi.mock('@/lib/workflows/orchestration', () => ({ - performChatDeploy: mockPerformChatDeploy, -})) - -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) +vi.mock('@/lib/workflows/orchestration', () => workflowsOrchestrationMock) vi.mock('@/lib/core/config/env', () => createEnvMock({ @@ -76,10 +47,6 @@ describe('Chat API Route', () => { beforeEach(() => { vi.clearAllMocks() - mockSelect.mockReturnValue({ from: mockFrom }) - mockFrom.mockReturnValue({ where: mockWhere }) - mockWhere.mockReturnValue({ limit: mockLimit }) - mockCreateSuccessResponse.mockImplementation((data) => { return new Response(JSON.stringify(data), { status: 200, @@ -101,13 +68,9 @@ describe('Chat API Route', () => { }) }) - afterEach(() => { - vi.clearAllMocks() - }) - describe('GET', () => { it('should return 401 when user is not authenticated', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = new NextRequest('http://localhost:3000/api/chat') const response = await GET(req) @@ -117,27 +80,27 @@ describe('Chat API Route', () => { }) it('should return chat deployments for authenticated user', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-id' }, }) const mockDeployments = [{ id: 'deployment-1' }, { id: 'deployment-2' }] - mockWhere.mockResolvedValue(mockDeployments) + dbChainMockFns.where.mockResolvedValueOnce(mockDeployments) const req = new NextRequest('http://localhost:3000/api/chat') const response = await GET(req) expect(response.status).toBe(200) expect(mockCreateSuccessResponse).toHaveBeenCalledWith({ deployments: mockDeployments }) - expect(mockWhere).toHaveBeenCalled() + expect(dbChainMockFns.where).toHaveBeenCalled() }) it('should handle errors when fetching deployments', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-id' }, }) - mockWhere.mockRejectedValue(new Error('Database error')) + dbChainMockFns.where.mockRejectedValueOnce(new Error('Database error')) const req = new NextRequest('http://localhost:3000/api/chat') const response = await GET(req) @@ -149,7 +112,7 @@ describe('Chat API Route', () => { describe('POST', () => { it('should return 401 when user is not authenticated', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = new NextRequest('http://localhost:3000/api/chat', { method: 'POST', @@ -162,7 +125,7 @@ describe('Chat API Route', () => { }) it('should validate request data', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-id' }, }) @@ -178,7 +141,7 @@ describe('Chat API Route', () => { }) it('should reject if identifier already exists', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-id' }, }) @@ -192,7 +155,7 @@ describe('Chat API Route', () => { }, } - mockLimit.mockResolvedValueOnce([{ id: 'existing-chat' }]) // Identifier exists + dbChainMockFns.limit.mockResolvedValueOnce([{ id: 'existing-chat' }]) // Identifier exists mockCheckWorkflowAccessForChatCreation.mockResolvedValue({ hasAccess: false }) const req = new NextRequest('http://localhost:3000/api/chat', { @@ -206,7 +169,7 @@ describe('Chat API Route', () => { }) it('should reject if workflow not found', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-id' }, }) @@ -220,7 +183,7 @@ describe('Chat API Route', () => { }, } - mockLimit.mockResolvedValueOnce([]) // Identifier is available + dbChainMockFns.limit.mockResolvedValueOnce([]) // Identifier is available mockCheckWorkflowAccessForChatCreation.mockResolvedValue({ hasAccess: false }) const req = new NextRequest('http://localhost:3000/api/chat', { @@ -237,7 +200,7 @@ describe('Chat API Route', () => { }) it('should allow chat deployment when user owns workflow directly', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-id', email: 'user@example.com' }, }) @@ -251,7 +214,7 @@ describe('Chat API Route', () => { }, } - mockLimit.mockResolvedValueOnce([]) // Identifier is available + dbChainMockFns.limit.mockResolvedValueOnce([]) // Identifier is available mockCheckWorkflowAccessForChatCreation.mockResolvedValue({ hasAccess: true, workflow: { userId: 'user-id', workspaceId: null, isDeployed: true }, @@ -275,7 +238,7 @@ describe('Chat API Route', () => { }) it('passes chat customizations and outputConfigs through in the API request shape', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-id', email: 'user@example.com' }, }) @@ -291,7 +254,7 @@ describe('Chat API Route', () => { outputConfigs: [{ blockId: 'agent-1', path: 'content' }], } - mockLimit.mockResolvedValueOnce([]) + dbChainMockFns.limit.mockResolvedValueOnce([]) mockCheckWorkflowAccessForChatCreation.mockResolvedValue({ hasAccess: true, workflow: { userId: 'user-id', workspaceId: null, isDeployed: true }, @@ -319,7 +282,7 @@ describe('Chat API Route', () => { }) it('should allow chat deployment when user has workspace admin permission', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-id', email: 'user@example.com' }, }) @@ -333,7 +296,7 @@ describe('Chat API Route', () => { }, } - mockLimit.mockResolvedValueOnce([]) // Identifier is available + dbChainMockFns.limit.mockResolvedValueOnce([]) // Identifier is available mockCheckWorkflowAccessForChatCreation.mockResolvedValue({ hasAccess: true, workflow: { userId: 'other-user-id', workspaceId: 'workspace-123', isDeployed: true }, @@ -356,7 +319,7 @@ describe('Chat API Route', () => { }) it('should reject when workflow is in workspace but user lacks admin permission', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-id' }, }) @@ -370,7 +333,7 @@ describe('Chat API Route', () => { }, } - mockLimit.mockResolvedValueOnce([]) // Identifier is available + dbChainMockFns.limit.mockResolvedValueOnce([]) // Identifier is available mockCheckWorkflowAccessForChatCreation.mockResolvedValue({ hasAccess: false, }) @@ -390,7 +353,7 @@ describe('Chat API Route', () => { }) it('should handle workspace permission check errors gracefully', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-id' }, }) @@ -404,7 +367,7 @@ describe('Chat API Route', () => { }, } - mockLimit.mockResolvedValueOnce([]) // Identifier is available + dbChainMockFns.limit.mockResolvedValueOnce([]) // Identifier is available mockCheckWorkflowAccessForChatCreation.mockRejectedValue(new Error('Permission check failed')) const req = new NextRequest('http://localhost:3000/api/chat', { @@ -418,7 +381,7 @@ describe('Chat API Route', () => { }) it('should call performChatDeploy for undeployed workflow', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-id', email: 'user@example.com' }, }) @@ -432,7 +395,7 @@ describe('Chat API Route', () => { }, } - mockLimit.mockResolvedValueOnce([]) // Identifier is available + dbChainMockFns.limit.mockResolvedValueOnce([]) // Identifier is available mockCheckWorkflowAccessForChatCreation.mockResolvedValue({ hasAccess: true, workflow: { userId: 'user-id', workspaceId: null, isDeployed: false }, diff --git a/apps/sim/app/api/chat/utils.test.ts b/apps/sim/app/api/chat/utils.test.ts index de604854f07..f4cdb54fb84 100644 --- a/apps/sim/app/api/chat/utils.test.ts +++ b/apps/sim/app/api/chat/utils.test.ts @@ -3,12 +3,16 @@ * * @vitest-environment node */ -import { databaseMock, loggerMock, requestUtilsMock } from '@sim/testing' +import { + encryptionMock, + encryptionMockFns, + loggingSessionMock, + workflowsUtilsMock, +} from '@sim/testing' import type { NextResponse } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' const { - mockDecryptSecret, mockMergeSubblockStateWithValues, mockMergeSubBlockValues, mockValidateAuthToken, @@ -16,7 +20,6 @@ const { mockAddCorsHeaders, mockIsEmailAllowed, } = vi.hoisted(() => ({ - mockDecryptSecret: vi.fn(), mockMergeSubblockStateWithValues: vi.fn().mockReturnValue({}), mockMergeSubBlockValues: vi.fn().mockReturnValue({}), mockValidateAuthToken: vi.fn().mockReturnValue(false), @@ -25,16 +28,9 @@ const { mockIsEmailAllowed: vi.fn(), })) -vi.mock('@sim/db', () => databaseMock) -vi.mock('@sim/logger', () => loggerMock) +const mockDecryptSecret = encryptionMockFns.mockDecryptSecret -vi.mock('@/lib/logs/execution/logging-session', () => ({ - LoggingSession: vi.fn().mockImplementation(() => ({ - safeStart: vi.fn().mockResolvedValue(undefined), - safeComplete: vi.fn().mockResolvedValue(undefined), - safeCompleteWithError: vi.fn().mockResolvedValue(undefined), - })), -})) +vi.mock('@/lib/logs/execution/logging-session', () => loggingSessionMock) vi.mock('@/executor', () => ({ Executor: vi.fn(), @@ -49,11 +45,7 @@ vi.mock('@/lib/workflows/subblocks', () => ({ mergeSubBlockValues: mockMergeSubBlockValues, })) -vi.mock('@/lib/core/security/encryption', () => ({ - decryptSecret: mockDecryptSecret, -})) - -vi.mock('@/lib/core/utils/request', () => requestUtilsMock) +vi.mock('@/lib/core/security/encryption', () => encryptionMock) vi.mock('@/lib/core/security/deployment', () => ({ validateAuthToken: mockValidateAuthToken, @@ -68,9 +60,7 @@ vi.mock('@/lib/core/config/feature-flags', () => ({ isProd: false, })) -vi.mock('@/lib/workflows/utils', () => ({ - authorizeWorkflowByWorkspacePermission: vi.fn(), -})) +vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) import { decryptSecret } from '@/lib/core/security/encryption' import { setChatAuthCookie, validateChatAuth } from '@/app/api/chat/utils' 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 81f3a64d57d..28f635828f4 100644 --- a/apps/sim/app/api/copilot/api-keys/route.test.ts +++ b/apps/sim/app/api/copilot/api-keys/route.test.ts @@ -3,33 +3,20 @@ * * @vitest-environment node */ +import { authMockFns, createEnvMock } from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetSession, mockFetch } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), +const { mockFetch } = vi.hoisted(() => ({ mockFetch: vi.fn(), })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - vi.mock('@/lib/copilot/constants', () => ({ SIM_AGENT_API_URL_DEFAULT: 'https://agent.sim.example.com', SIM_AGENT_API_URL: 'https://agent.sim.example.com', })) -vi.mock('@/lib/core/config/env', () => ({ - env: { - COPILOT_API_KEY: 'test-api-key', - }, - getEnv: vi.fn(), - 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, -})) +vi.mock('@/lib/core/config/env', () => createEnvMock({ COPILOT_API_KEY: 'test-api-key' })) import { DELETE, GET } from '@/app/api/copilot/api-keys/route' @@ -41,7 +28,7 @@ describe('Copilot API Keys API Route', () => { describe('GET', () => { it('should return 401 when user is not authenticated', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const request = new NextRequest('http://localhost:3000/api/copilot/api-keys') const response = await GET(request) @@ -52,7 +39,9 @@ describe('Copilot API Keys API Route', () => { }) it('should return list of API keys with masked values', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) const mockApiKeys = [ { @@ -90,7 +79,9 @@ describe('Copilot API Keys API Route', () => { }) it('should return empty array when user has no API keys', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) mockFetch.mockResolvedValueOnce({ ok: true, @@ -106,7 +97,9 @@ describe('Copilot API Keys API Route', () => { }) it('should forward userId to Sim Agent', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) mockFetch.mockResolvedValueOnce({ ok: true, @@ -130,7 +123,9 @@ describe('Copilot API Keys API Route', () => { }) it('should return error when Sim Agent returns non-ok response', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) mockFetch.mockResolvedValueOnce({ ok: false, @@ -147,7 +142,9 @@ describe('Copilot API Keys API Route', () => { }) it('should return 500 when Sim Agent returns invalid response', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) mockFetch.mockResolvedValueOnce({ ok: true, @@ -163,7 +160,9 @@ describe('Copilot API Keys API Route', () => { }) it('should handle network errors gracefully', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) mockFetch.mockRejectedValueOnce(new Error('Network error')) @@ -176,7 +175,9 @@ describe('Copilot API Keys API Route', () => { }) it('should handle API keys with empty apiKey string', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) const mockApiKeys = [ { @@ -202,7 +203,9 @@ describe('Copilot API Keys API Route', () => { }) it('should handle JSON parsing errors from Sim Agent', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) mockFetch.mockResolvedValueOnce({ ok: true, @@ -220,7 +223,7 @@ describe('Copilot API Keys API Route', () => { describe('DELETE', () => { it('should return 401 when user is not authenticated', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const request = new NextRequest('http://localhost:3000/api/copilot/api-keys?id=key-123') const response = await DELETE(request) @@ -231,7 +234,9 @@ describe('Copilot API Keys API Route', () => { }) it('should return 400 when id parameter is missing', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) const request = new NextRequest('http://localhost:3000/api/copilot/api-keys') const response = await DELETE(request) @@ -242,7 +247,9 @@ describe('Copilot API Keys API Route', () => { }) it('should successfully delete an API key', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) mockFetch.mockResolvedValueOnce({ ok: true, @@ -270,7 +277,9 @@ describe('Copilot API Keys API Route', () => { }) it('should return error when Sim Agent returns non-ok response', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) mockFetch.mockResolvedValueOnce({ ok: false, @@ -287,7 +296,9 @@ describe('Copilot API Keys API Route', () => { }) it('should return 500 when Sim Agent returns invalid response', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) mockFetch.mockResolvedValueOnce({ ok: true, @@ -303,7 +314,9 @@ describe('Copilot API Keys API Route', () => { }) it('should handle network errors gracefully', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) mockFetch.mockRejectedValueOnce(new Error('Network error')) @@ -316,7 +329,9 @@ describe('Copilot API Keys API Route', () => { }) it('should handle JSON parsing errors from Sim Agent on delete', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) mockFetch.mockResolvedValueOnce({ ok: true, diff --git a/apps/sim/app/api/copilot/chat/abort/route.ts b/apps/sim/app/api/copilot/chat/abort/route.ts index f49168b8820..75e60f2078c 100644 --- a/apps/sim/app/api/copilot/chat/abort/route.ts +++ b/apps/sim/app/api/copilot/chat/abort/route.ts @@ -1,11 +1,11 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { NextResponse } from 'next/server' import { getLatestRunForStream } from '@/lib/copilot/async-runs/repository' import { SIM_AGENT_API_URL } from '@/lib/copilot/constants' import { authenticateCopilotRequestSessionOnly } from '@/lib/copilot/request/http' import { abortActiveStream, waitForPendingChatStream } from '@/lib/copilot/request/session' import { env } from '@/lib/core/config/env' -import { toError } from '@/lib/core/utils/helpers' const logger = createLogger('CopilotChatAbortAPI') const GO_EXPLICIT_ABORT_TIMEOUT_MS = 3000 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 0493b3ffe89..4d1ef809e78 100644 --- a/apps/sim/app/api/copilot/chat/delete/route.test.ts +++ b/apps/sim/app/api/copilot/chat/delete/route.test.ts @@ -3,38 +3,15 @@ * * @vitest-environment node */ +import { authMockFns, dbChainMock, dbChainMockFns } from '@sim/testing' import { NextRequest } from 'next/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -const { mockDelete, mockWhere, mockGetSession, mockGetAccessibleCopilotChat } = vi.hoisted(() => ({ - mockDelete: vi.fn(), - mockWhere: vi.fn(), - mockGetSession: vi.fn(), +const { mockGetAccessibleCopilotChat } = vi.hoisted(() => ({ mockGetAccessibleCopilotChat: vi.fn(), })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - -vi.mock('@sim/db', () => ({ - db: { - delete: mockDelete, - }, -})) - -vi.mock('@sim/db/schema', () => ({ - copilotChats: { - id: 'id', - userId: 'userId', - workspaceId: 'workspaceId', - }, -})) - -vi.mock('drizzle-orm', () => ({ - and: vi.fn((...conditions: unknown[]) => ({ conditions, type: 'and' })), - eq: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'eq' })), -})) +vi.mock('@sim/db', () => dbChainMock) vi.mock('@/lib/copilot/chat/lifecycle', () => ({ getAccessibleCopilotChat: mockGetAccessibleCopilotChat, @@ -58,11 +35,9 @@ describe('Copilot Chat Delete API Route', () => { beforeEach(() => { vi.clearAllMocks() - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) - const mockReturning = vi.fn().mockResolvedValue([{ workspaceId: 'ws-1' }]) - mockWhere.mockReturnValue({ returning: mockReturning }) - mockDelete.mockReturnValue({ where: mockWhere }) + dbChainMockFns.returning.mockResolvedValue([{ workspaceId: 'ws-1' }]) mockGetAccessibleCopilotChat.mockResolvedValue({ id: 'chat-123', userId: 'user-123' }) }) @@ -72,7 +47,7 @@ describe('Copilot Chat Delete API Route', () => { describe('DELETE', () => { it('should return 401 when user is not authenticated', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = createMockRequest('DELETE', { chatId: 'chat-123', @@ -86,7 +61,7 @@ describe('Copilot Chat Delete API Route', () => { }) it('should successfully delete a chat', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const req = createMockRequest('DELETE', { chatId: 'chat-123', @@ -98,12 +73,12 @@ describe('Copilot Chat Delete API Route', () => { const responseData = await response.json() expect(responseData).toEqual({ success: true }) - expect(mockDelete).toHaveBeenCalled() - expect(mockWhere).toHaveBeenCalled() + expect(dbChainMockFns.delete).toHaveBeenCalled() + expect(dbChainMockFns.where).toHaveBeenCalled() }) it('should return 500 for invalid request body - missing chatId', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const req = createMockRequest('DELETE', {}) @@ -115,7 +90,7 @@ describe('Copilot Chat Delete API Route', () => { }) it('should return 500 for invalid request body - chatId is not a string', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const req = createMockRequest('DELETE', { chatId: 12345, @@ -129,9 +104,9 @@ describe('Copilot Chat Delete API Route', () => { }) it('should handle database errors gracefully', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) - mockWhere.mockRejectedValueOnce(new Error('Database connection failed')) + dbChainMockFns.returning.mockRejectedValueOnce(new Error('Database connection failed')) const req = createMockRequest('DELETE', { chatId: 'chat-123', @@ -145,7 +120,7 @@ describe('Copilot Chat Delete API Route', () => { }) it('should handle JSON parsing errors in request body', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const req = new NextRequest('http://localhost:3000/api/copilot/chat/delete', { method: 'DELETE', @@ -163,7 +138,7 @@ describe('Copilot Chat Delete API Route', () => { }) it('should delete chat even if it does not exist (idempotent)', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) mockGetAccessibleCopilotChat.mockResolvedValueOnce(null) @@ -179,7 +154,7 @@ describe('Copilot Chat Delete API Route', () => { }) it('should delete chat with empty string chatId (validation should fail)', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const req = createMockRequest('DELETE', { chatId: '', @@ -188,7 +163,7 @@ describe('Copilot Chat Delete API Route', () => { const response = await DELETE(req) expect(response.status).toBe(200) - expect(mockDelete).toHaveBeenCalled() + expect(dbChainMockFns.delete).toHaveBeenCalled() }) }) }) diff --git a/apps/sim/app/api/copilot/chat/queries.ts b/apps/sim/app/api/copilot/chat/queries.ts index 25274224be8..8252c493413 100644 --- a/apps/sim/app/api/copilot/chat/queries.ts +++ b/apps/sim/app/api/copilot/chat/queries.ts @@ -1,6 +1,7 @@ import { db } from '@sim/db' import { copilotChats } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { and, desc, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getLatestRunForStream } from '@/lib/copilot/async-runs/repository' @@ -16,7 +17,6 @@ import { import { readFilePreviewSessions } from '@/lib/copilot/request/session' import { readEvents } from '@/lib/copilot/request/session/buffer' import { toStreamBatchEvent } from '@/lib/copilot/request/session/types' -import { toError } from '@/lib/core/utils/helpers' import { authorizeWorkflowByWorkspacePermission } from '@/lib/workflows/utils' import { assertActiveWorkspaceAccess } from '@/lib/workspaces/permissions/utils' 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 21c32e38a76..0ac05257bf6 100644 --- a/apps/sim/app/api/copilot/chat/stop/route.test.ts +++ b/apps/sim/app/api/copilot/chat/stop/route.test.ts @@ -1,11 +1,11 @@ /** * @vitest-environment node */ +import { authMockFns } from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' const { - mockGetSession, mockSelect, mockFrom, mockWhereSelect, @@ -17,7 +17,6 @@ const { mockPublishStatusChanged, mockSql, } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), mockSelect: vi.fn(), mockFrom: vi.fn(), mockWhereSelect: vi.fn(), @@ -30,10 +29,6 @@ const { mockSql: vi.fn((strings: TemplateStringsArray, ...values: unknown[]) => ({ strings, values })), })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - vi.mock('@sim/db', () => ({ db: { select: mockSelect, @@ -41,16 +36,6 @@ vi.mock('@sim/db', () => ({ }, })) -vi.mock('@sim/db/schema', () => ({ - copilotChats: { - id: 'id', - userId: 'userId', - workspaceId: 'workspaceId', - messages: 'messages', - conversationId: 'conversationId', - }, -})) - vi.mock('drizzle-orm', () => ({ and: vi.fn((...conditions: unknown[]) => ({ conditions, type: 'and' })), eq: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'eq' })), @@ -77,7 +62,7 @@ describe('copilot chat stop route', () => { beforeEach(() => { vi.clearAllMocks() - mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) mockLimit.mockResolvedValue([ { @@ -96,7 +81,7 @@ describe('copilot chat stop route', () => { }) it('returns 401 when unauthenticated', async () => { - mockGetSession.mockResolvedValueOnce(null) + authMockFns.mockGetSession.mockResolvedValueOnce(null) const response = await POST( createRequest({ diff --git a/apps/sim/app/api/copilot/chat/stop/route.ts b/apps/sim/app/api/copilot/chat/stop/route.ts index 05e5935aa40..40070b64e0d 100644 --- a/apps/sim/app/api/copilot/chat/stop/route.ts +++ b/apps/sim/app/api/copilot/chat/stop/route.ts @@ -1,13 +1,13 @@ import { db } from '@sim/db' import { copilotChats } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { normalizeMessage, type PersistedMessage } from '@/lib/copilot/chat/persisted-message' import { taskPubSub } from '@/lib/copilot/tasks' -import { generateId } from '@/lib/core/utils/uuid' const logger = createLogger('CopilotChatStopAPI') diff --git a/apps/sim/app/api/copilot/chat/stream/route.test.ts b/apps/sim/app/api/copilot/chat/stream/route.test.ts index 3105f9216ce..5f24bc2e1e7 100644 --- a/apps/sim/app/api/copilot/chat/stream/route.test.ts +++ b/apps/sim/app/api/copilot/chat/stream/route.test.ts @@ -2,6 +2,7 @@ * @vitest-environment node */ +import { copilotHttpMock, copilotHttpMockFns } from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' import { @@ -9,19 +10,13 @@ import { MothershipStreamV1EventType, } from '@/lib/copilot/generated/mothership-stream-v1' -const { - getLatestRunForStream, - readEvents, - readFilePreviewSessions, - checkForReplayGap, - authenticateCopilotRequestSessionOnly, -} = vi.hoisted(() => ({ - getLatestRunForStream: vi.fn(), - readEvents: vi.fn(), - readFilePreviewSessions: vi.fn(), - checkForReplayGap: vi.fn(), - authenticateCopilotRequestSessionOnly: vi.fn(), -})) +const { getLatestRunForStream, readEvents, readFilePreviewSessions, checkForReplayGap } = + vi.hoisted(() => ({ + getLatestRunForStream: vi.fn(), + readEvents: vi.fn(), + readFilePreviewSessions: vi.fn(), + checkForReplayGap: vi.fn(), + })) vi.mock('@/lib/copilot/async-runs/repository', () => ({ getLatestRunForStream, @@ -48,9 +43,7 @@ vi.mock('@/lib/copilot/request/session', () => ({ }, })) -vi.mock('@/lib/copilot/request/http', () => ({ - authenticateCopilotRequestSessionOnly, -})) +vi.mock('@/lib/copilot/request/http', () => copilotHttpMock) import { GET } from './route' @@ -72,7 +65,7 @@ async function readAllChunks(response: Response): Promise { describe('copilot chat stream replay route', () => { beforeEach(() => { vi.clearAllMocks() - authenticateCopilotRequestSessionOnly.mockResolvedValue({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValue({ userId: 'user-1', isAuthenticated: true, }) diff --git a/apps/sim/app/api/copilot/chat/stream/route.ts b/apps/sim/app/api/copilot/chat/stream/route.ts index a0965b189e2..b88eec2915c 100644 --- a/apps/sim/app/api/copilot/chat/stream/route.ts +++ b/apps/sim/app/api/copilot/chat/stream/route.ts @@ -1,4 +1,6 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { sleep } from '@sim/utils/helpers' import { type NextRequest, NextResponse } from 'next/server' import { getLatestRunForStream } from '@/lib/copilot/async-runs/repository' import { @@ -15,7 +17,6 @@ import { SSE_RESPONSE_HEADERS, } from '@/lib/copilot/request/session' import { toStreamBatchEvent } from '@/lib/copilot/request/session/types' -import { sleep, toError } from '@/lib/core/utils/helpers' export const maxDuration = 3600 diff --git a/apps/sim/app/api/copilot/chat/update-messages/route.test.ts b/apps/sim/app/api/copilot/chat/update-messages/route.test.ts index 512a05cfd84..a2f45487a61 100644 --- a/apps/sim/app/api/copilot/chat/update-messages/route.test.ts +++ b/apps/sim/app/api/copilot/chat/update-messages/route.test.ts @@ -3,32 +3,20 @@ * * @vitest-environment node */ +import { authMockFns } from '@sim/testing' import { NextRequest } from 'next/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -const { - mockSelect, - mockFrom, - mockWhere, - mockLimit, - mockUpdate, - mockSet, - mockUpdateWhere, - mockGetSession, -} = vi.hoisted(() => ({ - mockSelect: vi.fn(), - mockFrom: vi.fn(), - mockWhere: vi.fn(), - mockLimit: vi.fn(), - mockUpdate: vi.fn(), - mockSet: vi.fn(), - mockUpdateWhere: vi.fn(), - mockGetSession: vi.fn(), -})) - -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) +const { mockSelect, mockFrom, mockWhere, mockLimit, mockUpdate, mockSet, mockUpdateWhere } = + vi.hoisted(() => ({ + mockSelect: vi.fn(), + mockFrom: vi.fn(), + mockWhere: vi.fn(), + mockLimit: vi.fn(), + mockUpdate: vi.fn(), + mockSet: vi.fn(), + mockUpdateWhere: vi.fn(), + })) vi.mock('@sim/db', () => ({ db: { @@ -37,15 +25,6 @@ vi.mock('@sim/db', () => ({ }, })) -vi.mock('@sim/db/schema', () => ({ - copilotChats: { - id: 'id', - userId: 'userId', - messages: 'messages', - updatedAt: 'updatedAt', - }, -})) - vi.mock('drizzle-orm', () => ({ and: vi.fn((...conditions: unknown[]) => ({ conditions, type: 'and' })), eq: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'eq' })), @@ -65,7 +44,7 @@ describe('Copilot Chat Update Messages API Route', () => { beforeEach(() => { vi.clearAllMocks() - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) mockSelect.mockReturnValue({ from: mockFrom }) mockFrom.mockReturnValue({ where: mockWhere }) @@ -82,7 +61,7 @@ describe('Copilot Chat Update Messages API Route', () => { describe('POST', () => { it('should return 401 when user is not authenticated', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = createMockRequest('POST', { chatId: 'chat-123', @@ -104,7 +83,7 @@ describe('Copilot Chat Update Messages API Route', () => { }) it('should return 400 for invalid request body - missing chatId', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const req = createMockRequest('POST', { messages: [ @@ -125,7 +104,7 @@ describe('Copilot Chat Update Messages API Route', () => { }) it('should return 400 for invalid request body - missing messages', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const req = createMockRequest('POST', { chatId: 'chat-123', @@ -139,7 +118,7 @@ describe('Copilot Chat Update Messages API Route', () => { }) it('should return 400 for invalid message structure - missing required fields', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const req = createMockRequest('POST', { chatId: 'chat-123', @@ -158,7 +137,7 @@ describe('Copilot Chat Update Messages API Route', () => { }) it('should return 400 for invalid message role', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const req = createMockRequest('POST', { chatId: 'chat-123', @@ -180,7 +159,7 @@ describe('Copilot Chat Update Messages API Route', () => { }) it('should return 404 when chat is not found', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) mockLimit.mockResolvedValueOnce([]) @@ -204,7 +183,7 @@ describe('Copilot Chat Update Messages API Route', () => { }) it('should return 404 when chat belongs to different user', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) mockLimit.mockResolvedValueOnce([]) @@ -228,7 +207,7 @@ describe('Copilot Chat Update Messages API Route', () => { }) it('should successfully update chat messages', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const existingChat = { id: 'chat-123', @@ -275,7 +254,7 @@ describe('Copilot Chat Update Messages API Route', () => { }) it('should successfully update chat messages with optional fields', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const existingChat = { id: 'chat-456', @@ -361,7 +340,7 @@ describe('Copilot Chat Update Messages API Route', () => { }) it('should handle empty messages array', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const existingChat = { id: 'chat-789', @@ -391,7 +370,7 @@ describe('Copilot Chat Update Messages API Route', () => { }) it('should handle database errors during chat lookup', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) mockLimit.mockRejectedValueOnce(new Error('Database connection failed')) @@ -415,7 +394,7 @@ describe('Copilot Chat Update Messages API Route', () => { }) it('should handle database errors during update operation', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const existingChat = { id: 'chat-123', @@ -448,7 +427,7 @@ describe('Copilot Chat Update Messages API Route', () => { }) it('should handle JSON parsing errors in request body', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const req = new NextRequest('http://localhost:3000/api/copilot/chat/update-messages', { method: 'POST', @@ -466,7 +445,7 @@ describe('Copilot Chat Update Messages API Route', () => { }) it('should handle large message arrays', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const existingChat = { id: 'chat-large', @@ -503,7 +482,7 @@ describe('Copilot Chat Update Messages API Route', () => { }) it('should handle messages with both user and assistant roles', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const existingChat = { id: 'chat-mixed', diff --git a/apps/sim/app/api/copilot/chats/route.test.ts b/apps/sim/app/api/copilot/chats/route.test.ts index 3dbbf2791f8..11046b7a349 100644 --- a/apps/sim/app/api/copilot/chats/route.test.ts +++ b/apps/sim/app/api/copilot/chats/route.test.ts @@ -3,26 +3,15 @@ * * @vitest-environment node */ +import { copilotHttpMock, copilotHttpMockFns } from '@sim/testing' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -const { - mockSelectDistinctOn, - mockFrom, - mockLeftJoin, - mockWhere, - mockOrderBy, - mockAuthenticate, - mockCreateUnauthorizedResponse, - mockCreateInternalServerErrorResponse, -} = vi.hoisted(() => ({ +const { mockSelectDistinctOn, mockFrom, mockLeftJoin, mockWhere, mockOrderBy } = vi.hoisted(() => ({ mockSelectDistinctOn: vi.fn(), mockFrom: vi.fn(), mockLeftJoin: vi.fn(), mockWhere: vi.fn(), mockOrderBy: vi.fn(), - mockAuthenticate: vi.fn(), - mockCreateUnauthorizedResponse: vi.fn(), - mockCreateInternalServerErrorResponse: vi.fn(), })) vi.mock('@sim/db', () => ({ @@ -31,32 +20,6 @@ vi.mock('@sim/db', () => ({ }, })) -vi.mock('@sim/db/schema', () => ({ - copilotChats: { - id: 'id', - title: 'title', - workflowId: 'workflowId', - workspaceId: 'workspaceId', - userId: 'userId', - updatedAt: 'updatedAt', - }, - workflow: { - id: 'id', - workspaceId: 'workspaceId', - archivedAt: 'archivedAt', - }, - workspace: { - id: 'id', - archivedAt: 'archivedAt', - }, - permissions: { - id: 'id', - entityType: 'entityType', - entityId: 'entityId', - userId: 'userId', - }, -})) - vi.mock('drizzle-orm', () => ({ and: vi.fn((...conditions: unknown[]) => ({ conditions, type: 'and' })), eq: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'eq' })), @@ -66,11 +29,7 @@ vi.mock('drizzle-orm', () => ({ sql: vi.fn(), })) -vi.mock('@/lib/copilot/request/http', () => ({ - authenticateCopilotRequestSessionOnly: mockAuthenticate, - createUnauthorizedResponse: mockCreateUnauthorizedResponse, - createInternalServerErrorResponse: mockCreateInternalServerErrorResponse, -})) +vi.mock('@/lib/copilot/request/http', () => copilotHttpMock) import { GET } from '@/app/api/copilot/chats/route' @@ -83,13 +42,6 @@ describe('Copilot Chats List API Route', () => { mockLeftJoin.mockReturnValue({ leftJoin: mockLeftJoin, where: mockWhere }) mockWhere.mockReturnValue({ orderBy: mockOrderBy }) mockOrderBy.mockResolvedValue([]) - - mockCreateUnauthorizedResponse.mockReturnValue( - new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 }) - ) - mockCreateInternalServerErrorResponse.mockImplementation( - (message: string) => new Response(JSON.stringify({ error: message }), { status: 500 }) - ) }) afterEach(() => { @@ -98,7 +50,7 @@ describe('Copilot Chats List API Route', () => { describe('GET', () => { it('should return 401 when user is not authenticated', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: null, isAuthenticated: false, }) @@ -112,7 +64,7 @@ describe('Copilot Chats List API Route', () => { }) it('should return empty chats array when user has no chats', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -131,7 +83,7 @@ describe('Copilot Chats List API Route', () => { }) it('should return list of chats for authenticated user', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -165,7 +117,7 @@ describe('Copilot Chats List API Route', () => { }) it('should return chats ordered by updatedAt descending', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -202,7 +154,7 @@ describe('Copilot Chats List API Route', () => { }) it('should handle chats with null workflowId', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -226,7 +178,7 @@ describe('Copilot Chats List API Route', () => { }) it('should handle database errors gracefully', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -242,7 +194,7 @@ describe('Copilot Chats List API Route', () => { }) it('should only return chats belonging to authenticated user', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -265,7 +217,7 @@ describe('Copilot Chats List API Route', () => { }) it('should return 401 when userId is null despite isAuthenticated being true', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: null, isAuthenticated: true, }) 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 fe4fb76f4d1..05520d59841 100644 --- a/apps/sim/app/api/copilot/checkpoints/revert/route.test.ts +++ b/apps/sim/app/api/copilot/checkpoints/revert/route.test.ts @@ -3,6 +3,7 @@ * * @vitest-environment node */ +import { authMockFns, workflowsUtilsMock, workflowsUtilsMockFns } from '@sim/testing' import { NextRequest } from 'next/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' @@ -13,8 +14,6 @@ const { mockThen, mockDelete, mockDeleteWhere, - mockAuthorize, - mockGetSession, mockGetAccessibleCopilotChat, } = vi.hoisted(() => ({ mockSelect: vi.fn(), @@ -23,15 +22,9 @@ const { mockThen: vi.fn(), mockDelete: vi.fn(), mockDeleteWhere: vi.fn(), - mockAuthorize: vi.fn(), - mockGetSession: vi.fn(), mockGetAccessibleCopilotChat: vi.fn(), })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - vi.mock('@/lib/core/utils/urls', () => ({ getBaseUrl: vi.fn(() => 'http://localhost:3000'), getInternalApiBaseUrl: vi.fn(() => 'http://localhost:3000'), @@ -39,9 +32,7 @@ vi.mock('@/lib/core/utils/urls', () => ({ getEmailDomain: vi.fn(() => 'localhost:3000'), })) -vi.mock('@/lib/workflows/utils', () => ({ - authorizeWorkflowByWorkspacePermission: mockAuthorize, -})) +vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) vi.mock('@/lib/copilot/chat/lifecycle', () => ({ getAccessibleCopilotChat: mockGetAccessibleCopilotChat, @@ -54,19 +45,6 @@ vi.mock('@sim/db', () => ({ }, })) -vi.mock('@sim/db/schema', () => ({ - workflowCheckpoints: { - id: 'id', - userId: 'userId', - workflowId: 'workflowId', - workflowState: 'workflowState', - }, - workflow: { - id: 'id', - userId: 'userId', - }, -})) - vi.mock('drizzle-orm', () => ({ and: vi.fn((...conditions: unknown[]) => ({ conditions, type: 'and' })), eq: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'eq' })), @@ -83,9 +61,9 @@ describe('Copilot Checkpoints Revert API Route', () => { thenResults = [] - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) - mockAuthorize.mockResolvedValue({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ allowed: true, status: 200, }) @@ -134,12 +112,12 @@ describe('Copilot Checkpoints Revert API Route', () => { /** Helper to set authenticated state */ function setAuthenticated(user = { id: 'user-123', email: 'test@example.com' }) { - mockGetSession.mockResolvedValue({ user }) + authMockFns.mockGetSession.mockResolvedValue({ user }) } /** Helper to set unauthenticated state */ function setUnauthenticated() { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) } describe('POST', () => { @@ -273,7 +251,7 @@ describe('Copilot Checkpoints Revert API Route', () => { thenResults.push(mockCheckpoint) // Checkpoint found thenResults.push(mockWorkflow) // Workflow found but different user - mockAuthorize.mockResolvedValueOnce({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ allowed: false, status: 403, }) diff --git a/apps/sim/app/api/copilot/checkpoints/route.test.ts b/apps/sim/app/api/copilot/checkpoints/route.test.ts index e1b3a1f4e81..e73b6ed0ca9 100644 --- a/apps/sim/app/api/copilot/checkpoints/route.test.ts +++ b/apps/sim/app/api/copilot/checkpoints/route.test.ts @@ -3,6 +3,7 @@ * * @vitest-environment node */ +import { authMockFns, workflowsUtilsMock, workflowsUtilsMockFns } from '@sim/testing' import { NextRequest } from 'next/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' @@ -15,9 +16,7 @@ const { mockInsert, mockValues, mockReturning, - mockGetSession, mockGetAccessibleCopilotChat, - mockAuthorizeWorkflowByWorkspacePermission, } = vi.hoisted(() => ({ mockSelect: vi.fn(), mockFrom: vi.fn(), @@ -27,13 +26,7 @@ const { mockInsert: vi.fn(), mockValues: vi.fn(), mockReturning: vi.fn(), - mockGetSession: vi.fn(), mockGetAccessibleCopilotChat: vi.fn(), - mockAuthorizeWorkflowByWorkspacePermission: vi.fn(), -})) - -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, })) vi.mock('@sim/db', () => ({ @@ -43,19 +36,6 @@ vi.mock('@sim/db', () => ({ }, })) -vi.mock('@sim/db/schema', () => ({ - copilotChats: { id: 'id', userId: 'userId' }, - workflowCheckpoints: { - id: 'id', - userId: 'userId', - workflowId: 'workflowId', - chatId: 'chatId', - messageId: 'messageId', - createdAt: 'createdAt', - updatedAt: 'updatedAt', - }, -})) - vi.mock('drizzle-orm', () => ({ and: vi.fn((...conditions: unknown[]) => ({ conditions, type: 'and' })), eq: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'eq' })), @@ -66,9 +46,7 @@ vi.mock('@/lib/copilot/chat/lifecycle', () => ({ getAccessibleCopilotChat: mockGetAccessibleCopilotChat, })) -vi.mock('@/lib/workflows/utils', () => ({ - authorizeWorkflowByWorkspacePermission: mockAuthorizeWorkflowByWorkspacePermission, -})) +vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) import { GET, POST } from './route' @@ -84,7 +62,7 @@ describe('Copilot Checkpoints API Route', () => { beforeEach(() => { vi.clearAllMocks() - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) mockSelect.mockReturnValue({ from: mockFrom }) mockFrom.mockReturnValue({ where: mockWhere }) @@ -101,7 +79,9 @@ describe('Copilot Checkpoints API Route', () => { userId: 'user-123', workflowId: 'workflow-123', }) - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ allowed: true }) + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ + allowed: true, + }) }) afterEach(() => { @@ -110,7 +90,7 @@ describe('Copilot Checkpoints API Route', () => { describe('POST', () => { it('should return 401 when user is not authenticated', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = createMockRequest('POST', { workflowId: 'workflow-123', @@ -126,7 +106,7 @@ describe('Copilot Checkpoints API Route', () => { }) it('should return 500 for invalid request body', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const req = createMockRequest('POST', { workflowId: 'workflow-123', @@ -140,7 +120,7 @@ describe('Copilot Checkpoints API Route', () => { }) it('should return 400 when chat not found or unauthorized', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) mockGetAccessibleCopilotChat.mockResolvedValueOnce(null) const req = createMockRequest('POST', { @@ -157,7 +137,7 @@ describe('Copilot Checkpoints API Route', () => { }) it('should return 400 for invalid workflow state JSON', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const req = createMockRequest('POST', { workflowId: 'workflow-123', @@ -173,7 +153,7 @@ describe('Copilot Checkpoints API Route', () => { }) it('should successfully create a checkpoint', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const checkpoint = { id: 'checkpoint-123', @@ -222,7 +202,7 @@ describe('Copilot Checkpoints API Route', () => { }) it('should create checkpoint without messageId', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const checkpoint = { id: 'checkpoint-123', @@ -251,7 +231,7 @@ describe('Copilot Checkpoints API Route', () => { }) it('should handle database errors during checkpoint creation', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) mockReturning.mockRejectedValue(new Error('Database insert failed')) @@ -269,7 +249,7 @@ describe('Copilot Checkpoints API Route', () => { }) it('should handle database errors during chat lookup', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) mockGetAccessibleCopilotChat.mockRejectedValueOnce(new Error('Database query failed')) @@ -289,7 +269,7 @@ describe('Copilot Checkpoints API Route', () => { describe('GET', () => { it('should return 401 when user is not authenticated', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = new NextRequest('http://localhost:3000/api/copilot/checkpoints?chatId=chat-123') @@ -301,7 +281,7 @@ describe('Copilot Checkpoints API Route', () => { }) it('should return 400 when chatId is missing', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const req = new NextRequest('http://localhost:3000/api/copilot/checkpoints') @@ -313,7 +293,7 @@ describe('Copilot Checkpoints API Route', () => { }) it('should return checkpoints for authenticated user and chat', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const mockCheckpoints = [ { @@ -374,7 +354,7 @@ describe('Copilot Checkpoints API Route', () => { }) it('should handle database errors when fetching checkpoints', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) mockOrderBy.mockRejectedValue(new Error('Database query failed')) @@ -388,7 +368,7 @@ describe('Copilot Checkpoints API Route', () => { }) it('should return empty array when no checkpoints found', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) mockOrderBy.mockResolvedValue([]) diff --git a/apps/sim/app/api/copilot/confirm/route.test.ts b/apps/sim/app/api/copilot/confirm/route.test.ts index a81e39c2821..880d26b52d8 100644 --- a/apps/sim/app/api/copilot/confirm/route.test.ts +++ b/apps/sim/app/api/copilot/confirm/route.test.ts @@ -1,36 +1,17 @@ /** * @vitest-environment node */ +import { copilotHttpMock, copilotHttpMockFns } from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' const { - authenticateCopilotRequestSessionOnly, - createBadRequestResponse, - createInternalServerErrorResponse, - createNotFoundResponse, - createRequestTracker, - createUnauthorizedResponse, getAsyncToolCall, getRunSegment, upsertAsyncToolCall, completeAsyncToolCall, publishToolConfirmation, } = vi.hoisted(() => ({ - authenticateCopilotRequestSessionOnly: vi.fn(), - createBadRequestResponse: vi.fn((message: string) => - Response.json({ error: message }, { status: 400 }) - ), - createInternalServerErrorResponse: vi.fn((message: string) => - Response.json({ error: message }, { status: 500 }) - ), - createNotFoundResponse: vi.fn((message: string) => - Response.json({ error: message }, { status: 404 }) - ), - createRequestTracker: vi.fn(() => ({ requestId: 'req-1', getDuration: () => 1 })), - createUnauthorizedResponse: vi.fn(() => - Response.json({ error: 'Unauthorized' }, { status: 401 }) - ), getAsyncToolCall: vi.fn(), getRunSegment: vi.fn(), upsertAsyncToolCall: vi.fn(), @@ -38,14 +19,7 @@ const { publishToolConfirmation: vi.fn(), })) -vi.mock('@/lib/copilot/request/http', () => ({ - authenticateCopilotRequestSessionOnly, - createBadRequestResponse, - createInternalServerErrorResponse, - createNotFoundResponse, - createRequestTracker, - createUnauthorizedResponse, -})) +vi.mock('@/lib/copilot/request/http', () => copilotHttpMock) vi.mock('@/lib/copilot/async-runs/repository', () => ({ getAsyncToolCall, @@ -71,7 +45,7 @@ describe('Copilot Confirm API Route', () => { beforeEach(() => { vi.clearAllMocks() - authenticateCopilotRequestSessionOnly.mockResolvedValue({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValue({ userId: 'user-1', isAuthenticated: true, }) @@ -90,7 +64,7 @@ describe('Copilot Confirm API Route', () => { } it('returns 401 when the session is unauthenticated', async () => { - authenticateCopilotRequestSessionOnly.mockResolvedValue({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValue({ userId: null, isAuthenticated: false, }) diff --git a/apps/sim/app/api/copilot/confirm/route.ts b/apps/sim/app/api/copilot/confirm/route.ts index ac1669d7c33..3e88151ffe5 100644 --- a/apps/sim/app/api/copilot/confirm/route.ts +++ b/apps/sim/app/api/copilot/confirm/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { @@ -22,7 +23,6 @@ import { createRequestTracker, createUnauthorizedResponse, } from '@/lib/copilot/request/http' -import { toError } from '@/lib/core/utils/helpers' const logger = createLogger('CopilotConfirmAPI') diff --git a/apps/sim/app/api/copilot/feedback/route.test.ts b/apps/sim/app/api/copilot/feedback/route.test.ts index 3f3a28598a6..b1121ee4b8c 100644 --- a/apps/sim/app/api/copilot/feedback/route.test.ts +++ b/apps/sim/app/api/copilot/feedback/route.test.ts @@ -3,67 +3,19 @@ * * @vitest-environment node */ +import { + copilotHttpMock, + copilotHttpMockFns, + dbChainMock, + dbChainMockFns, + resetDbChainMock, +} from '@sim/testing' import { NextRequest } from 'next/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -const { - mockInsert, - mockValues, - mockReturning, - mockSelect, - mockFrom, - mockWhere, - mockAuthenticate, - mockCreateUnauthorizedResponse, - mockCreateBadRequestResponse, - mockCreateInternalServerErrorResponse, - mockCreateRequestTracker, -} = vi.hoisted(() => ({ - mockInsert: vi.fn(), - mockValues: vi.fn(), - mockReturning: vi.fn(), - mockSelect: vi.fn(), - mockFrom: vi.fn(), - mockWhere: vi.fn(), - mockAuthenticate: vi.fn(), - mockCreateUnauthorizedResponse: vi.fn(), - mockCreateBadRequestResponse: vi.fn(), - mockCreateInternalServerErrorResponse: vi.fn(), - mockCreateRequestTracker: vi.fn(), -})) - -vi.mock('@sim/db', () => ({ - db: { - insert: mockInsert, - select: mockSelect, - }, -})) - -vi.mock('@sim/db/schema', () => ({ - copilotFeedback: { - feedbackId: 'feedbackId', - userId: 'userId', - chatId: 'chatId', - userQuery: 'userQuery', - agentResponse: 'agentResponse', - isPositive: 'isPositive', - feedback: 'feedback', - workflowYaml: 'workflowYaml', - createdAt: 'createdAt', - }, -})) - -vi.mock('drizzle-orm', () => ({ - eq: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'eq' })), -})) - -vi.mock('@/lib/copilot/request/http', () => ({ - authenticateCopilotRequestSessionOnly: mockAuthenticate, - createUnauthorizedResponse: mockCreateUnauthorizedResponse, - createBadRequestResponse: mockCreateBadRequestResponse, - createInternalServerErrorResponse: mockCreateInternalServerErrorResponse, - createRequestTracker: mockCreateRequestTracker, -})) +vi.mock('@sim/db', () => dbChainMock) + +vi.mock('@/lib/copilot/request/http', () => copilotHttpMock) import { GET, POST } from '@/app/api/copilot/feedback/route' @@ -78,27 +30,7 @@ function createMockRequest(method: string, body: Record): NextR describe('Copilot Feedback API Route', () => { beforeEach(() => { vi.clearAllMocks() - - mockInsert.mockReturnValue({ values: mockValues }) - mockValues.mockReturnValue({ returning: mockReturning }) - mockReturning.mockResolvedValue([]) - mockSelect.mockReturnValue({ from: mockFrom }) - mockFrom.mockReturnValue({ where: mockWhere }) - mockWhere.mockResolvedValue([]) - - mockCreateRequestTracker.mockReturnValue({ - requestId: 'test-request-id', - getDuration: vi.fn().mockReturnValue(100), - }) - mockCreateUnauthorizedResponse.mockReturnValue( - new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 }) - ) - mockCreateBadRequestResponse.mockImplementation( - (message: string) => new Response(JSON.stringify({ error: message }), { status: 400 }) - ) - mockCreateInternalServerErrorResponse.mockImplementation( - (message: string) => new Response(JSON.stringify({ error: message }), { status: 500 }) - ) + resetDbChainMock() }) afterEach(() => { @@ -107,7 +39,7 @@ describe('Copilot Feedback API Route', () => { describe('POST', () => { it('should return 401 when user is not authenticated', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: null, isAuthenticated: false, }) @@ -127,7 +59,7 @@ describe('Copilot Feedback API Route', () => { }) it('should successfully submit positive feedback', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -143,7 +75,7 @@ describe('Copilot Feedback API Route', () => { workflowYaml: null, createdAt: new Date('2024-01-01'), } - mockReturning.mockResolvedValueOnce([feedbackRecord]) + dbChainMockFns.returning.mockResolvedValueOnce([feedbackRecord]) const req = createMockRequest('POST', { chatId: '550e8400-e29b-41d4-a716-446655440000', @@ -162,7 +94,7 @@ describe('Copilot Feedback API Route', () => { }) it('should successfully submit negative feedback with text', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -178,7 +110,7 @@ describe('Copilot Feedback API Route', () => { workflowYaml: null, createdAt: new Date('2024-01-01'), } - mockReturning.mockResolvedValueOnce([feedbackRecord]) + dbChainMockFns.returning.mockResolvedValueOnce([feedbackRecord]) const req = createMockRequest('POST', { chatId: '550e8400-e29b-41d4-a716-446655440000', @@ -197,7 +129,7 @@ describe('Copilot Feedback API Route', () => { }) it('should successfully submit feedback with workflow YAML', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -224,7 +156,7 @@ edges: workflowYaml: workflowYaml, createdAt: new Date('2024-01-01'), } - mockReturning.mockResolvedValueOnce([feedbackRecord]) + dbChainMockFns.returning.mockResolvedValueOnce([feedbackRecord]) const req = createMockRequest('POST', { chatId: '550e8400-e29b-41d4-a716-446655440000', @@ -240,7 +172,7 @@ edges: const responseData = await response.json() expect(responseData.success).toBe(true) - expect(mockValues).toHaveBeenCalledWith( + expect(dbChainMockFns.values).toHaveBeenCalledWith( expect.objectContaining({ workflowYaml: workflowYaml, }) @@ -248,7 +180,7 @@ edges: }) it('should return 400 for invalid chatId format', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -268,7 +200,7 @@ edges: }) it('should return 400 for empty userQuery', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -288,7 +220,7 @@ edges: }) it('should return 400 for empty agentResponse', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -308,7 +240,7 @@ edges: }) it('should return 400 for missing isPositiveFeedback', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -327,12 +259,12 @@ edges: }) it('should handle database errors gracefully', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) - mockReturning.mockRejectedValueOnce(new Error('Database connection failed')) + dbChainMockFns.returning.mockRejectedValueOnce(new Error('Database connection failed')) const req = createMockRequest('POST', { chatId: '550e8400-e29b-41d4-a716-446655440000', @@ -349,7 +281,7 @@ edges: }) it('should handle JSON parsing errors in request body', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -370,7 +302,7 @@ edges: describe('GET', () => { it('should return 401 when user is not authenticated', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: null, isAuthenticated: false, }) @@ -384,12 +316,12 @@ edges: }) it('should return empty feedback array when no feedback exists', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) - mockWhere.mockResolvedValueOnce([]) + dbChainMockFns.where.mockResolvedValueOnce([]) const request = new Request('http://localhost:3000/api/copilot/feedback') const response = await GET(request as any) @@ -401,7 +333,7 @@ edges: }) it('should only return feedback records for the authenticated user', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -419,7 +351,7 @@ edges: createdAt: new Date('2024-01-01'), }, ] - mockWhere.mockResolvedValueOnce(mockFeedback) + dbChainMockFns.where.mockResolvedValueOnce(mockFeedback) const request = new Request('http://localhost:3000/api/copilot/feedback') const response = await GET(request as any) @@ -431,19 +363,18 @@ edges: expect(responseData.feedback[0].feedbackId).toBe('feedback-1') expect(responseData.feedback[0].userId).toBe('user-123') - // Verify the where clause was called with the authenticated user's ID const { eq } = await import('drizzle-orm') - expect(mockWhere).toHaveBeenCalled() + expect(dbChainMockFns.where).toHaveBeenCalled() expect(eq).toHaveBeenCalledWith('userId', 'user-123') }) it('should handle database errors gracefully', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) - mockWhere.mockRejectedValueOnce(new Error('Database connection failed')) + dbChainMockFns.where.mockRejectedValueOnce(new Error('Database connection failed')) const request = new Request('http://localhost:3000/api/copilot/feedback') const response = await GET(request as any) @@ -454,12 +385,12 @@ edges: }) it('should return metadata with response', async () => { - mockAuthenticate.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) - mockWhere.mockResolvedValueOnce([]) + dbChainMockFns.where.mockResolvedValueOnce([]) const request = new Request('http://localhost:3000/api/copilot/feedback') const response = await GET(request as any) diff --git a/apps/sim/app/api/copilot/models/route.ts b/apps/sim/app/api/copilot/models/route.ts index 5aaa7a44a75..73eaeee1979 100644 --- a/apps/sim/app/api/copilot/models/route.ts +++ b/apps/sim/app/api/copilot/models/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { SIM_AGENT_API_URL } from '@/lib/copilot/constants' import { authenticateCopilotRequestSessionOnly } from '@/lib/copilot/request/http' -import { toError } from '@/lib/core/utils/helpers' interface AvailableModel { id: string diff --git a/apps/sim/app/api/copilot/stats/route.test.ts b/apps/sim/app/api/copilot/stats/route.test.ts index ca6e97704f0..5f78d803472 100644 --- a/apps/sim/app/api/copilot/stats/route.test.ts +++ b/apps/sim/app/api/copilot/stats/route.test.ts @@ -3,54 +3,22 @@ * * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { copilotHttpMock, copilotHttpMockFns, createEnvMock, createMockRequest } from '@sim/testing' import { NextRequest } from 'next/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -const { - mockAuthenticateCopilotRequestSessionOnly, - mockCreateUnauthorizedResponse, - mockCreateBadRequestResponse, - mockCreateInternalServerErrorResponse, - mockCreateRequestTracker, - mockFetch, -} = vi.hoisted(() => ({ - mockAuthenticateCopilotRequestSessionOnly: vi.fn(), - mockCreateUnauthorizedResponse: vi.fn(), - mockCreateBadRequestResponse: vi.fn(), - mockCreateInternalServerErrorResponse: vi.fn(), - mockCreateRequestTracker: vi.fn(), +const { mockFetch } = vi.hoisted(() => ({ mockFetch: vi.fn(), })) -vi.mock('@/lib/copilot/request/http', () => ({ - authenticateCopilotRequestSessionOnly: mockAuthenticateCopilotRequestSessionOnly, - createUnauthorizedResponse: mockCreateUnauthorizedResponse, - createBadRequestResponse: mockCreateBadRequestResponse, - createInternalServerErrorResponse: mockCreateInternalServerErrorResponse, - createRequestTracker: mockCreateRequestTracker, -})) +vi.mock('@/lib/copilot/request/http', () => copilotHttpMock) vi.mock('@/lib/copilot/constants', () => ({ SIM_AGENT_API_URL_DEFAULT: 'https://agent.sim.example.com', SIM_AGENT_API_URL: 'https://agent.sim.example.com', })) -vi.mock('@/lib/core/config/env', () => ({ - env: { - COPILOT_API_KEY: 'test-api-key', - }, - getEnv: vi.fn((key: string) => { - const vals: Record = { - COPILOT_API_KEY: 'test-api-key', - } - return vals[key] - }), - 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, -})) +vi.mock('@/lib/core/config/env', () => createEnvMock({ COPILOT_API_KEY: 'test-api-key' })) import { POST } from '@/app/api/copilot/stats/route' @@ -58,20 +26,6 @@ describe('Copilot Stats API Route', () => { beforeEach(() => { vi.clearAllMocks() global.fetch = mockFetch - - mockCreateUnauthorizedResponse.mockReturnValue( - new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 }) - ) - mockCreateBadRequestResponse.mockImplementation( - (message: string) => new Response(JSON.stringify({ error: message }), { status: 400 }) - ) - mockCreateInternalServerErrorResponse.mockImplementation( - (message: string) => new Response(JSON.stringify({ error: message }), { status: 500 }) - ) - mockCreateRequestTracker.mockReturnValue({ - requestId: 'test-request-id', - getDuration: vi.fn().mockReturnValue(100), - }) }) afterEach(() => { @@ -80,7 +34,7 @@ describe('Copilot Stats API Route', () => { describe('POST', () => { it('should return 401 when user is not authenticated', async () => { - mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: null, isAuthenticated: false, }) @@ -99,7 +53,7 @@ describe('Copilot Stats API Route', () => { }) it('should successfully forward stats to Sim Agent', async () => { - mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -139,7 +93,7 @@ describe('Copilot Stats API Route', () => { }) it('should return 400 for invalid request body - missing messageId', async () => { - mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -157,7 +111,7 @@ describe('Copilot Stats API Route', () => { }) it('should return 400 for invalid request body - missing diffCreated', async () => { - mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -175,7 +129,7 @@ describe('Copilot Stats API Route', () => { }) it('should return 400 for invalid request body - missing diffAccepted', async () => { - mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -193,7 +147,7 @@ describe('Copilot Stats API Route', () => { }) it('should return 400 when upstream Sim Agent returns error', async () => { - mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -217,7 +171,7 @@ describe('Copilot Stats API Route', () => { }) it('should handle upstream error with message field', async () => { - mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -241,7 +195,7 @@ describe('Copilot Stats API Route', () => { }) it('should handle upstream error with no JSON response', async () => { - mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -265,7 +219,7 @@ describe('Copilot Stats API Route', () => { }) it('should handle network errors gracefully', async () => { - mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -286,7 +240,7 @@ describe('Copilot Stats API Route', () => { }) it('should handle JSON parsing errors in request body', async () => { - mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) @@ -307,7 +261,7 @@ describe('Copilot Stats API Route', () => { }) it('should forward stats with diffCreated=false and diffAccepted=false', async () => { - mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ + copilotHttpMockFns.mockAuthenticateCopilotRequestSessionOnly.mockResolvedValueOnce({ userId: 'user-123', isAuthenticated: true, }) diff --git a/apps/sim/app/api/creators/route.ts b/apps/sim/app/api/creators/route.ts index 6dedd133af1..07671d38ef6 100644 --- a/apps/sim/app/api/creators/route.ts +++ b/apps/sim/app/api/creators/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { member, templateCreators } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { generateId } from '@/lib/core/utils/uuid' import type { CreatorProfileDetails } from '@/app/_types/creator-profile' const logger = createLogger('CreatorProfilesAPI') diff --git a/apps/sim/app/api/credential-sets/[id]/invite/route.ts b/apps/sim/app/api/credential-sets/[id]/invite/route.ts index b9b0ccc4a95..874b0b31550 100644 --- a/apps/sim/app/api/credential-sets/[id]/invite/route.ts +++ b/apps/sim/app/api/credential-sets/[id]/invite/route.ts @@ -1,6 +1,7 @@ import { db } from '@sim/db' import { credentialSet, credentialSetInvitation, member, organization, user } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -9,7 +10,6 @@ import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { hasCredentialSetsAccess } from '@/lib/billing' import { getBaseUrl } from '@/lib/core/utils/urls' -import { generateId } from '@/lib/core/utils/uuid' import { sendEmail } from '@/lib/messaging/email/mailer' const logger = createLogger('CredentialSetInvite') diff --git a/apps/sim/app/api/credential-sets/[id]/members/route.ts b/apps/sim/app/api/credential-sets/[id]/members/route.ts index 8ec89923bbe..49959b85334 100644 --- a/apps/sim/app/api/credential-sets/[id]/members/route.ts +++ b/apps/sim/app/api/credential-sets/[id]/members/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { account, credentialSet, credentialSetMember, member, user } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, inArray } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { hasCredentialSetsAccess } from '@/lib/billing' -import { generateId } from '@/lib/core/utils/uuid' import { syncAllWebhooksForCredentialSet } from '@/lib/webhooks/utils.server' const logger = createLogger('CredentialSetMembers') diff --git a/apps/sim/app/api/credential-sets/invite/[token]/route.ts b/apps/sim/app/api/credential-sets/invite/[token]/route.ts index fc3759b0e27..a72b879128b 100644 --- a/apps/sim/app/api/credential-sets/invite/[token]/route.ts +++ b/apps/sim/app/api/credential-sets/invite/[token]/route.ts @@ -6,11 +6,11 @@ import { organization, } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' -import { generateId } from '@/lib/core/utils/uuid' import { syncAllWebhooksForCredentialSet } from '@/lib/webhooks/utils.server' const logger = createLogger('CredentialSetInviteToken') diff --git a/apps/sim/app/api/credential-sets/memberships/route.ts b/apps/sim/app/api/credential-sets/memberships/route.ts index 926714b98f9..ec6ba4b41d9 100644 --- a/apps/sim/app/api/credential-sets/memberships/route.ts +++ b/apps/sim/app/api/credential-sets/memberships/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { credentialSet, credentialSetMember, organization } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' -import { generateId } from '@/lib/core/utils/uuid' import { syncAllWebhooksForCredentialSet } from '@/lib/webhooks/utils.server' const logger = createLogger('CredentialSetMemberships') diff --git a/apps/sim/app/api/credential-sets/route.ts b/apps/sim/app/api/credential-sets/route.ts index c120e84b421..bfd73c78f50 100644 --- a/apps/sim/app/api/credential-sets/route.ts +++ b/apps/sim/app/api/credential-sets/route.ts @@ -1,13 +1,13 @@ import { db } from '@sim/db' import { credentialSet, credentialSetMember, member, organization, user } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, count, desc, eq } from 'drizzle-orm' import { NextResponse } from 'next/server' import { z } from 'zod' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { hasCredentialSetsAccess } from '@/lib/billing' -import { generateId } from '@/lib/core/utils/uuid' const logger = createLogger('CredentialSets') diff --git a/apps/sim/app/api/credentials/[id]/members/route.ts b/apps/sim/app/api/credentials/[id]/members/route.ts index c89657fe89f..220f12b04b0 100644 --- a/apps/sim/app/api/credentials/[id]/members/route.ts +++ b/apps/sim/app/api/credentials/[id]/members/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { credential, credentialMember, user } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' -import { generateId } from '@/lib/core/utils/uuid' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' const logger = createLogger('CredentialMembersAPI') diff --git a/apps/sim/app/api/credentials/[id]/route.ts b/apps/sim/app/api/credentials/[id]/route.ts index c3a61569051..f0429234488 100644 --- a/apps/sim/app/api/credentials/[id]/route.ts +++ b/apps/sim/app/api/credentials/[id]/route.ts @@ -1,13 +1,13 @@ import { db } from '@sim/db' import { credential, credentialMember, environment, workspaceEnvironment } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { encryptSecret } from '@/lib/core/security/encryption' -import { generateId } from '@/lib/core/utils/uuid' import { getCredentialActorContext } from '@/lib/credentials/access' import { syncPersonalEnvCredentialsForUser, diff --git a/apps/sim/app/api/credentials/draft/route.ts b/apps/sim/app/api/credentials/draft/route.ts index e00be23b0d0..504a6f0d334 100644 --- a/apps/sim/app/api/credentials/draft/route.ts +++ b/apps/sim/app/api/credentials/draft/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { credential, credentialMember, pendingCredentialDraft } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, lt } from 'drizzle-orm' import { NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' -import { generateId } from '@/lib/core/utils/uuid' import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils' const logger = createLogger('CredentialDraftAPI') diff --git a/apps/sim/app/api/credentials/route.ts b/apps/sim/app/api/credentials/route.ts index 0b210325064..3ed44d4a863 100644 --- a/apps/sim/app/api/credentials/route.ts +++ b/apps/sim/app/api/credentials/route.ts @@ -1,6 +1,7 @@ import { db } from '@sim/db' import { account, credential, credentialMember, workspace } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -8,7 +9,6 @@ import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { encryptSecret } from '@/lib/core/security/encryption' import { generateRequestId } from '@/lib/core/utils/request' -import { generateId } from '@/lib/core/utils/uuid' import { getWorkspaceMemberUserIds } from '@/lib/credentials/environment' import { syncWorkspaceOAuthCredentialsForUser } from '@/lib/credentials/oauth' import { getServiceConfigByProviderId } from '@/lib/oauth' diff --git a/apps/sim/app/api/cron/cleanup-stale-executions/route.ts b/apps/sim/app/api/cron/cleanup-stale-executions/route.ts index 2b52a3ea43e..4983927241f 100644 --- a/apps/sim/app/api/cron/cleanup-stale-executions/route.ts +++ b/apps/sim/app/api/cron/cleanup-stale-executions/route.ts @@ -1,12 +1,12 @@ import { asyncJobs, db } from '@sim/db' import { workflowExecutionLogs } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { and, eq, inArray, lt, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { verifyCronAuth } from '@/lib/auth/internal' import { JOB_RETENTION_HOURS, JOB_STATUS } from '@/lib/core/async-jobs' import { getMaxExecutionTimeout } from '@/lib/core/execution-limits' -import { toError } from '@/lib/core/utils/helpers' const logger = createLogger('CleanupStaleExecutions') diff --git a/apps/sim/app/api/environment/route.ts b/apps/sim/app/api/environment/route.ts index f8167e92ac2..39659a6d3d8 100644 --- a/apps/sim/app/api/environment/route.ts +++ b/apps/sim/app/api/environment/route.ts @@ -1,6 +1,7 @@ import { db } from '@sim/db' import { environment } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -8,7 +9,6 @@ import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { decryptSecret, encryptSecret } from '@/lib/core/security/encryption' import { generateRequestId } from '@/lib/core/utils/request' -import { generateId } from '@/lib/core/utils/uuid' import { syncPersonalEnvCredentialsForUser } from '@/lib/credentials/environment' import type { EnvironmentVariable } from '@/lib/environment/api' diff --git a/apps/sim/app/api/files/delete/route.test.ts b/apps/sim/app/api/files/delete/route.test.ts index 85ea1913565..977902d0be0 100644 --- a/apps/sim/app/api/files/delete/route.test.ts +++ b/apps/sim/app/api/files/delete/route.test.ts @@ -1,13 +1,10 @@ /** * @vitest-environment node */ +import { authMockFns, hybridAuthMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const mocks = vi.hoisted(() => { - const mockGetSession = vi.fn() - const mockCheckHybridAuth = vi.fn() - const mockCheckSessionOrInternalAuth = vi.fn() - const mockCheckInternalAuth = vi.fn() const mockVerifyFileAccess = vi.fn() const mockVerifyWorkspaceFileAccess = vi.fn() const mockDeleteFile = vi.fn() @@ -18,10 +15,6 @@ const mocks = vi.hoisted(() => { const mockDownloadFile = vi.fn() return { - mockGetSession, - mockCheckHybridAuth, - mockCheckSessionOrInternalAuth, - mockCheckInternalAuth, mockVerifyFileAccess, mockVerifyWorkspaceFileAccess, mockDeleteFile, @@ -33,30 +26,6 @@ const mocks = vi.hoisted(() => { } }) -vi.mock('@sim/logger', () => ({ - createLogger: vi.fn().mockReturnValue({ - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - }), -})) - -vi.mock('@sim/db/schema', () => ({ - workflowFolder: { - id: 'id', - userId: 'userId', - parentId: 'parentId', - updatedAt: 'updatedAt', - workspaceId: 'workspaceId', - sortOrder: 'sortOrder', - createdAt: 'createdAt', - }, - workflow: { id: 'id', folderId: 'folderId', userId: 'userId', updatedAt: 'updatedAt' }, - account: { userId: 'userId', providerId: 'providerId' }, - user: { email: 'email', id: 'id' }, -})) - vi.mock('drizzle-orm', () => ({ and: vi.fn((...conditions: unknown[]) => ({ conditions, type: 'and' })), eq: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'eq' })), @@ -82,7 +51,7 @@ vi.mock('drizzle-orm', () => ({ sql: vi.fn((strings: unknown, ...values: unknown[]) => ({ type: 'sql', sql: strings, values })), })) -vi.mock('@/lib/core/utils/uuid', () => ({ +vi.mock('@sim/utils/id', () => ({ generateId: vi.fn(() => 'test-uuid'), generateShortId: vi.fn(() => 'mock-short-id'), isValidUuid: vi.fn((v: string) => @@ -90,17 +59,6 @@ vi.mock('@/lib/core/utils/uuid', () => ({ ), })) -vi.mock('@/lib/auth', () => ({ - getSession: mocks.mockGetSession, -})) - -vi.mock('@/lib/auth/hybrid', () => ({ - AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, - checkHybridAuth: mocks.mockCheckHybridAuth, - checkSessionOrInternalAuth: mocks.mockCheckSessionOrInternalAuth, - checkInternalAuth: mocks.mockCheckInternalAuth, -})) - vi.mock('@/app/api/files/authorization', () => ({ verifyFileAccess: mocks.mockVerifyFileAccess, verifyWorkspaceFileAccess: mocks.mockVerifyWorkspaceFileAccess, @@ -151,8 +109,8 @@ describe('File Delete API Route', () => { randomUUID: vi.fn().mockReturnValue('mock-uuid-1234-5678'), }) - mocks.mockGetSession.mockResolvedValue({ user: { id: 'test-user-id' } }) - mocks.mockCheckSessionOrInternalAuth.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'test-user-id' } }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ success: true, userId: 'test-user-id', error: undefined, diff --git a/apps/sim/app/api/files/parse/route.test.ts b/apps/sim/app/api/files/parse/route.test.ts index 02baaffb6c0..8a2c06f19ff 100644 --- a/apps/sim/app/api/files/parse/route.test.ts +++ b/apps/sim/app/api/files/parse/route.test.ts @@ -3,7 +3,14 @@ * * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { + authMockFns, + createMockRequest, + hybridAuthMockFns, + inputValidationMock, + permissionsMock, + permissionsMockFns, +} from '@sim/testing' import { NextRequest } from 'next/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' @@ -22,10 +29,6 @@ const { mockFsReadFile, mockFsWriteFile, mockJoin, - mockGetSession, - mockCheckInternalAuth, - mockCheckHybridAuth, - mockCheckSessionOrInternalAuth, actualPath, } = vi.hoisted(() => { // eslint-disable-next-line @typescript-eslint/no-require-imports @@ -56,10 +59,6 @@ const { } return actualPath.join(...args) }), - mockGetSession: vi.fn(), - mockCheckInternalAuth: vi.fn(), - mockCheckHybridAuth: vi.fn(), - mockCheckSessionOrInternalAuth: vi.fn(), actualPath, } }) @@ -98,24 +97,7 @@ vi.mock('@/lib/uploads/core/setup.server', () => ({ UPLOAD_DIR_SERVER: '/test/uploads', })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, - auth: vi.fn(), - signIn: vi.fn(), - signUp: vi.fn(), -})) - -vi.mock('@/lib/auth/hybrid', () => ({ - AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, - checkInternalAuth: mockCheckInternalAuth, - checkHybridAuth: mockCheckHybridAuth, - checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, -})) - -vi.mock('@/lib/core/security/input-validation.server', () => ({ - secureFetchWithPinnedIP: vi.fn(), - validateUrlWithDNS: vi.fn(), -})) +vi.mock('@/lib/core/security/input-validation.server', () => inputValidationMock) vi.mock('@/lib/core/utils/logging', () => ({ sanitizeUrlForLog: vi.fn((url: string) => url), @@ -129,9 +111,7 @@ vi.mock('@/lib/uploads/server/metadata', () => ({ getFileMetadataByKey: vi.fn(), })) -vi.mock('@/lib/workspaces/permissions/utils', () => ({ - getUserEntityPermissions: vi.fn().mockResolvedValue({ canView: true }), -})) +vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) vi.mock('fs/promises', () => ({ default: { @@ -158,26 +138,26 @@ function setupFileApiMocks( const { authenticated = true, storageProvider = 's3', cloudEnabled = true } = options if (authenticated) { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'test-user-id', email: 'test@example.com' }, }) } else { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) } - mockCheckInternalAuth.mockResolvedValue({ + hybridAuthMockFns.mockCheckInternalAuth.mockResolvedValue({ success: authenticated, userId: authenticated ? 'test-user-id' : undefined, error: authenticated ? undefined : 'Unauthorized', }) - mockCheckHybridAuth.mockResolvedValue({ + hybridAuthMockFns.mockCheckHybridAuth.mockResolvedValue({ success: authenticated, userId: authenticated ? 'test-user-id' : undefined, error: authenticated ? undefined : 'Unauthorized', }) - mockCheckSessionOrInternalAuth.mockResolvedValue({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ success: authenticated, userId: authenticated ? 'test-user-id' : undefined, error: authenticated ? undefined : 'Unauthorized', @@ -195,6 +175,7 @@ describe('File Parse API Route', () => { authenticated: true, }) + permissionsMockFns.mockGetUserEntityPermissions.mockResolvedValue({ canView: true }) mockIsSupportedFileType.mockReturnValue(true) mockParseFile.mockResolvedValue({ content: 'parsed content', @@ -389,6 +370,7 @@ describe('Files Parse API - Path Traversal Security', () => { setupFileApiMocks({ authenticated: true, }) + permissionsMockFns.mockGetUserEntityPermissions.mockResolvedValue({ canView: true }) }) describe('Path Traversal Prevention', () => { diff --git a/apps/sim/app/api/files/presigned/route.test.ts b/apps/sim/app/api/files/presigned/route.test.ts index a752403a059..f6641c07d9f 100644 --- a/apps/sim/app/api/files/presigned/route.test.ts +++ b/apps/sim/app/api/files/presigned/route.test.ts @@ -4,11 +4,11 @@ * @vitest-environment node */ +import { authMockFns } from '@sim/testing' import { NextRequest } from 'next/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const { - mockGetSession, mockVerifyFileAccess, mockVerifyWorkspaceFileAccess, mockUseBlobStorage, @@ -25,7 +25,6 @@ const { mockGetStorageProviderUploads, mockIsUsingCloudStorageUploads, } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), mockVerifyFileAccess: vi.fn().mockResolvedValue(true), mockVerifyWorkspaceFileAccess: vi.fn().mockResolvedValue(true), mockUseBlobStorage: { value: false }, @@ -46,10 +45,6 @@ const { mockIsUsingCloudStorageUploads: vi.fn(), })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - vi.mock('@/app/api/files/authorization', () => ({ verifyFileAccess: mockVerifyFileAccess, verifyWorkspaceFileAccess: mockVerifyWorkspaceFileAccess, @@ -108,9 +103,9 @@ function setupFileApiMocks( const { authenticated = true, storageProvider = 's3', cloudEnabled = true } = options if (authenticated) { - mockGetSession.mockResolvedValue({ user: defaultMockUser }) + authMockFns.mockGetSession.mockResolvedValue({ user: defaultMockUser }) } else { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) } const useBlobStorage = storageProvider === 'blob' && cloudEnabled diff --git a/apps/sim/app/api/files/serve/[...path]/route.test.ts b/apps/sim/app/api/files/serve/[...path]/route.test.ts index 6a102b60340..17b7a8d2fda 100644 --- a/apps/sim/app/api/files/serve/[...path]/route.test.ts +++ b/apps/sim/app/api/files/serve/[...path]/route.test.ts @@ -3,11 +3,11 @@ * * @vitest-environment node */ +import { hybridAuthMockFns } from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' const { - mockCheckSessionOrInternalAuth, mockVerifyFileAccess, mockReadFile, mockIsUsingCloudStorage, @@ -27,7 +27,6 @@ const { } } return { - mockCheckSessionOrInternalAuth: vi.fn(), mockVerifyFileAccess: vi.fn(), mockReadFile: vi.fn(), mockIsUsingCloudStorage: vi.fn(), @@ -48,11 +47,6 @@ vi.mock('fs/promises', () => ({ stat: vi.fn().mockResolvedValue({ isFile: () => true, size: 100 }), })) -vi.mock('@/lib/auth/hybrid', () => ({ - AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, - checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, -})) - vi.mock('@/app/api/files/authorization', () => ({ verifyFileAccess: mockVerifyFileAccess, })) @@ -103,7 +97,7 @@ describe('File Serve API Route', () => { beforeEach(() => { vi.clearAllMocks() - mockCheckSessionOrInternalAuth.mockResolvedValue({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ success: true, userId: 'test-user-id', }) diff --git a/apps/sim/app/api/files/upload/route.test.ts b/apps/sim/app/api/files/upload/route.test.ts index 5f3d9064c9f..8e9ff1dbe8b 100644 --- a/apps/sim/app/api/files/upload/route.test.ts +++ b/apps/sim/app/api/files/upload/route.test.ts @@ -3,19 +3,15 @@ * * @vitest-environment node */ +import { authMockFns, hybridAuthMockFns, permissionsMock, permissionsMockFns } from '@sim/testing' import { NextRequest } from 'next/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const mocks = vi.hoisted(() => { - const mockGetSession = vi.fn() - const mockCheckHybridAuth = vi.fn() - const mockCheckSessionOrInternalAuth = vi.fn() - const mockCheckInternalAuth = vi.fn() const mockVerifyFileAccess = vi.fn() const mockVerifyWorkspaceFileAccess = vi.fn() const mockVerifyKBFileAccess = vi.fn() const mockVerifyCopilotFileAccess = vi.fn() - const mockGetUserEntityPermissions = vi.fn() const mockUploadWorkspaceFile = vi.fn() const mockGetStorageProvider = vi.fn() const mockIsUsingCloudStorage = vi.fn() @@ -24,15 +20,10 @@ const mocks = vi.hoisted(() => { const mockStorageUploadFile = vi.fn() return { - mockGetSession, - mockCheckHybridAuth, - mockCheckSessionOrInternalAuth, - mockCheckInternalAuth, mockVerifyFileAccess, mockVerifyWorkspaceFileAccess, mockVerifyKBFileAccess, mockVerifyCopilotFileAccess, - mockGetUserEntityPermissions, mockUploadWorkspaceFile, mockGetStorageProvider, mockIsUsingCloudStorage, @@ -42,30 +33,6 @@ const mocks = vi.hoisted(() => { } }) -vi.mock('@sim/logger', () => ({ - createLogger: vi.fn().mockReturnValue({ - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - }), -})) - -vi.mock('@sim/db/schema', () => ({ - workflowFolder: { - id: 'id', - userId: 'userId', - parentId: 'parentId', - updatedAt: 'updatedAt', - workspaceId: 'workspaceId', - sortOrder: 'sortOrder', - createdAt: 'createdAt', - }, - workflow: { id: 'id', folderId: 'folderId', userId: 'userId', updatedAt: 'updatedAt' }, - account: { userId: 'userId', providerId: 'providerId' }, - user: { email: 'email', id: 'id' }, -})) - vi.mock('drizzle-orm', () => ({ and: vi.fn((...conditions: unknown[]) => ({ conditions, type: 'and' })), eq: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'eq' })), @@ -91,7 +58,7 @@ vi.mock('drizzle-orm', () => ({ sql: vi.fn((strings: unknown, ...values: unknown[]) => ({ type: 'sql', sql: strings, values })), })) -vi.mock('@/lib/core/utils/uuid', () => ({ +vi.mock('@sim/utils/id', () => ({ generateId: vi.fn(() => 'test-uuid'), generateShortId: vi.fn(() => 'mock-short-id'), isValidUuid: vi.fn((v: string) => @@ -99,17 +66,6 @@ vi.mock('@/lib/core/utils/uuid', () => ({ ), })) -vi.mock('@/lib/auth', () => ({ - getSession: mocks.mockGetSession, -})) - -vi.mock('@/lib/auth/hybrid', () => ({ - AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, - checkHybridAuth: mocks.mockCheckHybridAuth, - checkSessionOrInternalAuth: mocks.mockCheckSessionOrInternalAuth, - checkInternalAuth: mocks.mockCheckInternalAuth, -})) - vi.mock('@/app/api/files/authorization', () => ({ verifyFileAccess: mocks.mockVerifyFileAccess, verifyWorkspaceFileAccess: mocks.mockVerifyWorkspaceFileAccess, @@ -117,9 +73,7 @@ vi.mock('@/app/api/files/authorization', () => ({ verifyCopilotFileAccess: mocks.mockVerifyCopilotFileAccess, })) -vi.mock('@/lib/workspaces/permissions/utils', () => ({ - getUserEntityPermissions: mocks.mockGetUserEntityPermissions, -})) +vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) vi.mock('@/lib/uploads/contexts/workspace', () => ({ uploadWorkspaceFile: mocks.mockUploadWorkspaceFile, @@ -160,12 +114,12 @@ function setupFileApiMocks( }) if (authenticated) { - mocks.mockGetSession.mockResolvedValue({ user: { id: 'test-user-id' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'test-user-id' } }) } else { - mocks.mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) } - mocks.mockCheckHybridAuth.mockResolvedValue({ + hybridAuthMockFns.mockCheckHybridAuth.mockResolvedValue({ success: authenticated, userId: authenticated ? 'test-user-id' : undefined, error: authenticated ? undefined : 'Unauthorized', @@ -176,7 +130,7 @@ function setupFileApiMocks( mocks.mockVerifyKBFileAccess.mockResolvedValue(true) mocks.mockVerifyCopilotFileAccess.mockResolvedValue(true) - mocks.mockGetUserEntityPermissions.mockResolvedValue('admin') + permissionsMockFns.mockGetUserEntityPermissions.mockResolvedValue('admin') mocks.mockUploadWorkspaceFile.mockResolvedValue({ id: 'test-file-id', @@ -367,7 +321,7 @@ describe('File Upload Security Tests', () => { beforeEach(() => { vi.clearAllMocks() - mocks.mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'test-user-id' }, }) @@ -529,7 +483,7 @@ describe('File Upload Security Tests', () => { describe('Authentication Requirements', () => { it('should reject uploads without authentication', async () => { - mocks.mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const formData = new FormData() const file = new File(['test content'], 'test.pdf', { type: 'application/pdf' }) diff --git a/apps/sim/app/api/folders/[id]/duplicate/route.ts b/apps/sim/app/api/folders/[id]/duplicate/route.ts index be6cea9d429..9225390746d 100644 --- a/apps/sim/app/api/folders/[id]/duplicate/route.ts +++ b/apps/sim/app/api/folders/[id]/duplicate/route.ts @@ -1,13 +1,13 @@ import { db } from '@sim/db' import { workflow, workflowFolder } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, isNull, min } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { generateId } from '@/lib/core/utils/uuid' import { duplicateWorkflow } from '@/lib/workflows/persistence/duplicate' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' diff --git a/apps/sim/app/api/folders/[id]/route.test.ts b/apps/sim/app/api/folders/[id]/route.test.ts index ee13e3a6c3f..54411b99d9c 100644 --- a/apps/sim/app/api/folders/[id]/route.test.ts +++ b/apps/sim/app/api/folders/[id]/route.test.ts @@ -3,17 +3,21 @@ * * @vitest-environment node */ -import { auditMock, createMockRequest, type MockUser } from '@sim/testing' +import { + auditMock, + authMockFns, + createMockRequest, + type MockUser, + permissionsMock, + permissionsMockFns, + workflowsOrchestrationMock, + workflowsOrchestrationMockFns, + workflowsUtilsMock, + workflowsUtilsMockFns, +} from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { - mockGetSession, - mockGetUserEntityPermissions, - mockLogger, - mockDbRef, - mockPerformDeleteFolder, - mockCheckForCircularReference, -} = vi.hoisted(() => { +const { mockLogger, mockDbRef } = vi.hoisted(() => { const logger = { info: vi.fn(), warn: vi.fn(), @@ -24,36 +28,27 @@ const { child: vi.fn(), } return { - mockGetSession: vi.fn(), - mockGetUserEntityPermissions: vi.fn(), mockLogger: logger, mockDbRef: { current: null as any }, - mockPerformDeleteFolder: vi.fn(), - mockCheckForCircularReference: vi.fn(), } }) +const mockPerformDeleteFolder = workflowsOrchestrationMockFns.mockPerformDeleteFolder + +const mockGetUserEntityPermissions = permissionsMockFns.mockGetUserEntityPermissions + vi.mock('@/lib/audit/log', () => auditMock) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) vi.mock('@sim/logger', () => ({ createLogger: vi.fn().mockReturnValue(mockLogger), })) -vi.mock('@/lib/workspaces/permissions/utils', () => ({ - getUserEntityPermissions: mockGetUserEntityPermissions, -})) +vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) vi.mock('@sim/db', () => ({ get db() { return mockDbRef.current }, })) -vi.mock('@/lib/workflows/orchestration', () => ({ - performDeleteFolder: mockPerformDeleteFolder, -})) -vi.mock('@/lib/workflows/utils', () => ({ - checkForCircularReference: mockCheckForCircularReference, -})) +vi.mock('@/lib/workflows/orchestration', () => workflowsOrchestrationMock) +vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) import { DELETE, PUT } from '@/app/api/folders/[id]/route' @@ -146,11 +141,11 @@ function createFolderDbMock(options: FolderDbMockOptions = {}) { } function mockAuthenticatedUser(user?: MockUser) { - mockGetSession.mockResolvedValue({ user: user || TEST_USER }) + authMockFns.mockGetSession.mockResolvedValue({ user: user || TEST_USER }) } function mockUnauthenticated() { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) } describe('Individual Folder API Route', () => { @@ -163,7 +158,7 @@ describe('Individual Folder API Route', () => { success: true, deletedItems: { folders: 1, workflows: 0 }, }) - mockCheckForCircularReference.mockResolvedValue(false) + workflowsUtilsMockFns.mockCheckForCircularReference.mockResolvedValue(false) }) describe('PUT /api/folders/[id]', () => { @@ -398,7 +393,7 @@ describe('Individual Folder API Route', () => { }, }) - mockCheckForCircularReference.mockResolvedValue(true) + workflowsUtilsMockFns.mockCheckForCircularReference.mockResolvedValue(true) const req = createMockRequest('PUT', { name: 'Updated Folder 3', @@ -412,7 +407,10 @@ describe('Individual Folder API Route', () => { const data = await response.json() expect(data).toHaveProperty('error', 'Cannot create circular folder reference') - expect(mockCheckForCircularReference).toHaveBeenCalledWith('folder-3', 'folder-1') + expect(workflowsUtilsMockFns.mockCheckForCircularReference).toHaveBeenCalledWith( + 'folder-3', + 'folder-1' + ) }) }) diff --git a/apps/sim/app/api/folders/route.test.ts b/apps/sim/app/api/folders/route.test.ts index b31b527a745..7f893b06b37 100644 --- a/apps/sim/app/api/folders/route.test.ts +++ b/apps/sim/app/api/folders/route.test.ts @@ -3,11 +3,17 @@ * * @vitest-environment node */ -import { auditMock, createMockRequest } from '@sim/testing' +import { + auditMock, + authMockFns, + createMockRequest, + permissionsMock, + permissionsMockFns, +} from '@sim/testing' import { drizzleOrmMock } from '@sim/testing/mocks' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetSession, mockGetUserEntityPermissions, mockLogger } = vi.hoisted(() => { +const { mockLogger } = vi.hoisted(() => { const logger = { info: vi.fn(), warn: vi.fn(), @@ -18,26 +24,21 @@ const { mockGetSession, mockGetUserEntityPermissions, mockLogger } = vi.hoisted( child: vi.fn(), } return { - mockGetSession: vi.fn(), - mockGetUserEntityPermissions: vi.fn(), mockLogger: logger, } }) +const mockGetUserEntityPermissions = permissionsMockFns.mockGetUserEntityPermissions + vi.mock('@/lib/audit/log', () => auditMock) vi.mock('drizzle-orm', () => ({ ...drizzleOrmMock, min: vi.fn((field) => ({ type: 'min', field })), })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) vi.mock('@sim/logger', () => ({ createLogger: vi.fn().mockReturnValue(mockLogger), })) -vi.mock('@/lib/workspaces/permissions/utils', () => ({ - getUserEntityPermissions: mockGetUserEntityPermissions, -})) +vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) import { db } from '@sim/db' import { GET, POST } from '@/app/api/folders/route' @@ -131,11 +132,11 @@ describe('Folders API Route', () => { const mockTransaction = mockDb.transaction function mockAuthenticatedUser() { - mockGetSession.mockResolvedValue({ user: defaultMockUser }) + authMockFns.mockGetSession.mockResolvedValue({ user: defaultMockUser }) } function mockUnauthenticated() { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) } beforeEach(() => { diff --git a/apps/sim/app/api/folders/route.ts b/apps/sim/app/api/folders/route.ts index 37e0ae8d1d8..4b0612fcb98 100644 --- a/apps/sim/app/api/folders/route.ts +++ b/apps/sim/app/api/folders/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { workflow, workflowFolder } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, asc, eq, isNotNull, isNull, min } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' -import { generateId } from '@/lib/core/utils/uuid' import { captureServerEvent } from '@/lib/posthog/server' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' diff --git a/apps/sim/app/api/form/[identifier]/route.ts b/apps/sim/app/api/form/[identifier]/route.ts index 2ecc89fcfbb..8cb11dabc19 100644 --- a/apps/sim/app/api/form/[identifier]/route.ts +++ b/apps/sim/app/api/form/[identifier]/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { form, workflow, workflowBlocks } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, isNull } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { addCorsHeaders, validateAuthToken } from '@/lib/core/security/deployment' import { generateRequestId } from '@/lib/core/utils/request' -import { generateId } from '@/lib/core/utils/uuid' import { preprocessExecution } from '@/lib/execution/preprocessing' import { LoggingSession } from '@/lib/logs/execution/logging-session' import { executeWorkflow } from '@/lib/workflows/executor/execute-workflow' diff --git a/apps/sim/app/api/form/route.ts b/apps/sim/app/api/form/route.ts index b42d804203e..500e5b4bb47 100644 --- a/apps/sim/app/api/form/route.ts +++ b/apps/sim/app/api/form/route.ts @@ -1,6 +1,7 @@ import { db } from '@sim/db' import { form } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, isNull } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { z } from 'zod' @@ -9,7 +10,6 @@ import { getSession } from '@/lib/auth' import { isDev } from '@/lib/core/config/feature-flags' import { encryptSecret } from '@/lib/core/security/encryption' import { getEmailDomain } from '@/lib/core/utils/urls' -import { generateId } from '@/lib/core/utils/uuid' import { notifySocketDeploymentChanged } from '@/lib/workflows/orchestration' import { deployWorkflow } from '@/lib/workflows/persistence/utils' import { diff --git a/apps/sim/app/api/form/utils.test.ts b/apps/sim/app/api/form/utils.test.ts index d88146cb69f..9c36ccc6e92 100644 --- a/apps/sim/app/api/form/utils.test.ts +++ b/apps/sim/app/api/form/utils.test.ts @@ -3,30 +3,25 @@ * * @vitest-environment node */ -import { databaseMock, loggerMock } from '@sim/testing' +import { encryptionMock, encryptionMockFns, workflowsUtilsMock } from '@sim/testing' import type { NextResponse } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' const { - mockDecryptSecret, mockValidateAuthToken, mockSetDeploymentAuthCookie, mockAddCorsHeaders, mockIsEmailAllowed, } = vi.hoisted(() => ({ - mockDecryptSecret: vi.fn(), mockValidateAuthToken: vi.fn().mockReturnValue(false), mockSetDeploymentAuthCookie: vi.fn(), mockAddCorsHeaders: vi.fn((response: unknown) => response), mockIsEmailAllowed: vi.fn(), })) -vi.mock('@sim/db', () => databaseMock) -vi.mock('@sim/logger', () => loggerMock) +const mockDecryptSecret = encryptionMockFns.mockDecryptSecret -vi.mock('@/lib/core/security/encryption', () => ({ - decryptSecret: mockDecryptSecret, -})) +vi.mock('@/lib/core/security/encryption', () => encryptionMock) vi.mock('@/lib/core/security/deployment', () => ({ validateAuthToken: mockValidateAuthToken, @@ -41,9 +36,7 @@ vi.mock('@/lib/core/config/feature-flags', () => ({ isProd: false, })) -vi.mock('@/lib/workflows/utils', () => ({ - authorizeWorkflowByWorkspacePermission: vi.fn(), -})) +vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) import { decryptSecret } from '@/lib/core/security/encryption' import { diff --git a/apps/sim/app/api/function/execute/route.test.ts b/apps/sim/app/api/function/execute/route.test.ts index 766bbb88fad..1176523c1c5 100644 --- a/apps/sim/app/api/function/execute/route.test.ts +++ b/apps/sim/app/api/function/execute/route.test.ts @@ -3,12 +3,16 @@ * * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { + createMockRequest, + featureFlagsMock, + hybridAuthMockFns, + workflowsUtilsMock, +} from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockCheckInternalAuth, mockExecuteInE2B, mockExecuteInIsolatedVM } = vi.hoisted(() => ({ - mockCheckInternalAuth: vi.fn(), +const { mockExecuteInE2B, mockExecuteInIsolatedVM } = vi.hoisted(() => ({ mockExecuteInE2B: vi.fn(), mockExecuteInIsolatedVM: vi.fn(), })) @@ -17,11 +21,6 @@ vi.mock('@/lib/execution/isolated-vm', () => ({ executeInIsolatedVM: mockExecuteInIsolatedVM, })) -vi.mock('@/lib/auth/hybrid', () => ({ - AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, - checkInternalAuth: mockCheckInternalAuth, -})) - vi.mock('@/lib/execution/e2b', () => ({ executeInE2B: mockExecuteInE2B, executeShellInE2B: vi.fn(), @@ -43,18 +42,9 @@ vi.mock('@/lib/uploads/contexts/workspace/workspace-file-manager', () => ({ uploadWorkspaceFile: vi.fn(), })) -vi.mock('@/lib/workflows/utils', () => ({ - getWorkflowById: vi.fn(), -})) +vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) -vi.mock('@/lib/core/config/feature-flags', () => ({ - isHosted: false, - isE2bEnabled: false, - isProd: false, - isDev: false, - isTest: true, - isEmailVerificationEnabled: false, -})) +vi.mock('@/lib/core/config/feature-flags', () => featureFlagsMock) import { validateProxyUrl } from '@/lib/core/security/input-validation' import { POST } from '@/app/api/function/execute/route' @@ -147,7 +137,7 @@ describe('Function Execute API Route', () => { beforeEach(() => { vi.clearAllMocks() - mockCheckInternalAuth.mockResolvedValue({ + hybridAuthMockFns.mockCheckInternalAuth.mockResolvedValue({ success: true, userId: 'user-123', authType: 'internal_jwt', @@ -164,7 +154,7 @@ describe('Function Execute API Route', () => { describe('Security Tests', () => { it('should reject unauthorized requests', async () => { - mockCheckInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckInternalAuth.mockResolvedValueOnce({ success: false, error: 'Unauthorized', }) diff --git a/apps/sim/app/api/jobs/[jobId]/route.test.ts b/apps/sim/app/api/jobs/[jobId]/route.test.ts index 6ebce09fa67..3caf1992098 100644 --- a/apps/sim/app/api/jobs/[jobId]/route.test.ts +++ b/apps/sim/app/api/jobs/[jobId]/route.test.ts @@ -1,51 +1,25 @@ /** * @vitest-environment node */ +import { hybridAuthMockFns, workflowsUtilsMock, workflowsUtilsMockFns } from '@sim/testing' import type { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { - mockCheckHybridAuth, - mockGetJobQueue, - mockVerifyWorkflowAccess, - mockGetWorkflowById, - mockGetJob, -} = vi.hoisted(() => ({ - mockCheckHybridAuth: vi.fn(), +const { mockGetJobQueue, mockVerifyWorkflowAccess, mockGetJob } = vi.hoisted(() => ({ mockGetJobQueue: vi.fn(), mockVerifyWorkflowAccess: vi.fn(), - mockGetWorkflowById: vi.fn(), mockGetJob: vi.fn(), })) -vi.mock('@sim/logger', () => ({ - createLogger: () => ({ - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - }), -})) - -vi.mock('@/lib/auth/hybrid', () => ({ - checkHybridAuth: mockCheckHybridAuth, -})) - vi.mock('@/lib/core/async-jobs', () => ({ getJobQueue: mockGetJobQueue, })) -vi.mock('@/lib/core/utils/request', () => ({ - generateRequestId: vi.fn().mockReturnValue('request-1'), -})) - vi.mock('@/socket/middleware/permissions', () => ({ verifyWorkflowAccess: mockVerifyWorkflowAccess, })) -vi.mock('@/lib/workflows/utils', () => ({ - getWorkflowById: mockGetWorkflowById, -})) +vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) import { GET } from './route' @@ -61,7 +35,7 @@ describe('GET /api/jobs/[jobId]', () => { beforeEach(() => { vi.clearAllMocks() - mockCheckHybridAuth.mockResolvedValue({ + hybridAuthMockFns.mockCheckHybridAuth.mockResolvedValue({ success: true, userId: 'user-1', apiKeyType: undefined, @@ -69,7 +43,7 @@ describe('GET /api/jobs/[jobId]', () => { }) mockVerifyWorkflowAccess.mockResolvedValue({ hasAccess: true }) - mockGetWorkflowById.mockResolvedValue({ + workflowsUtilsMockFns.mockGetWorkflowById.mockResolvedValue({ id: 'workflow-1', workspaceId: 'workspace-1', }) diff --git a/apps/sim/app/api/jobs/[jobId]/route.ts b/apps/sim/app/api/jobs/[jobId]/route.ts index 8dcfd8bafd2..65d9cba2dcf 100644 --- a/apps/sim/app/api/jobs/[jobId]/route.ts +++ b/apps/sim/app/api/jobs/[jobId]/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkHybridAuth } from '@/lib/auth/hybrid' import { getJobQueue } from '@/lib/core/async-jobs' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' import { createErrorResponse } from '@/app/api/workflows/utils' 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 132ddfb1ba8..5034584ee5c 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 @@ -1,10 +1,17 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { + auditMock, + createMockRequest, + hybridAuthMockFns, + knowledgeApiUtilsMock, + knowledgeApiUtilsMockFns, + requestUtilsMockFns, +} from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockCheckSession, mockCheckAccess, mockCheckWriteAccess, mockDbChain } = vi.hoisted(() => { +const { mockDbChain } = vi.hoisted(() => { const chain = { select: vi.fn().mockReturnThis(), from: vi.fn().mockReturnThis(), @@ -15,49 +22,15 @@ const { mockCheckSession, mockCheckAccess, mockCheckWriteAccess, mockDbChain } = set: vi.fn().mockReturnThis(), returning: vi.fn().mockResolvedValue([]), } - return { - mockCheckSession: vi.fn(), - mockCheckAccess: vi.fn(), - mockCheckWriteAccess: vi.fn(), - mockDbChain: chain, - } + return { mockDbChain: chain } }) +const mockCheckAccess = knowledgeApiUtilsMockFns.mockCheckKnowledgeBaseAccess +const mockCheckWriteAccess = knowledgeApiUtilsMockFns.mockCheckKnowledgeBaseWriteAccess + vi.mock('@sim/db', () => ({ db: mockDbChain })) -vi.mock('@sim/db/schema', () => ({ - document: { - id: 'id', - connectorId: 'connectorId', - deletedAt: 'deletedAt', - filename: 'filename', - externalId: 'externalId', - sourceUrl: 'sourceUrl', - enabled: 'enabled', - userExcluded: 'userExcluded', - uploadedAt: 'uploadedAt', - processingStatus: 'processingStatus', - }, - knowledgeConnector: { - id: 'id', - knowledgeBaseId: 'knowledgeBaseId', - deletedAt: 'deletedAt', - }, -})) -vi.mock('@/app/api/knowledge/utils', () => ({ - checkKnowledgeBaseAccess: mockCheckAccess, - checkKnowledgeBaseWriteAccess: mockCheckWriteAccess, -})) -vi.mock('@/lib/auth/hybrid', () => ({ - checkSessionOrInternalAuth: mockCheckSession, -})) -vi.mock('@/lib/core/utils/request', () => ({ - generateRequestId: vi.fn().mockReturnValue('test-req-id'), -})) -vi.mock('@/lib/audit/log', () => ({ - recordAudit: vi.fn(), - AuditAction: {}, - AuditResourceType: {}, -})) +vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock) +vi.mock('@/lib/audit/log', () => auditMock) import { GET, PATCH } from '@/app/api/knowledge/[id]/connectors/[connectorId]/documents/route' @@ -66,6 +39,7 @@ describe('Connector Documents API Route', () => { beforeEach(() => { vi.clearAllMocks() + requestUtilsMockFns.mockGenerateRequestId.mockReturnValue('test-req-id') mockDbChain.select.mockReturnThis() mockDbChain.from.mockReturnThis() mockDbChain.where.mockReturnThis() @@ -78,7 +52,10 @@ describe('Connector Documents API Route', () => { describe('GET', () => { it('returns 401 when unauthenticated', async () => { - mockCheckSession.mockResolvedValue({ success: false, userId: null }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: false, + userId: null, + }) const req = createMockRequest('GET') const response = await GET(req as never, { params: mockParams }) @@ -87,7 +64,10 @@ describe('Connector Documents API Route', () => { }) it('returns 404 when connector not found', async () => { - mockCheckSession.mockResolvedValue({ success: true, userId: 'user-1' }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: true, + userId: 'user-1', + }) mockCheckAccess.mockResolvedValue({ hasAccess: true }) mockDbChain.limit.mockResolvedValueOnce([]) @@ -98,7 +78,10 @@ describe('Connector Documents API Route', () => { }) it('returns documents list on success', async () => { - mockCheckSession.mockResolvedValue({ success: true, userId: 'user-1' }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: true, + userId: 'user-1', + }) mockCheckAccess.mockResolvedValue({ hasAccess: true }) const doc = { id: 'doc-1', filename: 'test.txt', userExcluded: false } @@ -118,7 +101,10 @@ describe('Connector Documents API Route', () => { }) it('includes excluded documents when includeExcluded=true', async () => { - mockCheckSession.mockResolvedValue({ success: true, userId: 'user-1' }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: true, + userId: 'user-1', + }) mockCheckAccess.mockResolvedValue({ hasAccess: true }) mockDbChain.limit.mockResolvedValueOnce([{ id: 'conn-456' }]) @@ -142,7 +128,10 @@ describe('Connector Documents API Route', () => { describe('PATCH', () => { it('returns 401 when unauthenticated', async () => { - mockCheckSession.mockResolvedValue({ success: false, userId: null }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: false, + userId: null, + }) const req = createMockRequest('PATCH', { operation: 'restore', documentIds: ['doc-1'] }) const response = await PATCH(req as never, { params: mockParams }) @@ -151,7 +140,10 @@ describe('Connector Documents API Route', () => { }) it('returns 400 for invalid body', async () => { - mockCheckSession.mockResolvedValue({ success: true, userId: 'user-1' }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: true, + userId: 'user-1', + }) mockCheckWriteAccess.mockResolvedValue({ hasAccess: true }) mockDbChain.limit.mockResolvedValueOnce([{ id: 'conn-456' }]) @@ -162,7 +154,10 @@ describe('Connector Documents API Route', () => { }) it('returns 404 when connector not found', async () => { - mockCheckSession.mockResolvedValue({ success: true, userId: 'user-1' }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: true, + userId: 'user-1', + }) mockCheckWriteAccess.mockResolvedValue({ hasAccess: true }) mockDbChain.limit.mockResolvedValueOnce([]) @@ -173,7 +168,7 @@ describe('Connector Documents API Route', () => { }) it('returns success for restore operation', async () => { - mockCheckSession.mockResolvedValue({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ success: true, userId: 'user-1', userName: 'Test', @@ -195,7 +190,7 @@ describe('Connector Documents API Route', () => { }) it('returns success for exclude operation', async () => { - mockCheckSession.mockResolvedValue({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ success: true, userId: 'user-1', userName: 'Test', 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 0ed9a961600..bfc04f50802 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 @@ -1,69 +1,44 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { + auditMock, + authOAuthUtilsMock, + createMockRequest, + hybridAuthMockFns, + knowledgeApiUtilsMock, + knowledgeApiUtilsMockFns, +} from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockCheckSession, mockCheckAccess, mockCheckWriteAccess, mockDbChain, mockValidateConfig } = - vi.hoisted(() => { - const chain = { - select: vi.fn().mockReturnThis(), - from: vi.fn().mockReturnThis(), - where: vi.fn().mockReturnThis(), - orderBy: vi.fn().mockReturnThis(), - limit: vi.fn().mockResolvedValue([]), - execute: vi.fn().mockResolvedValue(undefined), - transaction: vi.fn(), - insert: vi.fn().mockReturnThis(), - values: vi.fn().mockResolvedValue(undefined), - update: vi.fn().mockReturnThis(), - delete: vi.fn().mockReturnThis(), - set: vi.fn().mockReturnThis(), - returning: vi.fn().mockResolvedValue([]), - } - return { - mockCheckSession: vi.fn(), - mockCheckAccess: vi.fn(), - mockCheckWriteAccess: vi.fn(), - mockDbChain: chain, - mockValidateConfig: vi.fn(), - } - }) +const { mockDbChain, mockValidateConfig } = vi.hoisted(() => { + const chain = { + select: vi.fn().mockReturnThis(), + from: vi.fn().mockReturnThis(), + where: vi.fn().mockReturnThis(), + orderBy: vi.fn().mockReturnThis(), + limit: vi.fn().mockResolvedValue([]), + execute: vi.fn().mockResolvedValue(undefined), + transaction: vi.fn(), + insert: vi.fn().mockReturnThis(), + values: vi.fn().mockResolvedValue(undefined), + update: vi.fn().mockReturnThis(), + delete: vi.fn().mockReturnThis(), + set: vi.fn().mockReturnThis(), + returning: vi.fn().mockResolvedValue([]), + } + return { + mockDbChain: chain, + mockValidateConfig: vi.fn(), + } +}) + +const mockCheckAccess = knowledgeApiUtilsMockFns.mockCheckKnowledgeBaseAccess +const mockCheckWriteAccess = knowledgeApiUtilsMockFns.mockCheckKnowledgeBaseWriteAccess vi.mock('@sim/db', () => ({ db: mockDbChain })) -vi.mock('@sim/db/schema', () => ({ - document: { - id: 'id', - connectorId: 'connectorId', - fileUrl: 'fileUrl', - archivedAt: 'archivedAt', - deletedAt: 'deletedAt', - }, - embedding: { documentId: 'documentId' }, - knowledgeBase: { id: 'id', userId: 'userId' }, - knowledgeConnector: { - id: 'id', - knowledgeBaseId: 'knowledgeBaseId', - archivedAt: 'archivedAt', - deletedAt: 'deletedAt', - connectorType: 'connectorType', - credentialId: 'credentialId', - }, - knowledgeConnectorSyncLog: { connectorId: 'connectorId', startedAt: 'startedAt' }, -})) -vi.mock('@/app/api/knowledge/utils', () => ({ - checkKnowledgeBaseAccess: mockCheckAccess, - checkKnowledgeBaseWriteAccess: mockCheckWriteAccess, -})) -vi.mock('@/lib/auth/hybrid', () => ({ - checkSessionOrInternalAuth: mockCheckSession, -})) -vi.mock('@/lib/core/utils/request', () => ({ - generateRequestId: vi.fn().mockReturnValue('test-req-id'), -})) -vi.mock('@/app/api/auth/oauth/utils', () => ({ - refreshAccessTokenIfNeeded: vi.fn(), -})) +vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock) +vi.mock('@/app/api/auth/oauth/utils', () => authOAuthUtilsMock) vi.mock('@/connectors/registry', () => ({ CONNECTOR_REGISTRY: { jira: { validateConfig: mockValidateConfig }, @@ -75,11 +50,7 @@ vi.mock('@/lib/knowledge/tags/service', () => ({ vi.mock('@/lib/knowledge/documents/service', () => ({ deleteDocumentStorageFiles: vi.fn().mockResolvedValue(undefined), })) -vi.mock('@/lib/audit/log', () => ({ - recordAudit: vi.fn(), - AuditAction: {}, - AuditResourceType: {}, -})) +vi.mock('@/lib/audit/log', () => auditMock) import { DELETE, GET, PATCH } from '@/app/api/knowledge/[id]/connectors/[connectorId]/route' @@ -105,7 +76,10 @@ describe('Knowledge Connector By ID API Route', () => { describe('GET', () => { it('returns 401 when unauthenticated', async () => { - mockCheckSession.mockResolvedValue({ success: false, userId: null }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: false, + userId: null, + }) const req = createMockRequest('GET') const response = await GET(req, { params: mockParams }) @@ -114,7 +88,10 @@ describe('Knowledge Connector By ID API Route', () => { }) it('returns 404 when KB not found', async () => { - mockCheckSession.mockResolvedValue({ success: true, userId: 'user-1' }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: true, + userId: 'user-1', + }) mockCheckAccess.mockResolvedValue({ hasAccess: false, notFound: true }) const req = createMockRequest('GET') @@ -124,7 +101,10 @@ describe('Knowledge Connector By ID API Route', () => { }) it('returns 404 when connector not found', async () => { - mockCheckSession.mockResolvedValue({ success: true, userId: 'user-1' }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: true, + userId: 'user-1', + }) mockCheckAccess.mockResolvedValue({ hasAccess: true }) mockDbChain.limit.mockResolvedValueOnce([]) @@ -135,7 +115,10 @@ describe('Knowledge Connector By ID API Route', () => { }) it('returns connector with sync logs on success', async () => { - mockCheckSession.mockResolvedValue({ success: true, userId: 'user-1' }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: true, + userId: 'user-1', + }) mockCheckAccess.mockResolvedValue({ hasAccess: true }) const mockConnector = { id: 'conn-456', connectorType: 'jira', status: 'active' } @@ -156,7 +139,10 @@ describe('Knowledge Connector By ID API Route', () => { describe('PATCH', () => { it('returns 401 when unauthenticated', async () => { - mockCheckSession.mockResolvedValue({ success: false, userId: null }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: false, + userId: null, + }) const req = createMockRequest('PATCH', { status: 'paused' }) const response = await PATCH(req, { params: mockParams }) @@ -165,7 +151,10 @@ describe('Knowledge Connector By ID API Route', () => { }) it('returns 400 for invalid body', async () => { - mockCheckSession.mockResolvedValue({ success: true, userId: 'user-1' }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: true, + userId: 'user-1', + }) mockCheckWriteAccess.mockResolvedValue({ hasAccess: true }) const req = createMockRequest('PATCH', { syncIntervalMinutes: 'not a number' }) @@ -177,7 +166,10 @@ describe('Knowledge Connector By ID API Route', () => { }) it('returns 404 when connector not found during sourceConfig validation', async () => { - mockCheckSession.mockResolvedValue({ success: true, userId: 'user-1' }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: true, + userId: 'user-1', + }) mockCheckWriteAccess.mockResolvedValue({ hasAccess: true }) mockDbChain.limit.mockResolvedValueOnce([]) @@ -188,7 +180,7 @@ describe('Knowledge Connector By ID API Route', () => { }) it('returns 200 and updates status', async () => { - mockCheckSession.mockResolvedValue({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ success: true, userId: 'user-1', userName: 'Test', @@ -214,7 +206,10 @@ describe('Knowledge Connector By ID API Route', () => { describe('DELETE', () => { it('returns 401 when unauthenticated', async () => { - mockCheckSession.mockResolvedValue({ success: false, userId: null }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: false, + userId: null, + }) const req = createMockRequest('DELETE') const response = await DELETE(req, { params: mockParams }) @@ -223,7 +218,7 @@ describe('Knowledge Connector By ID API Route', () => { }) it('returns 200 on successful hard-delete', async () => { - mockCheckSession.mockResolvedValue({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ success: true, userId: 'user-1', userName: 'Test', 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 af517a61bb4..cb5145a1beb 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 @@ -1,10 +1,17 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { + auditMock, + createMockRequest, + hybridAuthMockFns, + knowledgeApiUtilsMock, + knowledgeApiUtilsMockFns, + requestUtilsMockFns, +} from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockCheckSession, mockCheckWriteAccess, mockDispatchSync, mockDbChain } = vi.hoisted(() => { +const { mockDispatchSync, mockDbChain } = vi.hoisted(() => { const chain = { select: vi.fn().mockReturnThis(), from: vi.fn().mockReturnThis(), @@ -15,39 +22,19 @@ const { mockCheckSession, mockCheckWriteAccess, mockDispatchSync, mockDbChain } set: vi.fn().mockReturnThis(), } return { - mockCheckSession: vi.fn(), - mockCheckWriteAccess: vi.fn(), mockDispatchSync: vi.fn().mockResolvedValue(undefined), mockDbChain: chain, } }) +const mockCheckWriteAccess = knowledgeApiUtilsMockFns.mockCheckKnowledgeBaseWriteAccess + vi.mock('@sim/db', () => ({ db: mockDbChain })) -vi.mock('@sim/db/schema', () => ({ - knowledgeConnector: { - id: 'id', - knowledgeBaseId: 'knowledgeBaseId', - deletedAt: 'deletedAt', - status: 'status', - }, -})) -vi.mock('@/app/api/knowledge/utils', () => ({ - checkKnowledgeBaseWriteAccess: mockCheckWriteAccess, -})) -vi.mock('@/lib/auth/hybrid', () => ({ - checkSessionOrInternalAuth: mockCheckSession, -})) -vi.mock('@/lib/core/utils/request', () => ({ - generateRequestId: vi.fn().mockReturnValue('test-req-id'), -})) +vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock) vi.mock('@/lib/knowledge/connectors/sync-engine', () => ({ dispatchSync: mockDispatchSync, })) -vi.mock('@/lib/audit/log', () => ({ - recordAudit: vi.fn(), - AuditAction: {}, - AuditResourceType: {}, -})) +vi.mock('@/lib/audit/log', () => auditMock) import { POST } from '@/app/api/knowledge/[id]/connectors/[connectorId]/sync/route' @@ -56,6 +43,7 @@ describe('Connector Manual Sync API Route', () => { beforeEach(() => { vi.clearAllMocks() + requestUtilsMockFns.mockGenerateRequestId.mockReturnValue('test-req-id') mockDbChain.select.mockReturnThis() mockDbChain.from.mockReturnThis() mockDbChain.where.mockReturnThis() @@ -66,7 +54,10 @@ describe('Connector Manual Sync API Route', () => { }) it('returns 401 when unauthenticated', async () => { - mockCheckSession.mockResolvedValue({ success: false, userId: null }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: false, + userId: null, + }) const req = createMockRequest('POST') const response = await POST(req as never, { params: mockParams }) @@ -75,7 +66,10 @@ describe('Connector Manual Sync API Route', () => { }) it('returns 404 when connector not found', async () => { - mockCheckSession.mockResolvedValue({ success: true, userId: 'user-1' }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: true, + userId: 'user-1', + }) mockCheckWriteAccess.mockResolvedValue({ hasAccess: true }) mockDbChain.limit.mockResolvedValueOnce([]) @@ -86,7 +80,10 @@ describe('Connector Manual Sync API Route', () => { }) it('returns 409 when connector is syncing', async () => { - mockCheckSession.mockResolvedValue({ success: true, userId: 'user-1' }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: true, + userId: 'user-1', + }) mockCheckWriteAccess.mockResolvedValue({ hasAccess: true }) mockDbChain.limit.mockResolvedValueOnce([{ id: 'conn-456', status: 'syncing' }]) @@ -97,7 +94,7 @@ describe('Connector Manual Sync API Route', () => { }) it('dispatches sync on valid request', async () => { - mockCheckSession.mockResolvedValue({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ success: true, userId: 'user-1', userName: 'Test', diff --git a/apps/sim/app/api/knowledge/[id]/connectors/route.ts b/apps/sim/app/api/knowledge/[id]/connectors/route.ts index 34da8e03276..41df290d871 100644 --- a/apps/sim/app/api/knowledge/[id]/connectors/route.ts +++ b/apps/sim/app/api/knowledge/[id]/connectors/route.ts @@ -1,6 +1,7 @@ import { db } from '@sim/db' import { knowledgeBase, knowledgeBaseTagDefinitions, knowledgeConnector } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, desc, eq, isNull, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -9,7 +10,6 @@ import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' import { hasLiveSyncAccess } from '@/lib/billing/core/subscription' import { generateRequestId } from '@/lib/core/utils/request' -import { generateId } from '@/lib/core/utils/uuid' import { dispatchSync } from '@/lib/knowledge/connectors/sync-engine' import { allocateTagSlots } from '@/lib/knowledge/constants' import { createTagDefinition } from '@/lib/knowledge/tags/service' diff --git a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/[chunkId]/route.ts b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/[chunkId]/route.ts index b992ca4b4fe..630db09774f 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/[chunkId]/route.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/[chunkId]/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' -import { generateId } from '@/lib/core/utils/uuid' import { deleteChunk, updateChunk } from '@/lib/knowledge/chunks/service' import { checkChunkAccess } from '@/app/api/knowledge/utils' 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 d3612f1bc4d..2562d3ff8ee 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 @@ -3,11 +3,10 @@ * * @vitest-environment node */ -import { auditMock, createMockRequest } from '@sim/testing' +import { auditMock, authMockFns, createMockRequest, knowledgeApiUtilsMock } from '@sim/testing' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetSession, mockDbChain } = vi.hoisted(() => { - const mockGetSession = vi.fn() +const { mockDbChain } = vi.hoisted(() => { const mockDbChain = { select: vi.fn().mockReturnThis(), from: vi.fn().mockReturnThis(), @@ -18,95 +17,14 @@ const { mockGetSession, mockDbChain } = vi.hoisted(() => { delete: vi.fn().mockReturnThis(), transaction: vi.fn(), } - return { mockGetSession, mockDbChain } + return { mockDbChain } }) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - vi.mock('@sim/db', () => ({ db: mockDbChain, })) -vi.mock('@sim/db/schema', () => ({ - knowledgeBase: { - id: 'kb_id', - userId: 'user_id', - name: 'kb_name', - description: 'description', - tokenCount: 'token_count', - embeddingModel: 'embedding_model', - embeddingDimension: 'embedding_dimension', - chunkingConfig: 'chunking_config', - workspaceId: 'workspace_id', - createdAt: 'created_at', - updatedAt: 'updated_at', - deletedAt: 'deleted_at', - }, - document: { - id: 'doc_id', - knowledgeBaseId: 'kb_id', - filename: 'filename', - fileUrl: 'file_url', - fileSize: 'file_size', - mimeType: 'mime_type', - chunkCount: 'chunk_count', - tokenCount: 'token_count', - characterCount: 'character_count', - processingStatus: 'processing_status', - processingStartedAt: 'processing_started_at', - processingCompletedAt: 'processing_completed_at', - processingError: 'processing_error', - enabled: 'enabled', - tag1: 'tag1', - tag2: 'tag2', - tag3: 'tag3', - tag4: 'tag4', - tag5: 'tag5', - tag6: 'tag6', - tag7: 'tag7', - uploadedAt: 'uploaded_at', - deletedAt: 'deleted_at', - }, - embedding: { - id: 'embedding_id', - documentId: 'doc_id', - knowledgeBaseId: 'kb_id', - chunkIndex: 'chunk_index', - content: 'content', - embedding: 'embedding', - tokenCount: 'token_count', - characterCount: 'character_count', - tag1: 'tag1', - tag2: 'tag2', - tag3: 'tag3', - tag4: 'tag4', - tag5: 'tag5', - tag6: 'tag6', - tag7: 'tag7', - createdAt: 'created_at', - }, - permissions: { - id: 'permission_id', - userId: 'user_id', - entityType: 'entity_type', - entityId: 'entity_id', - permissionType: 'permission_type', - createdAt: 'created_at', - updatedAt: 'updated_at', - }, -})) - -vi.mock('@/app/api/knowledge/utils', () => ({ - checkKnowledgeBaseAccess: vi.fn(), - checkKnowledgeBaseWriteAccess: vi.fn(), - checkDocumentAccess: vi.fn(), - checkDocumentWriteAccess: vi.fn(), - checkChunkAccess: vi.fn(), - generateEmbeddings: vi.fn(), - processDocumentAsync: vi.fn(), -})) +vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock) vi.mock('@/lib/knowledge/documents/service', () => ({ updateDocument: vi.fn(), @@ -192,7 +110,9 @@ describe('Document By ID API Route', () => { const mockParams = Promise.resolve({ id: 'kb-123', documentId: 'doc-123' }) it('should retrieve document successfully for authenticated user', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkDocumentAccess).mockResolvedValue({ hasAccess: true, document: mockDocument, @@ -211,7 +131,7 @@ describe('Document By ID API Route', () => { }) it('should return unauthorized for unauthenticated user', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = createMockRequest('GET') const response = await GET(req, { params: mockParams }) @@ -222,7 +142,9 @@ describe('Document By ID API Route', () => { }) it('should return not found for non-existent document', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkDocumentAccess).mockResolvedValue({ hasAccess: false, notFound: true, @@ -238,7 +160,9 @@ describe('Document By ID API Route', () => { }) it('should return unauthorized for document without access', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkDocumentAccess).mockResolvedValue({ hasAccess: false, reason: 'Access denied', @@ -253,7 +177,9 @@ describe('Document By ID API Route', () => { }) it('should handle database errors', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkDocumentAccess).mockRejectedValue(new Error('Database error')) const req = createMockRequest('GET') @@ -275,7 +201,9 @@ describe('Document By ID API Route', () => { } it('should update document successfully', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkDocumentWriteAccess).mockResolvedValue({ hasAccess: true, document: mockDocument, @@ -305,7 +233,9 @@ describe('Document By ID API Route', () => { }) it('should validate update data', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkDocumentWriteAccess).mockResolvedValue({ hasAccess: true, document: mockDocument, @@ -338,7 +268,9 @@ describe('Document By ID API Route', () => { processingStartedAt: new Date(Date.now() - 200000), // 200 seconds ago } - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkDocumentWriteAccess).mockResolvedValue({ hasAccess: true, document: processingDocument, @@ -367,7 +299,9 @@ describe('Document By ID API Route', () => { }) it('should reject marking failed for non-processing document', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkDocumentWriteAccess).mockResolvedValue({ hasAccess: true, document: { ...mockDocument, processingStatus: 'completed' }, @@ -389,7 +323,9 @@ describe('Document By ID API Route', () => { processingStartedAt: new Date(Date.now() - 60000), // 60 seconds ago } - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkDocumentWriteAccess).mockResolvedValue({ hasAccess: true, document: recentProcessingDocument, @@ -419,7 +355,9 @@ describe('Document By ID API Route', () => { processingError: 'Previous processing failed', } - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkDocumentWriteAccess).mockResolvedValue({ hasAccess: true, document: failedDocument, @@ -454,7 +392,9 @@ describe('Document By ID API Route', () => { }) it('should reject retry for non-failed document', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkDocumentWriteAccess).mockResolvedValue({ hasAccess: true, document: { ...mockDocument, processingStatus: 'completed' }, @@ -475,7 +415,7 @@ describe('Document By ID API Route', () => { const validUpdateData = { filename: 'updated-document.pdf' } it('should return unauthorized for unauthenticated user', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = createMockRequest('PUT', validUpdateData) const response = await PUT(req, { params: mockParams }) @@ -486,7 +426,9 @@ describe('Document By ID API Route', () => { }) it('should return not found for non-existent document', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkDocumentWriteAccess).mockResolvedValue({ hasAccess: false, notFound: true, @@ -502,7 +444,9 @@ describe('Document By ID API Route', () => { }) it('should handle database errors during update', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkDocumentWriteAccess).mockResolvedValue({ hasAccess: true, document: mockDocument, @@ -524,7 +468,9 @@ describe('Document By ID API Route', () => { const mockParams = Promise.resolve({ id: 'kb-123', documentId: 'doc-123' }) it('should delete document successfully', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkDocumentWriteAccess).mockResolvedValue({ hasAccess: true, document: mockDocument, @@ -547,7 +493,7 @@ describe('Document By ID API Route', () => { }) it('should return unauthorized for unauthenticated user', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = createMockRequest('DELETE') const response = await DELETE(req, { params: mockParams }) @@ -558,7 +504,9 @@ describe('Document By ID API Route', () => { }) it('should return not found for non-existent document', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkDocumentWriteAccess).mockResolvedValue({ hasAccess: false, notFound: true, @@ -574,7 +522,9 @@ describe('Document By ID API Route', () => { }) it('should return unauthorized for document without access', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkDocumentWriteAccess).mockResolvedValue({ hasAccess: false, reason: 'Access denied', @@ -589,7 +539,9 @@ describe('Document By ID API Route', () => { }) it('should handle database errors during deletion', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkDocumentWriteAccess).mockResolvedValue({ hasAccess: true, document: mockDocument, diff --git a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/tag-definitions/route.ts b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/tag-definitions/route.ts index b60638907b4..8f8cdaaf537 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/tag-definitions/route.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/tag-definitions/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' -import { generateId } from '@/lib/core/utils/uuid' import { SUPPORTED_FIELD_TYPES } from '@/lib/knowledge/constants' import { cleanupUnusedTagDefinitions, 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 2be0e79bc52..355f570d804 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/route.test.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/route.test.ts @@ -3,11 +3,10 @@ * * @vitest-environment node */ -import { auditMock, createMockRequest } from '@sim/testing' +import { auditMock, authMockFns, createMockRequest, knowledgeApiUtilsMock } from '@sim/testing' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetSession, mockDbChain } = vi.hoisted(() => { - const mockGetSession = vi.fn() +const { mockDbChain } = vi.hoisted(() => { const mockDbChain = { select: vi.fn().mockReturnThis(), from: vi.fn().mockReturnThis(), @@ -21,95 +20,14 @@ const { mockGetSession, mockDbChain } = vi.hoisted(() => { set: vi.fn().mockReturnThis(), transaction: vi.fn(), } - return { mockGetSession, mockDbChain } + return { mockDbChain } }) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - vi.mock('@sim/db', () => ({ db: mockDbChain, })) -vi.mock('@sim/db/schema', () => ({ - knowledgeBase: { - id: 'kb_id', - userId: 'user_id', - name: 'kb_name', - description: 'description', - tokenCount: 'token_count', - embeddingModel: 'embedding_model', - embeddingDimension: 'embedding_dimension', - chunkingConfig: 'chunking_config', - workspaceId: 'workspace_id', - createdAt: 'created_at', - updatedAt: 'updated_at', - deletedAt: 'deleted_at', - }, - document: { - id: 'doc_id', - knowledgeBaseId: 'kb_id', - filename: 'filename', - fileUrl: 'file_url', - fileSize: 'file_size', - mimeType: 'mime_type', - chunkCount: 'chunk_count', - tokenCount: 'token_count', - characterCount: 'character_count', - processingStatus: 'processing_status', - processingStartedAt: 'processing_started_at', - processingCompletedAt: 'processing_completed_at', - processingError: 'processing_error', - enabled: 'enabled', - tag1: 'tag1', - tag2: 'tag2', - tag3: 'tag3', - tag4: 'tag4', - tag5: 'tag5', - tag6: 'tag6', - tag7: 'tag7', - uploadedAt: 'uploaded_at', - deletedAt: 'deleted_at', - }, - embedding: { - id: 'embedding_id', - documentId: 'doc_id', - knowledgeBaseId: 'kb_id', - chunkIndex: 'chunk_index', - content: 'content', - embedding: 'embedding', - tokenCount: 'token_count', - characterCount: 'character_count', - tag1: 'tag1', - tag2: 'tag2', - tag3: 'tag3', - tag4: 'tag4', - tag5: 'tag5', - tag6: 'tag6', - tag7: 'tag7', - createdAt: 'created_at', - }, - permissions: { - id: 'permission_id', - userId: 'user_id', - entityType: 'entity_type', - entityId: 'entity_id', - permissionType: 'permission_type', - createdAt: 'created_at', - updatedAt: 'updated_at', - }, -})) - -vi.mock('@/app/api/knowledge/utils', () => ({ - checkKnowledgeBaseAccess: vi.fn(), - checkKnowledgeBaseWriteAccess: vi.fn(), - checkDocumentAccess: vi.fn(), - checkDocumentWriteAccess: vi.fn(), - checkChunkAccess: vi.fn(), - generateEmbeddings: vi.fn(), - processDocumentAsync: vi.fn(), -})) +vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock) vi.mock('@/lib/knowledge/documents/service', () => ({ getDocuments: vi.fn(), @@ -201,7 +119,9 @@ describe('Knowledge Base Documents API Route', () => { const mockParams = Promise.resolve({ id: 'kb-123' }) it('should retrieve documents successfully for authenticated user', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseAccess).mockResolvedValue({ hasAccess: true, knowledgeBase: { id: 'kb-123', userId: 'user-123' }, @@ -239,7 +159,9 @@ describe('Knowledge Base Documents API Route', () => { }) it('should return documents with default filter', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseAccess).mockResolvedValue({ hasAccess: true, knowledgeBase: { id: 'kb-123', userId: 'user-123' }, @@ -272,7 +194,9 @@ describe('Knowledge Base Documents API Route', () => { }) it('should filter documents by enabled status when requested', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseAccess).mockResolvedValue({ hasAccess: true, knowledgeBase: { id: 'kb-123', userId: 'user-123' }, @@ -307,7 +231,7 @@ describe('Knowledge Base Documents API Route', () => { }) it('should return unauthorized for unauthenticated user', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = createMockRequest('GET') const response = await GET(req, { params: mockParams }) @@ -318,7 +242,9 @@ describe('Knowledge Base Documents API Route', () => { }) it('should return not found for non-existent knowledge base', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseAccess).mockResolvedValue({ hasAccess: false, notFound: true, @@ -333,7 +259,9 @@ describe('Knowledge Base Documents API Route', () => { }) it('should return unauthorized for knowledge base without access', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseAccess).mockResolvedValue({ hasAccess: false }) const req = createMockRequest('GET') @@ -345,7 +273,9 @@ describe('Knowledge Base Documents API Route', () => { }) it('should handle database errors', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseAccess).mockResolvedValue({ hasAccess: true, knowledgeBase: { id: 'kb-123', userId: 'user-123' }, @@ -371,7 +301,9 @@ describe('Knowledge Base Documents API Route', () => { } it('should create single document successfully', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValue({ hasAccess: true, knowledgeBase: { id: 'kb-123', userId: 'user-123' }, @@ -415,7 +347,9 @@ describe('Knowledge Base Documents API Route', () => { }) it('should validate single document data', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValue({ hasAccess: true, knowledgeBase: { id: 'kb-123', userId: 'user-123' }, @@ -463,7 +397,9 @@ describe('Knowledge Base Documents API Route', () => { } it('should create bulk documents successfully', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValue({ hasAccess: true, knowledgeBase: { id: 'kb-123', userId: 'user-123' }, @@ -513,7 +449,9 @@ describe('Knowledge Base Documents API Route', () => { }) it('should validate bulk document data', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValue({ hasAccess: true, knowledgeBase: { id: 'kb-123', userId: 'user-123' }, @@ -545,7 +483,9 @@ describe('Knowledge Base Documents API Route', () => { }) it('should handle processing errors gracefully', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValue({ hasAccess: true, knowledgeBase: { id: 'kb-123', userId: 'user-123' }, @@ -589,7 +529,7 @@ describe('Knowledge Base Documents API Route', () => { } it('should return unauthorized for unauthenticated user', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = createMockRequest('POST', validDocumentData) const response = await POST(req, { params: mockParams }) @@ -600,7 +540,9 @@ describe('Knowledge Base Documents API Route', () => { }) it('should return not found for non-existent knowledge base', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValue({ hasAccess: false, notFound: true, @@ -615,7 +557,9 @@ describe('Knowledge Base Documents API Route', () => { }) it('should return unauthorized for knowledge base without access', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValue({ hasAccess: false }) const req = createMockRequest('POST', validDocumentData) @@ -627,7 +571,9 @@ describe('Knowledge Base Documents API Route', () => { }) it('should handle database errors during creation', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValue({ hasAccess: true, knowledgeBase: { id: 'kb-123', userId: 'user-123' }, diff --git a/apps/sim/app/api/knowledge/[id]/documents/route.ts b/apps/sim/app/api/knowledge/[id]/documents/route.ts index b5614aec41d..02e5793a691 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/route.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/route.ts @@ -1,10 +1,10 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { bulkDocumentOperation, bulkDocumentOperationByFilter, diff --git a/apps/sim/app/api/knowledge/[id]/documents/upsert/route.ts b/apps/sim/app/api/knowledge/[id]/documents/upsert/route.ts index 8d5ee153918..38b735dd090 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/upsert/route.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/upsert/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { document } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, isNull } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createDocumentRecords, deleteDocument, diff --git a/apps/sim/app/api/knowledge/[id]/next-available-slot/route.ts b/apps/sim/app/api/knowledge/[id]/next-available-slot/route.ts index a2e5572f8bc..77ddad14d4d 100644 --- a/apps/sim/app/api/knowledge/[id]/next-available-slot/route.ts +++ b/apps/sim/app/api/knowledge/[id]/next-available-slot/route.ts @@ -1,7 +1,7 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' -import { generateId } from '@/lib/core/utils/uuid' import { getNextAvailableSlot, getTagDefinitions } from '@/lib/knowledge/tags/service' import { checkKnowledgeBaseAccess } from '@/app/api/knowledge/utils' diff --git a/apps/sim/app/api/knowledge/[id]/route.test.ts b/apps/sim/app/api/knowledge/[id]/route.test.ts index 7ae829211d5..3dd0603c9f9 100644 --- a/apps/sim/app/api/knowledge/[id]/route.test.ts +++ b/apps/sim/app/api/knowledge/[id]/route.test.ts @@ -3,11 +3,10 @@ * * @vitest-environment node */ -import { auditMock, createMockRequest } from '@sim/testing' +import { auditMock, authMockFns, createMockRequest, knowledgeApiUtilsMock } from '@sim/testing' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetSession, mockDbChain } = vi.hoisted(() => { - const mockGetSession = vi.fn() +const { mockDbChain } = vi.hoisted(() => { const mockDbChain = { select: vi.fn().mockReturnThis(), from: vi.fn().mockReturnThis(), @@ -16,86 +15,13 @@ const { mockGetSession, mockDbChain } = vi.hoisted(() => { update: vi.fn().mockReturnThis(), set: vi.fn().mockReturnThis(), } - return { mockGetSession, mockDbChain } + return { mockDbChain } }) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - vi.mock('@sim/db', () => ({ db: mockDbChain, })) -vi.mock('@sim/db/schema', () => ({ - knowledgeBase: { - id: 'kb_id', - userId: 'user_id', - name: 'kb_name', - description: 'description', - tokenCount: 'token_count', - embeddingModel: 'embedding_model', - embeddingDimension: 'embedding_dimension', - chunkingConfig: 'chunking_config', - workspaceId: 'workspace_id', - createdAt: 'created_at', - updatedAt: 'updated_at', - deletedAt: 'deleted_at', - }, - document: { - id: 'doc_id', - knowledgeBaseId: 'kb_id', - filename: 'filename', - fileUrl: 'file_url', - fileSize: 'file_size', - mimeType: 'mime_type', - chunkCount: 'chunk_count', - tokenCount: 'token_count', - characterCount: 'character_count', - processingStatus: 'processing_status', - processingStartedAt: 'processing_started_at', - processingCompletedAt: 'processing_completed_at', - processingError: 'processing_error', - enabled: 'enabled', - tag1: 'tag1', - tag2: 'tag2', - tag3: 'tag3', - tag4: 'tag4', - tag5: 'tag5', - tag6: 'tag6', - tag7: 'tag7', - uploadedAt: 'uploaded_at', - deletedAt: 'deleted_at', - }, - embedding: { - id: 'embedding_id', - documentId: 'doc_id', - knowledgeBaseId: 'kb_id', - chunkIndex: 'chunk_index', - content: 'content', - embedding: 'embedding', - tokenCount: 'token_count', - characterCount: 'character_count', - tag1: 'tag1', - tag2: 'tag2', - tag3: 'tag3', - tag4: 'tag4', - tag5: 'tag5', - tag6: 'tag6', - tag7: 'tag7', - createdAt: 'created_at', - }, - permissions: { - id: 'permission_id', - userId: 'user_id', - entityType: 'entity_type', - entityId: 'entity_id', - permissionType: 'permission_type', - createdAt: 'created_at', - updatedAt: 'updated_at', - }, -})) - vi.mock('@/lib/audit/log', () => auditMock) vi.mock('@/lib/knowledge/service', async (importOriginal) => { @@ -108,10 +34,7 @@ vi.mock('@/lib/knowledge/service', async (importOriginal) => { } }) -vi.mock('@/app/api/knowledge/utils', () => ({ - checkKnowledgeBaseAccess: vi.fn(), - checkKnowledgeBaseWriteAccess: vi.fn(), -})) +vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock) import { deleteKnowledgeBase, @@ -162,7 +85,9 @@ describe('Knowledge Base By ID API Route', () => { const mockParams = Promise.resolve({ id: 'kb-123' }) it('should retrieve knowledge base successfully for authenticated user', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseAccess).mockResolvedValueOnce({ hasAccess: true, @@ -184,7 +109,7 @@ describe('Knowledge Base By ID API Route', () => { }) it('should return unauthorized for unauthenticated user', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = createMockRequest('GET') const response = await GET(req, { params: mockParams }) @@ -195,7 +120,9 @@ describe('Knowledge Base By ID API Route', () => { }) it('should return not found for non-existent knowledge base', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseAccess).mockResolvedValueOnce({ hasAccess: false, @@ -211,7 +138,9 @@ describe('Knowledge Base By ID API Route', () => { }) it('should return unauthorized for knowledge base owned by different user', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseAccess).mockResolvedValueOnce({ hasAccess: false, @@ -227,7 +156,9 @@ describe('Knowledge Base By ID API Route', () => { }) it('should return not found when service returns null', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseAccess).mockResolvedValueOnce({ hasAccess: true, @@ -245,7 +176,9 @@ describe('Knowledge Base By ID API Route', () => { }) it('should handle database errors', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseAccess).mockRejectedValueOnce(new Error('Database error')) @@ -266,7 +199,9 @@ describe('Knowledge Base By ID API Route', () => { } it('should update knowledge base successfully', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) resetMocks() @@ -299,7 +234,7 @@ describe('Knowledge Base By ID API Route', () => { }) it('should return unauthorized for unauthenticated user', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = createMockRequest('PUT', validUpdateData) const response = await PUT(req, { params: mockParams }) @@ -310,7 +245,9 @@ describe('Knowledge Base By ID API Route', () => { }) it('should return not found for non-existent knowledge base', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) resetMocks() @@ -328,7 +265,9 @@ describe('Knowledge Base By ID API Route', () => { }) it('should validate update data', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) resetMocks() @@ -351,7 +290,9 @@ describe('Knowledge Base By ID API Route', () => { }) it('should handle database errors during update', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValueOnce({ hasAccess: true, @@ -373,7 +314,9 @@ describe('Knowledge Base By ID API Route', () => { const mockParams = Promise.resolve({ id: 'kb-123' }) it('should delete knowledge base successfully', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) resetMocks() @@ -396,7 +339,7 @@ describe('Knowledge Base By ID API Route', () => { }) it('should return unauthorized for unauthenticated user', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = createMockRequest('DELETE') const response = await DELETE(req, { params: mockParams }) @@ -407,7 +350,9 @@ describe('Knowledge Base By ID API Route', () => { }) it('should return not found for non-existent knowledge base', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) resetMocks() @@ -425,7 +370,9 @@ describe('Knowledge Base By ID API Route', () => { }) it('should return unauthorized for knowledge base owned by different user', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) resetMocks() @@ -443,7 +390,9 @@ describe('Knowledge Base By ID API Route', () => { }) it('should handle database errors during delete', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValueOnce({ hasAccess: true, diff --git a/apps/sim/app/api/knowledge/[id]/tag-definitions/[tagId]/route.ts b/apps/sim/app/api/knowledge/[id]/tag-definitions/[tagId]/route.ts index bb6f8d9b46e..c78bc753513 100644 --- a/apps/sim/app/api/knowledge/[id]/tag-definitions/[tagId]/route.ts +++ b/apps/sim/app/api/knowledge/[id]/tag-definitions/[tagId]/route.ts @@ -1,7 +1,7 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { deleteTagDefinition } from '@/lib/knowledge/tags/service' import { checkKnowledgeBaseWriteAccess } from '@/app/api/knowledge/utils' diff --git a/apps/sim/app/api/knowledge/[id]/tag-definitions/route.ts b/apps/sim/app/api/knowledge/[id]/tag-definitions/route.ts index 57ad6c9fb2f..69ce37b7ad5 100644 --- a/apps/sim/app/api/knowledge/[id]/tag-definitions/route.ts +++ b/apps/sim/app/api/knowledge/[id]/tag-definitions/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { AuthType, checkSessionOrInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { SUPPORTED_FIELD_TYPES } from '@/lib/knowledge/constants' import { createTagDefinition, getTagDefinitions } from '@/lib/knowledge/tags/service' import { checkKnowledgeBaseWriteAccess } from '@/app/api/knowledge/utils' diff --git a/apps/sim/app/api/knowledge/[id]/tag-usage/route.ts b/apps/sim/app/api/knowledge/[id]/tag-usage/route.ts index 3ba49402445..03517068dc9 100644 --- a/apps/sim/app/api/knowledge/[id]/tag-usage/route.ts +++ b/apps/sim/app/api/knowledge/[id]/tag-usage/route.ts @@ -1,7 +1,7 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' -import { generateId } from '@/lib/core/utils/uuid' import { getTagUsage } from '@/lib/knowledge/tags/service' import { checkKnowledgeBaseAccess } from '@/app/api/knowledge/utils' diff --git a/apps/sim/app/api/knowledge/route.test.ts b/apps/sim/app/api/knowledge/route.test.ts index 362047b646b..64c3638e4f6 100644 --- a/apps/sim/app/api/knowledge/route.test.ts +++ b/apps/sim/app/api/knowledge/route.test.ts @@ -3,11 +3,16 @@ * * @vitest-environment node */ -import { auditMock, createMockRequest } from '@sim/testing' +import { + auditMock, + authMockFns, + createMockRequest, + permissionsMock, + permissionsMockFns, +} from '@sim/testing' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetSession, mockDbChain } = vi.hoisted(() => { - const mockGetSession = vi.fn() +const { mockDbChain } = vi.hoisted(() => { const mockDbChain = { select: vi.fn().mockReturnThis(), from: vi.fn().mockReturnThis(), @@ -19,91 +24,16 @@ const { mockGetSession, mockDbChain } = vi.hoisted(() => { insert: vi.fn().mockReturnThis(), values: vi.fn().mockResolvedValue(undefined), } - return { mockGetSession, mockDbChain } + return { mockDbChain } }) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - vi.mock('@sim/db', () => ({ db: mockDbChain, })) -vi.mock('@sim/db/schema', () => ({ - knowledgeBase: { - id: 'kb_id', - userId: 'user_id', - name: 'kb_name', - description: 'description', - tokenCount: 'token_count', - embeddingModel: 'embedding_model', - embeddingDimension: 'embedding_dimension', - chunkingConfig: 'chunking_config', - workspaceId: 'workspace_id', - createdAt: 'created_at', - updatedAt: 'updated_at', - deletedAt: 'deleted_at', - }, - document: { - id: 'doc_id', - knowledgeBaseId: 'kb_id', - filename: 'filename', - fileUrl: 'file_url', - fileSize: 'file_size', - mimeType: 'mime_type', - chunkCount: 'chunk_count', - tokenCount: 'token_count', - characterCount: 'character_count', - processingStatus: 'processing_status', - processingStartedAt: 'processing_started_at', - processingCompletedAt: 'processing_completed_at', - processingError: 'processing_error', - enabled: 'enabled', - tag1: 'tag1', - tag2: 'tag2', - tag3: 'tag3', - tag4: 'tag4', - tag5: 'tag5', - tag6: 'tag6', - tag7: 'tag7', - uploadedAt: 'uploaded_at', - deletedAt: 'deleted_at', - }, - embedding: { - id: 'embedding_id', - documentId: 'doc_id', - knowledgeBaseId: 'kb_id', - chunkIndex: 'chunk_index', - content: 'content', - embedding: 'embedding', - tokenCount: 'token_count', - characterCount: 'character_count', - tag1: 'tag1', - tag2: 'tag2', - tag3: 'tag3', - tag4: 'tag4', - tag5: 'tag5', - tag6: 'tag6', - tag7: 'tag7', - createdAt: 'created_at', - }, - permissions: { - id: 'permission_id', - userId: 'user_id', - entityType: 'entity_type', - entityId: 'entity_id', - permissionType: 'permission_type', - createdAt: 'created_at', - updatedAt: 'updated_at', - }, -})) - vi.mock('@/lib/audit/log', () => auditMock) -vi.mock('@/lib/workspaces/permissions/utils', () => ({ - getUserEntityPermissions: vi.fn().mockResolvedValue('admin'), -})) +vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) import { GET, POST } from '@/app/api/knowledge/route' @@ -120,6 +50,8 @@ describe('Knowledge Base API Route', () => { } }) + permissionsMockFns.mockGetUserEntityPermissions.mockResolvedValue('admin') + vi.stubGlobal('crypto', { randomUUID: vi.fn().mockReturnValue('mock-uuid-1234-5678'), }) @@ -131,7 +63,7 @@ describe('Knowledge Base API Route', () => { describe('GET /api/knowledge', () => { it('should return unauthorized for unauthenticated user', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = createMockRequest('GET') const response = await GET(req) @@ -142,7 +74,9 @@ describe('Knowledge Base API Route', () => { }) it('should handle database errors', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) mockDbChain.orderBy.mockRejectedValue(new Error('Database error')) const req = createMockRequest('GET') @@ -167,7 +101,9 @@ describe('Knowledge Base API Route', () => { } it('should create knowledge base successfully', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) const req = createMockRequest('POST', validKnowledgeBaseData) const response = await POST(req) @@ -181,7 +117,7 @@ describe('Knowledge Base API Route', () => { }) it('should return unauthorized for unauthenticated user', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = createMockRequest('POST', validKnowledgeBaseData) const response = await POST(req) @@ -192,7 +128,9 @@ describe('Knowledge Base API Route', () => { }) it('should validate required fields', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) const req = createMockRequest('POST', { description: 'Missing name' }) const response = await POST(req) @@ -204,7 +142,9 @@ describe('Knowledge Base API Route', () => { }) it('should require workspaceId', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) const req = createMockRequest('POST', { name: 'Test KB' }) const response = await POST(req) @@ -216,7 +156,9 @@ describe('Knowledge Base API Route', () => { }) it('should validate chunking config constraints', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) const invalidData = { name: 'Test KB', @@ -237,7 +179,9 @@ describe('Knowledge Base API Route', () => { }) it('should use default values for optional fields', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) const minimalData = { name: 'Test KB', workspaceId: 'test-workspace-id' } const req = createMockRequest('POST', minimalData) @@ -255,7 +199,9 @@ describe('Knowledge Base API Route', () => { }) it('should handle database errors during creation', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' } }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'user-123', email: 'test@example.com' }, + }) mockDbChain.values.mockRejectedValue(new Error('Database error')) const req = createMockRequest('POST', validKnowledgeBaseData) diff --git a/apps/sim/app/api/knowledge/search/route.test.ts b/apps/sim/app/api/knowledge/search/route.test.ts index 30027bca10b..e9efa572701 100644 --- a/apps/sim/app/api/knowledge/search/route.test.ts +++ b/apps/sim/app/api/knowledge/search/route.test.ts @@ -5,14 +5,19 @@ * * @vitest-environment node */ -import { createEnvMock, createMockRequest, requestUtilsMock } from '@sim/testing' +import { + createEnvMock, + createMockRequest, + hybridAuthMockFns, + knowledgeApiUtilsMock, + knowledgeApiUtilsMockFns, + workflowsUtilsMock, + workflowsUtilsMockFns, +} from '@sim/testing' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const { mockDbChain, - mockCheckSessionOrInternalAuth, - mockAuthorizeWorkflowByWorkspacePermission, - mockCheckKnowledgeBaseAccess, mockGetDocumentTagDefinitions, mockHandleTagOnlySearch, mockHandleVectorOnlySearch, @@ -32,9 +37,6 @@ const { groupBy: vi.fn().mockReturnThis(), having: vi.fn().mockReturnThis(), }, - mockCheckSessionOrInternalAuth: vi.fn(), - mockAuthorizeWorkflowByWorkspacePermission: vi.fn(), - mockCheckKnowledgeBaseAccess: vi.fn(), mockGetDocumentTagDefinitions: vi.fn(), mockHandleTagOnlySearch: vi.fn(), mockHandleVectorOnlySearch: vi.fn(), @@ -44,6 +46,8 @@ const { mockGetDocumentNamesByIds: vi.fn(), })) +const mockCheckKnowledgeBaseAccess = knowledgeApiUtilsMockFns.mockCheckKnowledgeBaseAccess + vi.mock('drizzle-orm', () => ({ and: vi.fn().mockImplementation((...args) => ({ and: args })), eq: vi.fn().mockImplementation((a, b) => ({ eq: [a, b] })), @@ -56,92 +60,14 @@ vi.mock('drizzle-orm', () => ({ })), })) -vi.mock('@sim/db/schema', () => ({ - knowledgeBase: { - id: 'kb_id', - userId: 'user_id', - name: 'kb_name', - description: 'description', - tokenCount: 'token_count', - embeddingModel: 'embedding_model', - embeddingDimension: 'embedding_dimension', - chunkingConfig: 'chunking_config', - workspaceId: 'workspace_id', - createdAt: 'created_at', - updatedAt: 'updated_at', - deletedAt: 'deleted_at', - }, - document: { - id: 'doc_id', - knowledgeBaseId: 'kb_id', - filename: 'filename', - fileUrl: 'file_url', - fileSize: 'file_size', - mimeType: 'mime_type', - chunkCount: 'chunk_count', - tokenCount: 'token_count', - characterCount: 'character_count', - processingStatus: 'processing_status', - processingStartedAt: 'processing_started_at', - processingCompletedAt: 'processing_completed_at', - processingError: 'processing_error', - enabled: 'enabled', - tag1: 'tag1', - tag2: 'tag2', - tag3: 'tag3', - tag4: 'tag4', - tag5: 'tag5', - tag6: 'tag6', - tag7: 'tag7', - uploadedAt: 'uploaded_at', - deletedAt: 'deleted_at', - }, - embedding: { - id: 'embedding_id', - documentId: 'doc_id', - knowledgeBaseId: 'kb_id', - chunkIndex: 'chunk_index', - content: 'content', - embedding: 'embedding', - tokenCount: 'token_count', - characterCount: 'character_count', - tag1: 'tag1', - tag2: 'tag2', - tag3: 'tag3', - tag4: 'tag4', - tag5: 'tag5', - tag6: 'tag6', - tag7: 'tag7', - createdAt: 'created_at', - }, - permissions: { - id: 'permission_id', - userId: 'user_id', - entityType: 'entity_type', - entityId: 'entity_id', - permissionType: 'permission_type', - createdAt: 'created_at', - updatedAt: 'updated_at', - }, -})) - vi.mock('@sim/db', () => ({ db: mockDbChain, })) -vi.mock('@/lib/auth/hybrid', () => ({ - AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, - checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, -})) - -vi.mock('@/lib/workflows/utils', () => ({ - authorizeWorkflowByWorkspacePermission: mockAuthorizeWorkflowByWorkspacePermission, -})) +vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) vi.mock('@/lib/core/config/env', () => createEnvMock({ OPENAI_API_KEY: 'test-api-key' })) -vi.mock('@/lib/core/utils/request', () => requestUtilsMock) - vi.mock('@/lib/documents/utils', () => ({ retryWithExponentialBackoff: vi.fn().mockImplementation((fn) => fn()), })) @@ -163,9 +89,7 @@ vi.mock('@/providers/utils', () => ({ }), })) -vi.mock('@/app/api/knowledge/utils', () => ({ - checkKnowledgeBaseAccess: mockCheckKnowledgeBaseAccess, -})) +vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock) vi.mock('@/lib/knowledge/tags/service', () => ({ getDocumentTagDefinitions: mockGetDocumentTagDefinitions, @@ -240,12 +164,12 @@ describe('Knowledge Search API Route', () => { doc2: 'Document 2', }) mockGetDocumentTagDefinitions.mockClear() - mockCheckSessionOrInternalAuth.mockClear().mockResolvedValue({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockClear().mockResolvedValue({ success: true, userId: 'user-123', authType: 'session', }) - mockAuthorizeWorkflowByWorkspacePermission.mockClear().mockResolvedValue({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockClear().mockResolvedValue({ allowed: true, status: 200, }) @@ -400,15 +324,17 @@ describe('Knowledge Search API Route', () => { expect(response.status).toBe(200) expect(data.success).toBe(true) - expect(mockAuthorizeWorkflowByWorkspacePermission).toHaveBeenCalledWith({ - workflowId: 'workflow-123', - userId: 'user-123', - action: 'read', - }) + expect(workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission).toHaveBeenCalledWith( + { + workflowId: 'workflow-123', + userId: 'user-123', + action: 'read', + } + ) }) it.concurrent('should return unauthorized for unauthenticated request', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: false, error: 'Unauthorized', }) @@ -427,7 +353,7 @@ describe('Knowledge Search API Route', () => { workflowId: 'nonexistent-workflow', } - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ allowed: false, status: 404, message: 'Workflow not found', diff --git a/apps/sim/app/api/knowledge/search/utils.test.ts b/apps/sim/app/api/knowledge/search/utils.test.ts index ccbdfdb8dde..9fd4fa34538 100644 --- a/apps/sim/app/api/knowledge/search/utils.test.ts +++ b/apps/sim/app/api/knowledge/search/utils.test.ts @@ -4,13 +4,11 @@ * * @vitest-environment node */ -import { createEnvMock, databaseMock, loggerMock } from '@sim/testing' +import { createEnvMock } from '@sim/testing' import { mockNextFetchResponse } from '@sim/testing/mocks' import { beforeEach, describe, expect, it, vi } from 'vitest' vi.mock('drizzle-orm') -vi.mock('@sim/logger', () => loggerMock) -vi.mock('@sim/db', () => databaseMock) vi.mock('@/lib/knowledge/documents/utils', () => ({ retryWithExponentialBackoff: (fn: any) => fn(), })) diff --git a/apps/sim/app/api/knowledge/utils.test.ts b/apps/sim/app/api/knowledge/utils.test.ts index ba0d438be88..650c7b1dc6b 100644 --- a/apps/sim/app/api/knowledge/utils.test.ts +++ b/apps/sim/app/api/knowledge/utils.test.ts @@ -82,16 +82,21 @@ vi.stubGlobal( }) ) -vi.mock('@sim/db', () => { +vi.mock('@sim/db', async () => { + const { schemaMock } = (await import('@sim/testing')) as typeof import('@sim/testing') + const tableNameFor = (table: any) => { + if (table === schemaMock.knowledgeBase) return 'knowledge_base' + if (table === schemaMock.document) return 'document' + if (table === schemaMock.embedding) return 'embedding' + return '' + } const selectBuilder = { from(table: any) { return { where() { return { limit(n: number) { - const tableSymbols = Object.getOwnPropertySymbols(table || {}) - const baseNameSymbol = tableSymbols.find((s) => s.toString().includes('BaseName')) - const tableName = baseNameSymbol ? table[baseNameSymbol] : '' + const tableName = tableNameFor(table) if (tableName === 'knowledge_base') { return Promise.resolve(kbRows.slice(0, n)) @@ -117,9 +122,7 @@ vi.mock('@sim/db', () => { update: (table: any) => ({ set: (payload: any) => ({ where: () => { - const tableSymbols = Object.getOwnPropertySymbols(table || {}) - const baseNameSymbol = tableSymbols.find((s) => s.toString().includes('BaseName')) - const tableName = baseNameSymbol ? table[baseNameSymbol] : '' + const tableName = tableNameFor(table) if (tableName === 'knowledge_base') { dbOps.order.push('updateKb') dbOps.updatePayloads.push(payload) diff --git a/apps/sim/app/api/mcp/copilot/route.ts b/apps/sim/app/api/mcp/copilot/route.ts index 422bee8e780..77235c3ff55 100644 --- a/apps/sim/app/api/mcp/copilot/route.ts +++ b/apps/sim/app/api/mcp/copilot/route.ts @@ -13,6 +13,8 @@ import { import { db } from '@sim/db' import { userStats } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' import { eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { validateOAuthAccessToken } from '@/lib/auth/oauth-token' @@ -26,9 +28,7 @@ import { prepareExecutionContext } from '@/lib/copilot/tools/handlers/context' import { DIRECT_TOOL_DEFS, SUBAGENT_TOOL_DEFS } from '@/lib/copilot/tools/mcp/definitions' import { env } from '@/lib/core/config/env' import { RateLimiter } from '@/lib/core/rate-limiter' -import { toError } from '@/lib/core/utils/helpers' import { getBaseUrl } from '@/lib/core/utils/urls' -import { generateId } from '@/lib/core/utils/uuid' import { authorizeWorkflowByWorkspacePermission, resolveWorkflowIdForUser, diff --git a/apps/sim/app/api/mcp/events/route.test.ts b/apps/sim/app/api/mcp/events/route.test.ts index ca9be354ce7..586d87d701b 100644 --- a/apps/sim/app/api/mcp/events/route.test.ts +++ b/apps/sim/app/api/mcp/events/route.test.ts @@ -3,26 +3,17 @@ * * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { authMockFns, createMockRequest, permissionsMock, permissionsMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetSession, mockGetUserEntityPermissions } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), - mockGetUserEntityPermissions: vi.fn(), -})) - -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) +const mockGetUserEntityPermissions = permissionsMockFns.mockGetUserEntityPermissions -vi.mock('@/lib/workspaces/permissions/utils', () => ({ - getUserEntityPermissions: mockGetUserEntityPermissions, -})) +vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) vi.mock('@/lib/events/sse-endpoint', () => ({ createWorkspaceSSE: (_config: any) => { return async (request: any) => { - const session = await mockGetSession() + const session = await authMockFns.mockGetSession() if (!session?.user?.id) { return new Response('Unauthorized', { status: 401 }) } @@ -72,7 +63,7 @@ describe('MCP Events SSE Endpoint', () => { }) it('returns 401 when session is missing', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const request = createMockRequest( 'GET', @@ -89,7 +80,7 @@ describe('MCP Events SSE Endpoint', () => { }) it('returns 400 when workspaceId is missing', async () => { - mockGetSession.mockResolvedValue({ user: defaultMockUser }) + authMockFns.mockGetSession.mockResolvedValue({ user: defaultMockUser }) const request = createMockRequest('GET', undefined, {}, 'http://localhost:3000/api/mcp/events') @@ -101,7 +92,7 @@ describe('MCP Events SSE Endpoint', () => { }) it('returns 403 when user lacks workspace access', async () => { - mockGetSession.mockResolvedValue({ user: defaultMockUser }) + authMockFns.mockGetSession.mockResolvedValue({ user: defaultMockUser }) mockGetUserEntityPermissions.mockResolvedValue(null) const request = createMockRequest( @@ -120,7 +111,7 @@ describe('MCP Events SSE Endpoint', () => { }) it('returns SSE stream when authorized', async () => { - mockGetSession.mockResolvedValue({ user: defaultMockUser }) + authMockFns.mockGetSession.mockResolvedValue({ user: defaultMockUser }) mockGetUserEntityPermissions.mockResolvedValue({ read: true }) const request = createMockRequest( 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 6113799bda7..bd9b10d4d3d 100644 --- a/apps/sim/app/api/mcp/serve/[serverId]/route.test.ts +++ b/apps/sim/app/api/mcp/serve/[serverId]/route.test.ts @@ -3,81 +3,32 @@ * * @vitest-environment node */ +import { + dbChainMock, + dbChainMockFns, + hybridAuthMockFns, + permissionsMock, + permissionsMockFns, + resetDbChainMock, +} from '@sim/testing' import { NextRequest } from 'next/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -const { - mockCheckHybridAuth, - mockGetUserEntityPermissions, - mockGenerateInternalToken, - mockDbSelect, - mockDbFrom, - mockDbInnerJoin, - mockDbWhere, - mockDbLimit, - fetchMock, -} = vi.hoisted(() => ({ - mockCheckHybridAuth: vi.fn(), - mockGetUserEntityPermissions: vi.fn(), +const { mockGenerateInternalToken, fetchMock } = vi.hoisted(() => ({ mockGenerateInternalToken: vi.fn(), - mockDbSelect: vi.fn(), - mockDbFrom: vi.fn(), - mockDbInnerJoin: vi.fn(), - mockDbWhere: vi.fn(), - mockDbLimit: vi.fn(), fetchMock: vi.fn(), })) +const mockGetUserEntityPermissions = permissionsMockFns.mockGetUserEntityPermissions + +vi.mock('@sim/db', () => dbChainMock) vi.mock('drizzle-orm', () => ({ and: vi.fn(), eq: vi.fn(), isNull: vi.fn(), })) -vi.mock('@sim/db', () => ({ - db: { - select: mockDbSelect, - }, -})) - -vi.mock('@sim/db/schema', () => ({ - workflowMcpServer: { - id: 'id', - name: 'name', - workspaceId: 'workspaceId', - isPublic: 'isPublic', - createdBy: 'createdBy', - deletedAt: 'deletedAt', - }, - workflowMcpTool: { - serverId: 'serverId', - toolName: 'toolName', - toolDescription: 'toolDescription', - parameterSchema: 'parameterSchema', - workflowId: 'workflowId', - archivedAt: 'archivedAt', - }, - workflow: { - id: 'id', - isDeployed: 'isDeployed', - archivedAt: 'archivedAt', - }, - workspace: { - id: 'id', - archivedAt: 'archivedAt', - }, -})) - -vi.mock('@/lib/auth/hybrid', () => ({ - AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, - checkHybridAuth: mockCheckHybridAuth, - checkSessionOrInternalAuth: vi.fn(), - checkInternalAuth: vi.fn(), -})) - -vi.mock('@/lib/workspaces/permissions/utils', () => ({ - getUserEntityPermissions: mockGetUserEntityPermissions, -})) +vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) vi.mock('@/lib/auth/internal', () => ({ generateInternalToken: mockGenerateInternalToken, @@ -97,12 +48,7 @@ import { GET, POST } from '@/app/api/mcp/serve/[serverId]/route' describe('MCP Serve Route', () => { beforeEach(() => { vi.clearAllMocks() - - mockDbSelect.mockReturnValue({ from: mockDbFrom }) - mockDbFrom.mockReturnValue({ innerJoin: mockDbInnerJoin, where: mockDbWhere }) - mockDbInnerJoin.mockReturnValue({ where: mockDbWhere }) - mockDbWhere.mockReturnValue({ limit: mockDbLimit }) - + resetDbChainMock() vi.stubGlobal('fetch', fetchMock) }) @@ -111,7 +57,7 @@ describe('MCP Serve Route', () => { }) it('returns 401 for private server when auth fails', async () => { - mockDbLimit.mockResolvedValueOnce([ + dbChainMockFns.limit.mockResolvedValueOnce([ { id: 'server-1', name: 'Private Server', @@ -120,7 +66,10 @@ describe('MCP Serve Route', () => { createdBy: 'owner-1', }, ]) - mockCheckHybridAuth.mockResolvedValueOnce({ success: false, error: 'Unauthorized' }) + hybridAuthMockFns.mockCheckHybridAuth.mockResolvedValueOnce({ + success: false, + error: 'Unauthorized', + }) const req = new NextRequest('http://localhost:3000/api/mcp/serve/server-1', { method: 'POST', @@ -132,7 +81,7 @@ describe('MCP Serve Route', () => { }) it('returns 401 on GET for private server when auth fails', async () => { - mockDbLimit.mockResolvedValueOnce([ + dbChainMockFns.limit.mockResolvedValueOnce([ { id: 'server-1', name: 'Private Server', @@ -141,7 +90,10 @@ describe('MCP Serve Route', () => { createdBy: 'owner-1', }, ]) - mockCheckHybridAuth.mockResolvedValueOnce({ success: false, error: 'Unauthorized' }) + hybridAuthMockFns.mockCheckHybridAuth.mockResolvedValueOnce({ + success: false, + error: 'Unauthorized', + }) const req = new NextRequest('http://localhost:3000/api/mcp/serve/server-1') const response = await GET(req, { params: Promise.resolve({ serverId: 'server-1' }) }) @@ -150,7 +102,7 @@ describe('MCP Serve Route', () => { }) it('forwards X-API-Key for private server api_key auth', async () => { - mockDbLimit + dbChainMockFns.limit .mockResolvedValueOnce([ { id: 'server-1', @@ -163,7 +115,7 @@ describe('MCP Serve Route', () => { .mockResolvedValueOnce([{ toolName: 'tool_a', workflowId: 'wf-1' }]) .mockResolvedValueOnce([{ isDeployed: true }]) - mockCheckHybridAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckHybridAuth.mockResolvedValueOnce({ success: true, userId: 'user-1', authType: 'api_key', @@ -199,7 +151,7 @@ describe('MCP Serve Route', () => { }) it('forwards internal token for private server session auth', async () => { - mockDbLimit + dbChainMockFns.limit .mockResolvedValueOnce([ { id: 'server-1', @@ -212,7 +164,7 @@ describe('MCP Serve Route', () => { .mockResolvedValueOnce([{ toolName: 'tool_a', workflowId: 'wf-1' }]) .mockResolvedValueOnce([{ isDeployed: true }]) - mockCheckHybridAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckHybridAuth.mockResolvedValueOnce({ success: true, userId: 'user-1', authType: 'session', diff --git a/apps/sim/app/api/mcp/servers/[id]/refresh/route.ts b/apps/sim/app/api/mcp/servers/[id]/refresh/route.ts index 7ddc8019d0e..002cd96764e 100644 --- a/apps/sim/app/api/mcp/servers/[id]/refresh/route.ts +++ b/apps/sim/app/api/mcp/servers/[id]/refresh/route.ts @@ -1,9 +1,9 @@ import { db } from '@sim/db' import { mcpServers, workflow, workflowBlocks } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { and, eq, isNull } from 'drizzle-orm' import type { NextRequest } from 'next/server' -import { toError } from '@/lib/core/utils/helpers' import { withMcpAuth } from '@/lib/mcp/middleware' import { mcpService } from '@/lib/mcp/service' import type { McpServerStatusConfig, McpTool, McpToolSchema } from '@/lib/mcp/types' diff --git a/apps/sim/app/api/mcp/servers/[id]/route.ts b/apps/sim/app/api/mcp/servers/[id]/route.ts index 93c4b3aca20..b13a1897855 100644 --- a/apps/sim/app/api/mcp/servers/[id]/route.ts +++ b/apps/sim/app/api/mcp/servers/[id]/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { mcpServers } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { and, eq, isNull } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' -import { toError } from '@/lib/core/utils/helpers' import { McpDnsResolutionError, McpDomainNotAllowedError, diff --git a/apps/sim/app/api/mcp/servers/route.ts b/apps/sim/app/api/mcp/servers/route.ts index b6ea4988035..13fb23a6702 100644 --- a/apps/sim/app/api/mcp/servers/route.ts +++ b/apps/sim/app/api/mcp/servers/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { mcpServers } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' import { and, eq, isNull } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' -import { toError } from '@/lib/core/utils/helpers' -import { generateId } from '@/lib/core/utils/uuid' import { McpDnsResolutionError, McpDomainNotAllowedError, diff --git a/apps/sim/app/api/mcp/servers/test-connection/route.ts b/apps/sim/app/api/mcp/servers/test-connection/route.ts index 2b4297d37ff..648730f0f4f 100644 --- a/apps/sim/app/api/mcp/servers/test-connection/route.ts +++ b/apps/sim/app/api/mcp/servers/test-connection/route.ts @@ -1,6 +1,6 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import type { NextRequest } from 'next/server' -import { toError } from '@/lib/core/utils/helpers' import { McpClient } from '@/lib/mcp/client' import { McpDnsResolutionError, diff --git a/apps/sim/app/api/mcp/tools/stored/route.ts b/apps/sim/app/api/mcp/tools/stored/route.ts index 8cb84a1362a..12792c8d5ff 100644 --- a/apps/sim/app/api/mcp/tools/stored/route.ts +++ b/apps/sim/app/api/mcp/tools/stored/route.ts @@ -1,9 +1,9 @@ import { db } from '@sim/db' import { workflow, workflowBlocks } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' -import { toError } from '@/lib/core/utils/helpers' import { withMcpAuth } from '@/lib/mcp/middleware' import type { McpToolSchema, StoredMcpTool } from '@/lib/mcp/types' import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils' diff --git a/apps/sim/app/api/mcp/workflow-servers/[id]/route.ts b/apps/sim/app/api/mcp/workflow-servers/[id]/route.ts index 57c1330a452..49699160995 100644 --- a/apps/sim/app/api/mcp/workflow-servers/[id]/route.ts +++ b/apps/sim/app/api/mcp/workflow-servers/[id]/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { workflowMcpServer, workflowMcpTool } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { and, eq, isNull } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' -import { toError } from '@/lib/core/utils/helpers' import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware' import { mcpPubSub } from '@/lib/mcp/pubsub' import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils' diff --git a/apps/sim/app/api/mcp/workflow-servers/[id]/tools/[toolId]/route.ts b/apps/sim/app/api/mcp/workflow-servers/[id]/tools/[toolId]/route.ts index 9d157b6a13c..e45ff5dc0dd 100644 --- a/apps/sim/app/api/mcp/workflow-servers/[id]/tools/[toolId]/route.ts +++ b/apps/sim/app/api/mcp/workflow-servers/[id]/tools/[toolId]/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { workflowMcpServer, workflowMcpTool } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { and, eq, isNull } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' -import { toError } from '@/lib/core/utils/helpers' import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware' import { mcpPubSub } from '@/lib/mcp/pubsub' import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils' diff --git a/apps/sim/app/api/mcp/workflow-servers/[id]/tools/route.ts b/apps/sim/app/api/mcp/workflow-servers/[id]/tools/route.ts index 0e4e9608fda..783e05655de 100644 --- a/apps/sim/app/api/mcp/workflow-servers/[id]/tools/route.ts +++ b/apps/sim/app/api/mcp/workflow-servers/[id]/tools/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { workflow, workflowMcpServer, workflowMcpTool } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' import { and, eq, isNull } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' -import { toError } from '@/lib/core/utils/helpers' -import { generateId } from '@/lib/core/utils/uuid' import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware' import { mcpPubSub } from '@/lib/mcp/pubsub' import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils' diff --git a/apps/sim/app/api/mcp/workflow-servers/route.ts b/apps/sim/app/api/mcp/workflow-servers/route.ts index 27a0032cd2f..089d2f9b713 100644 --- a/apps/sim/app/api/mcp/workflow-servers/route.ts +++ b/apps/sim/app/api/mcp/workflow-servers/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { workflow, workflowMcpServer, workflowMcpTool } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' import { and, eq, inArray, isNull, sql } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' -import { toError } from '@/lib/core/utils/helpers' -import { generateId } from '@/lib/core/utils/uuid' import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware' import { mcpPubSub } from '@/lib/mcp/pubsub' import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils' diff --git a/apps/sim/app/api/memory/route.ts b/apps/sim/app/api/memory/route.ts index 82ffffe6963..64bceae92b2 100644 --- a/apps/sim/app/api/memory/route.ts +++ b/apps/sim/app/api/memory/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { memory } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, isNull, like } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { generateId } from '@/lib/core/utils/uuid' import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils' const logger = createLogger('MemoryAPI') diff --git a/apps/sim/app/api/mothership/chats/[chatId]/route.ts b/apps/sim/app/api/mothership/chats/[chatId]/route.ts index 6be661ac09a..91704f28fed 100644 --- a/apps/sim/app/api/mothership/chats/[chatId]/route.ts +++ b/apps/sim/app/api/mothership/chats/[chatId]/route.ts @@ -1,6 +1,7 @@ import { db } from '@sim/db' import { copilotChats } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { and, eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -19,7 +20,6 @@ import { readEvents } from '@/lib/copilot/request/session/buffer' import { readFilePreviewSessions } from '@/lib/copilot/request/session/file-preview-session' import { type StreamBatchEvent, toStreamBatchEvent } from '@/lib/copilot/request/session/types' import { taskPubSub } from '@/lib/copilot/tasks' -import { toError } from '@/lib/core/utils/helpers' import { captureServerEvent } from '@/lib/posthog/server' const logger = createLogger('MothershipChatAPI') diff --git a/apps/sim/app/api/mothership/execute/route.ts b/apps/sim/app/api/mothership/execute/route.ts index 3337838ce1e..25c4618dbf6 100644 --- a/apps/sim/app/api/mothership/execute/route.ts +++ b/apps/sim/app/api/mothership/execute/route.ts @@ -1,4 +1,6 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' @@ -6,8 +8,6 @@ import { buildIntegrationToolSchemas } from '@/lib/copilot/chat/payload' import { generateWorkspaceContext } from '@/lib/copilot/chat/workspace-context' import { runHeadlessCopilotLifecycle } from '@/lib/copilot/request/lifecycle/headless' import { requestExplicitStreamAbort } from '@/lib/copilot/request/session/explicit-abort' -import { toError } from '@/lib/core/utils/helpers' -import { generateId } from '@/lib/core/utils/uuid' import { assertActiveWorkspaceAccess, getUserEntityPermissions, diff --git a/apps/sim/app/api/notifications/poll/route.ts b/apps/sim/app/api/notifications/poll/route.ts index 010f9ccffcc..200b541ff69 100644 --- a/apps/sim/app/api/notifications/poll/route.ts +++ b/apps/sim/app/api/notifications/poll/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateShortId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { verifyCronAuth } from '@/lib/auth/internal' import { acquireLock, releaseLock } from '@/lib/core/config/redis' -import { generateShortId } from '@/lib/core/utils/uuid' import { pollInactivityAlerts } from '@/lib/notifications/inactivity-polling' const logger = createLogger('InactivityAlertPoll') diff --git a/apps/sim/app/api/organizations/[id]/invitations/[invitationId]/route.ts b/apps/sim/app/api/organizations/[id]/invitations/[invitationId]/route.ts index 269efb6a32c..51ce6ac9099 100644 --- a/apps/sim/app/api/organizations/[id]/invitations/[invitationId]/route.ts +++ b/apps/sim/app/api/organizations/[id]/invitations/[invitationId]/route.ts @@ -14,6 +14,7 @@ import { workspaceInvitation, } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, inArray, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -27,7 +28,6 @@ import { ENTITLED_SUBSCRIPTION_STATUSES } from '@/lib/billing/subscriptions/util import { OUTBOX_EVENT_TYPES } from '@/lib/billing/webhooks/outbox-handlers' import { enqueueOutboxEvent } from '@/lib/core/outbox/service' import { getBaseUrl } from '@/lib/core/utils/urls' -import { generateId } from '@/lib/core/utils/uuid' import { syncWorkspaceEnvCredentials } from '@/lib/credentials/environment' import { sendEmail } from '@/lib/messaging/email/mailer' diff --git a/apps/sim/app/api/organizations/[id]/invitations/route.ts b/apps/sim/app/api/organizations/[id]/invitations/route.ts index 5a85cfbb4f0..1ecb244d291 100644 --- a/apps/sim/app/api/organizations/[id]/invitations/route.ts +++ b/apps/sim/app/api/organizations/[id]/invitations/route.ts @@ -9,6 +9,7 @@ import { workspaceInvitation, } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, inArray, isNull, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { @@ -23,7 +24,6 @@ import { validateSeatAvailability, } from '@/lib/billing/validation/seat-management' import { getBaseUrl } from '@/lib/core/utils/urls' -import { generateId } from '@/lib/core/utils/uuid' import { sendEmail } from '@/lib/messaging/email/mailer' import { quickValidateEmail } from '@/lib/messaging/email/validation' import { hasWorkspaceAdminAccess } from '@/lib/workspaces/permissions/utils' diff --git a/apps/sim/app/api/organizations/[id]/members/route.ts b/apps/sim/app/api/organizations/[id]/members/route.ts index ad4e4001c82..063699d12bc 100644 --- a/apps/sim/app/api/organizations/[id]/members/route.ts +++ b/apps/sim/app/api/organizations/[id]/members/route.ts @@ -8,6 +8,7 @@ import { userStats, } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, inArray } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getEmailSubject, renderInvitationEmail } from '@/components/emails' @@ -16,7 +17,6 @@ import { getSession } from '@/lib/auth' import { ENTITLED_SUBSCRIPTION_STATUSES } from '@/lib/billing/subscriptions/utils' import { validateSeatAvailability } from '@/lib/billing/validation/seat-management' import { getBaseUrl } from '@/lib/core/utils/urls' -import { generateId } from '@/lib/core/utils/uuid' import { sendEmail } from '@/lib/messaging/email/mailer' import { quickValidateEmail } from '@/lib/messaging/email/validation' diff --git a/apps/sim/app/api/permission-groups/[id]/members/bulk/route.ts b/apps/sim/app/api/permission-groups/[id]/members/bulk/route.ts index f0be70d26b3..8617746a553 100644 --- a/apps/sim/app/api/permission-groups/[id]/members/bulk/route.ts +++ b/apps/sim/app/api/permission-groups/[id]/members/bulk/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { member, permissionGroup, permissionGroupMember } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, inArray } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { hasAccessControlAccess } from '@/lib/billing' -import { generateId } from '@/lib/core/utils/uuid' const logger = createLogger('PermissionGroupBulkMembers') diff --git a/apps/sim/app/api/permission-groups/[id]/members/route.ts b/apps/sim/app/api/permission-groups/[id]/members/route.ts index 5b5fdc65df7..83cd4a0b17a 100644 --- a/apps/sim/app/api/permission-groups/[id]/members/route.ts +++ b/apps/sim/app/api/permission-groups/[id]/members/route.ts @@ -1,13 +1,13 @@ import { db } from '@sim/db' import { member, permissionGroup, permissionGroupMember, user } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { hasAccessControlAccess } from '@/lib/billing' -import { generateId } from '@/lib/core/utils/uuid' const logger = createLogger('PermissionGroupMembers') diff --git a/apps/sim/app/api/permission-groups/route.ts b/apps/sim/app/api/permission-groups/route.ts index dd5c09e5453..1d5e4d6a42c 100644 --- a/apps/sim/app/api/permission-groups/route.ts +++ b/apps/sim/app/api/permission-groups/route.ts @@ -1,13 +1,13 @@ import { db } from '@sim/db' import { member, organization, permissionGroup, permissionGroupMember, user } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, count, desc, eq } from 'drizzle-orm' import { NextResponse } from 'next/server' import { z } from 'zod' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { hasAccessControlAccess } from '@/lib/billing' -import { generateId } from '@/lib/core/utils/uuid' import { DEFAULT_PERMISSION_GROUP_CONFIG, type PermissionGroupConfig, diff --git a/apps/sim/app/api/providers/route.ts b/apps/sim/app/api/providers/route.ts index 417be50c89e..7e6522fee14 100644 --- a/apps/sim/app/api/providers/route.ts +++ b/apps/sim/app/api/providers/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { account } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils' import { diff --git a/apps/sim/app/api/resume/[workflowId]/[executionId]/[contextId]/route.ts b/apps/sim/app/api/resume/[workflowId]/[executionId]/[contextId]/route.ts index c97e5e5568b..4dc5af81f5f 100644 --- a/apps/sim/app/api/resume/[workflowId]/[executionId]/[contextId]/route.ts +++ b/apps/sim/app/api/resume/[workflowId]/[executionId]/[contextId]/route.ts @@ -1,12 +1,12 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { AuthType } from '@/lib/auth/hybrid' import { getJobQueue } from '@/lib/core/async-jobs' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' import { SSE_HEADERS } from '@/lib/core/utils/sse' import { getBaseUrl } from '@/lib/core/utils/urls' -import { generateId } from '@/lib/core/utils/uuid' import { setExecutionMeta } from '@/lib/execution/event-buffer' import { preprocessExecution } from '@/lib/execution/preprocessing' import { PauseResumeManager } from '@/lib/workflows/executor/human-in-the-loop-manager' diff --git a/apps/sim/app/api/schedules/[id]/route.test.ts b/apps/sim/app/api/schedules/[id]/route.test.ts index c970e41714e..01932c9fe3f 100644 --- a/apps/sim/app/api/schedules/[id]/route.test.ts +++ b/apps/sim/app/api/schedules/[id]/route.test.ts @@ -3,38 +3,17 @@ * * @vitest-environment node */ -import { auditMock, databaseMock, loggerMock, requestUtilsMock } from '@sim/testing' +import { + auditMock, + authMockFns, + databaseMock, + workflowsUtilsMock, + workflowsUtilsMockFns, +} from '@sim/testing' import { NextRequest } from 'next/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetSession, mockAuthorizeWorkflowByWorkspacePermission } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), - mockAuthorizeWorkflowByWorkspacePermission: vi.fn(), -})) - -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - -vi.mock('@/lib/workflows/utils', () => ({ - authorizeWorkflowByWorkspacePermission: mockAuthorizeWorkflowByWorkspacePermission, -})) - -vi.mock('@sim/db', () => databaseMock) - -vi.mock('@sim/db/schema', () => ({ - workflow: { id: 'id', userId: 'userId', workspaceId: 'workspaceId' }, - workflowSchedule: { - id: 'id', - workflowId: 'workflowId', - status: 'status', - cronExpression: 'cronExpression', - timezone: 'timezone', - sourceType: 'sourceType', - sourceWorkspaceId: 'sourceWorkspaceId', - archivedAt: 'archivedAt', - }, -})) +vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) vi.mock('drizzle-orm', () => ({ and: vi.fn(), @@ -42,10 +21,6 @@ vi.mock('drizzle-orm', () => ({ isNull: vi.fn(), })) -vi.mock('@/lib/core/utils/request', () => requestUtilsMock) - -vi.mock('@sim/logger', () => loggerMock) - vi.mock('@/lib/audit/log', () => auditMock) import { PUT } from './route' @@ -85,8 +60,8 @@ function mockDbChain(selectResults: unknown[][]) { describe('Schedule PUT API (Reactivate)', () => { beforeEach(() => { vi.clearAllMocks() - mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ allowed: true, status: 200, workflow: { id: 'wf-1', workspaceId: 'ws-1' }, @@ -100,7 +75,7 @@ describe('Schedule PUT API (Reactivate)', () => { describe('Authentication', () => { it('returns 401 when user is not authenticated', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const res = await PUT(createRequest({ action: 'reactivate' }), createParams('sched-1')) @@ -150,7 +125,7 @@ describe('Schedule PUT API (Reactivate)', () => { }) it('returns 404 when workflow does not exist for schedule', async () => { - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ allowed: false, status: 404, workflow: null, @@ -169,7 +144,7 @@ describe('Schedule PUT API (Reactivate)', () => { describe('Authorization', () => { it('returns 403 when user is not workflow owner', async () => { - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ allowed: false, status: 403, workflow: { id: 'wf-1', workspaceId: null }, @@ -190,7 +165,7 @@ describe('Schedule PUT API (Reactivate)', () => { }) it('returns 403 for workspace member with only read permission', async () => { - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ allowed: false, status: 403, workflow: { id: 'wf-1', workspaceId: 'ws-1' }, diff --git a/apps/sim/app/api/schedules/execute/route.test.ts b/apps/sim/app/api/schedules/execute/route.test.ts index aa1fba6d7b6..d5c50c6c647 100644 --- a/apps/sim/app/api/schedules/execute/route.test.ts +++ b/apps/sim/app/api/schedules/execute/route.test.ts @@ -3,6 +3,14 @@ * * @vitest-environment node */ +import { + dbChainMock, + dbChainMockFns, + requestUtilsMockFns, + resetDbChainMock, + workflowsUtilsMock, + workflowsUtilsMockFns, +} from '@sim/testing' import type { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' @@ -11,40 +19,25 @@ const { mockExecuteScheduleJob, mockExecuteJobInline, mockFeatureFlags, - mockDbReturning, - mockDbUpdate, mockEnqueue, mockStartJob, mockCompleteJob, mockMarkJobFailed, -} = vi.hoisted(() => { - const mockDbReturning = vi.fn().mockReturnValue([]) - const mockDbWhere = vi.fn().mockReturnValue({ returning: mockDbReturning }) - const mockDbSet = vi.fn().mockReturnValue({ where: mockDbWhere }) - const mockDbUpdate = vi.fn().mockReturnValue({ set: mockDbSet }) - const mockEnqueue = vi.fn().mockResolvedValue('job-id-1') - const mockStartJob = vi.fn().mockResolvedValue(undefined) - const mockCompleteJob = vi.fn().mockResolvedValue(undefined) - const mockMarkJobFailed = vi.fn().mockResolvedValue(undefined) - - return { - mockVerifyCronAuth: vi.fn().mockReturnValue(null), - mockExecuteScheduleJob: vi.fn().mockResolvedValue(undefined), - mockExecuteJobInline: vi.fn().mockResolvedValue(undefined), - mockFeatureFlags: { - isTriggerDevEnabled: false, - isHosted: false, - isProd: false, - isDev: true, - }, - mockDbReturning, - mockDbUpdate, - mockEnqueue, - mockStartJob, - mockCompleteJob, - mockMarkJobFailed, - } -}) +} = vi.hoisted(() => ({ + mockVerifyCronAuth: vi.fn().mockReturnValue(null), + mockExecuteScheduleJob: vi.fn().mockResolvedValue(undefined), + mockExecuteJobInline: vi.fn().mockResolvedValue(undefined), + mockFeatureFlags: { + isTriggerDevEnabled: false, + isHosted: false, + isProd: false, + isDev: true, + }, + mockEnqueue: vi.fn().mockResolvedValue('job-id-1'), + mockStartJob: vi.fn().mockResolvedValue(undefined), + mockCompleteJob: vi.fn().mockResolvedValue(undefined), + mockMarkJobFailed: vi.fn().mockResolvedValue(undefined), +})) vi.mock('@/lib/auth/internal', () => ({ verifyCronAuth: mockVerifyCronAuth, @@ -58,10 +51,6 @@ vi.mock('@/background/schedule-execution', () => ({ vi.mock('@/lib/core/config/feature-flags', () => mockFeatureFlags) -vi.mock('@/lib/core/utils/request', () => ({ - generateRequestId: vi.fn().mockReturnValue('test-request-id'), -})) - vi.mock('@/lib/core/async-jobs', () => ({ getJobQueue: vi.fn().mockResolvedValue({ enqueue: mockEnqueue, @@ -72,12 +61,7 @@ vi.mock('@/lib/core/async-jobs', () => ({ shouldExecuteInline: vi.fn().mockReturnValue(false), })) -vi.mock('@/lib/workflows/utils', () => ({ - getWorkflowById: vi.fn().mockResolvedValue({ - id: 'workflow-1', - workspaceId: 'workspace-1', - }), -})) +vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) vi.mock('drizzle-orm', () => ({ and: vi.fn((...conditions: unknown[]) => ({ type: 'and', conditions })), @@ -92,9 +76,7 @@ vi.mock('drizzle-orm', () => ({ })) vi.mock('@sim/db', () => ({ - db: { - update: mockDbUpdate, - }, + ...dbChainMock, workflowSchedule: { id: 'id', workflowId: 'workflowId', @@ -120,7 +102,7 @@ vi.mock('@sim/db', () => ({ }, })) -vi.mock('@/lib/core/utils/uuid', () => ({ +vi.mock('@sim/utils/id', () => ({ generateId: vi.fn(() => 'schedule-execution-1'), generateShortId: vi.fn(() => 'mock-short-id'), isValidUuid: vi.fn((v: string) => @@ -184,15 +166,21 @@ function createMockRequest(): NextRequest { describe('Scheduled Workflow Execution API Route', () => { beforeEach(() => { vi.clearAllMocks() + resetDbChainMock() + requestUtilsMockFns.mockGenerateRequestId.mockReturnValue('test-request-id') + workflowsUtilsMockFns.mockGetWorkflowById.mockResolvedValue({ + id: 'workflow-1', + workspaceId: 'workspace-1', + }) mockFeatureFlags.isTriggerDevEnabled = false mockFeatureFlags.isHosted = false mockFeatureFlags.isProd = false mockFeatureFlags.isDev = true - mockDbReturning.mockReturnValue([]) + dbChainMockFns.returning.mockReturnValue([]) }) it('should execute scheduled workflows with Trigger.dev disabled', async () => { - mockDbReturning.mockReturnValueOnce(SINGLE_SCHEDULE).mockReturnValueOnce([]) + dbChainMockFns.returning.mockReturnValueOnce(SINGLE_SCHEDULE).mockReturnValueOnce([]) const response = await GET(createMockRequest()) @@ -205,7 +193,7 @@ describe('Scheduled Workflow Execution API Route', () => { it('should queue schedules to Trigger.dev when enabled', async () => { mockFeatureFlags.isTriggerDevEnabled = true - mockDbReturning.mockReturnValueOnce(SINGLE_SCHEDULE).mockReturnValueOnce([]) + dbChainMockFns.returning.mockReturnValueOnce(SINGLE_SCHEDULE).mockReturnValueOnce([]) const response = await GET(createMockRequest()) @@ -216,7 +204,7 @@ describe('Scheduled Workflow Execution API Route', () => { }) it('should handle case with no due schedules', async () => { - mockDbReturning.mockReturnValueOnce([]).mockReturnValueOnce([]) + dbChainMockFns.returning.mockReturnValueOnce([]).mockReturnValueOnce([]) const response = await GET(createMockRequest()) @@ -227,7 +215,7 @@ describe('Scheduled Workflow Execution API Route', () => { }) it('should execute multiple schedules in parallel', async () => { - mockDbReturning.mockReturnValueOnce(MULTIPLE_SCHEDULES).mockReturnValueOnce([]) + dbChainMockFns.returning.mockReturnValueOnce(MULTIPLE_SCHEDULES).mockReturnValueOnce([]) const response = await GET(createMockRequest()) @@ -237,7 +225,7 @@ describe('Scheduled Workflow Execution API Route', () => { }) it('should execute mothership jobs inline', async () => { - mockDbReturning.mockReturnValueOnce([]).mockReturnValueOnce(SINGLE_JOB) + dbChainMockFns.returning.mockReturnValueOnce([]).mockReturnValueOnce(SINGLE_JOB) const response = await GET(createMockRequest()) @@ -253,7 +241,7 @@ describe('Scheduled Workflow Execution API Route', () => { }) it('should enqueue schedule with correlation metadata via job queue', async () => { - mockDbReturning.mockReturnValueOnce(SINGLE_SCHEDULE).mockReturnValueOnce([]) + dbChainMockFns.returning.mockReturnValueOnce(SINGLE_SCHEDULE).mockReturnValueOnce([]) const response = await GET(createMockRequest()) diff --git a/apps/sim/app/api/schedules/execute/route.ts b/apps/sim/app/api/schedules/execute/route.ts index 719f47df52e..d4c17f95a62 100644 --- a/apps/sim/app/api/schedules/execute/route.ts +++ b/apps/sim/app/api/schedules/execute/route.ts @@ -1,12 +1,12 @@ import { db, workflowDeploymentVersion, workflowSchedule } from '@sim/db' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' import { and, eq, isNull, lt, lte, ne, not, or, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { verifyCronAuth } from '@/lib/auth/internal' import { getJobQueue, shouldExecuteInline } from '@/lib/core/async-jobs' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' -import { generateId } from '@/lib/core/utils/uuid' import { executeJobInline, executeScheduleJob, diff --git a/apps/sim/app/api/schedules/route.test.ts b/apps/sim/app/api/schedules/route.test.ts index 434bc2fa8d0..7a0b1828db2 100644 --- a/apps/sim/app/api/schedules/route.test.ts +++ b/apps/sim/app/api/schedules/route.test.ts @@ -3,38 +3,11 @@ * * @vitest-environment node */ -import { databaseMock, loggerMock, requestUtilsMock } from '@sim/testing' +import { authMockFns, databaseMock, workflowsUtilsMock, workflowsUtilsMockFns } from '@sim/testing' import { NextRequest } from 'next/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -const { mockGetSession, mockAuthorizeWorkflowByWorkspacePermission } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), - mockAuthorizeWorkflowByWorkspacePermission: vi.fn(), -})) - -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - -vi.mock('@/lib/workflows/utils', () => ({ - authorizeWorkflowByWorkspacePermission: mockAuthorizeWorkflowByWorkspacePermission, -})) - -vi.mock('@sim/db', () => databaseMock) - -vi.mock('@sim/db/schema', () => ({ - workflow: { id: 'id', userId: 'userId', workspaceId: 'workspaceId' }, - workflowSchedule: { - workflowId: 'workflowId', - blockId: 'blockId', - deploymentVersionId: 'deploymentVersionId', - }, - workflowDeploymentVersion: { - id: 'id', - workflowId: 'workflowId', - isActive: 'isActive', - }, -})) +vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) vi.mock('drizzle-orm', () => ({ eq: vi.fn(), @@ -43,10 +16,6 @@ vi.mock('drizzle-orm', () => ({ isNull: vi.fn(), })) -vi.mock('@/lib/core/utils/request', () => requestUtilsMock) - -vi.mock('@sim/logger', () => loggerMock) - import { GET } from '@/app/api/schedules/route' function createRequest(url: string): NextRequest { @@ -74,8 +43,8 @@ function mockDbChain(results: any[]) { describe('Schedule GET API', () => { beforeEach(() => { vi.clearAllMocks() - mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ allowed: true, status: 200, workflow: { id: 'wf-1', workspaceId: 'ws-1' }, @@ -120,7 +89,7 @@ describe('Schedule GET API', () => { }) it('requires authentication', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const res = await GET(createRequest('http://test/api/schedules?workflowId=wf-1')) @@ -134,7 +103,7 @@ describe('Schedule GET API', () => { }) it('returns 404 for non-existent workflow', async () => { - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ allowed: false, status: 404, message: 'Workflow not found', @@ -149,7 +118,7 @@ describe('Schedule GET API', () => { }) it('denies access for unauthorized user', async () => { - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ allowed: false, status: 403, message: 'Unauthorized: Access denied to read this workflow', diff --git a/apps/sim/app/api/schedules/route.ts b/apps/sim/app/api/schedules/route.ts index a2f14f109a8..2f12c23863c 100644 --- a/apps/sim/app/api/schedules/route.ts +++ b/apps/sim/app/api/schedules/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { workflow, workflowDeploymentVersion, workflowSchedule } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, isNull, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { generateId } from '@/lib/core/utils/uuid' import { captureServerEvent } from '@/lib/posthog/server' import { validateCronExpression } from '@/lib/workflows/schedules/utils' import { authorizeWorkflowByWorkspacePermission } from '@/lib/workflows/utils' diff --git a/apps/sim/app/api/stars/route.ts b/apps/sim/app/api/stars/route.ts index 97106b13842..9f10c5db9f6 100644 --- a/apps/sim/app/api/stars/route.ts +++ b/apps/sim/app/api/stars/route.ts @@ -1,6 +1,9 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { env } from '@/lib/core/config/env' +const logger = createLogger('StarsRoute') + function formatStarCount(num: number): string { if (num < 1000) return String(num) const formatted = (Math.round(num / 100) / 10).toFixed(1) @@ -22,14 +25,14 @@ export async function GET() { }) if (!response.ok) { - console.warn('GitHub API request failed:', response.status) + logger.warn('GitHub API request failed:', response.status) return NextResponse.json({ stars: formatStarCount(19400) }) } const data = await response.json() return NextResponse.json({ stars: formatStarCount(Number(data?.stargazers_count ?? 19400)) }) } catch (error) { - console.warn('Error fetching GitHub stars:', error) + logger.warn('Error fetching GitHub stars:', error) return NextResponse.json({ stars: formatStarCount(19400) }) } } diff --git a/apps/sim/app/api/superuser/import-workflow/route.ts b/apps/sim/app/api/superuser/import-workflow/route.ts index 960723d31ed..cbdb7e7d2cd 100644 --- a/apps/sim/app/api/superuser/import-workflow/route.ts +++ b/apps/sim/app/api/superuser/import-workflow/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { copilotChats, workflow, workspace } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, isNull } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' -import { generateId } from '@/lib/core/utils/uuid' import { verifyEffectiveSuperUser } from '@/lib/templates/permissions' import { parseWorkflowJson } from '@/lib/workflows/operations/import-export' import { diff --git a/apps/sim/app/api/table/[tableId]/import-csv/route.test.ts b/apps/sim/app/api/table/[tableId]/import-csv/route.test.ts index 9e2f31edc9a..fc9a24c3072 100644 --- a/apps/sim/app/api/table/[tableId]/import-csv/route.test.ts +++ b/apps/sim/app/api/table/[tableId]/import-csv/route.test.ts @@ -1,32 +1,18 @@ /** * @vitest-environment node */ +import { hybridAuthMockFns } from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' import type { TableDefinition } from '@/lib/table' -const { - mockCheckSessionOrInternalAuth, - mockCheckAccess, - mockBatchInsertRows, - mockReplaceTableRows, -} = vi.hoisted(() => ({ - mockCheckSessionOrInternalAuth: vi.fn(), +const { mockCheckAccess, mockBatchInsertRows, mockReplaceTableRows } = vi.hoisted(() => ({ mockCheckAccess: vi.fn(), mockBatchInsertRows: vi.fn(), mockReplaceTableRows: vi.fn(), })) -vi.mock('@/lib/auth/hybrid', () => ({ - AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, - checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, -})) - -vi.mock('@/lib/core/utils/request', () => ({ - generateRequestId: vi.fn().mockReturnValue('req-test-123'), -})) - -vi.mock('@/lib/core/utils/uuid', () => ({ +vi.mock('@sim/utils/id', () => ({ generateId: vi.fn().mockReturnValue('deadbeefcafef00d'), generateShortId: vi.fn().mockReturnValue('short-id'), })) @@ -118,7 +104,7 @@ async function callPost(form: FormData, { tableId }: { tableId: string } = { tab describe('POST /api/table/[tableId]/import-csv', () => { beforeEach(() => { vi.clearAllMocks() - mockCheckSessionOrInternalAuth.mockResolvedValue({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ success: true, userId: 'user-1', authType: 'session', @@ -131,7 +117,7 @@ describe('POST /api/table/[tableId]/import-csv', () => { }) it('returns 401 when the user is not authenticated', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: false, error: 'Authentication required', }) diff --git a/apps/sim/app/api/table/[tableId]/import-csv/route.ts b/apps/sim/app/api/table/[tableId]/import-csv/route.ts index 3ad21468fec..20ef2dac2c4 100644 --- a/apps/sim/app/api/table/[tableId]/import-csv/route.ts +++ b/apps/sim/app/api/table/[tableId]/import-csv/route.ts @@ -1,9 +1,9 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' -import { generateId } from '@/lib/core/utils/uuid' import { batchInsertRows, buildAutoMapping, diff --git a/apps/sim/app/api/table/[tableId]/rows/[rowId]/route.ts b/apps/sim/app/api/table/[tableId]/rows/[rowId]/route.ts index 66790f68af7..6ed71100944 100644 --- a/apps/sim/app/api/table/[tableId]/rows/[rowId]/route.ts +++ b/apps/sim/app/api/table/[tableId]/rows/[rowId]/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { userTableRows } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' import type { RowData } from '@/lib/table' import { deleteRow, updateRow } from '@/lib/table' diff --git a/apps/sim/app/api/table/[tableId]/rows/route.ts b/apps/sim/app/api/table/[tableId]/rows/route.ts index 24def621927..762cc84ef7b 100644 --- a/apps/sim/app/api/table/[tableId]/rows/route.ts +++ b/apps/sim/app/api/table/[tableId]/rows/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { userTableRows } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { and, eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' import type { Filter, RowData, Sort, TableSchema } from '@/lib/table' import { diff --git a/apps/sim/app/api/table/[tableId]/rows/upsert/route.ts b/apps/sim/app/api/table/[tableId]/rows/upsert/route.ts index de510c7fdd9..174bb71cea7 100644 --- a/apps/sim/app/api/table/[tableId]/rows/upsert/route.ts +++ b/apps/sim/app/api/table/[tableId]/rows/upsert/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' import type { RowData } from '@/lib/table' import { upsertRow } from '@/lib/table' diff --git a/apps/sim/app/api/table/import-csv/route.ts b/apps/sim/app/api/table/import-csv/route.ts index 0fe52910fbd..3539cdeafb9 100644 --- a/apps/sim/app/api/table/import-csv/route.ts +++ b/apps/sim/app/api/table/import-csv/route.ts @@ -1,9 +1,9 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' -import { generateId } from '@/lib/core/utils/uuid' import { batchInsertRows, CSV_MAX_BATCH_SIZE, diff --git a/apps/sim/app/api/templates/[id]/star/route.ts b/apps/sim/app/api/templates/[id]/star/route.ts index c57ba28eaf9..8cc52518da4 100644 --- a/apps/sim/app/api/templates/[id]/star/route.ts +++ b/apps/sim/app/api/templates/[id]/star/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { templateStars, templates } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { generateId } from '@/lib/core/utils/uuid' const logger = createLogger('TemplateStarAPI') diff --git a/apps/sim/app/api/templates/[id]/use/route.ts b/apps/sim/app/api/templates/[id]/use/route.ts index a4c797381c1..d18187df7ee 100644 --- a/apps/sim/app/api/templates/[id]/use/route.ts +++ b/apps/sim/app/api/templates/[id]/use/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { templates, workflow, workflowDeploymentVersion } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' import { getInternalApiBaseUrl } from '@/lib/core/utils/urls' -import { generateId } from '@/lib/core/utils/uuid' import { canAccessTemplate, verifyTemplateOwnership } from '@/lib/templates/permissions' import { type RegenerateStateInput, diff --git a/apps/sim/app/api/templates/approved/sanitized/route.ts b/apps/sim/app/api/templates/approved/sanitized/route.ts index 6d481e8a704..67755e77c7f 100644 --- a/apps/sim/app/api/templates/approved/sanitized/route.ts +++ b/apps/sim/app/api/templates/approved/sanitized/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { templates } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalApiKey } from '@/lib/copilot/request/http' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' import { sanitizeForCopilot } from '@/lib/workflows/sanitization/json-sanitizer' diff --git a/apps/sim/app/api/templates/route.ts b/apps/sim/app/api/templates/route.ts index 0a9f9d02b72..a1b22b15de6 100644 --- a/apps/sim/app/api/templates/route.ts +++ b/apps/sim/app/api/templates/route.ts @@ -7,13 +7,13 @@ import { workflowDeploymentVersion, } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, desc, eq, ilike, or, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { generateId } from '@/lib/core/utils/uuid' import { canAccessTemplate, verifyEffectiveSuperUser } from '@/lib/templates/permissions' import { extractRequiredCredentials, diff --git a/apps/sim/app/api/tools/a2a/send-message/route.ts b/apps/sim/app/api/tools/a2a/send-message/route.ts index 0f4fa0445e2..eb4bc5b056e 100644 --- a/apps/sim/app/api/tools/a2a/send-message/route.ts +++ b/apps/sim/app/api/tools/a2a/send-message/route.ts @@ -1,13 +1,13 @@ import type { DataPart, FilePart, Message, Part, Task, TextPart } from '@a2a-js/sdk' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { createA2AClient, extractTextContent, isTerminalState } from '@/lib/a2a/utils' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' import { validateUrlWithDNS } from '@/lib/core/security/input-validation.server' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' -import { generateId } from '@/lib/core/utils/uuid' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/asana/create-task/route.ts b/apps/sim/app/api/tools/asana/create-task/route.ts index ddcdacdb5cc..4212cb2e547 100644 --- a/apps/sim/app/api/tools/asana/create-task/route.ts +++ b/apps/sim/app/api/tools/asana/create-task/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateAlphanumericId } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/asana/update-task/route.ts b/apps/sim/app/api/tools/asana/update-task/route.ts index 8831ed1d856..5d2b8a15b10 100644 --- a/apps/sim/app/api/tools/asana/update-task/route.ts +++ b/apps/sim/app/api/tools/asana/update-task/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateAlphanumericId } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/cloudwatch/utils.ts b/apps/sim/app/api/tools/cloudwatch/utils.ts index 4c074955298..5d4b4dd9a6a 100644 --- a/apps/sim/app/api/tools/cloudwatch/utils.ts +++ b/apps/sim/app/api/tools/cloudwatch/utils.ts @@ -5,8 +5,8 @@ import { GetQueryResultsCommand, type ResultField, } from '@aws-sdk/client-cloudwatch-logs' +import { sleep } from '@sim/utils/helpers' import { DEFAULT_EXECUTION_TIMEOUT_MS } from '@/lib/core/execution-limits' -import { sleep } from '@/lib/core/utils/helpers' interface AwsCredentials { region: string diff --git a/apps/sim/app/api/tools/crowdstrike/query/route.test.ts b/apps/sim/app/api/tools/crowdstrike/query/route.test.ts index 3e0b88d02de..39aa92dd783 100644 --- a/apps/sim/app/api/tools/crowdstrike/query/route.test.ts +++ b/apps/sim/app/api/tools/crowdstrike/query/route.test.ts @@ -1,17 +1,11 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { createMockRequest, hybridAuthMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { fetchMock, mockCheckInternalAuth } = vi.hoisted(() => ({ +const { fetchMock } = vi.hoisted(() => ({ fetchMock: vi.fn(), - mockCheckInternalAuth: vi.fn(), -})) - -vi.mock('@/lib/auth/hybrid', () => ({ - AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, - checkInternalAuth: mockCheckInternalAuth, })) import { POST } from '@/app/api/tools/crowdstrike/query/route' @@ -72,7 +66,7 @@ describe('CrowdStrike query route', () => { vi.clearAllMocks() vi.stubGlobal('fetch', fetchMock) - mockCheckInternalAuth.mockResolvedValue({ + hybridAuthMockFns.mockCheckInternalAuth.mockResolvedValue({ success: true, userId: 'user-123', authType: 'internal_jwt', diff --git a/apps/sim/app/api/tools/crowdstrike/query/route.ts b/apps/sim/app/api/tools/crowdstrike/query/route.ts index 63ad81e2899..19ee157f3b1 100644 --- a/apps/sim/app/api/tools/crowdstrike/query/route.ts +++ b/apps/sim/app/api/tools/crowdstrike/query/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import type { CrowdStrikeAggregateQuery, CrowdStrikeCloud, diff --git a/apps/sim/app/api/tools/custom/route.test.ts b/apps/sim/app/api/tools/custom/route.test.ts index 3781fc1c169..4414e922777 100644 --- a/apps/sim/app/api/tools/custom/route.test.ts +++ b/apps/sim/app/api/tools/custom/route.test.ts @@ -3,7 +3,15 @@ * * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { + authMockFns, + createMockRequest, + hybridAuthMockFns, + permissionsMock, + permissionsMockFns, + workflowsUtilsMock, + workflowsUtilsMockFns, +} from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' @@ -18,22 +26,8 @@ const { mockSet, mockDelete, mockLimit, - mockCheckSessionOrInternalAuth, - mockGetSession, - mockGetUserEntityPermissions, mockUpsertCustomTools, - mockAuthorizeWorkflowByWorkspacePermission, - mockLogger, } = vi.hoisted(() => { - const logger = { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - trace: vi.fn(), - fatal: vi.fn(), - child: vi.fn(), - } return { mockSelect: vi.fn(), mockFrom: vi.fn(), @@ -45,15 +39,12 @@ const { mockSet: vi.fn(), mockDelete: vi.fn(), mockLimit: vi.fn(), - mockCheckSessionOrInternalAuth: vi.fn(), - mockGetSession: vi.fn(), - mockGetUserEntityPermissions: vi.fn(), mockUpsertCustomTools: vi.fn(), - mockAuthorizeWorkflowByWorkspacePermission: vi.fn(), - mockLogger: logger, } }) +const mockGetUserEntityPermissions = permissionsMockFns.mockGetUserEntityPermissions + const sampleTools = [ { id: 'tool-1', @@ -163,36 +154,7 @@ vi.mock('@sim/db', () => ({ }, })) -vi.mock('@sim/db/schema', () => ({ - customTools: { - id: 'id', - workspaceId: 'workspaceId', - userId: 'userId', - title: 'title', - }, - workflow: { - id: 'id', - workspaceId: 'workspaceId', - userId: 'userId', - }, -})) - -vi.mock('@/lib/auth', () => ({ - getSession: (...args: unknown[]) => mockGetSession(...args), -})) - -vi.mock('@/lib/auth/hybrid', () => ({ - AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, - checkSessionOrInternalAuth: (...args: unknown[]) => mockCheckSessionOrInternalAuth(...args), -})) - -vi.mock('@/lib/workspaces/permissions/utils', () => ({ - getUserEntityPermissions: (...args: unknown[]) => mockGetUserEntityPermissions(...args), -})) - -vi.mock('@sim/logger', () => ({ - createLogger: vi.fn().mockReturnValue(mockLogger), -})) +vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) vi.mock('drizzle-orm', () => ({ eq: vi.fn().mockImplementation((field: unknown, value: unknown) => ({ @@ -217,18 +179,11 @@ vi.mock('drizzle-orm', () => ({ desc: vi.fn().mockImplementation((field: unknown) => ({ field, operator: 'desc' })), })) -vi.mock('@/lib/core/utils/request', () => ({ - generateRequestId: vi.fn().mockReturnValue('test-request-id'), -})) - vi.mock('@/lib/workflows/custom-tools/operations', () => ({ upsertCustomTools: (...args: unknown[]) => mockUpsertCustomTools(...args), })) -vi.mock('@/lib/workflows/utils', () => ({ - authorizeWorkflowByWorkspacePermission: (...args: unknown[]) => - mockAuthorizeWorkflowByWorkspacePermission(...args), -})) +vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) import { DELETE, GET, POST } from '@/app/api/tools/custom/route' @@ -270,15 +225,15 @@ describe('Custom Tools API Routes', () => { mockSet.mockReturnValue({ where: mockWhere }) mockDelete.mockReturnValue({ where: mockWhere }) - mockGetSession.mockResolvedValue(mockSession) - mockCheckSessionOrInternalAuth.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue(mockSession) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ success: true, userId: 'user-123', authType: 'session', }) mockGetUserEntityPermissions.mockResolvedValue('admin') mockUpsertCustomTools.mockResolvedValue(sampleTools) - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ allowed: true, status: 200, workflow: { workspaceId: 'workspace-123' }, @@ -316,7 +271,7 @@ describe('Custom Tools API Routes', () => { 'http://localhost:3000/api/tools/custom?workspaceId=workspace-123' ) - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: false, error: 'Unauthorized', }) @@ -346,7 +301,7 @@ describe('Custom Tools API Routes', () => { */ describe('POST /api/tools/custom', () => { it('should reject unauthorized requests', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: false, error: 'Unauthorized', }) @@ -421,7 +376,7 @@ describe('Custom Tools API Routes', () => { }) it('should prevent unauthorized deletion of user-scoped tool', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, userId: 'user-456', authType: 'session', @@ -441,7 +396,7 @@ describe('Custom Tools API Routes', () => { }) it('should reject unauthorized requests', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: false, error: 'Unauthorized', }) diff --git a/apps/sim/app/api/tools/dynamodb/introspect/route.ts b/apps/sim/app/api/tools/dynamodb/introspect/route.ts index bad2d517ca7..56fdea89898 100644 --- a/apps/sim/app/api/tools/dynamodb/introspect/route.ts +++ b/apps/sim/app/api/tools/dynamodb/introspect/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createRawDynamoDBClient, describeTable, listTables } from '@/app/api/tools/dynamodb/utils' const logger = createLogger('DynamoDBIntrospectAPI') diff --git a/apps/sim/app/api/tools/iam/add-user-to-group/route.ts b/apps/sim/app/api/tools/iam/add-user-to-group/route.ts index 88dc2e67501..5f69ced16de 100644 --- a/apps/sim/app/api/tools/iam/add-user-to-group/route.ts +++ b/apps/sim/app/api/tools/iam/add-user-to-group/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { addUserToGroup, createIAMClient } from '../utils' const logger = createLogger('IAMAddUserToGroupAPI') diff --git a/apps/sim/app/api/tools/iam/attach-role-policy/route.ts b/apps/sim/app/api/tools/iam/attach-role-policy/route.ts index 0360769c098..010b64faf53 100644 --- a/apps/sim/app/api/tools/iam/attach-role-policy/route.ts +++ b/apps/sim/app/api/tools/iam/attach-role-policy/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { attachRolePolicy, createIAMClient } from '../utils' const logger = createLogger('IAMAttachRolePolicyAPI') diff --git a/apps/sim/app/api/tools/iam/attach-user-policy/route.ts b/apps/sim/app/api/tools/iam/attach-user-policy/route.ts index 6ae4037ceb6..636d5e613d5 100644 --- a/apps/sim/app/api/tools/iam/attach-user-policy/route.ts +++ b/apps/sim/app/api/tools/iam/attach-user-policy/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { attachUserPolicy, createIAMClient } from '../utils' const logger = createLogger('IAMAttachUserPolicyAPI') diff --git a/apps/sim/app/api/tools/iam/create-access-key/route.ts b/apps/sim/app/api/tools/iam/create-access-key/route.ts index f8a82d794b7..6b247a12d78 100644 --- a/apps/sim/app/api/tools/iam/create-access-key/route.ts +++ b/apps/sim/app/api/tools/iam/create-access-key/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createAccessKey, createIAMClient } from '../utils' const logger = createLogger('IAMCreateAccessKeyAPI') diff --git a/apps/sim/app/api/tools/iam/create-role/route.ts b/apps/sim/app/api/tools/iam/create-role/route.ts index 0cd765c2012..a58f136ede9 100644 --- a/apps/sim/app/api/tools/iam/create-role/route.ts +++ b/apps/sim/app/api/tools/iam/create-role/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createIAMClient, createRole } from '../utils' const logger = createLogger('IAMCreateRoleAPI') diff --git a/apps/sim/app/api/tools/iam/create-user/route.ts b/apps/sim/app/api/tools/iam/create-user/route.ts index 42db82145fe..941972be5c5 100644 --- a/apps/sim/app/api/tools/iam/create-user/route.ts +++ b/apps/sim/app/api/tools/iam/create-user/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createIAMClient, createUser } from '../utils' const logger = createLogger('IAMCreateUserAPI') diff --git a/apps/sim/app/api/tools/iam/delete-access-key/route.ts b/apps/sim/app/api/tools/iam/delete-access-key/route.ts index 65486f5e5fa..50801563691 100644 --- a/apps/sim/app/api/tools/iam/delete-access-key/route.ts +++ b/apps/sim/app/api/tools/iam/delete-access-key/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createIAMClient, deleteAccessKey } from '../utils' const logger = createLogger('IAMDeleteAccessKeyAPI') diff --git a/apps/sim/app/api/tools/iam/delete-role/route.ts b/apps/sim/app/api/tools/iam/delete-role/route.ts index 488bb300483..4c0361868a1 100644 --- a/apps/sim/app/api/tools/iam/delete-role/route.ts +++ b/apps/sim/app/api/tools/iam/delete-role/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createIAMClient, deleteRole } from '../utils' const logger = createLogger('IAMDeleteRoleAPI') diff --git a/apps/sim/app/api/tools/iam/delete-user/route.ts b/apps/sim/app/api/tools/iam/delete-user/route.ts index 5d6d9e980c9..a9e484ace56 100644 --- a/apps/sim/app/api/tools/iam/delete-user/route.ts +++ b/apps/sim/app/api/tools/iam/delete-user/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createIAMClient, deleteUser } from '../utils' const logger = createLogger('IAMDeleteUserAPI') diff --git a/apps/sim/app/api/tools/iam/detach-role-policy/route.ts b/apps/sim/app/api/tools/iam/detach-role-policy/route.ts index 3e4af87dc98..e7bd77c1811 100644 --- a/apps/sim/app/api/tools/iam/detach-role-policy/route.ts +++ b/apps/sim/app/api/tools/iam/detach-role-policy/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createIAMClient, detachRolePolicy } from '../utils' const logger = createLogger('IAMDetachRolePolicyAPI') diff --git a/apps/sim/app/api/tools/iam/detach-user-policy/route.ts b/apps/sim/app/api/tools/iam/detach-user-policy/route.ts index 9972a2b78bb..5bbf0f6956b 100644 --- a/apps/sim/app/api/tools/iam/detach-user-policy/route.ts +++ b/apps/sim/app/api/tools/iam/detach-user-policy/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createIAMClient, detachUserPolicy } from '../utils' const logger = createLogger('IAMDetachUserPolicyAPI') diff --git a/apps/sim/app/api/tools/iam/get-role/route.ts b/apps/sim/app/api/tools/iam/get-role/route.ts index a7a0f20273b..3086f3f00fd 100644 --- a/apps/sim/app/api/tools/iam/get-role/route.ts +++ b/apps/sim/app/api/tools/iam/get-role/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createIAMClient, getRole } from '../utils' const logger = createLogger('IAMGetRoleAPI') diff --git a/apps/sim/app/api/tools/iam/get-user/route.ts b/apps/sim/app/api/tools/iam/get-user/route.ts index 148baf28834..22964c75a26 100644 --- a/apps/sim/app/api/tools/iam/get-user/route.ts +++ b/apps/sim/app/api/tools/iam/get-user/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createIAMClient, getUser } from '../utils' const logger = createLogger('IAMGetUserAPI') diff --git a/apps/sim/app/api/tools/iam/list-groups/route.ts b/apps/sim/app/api/tools/iam/list-groups/route.ts index 6512f71f84a..0d37da698c9 100644 --- a/apps/sim/app/api/tools/iam/list-groups/route.ts +++ b/apps/sim/app/api/tools/iam/list-groups/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createIAMClient, listGroups } from '../utils' const logger = createLogger('IAMListGroupsAPI') diff --git a/apps/sim/app/api/tools/iam/list-policies/route.ts b/apps/sim/app/api/tools/iam/list-policies/route.ts index ecd2c892c94..5e361bd66b8 100644 --- a/apps/sim/app/api/tools/iam/list-policies/route.ts +++ b/apps/sim/app/api/tools/iam/list-policies/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createIAMClient, listPolicies } from '../utils' const logger = createLogger('IAMListPoliciesAPI') diff --git a/apps/sim/app/api/tools/iam/list-roles/route.ts b/apps/sim/app/api/tools/iam/list-roles/route.ts index fac48e83475..a1b9a5676f7 100644 --- a/apps/sim/app/api/tools/iam/list-roles/route.ts +++ b/apps/sim/app/api/tools/iam/list-roles/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createIAMClient, listRoles } from '../utils' const logger = createLogger('IAMListRolesAPI') diff --git a/apps/sim/app/api/tools/iam/list-users/route.ts b/apps/sim/app/api/tools/iam/list-users/route.ts index 17668a8737c..13b99298574 100644 --- a/apps/sim/app/api/tools/iam/list-users/route.ts +++ b/apps/sim/app/api/tools/iam/list-users/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createIAMClient, listUsers } from '../utils' const logger = createLogger('IAMListUsersAPI') diff --git a/apps/sim/app/api/tools/iam/remove-user-from-group/route.ts b/apps/sim/app/api/tools/iam/remove-user-from-group/route.ts index d50c699e92e..5f3a537d3d0 100644 --- a/apps/sim/app/api/tools/iam/remove-user-from-group/route.ts +++ b/apps/sim/app/api/tools/iam/remove-user-from-group/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createIAMClient, removeUserFromGroup } from '../utils' const logger = createLogger('IAMRemoveUserFromGroupAPI') diff --git a/apps/sim/app/api/tools/image/route.ts b/apps/sim/app/api/tools/image/route.ts index 9fba35ad721..c83ef413d09 100644 --- a/apps/sim/app/api/tools/image/route.ts +++ b/apps/sim/app/api/tools/image/route.ts @@ -1,11 +1,11 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { secureFetchWithPinnedIP, validateUrlWithDNS, } from '@/lib/core/security/input-validation.server' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' const logger = createLogger('ImageProxyAPI') diff --git a/apps/sim/app/api/tools/jira/update/route.ts b/apps/sim/app/api/tools/jira/update/route.ts index 74ccc0f42a9..86986a7ccbc 100644 --- a/apps/sim/app/api/tools/jira/update/route.ts +++ b/apps/sim/app/api/tools/jira/update/route.ts @@ -1,9 +1,9 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage, toAdf } from '@/tools/jira/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/jira/write/route.ts b/apps/sim/app/api/tools/jira/write/route.ts index b54d24993b2..5cbd748fec7 100644 --- a/apps/sim/app/api/tools/jira/write/route.ts +++ b/apps/sim/app/api/tools/jira/write/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage, toAdf } from '@/tools/jira/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/jsm/approvals/route.ts b/apps/sim/app/api/tools/jsm/approvals/route.ts index 387f1b6dc83..aab4fd51db4 100644 --- a/apps/sim/app/api/tools/jsm/approvals/route.ts +++ b/apps/sim/app/api/tools/jsm/approvals/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { @@ -7,7 +8,6 @@ import { validateJiraCloudId, validateJiraIssueKey, } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/comment/route.ts b/apps/sim/app/api/tools/jsm/comment/route.ts index 7a1758c5125..3a651d36504 100644 --- a/apps/sim/app/api/tools/jsm/comment/route.ts +++ b/apps/sim/app/api/tools/jsm/comment/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/comments/route.ts b/apps/sim/app/api/tools/jsm/comments/route.ts index 43bbd8eefcf..7ecfce61857 100644 --- a/apps/sim/app/api/tools/jsm/comments/route.ts +++ b/apps/sim/app/api/tools/jsm/comments/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/customers/route.ts b/apps/sim/app/api/tools/jsm/customers/route.ts index 67cff52bd04..c60b2176ed5 100644 --- a/apps/sim/app/api/tools/jsm/customers/route.ts +++ b/apps/sim/app/api/tools/jsm/customers/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/forms/answers/route.ts b/apps/sim/app/api/tools/jsm/forms/answers/route.ts index 5d529f0c076..dbcb90d39b4 100644 --- a/apps/sim/app/api/tools/jsm/forms/answers/route.ts +++ b/apps/sim/app/api/tools/jsm/forms/answers/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/forms/attach/route.ts b/apps/sim/app/api/tools/jsm/forms/attach/route.ts index 2e33cc7afe2..4cea4451ca8 100644 --- a/apps/sim/app/api/tools/jsm/forms/attach/route.ts +++ b/apps/sim/app/api/tools/jsm/forms/attach/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/forms/copy/route.ts b/apps/sim/app/api/tools/jsm/forms/copy/route.ts index 0f65f9de63a..534b6d2e78c 100644 --- a/apps/sim/app/api/tools/jsm/forms/copy/route.ts +++ b/apps/sim/app/api/tools/jsm/forms/copy/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/forms/delete/route.ts b/apps/sim/app/api/tools/jsm/forms/delete/route.ts index 28ff73a017d..6849cfbff11 100644 --- a/apps/sim/app/api/tools/jsm/forms/delete/route.ts +++ b/apps/sim/app/api/tools/jsm/forms/delete/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/forms/externalise/route.ts b/apps/sim/app/api/tools/jsm/forms/externalise/route.ts index 5922bfc68b3..a9f9ee9cb77 100644 --- a/apps/sim/app/api/tools/jsm/forms/externalise/route.ts +++ b/apps/sim/app/api/tools/jsm/forms/externalise/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/forms/get/route.ts b/apps/sim/app/api/tools/jsm/forms/get/route.ts index 35ca42a6687..4582e626526 100644 --- a/apps/sim/app/api/tools/jsm/forms/get/route.ts +++ b/apps/sim/app/api/tools/jsm/forms/get/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/forms/internalise/route.ts b/apps/sim/app/api/tools/jsm/forms/internalise/route.ts index 8d8aae7201b..755686c36ea 100644 --- a/apps/sim/app/api/tools/jsm/forms/internalise/route.ts +++ b/apps/sim/app/api/tools/jsm/forms/internalise/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/forms/issue/route.ts b/apps/sim/app/api/tools/jsm/forms/issue/route.ts index 0d504359db0..754d209ea39 100644 --- a/apps/sim/app/api/tools/jsm/forms/issue/route.ts +++ b/apps/sim/app/api/tools/jsm/forms/issue/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/forms/reopen/route.ts b/apps/sim/app/api/tools/jsm/forms/reopen/route.ts index 333d9c347b4..7f734bc5f4e 100644 --- a/apps/sim/app/api/tools/jsm/forms/reopen/route.ts +++ b/apps/sim/app/api/tools/jsm/forms/reopen/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/forms/save/route.ts b/apps/sim/app/api/tools/jsm/forms/save/route.ts index 7869ac80246..82a3a94403d 100644 --- a/apps/sim/app/api/tools/jsm/forms/save/route.ts +++ b/apps/sim/app/api/tools/jsm/forms/save/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/forms/structure/route.ts b/apps/sim/app/api/tools/jsm/forms/structure/route.ts index 51fdb78e4f6..56497ef9511 100644 --- a/apps/sim/app/api/tools/jsm/forms/structure/route.ts +++ b/apps/sim/app/api/tools/jsm/forms/structure/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/forms/submit/route.ts b/apps/sim/app/api/tools/jsm/forms/submit/route.ts index d2c92b6e600..a54a8f0080b 100644 --- a/apps/sim/app/api/tools/jsm/forms/submit/route.ts +++ b/apps/sim/app/api/tools/jsm/forms/submit/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/forms/templates/route.ts b/apps/sim/app/api/tools/jsm/forms/templates/route.ts index a79703b703e..1021f5d0f6e 100644 --- a/apps/sim/app/api/tools/jsm/forms/templates/route.ts +++ b/apps/sim/app/api/tools/jsm/forms/templates/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/organization/route.ts b/apps/sim/app/api/tools/jsm/organization/route.ts index a2bfd2ca40d..54e52091360 100644 --- a/apps/sim/app/api/tools/jsm/organization/route.ts +++ b/apps/sim/app/api/tools/jsm/organization/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { @@ -6,7 +7,6 @@ import { validateEnum, validateJiraCloudId, } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/organizations/route.ts b/apps/sim/app/api/tools/jsm/organizations/route.ts index b963de9f55e..2677ecb840a 100644 --- a/apps/sim/app/api/tools/jsm/organizations/route.ts +++ b/apps/sim/app/api/tools/jsm/organizations/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/participants/route.ts b/apps/sim/app/api/tools/jsm/participants/route.ts index 6188181400e..004d3225e61 100644 --- a/apps/sim/app/api/tools/jsm/participants/route.ts +++ b/apps/sim/app/api/tools/jsm/participants/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { @@ -6,7 +7,6 @@ import { validateJiraCloudId, validateJiraIssueKey, } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/queues/route.ts b/apps/sim/app/api/tools/jsm/queues/route.ts index b70f8924cd1..c700415a0c9 100644 --- a/apps/sim/app/api/tools/jsm/queues/route.ts +++ b/apps/sim/app/api/tools/jsm/queues/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/request/route.ts b/apps/sim/app/api/tools/jsm/request/route.ts index 924d9fa6cb3..bd28305b3ff 100644 --- a/apps/sim/app/api/tools/jsm/request/route.ts +++ b/apps/sim/app/api/tools/jsm/request/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { @@ -6,7 +7,6 @@ import { validateJiraCloudId, validateJiraIssueKey, } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/requests/route.ts b/apps/sim/app/api/tools/jsm/requests/route.ts index 511e482490b..beff44d69ec 100644 --- a/apps/sim/app/api/tools/jsm/requests/route.ts +++ b/apps/sim/app/api/tools/jsm/requests/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { @@ -6,7 +7,6 @@ import { validateEnum, validateJiraCloudId, } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/requesttypefields/route.ts b/apps/sim/app/api/tools/jsm/requesttypefields/route.ts index da14d3e3dc1..aba218ccfb0 100644 --- a/apps/sim/app/api/tools/jsm/requesttypefields/route.ts +++ b/apps/sim/app/api/tools/jsm/requesttypefields/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/requesttypes/route.ts b/apps/sim/app/api/tools/jsm/requesttypes/route.ts index 2f2513eabfe..3f90b906e59 100644 --- a/apps/sim/app/api/tools/jsm/requesttypes/route.ts +++ b/apps/sim/app/api/tools/jsm/requesttypes/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/servicedesks/route.ts b/apps/sim/app/api/tools/jsm/servicedesks/route.ts index 74d60f9623f..209f06e9e8c 100644 --- a/apps/sim/app/api/tools/jsm/servicedesks/route.ts +++ b/apps/sim/app/api/tools/jsm/servicedesks/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateJiraCloudId } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/sla/route.ts b/apps/sim/app/api/tools/jsm/sla/route.ts index 7484a9d8527..083034f5b14 100644 --- a/apps/sim/app/api/tools/jsm/sla/route.ts +++ b/apps/sim/app/api/tools/jsm/sla/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/transition/route.ts b/apps/sim/app/api/tools/jsm/transition/route.ts index 39ac559c8e7..75231994c71 100644 --- a/apps/sim/app/api/tools/jsm/transition/route.ts +++ b/apps/sim/app/api/tools/jsm/transition/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { @@ -6,7 +7,6 @@ import { validateJiraCloudId, validateJiraIssueKey, } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/jsm/transitions/route.ts b/apps/sim/app/api/tools/jsm/transitions/route.ts index ed017b0a5c4..c736eabc64d 100644 --- a/apps/sim/app/api/tools/jsm/transitions/route.ts +++ b/apps/sim/app/api/tools/jsm/transitions/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils' import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' diff --git a/apps/sim/app/api/tools/microsoft-teams/channels/route.ts b/apps/sim/app/api/tools/microsoft-teams/channels/route.ts index 5d2ca796499..f985ac00ea9 100644 --- a/apps/sim/app/api/tools/microsoft-teams/channels/route.ts +++ b/apps/sim/app/api/tools/microsoft-teams/channels/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/microsoft-teams/chats/route.ts b/apps/sim/app/api/tools/microsoft-teams/chats/route.ts index 26ad9f8b33c..718d55ca999 100644 --- a/apps/sim/app/api/tools/microsoft-teams/chats/route.ts +++ b/apps/sim/app/api/tools/microsoft-teams/chats/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/microsoft-teams/teams/route.ts b/apps/sim/app/api/tools/microsoft-teams/teams/route.ts index 7045af36358..787f3b9649f 100644 --- a/apps/sim/app/api/tools/microsoft-teams/teams/route.ts +++ b/apps/sim/app/api/tools/microsoft-teams/teams/route.ts @@ -1,7 +1,7 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' diff --git a/apps/sim/app/api/tools/mongodb/delete/route.ts b/apps/sim/app/api/tools/mongodb/delete/route.ts index 5325aa57446..6202e0e6e5c 100644 --- a/apps/sim/app/api/tools/mongodb/delete/route.ts +++ b/apps/sim/app/api/tools/mongodb/delete/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createMongoDBConnection, sanitizeCollectionName, validateFilter } from '../utils' const logger = createLogger('MongoDBDeleteAPI') diff --git a/apps/sim/app/api/tools/mongodb/execute/route.ts b/apps/sim/app/api/tools/mongodb/execute/route.ts index 9ae06a07871..54c1289af8f 100644 --- a/apps/sim/app/api/tools/mongodb/execute/route.ts +++ b/apps/sim/app/api/tools/mongodb/execute/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createMongoDBConnection, sanitizeCollectionName, validatePipeline } from '../utils' const logger = createLogger('MongoDBExecuteAPI') diff --git a/apps/sim/app/api/tools/mongodb/insert/route.ts b/apps/sim/app/api/tools/mongodb/insert/route.ts index 94957ae5630..9461159d0ea 100644 --- a/apps/sim/app/api/tools/mongodb/insert/route.ts +++ b/apps/sim/app/api/tools/mongodb/insert/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createMongoDBConnection, sanitizeCollectionName } from '../utils' const logger = createLogger('MongoDBInsertAPI') diff --git a/apps/sim/app/api/tools/mongodb/introspect/route.ts b/apps/sim/app/api/tools/mongodb/introspect/route.ts index d6c4b6e5f7f..c22ff8f0c8a 100644 --- a/apps/sim/app/api/tools/mongodb/introspect/route.ts +++ b/apps/sim/app/api/tools/mongodb/introspect/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createMongoDBConnection, executeIntrospect } from '../utils' const logger = createLogger('MongoDBIntrospectAPI') diff --git a/apps/sim/app/api/tools/mongodb/query/route.ts b/apps/sim/app/api/tools/mongodb/query/route.ts index 24829c660b1..4ab2535fb73 100644 --- a/apps/sim/app/api/tools/mongodb/query/route.ts +++ b/apps/sim/app/api/tools/mongodb/query/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createMongoDBConnection, sanitizeCollectionName, validateFilter } from '../utils' const logger = createLogger('MongoDBQueryAPI') diff --git a/apps/sim/app/api/tools/mongodb/update/route.ts b/apps/sim/app/api/tools/mongodb/update/route.ts index 47022203ee7..43eb7931b8f 100644 --- a/apps/sim/app/api/tools/mongodb/update/route.ts +++ b/apps/sim/app/api/tools/mongodb/update/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createMongoDBConnection, sanitizeCollectionName, validateFilter } from '../utils' const logger = createLogger('MongoDBUpdateAPI') diff --git a/apps/sim/app/api/tools/mysql/delete/route.ts b/apps/sim/app/api/tools/mysql/delete/route.ts index b1871cff38a..b4125aa4334 100644 --- a/apps/sim/app/api/tools/mysql/delete/route.ts +++ b/apps/sim/app/api/tools/mysql/delete/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { buildDeleteQuery, createMySQLConnection, executeQuery } from '@/app/api/tools/mysql/utils' const logger = createLogger('MySQLDeleteAPI') diff --git a/apps/sim/app/api/tools/mysql/execute/route.ts b/apps/sim/app/api/tools/mysql/execute/route.ts index 1e8e1685ba3..4b235dd0700 100644 --- a/apps/sim/app/api/tools/mysql/execute/route.ts +++ b/apps/sim/app/api/tools/mysql/execute/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createMySQLConnection, executeQuery, validateQuery } from '@/app/api/tools/mysql/utils' const logger = createLogger('MySQLExecuteAPI') diff --git a/apps/sim/app/api/tools/mysql/insert/route.ts b/apps/sim/app/api/tools/mysql/insert/route.ts index 862a3332c25..bcb1b869806 100644 --- a/apps/sim/app/api/tools/mysql/insert/route.ts +++ b/apps/sim/app/api/tools/mysql/insert/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { buildInsertQuery, createMySQLConnection, executeQuery } from '@/app/api/tools/mysql/utils' const logger = createLogger('MySQLInsertAPI') diff --git a/apps/sim/app/api/tools/mysql/introspect/route.ts b/apps/sim/app/api/tools/mysql/introspect/route.ts index 792b012d126..fda8dc82b68 100644 --- a/apps/sim/app/api/tools/mysql/introspect/route.ts +++ b/apps/sim/app/api/tools/mysql/introspect/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createMySQLConnection, executeIntrospect } from '@/app/api/tools/mysql/utils' const logger = createLogger('MySQLIntrospectAPI') diff --git a/apps/sim/app/api/tools/mysql/query/route.ts b/apps/sim/app/api/tools/mysql/query/route.ts index 5e1105053fc..e6084340ea4 100644 --- a/apps/sim/app/api/tools/mysql/query/route.ts +++ b/apps/sim/app/api/tools/mysql/query/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createMySQLConnection, executeQuery, validateQuery } from '@/app/api/tools/mysql/utils' const logger = createLogger('MySQLQueryAPI') diff --git a/apps/sim/app/api/tools/mysql/update/route.ts b/apps/sim/app/api/tools/mysql/update/route.ts index 0eff371d83c..aa87d5c169b 100644 --- a/apps/sim/app/api/tools/mysql/update/route.ts +++ b/apps/sim/app/api/tools/mysql/update/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { buildUpdateQuery, createMySQLConnection, executeQuery } from '@/app/api/tools/mysql/utils' const logger = createLogger('MySQLUpdateAPI') diff --git a/apps/sim/app/api/tools/neo4j/create/route.ts b/apps/sim/app/api/tools/neo4j/create/route.ts index 83c1b16c6d4..6a4717fde47 100644 --- a/apps/sim/app/api/tools/neo4j/create/route.ts +++ b/apps/sim/app/api/tools/neo4j/create/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { convertNeo4jTypesToJSON, createNeo4jDriver, diff --git a/apps/sim/app/api/tools/neo4j/delete/route.ts b/apps/sim/app/api/tools/neo4j/delete/route.ts index 5b4c42d7da2..a36e9218124 100644 --- a/apps/sim/app/api/tools/neo4j/delete/route.ts +++ b/apps/sim/app/api/tools/neo4j/delete/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createNeo4jDriver, validateCypherQuery } from '@/app/api/tools/neo4j/utils' const logger = createLogger('Neo4jDeleteAPI') diff --git a/apps/sim/app/api/tools/neo4j/execute/route.ts b/apps/sim/app/api/tools/neo4j/execute/route.ts index 70eb498b5b2..456da1f8b3f 100644 --- a/apps/sim/app/api/tools/neo4j/execute/route.ts +++ b/apps/sim/app/api/tools/neo4j/execute/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { convertNeo4jTypesToJSON, createNeo4jDriver, diff --git a/apps/sim/app/api/tools/neo4j/introspect/route.ts b/apps/sim/app/api/tools/neo4j/introspect/route.ts index 36604473fb6..beba37b8c7c 100644 --- a/apps/sim/app/api/tools/neo4j/introspect/route.ts +++ b/apps/sim/app/api/tools/neo4j/introspect/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createNeo4jDriver } from '@/app/api/tools/neo4j/utils' import type { Neo4jNodeSchema, Neo4jRelationshipSchema } from '@/tools/neo4j/types' diff --git a/apps/sim/app/api/tools/neo4j/merge/route.ts b/apps/sim/app/api/tools/neo4j/merge/route.ts index e4865aeabf4..20f3e64c936 100644 --- a/apps/sim/app/api/tools/neo4j/merge/route.ts +++ b/apps/sim/app/api/tools/neo4j/merge/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { convertNeo4jTypesToJSON, createNeo4jDriver, diff --git a/apps/sim/app/api/tools/neo4j/query/route.ts b/apps/sim/app/api/tools/neo4j/query/route.ts index 7c6d8983675..40f8e9d4c45 100644 --- a/apps/sim/app/api/tools/neo4j/query/route.ts +++ b/apps/sim/app/api/tools/neo4j/query/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { convertNeo4jTypesToJSON, createNeo4jDriver, diff --git a/apps/sim/app/api/tools/neo4j/update/route.ts b/apps/sim/app/api/tools/neo4j/update/route.ts index 5d90e17a568..6ce96713f8a 100644 --- a/apps/sim/app/api/tools/neo4j/update/route.ts +++ b/apps/sim/app/api/tools/neo4j/update/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { convertNeo4jTypesToJSON, createNeo4jDriver, diff --git a/apps/sim/app/api/tools/onedrive/files/route.ts b/apps/sim/app/api/tools/onedrive/files/route.ts index 16baea2e23d..d2613a334e3 100644 --- a/apps/sim/app/api/tools/onedrive/files/route.ts +++ b/apps/sim/app/api/tools/onedrive/files/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { account } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation' -import { generateId } from '@/lib/core/utils/uuid' import { refreshAccessTokenIfNeeded, resolveOAuthAccountId } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/onedrive/folder/route.ts b/apps/sim/app/api/tools/onedrive/folder/route.ts index 2ab90355b7c..fd373641ec0 100644 --- a/apps/sim/app/api/tools/onedrive/folder/route.ts +++ b/apps/sim/app/api/tools/onedrive/folder/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { account } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation' -import { generateId } from '@/lib/core/utils/uuid' import { refreshAccessTokenIfNeeded, resolveOAuthAccountId } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/onedrive/folders/route.ts b/apps/sim/app/api/tools/onedrive/folders/route.ts index 5cf6981f801..9a35dc108f6 100644 --- a/apps/sim/app/api/tools/onedrive/folders/route.ts +++ b/apps/sim/app/api/tools/onedrive/folders/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { account } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation' -import { generateId } from '@/lib/core/utils/uuid' import { refreshAccessTokenIfNeeded, resolveOAuthAccountId } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/onepassword/create-item/route.ts b/apps/sim/app/api/tools/onepassword/create-item/route.ts index 497e71b1991..21a17c5bb36 100644 --- a/apps/sim/app/api/tools/onepassword/create-item/route.ts +++ b/apps/sim/app/api/tools/onepassword/create-item/route.ts @@ -1,9 +1,9 @@ import type { ItemCreateParams } from '@1password/sdk' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { connectRequest, createOnePasswordClient, diff --git a/apps/sim/app/api/tools/onepassword/delete-item/route.ts b/apps/sim/app/api/tools/onepassword/delete-item/route.ts index c2be6e8f1eb..0507a6add39 100644 --- a/apps/sim/app/api/tools/onepassword/delete-item/route.ts +++ b/apps/sim/app/api/tools/onepassword/delete-item/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { connectRequest, createOnePasswordClient, resolveCredentials } from '../utils' const logger = createLogger('OnePasswordDeleteItemAPI') diff --git a/apps/sim/app/api/tools/onepassword/get-item/route.ts b/apps/sim/app/api/tools/onepassword/get-item/route.ts index 92065228e81..f0986e58937 100644 --- a/apps/sim/app/api/tools/onepassword/get-item/route.ts +++ b/apps/sim/app/api/tools/onepassword/get-item/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { connectRequest, createOnePasswordClient, diff --git a/apps/sim/app/api/tools/onepassword/get-vault/route.ts b/apps/sim/app/api/tools/onepassword/get-vault/route.ts index 09b2b227fe2..5b01c9aa674 100644 --- a/apps/sim/app/api/tools/onepassword/get-vault/route.ts +++ b/apps/sim/app/api/tools/onepassword/get-vault/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { connectRequest, createOnePasswordClient, diff --git a/apps/sim/app/api/tools/onepassword/list-items/route.ts b/apps/sim/app/api/tools/onepassword/list-items/route.ts index 6f4d4c6eb9d..1009be87435 100644 --- a/apps/sim/app/api/tools/onepassword/list-items/route.ts +++ b/apps/sim/app/api/tools/onepassword/list-items/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { connectRequest, createOnePasswordClient, diff --git a/apps/sim/app/api/tools/onepassword/list-vaults/route.ts b/apps/sim/app/api/tools/onepassword/list-vaults/route.ts index e24d8567abc..d99c48db48f 100644 --- a/apps/sim/app/api/tools/onepassword/list-vaults/route.ts +++ b/apps/sim/app/api/tools/onepassword/list-vaults/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { connectRequest, createOnePasswordClient, diff --git a/apps/sim/app/api/tools/onepassword/replace-item/route.ts b/apps/sim/app/api/tools/onepassword/replace-item/route.ts index 48b84918b5f..cac00a6f22a 100644 --- a/apps/sim/app/api/tools/onepassword/replace-item/route.ts +++ b/apps/sim/app/api/tools/onepassword/replace-item/route.ts @@ -1,9 +1,9 @@ import type { Item } from '@1password/sdk' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { connectRequest, createOnePasswordClient, diff --git a/apps/sim/app/api/tools/onepassword/resolve-secret/route.ts b/apps/sim/app/api/tools/onepassword/resolve-secret/route.ts index e327da13d68..ea696f1f874 100644 --- a/apps/sim/app/api/tools/onepassword/resolve-secret/route.ts +++ b/apps/sim/app/api/tools/onepassword/resolve-secret/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createOnePasswordClient, resolveCredentials } from '../utils' const logger = createLogger('OnePasswordResolveSecretAPI') diff --git a/apps/sim/app/api/tools/onepassword/update-item/route.ts b/apps/sim/app/api/tools/onepassword/update-item/route.ts index 1bfca62a68b..d85c3daefee 100644 --- a/apps/sim/app/api/tools/onepassword/update-item/route.ts +++ b/apps/sim/app/api/tools/onepassword/update-item/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { connectRequest, createOnePasswordClient, diff --git a/apps/sim/app/api/tools/onepassword/utils.ts b/apps/sim/app/api/tools/onepassword/utils.ts index 07f4a43d11e..8fb53d59d50 100644 --- a/apps/sim/app/api/tools/onepassword/utils.ts +++ b/apps/sim/app/api/tools/onepassword/utils.ts @@ -10,9 +10,9 @@ import type { Website, } from '@1password/sdk' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import * as ipaddr from 'ipaddr.js' import { secureFetchWithPinnedIP } from '@/lib/core/security/input-validation.server' -import { toError } from '@/lib/core/utils/helpers' /** Connect-format field type strings returned by normalization. */ type ConnectFieldType = diff --git a/apps/sim/app/api/tools/outlook/folders/route.ts b/apps/sim/app/api/tools/outlook/folders/route.ts index 26fa0e9da80..ffb2833fb80 100644 --- a/apps/sim/app/api/tools/outlook/folders/route.ts +++ b/apps/sim/app/api/tools/outlook/folders/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { account } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { eq } from 'drizzle-orm' import { NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { validateAlphanumericId } from '@/lib/core/security/input-validation' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' import { refreshAccessTokenIfNeeded, resolveOAuthAccountId } from '@/app/api/auth/oauth/utils' diff --git a/apps/sim/app/api/tools/postgresql/delete/route.ts b/apps/sim/app/api/tools/postgresql/delete/route.ts index 05309cda907..8099febb5fa 100644 --- a/apps/sim/app/api/tools/postgresql/delete/route.ts +++ b/apps/sim/app/api/tools/postgresql/delete/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createPostgresConnection, executeDelete } from '@/app/api/tools/postgresql/utils' const logger = createLogger('PostgreSQLDeleteAPI') diff --git a/apps/sim/app/api/tools/postgresql/execute/route.ts b/apps/sim/app/api/tools/postgresql/execute/route.ts index 1dba7c11414..e898926280b 100644 --- a/apps/sim/app/api/tools/postgresql/execute/route.ts +++ b/apps/sim/app/api/tools/postgresql/execute/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createPostgresConnection, executeQuery, diff --git a/apps/sim/app/api/tools/postgresql/insert/route.ts b/apps/sim/app/api/tools/postgresql/insert/route.ts index 01073a96577..7030bd622a2 100644 --- a/apps/sim/app/api/tools/postgresql/insert/route.ts +++ b/apps/sim/app/api/tools/postgresql/insert/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createPostgresConnection, executeInsert } from '@/app/api/tools/postgresql/utils' const logger = createLogger('PostgreSQLInsertAPI') diff --git a/apps/sim/app/api/tools/postgresql/introspect/route.ts b/apps/sim/app/api/tools/postgresql/introspect/route.ts index cf376bef9de..e7e476cefa2 100644 --- a/apps/sim/app/api/tools/postgresql/introspect/route.ts +++ b/apps/sim/app/api/tools/postgresql/introspect/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createPostgresConnection, executeIntrospect } from '@/app/api/tools/postgresql/utils' const logger = createLogger('PostgreSQLIntrospectAPI') diff --git a/apps/sim/app/api/tools/postgresql/query/route.ts b/apps/sim/app/api/tools/postgresql/query/route.ts index 72e73489a5b..f41d2b8598c 100644 --- a/apps/sim/app/api/tools/postgresql/query/route.ts +++ b/apps/sim/app/api/tools/postgresql/query/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createPostgresConnection, executeQuery } from '@/app/api/tools/postgresql/utils' const logger = createLogger('PostgreSQLQueryAPI') diff --git a/apps/sim/app/api/tools/postgresql/update/route.ts b/apps/sim/app/api/tools/postgresql/update/route.ts index 4eb2cc9d4da..e7241f0f980 100644 --- a/apps/sim/app/api/tools/postgresql/update/route.ts +++ b/apps/sim/app/api/tools/postgresql/update/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createPostgresConnection, executeUpdate } from '@/app/api/tools/postgresql/utils' const logger = createLogger('PostgreSQLUpdateAPI') diff --git a/apps/sim/app/api/tools/rds/delete/route.ts b/apps/sim/app/api/tools/rds/delete/route.ts index 92b2c9d0b93..318a0f3c9e8 100644 --- a/apps/sim/app/api/tools/rds/delete/route.ts +++ b/apps/sim/app/api/tools/rds/delete/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createRdsClient, executeDelete } from '@/app/api/tools/rds/utils' const logger = createLogger('RDSDeleteAPI') diff --git a/apps/sim/app/api/tools/rds/execute/route.ts b/apps/sim/app/api/tools/rds/execute/route.ts index af6304f98da..5dfdd9ebdc2 100644 --- a/apps/sim/app/api/tools/rds/execute/route.ts +++ b/apps/sim/app/api/tools/rds/execute/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createRdsClient, executeStatement } from '@/app/api/tools/rds/utils' const logger = createLogger('RDSExecuteAPI') diff --git a/apps/sim/app/api/tools/rds/insert/route.ts b/apps/sim/app/api/tools/rds/insert/route.ts index 7fba5fcbb7c..f80680db656 100644 --- a/apps/sim/app/api/tools/rds/insert/route.ts +++ b/apps/sim/app/api/tools/rds/insert/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createRdsClient, executeInsert } from '@/app/api/tools/rds/utils' const logger = createLogger('RDSInsertAPI') diff --git a/apps/sim/app/api/tools/rds/introspect/route.ts b/apps/sim/app/api/tools/rds/introspect/route.ts index 2e8aa42a8ef..e08ed73cf13 100644 --- a/apps/sim/app/api/tools/rds/introspect/route.ts +++ b/apps/sim/app/api/tools/rds/introspect/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createRdsClient, executeIntrospect, type RdsEngine } from '@/app/api/tools/rds/utils' const logger = createLogger('RDSIntrospectAPI') diff --git a/apps/sim/app/api/tools/rds/query/route.ts b/apps/sim/app/api/tools/rds/query/route.ts index 21a73291073..0793083c903 100644 --- a/apps/sim/app/api/tools/rds/query/route.ts +++ b/apps/sim/app/api/tools/rds/query/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createRdsClient, executeStatement, validateQuery } from '@/app/api/tools/rds/utils' const logger = createLogger('RDSQueryAPI') diff --git a/apps/sim/app/api/tools/rds/update/route.ts b/apps/sim/app/api/tools/rds/update/route.ts index 1e2826e4ac7..2dd8418ac7d 100644 --- a/apps/sim/app/api/tools/rds/update/route.ts +++ b/apps/sim/app/api/tools/rds/update/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createRdsClient, executeUpdate } from '@/app/api/tools/rds/utils' const logger = createLogger('RDSUpdateAPI') diff --git a/apps/sim/app/api/tools/search/route.ts b/apps/sim/app/api/tools/search/route.ts index b45213115d8..ca56c9045ef 100644 --- a/apps/sim/app/api/tools/search/route.ts +++ b/apps/sim/app/api/tools/search/route.ts @@ -1,10 +1,10 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' import { SEARCH_TOOL_COST } from '@/lib/billing/constants' import { env } from '@/lib/core/config/env' -import { generateId } from '@/lib/core/utils/uuid' import { executeTool } from '@/tools' const logger = createLogger('search') diff --git a/apps/sim/app/api/tools/secrets_manager/create-secret/route.ts b/apps/sim/app/api/tools/secrets_manager/create-secret/route.ts index 7cb4a60160b..7f57a9c36b3 100644 --- a/apps/sim/app/api/tools/secrets_manager/create-secret/route.ts +++ b/apps/sim/app/api/tools/secrets_manager/create-secret/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSecret, createSecretsManagerClient } from '../utils' const logger = createLogger('SecretsManagerCreateSecretAPI') diff --git a/apps/sim/app/api/tools/secrets_manager/delete-secret/route.ts b/apps/sim/app/api/tools/secrets_manager/delete-secret/route.ts index 21a35e64af7..82a7229a0ec 100644 --- a/apps/sim/app/api/tools/secrets_manager/delete-secret/route.ts +++ b/apps/sim/app/api/tools/secrets_manager/delete-secret/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSecretsManagerClient, deleteSecret } from '../utils' const logger = createLogger('SecretsManagerDeleteSecretAPI') diff --git a/apps/sim/app/api/tools/secrets_manager/get-secret/route.ts b/apps/sim/app/api/tools/secrets_manager/get-secret/route.ts index 22df5b57072..b8ca8a4cecb 100644 --- a/apps/sim/app/api/tools/secrets_manager/get-secret/route.ts +++ b/apps/sim/app/api/tools/secrets_manager/get-secret/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSecretsManagerClient, getSecretValue } from '../utils' const logger = createLogger('SecretsManagerGetSecretAPI') diff --git a/apps/sim/app/api/tools/secrets_manager/list-secrets/route.ts b/apps/sim/app/api/tools/secrets_manager/list-secrets/route.ts index 58617b4864f..dfe225589d5 100644 --- a/apps/sim/app/api/tools/secrets_manager/list-secrets/route.ts +++ b/apps/sim/app/api/tools/secrets_manager/list-secrets/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSecretsManagerClient, listSecrets } from '../utils' const logger = createLogger('SecretsManagerListSecretsAPI') diff --git a/apps/sim/app/api/tools/secrets_manager/update-secret/route.ts b/apps/sim/app/api/tools/secrets_manager/update-secret/route.ts index 5becf7f0dc3..6be86a34552 100644 --- a/apps/sim/app/api/tools/secrets_manager/update-secret/route.ts +++ b/apps/sim/app/api/tools/secrets_manager/update-secret/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSecretsManagerClient, updateSecretValue } from '../utils' const logger = createLogger('SecretsManagerUpdateSecretAPI') diff --git a/apps/sim/app/api/tools/sftp/utils.ts b/apps/sim/app/api/tools/sftp/utils.ts index 094c784ac28..639672ae438 100644 --- a/apps/sim/app/api/tools/sftp/utils.ts +++ b/apps/sim/app/api/tools/sftp/utils.ts @@ -1,6 +1,6 @@ +import { toError } from '@sim/utils/errors' import { type Attributes, Client, type ConnectConfig, type SFTPWrapper } from 'ssh2' import { validateDatabaseHost } from '@/lib/core/security/input-validation.server' -import { toError } from '@/lib/core/utils/helpers' const S_IFMT = 0o170000 const S_IFDIR = 0o040000 diff --git a/apps/sim/app/api/tools/sharepoint/site/route.ts b/apps/sim/app/api/tools/sharepoint/site/route.ts index 7afc3954fa2..7984e96d7d6 100644 --- a/apps/sim/app/api/tools/sharepoint/site/route.ts +++ b/apps/sim/app/api/tools/sharepoint/site/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { account } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation' -import { generateId } from '@/lib/core/utils/uuid' import { refreshAccessTokenIfNeeded, resolveOAuthAccountId } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/smtp/send/route.ts b/apps/sim/app/api/tools/smtp/send/route.ts index 796b7114b3c..40f915367fe 100644 --- a/apps/sim/app/api/tools/smtp/send/route.ts +++ b/apps/sim/app/api/tools/smtp/send/route.ts @@ -1,10 +1,10 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import nodemailer from 'nodemailer' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateDatabaseHost } from '@/lib/core/security/input-validation.server' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' import { RawFileInputArraySchema } from '@/lib/uploads/utils/file-schemas' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' diff --git a/apps/sim/app/api/tools/sqs/send/route.ts b/apps/sim/app/api/tools/sqs/send/route.ts index c9078aecc4b..e0f12c38334 100644 --- a/apps/sim/app/api/tools/sqs/send/route.ts +++ b/apps/sim/app/api/tools/sqs/send/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSqsClient, sendMessage } from '../utils' const logger = createLogger('SQSSendMessageAPI') diff --git a/apps/sim/app/api/tools/ssh/check-command-exists/route.ts b/apps/sim/app/api/tools/ssh/check-command-exists/route.ts index 186b4c390aa..0e2d545f2df 100644 --- a/apps/sim/app/api/tools/ssh/check-command-exists/route.ts +++ b/apps/sim/app/api/tools/ssh/check-command-exists/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSSHConnection, escapeShellArg, executeSSHCommand } from '@/app/api/tools/ssh/utils' const logger = createLogger('SSHCheckCommandExistsAPI') diff --git a/apps/sim/app/api/tools/ssh/check-file-exists/route.ts b/apps/sim/app/api/tools/ssh/check-file-exists/route.ts index e7e65cc633b..2bcce214702 100644 --- a/apps/sim/app/api/tools/ssh/check-file-exists/route.ts +++ b/apps/sim/app/api/tools/ssh/check-file-exists/route.ts @@ -1,9 +1,9 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import type { Client, SFTPWrapper, Stats } from 'ssh2' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSSHConnection, getFileType, diff --git a/apps/sim/app/api/tools/ssh/create-directory/route.ts b/apps/sim/app/api/tools/ssh/create-directory/route.ts index 467c097d4e3..5bca7dcf229 100644 --- a/apps/sim/app/api/tools/ssh/create-directory/route.ts +++ b/apps/sim/app/api/tools/ssh/create-directory/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSSHConnection, escapeShellArg, diff --git a/apps/sim/app/api/tools/ssh/delete-file/route.ts b/apps/sim/app/api/tools/ssh/delete-file/route.ts index 44506996b07..b0cd4374300 100644 --- a/apps/sim/app/api/tools/ssh/delete-file/route.ts +++ b/apps/sim/app/api/tools/ssh/delete-file/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSSHConnection, escapeShellArg, diff --git a/apps/sim/app/api/tools/ssh/download-file/route.ts b/apps/sim/app/api/tools/ssh/download-file/route.ts index 6dbfdf3c5ee..6aa443d6338 100644 --- a/apps/sim/app/api/tools/ssh/download-file/route.ts +++ b/apps/sim/app/api/tools/ssh/download-file/route.ts @@ -1,10 +1,10 @@ import path from 'path' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import type { Client, SFTPWrapper } from 'ssh2' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { getFileExtension, getMimeTypeFromExtension } from '@/lib/uploads/utils/file-utils' import { createSSHConnection, sanitizePath } from '@/app/api/tools/ssh/utils' diff --git a/apps/sim/app/api/tools/ssh/execute-command/route.ts b/apps/sim/app/api/tools/ssh/execute-command/route.ts index 66b5dfb1555..b888e298bee 100644 --- a/apps/sim/app/api/tools/ssh/execute-command/route.ts +++ b/apps/sim/app/api/tools/ssh/execute-command/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSSHConnection, escapeShellArg, diff --git a/apps/sim/app/api/tools/ssh/execute-script/route.ts b/apps/sim/app/api/tools/ssh/execute-script/route.ts index b0158e43fdd..4ba3e6f4f26 100644 --- a/apps/sim/app/api/tools/ssh/execute-script/route.ts +++ b/apps/sim/app/api/tools/ssh/execute-script/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSSHConnection, escapeShellArg, executeSSHCommand } from '@/app/api/tools/ssh/utils' const logger = createLogger('SSHExecuteScriptAPI') diff --git a/apps/sim/app/api/tools/ssh/get-system-info/route.ts b/apps/sim/app/api/tools/ssh/get-system-info/route.ts index 65a25e82e21..6594baa718b 100644 --- a/apps/sim/app/api/tools/ssh/get-system-info/route.ts +++ b/apps/sim/app/api/tools/ssh/get-system-info/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSSHConnection, executeSSHCommand } from '@/app/api/tools/ssh/utils' const logger = createLogger('SSHGetSystemInfoAPI') diff --git a/apps/sim/app/api/tools/ssh/list-directory/route.ts b/apps/sim/app/api/tools/ssh/list-directory/route.ts index 2971ef9202b..d3f6895c574 100644 --- a/apps/sim/app/api/tools/ssh/list-directory/route.ts +++ b/apps/sim/app/api/tools/ssh/list-directory/route.ts @@ -1,9 +1,9 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import type { Client, FileEntry, SFTPWrapper } from 'ssh2' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSSHConnection, getFileType, diff --git a/apps/sim/app/api/tools/ssh/move-rename/route.ts b/apps/sim/app/api/tools/ssh/move-rename/route.ts index 98285041707..1c2a7c96758 100644 --- a/apps/sim/app/api/tools/ssh/move-rename/route.ts +++ b/apps/sim/app/api/tools/ssh/move-rename/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSSHConnection, escapeShellArg, diff --git a/apps/sim/app/api/tools/ssh/read-file-content/route.ts b/apps/sim/app/api/tools/ssh/read-file-content/route.ts index 7493a6cb10c..5bd159e78a3 100644 --- a/apps/sim/app/api/tools/ssh/read-file-content/route.ts +++ b/apps/sim/app/api/tools/ssh/read-file-content/route.ts @@ -1,9 +1,9 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import type { Client, SFTPWrapper } from 'ssh2' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSSHConnection, sanitizePath } from '@/app/api/tools/ssh/utils' const logger = createLogger('SSHReadFileContentAPI') diff --git a/apps/sim/app/api/tools/ssh/upload-file/route.ts b/apps/sim/app/api/tools/ssh/upload-file/route.ts index 2020271465b..941c6db0874 100644 --- a/apps/sim/app/api/tools/ssh/upload-file/route.ts +++ b/apps/sim/app/api/tools/ssh/upload-file/route.ts @@ -1,9 +1,9 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import type { Client, SFTPWrapper } from 'ssh2' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSSHConnection, sanitizePath } from '@/app/api/tools/ssh/utils' const logger = createLogger('SSHUploadFileAPI') diff --git a/apps/sim/app/api/tools/ssh/utils.ts b/apps/sim/app/api/tools/ssh/utils.ts index a5506419cfa..ed3dae88328 100644 --- a/apps/sim/app/api/tools/ssh/utils.ts +++ b/apps/sim/app/api/tools/ssh/utils.ts @@ -1,7 +1,7 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type Attributes, Client, type ConnectConfig } from 'ssh2' import { validateDatabaseHost } from '@/lib/core/security/input-validation.server' -import { toError } from '@/lib/core/utils/helpers' const logger = createLogger('SSHUtils') diff --git a/apps/sim/app/api/tools/ssh/write-file-content/route.ts b/apps/sim/app/api/tools/ssh/write-file-content/route.ts index 77a180b9dae..e670f9093a3 100644 --- a/apps/sim/app/api/tools/ssh/write-file-content/route.ts +++ b/apps/sim/app/api/tools/ssh/write-file-content/route.ts @@ -1,9 +1,9 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import type { Client, SFTPWrapper } from 'ssh2' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSSHConnection, sanitizePath } from '@/app/api/tools/ssh/utils' const logger = createLogger('SSHWriteFileContentAPI') diff --git a/apps/sim/app/api/tools/sts/assume-role/route.ts b/apps/sim/app/api/tools/sts/assume-role/route.ts index 4903eb8e8e2..ebec8b8de99 100644 --- a/apps/sim/app/api/tools/sts/assume-role/route.ts +++ b/apps/sim/app/api/tools/sts/assume-role/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { assumeRole, createSTSClient } from '../utils' const logger = createLogger('STSAssumeRoleAPI') diff --git a/apps/sim/app/api/tools/sts/get-access-key-info/route.ts b/apps/sim/app/api/tools/sts/get-access-key-info/route.ts index 2375aca7806..536a5d7eb02 100644 --- a/apps/sim/app/api/tools/sts/get-access-key-info/route.ts +++ b/apps/sim/app/api/tools/sts/get-access-key-info/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSTSClient, getAccessKeyInfo } from '../utils' const logger = createLogger('STSGetAccessKeyInfoAPI') diff --git a/apps/sim/app/api/tools/sts/get-caller-identity/route.ts b/apps/sim/app/api/tools/sts/get-caller-identity/route.ts index 1cb5d263c06..c625fb70615 100644 --- a/apps/sim/app/api/tools/sts/get-caller-identity/route.ts +++ b/apps/sim/app/api/tools/sts/get-caller-identity/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSTSClient, getCallerIdentity } from '../utils' const logger = createLogger('STSGetCallerIdentityAPI') diff --git a/apps/sim/app/api/tools/sts/get-session-token/route.ts b/apps/sim/app/api/tools/sts/get-session-token/route.ts index 05fb77bcb32..338c102572f 100644 --- a/apps/sim/app/api/tools/sts/get-session-token/route.ts +++ b/apps/sim/app/api/tools/sts/get-session-token/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' -import { generateId } from '@/lib/core/utils/uuid' import { createSTSClient, getSessionToken } from '../utils' const logger = createLogger('STSGetSessionTokenAPI') diff --git a/apps/sim/app/api/tools/stt/route.ts b/apps/sim/app/api/tools/stt/route.ts index fbbd8abdb93..213bcf112cd 100644 --- a/apps/sim/app/api/tools/stt/route.ts +++ b/apps/sim/app/api/tools/stt/route.ts @@ -1,4 +1,6 @@ import { createLogger } from '@sim/logger' +import { sleep } from '@sim/utils/helpers' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { extractAudioFromVideo, isVideoFile } from '@/lib/audio/extractor' import { checkInternalAuth } from '@/lib/auth/hybrid' @@ -7,8 +9,6 @@ import { secureFetchWithPinnedIP, validateUrlWithDNS, } from '@/lib/core/security/input-validation.server' -import { sleep } from '@/lib/core/utils/helpers' -import { generateId } from '@/lib/core/utils/uuid' import { getMimeTypeFromExtension, isInternalFileUrl } from '@/lib/uploads/utils/file-utils' import { downloadFileFromStorage, diff --git a/apps/sim/app/api/tools/textract/parse/route.ts b/apps/sim/app/api/tools/textract/parse/route.ts index 3191e428860..f5ce04fce91 100644 --- a/apps/sim/app/api/tools/textract/parse/route.ts +++ b/apps/sim/app/api/tools/textract/parse/route.ts @@ -1,5 +1,6 @@ import crypto from 'crypto' import { createLogger } from '@sim/logger' +import { sleep } from '@sim/utils/helpers' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' @@ -9,7 +10,6 @@ import { secureFetchWithPinnedIP, validateUrlWithDNS, } from '@/lib/core/security/input-validation.server' -import { sleep } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' import { RawFileInputSchema } from '@/lib/uploads/utils/file-schemas' import { isInternalFileUrl, processSingleFileToUserFile } from '@/lib/uploads/utils/file-utils' diff --git a/apps/sim/app/api/tools/tts/unified/route.ts b/apps/sim/app/api/tools/tts/unified/route.ts index 5b6ba35198a..7f9db6e26e6 100644 --- a/apps/sim/app/api/tools/tts/unified/route.ts +++ b/apps/sim/app/api/tools/tts/unified/route.ts @@ -1,10 +1,10 @@ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import type { NextRequest } from 'next/server' import { NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { getBaseUrl } from '@/lib/core/utils/urls' -import { generateId } from '@/lib/core/utils/uuid' import { StorageService } from '@/lib/uploads' import type { AzureTtsParams, diff --git a/apps/sim/app/api/tools/video/route.ts b/apps/sim/app/api/tools/video/route.ts index c8cad166824..48c9865be32 100644 --- a/apps/sim/app/api/tools/video/route.ts +++ b/apps/sim/app/api/tools/video/route.ts @@ -1,9 +1,9 @@ import { createLogger } from '@sim/logger' +import { sleep } from '@sim/utils/helpers' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { getMaxExecutionTimeout } from '@/lib/core/execution-limits' -import { sleep } from '@/lib/core/utils/helpers' -import { generateId } from '@/lib/core/utils/uuid' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import type { UserFile } from '@/executor/types' import type { VideoRequestBody } from '@/tools/video/types' diff --git a/apps/sim/app/api/users/me/api-keys/route.ts b/apps/sim/app/api/users/me/api-keys/route.ts index 173bc01be7a..ac09226946d 100644 --- a/apps/sim/app/api/users/me/api-keys/route.ts +++ b/apps/sim/app/api/users/me/api-keys/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { apiKey } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateShortId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { createApiKey, getApiKeyDisplayFormat } from '@/lib/api-key/auth' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' -import { generateShortId } from '@/lib/core/utils/uuid' const logger = createLogger('ApiKeysAPI') diff --git a/apps/sim/app/api/users/me/settings/route.ts b/apps/sim/app/api/users/me/settings/route.ts index 2c45a0e3ccf..fd8aa9e3055 100644 --- a/apps/sim/app/api/users/me/settings/route.ts +++ b/apps/sim/app/api/users/me/settings/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { settings } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateShortId } from '@sim/utils/id' import { eq } from 'drizzle-orm' import { NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { generateShortId } from '@/lib/core/utils/uuid' const logger = createLogger('UserSettingsAPI') diff --git a/apps/sim/app/api/users/me/subscription/[id]/transfer/route.ts b/apps/sim/app/api/users/me/subscription/[id]/transfer/route.ts index 96af579f452..a9e977d9633 100644 --- a/apps/sim/app/api/users/me/subscription/[id]/transfer/route.ts +++ b/apps/sim/app/api/users/me/subscription/[id]/transfer/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { member, organization, subscription } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { hasPaidSubscription } from '@/lib/billing' -import { toError } from '@/lib/core/utils/helpers' const logger = createLogger('SubscriptionTransferAPI') diff --git a/apps/sim/app/api/users/me/usage-logs/route.ts b/apps/sim/app/api/users/me/usage-logs/route.ts index 9de44c4d01e..05d0d7cb650 100644 --- a/apps/sim/app/api/users/me/usage-logs/route.ts +++ b/apps/sim/app/api/users/me/usage-logs/route.ts @@ -1,10 +1,10 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' import { getUserUsageLogs, type UsageLogSource } from '@/lib/billing/core/usage-log' import { dollarsToCredits } from '@/lib/billing/credits/conversion' -import { toError } from '@/lib/core/utils/helpers' const logger = createLogger('UsageLogsAPI') diff --git a/apps/sim/app/api/v1/admin/credits/route.ts b/apps/sim/app/api/v1/admin/credits/route.ts index 41bcdefd063..e4d393e8fb4 100644 --- a/apps/sim/app/api/v1/admin/credits/route.ts +++ b/apps/sim/app/api/v1/admin/credits/route.ts @@ -26,6 +26,7 @@ import { db } from '@sim/db' import { organization, subscription, user, userStats } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateShortId } from '@sim/utils/id' import { and, eq, inArray } from 'drizzle-orm' import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription' import { addCredits } from '@/lib/billing/credits/balance' @@ -36,7 +37,6 @@ import { getEffectiveSeats, isOrgScopedSubscription, } from '@/lib/billing/subscriptions/utils' -import { generateShortId } from '@/lib/core/utils/uuid' import { withAdminAuth } from '@/app/api/v1/admin/middleware' import { badRequestResponse, diff --git a/apps/sim/app/api/v1/admin/organizations/route.ts b/apps/sim/app/api/v1/admin/organizations/route.ts index f4da57737e6..0a87080fdf9 100644 --- a/apps/sim/app/api/v1/admin/organizations/route.ts +++ b/apps/sim/app/api/v1/admin/organizations/route.ts @@ -24,8 +24,8 @@ import { db } from '@sim/db' import { member, organization, user } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { count, eq } from 'drizzle-orm' -import { generateId } from '@/lib/core/utils/uuid' import { withAdminAuth } from '@/app/api/v1/admin/middleware' import { badRequestResponse, diff --git a/apps/sim/app/api/v1/admin/outbox/[id]/requeue/route.ts b/apps/sim/app/api/v1/admin/outbox/[id]/requeue/route.ts index 9de5c4696be..0b1693bcdf8 100644 --- a/apps/sim/app/api/v1/admin/outbox/[id]/requeue/route.ts +++ b/apps/sim/app/api/v1/admin/outbox/[id]/requeue/route.ts @@ -1,6 +1,7 @@ import { db } from '@sim/db' import { outboxEvent } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { and, eq } from 'drizzle-orm' import { NextResponse } from 'next/server' import { withAdminAuthParams } from '@/app/api/v1/admin/middleware' @@ -56,16 +57,7 @@ export const POST = withAdminAuthParams<{ id: string }>(async (_request, { param requeued: result[0], }) } catch (error) { - logger.error('Failed to requeue outbox event', { - eventId: id, - error: error instanceof Error ? error.message : error, - }) - return NextResponse.json( - { - success: false, - error: error instanceof Error ? error.message : 'Unknown error', - }, - { status: 500 } - ) + logger.error('Failed to requeue outbox event', { eventId: id, error: toError(error).message }) + return NextResponse.json({ success: false, error: toError(error).message }, { status: 500 }) } }) diff --git a/apps/sim/app/api/v1/admin/outbox/route.ts b/apps/sim/app/api/v1/admin/outbox/route.ts index addcb5bbe67..87acb5d006a 100644 --- a/apps/sim/app/api/v1/admin/outbox/route.ts +++ b/apps/sim/app/api/v1/admin/outbox/route.ts @@ -1,6 +1,7 @@ import { db } from '@sim/db' import { outboxEvent } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { and, desc, eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { withAdminAuth } from '@/app/api/v1/admin/middleware' @@ -77,15 +78,7 @@ export const GET = withAdminAuth(async (request: NextRequest) => { counts, }) } catch (error) { - logger.error('Failed to list outbox events', { - error: error instanceof Error ? error.message : error, - }) - return NextResponse.json( - { - success: false, - error: error instanceof Error ? error.message : 'Unknown error', - }, - { status: 500 } - ) + logger.error('Failed to list outbox events', { error: toError(error).message }) + return NextResponse.json({ success: false, error: toError(error).message }, { status: 500 }) } }) diff --git a/apps/sim/app/api/v1/admin/users/[id]/billing/route.ts b/apps/sim/app/api/v1/admin/users/[id]/billing/route.ts index ecc10aab242..537e5b70eab 100644 --- a/apps/sim/app/api/v1/admin/users/[id]/billing/route.ts +++ b/apps/sim/app/api/v1/admin/users/[id]/billing/route.ts @@ -21,10 +21,10 @@ import { db } from '@sim/db' import { member, organization, subscription, user, userStats } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateShortId } from '@sim/utils/id' import { eq, or } from 'drizzle-orm' import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription' import { isOrgScopedSubscription } from '@/lib/billing/subscriptions/utils' -import { generateShortId } from '@/lib/core/utils/uuid' import { withAdminAuthParams } from '@/app/api/v1/admin/middleware' import { badRequestResponse, diff --git a/apps/sim/app/api/v1/admin/workflows/import/route.ts b/apps/sim/app/api/v1/admin/workflows/import/route.ts index 1d384f0cf6b..5332b8a6afb 100644 --- a/apps/sim/app/api/v1/admin/workflows/import/route.ts +++ b/apps/sim/app/api/v1/admin/workflows/import/route.ts @@ -17,9 +17,9 @@ import { db } from '@sim/db' import { workflow, workspace } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, isNull } from 'drizzle-orm' import { NextResponse } from 'next/server' -import { generateId } from '@/lib/core/utils/uuid' import { parseWorkflowJson } from '@/lib/workflows/operations/import-export' import { saveWorkflowToNormalizedTables } from '@/lib/workflows/persistence/utils' import { deduplicateWorkflowName } from '@/lib/workflows/utils' diff --git a/apps/sim/app/api/v1/admin/workspaces/[id]/import/route.ts b/apps/sim/app/api/v1/admin/workspaces/[id]/import/route.ts index adcc927785d..37e13941b72 100644 --- a/apps/sim/app/api/v1/admin/workspaces/[id]/import/route.ts +++ b/apps/sim/app/api/v1/admin/workspaces/[id]/import/route.ts @@ -26,9 +26,9 @@ import { db } from '@sim/db' import { workflow, workflowFolder } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { eq } from 'drizzle-orm' import { NextResponse } from 'next/server' -import { generateId } from '@/lib/core/utils/uuid' import { extractWorkflowName, extractWorkflowsFromZip, diff --git a/apps/sim/app/api/v1/admin/workspaces/[id]/members/route.ts b/apps/sim/app/api/v1/admin/workspaces/[id]/members/route.ts index d9a399268fd..c5af318a5cf 100644 --- a/apps/sim/app/api/v1/admin/workspaces/[id]/members/route.ts +++ b/apps/sim/app/api/v1/admin/workspaces/[id]/members/route.ts @@ -33,8 +33,8 @@ import { db } from '@sim/db' import { permissions, user, workspaceEnvironment } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, count, eq } from 'drizzle-orm' -import { generateId } from '@/lib/core/utils/uuid' import { syncWorkspaceEnvCredentials } from '@/lib/credentials/environment' import { getWorkspaceById } from '@/lib/workspaces/permissions/utils' import { withAdminAuthParams } from '@/app/api/v1/admin/middleware' diff --git a/apps/sim/app/api/v1/audit-logs/[id]/route.ts b/apps/sim/app/api/v1/audit-logs/[id]/route.ts index 948a034afc0..5124c669a81 100644 --- a/apps/sim/app/api/v1/audit-logs/[id]/route.ts +++ b/apps/sim/app/api/v1/audit-logs/[id]/route.ts @@ -13,9 +13,9 @@ import { db } from '@sim/db' import { auditLog, workspace } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, inArray, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' -import { generateId } from '@/lib/core/utils/uuid' import { validateEnterpriseAuditAccess } from '@/app/api/v1/audit-logs/auth' import { formatAuditLogEntry } from '@/app/api/v1/audit-logs/format' import { createApiResponse, getUserLimits } from '@/app/api/v1/logs/meta' diff --git a/apps/sim/app/api/v1/audit-logs/route.ts b/apps/sim/app/api/v1/audit-logs/route.ts index 046680bde44..e21f6347f40 100644 --- a/apps/sim/app/api/v1/audit-logs/route.ts +++ b/apps/sim/app/api/v1/audit-logs/route.ts @@ -20,9 +20,9 @@ */ import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { generateId } from '@/lib/core/utils/uuid' import { validateEnterpriseAuditAccess } from '@/app/api/v1/audit-logs/auth' import { formatAuditLogEntry } from '@/app/api/v1/audit-logs/format' import { diff --git a/apps/sim/app/api/v1/copilot/chat/route.ts b/apps/sim/app/api/v1/copilot/chat/route.ts index 3fed69a78bb..79b09f4e9d3 100644 --- a/apps/sim/app/api/v1/copilot/chat/route.ts +++ b/apps/sim/app/api/v1/copilot/chat/route.ts @@ -1,10 +1,10 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { COPILOT_REQUEST_MODES } from '@/lib/copilot/constants' import { runHeadlessCopilotLifecycle } from '@/lib/copilot/request/lifecycle/headless' -import { toError } from '@/lib/core/utils/helpers' -import { generateId } from '@/lib/core/utils/uuid' import { getWorkflowById, resolveWorkflowIdForUser } from '@/lib/workflows/utils' import { authenticateV1Request } from '@/app/api/v1/auth' diff --git a/apps/sim/app/api/v1/logs/[id]/route.ts b/apps/sim/app/api/v1/logs/[id]/route.ts index e9b33de99ff..8af970e4be9 100644 --- a/apps/sim/app/api/v1/logs/[id]/route.ts +++ b/apps/sim/app/api/v1/logs/[id]/route.ts @@ -1,9 +1,9 @@ import { db } from '@sim/db' import { permissions, workflow, workflowExecutionLogs } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' -import { generateId } from '@/lib/core/utils/uuid' import { createApiResponse, getUserLimits } from '@/app/api/v1/logs/meta' import { checkRateLimit, createRateLimitResponse } from '@/app/api/v1/middleware' diff --git a/apps/sim/app/api/v1/logs/route.ts b/apps/sim/app/api/v1/logs/route.ts index cab370d8141..fc809c09c5e 100644 --- a/apps/sim/app/api/v1/logs/route.ts +++ b/apps/sim/app/api/v1/logs/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { permissions, workflow, workflowExecutionLogs } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { generateId } from '@/lib/core/utils/uuid' import { buildLogFilters, getOrderBy } from '@/app/api/v1/logs/filters' import { createApiResponse, getUserLimits } from '@/app/api/v1/logs/meta' import { checkRateLimit, createRateLimitResponse } from '@/app/api/v1/middleware' diff --git a/apps/sim/app/api/v1/tables/[tableId]/rows/[rowId]/route.ts b/apps/sim/app/api/v1/tables/[tableId]/rows/[rowId]/route.ts index af0d8525cc2..00b2420cbb8 100644 --- a/apps/sim/app/api/v1/tables/[tableId]/rows/[rowId]/route.ts +++ b/apps/sim/app/api/v1/tables/[tableId]/rows/[rowId]/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { userTableRows } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' import type { RowData } from '@/lib/table' import { updateRow } from '@/lib/table' diff --git a/apps/sim/app/api/v1/tables/[tableId]/rows/route.ts b/apps/sim/app/api/v1/tables/[tableId]/rows/route.ts index ee5a91bf436..31d5c1b1608 100644 --- a/apps/sim/app/api/v1/tables/[tableId]/rows/route.ts +++ b/apps/sim/app/api/v1/tables/[tableId]/rows/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { userTableRows } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { and, eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' import type { Filter, RowData, Sort, TableSchema } from '@/lib/table' import { diff --git a/apps/sim/app/api/v1/tables/[tableId]/rows/upsert/route.ts b/apps/sim/app/api/v1/tables/[tableId]/rows/upsert/route.ts index 13045436233..8671b61a104 100644 --- a/apps/sim/app/api/v1/tables/[tableId]/rows/upsert/route.ts +++ b/apps/sim/app/api/v1/tables/[tableId]/rows/upsert/route.ts @@ -1,7 +1,7 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' import type { RowData } from '@/lib/table' import { upsertRow } from '@/lib/table' diff --git a/apps/sim/app/api/v1/workflows/[id]/route.ts b/apps/sim/app/api/v1/workflows/[id]/route.ts index 15a97f9b9ad..5574d07b0bd 100644 --- a/apps/sim/app/api/v1/workflows/[id]/route.ts +++ b/apps/sim/app/api/v1/workflows/[id]/route.ts @@ -1,9 +1,9 @@ import { db } from '@sim/db' import { workflowBlocks } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' -import { generateId } from '@/lib/core/utils/uuid' import { getActiveWorkflowRecord } from '@/lib/workflows/active-context' import { extractInputFieldsFromBlocks } from '@/lib/workflows/input-format' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' diff --git a/apps/sim/app/api/v1/workflows/route.ts b/apps/sim/app/api/v1/workflows/route.ts index 718f0afb37f..2691c4b2378 100644 --- a/apps/sim/app/api/v1/workflows/route.ts +++ b/apps/sim/app/api/v1/workflows/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { workflow } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, asc, eq, gt, isNull, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { generateId } from '@/lib/core/utils/uuid' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' import { createApiResponse, getUserLimits } from '@/app/api/v1/logs/meta' import { checkRateLimit, createRateLimitResponse } from '@/app/api/v1/middleware' diff --git a/apps/sim/app/api/webhooks/agentmail/route.ts b/apps/sim/app/api/webhooks/agentmail/route.ts index 25e87f1de35..997e0cc688f 100644 --- a/apps/sim/app/api/webhooks/agentmail/route.ts +++ b/apps/sim/app/api/webhooks/agentmail/route.ts @@ -8,12 +8,12 @@ import { workspace, } from '@sim/db' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { tasks } from '@trigger.dev/sdk' import { and, eq, gt, ne, sql } from 'drizzle-orm' import { NextResponse } from 'next/server' import { Webhook } from 'svix' import { isTriggerDevEnabled } from '@/lib/core/config/feature-flags' -import { generateId } from '@/lib/core/utils/uuid' import { executeInboxTask } from '@/lib/mothership/inbox/executor' import type { AgentMailWebhookPayload, RejectionReason } from '@/lib/mothership/inbox/types' diff --git a/apps/sim/app/api/webhooks/outbox/process/route.ts b/apps/sim/app/api/webhooks/outbox/process/route.ts index 99b3f02baf6..caf2065768b 100644 --- a/apps/sim/app/api/webhooks/outbox/process/route.ts +++ b/apps/sim/app/api/webhooks/outbox/process/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { verifyCronAuth } from '@/lib/auth/internal' import { billingOutboxHandlers } from '@/lib/billing/webhooks/outbox-handlers' @@ -33,16 +34,9 @@ export async function GET(request: NextRequest) { result, }) } catch (error) { - logger.error('Outbox processing failed', { - requestId, - error: error instanceof Error ? error.message : error, - }) + logger.error('Outbox processing failed', { requestId, error: toError(error).message }) return NextResponse.json( - { - success: false, - requestId, - error: error instanceof Error ? error.message : 'Unknown error', - }, + { success: false, requestId, error: toError(error).message }, { status: 500 } ) } diff --git a/apps/sim/app/api/webhooks/poll/[provider]/route.ts b/apps/sim/app/api/webhooks/poll/[provider]/route.ts index 053d328b0dd..3934555a978 100644 --- a/apps/sim/app/api/webhooks/poll/[provider]/route.ts +++ b/apps/sim/app/api/webhooks/poll/[provider]/route.ts @@ -1,8 +1,8 @@ import { createLogger } from '@sim/logger' +import { generateShortId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { verifyCronAuth } from '@/lib/auth/internal' import { acquireLock, releaseLock } from '@/lib/core/config/redis' -import { generateShortId } from '@/lib/core/utils/uuid' import { pollProvider, VALID_POLLING_PROVIDERS } from '@/lib/webhooks/polling' const logger = createLogger('PollingAPI') diff --git a/apps/sim/app/api/webhooks/route.ts b/apps/sim/app/api/webhooks/route.ts index 0c7174f4294..b9823e24082 100644 --- a/apps/sim/app/api/webhooks/route.ts +++ b/apps/sim/app/api/webhooks/route.ts @@ -1,13 +1,13 @@ import { db } from '@sim/db' import { permissions, webhook, workflow, workflowDeploymentVersion } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId, generateShortId } from '@sim/utils/id' import { and, desc, eq, inArray, isNull, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { PlatformEvents } from '@/lib/core/telemetry' import { generateRequestId } from '@/lib/core/utils/request' -import { generateId, generateShortId } from '@/lib/core/utils/uuid' import { getProviderIdFromServiceId } from '@/lib/oauth' import { captureServerEvent } from '@/lib/posthog/server' import { resolveEnvVarsInObject } from '@/lib/webhooks/env-resolver' diff --git a/apps/sim/app/api/webhooks/trigger/[path]/route.test.ts b/apps/sim/app/api/webhooks/trigger/[path]/route.test.ts index 88073b11cba..83d3b81b14f 100644 --- a/apps/sim/app/api/webhooks/trigger/[path]/route.test.ts +++ b/apps/sim/app/api/webhooks/trigger/[path]/route.test.ts @@ -3,22 +3,27 @@ * * @vitest-environment node */ -import { createMockRequest, loggerMock, requestUtilsMock } from '@sim/testing' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { + createMockRequest, + encryptionMock, + executionPreprocessingMock, + executionPreprocessingMockFns, + loggingSessionMock, + workflowsPersistenceUtilsMock, + workflowsPersistenceUtilsMockFns, + workflowsUtilsMock, +} from '@sim/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' /** Mock execution dependencies for webhook tests */ function mockExecutionDependencies() { - vi.mock('@/lib/core/security/encryption', () => ({ - decryptSecret: vi.fn().mockResolvedValue({ decrypted: 'decrypted-value' }), - })) + vi.mock('@/lib/core/security/encryption', () => encryptionMock) vi.mock('@/lib/logs/execution/trace-spans/trace-spans', () => ({ buildTraceSpans: vi.fn().mockReturnValue({ traceSpans: [], totalDuration: 100 }), })) - vi.mock('@/lib/workflows/utils', () => ({ - updateWorkflowRunCounts: vi.fn().mockResolvedValue(undefined), - })) + vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) vi.mock('@/serializer', () => ({ Serializer: vi.fn().mockImplementation(() => ({ @@ -170,34 +175,9 @@ vi.mock('@/executor', () => ({ })), })) -vi.mock('@/lib/execution/preprocessing', () => ({ - preprocessExecution: vi.fn().mockResolvedValue({ - success: true, - actorUserId: 'test-user-id', - workflowRecord: { - id: 'test-workflow-id', - userId: 'test-user-id', - isDeployed: true, - workspaceId: 'test-workspace-id', - }, - userSubscription: { - plan: 'pro', - status: 'active', - }, - rateLimitInfo: { - allowed: true, - remaining: 100, - resetAt: new Date(), - }, - }), -})) +vi.mock('@/lib/execution/preprocessing', () => executionPreprocessingMock) -vi.mock('@/lib/logs/execution/logging-session', () => ({ - LoggingSession: vi.fn().mockImplementation(() => ({ - safeStart: vi.fn().mockResolvedValue(undefined), - safeCompleteWithError: vi.fn().mockResolvedValue(undefined), - })), -})) +vi.mock('@/lib/logs/execution/logging-session', () => loggingSessionMock) vi.mock('@/lib/workspaces/utils', () => ({ getWorkspaceBillingSettings: vi.fn().mockResolvedValue(null), @@ -223,16 +203,7 @@ vi.mock('@/lib/core/rate-limiter', () => ({ }, })) -vi.mock('@/lib/workflows/persistence/utils', () => ({ - loadWorkflowFromNormalizedTables: vi.fn().mockResolvedValue({ - blocks: {}, - edges: [], - loops: {}, - parallels: {}, - isFromNormalizedTables: true, - }), - blockExistsInDeployment: vi.fn().mockResolvedValue(true), -})) +vi.mock('@/lib/workflows/persistence/utils', () => workflowsPersistenceUtilsMock) vi.mock('@/lib/webhooks/processor', () => ({ findAllWebhooksForPath: vi.fn().mockImplementation(async (options: { path: string }) => { @@ -381,10 +352,6 @@ vi.mock('drizzle-orm/postgres-js', () => ({ vi.mock('postgres', () => vi.fn().mockReturnValue({})) -vi.mock('@sim/logger', () => loggerMock) - -vi.mock('@/lib/core/utils/request', () => requestUtilsMock) - process.env.DATABASE_URL = 'postgresql://test:test@localhost:5432/test' import { GET, POST } from '@/app/api/webhooks/trigger/[path]/route' @@ -397,6 +364,35 @@ describe('Webhook Trigger API Route', () => { testData.webhooks.length = 0 testData.workflows.length = 0 + executionPreprocessingMockFns.mockPreprocessExecution.mockResolvedValue({ + success: true, + actorUserId: 'test-user-id', + workflowRecord: { + id: 'test-workflow-id', + userId: 'test-user-id', + isDeployed: true, + workspaceId: 'test-workspace-id', + }, + userSubscription: { + plan: 'pro', + status: 'active', + }, + rateLimitInfo: { + allowed: true, + remaining: 100, + resetAt: new Date(), + }, + }) + + workflowsPersistenceUtilsMockFns.mockLoadWorkflowFromNormalizedTables.mockResolvedValue({ + blocks: {}, + edges: [], + loops: {}, + parallels: {}, + isFromNormalizedTables: true, + }) + workflowsPersistenceUtilsMockFns.mockBlockExistsInDeployment.mockResolvedValue(true) + mockExecutionDependencies() mockTriggerDevSdk() @@ -412,10 +408,6 @@ describe('Webhook Trigger API Route', () => { processWebhookMock.mockResolvedValue(new Response('Webhook processed', { status: 200 })) }) - afterEach(() => { - vi.clearAllMocks() - }) - it('should handle 404 for non-existent webhooks', async () => { const req = createMockRequest('POST', { type: 'event.test' }) 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 19917b7d8cb..fef312d7acf 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 @@ -3,77 +3,36 @@ * * @vitest-environment node */ +import { + dbChainMock, + dbChainMockFns, + hybridAuthMockFns, + resetDbChainMock, + workflowsUtilsMock, + workflowsUtilsMockFns, +} from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { - mockCheckSessionOrInternalAuth, - mockAuthorizeWorkflowByWorkspacePermission, - mockDbSelect, - mockDbFrom, - mockDbWhere, - mockDbLimit, -} = vi.hoisted(() => ({ - mockCheckSessionOrInternalAuth: vi.fn(), - mockAuthorizeWorkflowByWorkspacePermission: vi.fn(), - mockDbSelect: vi.fn(), - mockDbFrom: vi.fn(), - mockDbWhere: vi.fn(), - mockDbLimit: vi.fn(), -})) - +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('@sim/db', () => ({ - db: { - select: mockDbSelect, - }, -})) - -vi.mock('@sim/db/schema', () => ({ - chat: { - id: 'id', - identifier: 'identifier', - title: 'title', - description: 'description', - customizations: 'customizations', - authType: 'authType', - allowedEmails: 'allowedEmails', - outputConfigs: 'outputConfigs', - password: 'password', - isActive: 'isActive', - workflowId: 'workflowId', - archivedAt: 'archivedAt', - }, -})) - -vi.mock('@/lib/auth/hybrid', () => ({ - AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, - checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, -})) - -vi.mock('@/lib/workflows/utils', () => ({ - authorizeWorkflowByWorkspacePermission: mockAuthorizeWorkflowByWorkspacePermission, -})) +vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) import { GET } from '@/app/api/workflows/[id]/chat/status/route' describe('Workflow Chat Status Route', () => { beforeEach(() => { vi.clearAllMocks() - - mockDbSelect.mockReturnValue({ from: mockDbFrom }) - mockDbFrom.mockReturnValue({ where: mockDbWhere }) - mockDbWhere.mockReturnValue({ limit: mockDbLimit }) - mockDbLimit.mockResolvedValue([]) + resetDbChainMock() }) it('returns 401 when unauthenticated', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: false }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: false }) const req = new NextRequest('http://localhost:3000/api/workflows/wf-1/chat/status') const response = await GET(req, { params: Promise.resolve({ id: 'wf-1' }) }) @@ -82,12 +41,12 @@ describe('Workflow Chat Status Route', () => { }) it('returns 403 when user lacks workspace access', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, userId: 'user-1', authType: 'session', }) - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ allowed: false, status: 403, message: 'Access denied', @@ -102,18 +61,18 @@ describe('Workflow Chat Status Route', () => { }) it('returns deployment details when authorized', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, userId: 'user-1', authType: 'session', }) - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ allowed: true, status: 200, workflow: { id: 'wf-1', workspaceId: 'ws-1' }, workspacePermission: 'read', }) - mockDbLimit.mockResolvedValueOnce([ + dbChainMockFns.limit.mockResolvedValueOnce([ { id: 'chat-1', identifier: 'assistant', 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 164344640a3..6fd35a75438 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 @@ -2,40 +2,31 @@ * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { + createMockRequest, + executionPreprocessingMock, + executionPreprocessingMockFns, + hybridAuthMockFns, + loggingSessionMock, + requestUtilsMockFns, + workflowsUtilsMock, + workflowsUtilsMockFns, +} from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { - mockCheckHybridAuth, - mockAuthorizeWorkflowByWorkspacePermission, - mockPreprocessExecution, - mockEnqueue, -} = vi.hoisted(() => ({ - mockCheckHybridAuth: vi.fn(), - mockAuthorizeWorkflowByWorkspacePermission: vi.fn(), - mockPreprocessExecution: vi.fn(), +const { mockEnqueue } = vi.hoisted(() => ({ mockEnqueue: vi.fn().mockResolvedValue('job-123'), })) -vi.mock('@/lib/auth/hybrid', () => ({ - checkHybridAuth: mockCheckHybridAuth, - hasExternalApiCredentials: vi.fn().mockReturnValue(true), - AuthType: { - SESSION: 'session', - API_KEY: 'api_key', - INTERNAL_JWT: 'internal_jwt', - }, -})) +const mockCheckHybridAuth = hybridAuthMockFns.mockCheckHybridAuth +const mockPreprocessExecution = executionPreprocessingMockFns.mockPreprocessExecution -vi.mock('@/lib/workflows/utils', () => ({ - authorizeWorkflowByWorkspacePermission: mockAuthorizeWorkflowByWorkspacePermission, - createHttpResponseFromBlock: vi.fn(), - workflowHasResponseBlock: vi.fn().mockReturnValue(false), -})) +const mockAuthorizeWorkflowByWorkspacePermission = + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission -vi.mock('@/lib/execution/preprocessing', () => ({ - preprocessExecution: mockPreprocessExecution, -})) +vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) + +vi.mock('@/lib/execution/preprocessing', () => executionPreprocessingMock) vi.mock('@/lib/core/async-jobs', () => ({ getJobQueue: vi.fn().mockResolvedValue({ @@ -47,10 +38,6 @@ vi.mock('@/lib/core/async-jobs', () => ({ shouldExecuteInline: vi.fn().mockReturnValue(false), })) -vi.mock('@/lib/core/utils/request', () => ({ - generateRequestId: vi.fn().mockReturnValue('req-12345678'), -})) - vi.mock('@/lib/core/utils/urls', () => ({ getBaseUrl: vi.fn().mockReturnValue('http://localhost:3000'), getOllamaUrl: vi.fn().mockReturnValue('http://localhost:11434'), @@ -63,26 +50,13 @@ vi.mock('@/lib/execution/call-chain', () => ({ buildNextCallChain: vi.fn().mockReturnValue(['workflow-1']), })) -vi.mock('@/lib/logs/execution/logging-session', () => ({ - LoggingSession: vi.fn().mockImplementation(() => ({})), -})) +vi.mock('@/lib/logs/execution/logging-session', () => loggingSessionMock) vi.mock('@/background/workflow-execution', () => ({ executeWorkflowJob: vi.fn(), })) -vi.mock('@sim/logger', () => { - const createMockLogger = (): Record => ({ - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - withMetadata: vi.fn(() => createMockLogger()), - }) - return { createLogger: vi.fn(() => createMockLogger()) } -}) - -vi.mock('@/lib/core/utils/uuid', () => ({ +vi.mock('@sim/utils/id', () => ({ generateId: vi.fn(() => 'execution-123'), generateShortId: vi.fn(() => 'mock-short-id'), isValidUuid: vi.fn((v: string) => @@ -96,6 +70,10 @@ describe('workflow execute async route', () => { beforeEach(() => { vi.clearAllMocks() + requestUtilsMockFns.mockGenerateRequestId.mockReturnValue('req-12345678') + workflowsUtilsMockFns.mockWorkflowHasResponseBlock.mockReturnValue(false) + hybridAuthMockFns.mockHasExternalApiCredentials.mockReturnValue(true) + mockCheckHybridAuth.mockResolvedValue({ success: true, userId: 'session-user-1', diff --git a/apps/sim/app/api/workflows/[id]/execute/route.ts b/apps/sim/app/api/workflows/[id]/execute/route.ts index 307e79cc710..ccbccbc6a60 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.ts @@ -1,4 +1,6 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { generateId, isValidUuid } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { AuthType, checkHybridAuth, hasExternalApiCredentials } from '@/lib/auth/hybrid' @@ -9,11 +11,9 @@ import { getTimeoutErrorMessage, isTimeoutError, } from '@/lib/core/execution-limits' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' import { SSE_HEADERS } from '@/lib/core/utils/sse' import { getBaseUrl } from '@/lib/core/utils/urls' -import { generateId, isValidUuid } from '@/lib/core/utils/uuid' import { buildNextCallChain, parseCallChain, diff --git a/apps/sim/app/api/workflows/[id]/executions/[executionId]/cancel/route.test.ts b/apps/sim/app/api/workflows/[id]/executions/[executionId]/cancel/route.test.ts index 295b17e4e7e..c7e514a082f 100644 --- a/apps/sim/app/api/workflows/[id]/executions/[executionId]/cancel/route.test.ts +++ b/apps/sim/app/api/workflows/[id]/executions/[executionId]/cancel/route.test.ts @@ -2,13 +2,16 @@ * @vitest-environment node */ -import { databaseMock } from '@sim/testing' +import { + databaseMock, + hybridAuthMockFns, + workflowsUtilsMock, + workflowsUtilsMockFns, +} from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' const { - mockCheckHybridAuth, - mockAuthorizeWorkflowByWorkspacePermission, mockMarkExecutionCancelled, mockAbortManualExecution, mockCancelPausedExecution, @@ -16,8 +19,6 @@ const { mockWriteEvent, mockCloseWriter, } = vi.hoisted(() => ({ - mockCheckHybridAuth: vi.fn(), - mockAuthorizeWorkflowByWorkspacePermission: vi.fn(), mockMarkExecutionCancelled: vi.fn(), mockAbortManualExecution: vi.fn(), mockCancelPausedExecution: vi.fn(), @@ -26,10 +27,6 @@ const { mockCloseWriter: vi.fn(), })) -vi.mock('@/lib/auth/hybrid', () => ({ - checkHybridAuth: (...args: unknown[]) => mockCheckHybridAuth(...args), -})) - vi.mock('@/lib/execution/cancellation', () => ({ markExecutionCancelled: (...args: unknown[]) => mockMarkExecutionCancelled(...args), })) @@ -44,10 +41,7 @@ vi.mock('@/lib/workflows/executor/human-in-the-loop-manager', () => ({ }, })) -vi.mock('@/lib/workflows/utils', () => ({ - authorizeWorkflowByWorkspacePermission: (params: unknown) => - mockAuthorizeWorkflowByWorkspacePermission(params), -})) +vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) vi.mock('@/lib/posthog/server', () => ({ captureServerEvent: vi.fn(), @@ -73,8 +67,10 @@ const makeParams = () => ({ params: Promise.resolve({ id: 'wf-1', executionId: ' describe('POST /api/workflows/[id]/executions/[executionId]/cancel', () => { beforeEach(() => { vi.clearAllMocks() - mockCheckHybridAuth.mockResolvedValue({ success: true, userId: 'user-1' }) - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ allowed: true }) + hybridAuthMockFns.mockCheckHybridAuth.mockResolvedValue({ success: true, userId: 'user-1' }) + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ + allowed: true, + }) mockAbortManualExecution.mockReturnValue(false) mockCancelPausedExecution.mockResolvedValue(false) mockSetExecutionMeta.mockResolvedValue(undefined) @@ -185,7 +181,10 @@ describe('POST /api/workflows/[id]/executions/[executionId]/cancel', () => { }) it('returns 401 when auth fails', async () => { - mockCheckHybridAuth.mockResolvedValue({ success: false, error: 'Unauthorized' }) + hybridAuthMockFns.mockCheckHybridAuth.mockResolvedValue({ + success: false, + error: 'Unauthorized', + }) const response = await POST(makeRequest(), makeParams()) @@ -194,7 +193,7 @@ describe('POST /api/workflows/[id]/executions/[executionId]/cancel', () => { it('returns 403 when workflow access is denied', async () => { mockMarkExecutionCancelled.mockResolvedValue({ durablyRecorded: true, reason: 'recorded' }) - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ allowed: false, message: 'Access denied', status: 403, diff --git a/apps/sim/app/api/workflows/[id]/executions/[executionId]/stream/route.ts b/apps/sim/app/api/workflows/[id]/executions/[executionId]/stream/route.ts index f602e08bc57..5c1d7ee7659 100644 --- a/apps/sim/app/api/workflows/[id]/executions/[executionId]/stream/route.ts +++ b/apps/sim/app/api/workflows/[id]/executions/[executionId]/stream/route.ts @@ -1,7 +1,8 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { sleep } from '@sim/utils/helpers' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' -import { sleep, toError } from '@/lib/core/utils/helpers' import { SSE_HEADERS } from '@/lib/core/utils/sse' import { type ExecutionStreamStatus, diff --git a/apps/sim/app/api/workflows/[id]/form/status/route.test.ts b/apps/sim/app/api/workflows/[id]/form/status/route.test.ts index 4e16e491fd0..5787f6cd261 100644 --- a/apps/sim/app/api/workflows/[id]/form/status/route.test.ts +++ b/apps/sim/app/api/workflows/[id]/form/status/route.test.ts @@ -3,69 +3,35 @@ * * @vitest-environment node */ +import { + dbChainMock, + dbChainMockFns, + hybridAuthMockFns, + resetDbChainMock, + workflowsUtilsMock, + workflowsUtilsMockFns, +} from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { - mockCheckSessionOrInternalAuth, - mockAuthorizeWorkflowByWorkspacePermission, - mockDbSelect, - mockDbFrom, - mockDbWhere, - mockDbLimit, -} = vi.hoisted(() => ({ - mockCheckSessionOrInternalAuth: vi.fn(), - mockAuthorizeWorkflowByWorkspacePermission: vi.fn(), - mockDbSelect: vi.fn(), - mockDbFrom: vi.fn(), - mockDbWhere: vi.fn(), - mockDbLimit: vi.fn(), -})) - +vi.mock('@sim/db', () => dbChainMock) vi.mock('drizzle-orm', () => ({ and: vi.fn(), eq: vi.fn(), })) -vi.mock('@sim/db', () => ({ - db: { - select: mockDbSelect, - }, -})) - -vi.mock('@sim/db/schema', () => ({ - form: { - id: 'id', - identifier: 'identifier', - title: 'title', - workflowId: 'workflowId', - isActive: 'isActive', - }, -})) - -vi.mock('@/lib/auth/hybrid', () => ({ - AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, - checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, -})) - -vi.mock('@/lib/workflows/utils', () => ({ - authorizeWorkflowByWorkspacePermission: mockAuthorizeWorkflowByWorkspacePermission, -})) +vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) import { GET } from '@/app/api/workflows/[id]/form/status/route' describe('Workflow Form Status Route', () => { beforeEach(() => { vi.clearAllMocks() - - mockDbSelect.mockReturnValue({ from: mockDbFrom }) - mockDbFrom.mockReturnValue({ where: mockDbWhere }) - mockDbWhere.mockReturnValue({ limit: mockDbLimit }) - mockDbLimit.mockResolvedValue([]) + resetDbChainMock() }) it('returns 401 when unauthenticated', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: false }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: false }) const req = new NextRequest('http://localhost:3000/api/workflows/wf-1/form/status') const response = await GET(req, { params: Promise.resolve({ id: 'wf-1' }) }) @@ -74,12 +40,12 @@ describe('Workflow Form Status Route', () => { }) it('returns 403 when user lacks workspace access', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, userId: 'user-1', authType: 'session', }) - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ allowed: false, status: 403, message: 'Access denied', @@ -94,18 +60,18 @@ describe('Workflow Form Status Route', () => { }) it('returns deployed form when authorized', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, userId: 'user-1', authType: 'session', }) - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ allowed: true, status: 200, workflow: { id: 'wf-1', workspaceId: 'ws-1' }, workspacePermission: 'read', }) - mockDbLimit.mockResolvedValueOnce([ + dbChainMockFns.limit.mockResolvedValueOnce([ { id: 'form-1', identifier: 'feedback-form', diff --git a/apps/sim/app/api/workflows/[id]/route.test.ts b/apps/sim/app/api/workflows/[id]/route.test.ts index 383594b5453..8201561adc1 100644 --- a/apps/sim/app/api/workflows/[id]/route.test.ts +++ b/apps/sim/app/api/workflows/[id]/route.test.ts @@ -5,16 +5,27 @@ * @vitest-environment node */ -import { auditMock, envMock, loggerMock, requestUtilsMock, telemetryMock } from '@sim/testing' +import { + auditMock, + envMock, + hybridAuthMockFns, + telemetryMock, + workflowsOrchestrationMock, + workflowsOrchestrationMockFns, + workflowsPersistenceUtilsMock, + workflowsPersistenceUtilsMockFns, + workflowsUtilsMock, + workflowsUtilsMockFns, +} from '@sim/testing' import { NextRequest } from 'next/server' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -const mockCheckHybridAuth = vi.fn() -const mockCheckSessionOrInternalAuth = vi.fn() -const mockLoadWorkflowFromNormalizedTables = vi.fn() -const mockGetWorkflowById = vi.fn() -const mockAuthorizeWorkflowByWorkspacePermission = vi.fn() -const mockPerformDeleteWorkflow = vi.fn() +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const mockLoadWorkflowFromNormalizedTables = + workflowsPersistenceUtilsMockFns.mockLoadWorkflowFromNormalizedTables +const mockGetWorkflowById = workflowsUtilsMockFns.mockGetWorkflowById +const mockAuthorizeWorkflowByWorkspacePermission = + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission +const mockPerformDeleteWorkflow = workflowsOrchestrationMockFns.mockPerformDeleteWorkflow const mockDbUpdate = vi.fn() const mockDbSelect = vi.fn() @@ -23,51 +34,31 @@ const mockDbSelect = vi.fn() */ function mockGetSession(session: { user: { id: string } } | null) { if (session) { - mockCheckHybridAuth.mockResolvedValue({ success: true, userId: session.user.id }) - mockCheckSessionOrInternalAuth.mockResolvedValue({ success: true, userId: session.user.id }) + hybridAuthMockFns.mockCheckHybridAuth.mockResolvedValue({ + success: true, + userId: session.user.id, + }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: true, + userId: session.user.id, + }) } else { - mockCheckHybridAuth.mockResolvedValue({ success: false }) - mockCheckSessionOrInternalAuth.mockResolvedValue({ success: false }) + hybridAuthMockFns.mockCheckHybridAuth.mockResolvedValue({ success: false }) + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ success: false }) } } -vi.mock('@/lib/auth', () => ({ - getSession: vi.fn(), -})) - -vi.mock('@/lib/auth/hybrid', () => ({ - AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, - checkHybridAuth: (...args: unknown[]) => mockCheckHybridAuth(...args), - checkSessionOrInternalAuth: (...args: unknown[]) => mockCheckSessionOrInternalAuth(...args), -})) - vi.mock('@/lib/core/config/env', () => envMock) vi.mock('@/lib/core/telemetry', () => telemetryMock) -vi.mock('@/lib/core/utils/request', () => requestUtilsMock) - -vi.mock('@sim/logger', () => loggerMock) - vi.mock('@/lib/audit/log', () => auditMock) -vi.mock('@/lib/workflows/persistence/utils', () => ({ - loadWorkflowFromNormalizedTables: (workflowId: string) => - mockLoadWorkflowFromNormalizedTables(workflowId), -})) +vi.mock('@/lib/workflows/persistence/utils', () => workflowsPersistenceUtilsMock) -vi.mock('@/lib/workflows/utils', () => ({ - getWorkflowById: (workflowId: string) => mockGetWorkflowById(workflowId), - authorizeWorkflowByWorkspacePermission: (params: { - workflowId: string - userId: string - action?: 'read' | 'write' | 'admin' - }) => mockAuthorizeWorkflowByWorkspacePermission(params), -})) +vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) -vi.mock('@/lib/workflows/orchestration', () => ({ - performDeleteWorkflow: (...args: unknown[]) => mockPerformDeleteWorkflow(...args), -})) +vi.mock('@/lib/workflows/orchestration', () => workflowsOrchestrationMock) vi.mock('@sim/db', () => ({ db: { @@ -90,10 +81,6 @@ describe('Workflow By ID API Route', () => { mockLoadWorkflowFromNormalizedTables.mockResolvedValue(null) }) - afterEach(() => { - vi.clearAllMocks() - }) - describe('GET /api/workflows/[id]', () => { it('should return 401 when user is not authenticated', async () => { mockGetSession(null) diff --git a/apps/sim/app/api/workflows/[id]/state/route.ts b/apps/sim/app/api/workflows/[id]/state/route.ts index 0260a1129d9..05eb9e90483 100644 --- a/apps/sim/app/api/workflows/[id]/state/route.ts +++ b/apps/sim/app/api/workflows/[id]/state/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { workflow } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' import { env } from '@/lib/core/config/env' -import { toError } from '@/lib/core/utils/helpers' import { generateRequestId } from '@/lib/core/utils/request' import { getSocketServerUrl } from '@/lib/core/utils/urls' import { extractAndPersistCustomTools } from '@/lib/workflows/persistence/custom-tools-persistence' diff --git a/apps/sim/app/api/workflows/[id]/variables/route.test.ts b/apps/sim/app/api/workflows/[id]/variables/route.test.ts index 99a07d6f12b..511d46c09c7 100644 --- a/apps/sim/app/api/workflows/[id]/variables/route.test.ts +++ b/apps/sim/app/api/workflows/[id]/variables/route.test.ts @@ -4,31 +4,18 @@ * * @vitest-environment node */ -import { auditMock } from '@sim/testing' +import { + auditMock, + hybridAuthMockFns, + workflowsUtilsMock, + workflowsUtilsMockFns, +} from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockCheckSessionOrInternalAuth, mockAuthorizeWorkflowByWorkspacePermission } = vi.hoisted( - () => ({ - mockCheckSessionOrInternalAuth: vi.fn(), - mockAuthorizeWorkflowByWorkspacePermission: vi.fn(), - }) -) - vi.mock('@/lib/audit/log', () => auditMock) -vi.mock('@/lib/auth/hybrid', () => ({ - AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, - checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, -})) - -vi.mock('@/lib/workflows/utils', () => ({ - authorizeWorkflowByWorkspacePermission: mockAuthorizeWorkflowByWorkspacePermission, -})) - -vi.mock('@/lib/core/utils/request', () => ({ - generateRequestId: vi.fn().mockReturnValue('mock-request-id-12345678'), -})) +vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) import { GET, POST } from '@/app/api/workflows/[id]/variables/route' @@ -39,7 +26,7 @@ describe('Workflow Variables API Route', () => { describe('GET /api/workflows/[id]/variables', () => { it('should return 401 when user is not authenticated', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: false, error: 'Authentication required', }) @@ -55,12 +42,12 @@ describe('Workflow Variables API Route', () => { }) it('should return 404 when workflow does not exist', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, userId: 'user-123', authType: 'session', }) - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ allowed: false, status: 404, message: 'Workflow not found', @@ -88,12 +75,12 @@ describe('Workflow Variables API Route', () => { }, } - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, userId: 'user-123', authType: 'session', }) - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ allowed: true, status: 200, workflow: mockWorkflow, @@ -120,12 +107,12 @@ describe('Workflow Variables API Route', () => { }, } - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, userId: 'user-123', authType: 'session', }) - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ allowed: true, status: 200, workflow: mockWorkflow, @@ -150,12 +137,12 @@ describe('Workflow Variables API Route', () => { variables: {}, } - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, userId: 'user-123', authType: 'session', }) - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ allowed: false, status: 403, message: 'Unauthorized: Access denied to read this workflow', @@ -183,12 +170,12 @@ describe('Workflow Variables API Route', () => { }, } - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, userId: 'user-123', authType: 'session', }) - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ allowed: true, status: 200, workflow: mockWorkflow, @@ -215,12 +202,12 @@ describe('Workflow Variables API Route', () => { variables: {}, } - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, userId: 'user-123', authType: 'session', }) - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ allowed: true, status: 200, workflow: mockWorkflow, @@ -258,12 +245,12 @@ describe('Workflow Variables API Route', () => { variables: {}, } - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, userId: 'user-123', authType: 'session', }) - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ allowed: false, status: 403, message: 'Unauthorized: Access denied to write this workflow', @@ -302,12 +289,12 @@ describe('Workflow Variables API Route', () => { variables: {}, } - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, userId: 'user-123', authType: 'session', }) - mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValueOnce({ allowed: true, status: 200, workflow: mockWorkflow, @@ -332,12 +319,12 @@ describe('Workflow Variables API Route', () => { describe('Error handling', () => { it('should handle database errors gracefully', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValueOnce({ success: true, userId: 'user-123', authType: 'session', }) - mockAuthorizeWorkflowByWorkspacePermission.mockRejectedValueOnce( + workflowsUtilsMockFns.mockAuthorizeWorkflowByWorkspacePermission.mockRejectedValueOnce( new Error('Database connection failed') ) diff --git a/apps/sim/app/api/workflows/route.test.ts b/apps/sim/app/api/workflows/route.test.ts index af669d691c5..f01c4af4e06 100644 --- a/apps/sim/app/api/workflows/route.test.ts +++ b/apps/sim/app/api/workflows/route.test.ts @@ -1,24 +1,27 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { + auditMock, + createMockRequest, + hybridAuthMockFns, + permissionsMock, + permissionsMockFns, + workflowsApiUtilsMock, + workflowsPersistenceUtilsMock, + workflowsPersistenceUtilsMockFns, +} from '@sim/testing' import { drizzleOrmMock } from '@sim/testing/mocks' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { - mockCheckSessionOrInternalAuth, - mockGetUserEntityPermissions, - mockWorkflowCreated, - mockDbSelect, - mockDbInsert, -} = vi.hoisted(() => ({ - mockCheckSessionOrInternalAuth: vi.fn(), - mockGetUserEntityPermissions: vi.fn(), +const { mockWorkflowCreated, mockDbSelect, mockDbInsert } = vi.hoisted(() => ({ mockWorkflowCreated: vi.fn(), mockDbSelect: vi.fn(), mockDbInsert: vi.fn(), })) +const mockGetUserEntityPermissions = permissionsMockFns.mockGetUserEntityPermissions + vi.mock('drizzle-orm', () => ({ ...drizzleOrmMock, min: vi.fn((field) => ({ type: 'min', field })), @@ -38,55 +41,11 @@ vi.mock('@sim/db', () => ({ }, })) -vi.mock('@sim/db/schema', () => ({ - workflowFolder: { - id: 'id', - userId: 'userId', - parentId: 'parentId', - updatedAt: 'updatedAt', - workspaceId: 'workspaceId', - sortOrder: 'sortOrder', - createdAt: 'createdAt', - }, - workflow: { - id: 'id', - folderId: 'folderId', - userId: 'userId', - name: 'name', - archivedAt: 'archivedAt', - updatedAt: 'updatedAt', - workspaceId: 'workspaceId', - sortOrder: 'sortOrder', - createdAt: 'createdAt', - }, - permissions: { - entityId: 'entityId', - userId: 'userId', - entityType: 'entityType', - }, -})) - -vi.mock('@/lib/audit/log', () => ({ - recordAudit: vi.fn(), - AuditAction: { WORKFLOW_CREATED: 'workflow.created' }, - AuditResourceType: { WORKFLOW: 'workflow' }, -})) - -vi.mock('@/lib/auth/hybrid', () => ({ - AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' }, - checkHybridAuth: vi.fn(), - checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, - checkInternalAuth: vi.fn(), -})) +vi.mock('@/lib/audit/log', () => auditMock) -vi.mock('@/lib/workspaces/permissions/utils', () => ({ - getUserEntityPermissions: (...args: unknown[]) => mockGetUserEntityPermissions(...args), - workspaceExists: vi.fn(), -})) +vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) -vi.mock('@/app/api/workflows/utils', () => ({ - verifyWorkspaceMembership: vi.fn(), -})) +vi.mock('@/app/api/workflows/utils', () => workflowsApiUtilsMock) vi.mock('@/lib/core/telemetry', () => ({ PlatformEvents: { @@ -102,9 +61,7 @@ vi.mock('@/lib/workflows/defaults', () => ({ }), })) -vi.mock('@/lib/workflows/persistence/utils', () => ({ - saveWorkflowToNormalizedTables: vi.fn().mockResolvedValue({ success: true }), -})) +vi.mock('@/lib/workflows/persistence/utils', () => workflowsPersistenceUtilsMock) import { POST } from '@/app/api/workflows/route' @@ -116,13 +73,16 @@ describe('Workflows API Route - POST ordering', () => { randomUUID: vi.fn().mockReturnValue('workflow-new-id'), }) - mockCheckSessionOrInternalAuth.mockResolvedValue({ + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ success: true, userId: 'user-123', userName: 'Test User', userEmail: 'test@example.com', }) mockGetUserEntityPermissions.mockResolvedValue('write') + workflowsPersistenceUtilsMockFns.mockSaveWorkflowToNormalizedTables.mockResolvedValue({ + success: true, + }) }) it('uses top insertion against mixed siblings (folders + workflows)', async () => { diff --git a/apps/sim/app/api/workflows/route.ts b/apps/sim/app/api/workflows/route.ts index f96bd6d352f..2a61e33623a 100644 --- a/apps/sim/app/api/workflows/route.ts +++ b/apps/sim/app/api/workflows/route.ts @@ -1,13 +1,13 @@ import { db } from '@sim/db' import { permissions, workflow, workflowFolder } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, asc, eq, inArray, isNull, min, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { generateId } from '@/lib/core/utils/uuid' import { captureServerEvent } from '@/lib/posthog/server' import { getNextWorkflowColor } from '@/lib/workflows/colors' import { buildDefaultWorkflowArtifacts } from '@/lib/workflows/defaults' diff --git a/apps/sim/app/api/workspaces/[id]/_preview/create-preview-route.ts b/apps/sim/app/api/workspaces/[id]/_preview/create-preview-route.ts index 495dd9ead38..a369f9472a9 100644 --- a/apps/sim/app/api/workspaces/[id]/_preview/create-preview-route.ts +++ b/apps/sim/app/api/workspaces/[id]/_preview/create-preview-route.ts @@ -1,7 +1,7 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' -import { toError } from '@/lib/core/utils/helpers' import { MAX_DOCUMENT_PREVIEW_CODE_BYTES } from '@/lib/execution/constants' import { runSandboxTask } from '@/lib/execution/sandbox/run-task' import { verifyWorkspaceMembership } from '@/app/api/workflows/utils' diff --git a/apps/sim/app/api/workspaces/[id]/api-keys/route.ts b/apps/sim/app/api/workspaces/[id]/api-keys/route.ts index 4c156d06f94..261b60c6d2f 100644 --- a/apps/sim/app/api/workspaces/[id]/api-keys/route.ts +++ b/apps/sim/app/api/workspaces/[id]/api-keys/route.ts @@ -1,6 +1,7 @@ import { db } from '@sim/db' import { apiKey } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateShortId } from '@sim/utils/id' import { and, eq, inArray } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -9,7 +10,6 @@ import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { PlatformEvents } from '@/lib/core/telemetry' import { generateRequestId } from '@/lib/core/utils/request' -import { generateShortId } from '@/lib/core/utils/uuid' import { captureServerEvent } from '@/lib/posthog/server' import { getUserEntityPermissions, getWorkspaceById } from '@/lib/workspaces/permissions/utils' diff --git a/apps/sim/app/api/workspaces/[id]/byok-keys/route.ts b/apps/sim/app/api/workspaces/[id]/byok-keys/route.ts index 5ccda1fae77..95abedccca6 100644 --- a/apps/sim/app/api/workspaces/[id]/byok-keys/route.ts +++ b/apps/sim/app/api/workspaces/[id]/byok-keys/route.ts @@ -1,6 +1,7 @@ import { db } from '@sim/db' import { workspaceBYOKKeys } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateShortId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -8,7 +9,6 @@ import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { decryptSecret, encryptSecret } from '@/lib/core/security/encryption' import { generateRequestId } from '@/lib/core/utils/request' -import { generateShortId } from '@/lib/core/utils/uuid' import { captureServerEvent } from '@/lib/posthog/server' import { getUserEntityPermissions, getWorkspaceById } from '@/lib/workspaces/permissions/utils' diff --git a/apps/sim/app/api/workspaces/[id]/docx/preview/route.test.ts b/apps/sim/app/api/workspaces/[id]/docx/preview/route.test.ts index 17075958685..cffe9cf9aef 100644 --- a/apps/sim/app/api/workspaces/[id]/docx/preview/route.test.ts +++ b/apps/sim/app/api/workspaces/[id]/docx/preview/route.test.ts @@ -1,23 +1,18 @@ /** * @vitest-environment node */ +import { authMockFns, workflowsApiUtilsMock, workflowsApiUtilsMockFns } from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' import { MAX_DOCUMENT_PREVIEW_CODE_BYTES } from '@/lib/execution/constants' -const { mockGetSession, mockVerifyWorkspaceMembership, mockRunSandboxTask } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), - mockVerifyWorkspaceMembership: vi.fn(), +const { mockRunSandboxTask } = vi.hoisted(() => ({ mockRunSandboxTask: vi.fn(), })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) +const mockVerifyWorkspaceMembership = workflowsApiUtilsMockFns.mockVerifyWorkspaceMembership -vi.mock('@/app/api/workflows/utils', () => ({ - verifyWorkspaceMembership: mockVerifyWorkspaceMembership, -})) +vi.mock('@/app/api/workflows/utils', () => workflowsApiUtilsMock) vi.mock('@/lib/execution/sandbox/run-task', () => ({ runSandboxTask: mockRunSandboxTask, @@ -30,7 +25,7 @@ const DOCX_MIME = 'application/vnd.openxmlformats-officedocument.wordprocessingm describe('DOCX preview API route', () => { beforeEach(() => { vi.clearAllMocks() - mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) mockVerifyWorkspaceMembership.mockResolvedValue(true) mockRunSandboxTask.mockResolvedValue(Buffer.from('PK\x03\x04docx')) }) @@ -106,7 +101,7 @@ describe('DOCX preview API route', () => { }) it('returns 401 for unauthenticated requests', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const request = new NextRequest( 'http://localhost:3000/api/workspaces/workspace-1/docx/preview', diff --git a/apps/sim/app/api/workspaces/[id]/environment/route.ts b/apps/sim/app/api/workspaces/[id]/environment/route.ts index 67b1eddeb7a..14d17a4cb27 100644 --- a/apps/sim/app/api/workspaces/[id]/environment/route.ts +++ b/apps/sim/app/api/workspaces/[id]/environment/route.ts @@ -1,6 +1,7 @@ import { db } from '@sim/db' import { workspaceEnvironment } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -8,7 +9,6 @@ import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { encryptSecret } from '@/lib/core/security/encryption' import { generateRequestId } from '@/lib/core/utils/request' -import { generateId } from '@/lib/core/utils/uuid' import { syncWorkspaceEnvCredentials } from '@/lib/credentials/environment' import { getPersonalAndWorkspaceEnv } from '@/lib/environment/utils' import { getUserEntityPermissions, getWorkspaceById } from '@/lib/workspaces/permissions/utils' diff --git a/apps/sim/app/api/workspaces/[id]/inbox/senders/route.ts b/apps/sim/app/api/workspaces/[id]/inbox/senders/route.ts index 488f819a48f..21d20c688db 100644 --- a/apps/sim/app/api/workspaces/[id]/inbox/senders/route.ts +++ b/apps/sim/app/api/workspaces/[id]/inbox/senders/route.ts @@ -1,11 +1,11 @@ import { db, mothershipInboxAllowedSender, permissions, user } from '@sim/db' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { hasInboxAccess } from '@/lib/billing/core/subscription' -import { generateId } from '@/lib/core/utils/uuid' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' const logger = createLogger('InboxSendersAPI') diff --git a/apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/test/route.ts b/apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/test/route.ts index a2cf4ad4848..050e21bfccb 100644 --- a/apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/test/route.ts +++ b/apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/test/route.ts @@ -2,6 +2,8 @@ import { createHmac } from 'crypto' import { db } from '@sim/db' import { account, workspaceNotificationSubscription } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { @@ -12,9 +14,7 @@ import { import { getSession } from '@/lib/auth' import { decryptSecret } from '@/lib/core/security/encryption' import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server' -import { toError } from '@/lib/core/utils/helpers' import { getBaseUrl } from '@/lib/core/utils/urls' -import { generateId } from '@/lib/core/utils/uuid' import { sendEmail } from '@/lib/messaging/email/mailer' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' diff --git a/apps/sim/app/api/workspaces/[id]/notifications/route.ts b/apps/sim/app/api/workspaces/[id]/notifications/route.ts index 3ad7532f8e8..14b9fe56f5d 100644 --- a/apps/sim/app/api/workspaces/[id]/notifications/route.ts +++ b/apps/sim/app/api/workspaces/[id]/notifications/route.ts @@ -1,13 +1,13 @@ import { db } from '@sim/db' import { workflow, workspaceNotificationSubscription } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, inArray } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { encryptSecret } from '@/lib/core/security/encryption' -import { generateId } from '@/lib/core/utils/uuid' import { captureServerEvent } from '@/lib/posthog/server' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' import { MAX_EMAIL_RECIPIENTS, MAX_NOTIFICATIONS_PER_TYPE, MAX_WORKFLOW_IDS } from './constants' diff --git a/apps/sim/app/api/workspaces/[id]/pdf/preview/route.test.ts b/apps/sim/app/api/workspaces/[id]/pdf/preview/route.test.ts index e9592f3ed76..cf5bd49e454 100644 --- a/apps/sim/app/api/workspaces/[id]/pdf/preview/route.test.ts +++ b/apps/sim/app/api/workspaces/[id]/pdf/preview/route.test.ts @@ -1,23 +1,18 @@ /** * @vitest-environment node */ +import { authMockFns, workflowsApiUtilsMock, workflowsApiUtilsMockFns } from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' import { MAX_DOCUMENT_PREVIEW_CODE_BYTES } from '@/lib/execution/constants' -const { mockGetSession, mockVerifyWorkspaceMembership, mockRunSandboxTask } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), - mockVerifyWorkspaceMembership: vi.fn(), +const { mockRunSandboxTask } = vi.hoisted(() => ({ mockRunSandboxTask: vi.fn(), })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) +const mockVerifyWorkspaceMembership = workflowsApiUtilsMockFns.mockVerifyWorkspaceMembership -vi.mock('@/app/api/workflows/utils', () => ({ - verifyWorkspaceMembership: mockVerifyWorkspaceMembership, -})) +vi.mock('@/app/api/workflows/utils', () => workflowsApiUtilsMock) vi.mock('@/lib/execution/sandbox/run-task', () => ({ runSandboxTask: mockRunSandboxTask, @@ -28,7 +23,7 @@ import { POST } from '@/app/api/workspaces/[id]/pdf/preview/route' describe('PDF preview API route', () => { beforeEach(() => { vi.clearAllMocks() - mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) mockVerifyWorkspaceMembership.mockResolvedValue(true) mockRunSandboxTask.mockResolvedValue(Buffer.from('%PDF-test')) }) @@ -104,7 +99,7 @@ describe('PDF preview API route', () => { }) it('returns 401 for unauthenticated requests', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const request = new NextRequest( 'http://localhost:3000/api/workspaces/workspace-1/pdf/preview', diff --git a/apps/sim/app/api/workspaces/[id]/permissions/route.ts b/apps/sim/app/api/workspaces/[id]/permissions/route.ts index e7ee5385597..d5add813020 100644 --- a/apps/sim/app/api/workspaces/[id]/permissions/route.ts +++ b/apps/sim/app/api/workspaces/[id]/permissions/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { permissions, user, workspace, workspaceEnvironment } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' -import { generateId } from '@/lib/core/utils/uuid' import { syncWorkspaceEnvCredentials } from '@/lib/credentials/environment' import { captureServerEvent } from '@/lib/posthog/server' import { diff --git a/apps/sim/app/api/workspaces/[id]/pptx/preview/route.test.ts b/apps/sim/app/api/workspaces/[id]/pptx/preview/route.test.ts index 776ba4120e4..08a8e11f889 100644 --- a/apps/sim/app/api/workspaces/[id]/pptx/preview/route.test.ts +++ b/apps/sim/app/api/workspaces/[id]/pptx/preview/route.test.ts @@ -1,23 +1,18 @@ /** * @vitest-environment node */ +import { authMockFns, workflowsApiUtilsMock, workflowsApiUtilsMockFns } from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' import { MAX_DOCUMENT_PREVIEW_CODE_BYTES } from '@/lib/execution/constants' -const { mockGetSession, mockVerifyWorkspaceMembership, mockRunSandboxTask } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), - mockVerifyWorkspaceMembership: vi.fn(), +const { mockRunSandboxTask } = vi.hoisted(() => ({ mockRunSandboxTask: vi.fn(), })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) +const mockVerifyWorkspaceMembership = workflowsApiUtilsMockFns.mockVerifyWorkspaceMembership -vi.mock('@/app/api/workflows/utils', () => ({ - verifyWorkspaceMembership: mockVerifyWorkspaceMembership, -})) +vi.mock('@/app/api/workflows/utils', () => workflowsApiUtilsMock) vi.mock('@/lib/execution/sandbox/run-task', () => ({ runSandboxTask: mockRunSandboxTask, @@ -30,7 +25,7 @@ const PPTX_MIME = 'application/vnd.openxmlformats-officedocument.presentationml. describe('PPTX preview API route', () => { beforeEach(() => { vi.clearAllMocks() - mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) mockVerifyWorkspaceMembership.mockResolvedValue(true) mockRunSandboxTask.mockResolvedValue(Buffer.from('PK\x03\x04pptx')) }) @@ -106,7 +101,7 @@ describe('PPTX preview API route', () => { }) it('returns 401 for unauthenticated requests', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const request = new NextRequest( 'http://localhost:3000/api/workspaces/workspace-1/pptx/preview', diff --git a/apps/sim/app/api/workspaces/invitations/[invitationId]/route.test.ts b/apps/sim/app/api/workspaces/invitations/[invitationId]/route.test.ts index 5d1681f1052..6c6262769c4 100644 --- a/apps/sim/app/api/workspaces/invitations/[invitationId]/route.test.ts +++ b/apps/sim/app/api/workspaces/invitations/[invitationId]/route.test.ts @@ -1,10 +1,16 @@ -import { auditMock, createSession, createWorkspaceRecord, loggerMock } from '@sim/testing' +import { + auditMock, + authMockFns, + createSession, + createWorkspaceRecord, + permissionsMock, + permissionsMockFns, +} from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' -const mockGetSession = vi.fn() -const mockHasWorkspaceAdminAccess = vi.fn() -const mockGetWorkspaceById = vi.fn() +const mockHasWorkspaceAdminAccess = permissionsMockFns.mockHasWorkspaceAdminAccess +const mockGetWorkspaceById = permissionsMockFns.mockGetWorkspaceById let dbSelectResults: any[] = [] let dbSelectCallIndex = 0 @@ -57,22 +63,12 @@ const mockDbTransaction = vi.fn().mockImplementation(async (callback: any) => { }) }) -vi.mock('@/lib/auth', () => ({ - getSession: () => mockGetSession(), -})) - -vi.mock('@/lib/workspaces/permissions/utils', () => ({ - hasWorkspaceAdminAccess: (userId: string, workspaceId: string) => - mockHasWorkspaceAdminAccess(userId, workspaceId), - getWorkspaceById: (id: string) => mockGetWorkspaceById(id), -})) +vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) vi.mock('@/lib/credentials/environment', () => ({ syncWorkspaceEnvCredentials: vi.fn().mockResolvedValue(undefined), })) -vi.mock('@sim/logger', () => loggerMock) - vi.mock('@/lib/audit/log', () => auditMock) vi.mock('@/lib/core/utils/urls', () => ({ @@ -105,45 +101,13 @@ vi.mock('@sim/db', () => ({ }, })) -vi.mock('@sim/db/schema', () => ({ - workspaceInvitation: { - id: 'id', - workspaceId: 'workspaceId', - email: 'email', - inviterId: 'inviterId', - status: 'status', - token: 'token', - permissions: 'permissions', - expiresAt: 'expiresAt', - }, - workspace: { - id: 'id', - name: 'name', - }, - user: { - id: 'id', - email: 'email', - }, - permissions: { - id: 'id', - entityType: 'entityType', - entityId: 'entityId', - userId: 'userId', - permissionType: 'permissionType', - }, - workspaceEnvironment: { - workspaceId: 'workspaceId', - variables: 'variables', - }, -})) - vi.mock('drizzle-orm', () => ({ eq: vi.fn((a: unknown, b: unknown) => ({ type: 'eq', a, b })), and: vi.fn((...args: unknown[]) => ({ type: 'and', args })), isNull: vi.fn((field: unknown) => ({ type: 'isNull', field })), })) -vi.mock('@/lib/core/utils/uuid', () => ({ +vi.mock('@sim/utils/id', () => ({ generateId: vi.fn().mockReturnValue('mock-uuid-1234'), generateShortId: vi.fn(() => 'mock-short-id'), isValidUuid: vi.fn((v: string) => @@ -193,7 +157,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { describe('GET /api/workspaces/invitations/[invitationId]', () => { it('should return invitation details when caller is the invitee', async () => { const session = createSession({ userId: mockUser.id, email: 'invited@example.com' }) - mockGetSession.mockResolvedValue(session) + authMockFns.mockGetSession.mockResolvedValue(session) mockHasWorkspaceAdminAccess.mockResolvedValue(false) dbSelectResults = [[mockInvitation], [mockWorkspace]] @@ -214,7 +178,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { it('should return invitation details when caller is a workspace admin', async () => { const session = createSession({ userId: mockUser.id, email: mockUser.email }) - mockGetSession.mockResolvedValue(session) + authMockFns.mockGetSession.mockResolvedValue(session) mockHasWorkspaceAdminAccess.mockResolvedValue(true) dbSelectResults = [[mockInvitation], [mockWorkspace]] @@ -235,7 +199,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { it('should return 403 when caller is neither invitee nor workspace admin', async () => { const session = createSession({ userId: mockUser.id, email: 'unrelated@example.com' }) - mockGetSession.mockResolvedValue(session) + authMockFns.mockGetSession.mockResolvedValue(session) mockHasWorkspaceAdminAccess.mockResolvedValue(false) dbSelectResults = [[mockInvitation], [mockWorkspace]] @@ -250,7 +214,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { }) it('should redirect to login when unauthenticated with token', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const request = new NextRequest( 'http://localhost/api/workspaces/invitations/token-abc123?token=token-abc123' @@ -266,7 +230,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { }) it('should return 401 when unauthenticated without token', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const request = new NextRequest('http://localhost/api/workspaces/invitations/invitation-789') const params = Promise.resolve({ invitationId: 'invitation-789' }) @@ -284,7 +248,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { email: 'invited@example.com', name: mockUser.name, }) - mockGetSession.mockResolvedValue(session) + authMockFns.mockGetSession.mockResolvedValue(session) dbSelectResults = [ [mockInvitation], @@ -313,7 +277,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { email: 'invited@example.com', name: mockUser.name, }) - mockGetSession.mockResolvedValue(session) + authMockFns.mockGetSession.mockResolvedValue(session) const expiredInvitation = { ...mockInvitation, @@ -342,7 +306,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { email: 'wrong@example.com', name: mockUser.name, }) - mockGetSession.mockResolvedValue(session) + authMockFns.mockGetSession.mockResolvedValue(session) dbSelectResults = [ [mockInvitation], @@ -366,7 +330,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { it('should return 404 when invitation not found (without token)', async () => { const session = createSession({ userId: mockUser.id, email: mockUser.email }) - mockGetSession.mockResolvedValue(session) + authMockFns.mockGetSession.mockResolvedValue(session) dbSelectResults = [[]] const request = new NextRequest('http://localhost/api/workspaces/invitations/non-existent') @@ -381,7 +345,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { it('should redirect to error page with token preserved when invitation not found (with token)', async () => { const session = createSession({ userId: mockUser.id, email: mockUser.email }) - mockGetSession.mockResolvedValue(session) + authMockFns.mockGetSession.mockResolvedValue(session) dbSelectResults = [[]] const request = new NextRequest( @@ -404,7 +368,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { email: 'invited@example.com', name: mockUser.name, }) - mockGetSession.mockResolvedValue(session) + authMockFns.mockGetSession.mockResolvedValue(session) const acceptedInvitation = { ...mockInvitation, @@ -433,7 +397,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { email: 'invited@example.com', name: mockUser.name, }) - mockGetSession.mockResolvedValue(session) + authMockFns.mockGetSession.mockResolvedValue(session) dbSelectResults = [[mockInvitation], []] @@ -457,7 +421,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { email: 'invited@example.com', name: mockUser.name, }) - mockGetSession.mockResolvedValue(session) + authMockFns.mockGetSession.mockResolvedValue(session) dbSelectResults = [[mockInvitation], [mockWorkspace], []] @@ -481,7 +445,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { email: 'wrong@example.com', name: mockUser.name, }) - mockGetSession.mockResolvedValue(session) + authMockFns.mockGetSession.mockResolvedValue(session) dbSelectResults = [ [mockInvitation], @@ -511,7 +475,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { email: 'wrong@example.com', name: 'Wrong User', }) - mockGetSession.mockResolvedValue(wrongSession) + authMockFns.mockGetSession.mockResolvedValue(wrongSession) dbSelectResults = [ [mockInvitation], @@ -540,7 +504,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { email: 'invited@example.com', name: mockUser.name, }) - mockGetSession.mockResolvedValue(correctSession) + authMockFns.mockGetSession.mockResolvedValue(correctSession) dbSelectResults = [ [mockInvitation], @@ -566,7 +530,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { describe('DELETE /api/workspaces/invitations/[invitationId]', () => { it('should return 401 when user is not authenticated', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const request = new NextRequest( 'http://localhost/api/workspaces/invitations/invitation-789', @@ -583,7 +547,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { it('should return 404 when invitation does not exist', async () => { const session = createSession({ userId: mockUser.id, email: mockUser.email }) - mockGetSession.mockResolvedValue(session) + authMockFns.mockGetSession.mockResolvedValue(session) dbSelectResults = [[]] const request = new NextRequest('http://localhost/api/workspaces/invitations/non-existent', { @@ -600,7 +564,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { it('should return 403 when user lacks admin access', async () => { const session = createSession({ userId: mockUser.id, email: mockUser.email }) - mockGetSession.mockResolvedValue(session) + authMockFns.mockGetSession.mockResolvedValue(session) mockHasWorkspaceAdminAccess.mockResolvedValue(false) dbSelectResults = [[mockInvitation]] @@ -620,7 +584,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { it('should return 400 when trying to delete non-pending invitation', async () => { const session = createSession({ userId: mockUser.id, email: mockUser.email }) - mockGetSession.mockResolvedValue(session) + authMockFns.mockGetSession.mockResolvedValue(session) mockHasWorkspaceAdminAccess.mockResolvedValue(true) const acceptedInvitation = { ...mockInvitation, status: 'accepted' } @@ -641,7 +605,7 @@ describe('Workspace Invitation [invitationId] API Route', () => { it('should successfully delete pending invitation when user has admin access', async () => { const session = createSession({ userId: mockUser.id, email: mockUser.email }) - mockGetSession.mockResolvedValue(session) + authMockFns.mockGetSession.mockResolvedValue(session) mockHasWorkspaceAdminAccess.mockResolvedValue(true) dbSelectResults = [[mockInvitation]] diff --git a/apps/sim/app/api/workspaces/invitations/[invitationId]/route.ts b/apps/sim/app/api/workspaces/invitations/[invitationId]/route.ts index d76322d4e5f..2580c0df140 100644 --- a/apps/sim/app/api/workspaces/invitations/[invitationId]/route.ts +++ b/apps/sim/app/api/workspaces/invitations/[invitationId]/route.ts @@ -9,13 +9,13 @@ import { workspaceInvitation, } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, isNull } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { WorkspaceInvitationEmail } from '@/components/emails' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { getBaseUrl } from '@/lib/core/utils/urls' -import { generateId } from '@/lib/core/utils/uuid' import { syncWorkspaceEnvCredentials } from '@/lib/credentials/environment' import { sendEmail } from '@/lib/messaging/email/mailer' import { getFromEmailAddress } from '@/lib/messaging/email/utils' diff --git a/apps/sim/app/api/workspaces/invitations/route.test.ts b/apps/sim/app/api/workspaces/invitations/route.test.ts index 20b697fd8e1..63e02a2fc70 100644 --- a/apps/sim/app/api/workspaces/invitations/route.test.ts +++ b/apps/sim/app/api/workspaces/invitations/route.test.ts @@ -1,11 +1,10 @@ /** * @vitest-environment node */ -import { createMockRequest } from '@sim/testing' +import { authMockFns, createMockRequest, permissionsMock, permissionsMockFns } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const { - mockGetSession, mockInsertValues, mockDbResults, mockResendSend, @@ -15,9 +14,7 @@ const { mockGetEmailDomain, mockValidateInvitationsAllowed, mockRandomUUID, - mockGetWorkspaceById, } = vi.hoisted(() => { - const mockGetSession = vi.fn() const mockInsertValues = vi.fn().mockResolvedValue(undefined) const mockResendSend = vi.fn().mockResolvedValue({ id: 'email-id' }) const mockRender = vi.fn().mockResolvedValue('email content') @@ -25,7 +22,6 @@ const { const mockGetEmailDomain = vi.fn().mockReturnValue('sim.ai') const mockValidateInvitationsAllowed = vi.fn().mockResolvedValue(undefined) const mockRandomUUID = vi.fn().mockReturnValue('mock-uuid-1234') - const mockGetWorkspaceById = vi.fn() const mockDbResults: { value: any[] } = { value: [] } @@ -44,7 +40,6 @@ const { } return { - mockGetSession, mockInsertValues, mockDbResults, mockResendSend, @@ -54,11 +49,12 @@ const { mockGetEmailDomain, mockValidateInvitationsAllowed, mockRandomUUID, - mockGetWorkspaceById, } }) -vi.mock('@/lib/core/utils/uuid', () => ({ +const mockGetWorkspaceById = permissionsMockFns.mockGetWorkspaceById + +vi.mock('@sim/utils/id', () => ({ generateId: mockRandomUUID, generateShortId: vi.fn(() => 'mock-short-id'), isValidUuid: vi.fn((v: string) => @@ -66,39 +62,10 @@ vi.mock('@/lib/core/utils/uuid', () => ({ ), })) -vi.mock('@/lib/auth', () => ({ - getSession: mockGetSession, -})) - vi.mock('@sim/db', () => ({ db: mockDbChain, })) -vi.mock('@sim/db/schema', () => ({ - user: { id: 'user_id', email: 'user_email', name: 'user_name', image: 'user_image' }, - workspace: { id: 'workspace_id', name: 'workspace_name', ownerId: 'owner_id' }, - permissions: { - userId: 'user_id', - entityId: 'entity_id', - entityType: 'entity_type', - permissionType: 'permission_type', - }, - workspaceInvitation: { - id: 'invitation_id', - workspaceId: 'workspace_id', - email: 'invitation_email', - status: 'invitation_status', - token: 'invitation_token', - inviterId: 'inviter_id', - role: 'invitation_role', - permissions: 'invitation_permissions', - expiresAt: 'expires_at', - createdAt: 'created_at', - updatedAt: 'updated_at', - }, - permissionTypeEnum: { enumValues: ['admin', 'write', 'read'] as const }, -})) - vi.mock('resend', () => ({ Resend: vi.fn().mockImplementation(() => ({ emails: { send: mockResendSend }, @@ -118,9 +85,7 @@ vi.mock('@/lib/core/config/env', async () => { return createEnvMock() }) -vi.mock('@/lib/workspaces/permissions/utils', () => ({ - getWorkspaceById: mockGetWorkspaceById, -})) +vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) vi.mock('@/lib/core/utils/urls', () => ({ getEmailDomain: mockGetEmailDomain, @@ -131,15 +96,6 @@ vi.mock('@/lib/audit/log', async () => { return auditMock }) -vi.mock('@sim/logger', () => ({ - createLogger: vi.fn().mockReturnValue({ - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - }), -})) - vi.mock('drizzle-orm', () => ({ and: vi.fn().mockImplementation((...args: any[]) => ({ type: 'and', conditions: args })), eq: vi.fn().mockImplementation((field: any, value: any) => ({ type: 'eq', field, value })), @@ -193,7 +149,7 @@ describe('Workspace Invitations API Route', () => { describe('GET /api/workspaces/invitations', () => { it('should return 401 when user is not authenticated', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = createMockRequest('GET') const response = await GET(req) @@ -204,7 +160,7 @@ describe('Workspace Invitations API Route', () => { }) it('should return empty invitations when user has no workspaces', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) mockDbResults.value = [[], []] // No workspaces, no invitations const req = createMockRequest('GET') @@ -216,7 +172,7 @@ describe('Workspace Invitations API Route', () => { }) it('should return invitations for user workspaces', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const mockWorkspaces = [{ id: 'workspace-1' }, { id: 'workspace-2' }] const mockInvitations = [ { id: 'invitation-1', workspaceId: 'workspace-1', email: 'test@example.com' }, @@ -235,7 +191,7 @@ describe('Workspace Invitations API Route', () => { describe('POST /api/workspaces/invitations', () => { it('should return 401 when user is not authenticated', async () => { - mockGetSession.mockResolvedValue(null) + authMockFns.mockGetSession.mockResolvedValue(null) const req = createMockRequest('POST', { workspaceId: 'workspace-1', @@ -249,7 +205,7 @@ describe('Workspace Invitations API Route', () => { }) it('should return 400 when workspaceId is missing', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const req = createMockRequest('POST', { email: 'test@example.com' }) const response = await POST(req) @@ -260,7 +216,7 @@ describe('Workspace Invitations API Route', () => { }) it('should return 400 when email is missing', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const req = createMockRequest('POST', { workspaceId: 'workspace-1' }) const response = await POST(req) @@ -271,7 +227,7 @@ describe('Workspace Invitations API Route', () => { }) it('should return 400 when permission type is invalid', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) const req = createMockRequest('POST', { workspaceId: 'workspace-1', @@ -288,7 +244,7 @@ describe('Workspace Invitations API Route', () => { }) it('should return 403 when user does not have admin permissions', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) mockDbResults.value = [[]] // No admin permissions found const req = createMockRequest('POST', { @@ -303,7 +259,7 @@ describe('Workspace Invitations API Route', () => { }) it('should return 404 when workspace is not found', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) mockGetWorkspaceById.mockResolvedValueOnce(null) mockDbResults.value = [ [{ permissionType: 'admin' }], // User has admin permissions @@ -321,7 +277,7 @@ describe('Workspace Invitations API Route', () => { }) it('should return 400 when user already has workspace access', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) mockDbResults.value = [ [{ permissionType: 'admin' }], // User has admin permissions [mockWorkspace], // Workspace exists @@ -344,7 +300,7 @@ describe('Workspace Invitations API Route', () => { }) it('should return 400 when invitation already exists', async () => { - mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123' } }) mockDbResults.value = [ [{ permissionType: 'admin' }], // User has admin permissions [mockWorkspace], // Workspace exists @@ -367,7 +323,7 @@ describe('Workspace Invitations API Route', () => { }) it('should successfully create invitation and send email', async () => { - mockGetSession.mockResolvedValue({ + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123', name: 'Test User', email: 'sender@example.com' }, }) mockDbResults.value = [ diff --git a/apps/sim/app/api/workspaces/invitations/route.ts b/apps/sim/app/api/workspaces/invitations/route.ts index 020e350dbb2..ff1edf8d55c 100644 --- a/apps/sim/app/api/workspaces/invitations/route.ts +++ b/apps/sim/app/api/workspaces/invitations/route.ts @@ -9,6 +9,7 @@ import { workspaceInvitation, } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, eq, inArray, isNull } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { WorkspaceInvitationEmail } from '@/components/emails' @@ -16,7 +17,6 @@ import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { PlatformEvents } from '@/lib/core/telemetry' import { getBaseUrl } from '@/lib/core/utils/urls' -import { generateId } from '@/lib/core/utils/uuid' import { sendEmail } from '@/lib/messaging/email/mailer' import { getFromEmailAddress } from '@/lib/messaging/email/utils' import { captureServerEvent } from '@/lib/posthog/server' diff --git a/apps/sim/app/api/workspaces/route.ts b/apps/sim/app/api/workspaces/route.ts index f8350ec88bd..8bce8063fad 100644 --- a/apps/sim/app/api/workspaces/route.ts +++ b/apps/sim/app/api/workspaces/route.ts @@ -1,13 +1,13 @@ import { db } from '@sim/db' import { permissions, settings, workflow, workspace } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { and, desc, eq, isNull, sql } from 'drizzle-orm' import { NextResponse } from 'next/server' import { z } from 'zod' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { PlatformEvents } from '@/lib/core/telemetry' -import { generateId } from '@/lib/core/utils/uuid' import { captureServerEvent } from '@/lib/posthog/server' import { buildDefaultWorkflowArtifacts } from '@/lib/workflows/defaults' import { saveWorkflowToNormalizedTables } from '@/lib/workflows/persistence/utils' diff --git a/apps/sim/app/chat/[identifier]/chat.tsx b/apps/sim/app/chat/[identifier]/chat.tsx index d179385c59d..c6bda939c2d 100644 --- a/apps/sim/app/chat/[identifier]/chat.tsx +++ b/apps/sim/app/chat/[identifier]/chat.tsx @@ -2,9 +2,8 @@ import { type RefObject, useCallback, useEffect, useRef, useState } from 'react' import { createLogger } from '@sim/logger' +import { generateId } from '@sim/utils/id' import { noop } from '@/lib/core/utils/request' -import { generateId } from '@/lib/core/utils/uuid' -import { getFormattedGitHubStars } from '@/app/(landing)/actions/github' import { ChatErrorState, ChatHeader, @@ -19,34 +18,37 @@ import { import { CHAT_ERROR_MESSAGES, CHAT_REQUEST_TIMEOUT_MS } from '@/app/chat/constants' import { useAudioStreaming, useChatStreaming } from '@/app/chat/hooks' import SSOAuth from '@/ee/sso/components/sso-auth' +import { useDeployedChatConfig } from '@/hooks/queries/chats' +import { useGitHubStars } from '@/hooks/queries/github-stars' +import { useVoiceSettings } from '@/hooks/queries/voice-settings' const logger = createLogger('ChatClient') -interface ChatConfig { - id: string - title: string - description: string - customizations: { - primaryColor?: string - logoUrl?: string - imageUrl?: string - welcomeMessage?: string - headerText?: string - } - authType?: 'public' | 'password' | 'email' | 'sso' - outputConfigs?: Array<{ blockId: string; path?: string }> -} - interface AudioStreamingOptions { voiceId: string chatId?: string onError: (error: Error) => void } +interface ChatRequestFile { + name: string + size: number + type: string + data: string +} + +interface ChatRequestPayload { + input: string + conversationId: string + files?: ChatRequestFile[] +} + const DEFAULT_VOICE_SETTINGS = { voiceId: 'EXAVITQu4vr4xnSDxMaL', // Default ElevenLabs voice (Bella) } +const INITIAL_STARS = '25.8k' + /** * Converts a File object to a base64 data URL */ @@ -86,55 +88,42 @@ function createAudioStreamHandler( } } -function throttle any>(func: T, delay: number): T { - let timeoutId: NodeJS.Timeout | null = null - let lastExecTime = 0 - - return ((...args: Parameters) => { - const currentTime = Date.now() - - if (currentTime - lastExecTime > delay) { - func(...args) - lastExecTime = currentTime - } else { - if (timeoutId) clearTimeout(timeoutId) - timeoutId = setTimeout( - () => { - func(...args) - lastExecTime = Date.now() - }, - delay - (currentTime - lastExecTime) - ) - } - }) as T -} - export default function ChatClient({ identifier }: { identifier: string }) { const [messages, setMessages] = useState([]) const [inputValue, setInputValue] = useState('') const [isLoading, setIsLoading] = useState(false) - const [chatConfig, setChatConfig] = useState(null) - const [error, setError] = useState(null) const messagesEndRef = useRef(null) const messagesContainerRef = useRef(null) - const [starCount, setStarCount] = useState('25.8k') - const [conversationId, setConversationId] = useState('') + const [conversationId] = useState(() => generateId()) const [showScrollButton, setShowScrollButton] = useState(false) const [userHasScrolled, setUserHasScrolled] = useState(false) const isUserScrollingRef = useRef(false) - const [authRequired, setAuthRequired] = useState<'password' | 'email' | 'sso' | null>(null) - const [isVoiceFirstMode, setIsVoiceFirstMode] = useState(false) - const [sttAvailable, setSttAvailable] = useState(false) - useEffect(() => { - fetch('/api/settings/voice') - .then((r) => (r.ok ? r.json() : { sttAvailable: false })) - .then((data) => setSttAvailable(data.sttAvailable === true)) - .catch(() => setSttAvailable(false)) - }, []) + const { data: chatConfigResult, error: chatConfigError } = useDeployedChatConfig(identifier) + const { data: voiceSettings } = useVoiceSettings() + const { data: starCount } = useGitHubStars() + + const sttAvailable = voiceSettings?.sttAvailable === true + const authRequired = chatConfigResult?.kind === 'auth' ? chatConfigResult.authType : null + const chatConfig = chatConfigResult?.kind === 'config' ? chatConfigResult.config : null + + const welcomeMessage = chatConfig?.customizations?.welcomeMessage + const displayMessages: ChatMessage[] = welcomeMessage + ? [ + { + id: 'welcome', + content: welcomeMessage, + type: 'assistant', + timestamp: new Date(), + isInitialMessage: true, + }, + ...messages, + ] + : messages + const { isStreamingResponse, abortControllerRef, stopStreaming, handleStreamedResponse } = useChatStreaming() const audioContextRef = useRef(null) @@ -174,29 +163,26 @@ export default function ChatClient({ identifier }: { identifier: string }) { [messagesContainerRef] ) - const handleScroll = useCallback( - throttle(() => { - const container = messagesContainerRef.current - if (!container) return + const isStreamingResponseRef = useRef(isStreamingResponse) + isStreamingResponseRef.current = isStreamingResponse + useEffect(() => { + const container = messagesContainerRef.current + if (!container) return + + const handleScroll = () => { const { scrollTop, scrollHeight, clientHeight } = container const distanceFromBottom = scrollHeight - scrollTop - clientHeight setShowScrollButton(distanceFromBottom > 100) - if (isStreamingResponse && !isUserScrollingRef.current) { + if (isStreamingResponseRef.current && !isUserScrollingRef.current) { setUserHasScrolled(true) } - }, 100), - [isStreamingResponse] - ) - - useEffect(() => { - const container = messagesContainerRef.current - if (!container) return + } container.addEventListener('scroll', handleScroll, { passive: true }) return () => container.removeEventListener('scroll', handleScroll) - }, [handleScroll]) + }, []) useEffect(() => { if (isStreamingResponse) { @@ -209,83 +195,6 @@ export default function ChatClient({ identifier }: { identifier: string }) { } }, [isStreamingResponse]) - const fetchChatConfig = async () => { - try { - const response = await fetch(`/api/chat/${identifier}`, { - credentials: 'same-origin', - headers: { - 'X-Requested-With': 'XMLHttpRequest', - }, - }) - - if (!response.ok) { - if (response.status === 401) { - const errorData = await response.json() - - if (errorData.error === 'auth_required_password') { - setAuthRequired('password') - return - } - if (errorData.error === 'auth_required_email') { - setAuthRequired('email') - return - } - if (errorData.error === 'auth_required_sso') { - setAuthRequired('sso') - return - } - } - - throw new Error(`Failed to load chat configuration: ${response.status}`) - } - - setAuthRequired(null) - - const data = await response.json() - - setChatConfig(data) - - if (data?.customizations?.welcomeMessage) { - setMessages([ - { - id: 'welcome', - content: data.customizations.welcomeMessage, - type: 'assistant', - timestamp: new Date(), - isInitialMessage: true, - }, - ]) - } - } catch (error) { - logger.error('Error fetching chat config:', error) - setError(CHAT_ERROR_MESSAGES.CHAT_UNAVAILABLE) - } - } - - useEffect(() => { - fetchChatConfig() - setConversationId(generateId()) - - getFormattedGitHubStars() - .then((formattedStars) => { - setStarCount(formattedStars) - }) - .catch((err) => { - logger.error('Failed to fetch GitHub stars:', err) - }) - }, [identifier]) - - const refreshChat = () => { - fetchChatConfig() - } - - const handleAuthSuccess = () => { - setAuthRequired(null) - setTimeout(() => { - refreshChat() - }, 800) - } - const handleSendMessage = async ( messageParam?: string, isVoiceInput = false, @@ -338,23 +247,25 @@ export default function ChatClient({ identifier }: { identifier: string }) { }, CHAT_REQUEST_TIMEOUT_MS) try { - const payload: any = { + const payloadFiles = + files && files.length > 0 + ? await Promise.all( + files.map(async (file) => ({ + name: file.name, + size: file.size, + type: file.type, + data: file.dataUrl || (await fileToBase64(file.file)), + })) + ) + : undefined + + const payload: ChatRequestPayload = { input: typeof userMessage.content === 'string' ? userMessage.content : JSON.stringify(userMessage.content), conversationId, - } - - if (files && files.length > 0) { - payload.files = await Promise.all( - files.map(async (file) => ({ - name: file.name, - size: file.size, - type: file.type, - data: file.dataUrl || (await fileToBase64(file.file)), - })) - ) + ...(payloadFiles ? { files: payloadFiles } : {}), } logger.info('API payload:', { @@ -412,10 +323,10 @@ export default function ChatClient({ identifier }: { identifier: string }) { outputConfigs: chatConfig?.outputConfigs, } ) - } catch (error: any) { + } catch (error) { clearTimeout(timeoutId) - if (error.name === 'AbortError') { + if (error instanceof Error && error.name === 'AbortError') { logger.info('Request aborted by user or timeout') setIsLoading(false) return @@ -468,20 +379,17 @@ export default function ChatClient({ identifier }: { identifier: string }) { [handleSendMessage] ) - if (error) { - return + if (chatConfigError) { + logger.error('Error fetching chat config:', chatConfigError) + return } if (authRequired) { - // const title = new URLSearchParams(window.location.search).get('title') || 'chat' - // const primaryColor = - // new URLSearchParams(window.location.search).get('color') || 'var(--brand-hover)' - if (authRequired === 'password') { - return + return } if (authRequired === 'email') { - return + return } if (authRequired === 'sso') { return @@ -504,7 +412,7 @@ export default function ChatClient({ identifier }: { identifier: string }) { isPlayingAudio={isPlayingAudio} audioContextRef={audioContextRef} chatId={chatConfig?.id} - messages={messages.map((msg) => ({ + messages={displayMessages.map((msg) => ({ content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content), type: msg.type, }))} @@ -515,11 +423,11 @@ export default function ChatClient({ identifier }: { identifier: string }) { return (
{/* Header component */} - + {/* Message Container component */} } diff --git a/apps/sim/app/chat/components/auth/email/email-auth.tsx b/apps/sim/app/chat/components/auth/email/email-auth.tsx index 6100fde6314..fe8bf0606c0 100644 --- a/apps/sim/app/chat/components/auth/email/email-auth.tsx +++ b/apps/sim/app/chat/components/auth/email/email-auth.tsx @@ -1,7 +1,8 @@ 'use client' -import { type KeyboardEvent, useEffect, useState } from 'react' +import { useEffect, useState } from 'react' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { Loader2 } from 'lucide-react' import { Input, InputOTP, InputOTPGroup, InputOTPSlot, Label } from '@/components/emcn' import { cn } from '@/lib/core/utils/cn' @@ -10,12 +11,12 @@ import AuthBackground from '@/app/(auth)/components/auth-background' import { AUTH_SUBMIT_BTN } from '@/app/(auth)/components/auth-button-classes' import { SupportFooter } from '@/app/(auth)/components/support-footer' import Navbar from '@/app/(landing)/components/navbar/navbar' +import { useChatEmailOtpRequest, useChatEmailOtpVerify } from '@/hooks/queries/chats' const logger = createLogger('EmailAuth') interface EmailAuthProps { identifier: string - onAuthSuccess: () => void } const validateEmailField = (emailValue: string): string[] => { @@ -34,35 +35,24 @@ const validateEmailField = (emailValue: string): string[] => { return errors } -export default function EmailAuth({ identifier, onAuthSuccess }: EmailAuthProps) { +export default function EmailAuth({ identifier }: EmailAuthProps) { const [email, setEmail] = useState('') const [authError, setAuthError] = useState(null) - const [isSendingOtp, setIsSendingOtp] = useState(false) - const [isVerifyingOtp, setIsVerifyingOtp] = useState(false) const [emailErrors, setEmailErrors] = useState([]) const [showEmailValidationError, setShowEmailValidationError] = useState(false) const [showOtpVerification, setShowOtpVerification] = useState(false) const [otpValue, setOtpValue] = useState('') const [countdown, setCountdown] = useState(0) - const [isResendDisabled, setIsResendDisabled] = useState(false) - useEffect(() => { - if (countdown > 0) { - const timer = setTimeout(() => setCountdown((c) => c - 1), 1000) - return () => clearTimeout(timer) - } - if (countdown === 0 && isResendDisabled) { - setIsResendDisabled(false) - } - }, [countdown, isResendDisabled]) + const requestOtp = useChatEmailOtpRequest(identifier) + const verifyOtp = useChatEmailOtpVerify(identifier) - const handleEmailKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Enter') { - e.preventDefault() - handleSendOtp() - } - } + useEffect(() => { + if (countdown <= 0) return + const timer = setTimeout(() => setCountdown((c) => c - 1), 1000) + return () => clearTimeout(timer) + }, [countdown]) const handleEmailChange = (e: React.ChangeEvent) => { const newEmail = e.target.value @@ -82,32 +72,14 @@ export default function EmailAuth({ identifier, onAuthSuccess }: EmailAuthProps) } setAuthError(null) - setIsSendingOtp(true) try { - const response = await fetch(`/api/chat/${identifier}/otp`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-Requested-With': 'XMLHttpRequest', - }, - body: JSON.stringify({ email }), - }) - - if (!response.ok) { - const errorData = await response.json() - setEmailErrors([errorData.error || 'Failed to send verification code']) - setShowEmailValidationError(true) - return - } - + await requestOtp.mutateAsync({ email }) setShowOtpVerification(true) } catch (error) { logger.error('Error sending OTP:', error) - setEmailErrors(['An error occurred while sending the verification code']) + setEmailErrors([toError(error).message || 'Failed to send verification code']) setShowEmailValidationError(true) - } finally { - setIsSendingOtp(false) } } @@ -119,65 +91,26 @@ export default function EmailAuth({ identifier, onAuthSuccess }: EmailAuthProps) } setAuthError(null) - setIsVerifyingOtp(true) try { - const response = await fetch(`/api/chat/${identifier}/otp`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'X-Requested-With': 'XMLHttpRequest', - }, - body: JSON.stringify({ email, otp: codeToVerify }), - }) - - if (!response.ok) { - const errorData = await response.json() - setAuthError(errorData.error || 'Invalid verification code') - return - } - - onAuthSuccess() + await verifyOtp.mutateAsync({ email, otp: codeToVerify }) } catch (error) { logger.error('Error verifying OTP:', error) - setAuthError('An error occurred during verification') - } finally { - setIsVerifyingOtp(false) + setAuthError(toError(error).message || 'Invalid verification code') } } const handleResendOtp = async () => { setAuthError(null) - setIsSendingOtp(true) - setIsResendDisabled(true) setCountdown(30) try { - const response = await fetch(`/api/chat/${identifier}/otp`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-Requested-With': 'XMLHttpRequest', - }, - body: JSON.stringify({ email }), - }) - - if (!response.ok) { - const errorData = await response.json() - setAuthError(errorData.error || 'Failed to resend verification code') - setIsResendDisabled(false) - setCountdown(0) - return - } - + await requestOtp.mutateAsync({ email }) setOtpValue('') } catch (error) { logger.error('Error resending OTP:', error) - setAuthError('An error occurred while resending the verification code') - setIsResendDisabled(false) + setAuthError(toError(error).message || 'Failed to resend verification code') setCountdown(0) - } finally { - setIsSendingOtp(false) } } @@ -224,7 +157,6 @@ export default function EmailAuth({ identifier, onAuthSuccess }: EmailAuthProps) autoCorrect='off' value={email} onChange={handleEmailChange} - onKeyDown={handleEmailKeyDown} className={cn( showEmailValidationError && emailErrors.length > 0 && @@ -241,8 +173,12 @@ export default function EmailAuth({ identifier, onAuthSuccess }: EmailAuthProps) )}
- diff --git a/apps/sim/app/chat/components/auth/password/password-auth.tsx b/apps/sim/app/chat/components/auth/password/password-auth.tsx index a89f25c16da..669aca42aaa 100644 --- a/apps/sim/app/chat/components/auth/password/password-auth.tsx +++ b/apps/sim/app/chat/components/auth/password/password-auth.tsx @@ -1,7 +1,8 @@ 'use client' -import { type KeyboardEvent, useState } from 'react' +import { useState } from 'react' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { Eye, EyeOff, Loader2 } from 'lucide-react' import { Input, Label } from '@/components/emcn' import { cn } from '@/lib/core/utils/cn' @@ -9,27 +10,20 @@ import AuthBackground from '@/app/(auth)/components/auth-background' import { AUTH_SUBMIT_BTN } from '@/app/(auth)/components/auth-button-classes' import { SupportFooter } from '@/app/(auth)/components/support-footer' import Navbar from '@/app/(landing)/components/navbar/navbar' +import { useChatPasswordAuth } from '@/hooks/queries/chats' const logger = createLogger('PasswordAuth') interface PasswordAuthProps { identifier: string - onAuthSuccess: () => void } -export default function PasswordAuth({ identifier, onAuthSuccess }: PasswordAuthProps) { +export default function PasswordAuth({ identifier }: PasswordAuthProps) { const [password, setPassword] = useState('') const [showPassword, setShowPassword] = useState(false) const [showValidationError, setShowValidationError] = useState(false) const [passwordErrors, setPasswordErrors] = useState([]) - const [isAuthenticating, setIsAuthenticating] = useState(false) - - const handleKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Enter') { - e.preventDefault() - handleAuthenticate() - } - } + const authenticate = useChatPasswordAuth(identifier) const handlePasswordChange = (e: React.ChangeEvent) => { const newPassword = e.target.value @@ -45,36 +39,13 @@ export default function PasswordAuth({ identifier, onAuthSuccess }: PasswordAuth return } - setIsAuthenticating(true) - try { - const payload = { password } - - const response = await fetch(`/api/chat/${identifier}`, { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - 'X-Requested-With': 'XMLHttpRequest', - }, - body: JSON.stringify(payload), - }) - - if (!response.ok) { - const errorData = await response.json() - setPasswordErrors([errorData.error || 'Invalid password. Please try again.']) - setShowValidationError(true) - return - } - - onAuthSuccess() + await authenticate.mutateAsync({ password }) setPassword('') } catch (error) { logger.error('Authentication error:', error) - setPasswordErrors(['An error occurred during authentication']) + setPasswordErrors([toError(error).message || 'Invalid password. Please try again.']) setShowValidationError(true) - } finally { - setIsAuthenticating(false) } } @@ -120,7 +91,6 @@ export default function PasswordAuth({ identifier, onAuthSuccess }: PasswordAuth placeholder='Enter password' value={password} onChange={handlePasswordChange} - onKeyDown={handleKeyDown} className={cn( 'pr-10', showValidationError && @@ -160,10 +130,10 @@ export default function PasswordAuth({ identifier, onAuthSuccess }: PasswordAuth