| const { Constants } = require('librechat-data-provider'); |
| const { ImportBatchBuilder } = require('./importBatchBuilder'); |
| const { getImporter } = require('./importers'); |
|
|
| |
| jest.mock('~/models/Conversation', () => ({ |
| bulkSaveConvos: jest.fn(), |
| })); |
| jest.mock('~/models/Message', () => ({ |
| bulkSaveMessages: jest.fn(), |
| })); |
| jest.mock('~/cache/getLogStores'); |
| const getLogStores = require('~/cache/getLogStores'); |
| const mockedCacheGet = jest.fn(); |
| getLogStores.mockImplementation(() => ({ |
| get: mockedCacheGet, |
| })); |
|
|
| describe('Import Timestamp Ordering', () => { |
| beforeEach(() => { |
| jest.clearAllMocks(); |
| mockedCacheGet.mockResolvedValue(null); |
| }); |
|
|
| describe('LibreChat Import - Timestamp Issues', () => { |
| test('should maintain proper timestamp order between parent and child messages', async () => { |
| |
| const jsonData = { |
| conversationId: 'test-convo-123', |
| title: 'Test Conversation', |
| messages: [ |
| { |
| messageId: 'parent-1', |
| parentMessageId: Constants.NO_PARENT, |
| text: 'Parent Message', |
| sender: 'user', |
| isCreatedByUser: true, |
| createdAt: '2023-01-01T00:02:00Z', |
| }, |
| { |
| messageId: 'child-1', |
| parentMessageId: 'parent-1', |
| text: 'Child Message', |
| sender: 'assistant', |
| isCreatedByUser: false, |
| createdAt: '2023-01-01T00:01:00Z', |
| }, |
| { |
| messageId: 'grandchild-1', |
| parentMessageId: 'child-1', |
| text: 'Grandchild Message', |
| sender: 'user', |
| isCreatedByUser: true, |
| createdAt: '2023-01-01T00:00:30Z', |
| }, |
| ], |
| }; |
|
|
| const requestUserId = 'user-123'; |
| const importBatchBuilder = new ImportBatchBuilder(requestUserId); |
| jest.spyOn(importBatchBuilder, 'saveMessage'); |
|
|
| const importer = getImporter(jsonData); |
| await importer(jsonData, requestUserId, () => importBatchBuilder); |
|
|
| |
| const savedMessages = importBatchBuilder.messages; |
|
|
| const parent = savedMessages.find((msg) => msg.text === 'Parent Message'); |
| const child = savedMessages.find((msg) => msg.text === 'Child Message'); |
| const grandchild = savedMessages.find((msg) => msg.text === 'Grandchild Message'); |
|
|
| |
| expect(parent).toBeDefined(); |
| expect(child).toBeDefined(); |
| expect(grandchild).toBeDefined(); |
|
|
| |
| expect(new Date(child.createdAt).getTime()).toBeGreaterThan( |
| new Date(parent.createdAt).getTime(), |
| ); |
| expect(new Date(grandchild.createdAt).getTime()).toBeGreaterThan( |
| new Date(child.createdAt).getTime(), |
| ); |
| }); |
|
|
| test('should handle complex multi-branch scenario with out-of-order timestamps', async () => { |
| const jsonData = { |
| conversationId: 'complex-test-123', |
| title: 'Complex Test', |
| messages: [ |
| |
| { |
| messageId: 'root-1', |
| parentMessageId: Constants.NO_PARENT, |
| text: 'Root 1', |
| sender: 'user', |
| isCreatedByUser: true, |
| createdAt: '2023-01-01T00:03:00Z', |
| }, |
| { |
| messageId: 'a-1', |
| parentMessageId: 'root-1', |
| text: 'A1', |
| sender: 'assistant', |
| isCreatedByUser: false, |
| createdAt: '2023-01-01T00:02:00Z', |
| }, |
| { |
| messageId: 'b-1', |
| parentMessageId: 'a-1', |
| text: 'B1', |
| sender: 'user', |
| isCreatedByUser: true, |
| createdAt: '2023-01-01T00:01:00Z', |
| }, |
| |
| { |
| messageId: 'root-2', |
| parentMessageId: Constants.NO_PARENT, |
| text: 'Root 2', |
| sender: 'user', |
| isCreatedByUser: true, |
| createdAt: '2023-01-01T00:00:30Z', |
| }, |
| { |
| messageId: 'c-2', |
| parentMessageId: 'root-2', |
| text: 'C2', |
| sender: 'assistant', |
| isCreatedByUser: false, |
| createdAt: '2023-01-01T00:04:00Z', |
| }, |
| { |
| messageId: 'd-2', |
| parentMessageId: 'c-2', |
| text: 'D2', |
| sender: 'user', |
| isCreatedByUser: true, |
| createdAt: '2023-01-01T00:02:30Z', |
| }, |
| ], |
| }; |
|
|
| const requestUserId = 'user-123'; |
| const importBatchBuilder = new ImportBatchBuilder(requestUserId); |
| jest.spyOn(importBatchBuilder, 'saveMessage'); |
|
|
| const importer = getImporter(jsonData); |
| await importer(jsonData, requestUserId, () => importBatchBuilder); |
|
|
| const savedMessages = importBatchBuilder.messages; |
|
|
| |
| const root1 = savedMessages.find((msg) => msg.text === 'Root 1'); |
| const a1 = savedMessages.find((msg) => msg.text === 'A1'); |
| const b1 = savedMessages.find((msg) => msg.text === 'B1'); |
| const root2 = savedMessages.find((msg) => msg.text === 'Root 2'); |
| const c2 = savedMessages.find((msg) => msg.text === 'C2'); |
| const d2 = savedMessages.find((msg) => msg.text === 'D2'); |
|
|
| |
| expect(new Date(a1.createdAt).getTime()).toBeGreaterThan(new Date(root1.createdAt).getTime()); |
| expect(new Date(b1.createdAt).getTime()).toBeGreaterThan(new Date(a1.createdAt).getTime()); |
|
|
| |
| expect(new Date(c2.createdAt).getTime()).toBeGreaterThan(new Date(root2.createdAt).getTime()); |
| expect(new Date(d2.createdAt).getTime()).toBeGreaterThan(new Date(c2.createdAt).getTime()); |
| }); |
|
|
| test('recursive format should NOW have timestamp protection', async () => { |
| |
| const jsonData = { |
| conversationId: 'recursive-test-123', |
| title: 'Recursive Test', |
| recursive: true, |
| messages: [ |
| { |
| messageId: 'parent-1', |
| parentMessageId: Constants.NO_PARENT, |
| text: 'Parent Message', |
| sender: 'User', |
| isCreatedByUser: true, |
| createdAt: '2023-01-01T00:02:00Z', |
| children: [ |
| { |
| messageId: 'child-1', |
| parentMessageId: 'parent-1', |
| text: 'Child Message', |
| sender: 'Assistant', |
| isCreatedByUser: false, |
| createdAt: '2023-01-01T00:01:00Z', |
| children: [ |
| { |
| messageId: 'grandchild-1', |
| parentMessageId: 'child-1', |
| text: 'Grandchild Message', |
| sender: 'User', |
| isCreatedByUser: true, |
| createdAt: '2023-01-01T00:00:30Z', |
| children: [], |
| }, |
| ], |
| }, |
| ], |
| }, |
| ], |
| }; |
|
|
| const requestUserId = 'user-123'; |
| const importBatchBuilder = new ImportBatchBuilder(requestUserId); |
|
|
| const importer = getImporter(jsonData); |
| await importer(jsonData, requestUserId, () => importBatchBuilder); |
|
|
| const savedMessages = importBatchBuilder.messages; |
|
|
| |
| expect(savedMessages).toHaveLength(3); |
|
|
| |
| |
| const parent = savedMessages.find((msg) => msg.text === 'Parent Message'); |
| const child = savedMessages.find((msg) => msg.text === 'Child Message'); |
| const grandchild = savedMessages.find((msg) => msg.text === 'Grandchild Message'); |
|
|
| expect(parent).toBeDefined(); |
| expect(child).toBeDefined(); |
| expect(grandchild).toBeDefined(); |
|
|
| |
| expect(parent.createdAt).toBeDefined(); |
| expect(child.createdAt).toBeDefined(); |
| expect(grandchild.createdAt).toBeDefined(); |
|
|
| |
| expect(new Date(child.createdAt).getTime()).toBeGreaterThan( |
| new Date(parent.createdAt).getTime(), |
| ); |
| expect(new Date(grandchild.createdAt).getTime()).toBeGreaterThan( |
| new Date(child.createdAt).getTime(), |
| ); |
| }); |
| }); |
|
|
| describe('Comparison with Fork Functionality', () => { |
| test('fork functionality correctly handles timestamp issues (for comparison)', async () => { |
| const { cloneMessagesWithTimestamps } = require('./fork'); |
|
|
| const messagesToClone = [ |
| { |
| messageId: 'parent', |
| parentMessageId: Constants.NO_PARENT, |
| text: 'Parent Message', |
| createdAt: '2023-01-01T00:02:00Z', |
| }, |
| { |
| messageId: 'child', |
| parentMessageId: 'parent', |
| text: 'Child Message', |
| createdAt: '2023-01-01T00:01:00Z', |
| }, |
| ]; |
|
|
| const importBatchBuilder = new ImportBatchBuilder('user-123'); |
| jest.spyOn(importBatchBuilder, 'saveMessage'); |
|
|
| cloneMessagesWithTimestamps(messagesToClone, importBatchBuilder); |
|
|
| const savedMessages = importBatchBuilder.messages; |
| const parent = savedMessages.find((msg) => msg.text === 'Parent Message'); |
| const child = savedMessages.find((msg) => msg.text === 'Child Message'); |
|
|
| |
| expect(new Date(child.createdAt).getTime()).toBeGreaterThan( |
| new Date(parent.createdAt).getTime(), |
| ); |
| }); |
| }); |
| }); |
|
|