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