| const mongoose = require('mongoose'); |
| const { MongoMemoryServer } = require('mongodb-memory-server'); |
|
|
| const mockPluginService = { |
| updateUserPluginAuth: jest.fn(), |
| deleteUserPluginAuth: jest.fn(), |
| getUserPluginAuthValue: jest.fn(), |
| }; |
|
|
| jest.mock('~/server/services/PluginService', () => mockPluginService); |
|
|
| jest.mock('~/server/services/Config', () => ({ |
| getAppConfig: jest.fn().mockResolvedValue({ |
| |
| paths: { uploads: '/tmp' }, |
| fileStrategy: 'local', |
| filteredTools: [], |
| includedTools: [], |
| }), |
| getCachedTools: jest.fn().mockResolvedValue({ |
| |
| dalle: { |
| type: 'function', |
| function: { |
| name: 'dalle', |
| description: 'DALL-E image generation', |
| parameters: {}, |
| }, |
| }, |
| }), |
| })); |
|
|
| const { Calculator } = require('@librechat/agents'); |
|
|
| const { User } = require('~/db/models'); |
| const PluginService = require('~/server/services/PluginService'); |
| const { validateTools, loadTools, loadToolWithAuth } = require('./handleTools'); |
| const { StructuredSD, availableTools, DALLE3 } = require('../'); |
|
|
| describe('Tool Handlers', () => { |
| let mongoServer; |
| let fakeUser; |
| const pluginKey = 'dalle'; |
| const pluginKey2 = 'wolfram'; |
| const ToolClass = DALLE3; |
| const initialTools = [pluginKey, pluginKey2]; |
| const mockCredential = 'mock-credential'; |
| const mainPlugin = availableTools.find((tool) => tool.pluginKey === pluginKey); |
| const authConfigs = mainPlugin.authConfig; |
|
|
| beforeAll(async () => { |
| mongoServer = await MongoMemoryServer.create(); |
| const mongoUri = mongoServer.getUri(); |
| await mongoose.connect(mongoUri); |
|
|
| const userAuthValues = {}; |
| mockPluginService.getUserPluginAuthValue.mockImplementation((userId, authField) => { |
| return userAuthValues[`${userId}-${authField}`]; |
| }); |
| mockPluginService.updateUserPluginAuth.mockImplementation( |
| (userId, authField, _pluginKey, credential) => { |
| const fields = authField.split('||'); |
| fields.forEach((field) => { |
| userAuthValues[`${userId}-${field}`] = credential; |
| }); |
| }, |
| ); |
|
|
| fakeUser = new User({ |
| name: 'Fake User', |
| username: 'fakeuser', |
| email: 'fakeuser@example.com', |
| emailVerified: false, |
| |
| password: 'fakepassword123', |
| avatar: '', |
| provider: 'local', |
| role: 'USER', |
| googleId: null, |
| plugins: [], |
| refreshToken: [], |
| }); |
| await fakeUser.save(); |
| for (const authConfig of authConfigs) { |
| await PluginService.updateUserPluginAuth( |
| fakeUser._id, |
| authConfig.authField, |
| pluginKey, |
| mockCredential, |
| ); |
| } |
| }); |
|
|
| afterAll(async () => { |
| await mongoose.disconnect(); |
| await mongoServer.stop(); |
| }); |
|
|
| beforeEach(async () => { |
| |
| jest.clearAllMocks(); |
|
|
| |
| const userAuthValues = {}; |
| mockPluginService.getUserPluginAuthValue.mockImplementation((userId, authField) => { |
| return userAuthValues[`${userId}-${authField}`]; |
| }); |
| mockPluginService.updateUserPluginAuth.mockImplementation( |
| (userId, authField, _pluginKey, credential) => { |
| const fields = authField.split('||'); |
| fields.forEach((field) => { |
| userAuthValues[`${userId}-${field}`] = credential; |
| }); |
| }, |
| ); |
|
|
| |
| for (const authConfig of authConfigs) { |
| await PluginService.updateUserPluginAuth( |
| fakeUser._id, |
| authConfig.authField, |
| pluginKey, |
| mockCredential, |
| ); |
| } |
| }); |
|
|
| describe('validateTools', () => { |
| it('returns valid tools given input tools and user authentication', async () => { |
| const validTools = await validateTools(fakeUser._id, initialTools); |
| expect(validTools).toBeDefined(); |
| expect(validTools.some((tool) => tool === pluginKey)).toBeTruthy(); |
| expect(validTools.length).toBeGreaterThan(0); |
| }); |
|
|
| it('removes tools without valid credentials from the validTools array', async () => { |
| const validTools = await validateTools(fakeUser._id, initialTools); |
| expect(validTools.some((tool) => tool.pluginKey === pluginKey2)).toBeFalsy(); |
| }); |
|
|
| it('returns an empty array when no authenticated tools are provided', async () => { |
| const validTools = await validateTools(fakeUser._id, []); |
| expect(validTools).toEqual([]); |
| }); |
|
|
| it('should validate a tool from an Environment Variable', async () => { |
| const plugin = availableTools.find((tool) => tool.pluginKey === pluginKey2); |
| const authConfigs = plugin.authConfig; |
| for (const authConfig of authConfigs) { |
| process.env[authConfig.authField] = mockCredential; |
| } |
| const validTools = await validateTools(fakeUser._id, [pluginKey2]); |
| expect(validTools.length).toEqual(1); |
| for (const authConfig of authConfigs) { |
| delete process.env[authConfig.authField]; |
| } |
| }); |
| }); |
|
|
| describe('loadTools', () => { |
| let toolFunctions; |
| let loadTool1; |
| let loadTool2; |
| let loadTool3; |
| const sampleTools = [...initialTools, 'calculator']; |
| let ToolClass2 = Calculator; |
| let remainingTools = availableTools.filter( |
| (tool) => sampleTools.indexOf(tool.pluginKey) === -1, |
| ); |
|
|
| beforeAll(async () => { |
| const toolMap = await loadTools({ |
| user: fakeUser._id, |
| tools: sampleTools, |
| returnMap: true, |
| useSpecs: true, |
| }); |
| toolFunctions = toolMap; |
| loadTool1 = toolFunctions[sampleTools[0]]; |
| loadTool2 = toolFunctions[sampleTools[1]]; |
| loadTool3 = toolFunctions[sampleTools[2]]; |
| }); |
|
|
| let originalEnv; |
|
|
| beforeEach(() => { |
| originalEnv = process.env; |
| process.env = { ...originalEnv }; |
| }); |
|
|
| afterEach(() => { |
| process.env = originalEnv; |
| }); |
|
|
| it('returns the expected load functions for requested tools', async () => { |
| expect(loadTool1).toBeDefined(); |
| expect(loadTool2).toBeDefined(); |
| expect(loadTool3).toBeDefined(); |
|
|
| for (const tool of remainingTools) { |
| expect(toolFunctions[tool.pluginKey]).toBeUndefined(); |
| } |
| }); |
|
|
| it('should initialize an authenticated tool or one without authentication', async () => { |
| const authTool = await loadTool1(); |
| const tool = await loadTool3(); |
| expect(authTool).toBeInstanceOf(ToolClass); |
| expect(tool).toBeInstanceOf(ToolClass2); |
| }); |
|
|
| it('should initialize an authenticated tool with primary auth field', async () => { |
| process.env.DALLE3_API_KEY = 'mocked_api_key'; |
| const initToolFunction = loadToolWithAuth( |
| 'userId', |
| ['DALLE3_API_KEY||DALLE_API_KEY'], |
| ToolClass, |
| ); |
| const authTool = await initToolFunction(); |
|
|
| expect(authTool).toBeInstanceOf(ToolClass); |
| expect(mockPluginService.getUserPluginAuthValue).not.toHaveBeenCalled(); |
| }); |
|
|
| it('should initialize an authenticated tool with alternate auth field when primary is missing', async () => { |
| delete process.env.DALLE3_API_KEY; |
| process.env.DALLE_API_KEY = 'mocked_alternate_api_key'; |
| const initToolFunction = loadToolWithAuth( |
| 'userId', |
| ['DALLE3_API_KEY||DALLE_API_KEY'], |
| ToolClass, |
| ); |
| const authTool = await initToolFunction(); |
|
|
| expect(authTool).toBeInstanceOf(ToolClass); |
| expect(mockPluginService.getUserPluginAuthValue).toHaveBeenCalledTimes(1); |
| expect(mockPluginService.getUserPluginAuthValue).toHaveBeenCalledWith( |
| 'userId', |
| 'DALLE3_API_KEY', |
| true, |
| ); |
| }); |
|
|
| it('should fallback to getUserPluginAuthValue when env vars are missing', async () => { |
| mockPluginService.updateUserPluginAuth('userId', 'DALLE_API_KEY', 'dalle', 'mocked_api_key'); |
| const initToolFunction = loadToolWithAuth( |
| 'userId', |
| ['DALLE3_API_KEY||DALLE_API_KEY'], |
| ToolClass, |
| ); |
| const authTool = await initToolFunction(); |
|
|
| expect(authTool).toBeInstanceOf(ToolClass); |
| expect(mockPluginService.getUserPluginAuthValue).toHaveBeenCalledTimes(2); |
| }); |
|
|
| it('should throw an error for an unauthenticated tool', async () => { |
| try { |
| await loadTool2(); |
| } catch (error) { |
| expect(error).toBeDefined(); |
| } |
| }); |
| it('returns an empty object when no tools are requested', async () => { |
| toolFunctions = await loadTools({ |
| user: fakeUser._id, |
| returnMap: true, |
| useSpecs: true, |
| }); |
| expect(toolFunctions).toEqual({}); |
| }); |
| it('should return the StructuredTool version when using functions', async () => { |
| process.env.SD_WEBUI_URL = mockCredential; |
| toolFunctions = await loadTools({ |
| user: fakeUser._id, |
| tools: ['stable-diffusion'], |
| functions: true, |
| returnMap: true, |
| useSpecs: true, |
| }); |
| const structuredTool = await toolFunctions['stable-diffusion'](); |
| expect(structuredTool).toBeInstanceOf(StructuredSD); |
| delete process.env.SD_WEBUI_URL; |
| }); |
| }); |
| }); |
|
|