Download packages/data-schemas/src/methods/userGroup.spec.ts from helloya20/chat: direct link, hf CLI and curl.
- Browser
- Download file 20.3 kB
-
https://huggingface.co/spaces/helloya20/chat/resolve/main/packages/data-schemas/src/methods/userGroup.spec.ts
- Command line
-
hf download hf://spaces/helloya20/chat/packages/data-schemas/src/methods/userGroup.spec.ts
-
curl -L -o userGroup.spec.ts https://huggingface.co/spaces/helloya20/chat/resolve/main/packages/data-schemas/src/methods/userGroup.spec.ts
20.3 kB
| import mongoose from 'mongoose'; | |
| import { PrincipalType } from 'librechat-data-provider'; | |
| import { MongoMemoryServer } from 'mongodb-memory-server'; | |
| import type * as t from '~/types'; | |
| import { createUserGroupMethods } from './userGroup'; | |
| import groupSchema from '~/schema/group'; | |
| import userSchema from '~/schema/user'; | |
| /** Mocking logger */ | |
| jest.mock('~/config/winston', () => ({ | |
| error: jest.fn(), | |
| info: jest.fn(), | |
| debug: jest.fn(), | |
| })); | |
| let mongoServer: MongoMemoryServer; | |
| let Group: mongoose.Model<t.IGroup>; | |
| let User: mongoose.Model<t.IUser>; | |
| let methods: ReturnType<typeof createUserGroupMethods>; | |
| beforeAll(async () => { | |
| mongoServer = await MongoMemoryServer.create(); | |
| const mongoUri = mongoServer.getUri(); | |
| Group = mongoose.models.Group || mongoose.model<t.IGroup>('Group', groupSchema); | |
| User = mongoose.models.User || mongoose.model<t.IUser>('User', userSchema); | |
| methods = createUserGroupMethods(mongoose); | |
| await mongoose.connect(mongoUri); | |
| }); | |
| afterAll(async () => { | |
| await mongoose.disconnect(); | |
| await mongoServer.stop(); | |
| }); | |
| beforeEach(async () => { | |
| await mongoose.connection.dropDatabase(); | |
| }); | |
| describe('User Group Methods Tests', () => { | |
| describe('Group Query Methods', () => { | |
| let testGroup: t.IGroup; | |
| let testUser: t.IUser; | |
| beforeEach(async () => { | |
| /** Create a test user */ | |
| testUser = await User.create({ | |
| name: 'Test User', | |
| email: 'test@example.com', | |
| password: 'password123', | |
| provider: 'local', | |
| }); | |
| /** Create a test group */ | |
| testGroup = await Group.create({ | |
| name: 'Test Group', | |
| source: 'local', | |
| memberIds: [(testUser._id as mongoose.Types.ObjectId).toString()], | |
| }); | |
| /** No need to add group to user - using one-way relationship via Group.memberIds */ | |
| }); | |
| test('should find group by ID', async () => { | |
| const group = await methods.findGroupById(testGroup._id as mongoose.Types.ObjectId); | |
| expect(group).toBeDefined(); | |
| expect(group?._id.toString()).toBe(testGroup._id.toString()); | |
| expect(group?.name).toBe(testGroup.name); | |
| }); | |
| test('should find group by ID with specific projection', async () => { | |
| const group = await methods.findGroupById(testGroup._id as mongoose.Types.ObjectId, { | |
| name: 1, | |
| }); | |
| expect(group).toBeDefined(); | |
| expect(group?._id).toBeDefined(); | |
| expect(group?.name).toBe(testGroup.name); | |
| expect(group?.memberIds).toBeUndefined(); | |
| }); | |
| test('should find group by external ID', async () => { | |
| /** Create an external ID group first */ | |
| const entraGroup = await Group.create({ | |
| name: 'Entra Group', | |
| source: 'entra', | |
| idOnTheSource: 'entra-id-12345', | |
| }); | |
| const group = await methods.findGroupByExternalId('entra-id-12345', 'entra'); | |
| expect(group).toBeDefined(); | |
| expect(group?._id.toString()).toBe(entraGroup._id.toString()); | |
| expect(group?.idOnTheSource).toBe('entra-id-12345'); | |
| }); | |
| test('should return null for non-existent external ID', async () => { | |
| const group = await methods.findGroupByExternalId('non-existent-id', 'entra'); | |
| expect(group).toBeNull(); | |
| }); | |
| test('should find groups by name pattern', async () => { | |
| /** Create additional groups */ | |
| await Group.create({ name: 'Test Group 2', source: 'local' }); | |
| await Group.create({ name: 'Admin Group', source: 'local' }); | |
| await Group.create({ | |
| name: 'Test Entra Group', | |
| source: 'entra', | |
| idOnTheSource: 'entra-id-xyz', | |
| }); | |
| /** Search for all "Test" groups */ | |
| const testGroups = await methods.findGroupsByNamePattern('Test'); | |
| expect(testGroups).toHaveLength(3); | |
| /** Search with source filter */ | |
| const localTestGroups = await methods.findGroupsByNamePattern('Test', 'local'); | |
| expect(localTestGroups).toHaveLength(2); | |
| const entraTestGroups = await methods.findGroupsByNamePattern('Test', 'entra'); | |
| expect(entraTestGroups).toHaveLength(1); | |
| }); | |
| test('should respect limit parameter in name search', async () => { | |
| /** Create many groups with similar names */ | |
| for (let i = 0; i < 10; i++) { | |
| await Group.create({ name: `Numbered Group ${i}`, source: 'local' }); | |
| } | |
| const limitedGroups = await methods.findGroupsByNamePattern('Numbered', null, 5); | |
| expect(limitedGroups).toHaveLength(5); | |
| }); | |
| test('should find groups by member ID', async () => { | |
| /** Create additional groups with the test user as member */ | |
| const group2 = await Group.create({ | |
| name: 'Second Group', | |
| source: 'local', | |
| memberIds: [(testUser._id as mongoose.Types.ObjectId).toString()], | |
| }); | |
| const group3 = await Group.create({ | |
| name: 'Third Group', | |
| source: 'local', | |
| memberIds: [new mongoose.Types.ObjectId().toString()] /** Different user */, | |
| }); | |
| const userGroups = await methods.findGroupsByMemberId( | |
| testUser._id as mongoose.Types.ObjectId, | |
| ); | |
| expect(userGroups).toHaveLength(2); | |
| /** IDs should match the groups where user is a member */ | |
| const groupIds = userGroups.map((g) => g._id.toString()); | |
| expect(groupIds).toContain(testGroup._id.toString()); | |
| expect(groupIds).toContain(group2._id.toString()); | |
| expect(groupIds).not.toContain(group3._id.toString()); | |
| }); | |
| }); | |
| describe('Group Creation and Update Methods', () => { | |
| test('should create a new group', async () => { | |
| const groupData = { | |
| name: 'New Test Group', | |
| source: 'local' as const, | |
| }; | |
| const group = await methods.createGroup(groupData); | |
| expect(group).toBeDefined(); | |
| expect(group.name).toBe(groupData.name); | |
| expect(group.source).toBe(groupData.source); | |
| /** Verify it was saved to the database */ | |
| const savedGroup = await Group.findById(group._id); | |
| expect(savedGroup).toBeDefined(); | |
| }); | |
| test('should upsert a group by external ID (create new)', async () => { | |
| const groupData = { | |
| name: 'New Entra Group', | |
| idOnTheSource: 'new-entra-id', | |
| }; | |
| const group = await methods.upsertGroupByExternalId(groupData.idOnTheSource, 'entra', { | |
| name: groupData.name, | |
| }); | |
| expect(group).toBeDefined(); | |
| expect(group?.name).toBe(groupData.name); | |
| expect(group?.idOnTheSource).toBe(groupData.idOnTheSource); | |
| expect(group?.source).toBe('entra'); | |
| /** Verify it was saved to the database */ | |
| const savedGroup = await Group.findOne({ idOnTheSource: 'new-entra-id' }); | |
| expect(savedGroup).toBeDefined(); | |
| }); | |
| test('should upsert a group by external ID (update existing)', async () => { | |
| /** Create an existing group */ | |
| await Group.create({ | |
| name: 'Original Name', | |
| source: 'entra', | |
| idOnTheSource: 'existing-entra-id', | |
| }); | |
| /** Update it */ | |
| const updatedGroup = await methods.upsertGroupByExternalId('existing-entra-id', 'entra', { | |
| name: 'Updated Name', | |
| }); | |
| expect(updatedGroup).toBeDefined(); | |
| expect(updatedGroup?.name).toBe('Updated Name'); | |
| expect(updatedGroup?.idOnTheSource).toBe('existing-entra-id'); | |
| /** Verify the update in the database */ | |
| const savedGroup = await Group.findOne({ idOnTheSource: 'existing-entra-id' }); | |
| expect(savedGroup?.name).toBe('Updated Name'); | |
| }); | |
| }); | |
| describe('User-Group Relationship Methods', () => { | |
| let testUser1: t.IUser; | |
| let testGroup: t.IGroup; | |
| beforeEach(async () => { | |
| /** Create test users */ | |
| testUser1 = await User.create({ | |
| name: 'User One', | |
| email: 'user1@example.com', | |
| password: 'password123', | |
| provider: 'local', | |
| }); | |
| /** Create a test group */ | |
| testGroup = await Group.create({ | |
| name: 'Test Group', | |
| source: 'local', | |
| memberIds: [] /** Initialize empty array */, | |
| }); | |
| }); | |
| test('should add user to group', async () => { | |
| const result = await methods.addUserToGroup( | |
| testUser1._id as mongoose.Types.ObjectId, | |
| testGroup._id as mongoose.Types.ObjectId, | |
| ); | |
| /** Verify the result */ | |
| expect(result).toBeDefined(); | |
| expect(result.user).toBeDefined(); | |
| expect(result.group).toBeDefined(); | |
| /** Group should have the user in memberIds (using idOnTheSource or user ID) */ | |
| const userIdOnTheSource = | |
| result.user.idOnTheSource || (testUser1._id as mongoose.Types.ObjectId).toString(); | |
| expect(result.group?.memberIds).toContain(userIdOnTheSource); | |
| /** Verify in database */ | |
| const updatedGroup = await Group.findById(testGroup._id); | |
| expect(updatedGroup?.memberIds).toContain(userIdOnTheSource); | |
| }); | |
| test('should remove user from group', async () => { | |
| /** First add the user to the group */ | |
| await methods.addUserToGroup( | |
| testUser1._id as mongoose.Types.ObjectId, | |
| testGroup._id as mongoose.Types.ObjectId, | |
| ); | |
| /** Then remove them */ | |
| const result = await methods.removeUserFromGroup( | |
| testUser1._id as mongoose.Types.ObjectId, | |
| testGroup._id as mongoose.Types.ObjectId, | |
| ); | |
| /** Verify the result */ | |
| expect(result).toBeDefined(); | |
| expect(result.user).toBeDefined(); | |
| expect(result.group).toBeDefined(); | |
| /** Group should not have the user in memberIds */ | |
| const userIdOnTheSource = | |
| result.user.idOnTheSource || (testUser1._id as mongoose.Types.ObjectId).toString(); | |
| expect(result.group?.memberIds).not.toContain(userIdOnTheSource); | |
| /** Verify in database */ | |
| const updatedGroup = await Group.findById(testGroup._id); | |
| expect(updatedGroup?.memberIds).not.toContain(userIdOnTheSource); | |
| }); | |
| test('should get all groups for a user', async () => { | |
| /** Add user to multiple groups */ | |
| const group1 = await Group.create({ name: 'Group 1', source: 'local', memberIds: [] }); | |
| const group2 = await Group.create({ name: 'Group 2', source: 'local', memberIds: [] }); | |
| await methods.addUserToGroup( | |
| testUser1._id as mongoose.Types.ObjectId, | |
| group1._id as mongoose.Types.ObjectId, | |
| ); | |
| await methods.addUserToGroup( | |
| testUser1._id as mongoose.Types.ObjectId, | |
| group2._id as mongoose.Types.ObjectId, | |
| ); | |
| /** Get the user's groups */ | |
| const userGroups = await methods.getUserGroups(testUser1._id as mongoose.Types.ObjectId); | |
| expect(userGroups).toHaveLength(2); | |
| const groupIds = userGroups.map((g) => g._id.toString()); | |
| expect(groupIds).toContain(group1._id.toString()); | |
| expect(groupIds).toContain(group2._id.toString()); | |
| }); | |
| test('should return empty array for getUserGroups when user has no groups', async () => { | |
| const userGroups = await methods.getUserGroups(testUser1._id as mongoose.Types.ObjectId); | |
| expect(userGroups).toEqual([]); | |
| }); | |
| test('should get user principals', async () => { | |
| /** Add user to a group */ | |
| await methods.addUserToGroup( | |
| testUser1._id as mongoose.Types.ObjectId, | |
| testGroup._id as mongoose.Types.ObjectId, | |
| ); | |
| /** Get user principals */ | |
| const principals = await methods.getUserPrincipals({ | |
| userId: testUser1._id as mongoose.Types.ObjectId, | |
| }); | |
| /** Should include user, role (default USER), group, and public principals */ | |
| expect(principals).toHaveLength(4); | |
| /** Check principal types */ | |
| const userPrincipal = principals.find((p) => p.principalType === PrincipalType.USER); | |
| const groupPrincipal = principals.find((p) => p.principalType === PrincipalType.GROUP); | |
| const publicPrincipal = principals.find((p) => p.principalType === PrincipalType.PUBLIC); | |
| expect(userPrincipal).toBeDefined(); | |
| expect(userPrincipal?.principalId?.toString()).toBe( | |
| (testUser1._id as mongoose.Types.ObjectId).toString(), | |
| ); | |
| expect(groupPrincipal).toBeDefined(); | |
| expect(groupPrincipal?.principalId?.toString()).toBe(testGroup._id.toString()); | |
| expect(publicPrincipal).toBeDefined(); | |
| expect(publicPrincipal?.principalId).toBeUndefined(); | |
| }); | |
| test('should return user and public principals for non-existent user in getUserPrincipals', async () => { | |
| const nonExistentId = new mongoose.Types.ObjectId(); | |
| const principals = await methods.getUserPrincipals({ | |
| userId: nonExistentId, | |
| }); | |
| /** Should still return user and public principals even for non-existent user */ | |
| expect(principals).toHaveLength(2); | |
| expect(principals[0].principalType).toBe(PrincipalType.USER); | |
| expect(principals[0].principalId?.toString()).toBe(nonExistentId.toString()); | |
| expect(principals[1].principalType).toBe(PrincipalType.PUBLIC); | |
| expect(principals[1].principalId).toBeUndefined(); | |
| }); | |
| test('should convert string userId to ObjectId in getUserPrincipals', async () => { | |
| /** Add user to a group */ | |
| await methods.addUserToGroup( | |
| testUser1._id as mongoose.Types.ObjectId, | |
| testGroup._id as mongoose.Types.ObjectId, | |
| ); | |
| /** Get user principals with string userId */ | |
| const principals = await methods.getUserPrincipals({ | |
| userId: (testUser1._id as mongoose.Types.ObjectId).toString(), | |
| }); | |
| /** Should include user, role (default USER), group, and public principals */ | |
| expect(principals).toHaveLength(4); | |
| /** Check that USER principal has ObjectId */ | |
| const userPrincipal = principals.find((p) => p.principalType === PrincipalType.USER); | |
| expect(userPrincipal).toBeDefined(); | |
| expect(userPrincipal?.principalId).toBeInstanceOf(mongoose.Types.ObjectId); | |
| expect(userPrincipal?.principalId?.toString()).toBe( | |
| (testUser1._id as mongoose.Types.ObjectId).toString(), | |
| ); | |
| /** Check that GROUP principal has ObjectId */ | |
| const groupPrincipal = principals.find((p) => p.principalType === PrincipalType.GROUP); | |
| expect(groupPrincipal).toBeDefined(); | |
| expect(groupPrincipal?.principalId).toBeInstanceOf(mongoose.Types.ObjectId); | |
| expect(groupPrincipal?.principalId?.toString()).toBe(testGroup._id.toString()); | |
| }); | |
| test('should include role principal as string in getUserPrincipals', async () => { | |
| /** Create user with specific role */ | |
| const userWithRole = await User.create({ | |
| name: 'Admin User', | |
| email: 'admin@example.com', | |
| password: 'password123', | |
| provider: 'local', | |
| role: 'ADMIN', | |
| }); | |
| /** Get user principals */ | |
| const principals = await methods.getUserPrincipals({ | |
| userId: userWithRole._id as mongoose.Types.ObjectId, | |
| }); | |
| /** Should include user, role, and public principals */ | |
| expect(principals).toHaveLength(3); | |
| /** Check that ROLE principal has string ID */ | |
| const rolePrincipal = principals.find((p) => p.principalType === PrincipalType.ROLE); | |
| expect(rolePrincipal).toBeDefined(); | |
| expect(typeof rolePrincipal?.principalId).toBe('string'); | |
| expect(rolePrincipal?.principalId).toBe('ADMIN'); | |
| }); | |
| }); | |
| describe('Entra ID Synchronization', () => { | |
| let testUser: t.IUser; | |
| beforeEach(async () => { | |
| testUser = await User.create({ | |
| name: 'Entra User', | |
| email: 'entra@example.com', | |
| password: 'password123', | |
| provider: 'entra', | |
| idOnTheSource: 'entra-user-123', | |
| }); | |
| }); | |
| /** Skip the failing tests until they can be fixed properly */ | |
| test.skip('should sync Entra groups for a user (add new groups)', async () => { | |
| /** Mock Entra groups */ | |
| const entraGroups = [ | |
| { id: 'entra-group-1', name: 'Entra Group 1' }, | |
| { id: 'entra-group-2', name: 'Entra Group 2' }, | |
| ]; | |
| const result = await methods.syncUserEntraGroups( | |
| testUser._id as mongoose.Types.ObjectId, | |
| entraGroups, | |
| ); | |
| /** Check result */ | |
| expect(result).toBeDefined(); | |
| expect(result.user).toBeDefined(); | |
| expect(result.addedGroups).toHaveLength(2); | |
| expect(result.removedGroups).toHaveLength(0); | |
| /** Verify groups were created */ | |
| const groups = await Group.find({ source: 'entra' }); | |
| expect(groups).toHaveLength(2); | |
| /** Verify user is a member of both groups - skipping this assertion for now */ | |
| const user = await User.findById(testUser._id); | |
| expect(user).toBeDefined(); | |
| /** Verify each group has the user as a member */ | |
| for (const group of groups) { | |
| expect(group.memberIds).toContain( | |
| testUser.idOnTheSource || (testUser._id as mongoose.Types.ObjectId).toString(), | |
| ); | |
| } | |
| }); | |
| test.skip('should sync Entra groups for a user (add and remove groups)', async () => { | |
| /** Create existing Entra groups for the user */ | |
| await Group.create({ | |
| name: 'Existing Group 1', | |
| source: 'entra', | |
| idOnTheSource: 'existing-1', | |
| memberIds: [testUser.idOnTheSource], | |
| }); | |
| const existingGroup2 = await Group.create({ | |
| name: 'Existing Group 2', | |
| source: 'entra', | |
| idOnTheSource: 'existing-2', | |
| memberIds: [testUser.idOnTheSource], | |
| }); | |
| /** Groups already have user in memberIds from creation above */ | |
| /** New Entra groups (one existing, one new) */ | |
| const entraGroups = [ | |
| { id: 'existing-1', name: 'Existing Group 1' } /** Keep this one */, | |
| { id: 'new-group', name: 'New Group' } /** Add this one */, | |
| /** existing-2 is missing, should be removed */ | |
| ]; | |
| const result = await methods.syncUserEntraGroups( | |
| testUser._id as mongoose.Types.ObjectId, | |
| entraGroups, | |
| ); | |
| /** Check result */ | |
| expect(result).toBeDefined(); | |
| expect(result.addedGroups).toHaveLength(1); /** Skipping exact array length expectations */ | |
| expect(result.removedGroups).toHaveLength(1); | |
| /** Verify existing-2 no longer has user as member */ | |
| const removedGroup = await Group.findById(existingGroup2._id); | |
| expect(removedGroup?.memberIds).toHaveLength(0); | |
| /** Verify new group was created and has user as member */ | |
| const newGroup = await Group.findOne({ idOnTheSource: 'new-group' }); | |
| expect(newGroup).toBeDefined(); | |
| expect(newGroup?.memberIds).toContain( | |
| testUser.idOnTheSource || (testUser._id as mongoose.Types.ObjectId).toString(), | |
| ); | |
| }); | |
| test('should throw error for non-existent user in syncUserEntraGroups', async () => { | |
| const nonExistentId = new mongoose.Types.ObjectId(); | |
| const entraGroups = [{ id: 'some-id', name: 'Some Group' }]; | |
| await expect(methods.syncUserEntraGroups(nonExistentId, entraGroups)).rejects.toThrow( | |
| 'User not found', | |
| ); | |
| }); | |
| test.skip('should preserve local groups when syncing Entra groups', async () => { | |
| /** Create a local group for the user */ | |
| const localGroup = await Group.create({ | |
| name: 'Local Group', | |
| source: 'local', | |
| memberIds: [testUser.idOnTheSource || (testUser._id as mongoose.Types.ObjectId).toString()], | |
| }); | |
| /** Group already has user in memberIds from creation above */ | |
| /** Sync with Entra groups */ | |
| const entraGroups = [{ id: 'entra-group', name: 'Entra Group' }]; | |
| const result = await methods.syncUserEntraGroups( | |
| testUser._id as mongoose.Types.ObjectId, | |
| entraGroups, | |
| ); | |
| /** Check result */ | |
| expect(result).toBeDefined(); | |
| /** Verify the local group entry still exists */ | |
| const savedLocalGroup = await Group.findById(localGroup._id); | |
| expect(savedLocalGroup).toBeDefined(); | |
| expect(savedLocalGroup?.memberIds).toContain( | |
| testUser.idOnTheSource || (testUser._id as mongoose.Types.ObjectId).toString(), | |
| ); | |
| /** Verify the Entra group was created */ | |
| const entraGroup = await Group.findOne({ idOnTheSource: 'entra-group' }); | |
| expect(entraGroup).toBeDefined(); | |
| expect(entraGroup?.memberIds).toContain( | |
| testUser.idOnTheSource || (testUser._id as mongoose.Types.ObjectId).toString(), | |
| ); | |
| }); | |
| }); | |
| }); | |