| const express = require('express'); |
| const request = require('supertest'); |
| const mongoose = require('mongoose'); |
| const { v4: uuidv4 } = require('uuid'); |
| const { createMethods } = require('@librechat/data-schemas'); |
| const { MongoMemoryServer } = require('mongodb-memory-server'); |
| const { AccessRoleIds, ResourceType, PrincipalType } = require('librechat-data-provider'); |
| const { createAgent } = require('~/models/Agent'); |
| const { createFile } = require('~/models/File'); |
|
|
| |
| jest.mock('~/server/services/Files/process', () => ({ |
| processDeleteRequest: jest.fn().mockResolvedValue({}), |
| filterFile: jest.fn(), |
| processFileUpload: jest.fn(), |
| processAgentFileUpload: jest.fn(), |
| })); |
|
|
| jest.mock('~/server/services/Files/strategies', () => ({ |
| getStrategyFunctions: jest.fn(() => ({})), |
| })); |
|
|
| jest.mock('~/server/controllers/assistants/helpers', () => ({ |
| getOpenAIClient: jest.fn(), |
| })); |
|
|
| jest.mock('~/server/services/Tools/credentials', () => ({ |
| loadAuthValues: jest.fn(), |
| })); |
|
|
| |
| const router = require('~/server/routes/files/files'); |
|
|
| describe('File Routes - Agent Files Endpoint', () => { |
| let app; |
| let mongoServer; |
| let authorId; |
| let otherUserId; |
| let agentId; |
| let fileId1; |
| let fileId2; |
| let fileId3; |
| let File; |
| let User; |
| let Agent; |
| let methods; |
| let AclEntry; |
| |
| let AccessRole; |
| let modelsToCleanup = []; |
|
|
| beforeAll(async () => { |
| mongoServer = await MongoMemoryServer.create(); |
| const mongoUri = mongoServer.getUri(); |
| await mongoose.connect(mongoUri); |
|
|
| |
| const { createModels } = require('@librechat/data-schemas'); |
| const models = createModels(mongoose); |
|
|
| |
| modelsToCleanup = Object.keys(models); |
|
|
| |
| Object.assign(mongoose.models, models); |
|
|
| |
| methods = createMethods(mongoose); |
|
|
| |
| File = models.File; |
| Agent = models.Agent; |
| AclEntry = models.AclEntry; |
| User = models.User; |
| AccessRole = models.AccessRole; |
|
|
| |
| await methods.seedDefaultRoles(); |
|
|
| app = express(); |
| app.use(express.json()); |
|
|
| |
| app.use((req, res, next) => { |
| req.user = { id: otherUserId || 'default-user' }; |
| req.app = { locals: {} }; |
| next(); |
| }); |
|
|
| app.use('/files', router); |
| }); |
|
|
| afterAll(async () => { |
| |
| const collections = mongoose.connection.collections; |
| for (const key in collections) { |
| await collections[key].deleteMany({}); |
| } |
|
|
| |
| for (const modelName of modelsToCleanup) { |
| if (mongoose.models[modelName]) { |
| delete mongoose.models[modelName]; |
| } |
| } |
|
|
| await mongoose.disconnect(); |
| await mongoServer.stop(); |
| }); |
|
|
| beforeEach(async () => { |
| |
| await File.deleteMany({}); |
| await Agent.deleteMany({}); |
| await User.deleteMany({}); |
| await AclEntry.deleteMany({}); |
| |
|
|
| |
| authorId = new mongoose.Types.ObjectId(); |
| otherUserId = new mongoose.Types.ObjectId(); |
| agentId = uuidv4(); |
| fileId1 = uuidv4(); |
| fileId2 = uuidv4(); |
| fileId3 = uuidv4(); |
|
|
| |
| await User.create({ |
| _id: authorId, |
| username: 'author', |
| email: 'author@test.com', |
| }); |
|
|
| await User.create({ |
| _id: otherUserId, |
| username: 'other', |
| email: 'other@test.com', |
| }); |
|
|
| |
| await createFile({ |
| user: authorId, |
| file_id: fileId1, |
| filename: 'file1.txt', |
| filepath: '/uploads/file1.txt', |
| bytes: 100, |
| type: 'text/plain', |
| }); |
|
|
| await createFile({ |
| user: authorId, |
| file_id: fileId2, |
| filename: 'file2.txt', |
| filepath: '/uploads/file2.txt', |
| bytes: 200, |
| type: 'text/plain', |
| }); |
|
|
| await createFile({ |
| user: otherUserId, |
| file_id: fileId3, |
| filename: 'file3.txt', |
| filepath: '/uploads/file3.txt', |
| bytes: 300, |
| type: 'text/plain', |
| }); |
| }); |
|
|
| describe('GET /files/agent/:agent_id', () => { |
| it('should return files accessible through the agent for non-author with EDIT permission', async () => { |
| |
| const agent = await createAgent({ |
| id: agentId, |
| name: 'Test Agent', |
| provider: 'openai', |
| model: 'gpt-4', |
| author: authorId, |
| tool_resources: { |
| file_search: { |
| file_ids: [fileId1, fileId2], |
| }, |
| }, |
| }); |
|
|
| |
| const { grantPermission } = require('~/server/services/PermissionService'); |
| await grantPermission({ |
| principalType: PrincipalType.USER, |
| principalId: otherUserId, |
| resourceType: ResourceType.AGENT, |
| resourceId: agent._id, |
| accessRoleId: AccessRoleIds.AGENT_EDITOR, |
| grantedBy: authorId, |
| }); |
|
|
| |
| app.use((req, res, next) => { |
| req.user = { id: otherUserId.toString() }; |
| next(); |
| }); |
|
|
| const response = await request(app).get(`/files/agent/${agentId}`); |
|
|
| expect(response.status).toBe(200); |
| expect(Array.isArray(response.body)).toBe(true); |
| expect(response.body).toHaveLength(2); |
| expect(response.body.map((f) => f.file_id)).toContain(fileId1); |
| expect(response.body.map((f) => f.file_id)).toContain(fileId2); |
| }); |
|
|
| it('should return 400 when agent_id is not provided', async () => { |
| const response = await request(app).get('/files/agent/'); |
|
|
| expect(response.status).toBe(404); |
| }); |
|
|
| it('should return empty array for non-existent agent', async () => { |
| const response = await request(app).get('/files/agent/non-existent-agent'); |
|
|
| expect(response.status).toBe(200); |
| expect(Array.isArray(response.body)).toBe(true); |
| expect(response.body).toEqual([]); |
| }); |
|
|
| it('should return empty array when user only has VIEW permission', async () => { |
| |
| const agent = await createAgent({ |
| id: agentId, |
| name: 'Test Agent', |
| provider: 'openai', |
| model: 'gpt-4', |
| author: authorId, |
| tool_resources: { |
| file_search: { |
| file_ids: [fileId1, fileId2], |
| }, |
| }, |
| }); |
|
|
| |
| const { grantPermission } = require('~/server/services/PermissionService'); |
| await grantPermission({ |
| principalType: PrincipalType.USER, |
| principalId: otherUserId, |
| resourceType: ResourceType.AGENT, |
| resourceId: agent._id, |
| accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| grantedBy: authorId, |
| }); |
|
|
| const response = await request(app).get(`/files/agent/${agentId}`); |
|
|
| expect(response.status).toBe(200); |
| expect(Array.isArray(response.body)).toBe(true); |
| expect(response.body).toEqual([]); |
| }); |
|
|
| it('should return agent files for agent author', async () => { |
| |
| await createAgent({ |
| id: agentId, |
| name: 'Test Agent', |
| provider: 'openai', |
| model: 'gpt-4', |
| author: authorId, |
| tool_resources: { |
| file_search: { |
| file_ids: [fileId1, fileId2], |
| }, |
| }, |
| }); |
|
|
| |
| const authorApp = express(); |
| authorApp.use(express.json()); |
| authorApp.use((req, res, next) => { |
| req.user = { id: authorId.toString() }; |
| req.app = { locals: {} }; |
| next(); |
| }); |
| authorApp.use('/files', router); |
|
|
| const response = await request(authorApp).get(`/files/agent/${agentId}`); |
|
|
| expect(response.status).toBe(200); |
| expect(Array.isArray(response.body)).toBe(true); |
| expect(response.body).toHaveLength(2); |
| }); |
|
|
| it('should return files uploaded by other users to shared agent for author', async () => { |
| const anotherUserId = new mongoose.Types.ObjectId(); |
| const otherUserFileId = uuidv4(); |
|
|
| await User.create({ |
| _id: anotherUserId, |
| username: 'another', |
| email: 'another@test.com', |
| }); |
|
|
| await createFile({ |
| user: anotherUserId, |
| file_id: otherUserFileId, |
| filename: 'other-user-file.txt', |
| filepath: '/uploads/other-user-file.txt', |
| bytes: 400, |
| type: 'text/plain', |
| }); |
|
|
| |
| await createAgent({ |
| id: agentId, |
| name: 'Test Agent', |
| provider: 'openai', |
| model: 'gpt-4', |
| author: authorId, |
| tool_resources: { |
| file_search: { |
| file_ids: [fileId1, otherUserFileId], |
| }, |
| }, |
| }); |
|
|
| |
| const authorApp = express(); |
| authorApp.use(express.json()); |
| authorApp.use((req, res, next) => { |
| req.user = { id: authorId.toString() }; |
| req.app = { locals: {} }; |
| next(); |
| }); |
| authorApp.use('/files', router); |
|
|
| const response = await request(authorApp).get(`/files/agent/${agentId}`); |
|
|
| expect(response.status).toBe(200); |
| expect(Array.isArray(response.body)).toBe(true); |
| expect(response.body).toHaveLength(2); |
| expect(response.body.map((f) => f.file_id)).toContain(fileId1); |
| expect(response.body.map((f) => f.file_id)).toContain(otherUserFileId); |
| }); |
| }); |
| }); |
|
|