mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 06:00:56 +02:00

- Replaced string literals for principal types ('user', 'group', 'public') with the new PrincipalType enum across various models, services, and tests for improved type safety and consistency. - Updated permission handling in multiple files to utilize the PrincipalType enum, enhancing maintainability and reducing potential errors. - Ensured all relevant tests reflect these changes to maintain coverage and functionality.
279 lines
8.6 KiB
JavaScript
279 lines
8.6 KiB
JavaScript
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,
|
|
PermissionBits,
|
|
} = require('librechat-data-provider');
|
|
|
|
// Mock the config/connect module to prevent connection attempts during tests
|
|
jest.mock('../../config/connect', () => jest.fn().mockResolvedValue(true));
|
|
|
|
// Disable console for tests
|
|
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 () => {
|
|
// Set up MongoDB memory server
|
|
mongoServer = await MongoMemoryServer.create();
|
|
const mongoUri = mongoServer.getUri();
|
|
await mongoose.connect(mongoUri);
|
|
|
|
// Initialize models
|
|
const dbModels = require('~/db/models');
|
|
Prompt = dbModels.Prompt;
|
|
PromptGroup = dbModels.PromptGroup;
|
|
AclEntry = dbModels.AclEntry;
|
|
AccessRole = dbModels.AccessRole;
|
|
User = dbModels.User;
|
|
Project = dbModels.Project;
|
|
|
|
// Create test user
|
|
testOwner = await User.create({
|
|
name: 'Test Owner',
|
|
email: 'owner@test.com',
|
|
role: 'USER',
|
|
});
|
|
|
|
// Create test project with the proper name
|
|
const projectName = Constants.GLOBAL_PROJECT_NAME || 'instance';
|
|
testProject = await Project.create({
|
|
name: projectName,
|
|
description: 'Global project',
|
|
promptGroupIds: [],
|
|
});
|
|
|
|
// Create promptGroup access roles
|
|
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,
|
|
});
|
|
|
|
// Import migration function
|
|
const migration = require('../../config/migrate-prompt-permissions');
|
|
migrateToPromptGroupPermissions = migration.migrateToPromptGroupPermissions;
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await mongoose.disconnect();
|
|
await mongoServer.stop();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
// Clean up before each test
|
|
await Prompt.deleteMany({});
|
|
await PromptGroup.deleteMany({});
|
|
await AclEntry.deleteMany({});
|
|
// Reset the project's promptGroupIds array
|
|
testProject.promptGroupIds = [];
|
|
await testProject.save();
|
|
});
|
|
|
|
it('should categorize promptGroups correctly in dry run', async () => {
|
|
// Create global prompt group (in Global project)
|
|
const globalPromptGroup = await PromptGroup.create({
|
|
name: 'Global Group',
|
|
author: testOwner._id,
|
|
authorName: testOwner.name,
|
|
productionId: new ObjectId(),
|
|
});
|
|
|
|
// Create private prompt group (not in any project)
|
|
await PromptGroup.create({
|
|
name: 'Private Group',
|
|
author: testOwner._id,
|
|
authorName: testOwner.name,
|
|
productionId: new ObjectId(),
|
|
});
|
|
|
|
// Add global group to project's promptGroupIds array
|
|
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 () => {
|
|
// Create prompt groups
|
|
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(),
|
|
});
|
|
|
|
// Add global group to project's promptGroupIds array
|
|
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);
|
|
|
|
// Check global promptGroup permissions
|
|
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);
|
|
|
|
// Check private promptGroup permissions
|
|
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 () => {
|
|
// Create prompt groups
|
|
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(),
|
|
});
|
|
|
|
// Grant permission to one promptGroup manually (simulating it already has ACL)
|
|
await AclEntry.create({
|
|
principalType: PrincipalType.USER,
|
|
principalId: testOwner._id,
|
|
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 });
|
|
|
|
// Should only migrate promptGroup2, skip promptGroup1
|
|
expect(result.migrated).toBe(1);
|
|
expect(result.errors).toBe(0);
|
|
|
|
// Verify promptGroup2 now has permissions
|
|
const group2Entry = await AclEntry.findOne({
|
|
resourceType: ResourceType.PROMPTGROUP,
|
|
resourceId: promptGroup2._id,
|
|
});
|
|
expect(group2Entry).toBeTruthy();
|
|
});
|
|
|
|
it('should handle promptGroups with prompts correctly', async () => {
|
|
// Create a promptGroup with some prompts
|
|
const promptGroup = await PromptGroup.create({
|
|
name: 'Group with Prompts',
|
|
author: testOwner._id,
|
|
authorName: testOwner.name,
|
|
productionId: new ObjectId(),
|
|
});
|
|
|
|
// Create some prompts in this group
|
|
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);
|
|
|
|
// Verify the promptGroup has permissions
|
|
const groupEntry = await AclEntry.findOne({
|
|
resourceType: ResourceType.PROMPTGROUP,
|
|
resourceId: promptGroup._id,
|
|
});
|
|
expect(groupEntry).toBeTruthy();
|
|
|
|
// Verify no prompt-level permissions were created
|
|
const promptEntries = await AclEntry.find({
|
|
resourceType: 'prompt',
|
|
});
|
|
expect(promptEntries).toHaveLength(0);
|
|
});
|
|
});
|