From 61d4796d84960ec9f25392635afceea3a3bd0916 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Thu, 11 Dec 2025 08:47:08 +0000 Subject: refactor: move metrics tests to tests/sync/ structure (Phase 7) --- tests/sync/metrics.rs | 334 ++++++++++++++++++++++++++++++++++++++++++++++++++ tests/sync/mod.rs | 4 + 2 files changed, 338 insertions(+) create mode 100644 tests/sync/metrics.rs (limited to 'tests/sync') 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 @@ +//! Proactive Sync Metrics Tests +//! +//! Tests for Prometheus metrics integration with proactive sync: +//! - All sync metrics exposed at `/metrics` endpoint +//! - Connection metrics update correctly +//! - Health state metrics reflect actual state +//! - Gap events tracked correctly +//! - Load test with 3+ relays +//! +//! # Running Tests +//! +//! ```bash +//! cargo test --test sync metrics +//! cargo test --test sync metrics -- --nocapture +//! ``` + +use std::time::Duration; + +use nostr_sdk::prelude::*; + +use crate::common::{sync_helpers::*, TestRelay}; + +/// Test that sync metrics are exposed at /metrics endpoint +#[tokio::test] +async fn test_sync_metrics_exposed() { + let relay = TestRelay::start().await; + + // Give time for relay to start + tokio::time::sleep(Duration::from_millis(500)).await; + + // Fetch metrics using the shared helper + let metrics_result = fetch_metrics(&relay.url()).await; + + relay.stop().await; + + // Check that we got metrics (even if sync isn't configured) + let metrics = metrics_result.expect("Failed to fetch metrics"); + + // Verify basic metrics structure exists + assert!( + metrics.contains("ngit_") || metrics.contains("# HELP"), + "Metrics endpoint should return Prometheus metrics" + ); +} + +/// Test that sync metrics include expected metric names +#[tokio::test] +async fn test_sync_metric_names_present() { + // Start a relay with sync configured + let source_relay = TestRelay::start().await; + let sync_relay = TestRelay::start_with_sync(Some(source_relay.url().into())).await; + + // Give time for sync connection to attempt + tokio::time::sleep(Duration::from_secs(2)).await; + + // Fetch metrics from the syncing relay + let metrics = fetch_metrics(&sync_relay.url()) + .await + .expect("Failed to fetch metrics"); + + sync_relay.stop().await; + source_relay.stop().await; + + // Check for expected sync metric names (they may have zero values) + // At minimum, the ngit_ prefix metrics should be present + assert!( + metrics.contains("ngit_"), + "Metrics should include ngit_ prefixed metrics" + ); +} + +/// Test connection metrics update correctly on successful connection +#[tokio::test] +async fn test_connection_metrics_on_success() { + // Start source relay + let source_relay = TestRelay::start().await; + tokio::time::sleep(Duration::from_millis(200)).await; + + // Start syncing relay + let sync_relay = TestRelay::start_with_sync(Some(source_relay.url().into())).await; + + // Wait for connection to establish + tokio::time::sleep(Duration::from_secs(2)).await; + + // Fetch metrics - we can verify the relay started and metrics endpoint works + let metrics = fetch_metrics(&sync_relay.url()) + .await + .expect("Failed to fetch metrics"); + + sync_relay.stop().await; + source_relay.stop().await; + + // Verify metrics endpoint returned data + assert!(!metrics.is_empty(), "Metrics endpoint should return data"); +} + +/// Test that events syncing updates metrics +#[tokio::test] +async fn test_event_sync_metrics() { + // Start source relay + let source_relay = TestRelay::start().await; + tokio::time::sleep(Duration::from_millis(200)).await; + + // Start syncing relay + let sync_relay = TestRelay::start_with_sync(Some(source_relay.url().into())).await; + + // Wait for connection + tokio::time::sleep(Duration::from_secs(1)).await; + + // Create and submit an event to source relay + let keys = Keys::generate(); + let event = create_repo_announcement(&keys, &[&source_relay.domain()], "metrics-test-repo"); + + let client = Client::default(); + client + .add_relay(source_relay.url()) + .await + .expect("Failed to add relay"); + client.connect().await; + + let _ = client.send_event(&event).await; + + // Wait for sync to occur + tokio::time::sleep(Duration::from_secs(2)).await; + + // Fetch metrics from sync relay + let metrics = fetch_metrics(&sync_relay.url()) + .await + .expect("Failed to fetch metrics"); + + client.disconnect().await; + sync_relay.stop().await; + source_relay.stop().await; + + // Verify metrics endpoint returned data after sync activity + assert!( + !metrics.is_empty(), + "Metrics should be present after sync activity" + ); +} + +/// Test health state tracking in metrics +#[tokio::test] +async fn test_health_state_metrics() { + // Start a syncing relay pointing to a non-existent source + // This will result in connection failures and health state changes + let sync_relay = TestRelay::start_with_sync(Some("ws://127.0.0.1:19999".into())).await; + + // Wait for some connection attempts + tokio::time::sleep(Duration::from_secs(3)).await; + + // Fetch metrics + let metrics = fetch_metrics(&sync_relay.url()) + .await + .expect("Failed to fetch metrics"); + + sync_relay.stop().await; + + // The relay should still be operational even with failed sync + assert!( + !metrics.is_empty(), + "Metrics should be present even with sync failures" + ); +} + +/// Test gap event tracking (events received during catchup) +#[tokio::test] +async fn test_gap_event_tracking() { + // Start source relay and add some events first + let source_relay = TestRelay::start().await; + tokio::time::sleep(Duration::from_millis(200)).await; + + let keys = Keys::generate(); + + // Submit event before sync relay starts + let event = create_repo_announcement(&keys, &[&source_relay.domain()], "pre-existing-repo"); + + let client = Client::default(); + client + .add_relay(source_relay.url()) + .await + .expect("Failed to add relay"); + client.connect().await; + let _ = client.send_event(&event).await; + + // Now start syncing relay - it should catch up on existing events + let sync_relay = TestRelay::start_with_sync(Some(source_relay.url().into())).await; + + // Wait for catchup + tokio::time::sleep(Duration::from_secs(3)).await; + + // Fetch metrics + let metrics = fetch_metrics(&sync_relay.url()) + .await + .expect("Failed to fetch metrics"); + + client.disconnect().await; + sync_relay.stop().await; + source_relay.stop().await; + + // Verify metrics exist after gap sync scenario + assert!( + !metrics.is_empty(), + "Metrics should track gap sync activity" + ); +} + +/// Load test with 3+ relays configured for sync +#[tokio::test] +async fn test_multi_relay_load() { + // Start 3 source relays + let source_relay_1 = TestRelay::start().await; + let source_relay_2 = TestRelay::start().await; + let source_relay_3 = TestRelay::start().await; + + tokio::time::sleep(Duration::from_millis(500)).await; + + // Start a syncing relay pointing to first source + // Note: The current implementation only supports single sync relay URL + // but the test demonstrates the system handles multiple relay scenarios + let sync_relay = TestRelay::start_with_sync(Some(source_relay_1.url().into())).await; + + // Wait for connections + tokio::time::sleep(Duration::from_secs(2)).await; + + // Submit events to all source relays + let keys = Keys::generate(); + + let event1 = create_repo_announcement(&keys, &[&source_relay_1.domain()], "repo-1"); + let event2 = create_repo_announcement(&keys, &[&source_relay_2.domain()], "repo-2"); + let event3 = create_repo_announcement(&keys, &[&source_relay_3.domain()], "repo-3"); + + // Submit events + let client1 = Client::default(); + client1 + .add_relay(source_relay_1.url()) + .await + .expect("Failed to add relay"); + client1.connect().await; + let _ = client1.send_event(&event1).await; + + let client2 = Client::default(); + client2 + .add_relay(source_relay_2.url()) + .await + .expect("Failed to add relay"); + client2.connect().await; + let _ = client2.send_event(&event2).await; + + let client3 = Client::default(); + client3 + .add_relay(source_relay_3.url()) + .await + .expect("Failed to add relay"); + client3.connect().await; + let _ = client3.send_event(&event3).await; + + // Wait for sync + tokio::time::sleep(Duration::from_secs(3)).await; + + // Fetch metrics from sync relay + let metrics = fetch_metrics(&sync_relay.url()) + .await + .expect("Failed to fetch metrics"); + + // Cleanup + client1.disconnect().await; + client2.disconnect().await; + client3.disconnect().await; + sync_relay.stop().await; + source_relay_1.stop().await; + source_relay_2.stop().await; + source_relay_3.stop().await; + + // Verify metrics system handled load + assert!( + !metrics.is_empty(), + "Metrics should be available under multi-relay load" + ); +} + +/// Test that Prometheus text format is valid +#[tokio::test] +async fn test_prometheus_format_valid() { + let relay = TestRelay::start().await; + tokio::time::sleep(Duration::from_millis(500)).await; + + let metrics = fetch_metrics(&relay.url()) + .await + .expect("Failed to fetch metrics"); + + relay.stop().await; + + // Check for valid Prometheus format markers + // - Lines starting with # are comments (HELP, TYPE) + // - Metric lines have format: metric_name{labels} value + let lines: Vec<&str> = metrics.lines().collect(); + + // Should have some content + assert!(!lines.is_empty(), "Metrics should have content"); + + // Check for at least some standard Prometheus patterns + let has_help = lines.iter().any(|l| l.starts_with("# HELP")); + let has_type = lines.iter().any(|l| l.starts_with("# TYPE")); + + // At minimum we expect help/type comments for any registered metrics + assert!( + has_help || has_type || lines.iter().any(|l| l.contains("ngit_")), + "Metrics should contain Prometheus format elements" + ); +} + +/// Test metrics endpoint availability during sync operations +#[tokio::test] +async fn test_metrics_availability_during_sync() { + let source_relay = TestRelay::start().await; + let sync_relay = TestRelay::start_with_sync(Some(source_relay.url().into())).await; + + tokio::time::sleep(Duration::from_millis(500)).await; + + // Make multiple metrics requests while sync is active + for i in 0..3 { + let metrics = fetch_metrics(&sync_relay.url()).await; + assert!( + metrics.is_ok(), + "Metrics request {} should succeed during sync", + i + 1 + ); + tokio::time::sleep(Duration::from_millis(200)).await; + } + + sync_relay.stop().await; + source_relay.stop().await; +} \ 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 @@ //! - Live sync (events sync in real-time after connection established) //! - Tag variations (testing different Layer 2/3 tag types: a/A/q, e/E/q) //! - Catchup sync (events from disconnected period sync on reconnect) +//! - Metrics (Prometheus metrics for sync operations) //! //! # Test Files //! @@ -16,6 +17,7 @@ //! - `live_sync.rs` - Tests 5, 6, 7: real-time sync after connection //! - `tag_variations.rs` - Tests 8, 9: Layer 2/3 tag type coverage //! - `catchup.rs` - Test 0: catchup after disconnect (stub, `#[ignore]`) +//! - `metrics.rs` - Prometheus metrics integration tests //! //! # Shared Imports //! @@ -23,10 +25,12 @@ //! - `TestClient` - Client with retry logic //! - Event builders for Layer 2/3 events //! - `wait_for_event_on_relay()` - Non-panicking assertion helper +//! - `fetch_metrics()` - Prometheus metrics fetching // Test modules pub mod bootstrap; pub mod catchup; pub mod discovery; pub mod live_sync; +pub mod metrics; pub mod tag_variations; \ No newline at end of file -- cgit v1.2.3