| const mongoose = require('mongoose'); |
| const { ObjectId } = require('mongodb'); |
| const { logger } = require('@librechat/data-schemas'); |
| const { MongoMemoryServer } = require('mongodb-memory-server'); |
| const { |
| Constants, |
| ResourceType, |
| AccessRoleIds, |
| PrincipalType, |
| PrincipalModel, |
| PermissionBits, |
| } = require('librechat-data-provider'); |
|
|
| |
| jest.mock('../../config/connect', () => jest.fn().mockResolvedValue(true)); |
|
|
| |
| logger.silent = true; |
|
|
| describe('PromptGroup Migration Script', () => { |
| let mongoServer; |
| let Prompt, PromptGroup, AclEntry, AccessRole, User, Project; |
| let migrateToPromptGroupPermissions; |
| let testOwner, testProject; |
| let ownerRole, viewerRole; |
|
|
| beforeAll(async () => { |
| |
| mongoServer = await MongoMemoryServer.create(); |
| const mongoUri = mongoServer.getUri(); |
| await mongoose.connect(mongoUri); |
|
|
| |
| const dbModels = require('~/db/models'); |
| Prompt = dbModels.Prompt; |
| PromptGroup = dbModels.PromptGroup; |
| AclEntry = dbModels.AclEntry; |
| AccessRole = dbModels.AccessRole; |
| User = dbModels.User; |
| Project = dbModels.Project; |
|
|
| |
| testOwner = await User.create({ |
| name: 'Test Owner', |
| email: 'owner@test.com', |
| role: 'USER', |
| }); |
|
|
| |
| const projectName = Constants.GLOBAL_PROJECT_NAME || 'instance'; |
| testProject = await Project.create({ |
| name: projectName, |
| description: 'Global project', |
| promptGroupIds: [], |
| }); |
|
|
| |
| ownerRole = await AccessRole.create({ |
| accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER, |
| name: 'Owner', |
| description: 'Full control over promptGroups', |
| resourceType: ResourceType.PROMPTGROUP, |
| permBits: |
| PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE | PermissionBits.SHARE, |
| }); |
|
|
| viewerRole = await AccessRole.create({ |
| accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER, |
| name: 'Viewer', |
| description: 'Can view promptGroups', |
| resourceType: ResourceType.PROMPTGROUP, |
| permBits: PermissionBits.VIEW, |
| }); |
|
|
| await AccessRole.create({ |
| accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR, |
| name: 'Editor', |
| description: 'Can view and edit promptGroups', |
| resourceType: ResourceType.PROMPTGROUP, |
| permBits: PermissionBits.VIEW | PermissionBits.EDIT, |
| }); |
|
|
| |
| const migration = require('../../config/migrate-prompt-permissions'); |
| migrateToPromptGroupPermissions = migration.migrateToPromptGroupPermissions; |
| }); |
|
|
| afterAll(async () => { |
| await mongoose.disconnect(); |
| await mongoServer.stop(); |
| }); |
|
|
| beforeEach(async () => { |
| |
| await Prompt.deleteMany({}); |
| await PromptGroup.deleteMany({}); |
| await AclEntry.deleteMany({}); |
| |
| testProject.promptGroupIds = []; |
| await testProject.save(); |
| }); |
|
|
| it('should categorize promptGroups correctly in dry run', async () => { |
| |
| const globalPromptGroup = await PromptGroup.create({ |
| name: 'Global Group', |
| author: testOwner._id, |
| authorName: testOwner.name, |
| productionId: new ObjectId(), |
| }); |
|
|
| |
| await PromptGroup.create({ |
| name: 'Private Group', |
| author: testOwner._id, |
| authorName: testOwner.name, |
| productionId: new ObjectId(), |
| }); |
|
|
| |
| testProject.promptGroupIds = [globalPromptGroup._id]; |
| await testProject.save(); |
|
|
| const result = await migrateToPromptGroupPermissions({ dryRun: true }); |
|
|
| expect(result.dryRun).toBe(true); |
| expect(result.summary.total).toBe(2); |
| expect(result.summary.globalViewAccess).toBe(1); |
| expect(result.summary.privateGroups).toBe(1); |
| }); |
|
|
| it('should grant appropriate permissions during migration', async () => { |
| |
| const globalPromptGroup = await PromptGroup.create({ |
| name: 'Global Group', |
| author: testOwner._id, |
| authorName: testOwner.name, |
| productionId: new ObjectId(), |
| }); |
|
|
| const privatePromptGroup = await PromptGroup.create({ |
| name: 'Private Group', |
| author: testOwner._id, |
| authorName: testOwner.name, |
| productionId: new ObjectId(), |
| }); |
|
|
| |
| testProject.promptGroupIds = [globalPromptGroup._id]; |
| await testProject.save(); |
|
|
| const result = await migrateToPromptGroupPermissions({ dryRun: false }); |
|
|
| expect(result.migrated).toBe(2); |
| expect(result.errors).toBe(0); |
| expect(result.ownerGrants).toBe(2); |
| expect(result.publicViewGrants).toBe(1); |
|
|
| |
| const globalOwnerEntry = await AclEntry.findOne({ |
| resourceType: ResourceType.PROMPTGROUP, |
| resourceId: globalPromptGroup._id, |
| principalType: PrincipalType.USER, |
| principalId: testOwner._id, |
| }); |
| expect(globalOwnerEntry).toBeTruthy(); |
| expect(globalOwnerEntry.permBits).toBe(ownerRole.permBits); |
|
|
| const globalPublicEntry = await AclEntry.findOne({ |
| resourceType: ResourceType.PROMPTGROUP, |
| resourceId: globalPromptGroup._id, |
| principalType: PrincipalType.PUBLIC, |
| }); |
| expect(globalPublicEntry).toBeTruthy(); |
| expect(globalPublicEntry.permBits).toBe(viewerRole.permBits); |
|
|
| |
| const privateOwnerEntry = await AclEntry.findOne({ |
| resourceType: ResourceType.PROMPTGROUP, |
| resourceId: privatePromptGroup._id, |
| principalType: PrincipalType.USER, |
| principalId: testOwner._id, |
| }); |
| expect(privateOwnerEntry).toBeTruthy(); |
| expect(privateOwnerEntry.permBits).toBe(ownerRole.permBits); |
|
|
| const privatePublicEntry = await AclEntry.findOne({ |
| resourceType: ResourceType.PROMPTGROUP, |
| resourceId: privatePromptGroup._id, |
| principalType: PrincipalType.PUBLIC, |
| }); |
| expect(privatePublicEntry).toBeNull(); |
| }); |
|
|
| it('should skip promptGroups that already have ACL entries', async () => { |
| |
| const promptGroup1 = await PromptGroup.create({ |
| name: 'Group 1', |
| author: testOwner._id, |
| authorName: testOwner.name, |
| productionId: new ObjectId(), |
| }); |
|
|
| const promptGroup2 = await PromptGroup.create({ |
| name: 'Group 2', |
| author: testOwner._id, |
| authorName: testOwner.name, |
| productionId: new ObjectId(), |
| }); |
|
|
| |
| await AclEntry.create({ |
| principalType: PrincipalType.USER, |
| principalId: testOwner._id, |
| principalModel: PrincipalModel.USER, |
| resourceType: ResourceType.PROMPTGROUP, |
| resourceId: promptGroup1._id, |
| permBits: ownerRole.permBits, |
| roleId: ownerRole._id, |
| grantedBy: testOwner._id, |
| grantedAt: new Date(), |
| }); |
|
|
| const result = await migrateToPromptGroupPermissions({ dryRun: false }); |
|
|
| |
| expect(result.migrated).toBe(1); |
| expect(result.errors).toBe(0); |
|
|
| |
| const group2Entry = await AclEntry.findOne({ |
| resourceType: ResourceType.PROMPTGROUP, |
| resourceId: promptGroup2._id, |
| }); |
| expect(group2Entry).toBeTruthy(); |
| }); |
|
|
| it('should handle promptGroups with prompts correctly', async () => { |
| |
| const promptGroup = await PromptGroup.create({ |
| name: 'Group with Prompts', |
| author: testOwner._id, |
| authorName: testOwner.name, |
| productionId: new ObjectId(), |
| }); |
|
|
| |
| await Prompt.create({ |
| prompt: 'First prompt', |
| author: testOwner._id, |
| groupId: promptGroup._id, |
| type: 'text', |
| }); |
|
|
| await Prompt.create({ |
| prompt: 'Second prompt', |
| author: testOwner._id, |
| groupId: promptGroup._id, |
| type: 'text', |
| }); |
|
|
| const result = await migrateToPromptGroupPermissions({ dryRun: false }); |
|
|
| expect(result.migrated).toBe(1); |
| expect(result.errors).toBe(0); |
|
|
| |
| const groupEntry = await AclEntry.findOne({ |
| resourceType: ResourceType.PROMPTGROUP, |
| resourceId: promptGroup._id, |
| }); |
| expect(groupEntry).toBeTruthy(); |
|
|
| |
| const promptEntries = await AclEntry.find({ |
| resourceType: 'prompt', |
| }); |
| expect(promptEntries).toHaveLength(0); |
| }); |
| }); |
|
|