File size: 9,135 Bytes
f0743f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
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';

    // Create messages with inconsistent timestamps
    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'),
      }, // Note: Earlier than its parent
      {
        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'),
      },
    ];

    // Add common properties to all messages
    messages.forEach((msg) => {
      msg.conversationId = conversationId;
      msg.user = userId;
      msg.isCreatedByUser = false;
      msg.error = false;
      msg.unfinished = false;
    });

    // Save messages with overrideTimestamp omitted (default is false)
    await bulkSaveMessages(messages, true);

    // Retrieve messages (this will sort by createdAt)
    const retrievedMessages = await getMessages({ conversationId, user: userId });

    // Build tree
    const tree = buildTree({ messages: retrievedMessages });

    // Check if the tree is incorrect (folded/corrupted)
    expect(tree.length).toBeGreaterThan(1); // Should have multiple root messages, indicating corruption
  });

  test('Fix: Conversation structure maintained with more than 16 messages', async () => {
    const userId = 'testUser';
    const conversationId = 'testConversation';

    // Create more than 16 messages
    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)),
    }));

    // Save messages with new timestamps being generated (message objects ignored)
    await bulkSaveMessages(messages);

    // Retrieve messages (this will sort by createdAt, but it shouldn't matter now)
    const retrievedMessages = await getMessages({ conversationId, user: userId });

    // Build tree
    const tree = buildTree({ messages: retrievedMessages });

    // Check if the tree is correct
    expect(tree.length).toBe(1); // Should have only one root message
    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); // Last message should have no children
  });

  test('Simulate MongoDB ordering issue with more than 16 messages and close timestamps', async () => {
    const userId = 'testUser';
    const conversationId = 'testConversation';

    // Create more than 16 messages with very close timestamps
    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)),
    }));

    // Add common properties to all messages
    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';

    // Create more than 16 messages with distinct timestamps
    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), // Ensure each message has a distinct timestamp
    }));

    // Add common properties to all messages
    messages.forEach((msg) => {
      msg.isCreatedByUser = false;
      msg.error = false;
      msg.unfinished = false;
    });

    // Save messages with overriding timestamps (preserve original timestamps)
    await bulkSaveMessages(messages, true);

    // Retrieve messages (this will sort by createdAt)
    const retrievedMessages = await getMessages({ conversationId, user: userId });

    // Build tree
    const tree = buildTree({ messages: retrievedMessages });

    // Check if the tree is correct
    expect(tree.length).toBe(1); // Should have only one root message
    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); // Last message should have no children
  });

  test('Random order dates between parent and children messages', async () => {
    const userId = 'testUser';
    const conversationId = 'testConversation';

    // Create messages with deliberately out-of-order timestamps but sequential creation
    const messages = [
      {
        messageId: 'parent',
        parentMessageId: null,
        text: 'Parent Message',
        createdAt: new Date('2023-01-01T00:00:00Z'), // Make parent earliest
      },
      {
        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'),
      },
    ];

    // Add common properties to all messages
    messages.forEach((msg) => {
      msg.conversationId = conversationId;
      msg.user = userId;
      msg.isCreatedByUser = false;
      msg.error = false;
      msg.unfinished = false;
    });

    // Save messages with overrideTimestamp set to true
    await bulkSaveMessages(messages, true);

    // Retrieve messages
    const retrievedMessages = await getMessages({ conversationId, user: userId });

    // Debug log to see what's being returned
    console.log(
      'Retrieved Messages:',
      retrievedMessages.map((msg) => ({
        messageId: msg.messageId,
        parentMessageId: msg.parentMessageId,
        createdAt: msg.createdAt,
      })),
    );

    // Build tree
    const tree = buildTree({ messages: retrievedMessages });

    // Debug log to see the tree structure
    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,
          })),
        })),
      })),
    );

    // Verify the structure before making assertions
    expect(retrievedMessages.length).toBe(4); // Should have all 4 messages

    // Check if messages are properly linked
    const parentMsg = retrievedMessages.find((msg) => msg.messageId === 'parent');
    expect(parentMsg.parentMessageId).toBeNull(); // Parent should have null parentMessageId

    const childMsg1 = retrievedMessages.find((msg) => msg.messageId === 'child1');
    expect(childMsg1.parentMessageId).toBe('parent');

    // Then check tree structure
    expect(tree.length).toBe(1); // Should have only one root message
    expect(tree[0].messageId).toBe('parent');
    expect(tree[0].children.length).toBe(2); // Should have two children
  });
});