mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-04-03 22:37:20 +02:00
117 lines
4.1 KiB
JavaScript
117 lines
4.1 KiB
JavaScript
|
|
/**
|
||
|
|
* 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();
|
||
|
|
});
|
||
|
|
});
|