| | const mongoose = require('mongoose'); |
| | const { buildTree } = require('librechat-data-provider'); |
| | const { MongoMemoryServer } = require('mongodb-memory-server'); |
| | const { getMessages, bulkSaveMessages } = require('./Message'); |
| | const { Message } = require('~/db/models'); |
| |
|
| | let mongod; |
| | beforeAll(async () => { |
| | mongod = await MongoMemoryServer.create(); |
| | const uri = mongod.getUri(); |
| | await mongoose.connect(uri); |
| | }); |
| |
|
| | afterAll(async () => { |
| | await mongoose.disconnect(); |
| | await mongod.stop(); |
| | }); |
| |
|
| | beforeEach(async () => { |
| | await Message.deleteMany({}); |
| | }); |
| |
|
| | describe('Conversation Structure Tests', () => { |
| | test('Conversation folding/corrupting with inconsistent timestamps', async () => { |
| | const userId = 'testUser'; |
| | const conversationId = 'testConversation'; |
| |
|
| | |
| | const messages = [ |
| | { |
| | messageId: 'message0', |
| | parentMessageId: null, |
| | text: 'Message 0', |
| | createdAt: new Date('2023-01-01T00:00:00Z'), |
| | }, |
| | { |
| | messageId: 'message1', |
| | parentMessageId: 'message0', |
| | text: 'Message 1', |
| | createdAt: new Date('2023-01-01T00:02:00Z'), |
| | }, |
| | { |
| | messageId: 'message2', |
| | parentMessageId: 'message1', |
| | text: 'Message 2', |
| | createdAt: new Date('2023-01-01T00:01:00Z'), |
| | }, |
| | { |
| | messageId: 'message3', |
| | parentMessageId: 'message1', |
| | text: 'Message 3', |
| | createdAt: new Date('2023-01-01T00:03:00Z'), |
| | }, |
| | { |
| | messageId: 'message4', |
| | parentMessageId: 'message2', |
| | text: 'Message 4', |
| | createdAt: new Date('2023-01-01T00:04:00Z'), |
| | }, |
| | ]; |
| |
|
| | |
| | messages.forEach((msg) => { |
| | msg.conversationId = conversationId; |
| | msg.user = userId; |
| | msg.isCreatedByUser = false; |
| | msg.error = false; |
| | msg.unfinished = false; |
| | }); |
| |
|
| | |
| | await bulkSaveMessages(messages, true); |
| |
|
| | |
| | const retrievedMessages = await getMessages({ conversationId, user: userId }); |
| |
|
| | |
| | const tree = buildTree({ messages: retrievedMessages }); |
| |
|
| | |
| | expect(tree.length).toBeGreaterThan(1); |
| | }); |
| |
|
| | test('Fix: Conversation structure maintained with more than 16 messages', async () => { |
| | const userId = 'testUser'; |
| | const conversationId = 'testConversation'; |
| |
|
| | |
| | const messages = Array.from({ length: 20 }, (_, i) => ({ |
| | messageId: `message${i}`, |
| | parentMessageId: i === 0 ? null : `message${i - 1}`, |
| | conversationId, |
| | user: userId, |
| | text: `Message ${i}`, |
| | createdAt: new Date(Date.now() + (i % 2 === 0 ? i * 500000 : -i * 500000)), |
| | })); |
| |
|
| | |
| | await bulkSaveMessages(messages); |
| |
|
| | |
| | const retrievedMessages = await getMessages({ conversationId, user: userId }); |
| |
|
| | |
| | const tree = buildTree({ messages: retrievedMessages }); |
| |
|
| | |
| | expect(tree.length).toBe(1); |
| | let currentNode = tree[0]; |
| | for (let i = 1; i < 20; i++) { |
| | expect(currentNode.children.length).toBe(1); |
| | currentNode = currentNode.children[0]; |
| | expect(currentNode.text).toBe(`Message ${i}`); |
| | } |
| | expect(currentNode.children.length).toBe(0); |
| | }); |
| |
|
| | test('Simulate MongoDB ordering issue with more than 16 messages and close timestamps', async () => { |
| | const userId = 'testUser'; |
| | const conversationId = 'testConversation'; |
| |
|
| | |
| | const messages = Array.from({ length: 20 }, (_, i) => ({ |
| | messageId: `message${i}`, |
| | parentMessageId: i === 0 ? null : `message${i - 1}`, |
| | conversationId, |
| | user: userId, |
| | text: `Message ${i}`, |
| | createdAt: new Date(Date.now() + (i % 2 === 0 ? i * 1 : -i * 1)), |
| | })); |
| |
|
| | |
| | messages.forEach((msg) => { |
| | msg.isCreatedByUser = false; |
| | msg.error = false; |
| | msg.unfinished = false; |
| | }); |
| |
|
| | await bulkSaveMessages(messages, true); |
| | const retrievedMessages = await getMessages({ conversationId, user: userId }); |
| | const tree = buildTree({ messages: retrievedMessages }); |
| | expect(tree.length).toBeGreaterThan(1); |
| | }); |
| |
|
| | test('Fix: Preserve order with more than 16 messages by maintaining original timestamps', async () => { |
| | const userId = 'testUser'; |
| | const conversationId = 'testConversation'; |
| |
|
| | |
| | const messages = Array.from({ length: 20 }, (_, i) => ({ |
| | messageId: `message${i}`, |
| | parentMessageId: i === 0 ? null : `message${i - 1}`, |
| | conversationId, |
| | user: userId, |
| | text: `Message ${i}`, |
| | createdAt: new Date(Date.now() + i * 1000), |
| | })); |
| |
|
| | |
| | messages.forEach((msg) => { |
| | msg.isCreatedByUser = false; |
| | msg.error = false; |
| | msg.unfinished = false; |
| | }); |
| |
|
| | |
| | await bulkSaveMessages(messages, true); |
| |
|
| | |
| | const retrievedMessages = await getMessages({ conversationId, user: userId }); |
| |
|
| | |
| | const tree = buildTree({ messages: retrievedMessages }); |
| |
|
| | |
| | expect(tree.length).toBe(1); |
| | let currentNode = tree[0]; |
| | for (let i = 1; i < 20; i++) { |
| | expect(currentNode.children.length).toBe(1); |
| | currentNode = currentNode.children[0]; |
| | expect(currentNode.text).toBe(`Message ${i}`); |
| | } |
| | expect(currentNode.children.length).toBe(0); |
| | }); |
| |
|
| | test('Random order dates between parent and children messages', async () => { |
| | const userId = 'testUser'; |
| | const conversationId = 'testConversation'; |
| |
|
| | |
| | const messages = [ |
| | { |
| | messageId: 'parent', |
| | parentMessageId: null, |
| | text: 'Parent Message', |
| | createdAt: new Date('2023-01-01T00:00:00Z'), |
| | }, |
| | { |
| | messageId: 'child1', |
| | parentMessageId: 'parent', |
| | text: 'Child Message 1', |
| | createdAt: new Date('2023-01-01T00:01:00Z'), |
| | }, |
| | { |
| | messageId: 'child2', |
| | parentMessageId: 'parent', |
| | text: 'Child Message 2', |
| | createdAt: new Date('2023-01-01T00:02:00Z'), |
| | }, |
| | { |
| | messageId: 'grandchild1', |
| | parentMessageId: 'child1', |
| | text: 'Grandchild Message 1', |
| | createdAt: new Date('2023-01-01T00:03:00Z'), |
| | }, |
| | ]; |
| |
|
| | |
| | messages.forEach((msg) => { |
| | msg.conversationId = conversationId; |
| | msg.user = userId; |
| | msg.isCreatedByUser = false; |
| | msg.error = false; |
| | msg.unfinished = false; |
| | }); |
| |
|
| | |
| | await bulkSaveMessages(messages, true); |
| |
|
| | |
| | const retrievedMessages = await getMessages({ conversationId, user: userId }); |
| |
|
| | |
| | console.log( |
| | 'Retrieved Messages:', |
| | retrievedMessages.map((msg) => ({ |
| | messageId: msg.messageId, |
| | parentMessageId: msg.parentMessageId, |
| | createdAt: msg.createdAt, |
| | })), |
| | ); |
| |
|
| | |
| | const tree = buildTree({ messages: retrievedMessages }); |
| |
|
| | |
| | console.log( |
| | 'Tree structure:', |
| | tree.map((root) => ({ |
| | messageId: root.messageId, |
| | children: root.children.map((child) => ({ |
| | messageId: child.messageId, |
| | children: child.children.map((grandchild) => ({ |
| | messageId: grandchild.messageId, |
| | })), |
| | })), |
| | })), |
| | ); |
| |
|
| | |
| | expect(retrievedMessages.length).toBe(4); |
| |
|
| | |
| | const parentMsg = retrievedMessages.find((msg) => msg.messageId === 'parent'); |
| | expect(parentMsg.parentMessageId).toBeNull(); |
| |
|
| | const childMsg1 = retrievedMessages.find((msg) => msg.messageId === 'child1'); |
| | expect(childMsg1.parentMessageId).toBe('parent'); |
| |
|
| | |
| | expect(tree.length).toBe(1); |
| | expect(tree[0].messageId).toBe('parent'); |
| | expect(tree[0].children.length).toBe(2); |
| | }); |
| | }); |
| |
|