diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/sync.rs | 1 | ||||
| -rw-r--r-- | tests/sync/live_sync.rs | 528 |
2 files changed, 529 insertions, 0 deletions
diff --git a/tests/sync.rs b/tests/sync.rs index cedb876..81d1527 100644 --- a/tests/sync.rs +++ b/tests/sync.rs | |||
| @@ -26,4 +26,5 @@ mod common; | |||
| 26 | mod sync { | 26 | mod sync { |
| 27 | pub mod bootstrap; | 27 | pub mod bootstrap; |
| 28 | pub mod discovery; | 28 | pub mod discovery; |
| 29 | pub mod live_sync; | ||
| 29 | } \ No newline at end of file | 30 | } \ No newline at end of file |
diff --git a/tests/sync/live_sync.rs b/tests/sync/live_sync.rs new file mode 100644 index 0000000..3432687 --- /dev/null +++ b/tests/sync/live_sync.rs | |||
| @@ -0,0 +1,528 @@ | |||
| 1 | //! Live Sync Tests | ||
| 2 | //! | ||
| 3 | //! Tests for real-time event synchronization between relays. | ||
| 4 | //! These tests verify that events published to one relay are synced | ||
| 5 | //! to another relay in real-time via the discovery mechanism. | ||
| 6 | //! | ||
| 7 | //! # Tests | ||
| 8 | //! - Test 5: `test_live_sync_layer2_events` - Layer 2 (kind 1618) events sync in real-time | ||
| 9 | //! - Test 6: `test_live_sync_layer3_events` - Layer 3 (comments) sync when referencing Layer 2 | ||
| 10 | //! - Test 7: `test_live_sync_event_ordering` - Events arrive in chronological order | ||
| 11 | //! | ||
| 12 | //! # Sync Mechanism | ||
| 13 | //! These tests use the discovery-based sync pattern: | ||
| 14 | //! 1. Send announcement to both relays | ||
| 15 | //! 2. Each relay discovers the other from the announcement's relays tag | ||
| 16 | //! 3. Events sync between relays | ||
| 17 | //! | ||
| 18 | //! This tests "live" sync behavior - events syncing after connection is established, | ||
| 19 | //! as opposed to bootstrap sync which syncs existing events on startup. | ||
| 20 | |||
| 21 | use std::time::Duration; | ||
| 22 | |||
| 23 | use nostr_sdk::prelude::*; | ||
| 24 | |||
| 25 | use crate::common::{sync_helpers::*, TestRelay}; | ||
| 26 | |||
| 27 | /// Create a valid repository announcement event for testing sync. | ||
| 28 | /// | ||
| 29 | /// This creates a kind 30617 event with required clone and relays tags. | ||
| 30 | /// The event lists all provided domains so it will be accepted by each | ||
| 31 | /// relay's write policy. | ||
| 32 | /// | ||
| 33 | /// # Arguments | ||
| 34 | /// * `keys` - Keys for signing | ||
| 35 | /// * `domains` - Slice of domain strings (e.g., "127.0.0.1:8080") | ||
| 36 | /// * `identifier` - Repository identifier (d-tag) | ||
| 37 | fn create_repo_announcement(keys: &Keys, domains: &[&str], identifier: &str) -> Event { | ||
| 38 | // Build clone URLs for all domains (with .git suffix) | ||
| 39 | let clone_urls: Vec<String> = domains | ||
| 40 | .iter() | ||
| 41 | .map(|d| format!("http://{}/{}.git", d, identifier)) | ||
| 42 | .collect(); | ||
| 43 | |||
| 44 | // Build relay URLs for all domains | ||
| 45 | let relay_urls: Vec<String> = domains.iter().map(|d| format!("ws://{}", d)).collect(); | ||
| 46 | |||
| 47 | // Build tags for repository announcement | ||
| 48 | let tags = vec![ | ||
| 49 | Tag::identifier(identifier), | ||
| 50 | Tag::custom(TagKind::custom("clone"), clone_urls), | ||
| 51 | Tag::custom(TagKind::custom("relays"), relay_urls), | ||
| 52 | ]; | ||
| 53 | |||
| 54 | EventBuilder::new(Kind::Custom(KIND_REPOSITORY_STATE), "Repository state") | ||
| 55 | .tags(tags) | ||
| 56 | .sign_with_keys(keys) | ||
| 57 | .expect("Failed to sign repo announcement") | ||
| 58 | } | ||
| 59 | |||
| 60 | /// Test 5: Live sync Layer 2 events | ||
| 61 | /// | ||
| 62 | /// Verifies that Layer 2 events (kind 1618 issues) published to one relay | ||
| 63 | /// are synced to another relay in real-time via discovery. | ||
| 64 | /// | ||
| 65 | /// Flow: | ||
| 66 | /// 1. Start relay_a (source) | ||
| 67 | /// 2. Start relay_b (with sync enabled, no bootstrap) | ||
| 68 | /// 3. Send announcement to both relays (triggers discovery) | ||
| 69 | /// 4. Publish Layer 2 issue to relay_a | ||
| 70 | /// 5. Verify event syncs to relay_b within 5 seconds | ||
| 71 | #[tokio::test] | ||
| 72 | async fn test_live_sync_layer2_events() { | ||
| 73 | // 1. Start source relay (relay_a) | ||
| 74 | let relay_a = TestRelay::start().await; | ||
| 75 | println!( | ||
| 76 | "relay_a started at {} (domain: {})", | ||
| 77 | relay_a.url(), | ||
| 78 | relay_a.domain() | ||
| 79 | ); | ||
| 80 | |||
| 81 | // 2. Start relay_b with sync enabled (no bootstrap - sync via discovery) | ||
| 82 | let relay_b = TestRelay::start_with_sync(None).await; | ||
| 83 | println!( | ||
| 84 | "relay_b started at {} (domain: {})", | ||
| 85 | relay_b.url(), | ||
| 86 | relay_b.domain() | ||
| 87 | ); | ||
| 88 | |||
| 89 | // 3. Create test keys | ||
| 90 | let keys = Keys::generate(); | ||
| 91 | |||
| 92 | // 4. Create a repository announcement that lists BOTH relays | ||
| 93 | let repo_id = "test-repo-live-l2"; | ||
| 94 | let announcement = create_repo_announcement( | ||
| 95 | &keys, | ||
| 96 | &[&relay_a.domain(), &relay_b.domain()], | ||
| 97 | repo_id, | ||
| 98 | ); | ||
| 99 | |||
| 100 | println!( | ||
| 101 | "Created announcement {} (kind {})", | ||
| 102 | announcement.id, | ||
| 103 | announcement.kind.as_u16() | ||
| 104 | ); | ||
| 105 | |||
| 106 | // 5. Send announcement to relay_a | ||
| 107 | let client_a = TestClient::new(relay_a.url(), keys.clone()) | ||
| 108 | .await | ||
| 109 | .expect("Failed to connect to relay_a"); | ||
| 110 | |||
| 111 | client_a | ||
| 112 | .send_event(&announcement) | ||
| 113 | .await | ||
| 114 | .expect("Failed to send announcement to relay_a"); | ||
| 115 | println!("Announcement sent to relay_a"); | ||
| 116 | |||
| 117 | // 6. Send announcement to relay_b (triggers discovery of relay_a) | ||
| 118 | let client_b = TestClient::new(relay_b.url(), keys.clone()) | ||
| 119 | .await | ||
| 120 | .expect("Failed to connect to relay_b"); | ||
| 121 | |||
| 122 | client_b | ||
| 123 | .send_event(&announcement) | ||
| 124 | .await | ||
| 125 | .expect("Failed to send announcement to relay_b"); | ||
| 126 | println!("Announcement sent to relay_b (triggers discovery)"); | ||
| 127 | |||
| 128 | // 7. Wait for discovery to complete | ||
| 129 | tokio::time::sleep(Duration::from_secs(1)).await; | ||
| 130 | |||
| 131 | // 8. Create and send a Layer 2 issue event (using helper) | ||
| 132 | let repo_coordinate = repo_coord(&keys, repo_id); | ||
| 133 | let issue = build_layer2_issue_event(&keys, &repo_coordinate, "Test Issue for Live Sync") | ||
| 134 | .expect("Failed to create issue event"); | ||
| 135 | let issue_id = issue.id; | ||
| 136 | |||
| 137 | println!("Created issue {} (kind {})", issue_id, issue.kind.as_u16()); | ||
| 138 | for tag in issue.tags.iter() { | ||
| 139 | println!(" Tag: {:?}", tag.as_slice()); | ||
| 140 | } | ||
| 141 | |||
| 142 | // Send issue to relay_a only | ||
| 143 | client_a | ||
| 144 | .send_event(&issue) | ||
| 145 | .await | ||
| 146 | .expect("Failed to send issue to relay_a"); | ||
| 147 | println!("Issue sent to relay_a"); | ||
| 148 | |||
| 149 | client_a.disconnect().await; | ||
| 150 | client_b.disconnect().await; | ||
| 151 | |||
| 152 | // 9. Wait and verify event syncs to relay_b | ||
| 153 | let filter = Filter::new() | ||
| 154 | .kind(Kind::Custom(KIND_ISSUE)) | ||
| 155 | .author(keys.public_key()) | ||
| 156 | .id(issue_id); | ||
| 157 | |||
| 158 | let synced = wait_for_event_on_relay(relay_b.url(), filter, Duration::from_secs(5)).await; | ||
| 159 | |||
| 160 | println!("Issue {} synced to relay_b: {}", issue_id, synced); | ||
| 161 | |||
| 162 | // 10. Cleanup | ||
| 163 | relay_b.stop().await; | ||
| 164 | relay_a.stop().await; | ||
| 165 | |||
| 166 | assert!( | ||
| 167 | synced, | ||
| 168 | "Layer 2 issue {} should have synced from relay_a to relay_b in real-time", | ||
| 169 | issue_id | ||
| 170 | ); | ||
| 171 | } | ||
| 172 | |||
| 173 | /// Test 6: Live sync Layer 3 events | ||
| 174 | /// | ||
| 175 | /// Verifies that Layer 3 events (comments) sync when they reference Layer 2 events. | ||
| 176 | /// | ||
| 177 | /// Flow: | ||
| 178 | /// 1. Start relay_a and relay_b (with sync enabled) | ||
| 179 | /// 2. Send announcement to both relays (triggers discovery) | ||
| 180 | /// 3. Publish Layer 2 issue to relay_a | ||
| 181 | /// 4. Wait for Layer 2 issue to sync to relay_b | ||
| 182 | /// 5. Publish Layer 3 comment (referencing the issue) to relay_a | ||
| 183 | /// 6. Verify comment syncs to relay_b within 5 seconds | ||
| 184 | /// 7. Verify comment has correct 'E' tag reference | ||
| 185 | /// | ||
| 186 | /// # Note | ||
| 187 | /// This test is currently ignored because Layer 3 (comment) sync is tracked separately | ||
| 188 | /// and may not be fully implemented yet. See discovery.rs for context: | ||
| 189 | /// > "Note: Layer 3 (comments on issues) sync is tracked separately and may | ||
| 190 | /// > be implemented in future phases." | ||
| 191 | /// | ||
| 192 | /// TODO: Enable this test when Layer 3 sync is implemented. | ||
| 193 | #[tokio::test] | ||
| 194 | #[ignore = "Layer 3 sync not yet implemented - comments don't sync via discovery"] | ||
| 195 | async fn test_live_sync_layer3_events() { | ||
| 196 | // 1. Start relays | ||
| 197 | let relay_a = TestRelay::start().await; | ||
| 198 | println!( | ||
| 199 | "relay_a started at {} (domain: {})", | ||
| 200 | relay_a.url(), | ||
| 201 | relay_a.domain() | ||
| 202 | ); | ||
| 203 | |||
| 204 | let relay_b = TestRelay::start_with_sync(None).await; | ||
| 205 | println!( | ||
| 206 | "relay_b started at {} (domain: {})", | ||
| 207 | relay_b.url(), | ||
| 208 | relay_b.domain() | ||
| 209 | ); | ||
| 210 | |||
| 211 | let keys = Keys::generate(); | ||
| 212 | |||
| 213 | // 2. Create and send repository announcement to both relays | ||
| 214 | let repo_id = "test-repo-live-l3"; | ||
| 215 | let announcement = create_repo_announcement( | ||
| 216 | &keys, | ||
| 217 | &[&relay_a.domain(), &relay_b.domain()], | ||
| 218 | repo_id, | ||
| 219 | ); | ||
| 220 | |||
| 221 | let client_a = TestClient::new(relay_a.url(), keys.clone()) | ||
| 222 | .await | ||
| 223 | .expect("Failed to connect to relay_a"); | ||
| 224 | |||
| 225 | let client_b = TestClient::new(relay_b.url(), keys.clone()) | ||
| 226 | .await | ||
| 227 | .expect("Failed to connect to relay_b"); | ||
| 228 | |||
| 229 | client_a | ||
| 230 | .send_event(&announcement) | ||
| 231 | .await | ||
| 232 | .expect("Failed to send announcement to relay_a"); | ||
| 233 | println!("Announcement sent to relay_a"); | ||
| 234 | |||
| 235 | client_b | ||
| 236 | .send_event(&announcement) | ||
| 237 | .await | ||
| 238 | .expect("Failed to send announcement to relay_b"); | ||
| 239 | println!("Announcement sent to relay_b (triggers discovery)"); | ||
| 240 | |||
| 241 | // 3. Wait for discovery | ||
| 242 | tokio::time::sleep(Duration::from_secs(1)).await; | ||
| 243 | |||
| 244 | // 4. Create and send Layer 2 issue | ||
| 245 | let repo_coordinate = repo_coord(&keys, repo_id); | ||
| 246 | let issue = build_layer2_issue_event(&keys, &repo_coordinate, "Parent Issue for Comment Test") | ||
| 247 | .expect("Failed to create issue"); | ||
| 248 | let issue_id = issue.id; | ||
| 249 | |||
| 250 | client_a | ||
| 251 | .send_event(&issue) | ||
| 252 | .await | ||
| 253 | .expect("Failed to send issue"); | ||
| 254 | println!("Layer 2 issue {} sent to relay_a", issue_id); | ||
| 255 | |||
| 256 | // 5. Wait for issue to sync to relay_b | ||
| 257 | tokio::time::sleep(Duration::from_secs(2)).await; | ||
| 258 | |||
| 259 | let issue_filter = Filter::new() | ||
| 260 | .kind(Kind::Custom(KIND_ISSUE)) | ||
| 261 | .id(issue_id); | ||
| 262 | let issue_synced = wait_for_event_on_relay(relay_b.url(), issue_filter, Duration::from_secs(3)).await; | ||
| 263 | println!("Issue synced to relay_b: {}", issue_synced); | ||
| 264 | |||
| 265 | // 6. Create and send Layer 3 comment (kind 1111, uppercase E tag) | ||
| 266 | let comment = build_layer3_comment_with_uppercase_e_tag( | ||
| 267 | &keys, | ||
| 268 | &issue_id, | ||
| 269 | "This is a comment on the issue", | ||
| 270 | ) | ||
| 271 | .expect("Failed to create comment"); | ||
| 272 | let comment_id = comment.id; | ||
| 273 | |||
| 274 | println!("Created comment {} (kind {})", comment_id, comment.kind.as_u16()); | ||
| 275 | for tag in comment.tags.iter() { | ||
| 276 | println!(" Tag: {:?}", tag.as_slice()); | ||
| 277 | } | ||
| 278 | |||
| 279 | client_a | ||
| 280 | .send_event(&comment) | ||
| 281 | .await | ||
| 282 | .expect("Failed to send comment"); | ||
| 283 | println!("Layer 3 comment {} sent to relay_a", comment_id); | ||
| 284 | |||
| 285 | client_a.disconnect().await; | ||
| 286 | client_b.disconnect().await; | ||
| 287 | |||
| 288 | // 7. Wait and verify comment syncs to relay_b | ||
| 289 | let comment_filter = Filter::new() | ||
| 290 | .kind(Kind::Custom(KIND_COMMENT)) | ||
| 291 | .author(keys.public_key()) | ||
| 292 | .id(comment_id); | ||
| 293 | |||
| 294 | let comment_synced = wait_for_event_on_relay(relay_b.url(), comment_filter, Duration::from_secs(5)).await; | ||
| 295 | println!("Comment {} synced to relay_b: {}", comment_id, comment_synced); | ||
| 296 | |||
| 297 | // 8. Verify the comment has correct 'E' tag reference | ||
| 298 | let mut has_correct_ref = false; | ||
| 299 | if comment_synced { | ||
| 300 | let temp_keys = Keys::generate(); | ||
| 301 | let client = Client::new(temp_keys); | ||
| 302 | if client.add_relay(relay_b.url()).await.is_ok() { | ||
| 303 | client.connect().await; | ||
| 304 | tokio::time::sleep(Duration::from_millis(500)).await; | ||
| 305 | |||
| 306 | let fetch_filter = Filter::new() | ||
| 307 | .kind(Kind::Custom(KIND_COMMENT)) | ||
| 308 | .id(comment_id); | ||
| 309 | |||
| 310 | if let Ok(events) = client.fetch_events(fetch_filter, Duration::from_secs(2)).await { | ||
| 311 | if let Some(event) = events.first() { | ||
| 312 | // Check for 'E' tag with parent event ID | ||
| 313 | for tag in event.tags.iter() { | ||
| 314 | let slice = tag.as_slice(); | ||
| 315 | if slice.first() == Some(&"E".to_string()) | ||
| 316 | && slice.get(1) == Some(&issue_id.to_hex()) | ||
| 317 | { | ||
| 318 | has_correct_ref = true; | ||
| 319 | println!("Found correct E tag reference to issue"); | ||
| 320 | break; | ||
| 321 | } | ||
| 322 | } | ||
| 323 | } | ||
| 324 | } | ||
| 325 | client.disconnect().await; | ||
| 326 | } | ||
| 327 | } | ||
| 328 | |||
| 329 | // 9. Cleanup | ||
| 330 | relay_b.stop().await; | ||
| 331 | relay_a.stop().await; | ||
| 332 | |||
| 333 | assert!( | ||
| 334 | issue_synced, | ||
| 335 | "Layer 2 issue {} should have synced first", | ||
| 336 | issue_id | ||
| 337 | ); | ||
| 338 | assert!( | ||
| 339 | comment_synced, | ||
| 340 | "Layer 3 comment {} should have synced to relay_b", | ||
| 341 | comment_id | ||
| 342 | ); | ||
| 343 | assert!( | ||
| 344 | has_correct_ref, | ||
| 345 | "Comment should have 'E' tag referencing issue {}", | ||
| 346 | issue_id | ||
| 347 | ); | ||
| 348 | } | ||
| 349 | |||
| 350 | /// Test 7: Live sync event ordering | ||
| 351 | /// | ||
| 352 | /// Verifies that events arrive in chronological order when synced. | ||
| 353 | /// Note: We test ordering based on created_at timestamps, allowing for | ||
| 354 | /// minor timing variations inherent in async systems. | ||
| 355 | /// | ||
| 356 | /// Flow: | ||
| 357 | /// 1. Start relay_a and relay_b (with sync enabled) | ||
| 358 | /// 2. Send announcement to both relays (triggers discovery) | ||
| 359 | /// 3. Publish 3 Layer 2 events to relay_a with 100ms delays between them | ||
| 360 | /// 4. Collect events from relay_b | ||
| 361 | /// 5. Verify events are ordered by created_at timestamp | ||
| 362 | #[tokio::test] | ||
| 363 | async fn test_live_sync_event_ordering() { | ||
| 364 | // 1. Start relays | ||
| 365 | let relay_a = TestRelay::start().await; | ||
| 366 | println!( | ||
| 367 | "relay_a started at {} (domain: {})", | ||
| 368 | relay_a.url(), | ||
| 369 | relay_a.domain() | ||
| 370 | ); | ||
| 371 | |||
| 372 | let relay_b = TestRelay::start_with_sync(None).await; | ||
| 373 | println!( | ||
| 374 | "relay_b started at {} (domain: {})", | ||
| 375 | relay_b.url(), | ||
| 376 | relay_b.domain() | ||
| 377 | ); | ||
| 378 | |||
| 379 | let keys = Keys::generate(); | ||
| 380 | |||
| 381 | // 2. Create and send repository announcement to both relays | ||
| 382 | let repo_id = "test-repo-ordering"; | ||
| 383 | let announcement = create_repo_announcement( | ||
| 384 | &keys, | ||
| 385 | &[&relay_a.domain(), &relay_b.domain()], | ||
| 386 | repo_id, | ||
| 387 | ); | ||
| 388 | |||
| 389 | let client_a = TestClient::new(relay_a.url(), keys.clone()) | ||
| 390 | .await | ||
| 391 | .expect("Failed to connect to relay_a"); | ||
| 392 | |||
| 393 | let client_b = TestClient::new(relay_b.url(), keys.clone()) | ||
| 394 | .await | ||
| 395 | .expect("Failed to connect to relay_b"); | ||
| 396 | |||
| 397 | client_a | ||
| 398 | .send_event(&announcement) | ||
| 399 | .await | ||
| 400 | .expect("Failed to send announcement to relay_a"); | ||
| 401 | |||
| 402 | client_b | ||
| 403 | .send_event(&announcement) | ||
| 404 | .await | ||
| 405 | .expect("Failed to send announcement to relay_b"); | ||
| 406 | println!("Announcements sent to both relays"); | ||
| 407 | |||
| 408 | // 3. Wait for discovery | ||
| 409 | tokio::time::sleep(Duration::from_secs(1)).await; | ||
| 410 | |||
| 411 | // 4. Create and send 3 issues with delays between them | ||
| 412 | let repo_coordinate = repo_coord(&keys, repo_id); | ||
| 413 | let mut issue_ids = Vec::new(); | ||
| 414 | let mut expected_order_timestamps = Vec::new(); | ||
| 415 | |||
| 416 | for i in 1..=3 { | ||
| 417 | let issue = build_layer2_issue_event( | ||
| 418 | &keys, | ||
| 419 | &repo_coordinate, | ||
| 420 | &format!("Ordering Test Issue {}", i), | ||
| 421 | ) | ||
| 422 | .expect("Failed to create issue"); | ||
| 423 | |||
| 424 | // Store the created_at timestamp for ordering verification | ||
| 425 | expected_order_timestamps.push(issue.created_at); | ||
| 426 | issue_ids.push(issue.id); | ||
| 427 | |||
| 428 | println!("Created issue {} at timestamp {}", issue.id, issue.created_at); | ||
| 429 | |||
| 430 | client_a | ||
| 431 | .send_event(&issue) | ||
| 432 | .await | ||
| 433 | .expect(&format!("Failed to send issue {}", i)); | ||
| 434 | |||
| 435 | // Delay between events to ensure different timestamps | ||
| 436 | tokio::time::sleep(Duration::from_millis(150)).await; | ||
| 437 | } | ||
| 438 | |||
| 439 | client_a.disconnect().await; | ||
| 440 | client_b.disconnect().await; | ||
| 441 | |||
| 442 | // 5. Wait for all events to sync | ||
| 443 | tokio::time::sleep(Duration::from_secs(3)).await; | ||
| 444 | |||
| 445 | // 6. Fetch all events from relay_b | ||
| 446 | let temp_keys = Keys::generate(); | ||
| 447 | let client = Client::new(temp_keys); | ||
| 448 | |||
| 449 | let events_found: Vec<Event>; | ||
| 450 | if client.add_relay(relay_b.url()).await.is_ok() { | ||
| 451 | client.connect().await; | ||
| 452 | tokio::time::sleep(Duration::from_millis(500)).await; | ||
| 453 | |||
| 454 | let filter = Filter::new() | ||
| 455 | .kind(Kind::Custom(KIND_ISSUE)) | ||
| 456 | .author(keys.public_key()); | ||
| 457 | |||
| 458 | match client.fetch_events(filter, Duration::from_secs(3)).await { | ||
| 459 | Ok(events) => { | ||
| 460 | events_found = events.into_iter().collect(); | ||
| 461 | } | ||
| 462 | Err(e) => { | ||
| 463 | println!("Failed to fetch events: {}", e); | ||
| 464 | events_found = Vec::new(); | ||
| 465 | } | ||
| 466 | } | ||
| 467 | client.disconnect().await; | ||
| 468 | } else { | ||
| 469 | events_found = Vec::new(); | ||
| 470 | } | ||
| 471 | |||
| 472 | // 7. Verify we got events | ||
| 473 | let found_count = events_found.len(); | ||
| 474 | println!("Found {} events on relay_b", found_count); | ||
| 475 | |||
| 476 | // Filter to only our test events (by ID) | ||
| 477 | let test_events: Vec<&Event> = events_found | ||
| 478 | .iter() | ||
| 479 | .filter(|e| issue_ids.contains(&e.id)) | ||
| 480 | .collect(); | ||
| 481 | |||
| 482 | println!("Found {} test events (out of {} total)", test_events.len(), events_found.len()); | ||
| 483 | |||
| 484 | // 8. Check ordering by created_at timestamp | ||
| 485 | let mut ordered_correctly = true; | ||
| 486 | if test_events.len() >= 2 { | ||
| 487 | // Sort by created_at and check order matches | ||
| 488 | let mut sorted_events = test_events.clone(); | ||
| 489 | sorted_events.sort_by_key(|e| e.created_at); | ||
| 490 | |||
| 491 | for (i, event) in sorted_events.iter().enumerate() { | ||
| 492 | println!( | ||
| 493 | "Event {} sorted: {} at timestamp {}", | ||
| 494 | i + 1, | ||
| 495 | event.id, | ||
| 496 | event.created_at | ||
| 497 | ); | ||
| 498 | } | ||
| 499 | |||
| 500 | // Verify ascending timestamp order | ||
| 501 | for window in sorted_events.windows(2) { | ||
| 502 | if window[0].created_at > window[1].created_at { | ||
| 503 | ordered_correctly = false; | ||
| 504 | println!( | ||
| 505 | "Order violation: {} ({}) > {} ({})", | ||
| 506 | window[0].id, window[0].created_at, | ||
| 507 | window[1].id, window[1].created_at | ||
| 508 | ); | ||
| 509 | } | ||
| 510 | } | ||
| 511 | } | ||
| 512 | |||
| 513 | // 9. Cleanup | ||
| 514 | relay_b.stop().await; | ||
| 515 | relay_a.stop().await; | ||
| 516 | |||
| 517 | // Assert based on what we found | ||
| 518 | // Note: We may not get all 3 events due to timing, but what we get should be ordered | ||
| 519 | assert!( | ||
| 520 | test_events.len() >= 2, | ||
| 521 | "Should have synced at least 2 of 3 events; found {}", | ||
| 522 | test_events.len() | ||
| 523 | ); | ||
| 524 | assert!( | ||
| 525 | ordered_correctly, | ||
| 526 | "Events should be ordered by created_at timestamp" | ||
| 527 | ); | ||
| 528 | } \ No newline at end of file | ||