/** * Integration test: verifies that requireJwtAuth chains tenantContextMiddleware * after successful passport authentication, so ALS tenant context is set for * all downstream middleware and route handlers. * * requireJwtAuth must chain tenantContextMiddleware after passport populates * req.user (not at global app.use() scope where req.user is undefined). * If the chaining is removed, these tests fail. */ const { getTenantId } = require('@librechat/data-schemas'); // ── Mocks ────────────────────────────────────────────────────────────── let mockPassportError = null; jest.mock('passport', () => ({ authenticate: jest.fn(() => { return (req, _res, done) => { if (mockPassportError) { return done(mockPassportError); } if (req._mockUser) { req.user = req._mockUser; } done(); }; }), })); // Mock @librechat/api — the real tenantContextMiddleware is TS and cannot be // required directly from CJS tests. This thin wrapper mirrors the real logic // (read req.user.tenantId, call tenantStorage.run) using the same data-schemas // primitives. The real implementation is covered by packages/api tenant.spec.ts. jest.mock('@librechat/api', () => { const { tenantStorage } = require('@librechat/data-schemas'); return { isEnabled: jest.fn(() => false), tenantContextMiddleware: (req, res, next) => { const tenantId = req.user?.tenantId; if (!tenantId) { return next(); } return tenantStorage.run({ tenantId }, async () => next()); }, }; }); // ── Helpers ───────────────────────────────────────────────────────────── const requireJwtAuth = require('../requireJwtAuth'); function mockReq(user) { return { headers: {}, _mockUser: user }; } function mockRes() { return { status: jest.fn().mockReturnThis(), json: jest.fn().mockReturnThis() }; } /** Runs requireJwtAuth and returns the tenantId observed inside next(). */ function runAuth(user) { return new Promise((resolve) => { const req = mockReq(user); const res = mockRes(); requireJwtAuth(req, res, () => { resolve(getTenantId()); }); }); } // ── Tests ────────────────────────────────────────────────────────────── describe('requireJwtAuth tenant context chaining', () => { afterEach(() => { mockPassportError = null; }); it('forwards passport errors to next() without entering tenant middleware', async () => { mockPassportError = new Error('JWT signature invalid'); const req = mockReq(undefined); const res = mockRes(); const err = await new Promise((resolve) => { requireJwtAuth(req, res, (e) => resolve(e)); }); expect(err).toBeInstanceOf(Error); expect(err.message).toBe('JWT signature invalid'); expect(getTenantId()).toBeUndefined(); }); it('sets ALS tenant context after passport auth succeeds', async () => { const tenantId = await runAuth({ tenantId: 'tenant-abc', role: 'user' }); expect(tenantId).toBe('tenant-abc'); }); it('ALS tenant context is NOT set when user has no tenantId', async () => { const tenantId = await runAuth({ role: 'user' }); expect(tenantId).toBeUndefined(); }); it('ALS tenant context is NOT set when user is undefined', async () => { const tenantId = await runAuth(undefined); expect(tenantId).toBeUndefined(); }); it('concurrent requests get isolated tenant contexts', async () => { const results = await Promise.all( ['tenant-1', 'tenant-2', 'tenant-3'].map((tid) => runAuth({ tenantId: tid, role: 'user' })), ); expect(results).toEqual(['tenant-1', 'tenant-2', 'tenant-3']); }); it('ALS context is not set at top-level scope (outside any request)', () => { expect(getTenantId()).toBeUndefined(); }); });