From 225d50bebefb7310880db9b6898e648c860b8069 Mon Sep 17 00:00:00 2001 From: Ravi Kumar L Date: Sun, 21 Jun 2026 17:41:43 +0200 Subject: [PATCH] test: stabilize librechat suite --- api/server/routes/__tests__/convos.spec.js | 30 ++-- .../routes/__tests__/messages-delete.spec.js | 3 + .../agents/__tests__/streamTenant.spec.js | 13 +- api/server/routes/oauth.test.js | 56 ++++---- api/server/routes/prompts.test.js | 10 +- api/server/services/AuthService.spec.js | 131 +++++++++++------- .../utils/__tests__/staticCache.spec.js | 25 ++-- .../__tests__/UploadSkillDialog.spec.tsx | 34 ++--- package.json | 4 +- packages/api/src/agents/openai/service.ts | 1 + packages/api/src/langfuse/config.ts | 4 +- .../data-schemas/src/methods/config.spec.ts | 27 ++-- .../src/methods/systemGrant.spec.ts | 3 +- 13 files changed, 199 insertions(+), 142 deletions(-) diff --git a/api/server/routes/__tests__/convos.spec.js b/api/server/routes/__tests__/convos.spec.js index 9c760f5057..9a848786d6 100644 --- a/api/server/routes/__tests__/convos.spec.js +++ b/api/server/routes/__tests__/convos.spec.js @@ -27,23 +27,27 @@ describe('Convos Routes', () => { deleteConvoSharedLinksWithCleanup, } = require('@librechat/api'); - beforeAll(() => { - convosRouter = require('../convos'); - - app = express(); - app.use(express.json()); + const createApp = (userId = 'test-user-123') => { + const testApp = express(); + testApp.use(express.json()); /** Mock authenticated user */ - app.use((req, res, next) => { - req.user = { id: 'test-user-123' }; + testApp.use((req, res, next) => { + req.user = { id: userId }; next(); }); - app.use('/api/convos', convosRouter); + testApp.use('/api/convos', convosRouter); + return testApp; + }; + + beforeAll(() => { + convosRouter = require('../convos'); }); beforeEach(() => { - jest.clearAllMocks(); + jest.resetAllMocks(); + app = createApp(); }); describe('DELETE /all', () => { @@ -146,13 +150,7 @@ describe('Convos Routes', () => { jest.clearAllMocks(); /** Second user (simulate different user by modifying middleware) */ - const app2 = express(); - app2.use(express.json()); - app2.use((req, res, next) => { - req.user = { id: 'test-user-456' }; - next(); - }); - app2.use('/api/convos', require('../convos')); + const app2 = createApp('test-user-456'); deleteConvos.mockResolvedValue({ deletedCount: 7 }); deleteToolCalls.mockResolvedValue({ deletedCount: 12 }); diff --git a/api/server/routes/__tests__/messages-delete.spec.js b/api/server/routes/__tests__/messages-delete.spec.js index 36c4e8e9e6..cf3a7fb775 100644 --- a/api/server/routes/__tests__/messages-delete.spec.js +++ b/api/server/routes/__tests__/messages-delete.spec.js @@ -11,6 +11,8 @@ jest.mock('@librechat/agents', () => ({ jest.mock('@librechat/api', () => ({ unescapeLaTeX: jest.fn((x) => x), countTokens: jest.fn().mockResolvedValue(10), + sendFeedbackScore: jest.fn().mockResolvedValue(undefined), + traceIdForMessage: jest.fn((messageId) => `trace-${messageId}`), })); jest.mock('@librechat/data-schemas', () => ({ @@ -49,6 +51,7 @@ jest.mock('~/server/middleware/requireJwtAuth', () => (req, res, next) => next() jest.mock('~/server/middleware', () => ({ requireJwtAuth: (req, res, next) => next(), validateMessageReq: (req, res, next) => next(), + configMiddleware: (req, res, next) => next(), })); jest.mock('~/db/models', () => ({ diff --git a/api/server/routes/agents/__tests__/streamTenant.spec.js b/api/server/routes/agents/__tests__/streamTenant.spec.js index 708a071228..c606d74998 100644 --- a/api/server/routes/agents/__tests__/streamTenant.spec.js +++ b/api/server/routes/agents/__tests__/streamTenant.spec.js @@ -25,6 +25,16 @@ jest.mock('@librechat/api', () => ({ GenerationJobManager: mockGenerationJobManager, })); +jest.mock('@librechat/api/telemetry', () => ({ + createSseStreamTelemetry: jest.fn(() => ({ + recordHeadersFlushed: jest.fn(), + recordWrite: jest.fn(), + recordFinalEventEmitted: jest.fn(), + recordErrorEventEmitted: jest.fn(), + recordSubscribeFailed: jest.fn(), + })), +})); + jest.mock('~/models', () => ({ saveMessage: jest.fn(), })); @@ -67,7 +77,8 @@ function mockSubscribeSuccess() { describe('SSE stream tenant isolation', () => { beforeEach(() => { - jest.clearAllMocks(); + Object.values(mockGenerationJobManager).forEach((mock) => mock.mockReset()); + mockGenerationJobManager.getActiveJobIdsForUser.mockResolvedValue([]); mockUserId = 'user-123'; mockTenantId = undefined; }); diff --git a/api/server/routes/oauth.test.js b/api/server/routes/oauth.test.js index b6739ffdc5..d4593bf438 100644 --- a/api/server/routes/oauth.test.js +++ b/api/server/routes/oauth.test.js @@ -11,14 +11,15 @@ const mockLogger = { debug: jest.fn(), }; +let mockOpenIDCallbackAuthenticatorOptions; const mockOAuthHandler = jest.fn((_req, res) => res.status(204).end()); const mockOpenIDCallbackMiddleware = jest.fn((_req, _res, next) => next()); -let mockOpenIDCallbackAuthenticatorOptions; -const mockCreateOpenIDCallbackAuthenticator = jest.fn((options) => { +const setMockOpenIDCallbackAuthenticator = (options) => { mockOpenIDCallbackAuthenticatorOptions = options; return mockOpenIDCallbackMiddleware; -}); -const mockBuildOAuthFailureLog = jest.fn(({ provider, req, err, info, defaultMessage }) => ({ +}; +const mockCreateOpenIDCallbackAuthenticator = jest.fn(setMockOpenIDCallbackAuthenticator); +const buildMockOAuthFailureLog = ({ provider, req, err, info, defaultMessage }) => ({ provider, code: err?.code ?? info?.code ?? info?.error ?? req.query?.error, name: err?.name ?? info?.name, @@ -37,17 +38,17 @@ const mockBuildOAuthFailureLog = jest.fn(({ provider, req, err, info, defaultMes path: req.path, forwarded_for: req.headers?.['x-forwarded-for'], user_agent: req.headers?.['user-agent'], -})); -const mockGetOAuthFailureMessage = jest.fn( - (req) => - req.session?.messages?.pop() ?? - req.query?.error_description ?? - req.query?.error ?? - 'OAuth authentication failed', -); -const mockRedirectToAuthFailure = jest.fn((res, { clientDomain, authFailedError }) => - res.redirect(`${clientDomain}/login?redirect=false&error=${authFailedError}`), -); +}); +const mockBuildOAuthFailureLog = jest.fn(buildMockOAuthFailureLog); +const getMockOAuthFailureMessage = (req) => + req.session?.messages?.pop() ?? + req.query?.error_description ?? + req.query?.error ?? + 'OAuth authentication failed'; +const mockGetOAuthFailureMessage = jest.fn(getMockOAuthFailureMessage); +const redirectMockToAuthFailure = (res, { clientDomain, authFailedError }) => + res.redirect(`${clientDomain}/login?redirect=false&error=${authFailedError}`); +const mockRedirectToAuthFailure = jest.fn(redirectMockToAuthFailure); const mockPassportAuthenticate = jest.fn(() => (_req, _res, next) => next()); jest.mock('passport', () => ({ @@ -105,8 +106,11 @@ afterAll(() => { }); function getOAuthRouter() { - jest.resetModules(); - return require('./oauth'); + let router; + jest.isolateModules(() => { + router = require('./oauth'); + }); + return router; } function createApp(sessionMessages) { @@ -126,19 +130,17 @@ function createApp(sessionMessages) { describe('OAuth route failure logging', () => { beforeEach(() => { - mockLogger.warn.mockClear(); - mockLogger.error.mockClear(); - mockLogger.info.mockClear(); - mockLogger.debug.mockClear(); - mockOAuthHandler.mockClear(); - mockOpenIDCallbackMiddleware.mockClear(); - mockBuildOAuthFailureLog.mockClear(); - mockGetOAuthFailureMessage.mockClear(); - mockRedirectToAuthFailure.mockClear(); - mockPassportAuthenticate.mockClear(); + jest.resetModules(); + jest.clearAllMocks(); + process.env.DOMAIN_CLIENT = 'http://client.test'; mockOpenIDCallbackAuthenticatorOptions = undefined; mockPassportAuthenticate.mockImplementation(() => (_req, _res, next) => next()); + mockCreateOpenIDCallbackAuthenticator.mockImplementation(setMockOpenIDCallbackAuthenticator); mockOpenIDCallbackMiddleware.mockImplementation((_req, _res, next) => next()); + mockOAuthHandler.mockImplementation((_req, res) => res.status(204).end()); + mockBuildOAuthFailureLog.mockImplementation(buildMockOAuthFailureLog); + mockGetOAuthFailureMessage.mockImplementation(getMockOAuthFailureMessage); + mockRedirectToAuthFailure.mockImplementation(redirectMockToAuthFailure); }); it('wires the package OpenID callback middleware into the route', async () => { diff --git a/api/server/routes/prompts.test.js b/api/server/routes/prompts.test.js index c979023ffc..39f2dfe15f 100644 --- a/api/server/routes/prompts.test.js +++ b/api/server/routes/prompts.test.js @@ -56,6 +56,10 @@ function setTestUser(app, user) { } beforeAll(async () => { + if (mongoose.connection.readyState !== 0) { + await mongoose.disconnect(); + } + mongoServer = await MongoMemoryServer.create(); const mongoUri = mongoServer.getUri(); await mongoose.connect(mongoUri); @@ -97,8 +101,10 @@ beforeAll(async () => { // Set default user currentTestUser = testUsers.owner; - // Import routes after middleware is set up - promptRoutes = require('./prompts'); + // Import routes after middleware is set up and test-local mocks are registered. + jest.isolateModules(() => { + promptRoutes = require('./prompts'); + }); app.use('/api/prompts', promptRoutes); }); diff --git a/api/server/services/AuthService.spec.js b/api/server/services/AuthService.spec.js index 03579e7278..36ad7c1f80 100644 --- a/api/server/services/AuthService.spec.js +++ b/api/server/services/AuthService.spec.js @@ -1,45 +1,33 @@ -jest.mock( - '@librechat/data-schemas', - () => ({ - logger: { info: jest.fn(), warn: jest.fn(), debug: jest.fn(), error: jest.fn() }, - getTenantId: jest.fn(() => undefined), - DEFAULT_SESSION_EXPIRY: 900000, - DEFAULT_REFRESH_TOKEN_EXPIRY: 604800000, - }), - { virtual: true }, -); -jest.mock( - 'librechat-data-provider', - () => ({ - ErrorTypes: {}, - SystemRoles: { USER: 'USER', ADMIN: 'ADMIN' }, - errorsToString: jest.fn(), - }), - { virtual: true }, -); -jest.mock( - '@librechat/api', - () => ({ - isEnabled: jest.fn((val) => val === 'true' || val === true), - checkEmailConfig: jest.fn(), - isEmailDomainAllowed: jest.fn(), - math: jest.fn((val, fallback) => (val ? Number(val) : fallback)), - shouldUseSecureCookie: jest.fn(() => false), - resolveAppConfigForUser: jest.fn(async (_getAppConfig, _user) => ({})), - setCloudFrontCookies: jest.fn(() => true), - getCloudFrontConfig: jest.fn(() => ({ - domain: 'https://cdn.example.com', - imageSigning: 'cookies', - cookieDomain: '.example.com', - privateKey: 'test-private-key', - keyPairId: 'K123ABC', - })), - parseCloudFrontCookieScope: jest.fn(() => null), - CLOUDFRONT_SCOPE_COOKIE: 'LibreChat-CloudFront-Scope', - }), - { virtual: true }, -); -jest.mock('~/models', () => ({ +const dataSchemasMock = { + logger: { info: jest.fn(), warn: jest.fn(), debug: jest.fn(), error: jest.fn() }, + getTenantId: jest.fn(() => undefined), + DEFAULT_SESSION_EXPIRY: 900000, + DEFAULT_REFRESH_TOKEN_EXPIRY: 604800000, +}; +const dataProviderMock = { + ErrorTypes: {}, + SystemRoles: { USER: 'USER', ADMIN: 'ADMIN' }, + errorsToString: jest.fn(), +}; +const apiMock = { + isEnabled: jest.fn((val) => val === 'true' || val === true), + checkEmailConfig: jest.fn(), + isEmailDomainAllowed: jest.fn(), + math: jest.fn((val, fallback) => (val ? Number(val) : fallback)), + shouldUseSecureCookie: jest.fn(() => false), + resolveAppConfigForUser: jest.fn(async (_getAppConfig, _user) => ({})), + setCloudFrontCookies: jest.fn(() => true), + getCloudFrontConfig: jest.fn(() => ({ + domain: 'https://cdn.example.com', + imageSigning: 'cookies', + cookieDomain: '.example.com', + privateKey: 'test-private-key', + keyPairId: 'K123ABC', + })), + parseCloudFrontCookieScope: jest.fn(() => null), + CLOUDFRONT_SCOPE_COOKIE: 'LibreChat-CloudFront-Scope', +}; +const modelsMock = { findUser: jest.fn(), findToken: jest.fn(), createUser: jest.fn(), @@ -54,8 +42,8 @@ jest.mock('~/models', () => ({ generateToken: jest.fn(), deleteUserById: jest.fn(), generateRefreshToken: jest.fn(), -})); -jest.mock('~/strategies/validators', () => ({ +}; +const validatorsMock = { registerSchema: { safeParse: jest.fn((user) => ({ success: true, @@ -68,9 +56,18 @@ jest.mock('~/strategies/validators', () => ({ }, })), }, -})); -jest.mock('~/server/services/Config', () => ({ getAppConfig: jest.fn() })); -jest.mock('~/server/utils', () => ({ sendEmail: jest.fn() })); +}; +const configMock = { getAppConfig: jest.fn() }; +const serverUtilsMock = { sendEmail: jest.fn() }; + +jest.resetModules(); +jest.doMock('@librechat/data-schemas', () => dataSchemasMock); +jest.doMock('librechat-data-provider', () => dataProviderMock); +jest.doMock('@librechat/api', () => apiMock); +jest.doMock('~/models', () => modelsMock); +jest.doMock('~/strategies/validators', () => validatorsMock); +jest.doMock('~/server/services/Config', () => configMock); +jest.doMock('~/server/utils', () => serverUtilsMock); const { checkEmailConfig, @@ -80,9 +77,9 @@ const { setCloudFrontCookies, getCloudFrontConfig, parseCloudFrontCookieScope, -} = require('@librechat/api'); +} = apiMock; const jwt = require('jsonwebtoken'); -const { logger, getTenantId } = require('@librechat/data-schemas'); +const { logger, getTenantId } = dataSchemasMock; const { findUser, findToken, @@ -95,9 +92,9 @@ const { createSession, createToken, deleteTokens, -} = require('~/models'); -const { getAppConfig } = require('~/server/services/Config'); -const { sendEmail } = require('~/server/utils'); +} = modelsMock; +const { getAppConfig } = configMock; +const { sendEmail } = serverUtilsMock; const bcrypt = require('bcryptjs'); const { setOpenIDAuthTokens, @@ -130,6 +127,36 @@ function mockRequest(sessionData = {}, cookies = {}) { }; } +const originalEnv = { ...process.env }; +const defaultCloudFrontCookieConfig = { + domain: 'https://cdn.example.com', + imageSigning: 'cookies', + cookieDomain: '.example.com', + privateKey: 'test-private-key', + keyPairId: 'K123ABC', +}; + +beforeEach(() => { + jest.clearAllMocks(); + process.env = { + ...originalEnv, + JWT_REFRESH_SECRET: 'test-refresh-secret', + OPENID_REUSE_TOKENS: 'true', + }; + checkEmailConfig.mockReturnValue(false); + shouldUseSecureCookie.mockReturnValue(false); + isEmailDomainAllowed.mockReturnValue(true); + resolveAppConfigForUser.mockResolvedValue({}); + setCloudFrontCookies.mockReturnValue(true); + getCloudFrontConfig.mockReturnValue(defaultCloudFrontCookieConfig); + parseCloudFrontCookieScope.mockReturnValue(null); + getTenantId.mockReturnValue(undefined); +}); + +afterAll(() => { + process.env = originalEnv; +}); + describe('setOpenIDAuthTokens', () => { const env = process.env; diff --git a/api/server/utils/__tests__/staticCache.spec.js b/api/server/utils/__tests__/staticCache.spec.js index 2b22223393..a4153b110e 100644 --- a/api/server/utils/__tests__/staticCache.spec.js +++ b/api/server/utils/__tests__/staticCache.spec.js @@ -1,9 +1,9 @@ const fs = require('fs'); +const os = require('os'); const path = require('path'); const express = require('express'); const request = require('supertest'); const zlib = require('zlib'); -const staticCache = require('../staticCache'); const binaryParser = (res, callback) => { const chunks = []; @@ -18,15 +18,9 @@ describe('staticCache', () => { let indexFile; let manifestFile; let swFile; + let staticCache; - beforeAll(() => { - // Create a test directory and files - testDir = path.join(__dirname, 'test-static'); - if (!fs.existsSync(testDir)) { - fs.mkdirSync(testDir, { recursive: true }); - } - - // Create test files + const createTestFiles = () => { testFile = path.join(testDir, 'test.js'); indexFile = path.join(testDir, 'index.html'); manifestFile = path.join(testDir, 'manifest.json'); @@ -62,6 +56,10 @@ describe('staticCache', () => { const distImagesDir = path.join(testDir, 'dist', 'images'); fs.mkdirSync(distImagesDir, { recursive: true }); fs.writeFileSync(path.join(distImagesDir, 'logo.png'), 'fake-png-data'); + }; + + beforeAll(() => { + testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'librechat-static-cache-')); }); afterAll(() => { @@ -79,6 +77,15 @@ describe('staticCache', () => { delete process.env.STATIC_CACHE_S_MAX_AGE; delete process.env.STATIC_CACHE_MAX_AGE; delete process.env.ENABLE_STATIC_ASSET_BROTLI; + + if (fs.existsSync(testDir)) { + fs.rmSync(testDir, { recursive: true, force: true }); + } + fs.mkdirSync(testDir, { recursive: true }); + createTestFiles(); + + jest.resetModules(); + staticCache = require('../staticCache'); }); describe('cache headers in production', () => { beforeEach(() => { diff --git a/client/src/components/Skills/dialogs/__tests__/UploadSkillDialog.spec.tsx b/client/src/components/Skills/dialogs/__tests__/UploadSkillDialog.spec.tsx index 803124adbc..d38ee15773 100644 --- a/client/src/components/Skills/dialogs/__tests__/UploadSkillDialog.spec.tsx +++ b/client/src/components/Skills/dialogs/__tests__/UploadSkillDialog.spec.tsx @@ -1,4 +1,4 @@ -import { fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import type { ReactNode } from 'react'; import type { FileConfigInput } from 'librechat-data-provider'; import UploadSkillDialog from '../UploadSkillDialog'; @@ -70,12 +70,14 @@ jest.mock('~/utils', () => ({ cn: (...classes: Array) => classes.filter(Boolean).join(' '), })); -function getFileInput(container: HTMLElement): HTMLInputElement { - const input = container.querySelector('input[type="file"]'); - if (!(input instanceof HTMLInputElement)) { - throw new Error('Upload input was not rendered'); - } - return input; +async function getFileInput(baseElement: HTMLElement): Promise { + return await waitFor(() => { + const input = baseElement.querySelector('input[type="file"]'); + if (!(input instanceof HTMLInputElement)) { + throw new Error('Upload input was not rendered'); + } + return input; + }); } describe('UploadSkillDialog', () => { @@ -106,13 +108,13 @@ describe('UploadSkillDialog', () => { expect(screen.getByText('File size must not exceed 1.06 MB')).toBeInTheDocument(); }); - it('rejects files above the configured skill import limit before upload', () => { - const { container } = render(); + it('rejects files above the configured skill import limit before upload', async () => { + const { baseElement } = render(); const file = new File([new Uint8Array(1024 * 1024 + 1)], 'too-large.skill', { type: 'application/zip', }); - fireEvent.change(getFileInput(container), { + fireEvent.change(await getFileInput(baseElement), { target: { files: [file], }, @@ -125,14 +127,14 @@ describe('UploadSkillDialog', () => { }); }); - it('uploads files exactly at the configured skill import limit', () => { + it('uploads files exactly at the configured skill import limit', async () => { const appendSpy = jest.spyOn(FormData.prototype, 'append'); - const { container } = render(); + const { baseElement } = render(); const file = new File([new Uint8Array(1024 * 1024)], 'exact-limit.skill', { type: 'application/zip', }); - fireEvent.change(getFileInput(container), { + fireEvent.change(await getFileInput(baseElement), { target: { files: [file], }, @@ -144,14 +146,14 @@ describe('UploadSkillDialog', () => { appendSpy.mockRestore(); }); - it('uploads files under the configured skill import limit', () => { + it('uploads files under the configured skill import limit', async () => { const appendSpy = jest.spyOn(FormData.prototype, 'append'); - const { container } = render(); + const { baseElement } = render(); const file = new File([new Uint8Array(1024)], 'small.skill', { type: 'application/zip', }); - fireEvent.change(getFileInput(container), { + fireEvent.change(await getFileInput(baseElement), { target: { files: [file], }, diff --git a/package.json b/package.json index 8d256849ea..c9680fcd75 100644 --- a/package.json +++ b/package.json @@ -78,8 +78,8 @@ "test:all": "npm run test:client && npm run test:api && npm run test:packages:api && npm run test:packages:data-provider && npm run test:packages:data-schemas", "e2e:update": "npm run e2e:prepare && playwright test --config=e2e/playwright.config.local.ts --update-snapshots", "e2e:report": "npx playwright show-report e2e/playwright-report", - "lint:fix": "eslint --fix \"{,!(node_modules|venv)/**/}*.{js,jsx,ts,tsx}\"", - "lint": "eslint \"{,!(node_modules|venv)/**/}*.{js,jsx,ts,tsx}\"", + "lint:fix": "eslint --fix --no-error-on-unmatched-pattern \"**/*.{js,jsx,ts,tsx}\"", + "lint": "eslint --no-error-on-unmatched-pattern \"**/*.{js,jsx,ts,tsx}\"", "sort-imports": "node scripts/sort-imports.mts", "sort-imports:check": "node scripts/sort-imports.mts --check", "format": "npx prettier --write \"{,!(node_modules|venv)/**/}*.{js,jsx,ts,tsx}\"", diff --git a/packages/api/src/agents/openai/service.ts b/packages/api/src/agents/openai/service.ts index d1fa256772..a89550fd4f 100644 --- a/packages/api/src/agents/openai/service.ts +++ b/packages/api/src/agents/openai/service.ts @@ -178,6 +178,7 @@ type CreateRunFn = (params: { requestBody: Record; user: Record; tenantId?: string; + appConfig?: AppConfig; tokenCounter?: (message: unknown) => number; }) => Promise<{ Graph?: unknown; diff --git a/packages/api/src/langfuse/config.ts b/packages/api/src/langfuse/config.ts index c202d607ab..241fe9328c 100644 --- a/packages/api/src/langfuse/config.ts +++ b/packages/api/src/langfuse/config.ts @@ -62,9 +62,7 @@ export function buildLangfuseConfig({ normalizeString(fanout?.collectorUrl) ?? normalizeString(process.env.LANGFUSE_FANOUT_COLLECTOR_URL); const baseUrl = - fanoutEnabled && fanoutCollectorUrl - ? fanoutCollectorUrl - : normalizeString(config?.baseUrl); + fanoutEnabled && fanoutCollectorUrl ? fanoutCollectorUrl : normalizeString(config?.baseUrl); if (baseUrl) { langfuse.baseUrl = baseUrl; diff --git a/packages/data-schemas/src/methods/config.spec.ts b/packages/data-schemas/src/methods/config.spec.ts index 35cc3cece7..fe134d6bb0 100644 --- a/packages/data-schemas/src/methods/config.spec.ts +++ b/packages/data-schemas/src/methods/config.spec.ts @@ -1,4 +1,4 @@ -import mongoose, { Types } from 'mongoose'; +import mongoose, { Mongoose, Types } from 'mongoose'; import { MongoMemoryServer } from 'mongodb-memory-server'; import { PrincipalType, PrincipalModel } from 'librechat-data-provider'; import type { IConfig } from '~/types'; @@ -6,24 +6,25 @@ import { createConfigMethods } from './config'; import configSchema from '~/schema/config'; let mongoServer: MongoMemoryServer; +let testMongoose: Mongoose; let methods: ReturnType; beforeAll(async () => { mongoServer = await MongoMemoryServer.create(); - await mongoose.connect(mongoServer.getUri()); - if (!mongoose.models.Config) { - mongoose.model('Config', configSchema); - } - methods = createConfigMethods(mongoose); + testMongoose = new mongoose.Mongoose(); + await testMongoose.connect(mongoServer.getUri()); + testMongoose.model('Config', configSchema.clone()); + await testMongoose.models.Config.init(); + methods = createConfigMethods(testMongoose as unknown as typeof mongoose); }); afterAll(async () => { - await mongoose.disconnect(); + await testMongoose.disconnect(); await mongoServer.stop(); }); beforeEach(async () => { - await mongoose.models.Config.deleteMany({}); + await testMongoose.models.Config.deleteMany({}); }); describe('upsertConfig tombstone preservation', () => { @@ -61,7 +62,7 @@ describe('upsertConfig tombstone preservation', () => { 10, ); - const count = await mongoose.models.Config.countDocuments({}); + const count = await testMongoose.models.Config.countDocuments({}); expect(count).toBe(1); }); @@ -486,7 +487,7 @@ describe('expectEmpty atomic guard', () => { }); expect(result).toBeTruthy(); - const remaining = await mongoose.models.Config.countDocuments({}); + const remaining = await testMongoose.models.Config.countDocuments({}); expect(remaining).toBe(0); }); @@ -504,12 +505,12 @@ describe('expectEmpty atomic guard', () => { }); expect(result).toBeNull(); - const remaining = await mongoose.models.Config.countDocuments({}); + const remaining = await testMongoose.models.Config.countDocuments({}); expect(remaining).toBe(1); }); it('deleteConfig with expectEmpty returns null when tombstones is non-empty (doc preserved)', async () => { - await mongoose.models.Config.create({ + await testMongoose.models.Config.create({ principalType: PrincipalType.ROLE, principalId: 'admin', principalModel: PrincipalModel.ROLE, @@ -525,7 +526,7 @@ describe('expectEmpty atomic guard', () => { }); expect(result).toBeNull(); - const remaining = await mongoose.models.Config.countDocuments({}); + const remaining = await testMongoose.models.Config.countDocuments({}); expect(remaining).toBe(1); }); diff --git a/packages/data-schemas/src/methods/systemGrant.spec.ts b/packages/data-schemas/src/methods/systemGrant.spec.ts index b58a9431fa..add41dfa4e 100644 --- a/packages/data-schemas/src/methods/systemGrant.spec.ts +++ b/packages/data-schemas/src/methods/systemGrant.spec.ts @@ -21,10 +21,11 @@ let methods: ReturnType; beforeAll(async () => { mongoServer = await MongoMemoryServer.create(); + await mongoose.connect(mongoServer.getUri()); SystemGrant = mongoose.models.SystemGrant || mongoose.model('SystemGrant', systemGrantSchema); + await SystemGrant.init(); methods = createSystemGrantMethods(mongoose); - await mongoose.connect(mongoServer.getUri()); }); afterAll(async () => {