const express = require('express'); const request = require('supertest'); const MOCKS = '../__test-utils__/convos-route-mocks'; jest.mock('@librechat/agents', () => require(MOCKS).agents()); jest.mock('@librechat/api', () => require(MOCKS).api({ limiterCache: jest.fn(() => undefined) })); jest.mock('@librechat/data-schemas', () => require(MOCKS).dataSchemas()); jest.mock('librechat-data-provider', () => require(MOCKS).dataProvider({ ViolationTypes: { FILE_UPLOAD_LIMIT: 'file_upload_limit' } }), ); jest.mock('~/cache/logViolation', () => jest.fn().mockResolvedValue(undefined)); jest.mock('~/cache/getLogStores', () => require(MOCKS).logStores()); jest.mock('~/models/Conversation', () => require(MOCKS).conversationModel()); jest.mock('~/models/ToolCall', () => require(MOCKS).toolCallModel()); jest.mock('~/models', () => require(MOCKS).sharedModels()); jest.mock('~/server/middleware/requireJwtAuth', () => require(MOCKS).requireJwtAuth()); jest.mock('~/server/middleware', () => { const { createForkLimiters } = jest.requireActual('~/server/middleware/limiters/forkLimiters'); return { createImportLimiters: jest.fn(() => ({ importIpLimiter: (req, res, next) => next(), importUserLimiter: (req, res, next) => next(), })), createForkLimiters, configMiddleware: (req, res, next) => next(), validateConvoAccess: (req, res, next) => next(), }; }); jest.mock('~/server/utils/import/fork', () => require(MOCKS).forkUtils()); jest.mock('~/server/utils/import', () => require(MOCKS).importUtils()); jest.mock('~/server/routes/files/multer', () => require(MOCKS).multerSetup()); jest.mock('multer', () => require(MOCKS).multerLib()); jest.mock('~/server/services/Endpoints/azureAssistants', () => require(MOCKS).assistantEndpoint()); jest.mock('~/server/services/Endpoints/assistants', () => require(MOCKS).assistantEndpoint()); describe('POST /api/convos/duplicate - Rate Limiting', () => { let app; let duplicateConversation; const savedEnv = {}; beforeAll(() => { savedEnv.FORK_USER_MAX = process.env.FORK_USER_MAX; savedEnv.FORK_USER_WINDOW = process.env.FORK_USER_WINDOW; savedEnv.FORK_IP_MAX = process.env.FORK_IP_MAX; savedEnv.FORK_IP_WINDOW = process.env.FORK_IP_WINDOW; }); afterAll(() => { for (const key of Object.keys(savedEnv)) { if (savedEnv[key] === undefined) { delete process.env[key]; } else { process.env[key] = savedEnv[key]; } } }); const setupApp = () => { jest.clearAllMocks(); jest.isolateModules(() => { const convosRouter = require('../convos'); ({ duplicateConversation } = require('~/server/utils/import/fork')); app = express(); app.use(express.json()); app.use((req, res, next) => { req.user = { id: 'rate-limit-test-user' }; next(); }); app.use('/api/convos', convosRouter); }); duplicateConversation.mockResolvedValue({ conversation: { conversationId: 'duplicated-conv' }, }); }; describe('user limit', () => { beforeEach(() => { process.env.FORK_USER_MAX = '2'; process.env.FORK_USER_WINDOW = '1'; process.env.FORK_IP_MAX = '100'; process.env.FORK_IP_WINDOW = '1'; setupApp(); }); it('should return 429 after exceeding the user rate limit', async () => { const userMax = parseInt(process.env.FORK_USER_MAX, 10); for (let i = 0; i < userMax; i++) { const res = await request(app) .post('/api/convos/duplicate') .send({ conversationId: 'conv-123' }); expect(res.status).toBe(201); } const res = await request(app) .post('/api/convos/duplicate') .send({ conversationId: 'conv-123' }); expect(res.status).toBe(429); expect(res.body.message).toMatch(/too many/i); }); }); describe('IP limit', () => { beforeEach(() => { process.env.FORK_USER_MAX = '100'; process.env.FORK_USER_WINDOW = '1'; process.env.FORK_IP_MAX = '2'; process.env.FORK_IP_WINDOW = '1'; setupApp(); }); it('should return 429 after exceeding the IP rate limit', async () => { const ipMax = parseInt(process.env.FORK_IP_MAX, 10); for (let i = 0; i < ipMax; i++) { const res = await request(app) .post('/api/convos/duplicate') .send({ conversationId: 'conv-123' }); expect(res.status).toBe(201); } const res = await request(app) .post('/api/convos/duplicate') .send({ conversationId: 'conv-123' }); expect(res.status).toBe(429); expect(res.body.message).toMatch(/too many/i); }); }); });