mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-22 19:30:15 +01:00
ci: Integrate getAppConfig into remaining tests
This commit is contained in:
parent
5b43bf6c95
commit
5bb731764c
6 changed files with 164 additions and 84 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
const { Constants } = require('librechat-data-provider');
|
const { Constants } = require('librechat-data-provider');
|
||||||
const { getCustomConfig, getCachedTools } = require('~/server/services/Config');
|
const { getCustomConfig, getCachedTools, getAppConfig } = require('~/server/services/Config');
|
||||||
const { getLogStores } = require('~/cache');
|
const { getLogStores } = require('~/cache');
|
||||||
|
|
||||||
// Mock the dependencies
|
// Mock the dependencies
|
||||||
|
|
@ -13,6 +13,7 @@ jest.mock('@librechat/data-schemas', () => ({
|
||||||
jest.mock('~/server/services/Config', () => ({
|
jest.mock('~/server/services/Config', () => ({
|
||||||
getCustomConfig: jest.fn(),
|
getCustomConfig: jest.fn(),
|
||||||
getCachedTools: jest.fn(),
|
getCachedTools: jest.fn(),
|
||||||
|
getAppConfig: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('~/server/services/ToolService', () => ({
|
jest.mock('~/server/services/ToolService', () => ({
|
||||||
|
|
@ -64,7 +65,10 @@ describe('PluginController', () => {
|
||||||
|
|
||||||
describe('getAvailablePluginsController', () => {
|
describe('getAvailablePluginsController', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockReq.app = { locals: { filteredTools: [], includedTools: [] } };
|
getAppConfig.mockResolvedValue({
|
||||||
|
filteredTools: [],
|
||||||
|
includedTools: [],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use filterUniquePlugins to remove duplicate plugins', async () => {
|
it('should use filterUniquePlugins to remove duplicate plugins', async () => {
|
||||||
|
|
@ -122,7 +126,10 @@ describe('PluginController', () => {
|
||||||
{ name: 'Plugin2', pluginKey: 'key2', description: 'Second' },
|
{ name: 'Plugin2', pluginKey: 'key2', description: 'Second' },
|
||||||
];
|
];
|
||||||
|
|
||||||
mockReq.app.locals.includedTools = ['key1'];
|
getAppConfig.mockResolvedValue({
|
||||||
|
filteredTools: [],
|
||||||
|
includedTools: ['key1'],
|
||||||
|
});
|
||||||
mockCache.get.mockResolvedValue(null);
|
mockCache.get.mockResolvedValue(null);
|
||||||
filterUniquePlugins.mockReturnValue(mockPlugins);
|
filterUniquePlugins.mockReturnValue(mockPlugins);
|
||||||
checkPluginAuth.mockReturnValue(false);
|
checkPluginAuth.mockReturnValue(false);
|
||||||
|
|
@ -480,8 +487,8 @@ describe('PluginController', () => {
|
||||||
expect(responseData[0].authConfig).toEqual([]);
|
expect(responseData[0].authConfig).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle req.app.locals with undefined filteredTools and includedTools', async () => {
|
it('should handle undefined filteredTools and includedTools', async () => {
|
||||||
mockReq.app = { locals: {} };
|
getAppConfig.mockResolvedValue({});
|
||||||
mockCache.get.mockResolvedValue(null);
|
mockCache.get.mockResolvedValue(null);
|
||||||
filterUniquePlugins.mockReturnValue([]);
|
filterUniquePlugins.mockReturnValue([]);
|
||||||
checkPluginAuth.mockReturnValue(false);
|
checkPluginAuth.mockReturnValue(false);
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,18 @@
|
||||||
const jwt = require('jsonwebtoken');
|
const jwt = require('jsonwebtoken');
|
||||||
const validateImageRequest = require('~/server/middleware/validateImageRequest');
|
const validateImageRequest = require('~/server/middleware/validateImageRequest');
|
||||||
|
|
||||||
|
jest.mock('~/server/services/Config/getAppConfig', () => ({
|
||||||
|
getAppConfig: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
describe('validateImageRequest middleware', () => {
|
describe('validateImageRequest middleware', () => {
|
||||||
let req, res, next;
|
let req, res, next;
|
||||||
const validObjectId = '65cfb246f7ecadb8b1e8036b';
|
const validObjectId = '65cfb246f7ecadb8b1e8036b';
|
||||||
|
const { getAppConfig } = require('~/server/services/Config/getAppConfig');
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
req = {
|
req = {
|
||||||
app: { locals: { secureImageLinks: true } },
|
|
||||||
headers: {},
|
headers: {},
|
||||||
originalUrl: '',
|
originalUrl: '',
|
||||||
};
|
};
|
||||||
|
|
@ -17,79 +22,86 @@ describe('validateImageRequest middleware', () => {
|
||||||
};
|
};
|
||||||
next = jest.fn();
|
next = jest.fn();
|
||||||
process.env.JWT_REFRESH_SECRET = 'test-secret';
|
process.env.JWT_REFRESH_SECRET = 'test-secret';
|
||||||
|
|
||||||
|
// Mock getAppConfig to return secureImageLinks: true by default
|
||||||
|
getAppConfig.mockResolvedValue({
|
||||||
|
secureImageLinks: true,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should call next() if secureImageLinks is false', () => {
|
test('should call next() if secureImageLinks is false', async () => {
|
||||||
req.app.locals.secureImageLinks = false;
|
getAppConfig.mockResolvedValue({
|
||||||
validateImageRequest(req, res, next);
|
secureImageLinks: false,
|
||||||
|
});
|
||||||
|
await validateImageRequest(req, res, next);
|
||||||
expect(next).toHaveBeenCalled();
|
expect(next).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return 401 if refresh token is not provided', () => {
|
test('should return 401 if refresh token is not provided', async () => {
|
||||||
validateImageRequest(req, res, next);
|
await validateImageRequest(req, res, next);
|
||||||
expect(res.status).toHaveBeenCalledWith(401);
|
expect(res.status).toHaveBeenCalledWith(401);
|
||||||
expect(res.send).toHaveBeenCalledWith('Unauthorized');
|
expect(res.send).toHaveBeenCalledWith('Unauthorized');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return 403 if refresh token is invalid', () => {
|
test('should return 403 if refresh token is invalid', async () => {
|
||||||
req.headers.cookie = 'refreshToken=invalid-token';
|
req.headers.cookie = 'refreshToken=invalid-token';
|
||||||
validateImageRequest(req, res, next);
|
await validateImageRequest(req, res, next);
|
||||||
expect(res.status).toHaveBeenCalledWith(403);
|
expect(res.status).toHaveBeenCalledWith(403);
|
||||||
expect(res.send).toHaveBeenCalledWith('Access Denied');
|
expect(res.send).toHaveBeenCalledWith('Access Denied');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return 403 if refresh token is expired', () => {
|
test('should return 403 if refresh token is expired', async () => {
|
||||||
const expiredToken = jwt.sign(
|
const expiredToken = jwt.sign(
|
||||||
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) - 3600 },
|
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) - 3600 },
|
||||||
process.env.JWT_REFRESH_SECRET,
|
process.env.JWT_REFRESH_SECRET,
|
||||||
);
|
);
|
||||||
req.headers.cookie = `refreshToken=${expiredToken}`;
|
req.headers.cookie = `refreshToken=${expiredToken}`;
|
||||||
validateImageRequest(req, res, next);
|
await validateImageRequest(req, res, next);
|
||||||
expect(res.status).toHaveBeenCalledWith(403);
|
expect(res.status).toHaveBeenCalledWith(403);
|
||||||
expect(res.send).toHaveBeenCalledWith('Access Denied');
|
expect(res.send).toHaveBeenCalledWith('Access Denied');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should call next() for valid image path', () => {
|
test('should call next() for valid image path', async () => {
|
||||||
const validToken = jwt.sign(
|
const validToken = jwt.sign(
|
||||||
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
||||||
process.env.JWT_REFRESH_SECRET,
|
process.env.JWT_REFRESH_SECRET,
|
||||||
);
|
);
|
||||||
req.headers.cookie = `refreshToken=${validToken}`;
|
req.headers.cookie = `refreshToken=${validToken}`;
|
||||||
req.originalUrl = `/images/${validObjectId}/example.jpg`;
|
req.originalUrl = `/images/${validObjectId}/example.jpg`;
|
||||||
validateImageRequest(req, res, next);
|
await validateImageRequest(req, res, next);
|
||||||
expect(next).toHaveBeenCalled();
|
expect(next).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return 403 for invalid image path', () => {
|
test('should return 403 for invalid image path', async () => {
|
||||||
const validToken = jwt.sign(
|
const validToken = jwt.sign(
|
||||||
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
||||||
process.env.JWT_REFRESH_SECRET,
|
process.env.JWT_REFRESH_SECRET,
|
||||||
);
|
);
|
||||||
req.headers.cookie = `refreshToken=${validToken}`;
|
req.headers.cookie = `refreshToken=${validToken}`;
|
||||||
req.originalUrl = '/images/65cfb246f7ecadb8b1e8036c/example.jpg'; // Different ObjectId
|
req.originalUrl = '/images/65cfb246f7ecadb8b1e8036c/example.jpg'; // Different ObjectId
|
||||||
validateImageRequest(req, res, next);
|
await validateImageRequest(req, res, next);
|
||||||
expect(res.status).toHaveBeenCalledWith(403);
|
expect(res.status).toHaveBeenCalledWith(403);
|
||||||
expect(res.send).toHaveBeenCalledWith('Access Denied');
|
expect(res.send).toHaveBeenCalledWith('Access Denied');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return 403 for invalid ObjectId format', () => {
|
test('should return 403 for invalid ObjectId format', async () => {
|
||||||
const validToken = jwt.sign(
|
const validToken = jwt.sign(
|
||||||
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
||||||
process.env.JWT_REFRESH_SECRET,
|
process.env.JWT_REFRESH_SECRET,
|
||||||
);
|
);
|
||||||
req.headers.cookie = `refreshToken=${validToken}`;
|
req.headers.cookie = `refreshToken=${validToken}`;
|
||||||
req.originalUrl = '/images/123/example.jpg'; // Invalid ObjectId
|
req.originalUrl = '/images/123/example.jpg'; // Invalid ObjectId
|
||||||
validateImageRequest(req, res, next);
|
await validateImageRequest(req, res, next);
|
||||||
expect(res.status).toHaveBeenCalledWith(403);
|
expect(res.status).toHaveBeenCalledWith(403);
|
||||||
expect(res.send).toHaveBeenCalledWith('Access Denied');
|
expect(res.send).toHaveBeenCalledWith('Access Denied');
|
||||||
});
|
});
|
||||||
|
|
||||||
// File traversal tests
|
// File traversal tests
|
||||||
test('should prevent file traversal attempts', () => {
|
test('should prevent file traversal attempts', async () => {
|
||||||
const validToken = jwt.sign(
|
const validToken = jwt.sign(
|
||||||
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
||||||
process.env.JWT_REFRESH_SECRET,
|
process.env.JWT_REFRESH_SECRET,
|
||||||
|
|
@ -103,23 +115,23 @@ describe('validateImageRequest middleware', () => {
|
||||||
`/images/${validObjectId}/%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd`,
|
`/images/${validObjectId}/%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd`,
|
||||||
];
|
];
|
||||||
|
|
||||||
traversalAttempts.forEach((attempt) => {
|
for (const attempt of traversalAttempts) {
|
||||||
req.originalUrl = attempt;
|
req.originalUrl = attempt;
|
||||||
validateImageRequest(req, res, next);
|
await validateImageRequest(req, res, next);
|
||||||
expect(res.status).toHaveBeenCalledWith(403);
|
expect(res.status).toHaveBeenCalledWith(403);
|
||||||
expect(res.send).toHaveBeenCalledWith('Access Denied');
|
expect(res.send).toHaveBeenCalledWith('Access Denied');
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should handle URL encoded characters in valid paths', () => {
|
test('should handle URL encoded characters in valid paths', async () => {
|
||||||
const validToken = jwt.sign(
|
const validToken = jwt.sign(
|
||||||
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
||||||
process.env.JWT_REFRESH_SECRET,
|
process.env.JWT_REFRESH_SECRET,
|
||||||
);
|
);
|
||||||
req.headers.cookie = `refreshToken=${validToken}`;
|
req.headers.cookie = `refreshToken=${validToken}`;
|
||||||
req.originalUrl = `/images/${validObjectId}/image%20with%20spaces.jpg`;
|
req.originalUrl = `/images/${validObjectId}/image%20with%20spaces.jpg`;
|
||||||
validateImageRequest(req, res, next);
|
await validateImageRequest(req, res, next);
|
||||||
expect(next).toHaveBeenCalled();
|
expect(next).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ jest.mock('~/server/services/Config', () => ({
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
getAppConfig: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('Multer Configuration', () => {
|
describe('Multer Configuration', () => {
|
||||||
|
|
@ -36,13 +37,6 @@ describe('Multer Configuration', () => {
|
||||||
|
|
||||||
mockReq = {
|
mockReq = {
|
||||||
user: { id: 'test-user-123' },
|
user: { id: 'test-user-123' },
|
||||||
app: {
|
|
||||||
locals: {
|
|
||||||
paths: {
|
|
||||||
uploads: tempDir,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
body: {},
|
body: {},
|
||||||
originalUrl: '/api/files/upload',
|
originalUrl: '/api/files/upload',
|
||||||
};
|
};
|
||||||
|
|
@ -53,6 +47,14 @@ describe('Multer Configuration', () => {
|
||||||
size: 1024,
|
size: 1024,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Mock getAppConfig to return paths
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
|
getAppConfig.mockResolvedValue({
|
||||||
|
paths: {
|
||||||
|
uploads: tempDir,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Clear mocks
|
// Clear mocks
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
@ -79,7 +81,12 @@ describe('Multer Configuration', () => {
|
||||||
|
|
||||||
it("should create directory recursively if it doesn't exist", (done) => {
|
it("should create directory recursively if it doesn't exist", (done) => {
|
||||||
const deepPath = path.join(tempDir, 'deep', 'nested', 'path');
|
const deepPath = path.join(tempDir, 'deep', 'nested', 'path');
|
||||||
mockReq.app.locals.paths.uploads = deepPath;
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
|
getAppConfig.mockResolvedValue({
|
||||||
|
paths: {
|
||||||
|
uploads: deepPath,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const cb = jest.fn((err, destination) => {
|
const cb = jest.fn((err, destination) => {
|
||||||
expect(err).toBeNull();
|
expect(err).toBeNull();
|
||||||
|
|
@ -465,7 +472,12 @@ describe('Multer Configuration', () => {
|
||||||
it('should handle file system errors when directory creation fails', (done) => {
|
it('should handle file system errors when directory creation fails', (done) => {
|
||||||
// Test with a non-existent parent directory to simulate fs issues
|
// Test with a non-existent parent directory to simulate fs issues
|
||||||
const invalidPath = '/nonexistent/path/that/should/not/exist';
|
const invalidPath = '/nonexistent/path/that/should/not/exist';
|
||||||
mockReq.app.locals.paths.uploads = invalidPath;
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
|
getAppConfig.mockResolvedValue({
|
||||||
|
paths: {
|
||||||
|
uploads: invalidPath,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Call getDestination which should fail due to permission/path issues
|
// Call getDestination which should fail due to permission/path issues
|
||||||
|
|
|
||||||
|
|
@ -32,15 +32,19 @@ jest.mock('./start/checks', () => ({
|
||||||
checkWebSearchConfig: jest.fn(),
|
checkWebSearchConfig: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('./Config/getAppConfig', () => ({
|
||||||
|
initializeAppConfig: jest.fn(),
|
||||||
|
getAppConfig: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
const AppService = require('./AppService');
|
const AppService = require('./AppService');
|
||||||
const { loadDefaultInterface } = require('./start/interface');
|
const { loadDefaultInterface } = require('./start/interface');
|
||||||
|
|
||||||
describe('AppService interface configuration', () => {
|
describe('AppService interface configuration', () => {
|
||||||
let app;
|
|
||||||
let mockLoadCustomConfig;
|
let mockLoadCustomConfig;
|
||||||
|
const { initializeAppConfig } = require('./Config/getAppConfig');
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
app = { locals: {} };
|
|
||||||
jest.resetModules();
|
jest.resetModules();
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
mockLoadCustomConfig = require('./Config/loadCustomConfig');
|
mockLoadCustomConfig = require('./Config/loadCustomConfig');
|
||||||
|
|
@ -50,10 +54,16 @@ describe('AppService interface configuration', () => {
|
||||||
mockLoadCustomConfig.mockResolvedValue({});
|
mockLoadCustomConfig.mockResolvedValue({});
|
||||||
loadDefaultInterface.mockResolvedValue({ prompts: true, bookmarks: true });
|
loadDefaultInterface.mockResolvedValue({ prompts: true, bookmarks: true });
|
||||||
|
|
||||||
await AppService(app);
|
await AppService();
|
||||||
|
|
||||||
expect(app.locals.interfaceConfig.prompts).toBe(true);
|
expect(initializeAppConfig).toHaveBeenCalledWith(
|
||||||
expect(app.locals.interfaceConfig.bookmarks).toBe(true);
|
expect.objectContaining({
|
||||||
|
interfaceConfig: expect.objectContaining({
|
||||||
|
prompts: true,
|
||||||
|
bookmarks: true,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
expect(loadDefaultInterface).toHaveBeenCalled();
|
expect(loadDefaultInterface).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -61,10 +71,16 @@ describe('AppService interface configuration', () => {
|
||||||
mockLoadCustomConfig.mockResolvedValue({ interface: { prompts: false, bookmarks: false } });
|
mockLoadCustomConfig.mockResolvedValue({ interface: { prompts: false, bookmarks: false } });
|
||||||
loadDefaultInterface.mockResolvedValue({ prompts: false, bookmarks: false });
|
loadDefaultInterface.mockResolvedValue({ prompts: false, bookmarks: false });
|
||||||
|
|
||||||
await AppService(app);
|
await AppService();
|
||||||
|
|
||||||
expect(app.locals.interfaceConfig.prompts).toBe(false);
|
expect(initializeAppConfig).toHaveBeenCalledWith(
|
||||||
expect(app.locals.interfaceConfig.bookmarks).toBe(false);
|
expect.objectContaining({
|
||||||
|
interfaceConfig: expect.objectContaining({
|
||||||
|
prompts: false,
|
||||||
|
bookmarks: false,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
expect(loadDefaultInterface).toHaveBeenCalled();
|
expect(loadDefaultInterface).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -72,10 +88,18 @@ describe('AppService interface configuration', () => {
|
||||||
mockLoadCustomConfig.mockResolvedValue({});
|
mockLoadCustomConfig.mockResolvedValue({});
|
||||||
loadDefaultInterface.mockResolvedValue({});
|
loadDefaultInterface.mockResolvedValue({});
|
||||||
|
|
||||||
await AppService(app);
|
await AppService();
|
||||||
|
|
||||||
expect(app.locals.interfaceConfig.prompts).toBeUndefined();
|
expect(initializeAppConfig).toHaveBeenCalledWith(
|
||||||
expect(app.locals.interfaceConfig.bookmarks).toBeUndefined();
|
expect.objectContaining({
|
||||||
|
interfaceConfig: expect.anything(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify that prompts and bookmarks are undefined when not provided
|
||||||
|
const initCall = initializeAppConfig.mock.calls[0][0];
|
||||||
|
expect(initCall.interfaceConfig.prompts).toBeUndefined();
|
||||||
|
expect(initCall.interfaceConfig.bookmarks).toBeUndefined();
|
||||||
expect(loadDefaultInterface).toHaveBeenCalled();
|
expect(loadDefaultInterface).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -83,10 +107,16 @@ describe('AppService interface configuration', () => {
|
||||||
mockLoadCustomConfig.mockResolvedValue({ interface: { prompts: true, bookmarks: false } });
|
mockLoadCustomConfig.mockResolvedValue({ interface: { prompts: true, bookmarks: false } });
|
||||||
loadDefaultInterface.mockResolvedValue({ prompts: true, bookmarks: false });
|
loadDefaultInterface.mockResolvedValue({ prompts: true, bookmarks: false });
|
||||||
|
|
||||||
await AppService(app);
|
await AppService();
|
||||||
|
|
||||||
expect(app.locals.interfaceConfig.prompts).toBe(true);
|
expect(initializeAppConfig).toHaveBeenCalledWith(
|
||||||
expect(app.locals.interfaceConfig.bookmarks).toBe(false);
|
expect.objectContaining({
|
||||||
|
interfaceConfig: expect.objectContaining({
|
||||||
|
prompts: true,
|
||||||
|
bookmarks: false,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
expect(loadDefaultInterface).toHaveBeenCalled();
|
expect(loadDefaultInterface).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -108,14 +138,19 @@ describe('AppService interface configuration', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await AppService(app);
|
await AppService();
|
||||||
|
|
||||||
expect(app.locals.interfaceConfig.peoplePicker).toBeDefined();
|
expect(initializeAppConfig).toHaveBeenCalledWith(
|
||||||
expect(app.locals.interfaceConfig.peoplePicker).toMatchObject({
|
expect.objectContaining({
|
||||||
|
interfaceConfig: expect.objectContaining({
|
||||||
|
peoplePicker: expect.objectContaining({
|
||||||
users: true,
|
users: true,
|
||||||
groups: true,
|
groups: true,
|
||||||
roles: true,
|
roles: true,
|
||||||
});
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
expect(loadDefaultInterface).toHaveBeenCalled();
|
expect(loadDefaultInterface).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -137,11 +172,19 @@ describe('AppService interface configuration', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await AppService(app);
|
await AppService();
|
||||||
|
|
||||||
expect(app.locals.interfaceConfig.peoplePicker.users).toBe(true);
|
expect(initializeAppConfig).toHaveBeenCalledWith(
|
||||||
expect(app.locals.interfaceConfig.peoplePicker.groups).toBe(false);
|
expect.objectContaining({
|
||||||
expect(app.locals.interfaceConfig.peoplePicker.roles).toBe(true);
|
interfaceConfig: expect.objectContaining({
|
||||||
|
peoplePicker: expect.objectContaining({
|
||||||
|
users: true,
|
||||||
|
groups: false,
|
||||||
|
roles: true,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set default peoplePicker permissions when not provided', async () => {
|
it('should set default peoplePicker permissions when not provided', async () => {
|
||||||
|
|
@ -154,11 +197,18 @@ describe('AppService interface configuration', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await AppService(app);
|
await AppService();
|
||||||
|
|
||||||
expect(app.locals.interfaceConfig.peoplePicker).toBeDefined();
|
expect(initializeAppConfig).toHaveBeenCalledWith(
|
||||||
expect(app.locals.interfaceConfig.peoplePicker.users).toBe(true);
|
expect.objectContaining({
|
||||||
expect(app.locals.interfaceConfig.peoplePicker.groups).toBe(true);
|
interfaceConfig: expect.objectContaining({
|
||||||
expect(app.locals.interfaceConfig.peoplePicker.roles).toBe(true);
|
peoplePicker: expect.objectContaining({
|
||||||
|
users: true,
|
||||||
|
groups: true,
|
||||||
|
roles: true,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
const { fetchModels } = require('~/server/services/ModelService');
|
const { fetchModels } = require('~/server/services/ModelService');
|
||||||
const { getCustomConfig } = require('./getCustomConfig');
|
const { getCustomConfig } = require('./getCustomConfig');
|
||||||
|
const { getAppConfig } = require('./getAppConfig');
|
||||||
const loadConfigModels = require('./loadConfigModels');
|
const loadConfigModels = require('./loadConfigModels');
|
||||||
|
|
||||||
jest.mock('~/server/services/ModelService');
|
jest.mock('~/server/services/ModelService');
|
||||||
jest.mock('./getCustomConfig');
|
jest.mock('./getCustomConfig');
|
||||||
|
jest.mock('./getAppConfig');
|
||||||
|
|
||||||
const exampleConfig = {
|
const exampleConfig = {
|
||||||
endpoints: {
|
endpoints: {
|
||||||
|
|
@ -60,7 +62,7 @@ const exampleConfig = {
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('loadConfigModels', () => {
|
describe('loadConfigModels', () => {
|
||||||
const mockRequest = { app: { locals: {} }, user: { id: 'testUserId' } };
|
const mockRequest = { user: { id: 'testUserId' } };
|
||||||
|
|
||||||
const originalEnv = process.env;
|
const originalEnv = process.env;
|
||||||
|
|
||||||
|
|
@ -68,6 +70,9 @@ describe('loadConfigModels', () => {
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
jest.resetModules();
|
jest.resetModules();
|
||||||
process.env = { ...originalEnv };
|
process.env = { ...originalEnv };
|
||||||
|
|
||||||
|
// Default mock for getAppConfig
|
||||||
|
getAppConfig.mockResolvedValue({});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|
@ -81,7 +86,9 @@ describe('loadConfigModels', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles azure models and endpoint correctly', async () => {
|
it('handles azure models and endpoint correctly', async () => {
|
||||||
mockRequest.app.locals.azureOpenAI = { modelNames: ['model1', 'model2'] };
|
getAppConfig.mockResolvedValue({
|
||||||
|
azureOpenAI: { modelNames: ['model1', 'model2'] },
|
||||||
|
});
|
||||||
getCustomConfig.mockResolvedValue({
|
getCustomConfig.mockResolvedValue({
|
||||||
endpoints: {
|
endpoints: {
|
||||||
azureOpenAI: {
|
azureOpenAI: {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { logger } from '@librechat/data-schemas';
|
||||||
import { EModelEndpoint, EToolResources, AgentCapabilities } from 'librechat-data-provider';
|
import { EModelEndpoint, EToolResources, AgentCapabilities } from 'librechat-data-provider';
|
||||||
import type { TAgentsEndpoint, TFile } from 'librechat-data-provider';
|
import type { TAgentsEndpoint, TFile } from 'librechat-data-provider';
|
||||||
import type { Request as ServerRequest } from 'express';
|
import type { Request as ServerRequest } from 'express';
|
||||||
|
import type { IUser } from '@librechat/data-schemas';
|
||||||
import type { TGetFiles } from './resources';
|
import type { TGetFiles } from './resources';
|
||||||
import type { AppConfig } from '~/types';
|
import type { AppConfig } from '~/types';
|
||||||
|
|
||||||
|
|
@ -14,7 +15,7 @@ jest.mock('@librechat/data-schemas', () => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('primeResources', () => {
|
describe('primeResources', () => {
|
||||||
let mockReq: ServerRequest;
|
let mockReq: ServerRequest & { user?: IUser };
|
||||||
let mockAppConfig: AppConfig;
|
let mockAppConfig: AppConfig;
|
||||||
let mockGetFiles: jest.MockedFunction<TGetFiles>;
|
let mockGetFiles: jest.MockedFunction<TGetFiles>;
|
||||||
let requestFileSet: Set<string>;
|
let requestFileSet: Set<string>;
|
||||||
|
|
@ -24,15 +25,7 @@ describe('primeResources', () => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
// Setup mock request
|
// Setup mock request
|
||||||
mockReq = {
|
mockReq = {} as unknown as ServerRequest & { user?: IUser };
|
||||||
app: {
|
|
||||||
locals: {
|
|
||||||
[EModelEndpoint.agents]: {
|
|
||||||
capabilities: [AgentCapabilities.ocr],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as unknown as ServerRequest;
|
|
||||||
|
|
||||||
// Setup mock appConfig
|
// Setup mock appConfig
|
||||||
mockAppConfig = {
|
mockAppConfig = {
|
||||||
|
|
@ -94,7 +87,6 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
describe('when OCR is disabled', () => {
|
describe('when OCR is disabled', () => {
|
||||||
it('should not fetch OCR files even if tool_resources has OCR file_ids', async () => {
|
it('should not fetch OCR files even if tool_resources has OCR file_ids', async () => {
|
||||||
(mockReq.app as ServerRequest['app']).locals[EModelEndpoint.agents].capabilities = [];
|
|
||||||
(mockAppConfig[EModelEndpoint.agents] as TAgentsEndpoint).capabilities = [];
|
(mockAppConfig[EModelEndpoint.agents] as TAgentsEndpoint).capabilities = [];
|
||||||
|
|
||||||
const tool_resources = {
|
const tool_resources = {
|
||||||
|
|
@ -956,8 +948,8 @@ describe('primeResources', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('edge cases', () => {
|
describe('edge cases', () => {
|
||||||
it('should handle missing app.locals gracefully', async () => {
|
it('should handle missing appConfig agents endpoint gracefully', async () => {
|
||||||
const reqWithoutLocals = {} as ServerRequest;
|
const reqWithoutLocals = {} as ServerRequest & { user?: IUser };
|
||||||
const emptyAppConfig = {} as AppConfig;
|
const emptyAppConfig = {} as AppConfig;
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
|
|
@ -974,9 +966,9 @@ describe('primeResources', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockGetFiles).not.toHaveBeenCalled();
|
expect(mockGetFiles).not.toHaveBeenCalled();
|
||||||
// When app.locals is missing and there's an error accessing properties,
|
// When appConfig agents endpoint is missing, OCR is disabled
|
||||||
// the function falls back to the catch block which returns an empty array
|
// and no attachments are provided, the function returns undefined
|
||||||
expect(result.attachments).toEqual([]);
|
expect(result.attachments).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle undefined tool_resources', async () => {
|
it('should handle undefined tool_resources', async () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue