mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-03-01 13:50:19 +01:00
📦 refactor: Consolidate DB models, encapsulating Mongoose usage in data-schemas (#11830)
* chore: move database model methods to /packages/data-schemas * chore: add TypeScript ESLint rule to warn on unused variables * refactor: model imports to streamline access - Consolidated model imports across various files to improve code organization and reduce redundancy. - Updated imports for models such as Assistant, Message, Conversation, and others to a unified import path. - Adjusted middleware and service files to reflect the new import structure, ensuring functionality remains intact. - Enhanced test files to align with the new import paths, maintaining test coverage and integrity. * chore: migrate database models to packages/data-schemas and refactor all direct Mongoose Model usage outside of data-schemas * test: update agent model mocks in unit tests - Added `getAgent` mock to `client.test.js` to enhance test coverage for agent-related functionality. - Removed redundant `getAgent` and `getAgents` mocks from `openai.spec.js` and `responses.unit.spec.js` to streamline test setup and reduce duplication. - Ensured consistency in agent mock implementations across test files. * fix: update types in data-schemas * refactor: enhance type definitions in transaction and spending methods - Updated type definitions in `checkBalance.ts` to use specific request and response types. - Refined `spendTokens.ts` to utilize a new `SpendTxData` interface for better clarity and type safety. - Improved transaction handling in `transaction.ts` by introducing `TransactionResult` and `TxData` interfaces, ensuring consistent data structures across methods. - Adjusted unit tests in `transaction.spec.ts` to accommodate new type definitions and enhance robustness. * refactor: streamline model imports and enhance code organization - Consolidated model imports across various controllers and services to a unified import path, improving code clarity and reducing redundancy. - Updated multiple files to reflect the new import structure, ensuring all functionalities remain intact. - Enhanced overall code organization by removing duplicate import statements and optimizing the usage of model methods. * feat: implement loadAddedAgent and refactor agent loading logic - Introduced `loadAddedAgent` function to handle loading agents from added conversations, supporting multi-convo parallel execution. - Created a new `load.ts` file to encapsulate agent loading functionalities, including `loadEphemeralAgent` and `loadAgent`. - Updated the `index.ts` file to export the new `load` module instead of the deprecated `loadAgent`. - Enhanced type definitions and improved error handling in the agent loading process. - Adjusted unit tests to reflect changes in the agent loading structure and ensure comprehensive coverage. * refactor: enhance balance handling with new update interface - Introduced `IBalanceUpdate` interface to streamline balance update operations across the codebase. - Updated `upsertBalanceFields` method signatures in `balance.ts`, `transaction.ts`, and related tests to utilize the new interface for improved type safety. - Adjusted type imports in `balance.spec.ts` to include `IBalanceUpdate`, ensuring consistency in balance management functionalities. - Enhanced overall code clarity and maintainability by refining type definitions related to balance operations. * feat: add unit tests for loadAgent functionality and enhance agent loading logic - Introduced comprehensive unit tests for the `loadAgent` function, covering various scenarios including null and empty agent IDs, loading of ephemeral agents, and permission checks. - Enhanced the `initializeClient` function by moving `getConvoFiles` to the correct position in the database method exports, ensuring proper functionality. - Improved test coverage for agent loading, including handling of non-existent agents and user permissions. * chore: reorder memory method exports for consistency - Moved `deleteAllUserMemories` to the correct position in the exported memory methods, ensuring a consistent and logical order of method exports in `memory.ts`.
This commit is contained in:
parent
10e5c5f677
commit
7a62c42675
182 changed files with 8596 additions and 8153 deletions
627
packages/data-schemas/src/methods/prompt.spec.ts
Normal file
627
packages/data-schemas/src/methods/prompt.spec.ts
Normal file
|
|
@ -0,0 +1,627 @@
|
|||
import mongoose from 'mongoose';
|
||||
import { ObjectId } from 'mongodb';
|
||||
import { MongoMemoryServer } from 'mongodb-memory-server';
|
||||
import {
|
||||
SystemRoles,
|
||||
ResourceType,
|
||||
AccessRoleIds,
|
||||
PrincipalType,
|
||||
PermissionBits,
|
||||
} from 'librechat-data-provider';
|
||||
import type { IPromptGroup, AccessRole as TAccessRole, AclEntry as TAclEntry } from '..';
|
||||
import { createAclEntryMethods } from './aclEntry';
|
||||
import { logger, createModels } from '..';
|
||||
import { createMethods } from './index';
|
||||
|
||||
// Disable console for tests
|
||||
logger.silent = true;
|
||||
|
||||
/** Lean user object from .toObject() */
|
||||
type LeanUser = {
|
||||
_id: mongoose.Types.ObjectId | string;
|
||||
name?: string;
|
||||
email: string;
|
||||
role?: string;
|
||||
};
|
||||
|
||||
/** Lean group object from .toObject() */
|
||||
type LeanGroup = {
|
||||
_id: mongoose.Types.ObjectId | string;
|
||||
name: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
/** Lean access role object from .toObject() / .lean() */
|
||||
type LeanAccessRole = TAccessRole & { _id: mongoose.Types.ObjectId | string };
|
||||
|
||||
/** Lean ACL entry from .lean() */
|
||||
type LeanAclEntry = TAclEntry & { _id: mongoose.Types.ObjectId | string };
|
||||
|
||||
/** Lean prompt group from .toObject() */
|
||||
type LeanPromptGroup = IPromptGroup & { _id: mongoose.Types.ObjectId | string };
|
||||
|
||||
let Prompt: mongoose.Model<unknown>;
|
||||
let PromptGroup: mongoose.Model<unknown>;
|
||||
let AclEntry: mongoose.Model<unknown>;
|
||||
let AccessRole: mongoose.Model<unknown>;
|
||||
let User: mongoose.Model<unknown>;
|
||||
let Group: mongoose.Model<unknown>;
|
||||
let methods: ReturnType<typeof createMethods>;
|
||||
let aclMethods: ReturnType<typeof createAclEntryMethods>;
|
||||
let testUsers: Record<string, LeanUser>;
|
||||
let testGroups: Record<string, LeanGroup>;
|
||||
let testRoles: Record<string, LeanAccessRole>;
|
||||
|
||||
let mongoServer: MongoMemoryServer;
|
||||
|
||||
beforeAll(async () => {
|
||||
mongoServer = await MongoMemoryServer.create();
|
||||
const mongoUri = mongoServer.getUri();
|
||||
await mongoose.connect(mongoUri);
|
||||
|
||||
createModels(mongoose);
|
||||
Prompt = mongoose.models.Prompt;
|
||||
PromptGroup = mongoose.models.PromptGroup;
|
||||
AclEntry = mongoose.models.AclEntry;
|
||||
AccessRole = mongoose.models.AccessRole;
|
||||
User = mongoose.models.User;
|
||||
Group = mongoose.models.Group;
|
||||
|
||||
methods = createMethods(mongoose, {
|
||||
removeAllPermissions: async ({ resourceType, resourceId }) => {
|
||||
await AclEntry.deleteMany({ resourceType, resourceId });
|
||||
},
|
||||
});
|
||||
aclMethods = createAclEntryMethods(mongoose);
|
||||
|
||||
await setupTestData();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await mongoose.disconnect();
|
||||
await mongoServer.stop();
|
||||
});
|
||||
|
||||
async function setupTestData() {
|
||||
testRoles = {
|
||||
viewer: (
|
||||
await AccessRole.create({
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||
name: 'Viewer',
|
||||
description: 'Can view promptGroups',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
permBits: PermissionBits.VIEW,
|
||||
})
|
||||
).toObject() as unknown as LeanAccessRole,
|
||||
editor: (
|
||||
await AccessRole.create({
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR,
|
||||
name: 'Editor',
|
||||
description: 'Can view and edit promptGroups',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
permBits: PermissionBits.VIEW | PermissionBits.EDIT,
|
||||
})
|
||||
).toObject() as unknown as LeanAccessRole,
|
||||
owner: (
|
||||
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,
|
||||
})
|
||||
).toObject() as unknown as LeanAccessRole,
|
||||
};
|
||||
|
||||
testUsers = {
|
||||
owner: (
|
||||
await User.create({
|
||||
name: 'Prompt Owner',
|
||||
email: 'owner@example.com',
|
||||
role: SystemRoles.USER,
|
||||
})
|
||||
).toObject() as unknown as LeanUser,
|
||||
editor: (
|
||||
await User.create({
|
||||
name: 'Prompt Editor',
|
||||
email: 'editor@example.com',
|
||||
role: SystemRoles.USER,
|
||||
})
|
||||
).toObject() as unknown as LeanUser,
|
||||
viewer: (
|
||||
await User.create({
|
||||
name: 'Prompt Viewer',
|
||||
email: 'viewer@example.com',
|
||||
role: SystemRoles.USER,
|
||||
})
|
||||
).toObject() as unknown as LeanUser,
|
||||
admin: (
|
||||
await User.create({
|
||||
name: 'Admin User',
|
||||
email: 'admin@example.com',
|
||||
role: SystemRoles.ADMIN,
|
||||
})
|
||||
).toObject() as unknown as LeanUser,
|
||||
noAccess: (
|
||||
await User.create({
|
||||
name: 'No Access User',
|
||||
email: 'noaccess@example.com',
|
||||
role: SystemRoles.USER,
|
||||
})
|
||||
).toObject() as unknown as LeanUser,
|
||||
};
|
||||
|
||||
testGroups = {
|
||||
editors: (
|
||||
await Group.create({
|
||||
name: 'Prompt Editors',
|
||||
description: 'Group with editor access',
|
||||
})
|
||||
).toObject() as unknown as LeanGroup,
|
||||
viewers: (
|
||||
await Group.create({
|
||||
name: 'Prompt Viewers',
|
||||
description: 'Group with viewer access',
|
||||
})
|
||||
).toObject() as unknown as LeanGroup,
|
||||
};
|
||||
}
|
||||
|
||||
/** Helper: grant permission via direct AclEntry.create */
|
||||
async function grantPermission(params: {
|
||||
principalType: string;
|
||||
principalId: mongoose.Types.ObjectId | string;
|
||||
resourceType: string;
|
||||
resourceId: mongoose.Types.ObjectId | string;
|
||||
accessRoleId: string;
|
||||
grantedBy: mongoose.Types.ObjectId | string;
|
||||
}) {
|
||||
const role = (await AccessRole.findOne({
|
||||
accessRoleId: params.accessRoleId,
|
||||
}).lean()) as LeanAccessRole | null;
|
||||
if (!role) {
|
||||
throw new Error(`AccessRole ${params.accessRoleId} not found`);
|
||||
}
|
||||
return aclMethods.grantPermission(
|
||||
params.principalType,
|
||||
params.principalId,
|
||||
params.resourceType,
|
||||
params.resourceId,
|
||||
role.permBits,
|
||||
params.grantedBy,
|
||||
undefined,
|
||||
role._id,
|
||||
);
|
||||
}
|
||||
|
||||
/** Helper: check permission via getUserPrincipals + hasPermission */
|
||||
async function checkPermission(params: {
|
||||
userId: mongoose.Types.ObjectId | string;
|
||||
resourceType: string;
|
||||
resourceId: mongoose.Types.ObjectId | string;
|
||||
requiredPermission: number;
|
||||
includePublic?: boolean;
|
||||
}) {
|
||||
// getUserPrincipals already includes user, role, groups, and public
|
||||
const principals = await methods.getUserPrincipals({
|
||||
userId: params.userId,
|
||||
});
|
||||
|
||||
// If not including public, filter it out
|
||||
const filteredPrincipals = params.includePublic
|
||||
? principals
|
||||
: principals.filter((p) => p.principalType !== PrincipalType.PUBLIC);
|
||||
|
||||
return aclMethods.hasPermission(
|
||||
filteredPrincipals,
|
||||
params.resourceType,
|
||||
params.resourceId,
|
||||
params.requiredPermission,
|
||||
);
|
||||
}
|
||||
|
||||
describe('Prompt ACL Permissions', () => {
|
||||
describe('Creating Prompts with Permissions', () => {
|
||||
it('should grant owner permissions when creating a prompt', async () => {
|
||||
const testGroup = (
|
||||
await PromptGroup.create({
|
||||
name: 'Test Group',
|
||||
category: 'testing',
|
||||
author: testUsers.owner._id,
|
||||
authorName: testUsers.owner.name,
|
||||
productionId: new mongoose.Types.ObjectId(),
|
||||
})
|
||||
).toObject() as unknown as LeanPromptGroup;
|
||||
|
||||
const promptData = {
|
||||
prompt: {
|
||||
prompt: 'Test prompt content',
|
||||
name: 'Test Prompt',
|
||||
type: 'text',
|
||||
groupId: testGroup._id,
|
||||
},
|
||||
author: testUsers.owner._id,
|
||||
};
|
||||
|
||||
await methods.savePrompt(promptData);
|
||||
|
||||
// Grant owner permission
|
||||
await grantPermission({
|
||||
principalType: PrincipalType.USER,
|
||||
principalId: testUsers.owner._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testGroup._id,
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
|
||||
// Check ACL entry
|
||||
const aclEntry = (await AclEntry.findOne({
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testGroup._id,
|
||||
principalType: PrincipalType.USER,
|
||||
principalId: testUsers.owner._id,
|
||||
}).lean()) as LeanAclEntry | null;
|
||||
|
||||
expect(aclEntry).toBeTruthy();
|
||||
expect(aclEntry!.permBits).toBe(testRoles.owner.permBits);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accessing Prompts', () => {
|
||||
let testPromptGroup: LeanPromptGroup;
|
||||
|
||||
beforeEach(async () => {
|
||||
testPromptGroup = (
|
||||
await PromptGroup.create({
|
||||
name: 'Test Group',
|
||||
author: testUsers.owner._id,
|
||||
authorName: testUsers.owner.name,
|
||||
productionId: new ObjectId(),
|
||||
})
|
||||
).toObject() as unknown as LeanPromptGroup;
|
||||
|
||||
await Prompt.create({
|
||||
prompt: 'Test prompt for access control',
|
||||
name: 'Access Test Prompt',
|
||||
author: testUsers.owner._id,
|
||||
groupId: testPromptGroup._id,
|
||||
type: 'text',
|
||||
});
|
||||
|
||||
await grantPermission({
|
||||
principalType: PrincipalType.USER,
|
||||
principalId: testUsers.owner._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await Prompt.deleteMany({});
|
||||
await PromptGroup.deleteMany({});
|
||||
await AclEntry.deleteMany({});
|
||||
});
|
||||
|
||||
it('owner should have full access to their prompt', async () => {
|
||||
const hasAccess = await checkPermission({
|
||||
userId: testUsers.owner._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
requiredPermission: PermissionBits.VIEW,
|
||||
});
|
||||
|
||||
expect(hasAccess).toBe(true);
|
||||
|
||||
const canEdit = await checkPermission({
|
||||
userId: testUsers.owner._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
requiredPermission: PermissionBits.EDIT,
|
||||
});
|
||||
|
||||
expect(canEdit).toBe(true);
|
||||
});
|
||||
|
||||
it('user with viewer role should only have view access', async () => {
|
||||
await grantPermission({
|
||||
principalType: PrincipalType.USER,
|
||||
principalId: testUsers.viewer._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
|
||||
const canView = await checkPermission({
|
||||
userId: testUsers.viewer._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
requiredPermission: PermissionBits.VIEW,
|
||||
});
|
||||
|
||||
const canEdit = await checkPermission({
|
||||
userId: testUsers.viewer._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
requiredPermission: PermissionBits.EDIT,
|
||||
});
|
||||
|
||||
expect(canView).toBe(true);
|
||||
expect(canEdit).toBe(false);
|
||||
});
|
||||
|
||||
it('user without permissions should have no access', async () => {
|
||||
const hasAccess = await checkPermission({
|
||||
userId: testUsers.noAccess._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
requiredPermission: PermissionBits.VIEW,
|
||||
});
|
||||
|
||||
expect(hasAccess).toBe(false);
|
||||
});
|
||||
|
||||
it('admin should have access regardless of permissions', async () => {
|
||||
// Admin users should work through normal permission system
|
||||
// The middleware layer handles admin bypass, not the permission service
|
||||
const hasAccess = await checkPermission({
|
||||
userId: testUsers.admin._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
requiredPermission: PermissionBits.VIEW,
|
||||
});
|
||||
|
||||
// Without explicit permissions, even admin won't have access at this layer
|
||||
expect(hasAccess).toBe(false);
|
||||
|
||||
// The actual admin bypass happens in the middleware layer
|
||||
});
|
||||
});
|
||||
|
||||
describe('Group-based Access', () => {
|
||||
afterEach(async () => {
|
||||
await Prompt.deleteMany({});
|
||||
await AclEntry.deleteMany({});
|
||||
await User.updateMany({}, { $set: { groups: [] } });
|
||||
});
|
||||
|
||||
it('group members should inherit group permissions', async () => {
|
||||
const testPromptGroup = (
|
||||
await PromptGroup.create({
|
||||
name: 'Group Test Group',
|
||||
author: testUsers.owner._id,
|
||||
authorName: testUsers.owner.name,
|
||||
productionId: new ObjectId(),
|
||||
})
|
||||
).toObject() as unknown as LeanPromptGroup;
|
||||
|
||||
// Add user to group
|
||||
await methods.addUserToGroup(testUsers.editor._id, testGroups.editors._id);
|
||||
|
||||
await methods.savePrompt({
|
||||
author: testUsers.owner._id,
|
||||
prompt: {
|
||||
prompt: 'Group test prompt',
|
||||
name: 'Group Test',
|
||||
groupId: testPromptGroup._id,
|
||||
type: 'text',
|
||||
},
|
||||
});
|
||||
|
||||
// Grant edit permissions to the group
|
||||
await grantPermission({
|
||||
principalType: PrincipalType.GROUP,
|
||||
principalId: testGroups.editors._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
|
||||
// Check if group member has access
|
||||
const hasAccess = await checkPermission({
|
||||
userId: testUsers.editor._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
requiredPermission: PermissionBits.EDIT,
|
||||
});
|
||||
|
||||
expect(hasAccess).toBe(true);
|
||||
|
||||
// Check that non-member doesn't have access
|
||||
const nonMemberAccess = await checkPermission({
|
||||
userId: testUsers.viewer._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
requiredPermission: PermissionBits.EDIT,
|
||||
});
|
||||
|
||||
expect(nonMemberAccess).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Public Access', () => {
|
||||
let publicPromptGroup: LeanPromptGroup;
|
||||
let privatePromptGroup: LeanPromptGroup;
|
||||
|
||||
beforeEach(async () => {
|
||||
publicPromptGroup = (
|
||||
await PromptGroup.create({
|
||||
name: 'Public Access Test Group',
|
||||
author: testUsers.owner._id,
|
||||
authorName: testUsers.owner.name,
|
||||
productionId: new ObjectId(),
|
||||
})
|
||||
).toObject() as unknown as LeanPromptGroup;
|
||||
|
||||
privatePromptGroup = (
|
||||
await PromptGroup.create({
|
||||
name: 'Private Access Test Group',
|
||||
author: testUsers.owner._id,
|
||||
authorName: testUsers.owner.name,
|
||||
productionId: new ObjectId(),
|
||||
})
|
||||
).toObject() as unknown as LeanPromptGroup;
|
||||
|
||||
await Prompt.create({
|
||||
prompt: 'Public prompt',
|
||||
name: 'Public',
|
||||
author: testUsers.owner._id,
|
||||
groupId: publicPromptGroup._id,
|
||||
type: 'text',
|
||||
});
|
||||
|
||||
await Prompt.create({
|
||||
prompt: 'Private prompt',
|
||||
name: 'Private',
|
||||
author: testUsers.owner._id,
|
||||
groupId: privatePromptGroup._id,
|
||||
type: 'text',
|
||||
});
|
||||
|
||||
// Grant public view access
|
||||
await aclMethods.grantPermission(
|
||||
PrincipalType.PUBLIC,
|
||||
null,
|
||||
ResourceType.PROMPTGROUP,
|
||||
publicPromptGroup._id,
|
||||
PermissionBits.VIEW,
|
||||
testUsers.owner._id,
|
||||
);
|
||||
|
||||
// Grant only owner access to private
|
||||
await grantPermission({
|
||||
principalType: PrincipalType.USER,
|
||||
principalId: testUsers.owner._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: privatePromptGroup._id,
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await Prompt.deleteMany({});
|
||||
await PromptGroup.deleteMany({});
|
||||
await AclEntry.deleteMany({});
|
||||
});
|
||||
|
||||
it('public prompt should be accessible to any user', async () => {
|
||||
const hasAccess = await checkPermission({
|
||||
userId: testUsers.noAccess._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: publicPromptGroup._id,
|
||||
requiredPermission: PermissionBits.VIEW,
|
||||
includePublic: true,
|
||||
});
|
||||
|
||||
expect(hasAccess).toBe(true);
|
||||
});
|
||||
|
||||
it('private prompt should not be accessible to unauthorized users', async () => {
|
||||
const hasAccess = await checkPermission({
|
||||
userId: testUsers.noAccess._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: privatePromptGroup._id,
|
||||
requiredPermission: PermissionBits.VIEW,
|
||||
includePublic: true,
|
||||
});
|
||||
|
||||
expect(hasAccess).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Prompt Deletion', () => {
|
||||
it('should remove ACL entries when prompt is deleted', async () => {
|
||||
const testPromptGroup = (
|
||||
await PromptGroup.create({
|
||||
name: 'Deletion Test Group',
|
||||
author: testUsers.owner._id,
|
||||
authorName: testUsers.owner.name,
|
||||
productionId: new ObjectId(),
|
||||
})
|
||||
).toObject() as unknown as LeanPromptGroup;
|
||||
|
||||
const result = await methods.savePrompt({
|
||||
author: testUsers.owner._id,
|
||||
prompt: {
|
||||
prompt: 'To be deleted',
|
||||
name: 'Delete Test',
|
||||
groupId: testPromptGroup._id,
|
||||
type: 'text',
|
||||
},
|
||||
});
|
||||
|
||||
const savedPrompt = result as { prompt?: { _id: mongoose.Types.ObjectId } } | null;
|
||||
if (!savedPrompt?.prompt) {
|
||||
throw new Error('Failed to save prompt');
|
||||
}
|
||||
const testPromptId = savedPrompt.prompt._id;
|
||||
|
||||
await grantPermission({
|
||||
principalType: PrincipalType.USER,
|
||||
principalId: testUsers.owner._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
|
||||
// Verify ACL entry exists
|
||||
const beforeDelete = await AclEntry.find({
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
});
|
||||
expect(beforeDelete).toHaveLength(1);
|
||||
|
||||
// Delete the prompt
|
||||
await methods.deletePrompt({
|
||||
promptId: testPromptId,
|
||||
groupId: testPromptGroup._id,
|
||||
author: testUsers.owner._id,
|
||||
role: SystemRoles.USER,
|
||||
});
|
||||
|
||||
// Verify ACL entries are removed
|
||||
const aclEntries = await AclEntry.find({
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
});
|
||||
|
||||
expect(aclEntries).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Backwards Compatibility', () => {
|
||||
it('should handle prompts without ACL entries gracefully', async () => {
|
||||
const promptGroup = (
|
||||
await PromptGroup.create({
|
||||
name: 'Legacy Test Group',
|
||||
author: testUsers.owner._id,
|
||||
authorName: testUsers.owner.name,
|
||||
productionId: new ObjectId(),
|
||||
})
|
||||
).toObject() as unknown as LeanPromptGroup;
|
||||
|
||||
const legacyPrompt = (
|
||||
await Prompt.create({
|
||||
prompt: 'Legacy prompt without ACL',
|
||||
name: 'Legacy',
|
||||
author: testUsers.owner._id,
|
||||
groupId: promptGroup._id,
|
||||
type: 'text',
|
||||
})
|
||||
).toObject() as { _id: mongoose.Types.ObjectId };
|
||||
|
||||
const prompt = (await methods.getPrompt({ _id: legacyPrompt._id })) as {
|
||||
_id: mongoose.Types.ObjectId;
|
||||
} | null;
|
||||
expect(prompt).toBeTruthy();
|
||||
expect(String(prompt!._id)).toBe(String(legacyPrompt._id));
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue