use event_integration_test::user_event::use_localhost_af_cloud; use event_integration_test::EventIntegrationTest; use flowy_ai_pub::cloud::MessageCursor; use flowy_ai_pub::persistence::{ select_answer_where_match_reply_message_id, select_chat_messages, select_message, select_message_content, total_message_count, upsert_chat_messages, ChatMessageTable, }; use uuid::Uuid; #[tokio::test] async fn chat_message_table_insert_select_test() { use_localhost_af_cloud().await; let test = EventIntegrationTest::new().await; test.sign_up_as_anon().await; let uid = test.user_manager.get_anon_user().await.unwrap().id; let db_conn = test.user_manager.db_connection(uid).unwrap(); let chat_id = Uuid::new_v4().to_string(); let message_id_1 = 1000; let message_id_2 = 2000; // Create test messages let messages = vec![ ChatMessageTable { message_id: message_id_1, chat_id: chat_id.clone(), content: "Hello, this is a test message".to_string(), created_at: 1625097600, // 2021-07-01 author_type: 1, // User author_id: "user_1".to_string(), reply_message_id: None, metadata: None, is_sync: false, }, ChatMessageTable { message_id: message_id_2, chat_id: chat_id.clone(), content: "This is a reply to the test message".to_string(), created_at: 1625097700, // 2021-07-01, 100 seconds later author_type: 0, // AI author_id: "ai".to_string(), reply_message_id: Some(message_id_1), metadata: Some(r#"{"source": "test"}"#.to_string()), is_sync: false, }, ]; // Test insert_chat_messages let result = upsert_chat_messages(db_conn, &messages); assert!( result.is_ok(), "Failed to insert chat messages: {:?}", result ); // Test select_chat_messages let db_conn = test.user_manager.db_connection(uid).unwrap(); let messages_result = select_chat_messages(db_conn, &chat_id, 10, MessageCursor::Offset(0)).unwrap(); assert_eq!(messages_result.messages.len(), 2); assert_eq!(messages_result.total_count, 2); assert!(!messages_result.has_more); // Verify the content of the returned messages let first_message = messages_result .messages .iter() .find(|m| m.message_id == message_id_1) .unwrap(); assert_eq!(first_message.content, "Hello, this is a test message"); assert_eq!(first_message.author_type, 1); let second_message = messages_result .messages .iter() .find(|m| m.message_id == message_id_2) .unwrap(); assert_eq!( second_message.content, "This is a reply to the test message" ); assert_eq!(second_message.reply_message_id, Some(message_id_1)); } #[tokio::test] async fn chat_message_table_cursor_test() { use_localhost_af_cloud().await; let test = EventIntegrationTest::new().await; test.sign_up_as_anon().await; let uid = test.user_manager.get_anon_user().await.unwrap().id; let db_conn = test.user_manager.db_connection(uid).unwrap(); let chat_id = Uuid::new_v4().to_string(); // Create multiple test messages with sequential IDs let mut messages = Vec::new(); for i in 1..6 { messages.push(ChatMessageTable { message_id: i * 1000, chat_id: chat_id.clone(), content: format!("Message {}", i), created_at: 1625097600 + (i * 100), // Increasing timestamps author_type: 1, // User author_id: "user_1".to_string(), reply_message_id: None, metadata: None, is_sync: false, }); } // Insert messages upsert_chat_messages(db_conn, &messages).unwrap(); // Test MessageCursor::Offset let db_conn = test.user_manager.db_connection(uid).unwrap(); let result_offset = select_chat_messages( db_conn, &chat_id, 2, // Limit to 2 messages MessageCursor::Offset(0), ) .unwrap(); assert_eq!(result_offset.messages.len(), 2); assert!(result_offset.has_more); // Test MessageCursor::AfterMessageId let db_conn = test.user_manager.db_connection(uid).unwrap(); let result_after = select_chat_messages( db_conn, &chat_id, 3, // Limit to 3 messages MessageCursor::AfterMessageId(2000), ) .unwrap(); assert_eq!(result_after.messages.len(), 3); // Should get message IDs 3000, 4000, 5000 assert!(result_after.messages.iter().all(|m| m.message_id > 2000)); // Test MessageCursor::BeforeMessageId let db_conn = test.user_manager.db_connection(uid).unwrap(); let result_before = select_chat_messages( db_conn, &chat_id, 2, // Limit to 2 messages MessageCursor::BeforeMessageId(4000), ) .unwrap(); assert_eq!(result_before.messages.len(), 2); // Should get message IDs 1000, 2000, 3000 assert!(result_before.messages.iter().all(|m| m.message_id < 4000)); } #[tokio::test] async fn chat_message_total_count_test() { use_localhost_af_cloud().await; let test = EventIntegrationTest::new().await; test.sign_up_as_anon().await; let uid = test.user_manager.get_anon_user().await.unwrap().id; let db_conn = test.user_manager.db_connection(uid).unwrap(); let chat_id = Uuid::new_v4().to_string(); // Create test messages let messages = vec![ ChatMessageTable { message_id: 1001, chat_id: chat_id.clone(), content: "Message 1".to_string(), created_at: 1625097600, author_type: 1, author_id: "user_1".to_string(), reply_message_id: None, metadata: None, is_sync: false, }, ChatMessageTable { message_id: 1002, chat_id: chat_id.clone(), content: "Message 2".to_string(), created_at: 1625097700, author_type: 0, author_id: "ai".to_string(), reply_message_id: None, metadata: None, is_sync: false, }, ]; // Insert messages upsert_chat_messages(db_conn, &messages).unwrap(); // Test total_message_count let db_conn = test.user_manager.db_connection(uid).unwrap(); let count = total_message_count(db_conn, &chat_id).unwrap(); assert_eq!(count, 2); // Add one more message let db_conn = test.user_manager.db_connection(uid).unwrap(); let additional_message = ChatMessageTable { message_id: 1003, chat_id: chat_id.clone(), content: "Message 3".to_string(), created_at: 1625097800, author_type: 1, author_id: "user_1".to_string(), reply_message_id: None, metadata: None, is_sync: false, }; upsert_chat_messages(db_conn, &[additional_message]).unwrap(); // Verify count increased let db_conn = test.user_manager.db_connection(uid).unwrap(); let updated_count = total_message_count(db_conn, &chat_id).unwrap(); assert_eq!(updated_count, 3); // Test count for non-existent chat let db_conn = test.user_manager.db_connection(uid).unwrap(); let empty_count = total_message_count(db_conn, "non_existent_chat").unwrap(); assert_eq!(empty_count, 0); } #[tokio::test] async fn chat_message_select_message_test() { use_localhost_af_cloud().await; let test = EventIntegrationTest::new().await; test.sign_up_as_anon().await; let uid = test.user_manager.get_anon_user().await.unwrap().id; let db_conn = test.user_manager.db_connection(uid).unwrap(); let chat_id = Uuid::new_v4().to_string(); let message_id = 2001; // Create test message let message = ChatMessageTable { message_id, chat_id: chat_id.clone(), content: "This is a test message for select_message".to_string(), created_at: 1625097600, author_type: 1, author_id: "user_1".to_string(), reply_message_id: None, metadata: Some(r#"{"test_key": "test_value"}"#.to_string()), is_sync: false, }; // Insert message upsert_chat_messages(db_conn, &[message]).unwrap(); // Test select_message let db_conn = test.user_manager.db_connection(uid).unwrap(); let result = select_message(db_conn, message_id).unwrap(); assert!(result.is_some()); let retrieved_message = result.unwrap(); assert_eq!(retrieved_message.message_id, message_id); assert_eq!(retrieved_message.chat_id, chat_id); assert_eq!( retrieved_message.content, "This is a test message for select_message" ); assert_eq!(retrieved_message.author_id, "user_1"); assert_eq!( retrieved_message.metadata, Some(r#"{"test_key": "test_value"}"#.to_string()) ); // Test select_message with non-existent ID let db_conn = test.user_manager.db_connection(uid).unwrap(); let non_existent = select_message(db_conn, 9999).unwrap(); assert!(non_existent.is_none()); } #[tokio::test] async fn chat_message_select_content_test() { use_localhost_af_cloud().await; let test = EventIntegrationTest::new().await; test.sign_up_as_anon().await; let uid = test.user_manager.get_anon_user().await.unwrap().id; let db_conn = test.user_manager.db_connection(uid).unwrap(); let chat_id = Uuid::new_v4().to_string(); let message_id = 3001; let message_content = "This is the content to retrieve"; // Create test message let message = ChatMessageTable { message_id, chat_id: chat_id.clone(), content: message_content.to_string(), created_at: 1625097600, author_type: 1, author_id: "user_1".to_string(), reply_message_id: None, metadata: None, is_sync: false, }; // Insert message upsert_chat_messages(db_conn, &[message]).unwrap(); // Test select_message_content let db_conn = test.user_manager.db_connection(uid).unwrap(); let content = select_message_content(db_conn, message_id).unwrap(); assert!(content.is_some()); assert_eq!(content.unwrap(), message_content); // Test with non-existent message let db_conn = test.user_manager.db_connection(uid).unwrap(); let no_content = select_message_content(db_conn, 9999).unwrap(); assert!(no_content.is_none()); } #[tokio::test] async fn chat_message_reply_test() { use_localhost_af_cloud().await; let test = EventIntegrationTest::new().await; test.sign_up_as_anon().await; let uid = test.user_manager.get_anon_user().await.unwrap().id; let db_conn = test.user_manager.db_connection(uid).unwrap(); let chat_id = Uuid::new_v4().to_string(); let question_id = 4001; let answer_id = 4002; // Create question and answer messages let question = ChatMessageTable { message_id: question_id, chat_id: chat_id.clone(), content: "What is the question?".to_string(), created_at: 1625097600, author_type: 1, // User author_id: "user_1".to_string(), reply_message_id: None, metadata: None, is_sync: false, }; let answer = ChatMessageTable { message_id: answer_id, chat_id: chat_id.clone(), content: "This is the answer".to_string(), created_at: 1625097700, author_type: 0, // AI author_id: "ai".to_string(), reply_message_id: Some(question_id), // Link to question metadata: None, is_sync: false, }; // Insert messages upsert_chat_messages(db_conn, &[question, answer]).unwrap(); // Test select_message_where_match_reply_message_id let db_conn = test.user_manager.db_connection(uid).unwrap(); let result = select_answer_where_match_reply_message_id(db_conn, &chat_id, question_id).unwrap(); assert!(result.is_some()); let reply = result.unwrap(); assert_eq!(reply.message_id, answer_id); assert_eq!(reply.content, "This is the answer"); assert_eq!(reply.reply_message_id, Some(question_id)); // Test with non-existent reply relation let db_conn = test.user_manager.db_connection(uid).unwrap(); let no_reply = select_answer_where_match_reply_message_id( db_conn, &chat_id, 9999, // Non-existent question ID ) .unwrap(); assert!(no_reply.is_none()); // Test with wrong chat_id let db_conn = test.user_manager.db_connection(uid).unwrap(); let wrong_chat = select_answer_where_match_reply_message_id(db_conn, "wrong_chat_id", question_id).unwrap(); assert!(wrong_chat.is_none()); } #[tokio::test] async fn chat_message_upsert_test() { use_localhost_af_cloud().await; let test = EventIntegrationTest::new().await; test.sign_up_as_anon().await; let uid = test.user_manager.get_anon_user().await.unwrap().id; let db_conn = test.user_manager.db_connection(uid).unwrap(); let chat_id = Uuid::new_v4().to_string(); let message_id = 5001; // Create initial message let message = ChatMessageTable { message_id, chat_id: chat_id.clone(), content: "Original content".to_string(), created_at: 1625097600, author_type: 1, author_id: "user_1".to_string(), reply_message_id: None, metadata: None, is_sync: false, }; // Insert message upsert_chat_messages(db_conn, &[message]).unwrap(); // Check original content let db_conn = test.user_manager.db_connection(uid).unwrap(); let original = select_message(db_conn, message_id).unwrap().unwrap(); assert_eq!(original.content, "Original content"); // Create updated message with same ID but different content let db_conn = test.user_manager.db_connection(uid).unwrap(); let updated_message = ChatMessageTable { message_id, // Same ID chat_id: chat_id.clone(), content: "Updated content".to_string(), // New content created_at: 1625097700, // Updated timestamp author_type: 1, author_id: "user_1".to_string(), reply_message_id: Some(1000), // Added reply ID metadata: Some(r#"{"updated": true}"#.to_string()), is_sync: false, }; // Upsert message upsert_chat_messages(db_conn, &[updated_message]).unwrap(); // Verify update let db_conn = test.user_manager.db_connection(uid).unwrap(); let result = select_message(db_conn, message_id).unwrap().unwrap(); assert_eq!(result.content, "Updated content"); assert_eq!(result.created_at, 1625097700); assert_eq!(result.reply_message_id, Some(1000)); assert_eq!(result.metadata, Some(r#"{"updated": true}"#.to_string())); // Count should still be 1 (update, not insert) let db_conn = test.user_manager.db_connection(uid).unwrap(); let count = total_message_count(db_conn, &chat_id).unwrap(); assert_eq!(count, 1); } #[tokio::test] async fn chat_message_select_with_large_dataset() { use_localhost_af_cloud().await; let test = EventIntegrationTest::new().await; test.sign_up_as_anon().await; let uid = test.user_manager.get_anon_user().await.unwrap().id; let db_conn = test.user_manager.db_connection(uid).unwrap(); let chat_id = Uuid::new_v4().to_string(); // Create 100 test messages with sequential IDs let mut messages = Vec::new(); for i in 1..=100 { messages.push(ChatMessageTable { message_id: i * 100, chat_id: chat_id.clone(), content: format!("Message {}", i), created_at: 1625097600 + (i * 10), // Increasing timestamps author_type: if i % 2 == 0 { 0 } else { 1 }, // Alternate between AI and User author_id: if i % 2 == 0 { "ai".to_string() } else { "user_1".to_string() }, reply_message_id: if i > 1 && i % 2 == 0 { Some((i - 1) * 100) } else { None }, // Even messages reply to previous message metadata: if i % 5 == 0 { Some(format!(r#"{{"index": {}}}"#, i)) } else { None }, is_sync: false, }); } // Insert all 100 messages upsert_chat_messages(db_conn, &messages).unwrap(); // Verify total count let db_conn = test.user_manager.db_connection(uid).unwrap(); let count = total_message_count(db_conn, &chat_id).unwrap(); assert_eq!(count, 100, "Should have 100 messages in the database"); // Test 1: MessageCursor::Offset with small page size let db_conn = test.user_manager.db_connection(uid).unwrap(); let page_size = 10; let result_offset = select_chat_messages(db_conn, &chat_id, page_size, MessageCursor::Offset(0)).unwrap(); assert_eq!( result_offset.messages.len(), page_size as usize, "Should return exactly {page_size} messages" ); assert!( result_offset.has_more, "Should have more messages available" ); assert_eq!(result_offset.total_count, 100, "Total count should be 100"); // Test 2: Pagination with offset let db_conn = test.user_manager.db_connection(uid).unwrap(); let result_page2 = select_chat_messages( db_conn, &chat_id, page_size, MessageCursor::Offset(page_size), ) .unwrap(); assert_eq!(result_page2.messages.len(), page_size as usize); assert!( result_page2.messages[0].message_id != result_offset.messages[0].message_id, "Second page should have different messages than first page" ); // Test 3: MessageCursor::AfterMessageId (forward pagination) let db_conn = test.user_manager.db_connection(uid).unwrap(); let middle_message_id = 5000; // Message ID from the middle let result_after = match select_chat_messages( db_conn, &chat_id, page_size, MessageCursor::AfterMessageId(middle_message_id), ) { Ok(result) => result, Err(e) if e.to_string().contains("NotFound") => { // If message with ID 5000 doesn't exist, try with a different ID let db_conn = test.user_manager.db_connection(uid).unwrap(); select_chat_messages( db_conn, &chat_id, page_size, MessageCursor::AfterMessageId(4500), ) .unwrap() }, Err(e) => panic!("Failed to select messages: {}", e), }; assert!( !result_after.messages.is_empty(), "Should return at least one message" ); assert!( result_after.messages.iter().all(|m| m.message_id > 4500), "All messages should have ID greater than the cursor" ); // Test 4: MessageCursor::BeforeMessageId (backward pagination) let db_conn = test.user_manager.db_connection(uid).unwrap(); let result_before = match select_chat_messages( db_conn, &chat_id, page_size, MessageCursor::BeforeMessageId(middle_message_id), ) { Ok(result) => result, Err(e) if e.to_string().contains("NotFound") => { // If message with ID 5000 doesn't exist, try with a different ID let db_conn = test.user_manager.db_connection(uid).unwrap(); select_chat_messages( db_conn, &chat_id, page_size, MessageCursor::BeforeMessageId(6000), ) .unwrap() }, Err(e) => panic!("Failed to select messages: {}", e), }; assert!( !result_before.messages.is_empty(), "Should return at least one message" ); assert!( result_before.messages.iter().all(|m| m.message_id < 6000), "All messages should have ID less than the cursor" ); // Test 5: Large page size (retrieve all) let db_conn = test.user_manager.db_connection(uid).unwrap(); let result_all = select_chat_messages( db_conn, &chat_id, 200, // More than we have MessageCursor::Offset(0), ) .unwrap(); assert_eq!( result_all.messages.len(), 100, "Should return all 100 messages" ); assert!(!result_all.has_more, "Should not have more messages"); // Test 6: Empty result when using out of range cursor let db_conn = test.user_manager.db_connection(uid).unwrap(); let result_out_of_range = match select_chat_messages( db_conn, &chat_id, page_size, MessageCursor::AfterMessageId(10000), // After the last message ) { Ok(result) => result, Err(e) if e.to_string().contains("NotFound") => { // Use Offset(0) with limit 0 to get an empty result with the right structure let db_conn = test.user_manager.db_connection(uid).unwrap(); select_chat_messages(db_conn, &chat_id, 0, MessageCursor::Offset(0)).unwrap() }, Err(e) => panic!("Failed to select messages: {}", e), }; // If we get to this point, we can just verify has_more is false assert!( !result_out_of_range.has_more, "Should not have more messages" ); } #[tokio::test] async fn chat_message_order_test() { use_localhost_af_cloud().await; let test = EventIntegrationTest::new().await; test.sign_up_as_anon().await; let uid = test.user_manager.get_anon_user().await.unwrap().id; let db_conn = test.user_manager.db_connection(uid).unwrap(); let chat_id = Uuid::new_v4().to_string(); // Create test messages with different timestamps but out-of-order IDs // to verify which field controls the sorting let messages = vec![ ChatMessageTable { message_id: 2000, // Higher ID but earlier timestamp chat_id: chat_id.clone(), content: "Message 2".to_string(), created_at: 1625097600, // Earlier author_type: 1, author_id: "user_1".to_string(), reply_message_id: None, metadata: None, is_sync: false, }, ChatMessageTable { message_id: 1000, // Lower ID but later timestamp chat_id: chat_id.clone(), content: "Message 1".to_string(), created_at: 1625097700, // Later author_type: 1, author_id: "user_1".to_string(), reply_message_id: None, metadata: None, is_sync: false, }, ChatMessageTable { message_id: 3000, chat_id: chat_id.clone(), content: "Message 3".to_string(), created_at: 1625097800, // Latest author_type: 1, author_id: "user_1".to_string(), reply_message_id: None, metadata: None, is_sync: false, }, ]; // Insert messages upsert_chat_messages(db_conn, &messages).unwrap(); // Test 1: Verify default order (should be by created_at, not message_id) let db_conn = test.user_manager.db_connection(uid).unwrap(); let result = select_chat_messages(db_conn, &chat_id, 10, MessageCursor::Offset(0)).unwrap(); assert_eq!(result.messages.len(), 3); // Check if ordered by created_at (descending) let is_ordered_by_created_at_desc = result .messages .windows(2) .all(|w| w[0].created_at >= w[1].created_at); assert!( is_ordered_by_created_at_desc, "Messages are not ordered by created_at in descending order" ); println!( "Message order is {}", if is_ordered_by_created_at_desc { "descending by created_at" } else { "not descending by created_at" } ); // Test 2: Test cursor with AfterMessageId let db_conn = test.user_manager.db_connection(uid).unwrap(); let middle_message = &result.messages[1]; let result_after = select_chat_messages( db_conn, &chat_id, 10, MessageCursor::AfterMessageId(middle_message.message_id), ) .unwrap(); // With our simplified implementation, we should get messages with ID > middle_message.message_id for msg in &result_after.messages { assert!( msg.message_id > middle_message.message_id, "Message with ID {} should have higher ID than middle message ({})", msg.message_id, middle_message.message_id ); } // Test 3: Test cursor with BeforeMessageId let db_conn = test.user_manager.db_connection(uid).unwrap(); let result_before = select_chat_messages( db_conn, &chat_id, 10, MessageCursor::BeforeMessageId(middle_message.message_id), ) .unwrap(); // With our simplified implementation, we should get messages with ID < middle_message.message_id for msg in &result_before.messages { assert!( msg.message_id < middle_message.message_id, "Message with ID {} should have lower ID than middle message ({})", msg.message_id, middle_message.message_id ); } // Test 4: Create messages with identical timestamps but different IDs let db_conn = test.user_manager.db_connection(uid).unwrap(); let same_time_messages = vec![ ChatMessageTable { message_id: 4000, chat_id: chat_id.clone(), content: "Same time 1".to_string(), created_at: 1625098000, // Same timestamp author_type: 1, author_id: "user_1".to_string(), reply_message_id: None, metadata: None, is_sync: false, }, ChatMessageTable { message_id: 5000, chat_id: chat_id.clone(), content: "Same time 2".to_string(), created_at: 1625098000, // Same timestamp author_type: 1, author_id: "user_1".to_string(), reply_message_id: None, metadata: None, is_sync: false, }, ]; upsert_chat_messages(db_conn, &same_time_messages).unwrap(); // Fetch all messages let db_conn = test.user_manager.db_connection(uid).unwrap(); let result_all = select_chat_messages(db_conn, &chat_id, 10, MessageCursor::Offset(0)).unwrap(); assert_eq!(result_all.messages.len(), 5); // Find our two messages with identical timestamps let same_time_results: Vec<&ChatMessageTable> = result_all .messages .iter() .filter(|m| m.created_at == 1625098000) .collect(); assert_eq!( same_time_results.len(), 2, "Should find both messages with same timestamp" ); // Verify that message_id is used as a secondary sort key when timestamps are identical if same_time_results[0].message_id > same_time_results[1].message_id { // If we're in descending order (which we are), higher IDs should come first assert!( same_time_results[0].created_at >= same_time_results[1].created_at, "When timestamps are equal, messages should be ordered by message_id" ); } else { // This shouldn't happen with our current implementation panic!("Unexpected ordering of messages with identical timestamps"); } } #[tokio::test] async fn chat_message_cursor_order_consistency_test() { use_localhost_af_cloud().await; let test = EventIntegrationTest::new().await; test.sign_up_as_anon().await; let uid = test.user_manager.get_anon_user().await.unwrap().id; let db_conn = test.user_manager.db_connection(uid).unwrap(); let chat_id = Uuid::new_v4().to_string(); // Create 20 test messages with sequential timestamps let mut messages = Vec::new(); for i in 1..=20 { messages.push(ChatMessageTable { message_id: i * 100, chat_id: chat_id.clone(), content: format!("Message {}", i), created_at: 1625097600 + (i * 100), // Increasing timestamps with good separation author_type: 1, author_id: "user_1".to_string(), reply_message_id: None, metadata: None, is_sync: false, }); } // Insert messages upsert_chat_messages(db_conn, &messages).unwrap(); // Determine the ordering (ascending or descending) let db_conn = test.user_manager.db_connection(uid).unwrap(); let first_page = select_chat_messages(db_conn, &chat_id, 5, MessageCursor::Offset(0)).unwrap(); let is_descending = first_page.messages[0].created_at > first_page.messages[1].created_at; println!( "Message order is {}", if is_descending { "descending" } else { "ascending" } ); // Test forward pagination consistency // First, get page 1 with offset let first_with_offset = first_page; assert_eq!(first_with_offset.messages.len(), 5); // Then get page 2 with offset let db_conn = test.user_manager.db_connection(uid).unwrap(); let second_with_offset = select_chat_messages(db_conn, &chat_id, 5, MessageCursor::Offset(5)).unwrap(); assert!( !second_with_offset.messages.is_empty(), "Second page should have at least one message" ); // Now get page 2 using AfterMessageId based on the last message of page 1 let db_conn = test.user_manager.db_connection(uid).unwrap(); let last_msg_of_first_page = &first_with_offset.messages[4]; let second_with_after = select_chat_messages( db_conn, &chat_id, 5, MessageCursor::AfterMessageId(last_msg_of_first_page.message_id), ) .unwrap(); // The AfterMessageId cursor should return messages with IDs > last_msg_of_first_page.message_id assert!( !second_with_after.messages.is_empty(), "Second page with AfterMessageId should have at least one message" ); println!( "Offset-based page size: {}", second_with_offset.messages.len() ); println!( "Cursor-based page size: {}", second_with_after.messages.len() ); // Check that all returned messages have message_id > last_msg_of_first_page.message_id for msg in &second_with_after.messages { assert!( msg.message_id > last_msg_of_first_page.message_id, "Message ID {} should be greater than last message of first page ({})", msg.message_id, last_msg_of_first_page.message_id ); } // Test backward pagination // Get the third page with offset let db_conn = test.user_manager.db_connection(uid).unwrap(); let third_with_offset = select_chat_messages(db_conn, &chat_id, 5, MessageCursor::Offset(10)).unwrap(); assert!( !third_with_offset.messages.is_empty(), "Third page should have at least one message" ); // Now go back to the second page using BeforeMessageId based on the first message of third page let db_conn = test.user_manager.db_connection(uid).unwrap(); let first_msg_of_third_page = &third_with_offset.messages[0]; let second_with_before = select_chat_messages( db_conn, &chat_id, 5, MessageCursor::BeforeMessageId(first_msg_of_third_page.message_id), ) .unwrap(); assert!( !second_with_before.messages.is_empty(), "Second page with BeforeMessageId should have at least one message" ); println!( "Before-based page size: {}", second_with_before.messages.len() ); // Check that all returned messages have message_id < first_msg_of_third_page.message_id for msg in &second_with_before.messages { assert!( msg.message_id < first_msg_of_third_page.message_id, "Message ID {} should be less than first message of third page ({})", msg.message_id, first_msg_of_third_page.message_id ); } }