mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 08:12:00 +02:00
247 lines
9.2 KiB
JavaScript
247 lines
9.2 KiB
JavaScript
![]() |
const fs = require('fs');
|
||
|
const path = require('path');
|
||
|
const { EModelEndpoint, Constants } = require('librechat-data-provider');
|
||
|
const { ImportBatchBuilder } = require('./importBatchBuilder');
|
||
|
const { getImporter } = require('./importers');
|
||
|
|
||
|
// Mocking the ImportBatchBuilder class and its methods
|
||
|
jest.mock('./importBatchBuilder', () => {
|
||
|
return {
|
||
|
ImportBatchBuilder: jest.fn().mockImplementation(() => {
|
||
|
return {
|
||
|
startConversation: jest.fn().mockResolvedValue(undefined),
|
||
|
addUserMessage: jest.fn().mockResolvedValue(undefined),
|
||
|
addGptMessage: jest.fn().mockResolvedValue(undefined),
|
||
|
saveMessage: jest.fn().mockResolvedValue(undefined),
|
||
|
finishConversation: jest.fn().mockResolvedValue(undefined),
|
||
|
saveBatch: jest.fn().mockResolvedValue(undefined),
|
||
|
};
|
||
|
}),
|
||
|
};
|
||
|
});
|
||
|
|
||
|
describe('importChatGptConvo', () => {
|
||
|
it('should import conversation correctly', async () => {
|
||
|
const expectedNumberOfMessages = 19;
|
||
|
const expectedNumberOfConversations = 2;
|
||
|
// Given
|
||
|
const jsonData = JSON.parse(
|
||
|
fs.readFileSync(path.join(__dirname, '__data__', 'chatgpt-export.json'), 'utf8'),
|
||
|
);
|
||
|
const requestUserId = 'user-123';
|
||
|
const mockedBuilderFactory = jest.fn().mockReturnValue(new ImportBatchBuilder(requestUserId));
|
||
|
|
||
|
// When
|
||
|
const importer = getImporter(jsonData);
|
||
|
await importer(jsonData, requestUserId, mockedBuilderFactory);
|
||
|
|
||
|
// Then
|
||
|
expect(mockedBuilderFactory).toHaveBeenCalledWith(requestUserId);
|
||
|
const mockImportBatchBuilder = mockedBuilderFactory.mock.results[0].value;
|
||
|
|
||
|
expect(mockImportBatchBuilder.startConversation).toHaveBeenCalledWith(EModelEndpoint.openAI);
|
||
|
expect(mockImportBatchBuilder.saveMessage).toHaveBeenCalledTimes(expectedNumberOfMessages); // Adjust expected number
|
||
|
expect(mockImportBatchBuilder.finishConversation).toHaveBeenCalledTimes(
|
||
|
expectedNumberOfConversations,
|
||
|
); // Adjust expected number
|
||
|
expect(mockImportBatchBuilder.saveBatch).toHaveBeenCalled();
|
||
|
});
|
||
|
it('should maintain correct message hierarchy (tree parent/children relationship)', async () => {
|
||
|
// Prepare test data with known hierarchy
|
||
|
const jsonData = JSON.parse(
|
||
|
fs.readFileSync(path.join(__dirname, '__data__', 'chatgpt-tree.json'), 'utf8'),
|
||
|
);
|
||
|
|
||
|
const requestUserId = 'user-123';
|
||
|
const mockedBuilderFactory = jest.fn().mockReturnValue(new ImportBatchBuilder(requestUserId));
|
||
|
|
||
|
// When
|
||
|
const importer = getImporter(jsonData);
|
||
|
await importer(jsonData, requestUserId, mockedBuilderFactory);
|
||
|
|
||
|
// Then
|
||
|
expect(mockedBuilderFactory).toHaveBeenCalledWith(requestUserId);
|
||
|
const mockImportBatchBuilder = mockedBuilderFactory.mock.results[0].value;
|
||
|
|
||
|
const entries = Object.keys(jsonData[0].mapping);
|
||
|
// Filter entries that should be processed (not system and have content)
|
||
|
const messageEntries = entries.filter(
|
||
|
(id) =>
|
||
|
jsonData[0].mapping[id].message &&
|
||
|
jsonData[0].mapping[id].message.author.role !== 'system' &&
|
||
|
jsonData[0].mapping[id].message.content,
|
||
|
);
|
||
|
|
||
|
// Expect the saveMessage to be called for each valid entry
|
||
|
expect(mockImportBatchBuilder.saveMessage).toHaveBeenCalledTimes(messageEntries.length);
|
||
|
|
||
|
const idToUUIDMap = new Map();
|
||
|
// Map original IDs to dynamically generated UUIDs
|
||
|
mockImportBatchBuilder.saveMessage.mock.calls.forEach((call, index) => {
|
||
|
const originalId = messageEntries[index];
|
||
|
idToUUIDMap.set(originalId, call[0].messageId);
|
||
|
});
|
||
|
|
||
|
// Validate the UUID map contains all expected entries
|
||
|
expect(idToUUIDMap.size).toBe(messageEntries.length);
|
||
|
|
||
|
// Validate correct parent-child relationships
|
||
|
messageEntries.forEach((id) => {
|
||
|
const { parent } = jsonData[0].mapping[id];
|
||
|
|
||
|
const expectedParentId = parent
|
||
|
? idToUUIDMap.get(parent) ?? Constants.NO_PARENT
|
||
|
: Constants.NO_PARENT;
|
||
|
|
||
|
const actualParentId = idToUUIDMap.get(id)
|
||
|
? mockImportBatchBuilder.saveMessage.mock.calls.find(
|
||
|
(call) => call[0].messageId === idToUUIDMap.get(id),
|
||
|
)[0].parentMessageId
|
||
|
: Constants.NO_PARENT;
|
||
|
|
||
|
expect(actualParentId).toBe(expectedParentId);
|
||
|
});
|
||
|
|
||
|
expect(mockImportBatchBuilder.saveBatch).toHaveBeenCalled();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('importLibreChatConvo', () => {
|
||
|
it('should import conversation correctly', async () => {
|
||
|
const expectedNumberOfMessages = 6;
|
||
|
const expectedNumberOfConversations = 1;
|
||
|
|
||
|
// Given
|
||
|
const jsonData = JSON.parse(
|
||
|
fs.readFileSync(path.join(__dirname, '__data__', 'librechat-export.json'), 'utf8'),
|
||
|
);
|
||
|
const requestUserId = 'user-123';
|
||
|
const mockedBuilderFactory = jest.fn().mockReturnValue(new ImportBatchBuilder(requestUserId));
|
||
|
|
||
|
// When
|
||
|
const importer = getImporter(jsonData);
|
||
|
await importer(jsonData, requestUserId, mockedBuilderFactory);
|
||
|
|
||
|
// Then
|
||
|
const mockImportBatchBuilder = mockedBuilderFactory.mock.results[0].value;
|
||
|
expect(mockImportBatchBuilder.startConversation).toHaveBeenCalledWith(EModelEndpoint.openAI);
|
||
|
expect(mockImportBatchBuilder.saveMessage).toHaveBeenCalledTimes(expectedNumberOfMessages); // Adjust expected number
|
||
|
expect(mockImportBatchBuilder.finishConversation).toHaveBeenCalledTimes(
|
||
|
expectedNumberOfConversations,
|
||
|
); // Adjust expected number
|
||
|
expect(mockImportBatchBuilder.saveBatch).toHaveBeenCalled();
|
||
|
});
|
||
|
it('should maintain correct message hierarchy (tree parent/children relationship)', async () => {
|
||
|
// Load test data
|
||
|
const jsonData = JSON.parse(
|
||
|
fs.readFileSync(path.join(__dirname, '__data__', 'librechat-tree.json'), 'utf8'),
|
||
|
);
|
||
|
const requestUserId = 'user-123';
|
||
|
const mockedBuilderFactory = jest.fn().mockReturnValue(new ImportBatchBuilder(requestUserId));
|
||
|
|
||
|
// When
|
||
|
const importer = getImporter(jsonData);
|
||
|
await importer(jsonData, requestUserId, mockedBuilderFactory);
|
||
|
|
||
|
// Then
|
||
|
const mockImportBatchBuilder = mockedBuilderFactory.mock.results[0].value;
|
||
|
|
||
|
// Create a map to track original message IDs to new UUIDs
|
||
|
const idToUUIDMap = new Map();
|
||
|
mockImportBatchBuilder.saveMessage.mock.calls.forEach((call) => {
|
||
|
const message = call[0];
|
||
|
idToUUIDMap.set(message.originalMessageId, message.messageId);
|
||
|
});
|
||
|
|
||
|
// Function to recursively check children
|
||
|
const checkChildren = (children, parentId) => {
|
||
|
children.forEach((child) => {
|
||
|
const childUUID = idToUUIDMap.get(child.messageId);
|
||
|
const expectedParentId = idToUUIDMap.get(parentId) ?? null;
|
||
|
const messageCall = mockImportBatchBuilder.saveMessage.mock.calls.find(
|
||
|
(call) => call[0].messageId === childUUID,
|
||
|
);
|
||
|
|
||
|
const actualParentId = messageCall[0].parentMessageId;
|
||
|
expect(actualParentId).toBe(expectedParentId);
|
||
|
|
||
|
if (child.children && child.children.length > 0) {
|
||
|
checkChildren(child.children, child.messageId);
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
// Start hierarchy validation from root messages
|
||
|
checkChildren(jsonData.messagesTree, null); // Assuming root messages have no parent
|
||
|
|
||
|
expect(mockImportBatchBuilder.saveBatch).toHaveBeenCalled();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('importChatBotUiConvo', () => {
|
||
|
it('should import custom conversation correctly', async () => {
|
||
|
// Given
|
||
|
const jsonData = JSON.parse(
|
||
|
fs.readFileSync(path.join(__dirname, '__data__', 'chatbotui-export.json'), 'utf8'),
|
||
|
);
|
||
|
const requestUserId = 'custom-user-456';
|
||
|
const mockedBuilderFactory = jest.fn().mockReturnValue(new ImportBatchBuilder(requestUserId));
|
||
|
|
||
|
// When
|
||
|
const importer = getImporter(jsonData);
|
||
|
await importer(jsonData, requestUserId, mockedBuilderFactory);
|
||
|
|
||
|
// Then
|
||
|
const mockImportBatchBuilder = mockedBuilderFactory.mock.results[0].value;
|
||
|
expect(mockImportBatchBuilder.startConversation).toHaveBeenCalledWith('openAI');
|
||
|
|
||
|
// User messages
|
||
|
expect(mockImportBatchBuilder.addUserMessage).toHaveBeenCalledTimes(3);
|
||
|
expect(mockImportBatchBuilder.addUserMessage).toHaveBeenNthCalledWith(
|
||
|
1,
|
||
|
'Hello what are you able to do?',
|
||
|
);
|
||
|
expect(mockImportBatchBuilder.addUserMessage).toHaveBeenNthCalledWith(
|
||
|
3,
|
||
|
'Give me the code that inverts binary tree in COBOL',
|
||
|
);
|
||
|
|
||
|
// GPT messages
|
||
|
expect(mockImportBatchBuilder.addGptMessage).toHaveBeenCalledTimes(3);
|
||
|
expect(mockImportBatchBuilder.addGptMessage).toHaveBeenNthCalledWith(
|
||
|
1,
|
||
|
expect.stringMatching(/^Hello! As an AI developed by OpenAI/),
|
||
|
'gpt-4-1106-preview',
|
||
|
);
|
||
|
expect(mockImportBatchBuilder.addGptMessage).toHaveBeenNthCalledWith(
|
||
|
3,
|
||
|
expect.stringContaining('```cobol'),
|
||
|
'gpt-3.5-turbo',
|
||
|
);
|
||
|
|
||
|
expect(mockImportBatchBuilder.finishConversation).toHaveBeenCalledTimes(2);
|
||
|
expect(mockImportBatchBuilder.finishConversation).toHaveBeenNthCalledWith(
|
||
|
1,
|
||
|
'Hello what are you able to do?',
|
||
|
expect.any(Date),
|
||
|
);
|
||
|
expect(mockImportBatchBuilder.finishConversation).toHaveBeenNthCalledWith(
|
||
|
2,
|
||
|
'Give me the code that inverts ...',
|
||
|
expect.any(Date),
|
||
|
);
|
||
|
|
||
|
expect(mockImportBatchBuilder.saveBatch).toHaveBeenCalled();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('getImporter', () => {
|
||
|
it('should throw an error if the import type is not supported', () => {
|
||
|
// Given
|
||
|
const jsonData = { unsupported: 'data' };
|
||
|
|
||
|
// When
|
||
|
expect(() => getImporter(jsonData)).toThrow('Unsupported import type');
|
||
|
});
|
||
|
});
|