diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-11 08:47:08 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-11 08:47:08 +0000 |
| commit | 61d4796d84960ec9f25392635afceea3a3bd0916 (patch) | |
| tree | 98bd7c10d34decc2f7c96f50ddead8e8dc63f473 /tests/sync | |
| parent | ffcf8a7bc679f0aff9135063d343be3161b3b439 (diff) | |
refactor: move metrics tests to tests/sync/ structure (Phase 7)
Diffstat (limited to 'tests/sync')
| -rw-r--r-- | tests/sync/metrics.rs | 334 | ||||
| -rw-r--r-- | tests/sync/mod.rs | 4 |
2 files changed, 338 insertions, 0 deletions
diff --git a/tests/sync/metrics.rs b/tests/sync/metrics.rs new file mode 100644 index 0000000..dbb9dc0 --- /dev/null +++ b/tests/sync/metrics.rs | |||
| @@ -0,0 +1,334 @@ | |||
| 1 | //! Proactive Sync Metrics Tests | ||
| 2 | //! | ||
| 3 | //! Tests for Prometheus metrics integration with proactive sync: | ||
| 4 | //! - All sync metrics exposed at `/metrics` endpoint | ||
| 5 | //! - Connection metrics update correctly | ||
| 6 | //! - Health state metrics reflect actual state | ||
| 7 | //! - Gap events tracked correctly | ||
| 8 | //! - Load test with 3+ relays | ||
| 9 | //! | ||
| 10 | //! # Running Tests | ||
| 11 | //! | ||
| 12 | //! ```bash | ||
| 13 | //! cargo test --test sync metrics | ||
| 14 | //! cargo test --test sync metrics -- --nocapture | ||
| 15 | //! ``` | ||
| 16 | |||
| 17 | use std::time::Duration; | ||
| 18 | |||
| 19 | use nostr_sdk::prelude::*; | ||
| 20 | |||
| 21 | use crate::common::{sync_helpers::*, TestRelay}; | ||
| 22 | |||
| 23 | /// Test that sync metrics are exposed at /metrics endpoint | ||
| 24 | #[tokio::test] | ||
| 25 | async fn test_sync_metrics_exposed() { | ||
| 26 | let relay = TestRelay::start().await; | ||
| 27 | |||
| 28 | // Give time for relay to start | ||
| 29 | tokio::time::sleep(Duration::from_millis(500)).await; | ||
| 30 | |||
| 31 | // Fetch metrics using the shared helper | ||
| 32 | let metrics_result = fetch_metrics(&relay.url()).await; | ||
| 33 | |||
| 34 | relay.stop().await; | ||
| 35 | |||
| 36 | // Check that we got metrics (even if sync isn't configured) | ||
| 37 | let metrics = metrics_result.expect("Failed to fetch metrics"); | ||
| 38 | |||
| 39 | // Verify basic metrics structure exists | ||
| 40 | assert!( | ||
| 41 | metrics.contains("ngit_") || metrics.contains("# HELP"), | ||
| 42 | "Metrics endpoint should return Prometheus metrics" | ||
| 43 | ); | ||
| 44 | } | ||
| 45 | |||
| 46 | /// Test that sync metrics include expected metric names | ||
| 47 | #[tokio::test] | ||
| 48 | async fn test_sync_metric_names_present() { | ||
| 49 | // Start a relay with sync configured | ||
| 50 | let source_relay = TestRelay::start().await; | ||
| 51 | let sync_relay = TestRelay::start_with_sync(Some(source_relay.url().into())).await; | ||
| 52 | |||
| 53 | // Give time for sync connection to attempt | ||
| 54 | tokio::time::sleep(Duration::from_secs(2)).await; | ||
| 55 | |||
| 56 | // Fetch metrics from the syncing relay | ||
| 57 | let metrics = fetch_metrics(&sync_relay.url()) | ||
| 58 | .await | ||
| 59 | .expect("Failed to fetch metrics"); | ||
| 60 | |||
| 61 | sync_relay.stop().await; | ||
| 62 | source_relay.stop().await; | ||
| 63 | |||
| 64 | // Check for expected sync metric names (they may have zero values) | ||
| 65 | // At minimum, the ngit_ prefix metrics should be present | ||
| 66 | assert!( | ||
| 67 | metrics.contains("ngit_"), | ||
| 68 | "Metrics should include ngit_ prefixed metrics" | ||
| 69 | ); | ||
| 70 | } | ||
| 71 | |||
| 72 | /// Test connection metrics update correctly on successful connection | ||
| 73 | #[tokio::test] | ||
| 74 | async fn test_connection_metrics_on_success() { | ||
| 75 | // Start source relay | ||
| 76 | let source_relay = TestRelay::start().await; | ||
| 77 | tokio::time::sleep(Duration::from_millis(200)).await; | ||
| 78 | |||
| 79 | // Start syncing relay | ||
| 80 | let sync_relay = TestRelay::start_with_sync(Some(source_relay.url().into())).await; | ||
| 81 | |||
| 82 | // Wait for connection to establish | ||
| 83 | tokio::time::sleep(Duration::from_secs(2)).await; | ||
| 84 | |||
| 85 | // Fetch metrics - we can verify the relay started and metrics endpoint works | ||
| 86 | let metrics = fetch_metrics(&sync_relay.url()) | ||
| 87 | .await | ||
| 88 | .expect("Failed to fetch metrics"); | ||
| 89 | |||
| 90 | sync_relay.stop().await; | ||
| 91 | source_relay.stop().await; | ||
| 92 | |||
| 93 | // Verify metrics endpoint returned data | ||
| 94 | assert!(!metrics.is_empty(), "Metrics endpoint should return data"); | ||
| 95 | } | ||
| 96 | |||
| 97 | /// Test that events syncing updates metrics | ||
| 98 | #[tokio::test] | ||
| 99 | async fn test_event_sync_metrics() { | ||
| 100 | // Start source relay | ||
| 101 | let source_relay = TestRelay::start().await; | ||
| 102 | tokio::time::sleep(Duration::from_millis(200)).await; | ||
| 103 | |||
| 104 | // Start syncing relay | ||
| 105 | let sync_relay = TestRelay::start_with_sync(Some(source_relay.url().into())).await; | ||
| 106 | |||
| 107 | // Wait for connection | ||
| 108 | tokio::time::sleep(Duration::from_secs(1)).await; | ||
| 109 | |||
| 110 | // Create and submit an event to source relay | ||
| 111 | let keys = Keys::generate(); | ||
| 112 | let event = create_repo_announcement(&keys, &[&source_relay.domain()], "metrics-test-repo"); | ||
| 113 | |||
| 114 | let client = Client::default(); | ||
| 115 | client | ||
| 116 | .add_relay(source_relay.url()) | ||
| 117 | .await | ||
| 118 | .expect("Failed to add relay"); | ||
| 119 | client.connect().await; | ||
| 120 | |||
| 121 | let _ = client.send_event(&event).await; | ||
| 122 | |||
| 123 | // Wait for sync to occur | ||
| 124 | tokio::time::sleep(Duration::from_secs(2)).await; | ||
| 125 | |||
| 126 | // Fetch metrics from sync relay | ||
| 127 | let metrics = fetch_metrics(&sync_relay.url()) | ||
| 128 | .await | ||
| 129 | .expect("Failed to fetch metrics"); | ||
| 130 | |||
| 131 | client.disconnect().await; | ||
| 132 | sync_relay.stop().await; | ||
| 133 | source_relay.stop().await; | ||
| 134 | |||
| 135 | // Verify metrics endpoint returned data after sync activity | ||
| 136 | assert!( | ||
| 137 | !metrics.is_empty(), | ||
| 138 | "Metrics should be present after sync activity" | ||
| 139 | ); | ||
| 140 | } | ||
| 141 | |||
| 142 | /// Test health state tracking in metrics | ||
| 143 | #[tokio::test] | ||
| 144 | async fn test_health_state_metrics() { | ||
| 145 | // Start a syncing relay pointing to a non-existent source | ||
| 146 | // This will result in connection failures and health state changes | ||
| 147 | let sync_relay = TestRelay::start_with_sync(Some("ws://127.0.0.1:19999".into())).await; | ||
| 148 | |||
| 149 | // Wait for some connection attempts | ||
| 150 | tokio::time::sleep(Duration::from_secs(3)).await; | ||
| 151 | |||
| 152 | // Fetch metrics | ||
| 153 | let metrics = fetch_metrics(&sync_relay.url()) | ||
| 154 | .await | ||
| 155 | .expect("Failed to fetch metrics"); | ||
| 156 | |||
| 157 | sync_relay.stop().await; | ||
| 158 | |||
| 159 | // The relay should still be operational even with failed sync | ||
| 160 | assert!( | ||
| 161 | !metrics.is_empty(), | ||
| 162 | "Metrics should be present even with sync failures" | ||
| 163 | ); | ||
| 164 | } | ||
| 165 | |||
| 166 | /// Test gap event tracking (events received during catchup) | ||
| 167 | #[tokio::test] | ||
| 168 | async fn test_gap_event_tracking() { | ||
| 169 | // Start source relay and add some events first | ||
| 170 | let source_relay = TestRelay::start().await; | ||
| 171 | tokio::time::sleep(Duration::from_millis(200)).await; | ||
| 172 | |||
| 173 | let keys = Keys::generate(); | ||
| 174 | |||
| 175 | // Submit event before sync relay starts | ||
| 176 | let event = create_repo_announcement(&keys, &[&source_relay.domain()], "pre-existing-repo"); | ||
| 177 | |||
| 178 | let client = Client::default(); | ||
| 179 | client | ||
| 180 | .add_relay(source_relay.url()) | ||
| 181 | .await | ||
| 182 | .expect("Failed to add relay"); | ||
| 183 | client.connect().await; | ||
| 184 | let _ = client.send_event(&event).await; | ||
| 185 | |||
| 186 | // Now start syncing relay - it should catch up on existing events | ||
| 187 | let sync_relay = TestRelay::start_with_sync(Some(source_relay.url().into())).await; | ||
| 188 | |||
| 189 | // Wait for catchup | ||
| 190 | tokio::time::sleep(Duration::from_secs(3)).await; | ||
| 191 | |||
| 192 | // Fetch metrics | ||
| 193 | let metrics = fetch_metrics(&sync_relay.url()) | ||
| 194 | .await | ||
| 195 | .expect("Failed to fetch metrics"); | ||
| 196 | |||
| 197 | client.disconnect().await; | ||
| 198 | sync_relay.stop().await; | ||
| 199 | source_relay.stop().await; | ||
| 200 | |||
| 201 | // Verify metrics exist after gap sync scenario | ||
| 202 | assert!( | ||
| 203 | !metrics.is_empty(), | ||
| 204 | "Metrics should track gap sync activity" | ||
| 205 | ); | ||
| 206 | } | ||
| 207 | |||
| 208 | /// Load test with 3+ relays configured for sync | ||
| 209 | #[tokio::test] | ||
| 210 | async fn test_multi_relay_load() { | ||
| 211 | // Start 3 source relays | ||
| 212 | let source_relay_1 = TestRelay::start().await; | ||
| 213 | let source_relay_2 = TestRelay::start().await; | ||
| 214 | let source_relay_3 = TestRelay::start().await; | ||
| 215 | |||
| 216 | tokio::time::sleep(Duration::from_millis(500)).await; | ||
| 217 | |||
| 218 | // Start a syncing relay pointing to first source | ||
| 219 | // Note: The current implementation only supports single sync relay URL | ||
| 220 | // but the test demonstrates the system handles multiple relay scenarios | ||
| 221 | let sync_relay = TestRelay::start_with_sync(Some(source_relay_1.url().into())).await; | ||
| 222 | |||
| 223 | // Wait for connections | ||
| 224 | tokio::time::sleep(Duration::from_secs(2)).await; | ||
| 225 | |||
| 226 | // Submit events to all source relays | ||
| 227 | let keys = Keys::generate(); | ||
| 228 | |||
| 229 | let event1 = create_repo_announcement(&keys, &[&source_relay_1.domain()], "repo-1"); | ||
| 230 | let event2 = create_repo_announcement(&keys, &[&source_relay_2.domain()], "repo-2"); | ||
| 231 | let event3 = create_repo_announcement(&keys, &[&source_relay_3.domain()], "repo-3"); | ||
| 232 | |||
| 233 | // Submit events | ||
| 234 | let client1 = Client::default(); | ||
| 235 | client1 | ||
| 236 | .add_relay(source_relay_1.url()) | ||
| 237 | .await | ||
| 238 | .expect("Failed to add relay"); | ||
| 239 | client1.connect().await; | ||
| 240 | let _ = client1.send_event(&event1).await; | ||
| 241 | |||
| 242 | let client2 = Client::default(); | ||
| 243 | client2 | ||
| 244 | .add_relay(source_relay_2.url()) | ||
| 245 | .await | ||
| 246 | .expect("Failed to add relay"); | ||
| 247 | client2.connect().await; | ||
| 248 | let _ = client2.send_event(&event2).await; | ||
| 249 | |||
| 250 | let client3 = Client::default(); | ||
| 251 | client3 | ||
| 252 | .add_relay(source_relay_3.url()) | ||
| 253 | .await | ||
| 254 | .expect("Failed to add relay"); | ||
| 255 | client3.connect().await; | ||
| 256 | let _ = client3.send_event(&event3).await; | ||
| 257 | |||
| 258 | // Wait for sync | ||
| 259 | tokio::time::sleep(Duration::from_secs(3)).await; | ||
| 260 | |||
| 261 | // Fetch metrics from sync relay | ||
| 262 | let metrics = fetch_metrics(&sync_relay.url()) | ||
| 263 | .await | ||
| 264 | .expect("Failed to fetch metrics"); | ||
| 265 | |||
| 266 | // Cleanup | ||
| 267 | client1.disconnect().await; | ||
| 268 | client2.disconnect().await; | ||
| 269 | client3.disconnect().await; | ||
| 270 | sync_relay.stop().await; | ||
| 271 | source_relay_1.stop().await; | ||
| 272 | source_relay_2.stop().await; | ||
| 273 | source_relay_3.stop().await; | ||
| 274 | |||
| 275 | // Verify metrics system handled load | ||
| 276 | assert!( | ||
| 277 | !metrics.is_empty(), | ||
| 278 | "Metrics should be available under multi-relay load" | ||
| 279 | ); | ||
| 280 | } | ||
| 281 | |||
| 282 | /// Test that Prometheus text format is valid | ||
| 283 | #[tokio::test] | ||
| 284 | async fn test_prometheus_format_valid() { | ||
| 285 | let relay = TestRelay::start().await; | ||
| 286 | tokio::time::sleep(Duration::from_millis(500)).await; | ||
| 287 | |||
| 288 | let metrics = fetch_metrics(&relay.url()) | ||
| 289 | .await | ||
| 290 | .expect("Failed to fetch metrics"); | ||
| 291 | |||
| 292 | relay.stop().await; | ||
| 293 | |||
| 294 | // Check for valid Prometheus format markers | ||
| 295 | // - Lines starting with # are comments (HELP, TYPE) | ||
| 296 | // - Metric lines have format: metric_name{labels} value | ||
| 297 | let lines: Vec<&str> = metrics.lines().collect(); | ||
| 298 | |||
| 299 | // Should have some content | ||
| 300 | assert!(!lines.is_empty(), "Metrics should have content"); | ||
| 301 | |||
| 302 | // Check for at least some standard Prometheus patterns | ||
| 303 | let has_help = lines.iter().any(|l| l.starts_with("# HELP")); | ||
| 304 | let has_type = lines.iter().any(|l| l.starts_with("# TYPE")); | ||
| 305 | |||
| 306 | // At minimum we expect help/type comments for any registered metrics | ||
| 307 | assert!( | ||
| 308 | has_help || has_type || lines.iter().any(|l| l.contains("ngit_")), | ||
| 309 | "Metrics should contain Prometheus format elements" | ||
| 310 | ); | ||
| 311 | } | ||
| 312 | |||
| 313 | /// Test metrics endpoint availability during sync operations | ||
| 314 | #[tokio::test] | ||
| 315 | async fn test_metrics_availability_during_sync() { | ||
| 316 | let source_relay = TestRelay::start().await; | ||
| 317 | let sync_relay = TestRelay::start_with_sync(Some(source_relay.url().into())).await; | ||
| 318 | |||
| 319 | tokio::time::sleep(Duration::from_millis(500)).await; | ||
| 320 | |||
| 321 | // Make multiple metrics requests while sync is active | ||
| 322 | for i in 0..3 { | ||
| 323 | let metrics = fetch_metrics(&sync_relay.url()).await; | ||
| 324 | assert!( | ||
| 325 | metrics.is_ok(), | ||
| 326 | "Metrics request {} should succeed during sync", | ||
| 327 | i + 1 | ||
| 328 | ); | ||
| 329 | tokio::time::sleep(Duration::from_millis(200)).await; | ||
| 330 | } | ||
| 331 | |||
| 332 | sync_relay.stop().await; | ||
| 333 | source_relay.stop().await; | ||
| 334 | } \ No newline at end of file | ||
diff --git a/tests/sync/mod.rs b/tests/sync/mod.rs index b0da8b8..cf8f599 100644 --- a/tests/sync/mod.rs +++ b/tests/sync/mod.rs | |||
| @@ -8,6 +8,7 @@ | |||
| 8 | //! - Live sync (events sync in real-time after connection established) | 8 | //! - Live sync (events sync in real-time after connection established) |
| 9 | //! - Tag variations (testing different Layer 2/3 tag types: a/A/q, e/E/q) | 9 | //! - Tag variations (testing different Layer 2/3 tag types: a/A/q, e/E/q) |
| 10 | //! - Catchup sync (events from disconnected period sync on reconnect) | 10 | //! - Catchup sync (events from disconnected period sync on reconnect) |
| 11 | //! - Metrics (Prometheus metrics for sync operations) | ||
| 11 | //! | 12 | //! |
| 12 | //! # Test Files | 13 | //! # Test Files |
| 13 | //! | 14 | //! |
| @@ -16,6 +17,7 @@ | |||
| 16 | //! - `live_sync.rs` - Tests 5, 6, 7: real-time sync after connection | 17 | //! - `live_sync.rs` - Tests 5, 6, 7: real-time sync after connection |
| 17 | //! - `tag_variations.rs` - Tests 8, 9: Layer 2/3 tag type coverage | 18 | //! - `tag_variations.rs` - Tests 8, 9: Layer 2/3 tag type coverage |
| 18 | //! - `catchup.rs` - Test 0: catchup after disconnect (stub, `#[ignore]`) | 19 | //! - `catchup.rs` - Test 0: catchup after disconnect (stub, `#[ignore]`) |
| 20 | //! - `metrics.rs` - Prometheus metrics integration tests | ||
| 19 | //! | 21 | //! |
| 20 | //! # Shared Imports | 22 | //! # Shared Imports |
| 21 | //! | 23 | //! |
| @@ -23,10 +25,12 @@ | |||
| 23 | //! - `TestClient` - Client with retry logic | 25 | //! - `TestClient` - Client with retry logic |
| 24 | //! - Event builders for Layer 2/3 events | 26 | //! - Event builders for Layer 2/3 events |
| 25 | //! - `wait_for_event_on_relay()` - Non-panicking assertion helper | 27 | //! - `wait_for_event_on_relay()` - Non-panicking assertion helper |
| 28 | //! - `fetch_metrics()` - Prometheus metrics fetching | ||
| 26 | 29 | ||
| 27 | // Test modules | 30 | // Test modules |
| 28 | pub mod bootstrap; | 31 | pub mod bootstrap; |
| 29 | pub mod catchup; | 32 | pub mod catchup; |
| 30 | pub mod discovery; | 33 | pub mod discovery; |
| 31 | pub mod live_sync; | 34 | pub mod live_sync; |
| 35 | pub mod metrics; | ||
| 32 | pub mod tag_variations; \ No newline at end of file | 36 | pub mod tag_variations; \ No newline at end of file |