diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-11 16:53:03 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-11 16:53:03 +0000 |
| commit | 2a9160836bb87fdea3ae891563b0169c68d1c2ab (patch) | |
| tree | 583c890687beaf7f380fc0be131bdf17485f06fa | |
| parent | 52489d3b1a7d79e164b4cc901b53fd06c05ce1b1 (diff) | |
fix: resolve all fmt and clippy warnings
Main lib (src/):
- Add #[allow(dead_code)] for build_info field (stored to prevent Prometheus unregistration)
- Add #[allow(dead_code)] for first_seen field (reserved for future rate limiting)
- Replace .or_insert_with(RelaySyncNeeds::default) with .or_default()
- Replace manual div_ceil implementations with .div_ceil(100)
Test code (tests/):
- Replace .expect(&format!(...)) with .unwrap_or_else(|_| panic!(...))
- Remove needless borrows in fetch_metrics() calls
- Add #[allow(dead_code)] and #[allow(unused_imports)] to test helpers module
grasp-audit:
- Apply cargo fmt to fix formatting
37 files changed, 516 insertions, 334 deletions
| @@ -17,4 +17,4 @@ fn main() { | |||
| 17 | // Re-run if HEAD changes (new commits) | 17 | // Re-run if HEAD changes (new commits) |
| 18 | println!("cargo:rerun-if-changed=.git/HEAD"); | 18 | println!("cargo:rerun-if-changed=.git/HEAD"); |
| 19 | println!("cargo:rerun-if-changed=.git/refs/heads/"); | 19 | println!("cargo:rerun-if-changed=.git/refs/heads/"); |
| 20 | } \ No newline at end of file | 20 | } |
diff --git a/grasp-audit/src/client.rs b/grasp-audit/src/client.rs index 21c70be..259a317 100644 --- a/grasp-audit/src/client.rs +++ b/grasp-audit/src/client.rs | |||
| @@ -585,7 +585,9 @@ mod tests { | |||
| 585 | "Missing 'grasp-audit-test-event' tag" | 585 | "Missing 'grasp-audit-test-event' tag" |
| 586 | ); | 586 | ); |
| 587 | assert!( | 587 | assert!( |
| 588 | tag_contents.iter().any(|t| t.starts_with("audit-isolated-")), | 588 | tag_contents |
| 589 | .iter() | ||
| 590 | .any(|t| t.starts_with("audit-isolated-")), | ||
| 589 | "Missing 'audit-isolated-*' tag" | 591 | "Missing 'audit-isolated-*' tag" |
| 590 | ); | 592 | ); |
| 591 | assert!( | 593 | assert!( |
diff --git a/grasp-audit/src/fixtures.rs b/grasp-audit/src/fixtures.rs index 174f83d..a15bd79 100644 --- a/grasp-audit/src/fixtures.rs +++ b/grasp-audit/src/fixtures.rs | |||
| @@ -1973,11 +1973,11 @@ mod tests { | |||
| 1973 | 1973 | ||
| 1974 | #[test] | 1974 | #[test] |
| 1975 | fn test_context_mode_from_audit_mode() { | 1975 | fn test_context_mode_from_audit_mode() { |
| 1976 | assert_eq!(ContextMode::from(AuditMode::Isolated), ContextMode::Isolated); | ||
| 1977 | assert_eq!( | 1976 | assert_eq!( |
| 1978 | ContextMode::from(AuditMode::Shared), | 1977 | ContextMode::from(AuditMode::Isolated), |
| 1979 | ContextMode::Shared | 1978 | ContextMode::Isolated |
| 1980 | ); | 1979 | ); |
| 1980 | assert_eq!(ContextMode::from(AuditMode::Shared), ContextMode::Shared); | ||
| 1981 | } | 1981 | } |
| 1982 | 1982 | ||
| 1983 | #[test] | 1983 | #[test] |
diff --git a/grasp-audit/src/result.rs b/grasp-audit/src/result.rs index 0de16ae..f296633 100644 --- a/grasp-audit/src/result.rs +++ b/grasp-audit/src/result.rs | |||
| @@ -38,7 +38,9 @@ fn parse_spec_lines(spec_ref: &str) -> Vec<u32> { | |||
| 38 | if line_part.contains('-') { | 38 | if line_part.contains('-') { |
| 39 | let range_parts: Vec<&str> = line_part.split('-').collect(); | 39 | let range_parts: Vec<&str> = line_part.split('-').collect(); |
| 40 | if range_parts.len() == 2 { | 40 | if range_parts.len() == 2 { |
| 41 | if let (Ok(start), Ok(end)) = (range_parts[0].parse::<u32>(), range_parts[1].parse::<u32>()) { | 41 | if let (Ok(start), Ok(end)) = |
| 42 | (range_parts[0].parse::<u32>(), range_parts[1].parse::<u32>()) | ||
| 43 | { | ||
| 42 | return (start..=end).collect(); | 44 | return (start..=end).collect(); |
| 43 | } | 45 | } |
| 44 | } | 46 | } |
| @@ -162,10 +164,19 @@ impl AuditResult { | |||
| 162 | /// Print a detailed report aligned to GRASP-01 specification | 164 | /// Print a detailed report aligned to GRASP-01 specification |
| 163 | pub fn print_report(&self) { | 165 | pub fn print_report(&self) { |
| 164 | println!(); | 166 | println!(); |
| 165 | println!("{}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{}", BOLD, RESET); | 167 | println!( |
| 168 | "{}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{}", | ||
| 169 | BOLD, RESET | ||
| 170 | ); | ||
| 166 | println!("{}GRASP-01 Compliance Report{}", BOLD, RESET); | 171 | println!("{}GRASP-01 Compliance Report{}", BOLD, RESET); |
| 167 | println!("Source: github.com/nostr-protocol/grasp (commit: {})", GRASP_COMMIT_ID); | 172 | println!( |
| 168 | println!("{}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{}", BOLD, RESET); | 173 | "Source: github.com/nostr-protocol/grasp (commit: {})", |
| 174 | GRASP_COMMIT_ID | ||
| 175 | ); | ||
| 176 | println!( | ||
| 177 | "{}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{}", | ||
| 178 | BOLD, RESET | ||
| 179 | ); | ||
| 169 | 180 | ||
| 170 | // Build a map of spec line -> tests that cover it | 181 | // Build a map of spec line -> tests that cover it |
| 171 | let mut tests_by_line: BTreeMap<u32, Vec<&TestResult>> = BTreeMap::new(); | 182 | let mut tests_by_line: BTreeMap<u32, Vec<&TestResult>> = BTreeMap::new(); |
| @@ -185,7 +196,10 @@ impl AuditResult { | |||
| 185 | println!(); | 196 | println!(); |
| 186 | println!("{}{}## {}{}", CYAN, BOLD, section, RESET); | 197 | println!("{}{}## {}{}", CYAN, BOLD, section, RESET); |
| 187 | 198 | ||
| 188 | for req in GRASP_01_REQUIREMENTS.iter().filter(|r| r.section == section) { | 199 | for req in GRASP_01_REQUIREMENTS |
| 200 | .iter() | ||
| 201 | .filter(|r| r.section == section) | ||
| 202 | { | ||
| 189 | println!(); | 203 | println!(); |
| 190 | // Print spec requirement in blue | 204 | // Print spec requirement in blue |
| 191 | println!("{}📘 Line {}: {}{}", BLUE, req.line, req.text, RESET); | 205 | println!("{}📘 Line {}: {}{}", BLUE, req.line, req.text, RESET); |
| @@ -218,7 +232,10 @@ impl AuditResult { | |||
| 218 | } | 232 | } |
| 219 | 233 | ||
| 220 | println!(); | 234 | println!(); |
| 221 | println!("{}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{}", BOLD, RESET); | 235 | println!( |
| 236 | "{}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{}", | ||
| 237 | BOLD, RESET | ||
| 238 | ); | ||
| 222 | 239 | ||
| 223 | // Summary statistics | 240 | // Summary statistics |
| 224 | let passed = self.passed_count(); | 241 | let passed = self.passed_count(); |
| @@ -252,7 +269,10 @@ impl AuditResult { | |||
| 252 | "{}Test results: {}/{} tests passed ({:.1}%){}", | 269 | "{}Test results: {}/{} tests passed ({:.1}%){}", |
| 253 | summary_color, passed, total_tests, pass_rate, RESET | 270 | summary_color, passed, total_tests, pass_rate, RESET |
| 254 | ); | 271 | ); |
| 255 | println!("{}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{}", BOLD, RESET); | 272 | println!( |
| 273 | "{}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{}", | ||
| 274 | BOLD, RESET | ||
| 275 | ); | ||
| 256 | println!(); | 276 | println!(); |
| 257 | } | 277 | } |
| 258 | 278 | ||
| @@ -313,7 +333,10 @@ mod tests { | |||
| 313 | #[test] | 333 | #[test] |
| 314 | fn test_parse_spec_lines_range() { | 334 | fn test_parse_spec_lines_range() { |
| 315 | assert_eq!(parse_spec_lines("GRASP-01:nostr-relay:7-9"), vec![7, 8, 9]); | 335 | assert_eq!(parse_spec_lines("GRASP-01:nostr-relay:7-9"), vec![7, 8, 9]); |
| 316 | assert_eq!(parse_spec_lines("GRASP-01:cors:44-47"), vec![44, 45, 46, 47]); | 336 | assert_eq!( |
| 337 | parse_spec_lines("GRASP-01:cors:44-47"), | ||
| 338 | vec![44, 45, 46, 47] | ||
| 339 | ); | ||
| 317 | } | 340 | } |
| 318 | 341 | ||
| 319 | #[test] | 342 | #[test] |
| @@ -327,4 +350,4 @@ mod tests { | |||
| 327 | assert_eq!(parse_spec_lines("GRASP-01:invalid"), Vec::<u32>::new()); | 350 | assert_eq!(parse_spec_lines("GRASP-01:invalid"), Vec::<u32>::new()); |
| 328 | assert_eq!(parse_spec_lines("GRASP-01:test:abc"), Vec::<u32>::new()); | 351 | assert_eq!(parse_spec_lines("GRASP-01:test:abc"), Vec::<u32>::new()); |
| 329 | } | 352 | } |
| 330 | } \ No newline at end of file | 353 | } |
diff --git a/grasp-audit/src/specs/grasp01/mod.rs b/grasp-audit/src/specs/grasp01/mod.rs index ba27fef..fa05f35 100644 --- a/grasp-audit/src/specs/grasp01/mod.rs +++ b/grasp-audit/src/specs/grasp01/mod.rs | |||
| @@ -29,6 +29,6 @@ pub use nip11_document::Nip11DocumentTests; | |||
| 29 | pub use push_authorization::PushAuthorizationTests; | 29 | pub use push_authorization::PushAuthorizationTests; |
| 30 | pub use repository_creation::RepositoryCreationTests; | 30 | pub use repository_creation::RepositoryCreationTests; |
| 31 | pub use spec_requirements::{ | 31 | pub use spec_requirements::{ |
| 32 | get_requirement, get_requirements_for_section, get_sections, RequirementLevel, | 32 | get_requirement, get_requirements_for_section, get_sections, RequirementLevel, SpecRequirement, |
| 33 | SpecRequirement, GRASP_01_REQUIREMENTS, GRASP_COMMIT_ID, | 33 | GRASP_01_REQUIREMENTS, GRASP_COMMIT_ID, |
| 34 | }; | 34 | }; |
diff --git a/grasp-audit/src/specs/grasp01/nip01_smoke.rs b/grasp-audit/src/specs/grasp01/nip01_smoke.rs index 8a0a4d1..4dbcd3d 100644 --- a/grasp-audit/src/specs/grasp01/nip01_smoke.rs +++ b/grasp-audit/src/specs/grasp01/nip01_smoke.rs | |||
| @@ -163,23 +163,27 @@ impl Nip01SmokeTests { | |||
| 163 | /// Spec: NIP-01 CLOSE message | 163 | /// Spec: NIP-01 CLOSE message |
| 164 | /// Requirement: Relay MUST support CLOSE to end subscriptions | 164 | /// Requirement: Relay MUST support CLOSE to end subscriptions |
| 165 | pub async fn test_close_subscription(client: &AuditClient) -> TestResult { | 165 | pub async fn test_close_subscription(client: &AuditClient) -> TestResult { |
| 166 | TestResult::new("close_subscription", "GRASP-01:nostr-relay:7", "Can close subscriptions") | 166 | TestResult::new( |
| 167 | .run(|| async { | 167 | "close_subscription", |
| 168 | // For now, we just verify we can query events | 168 | "GRASP-01:nostr-relay:7", |
| 169 | // Full subscription management with CLOSE would require | 169 | "Can close subscriptions", |
| 170 | // lower-level WebSocket access | 170 | ) |
| 171 | .run(|| async { | ||
| 172 | // For now, we just verify we can query events | ||
| 173 | // Full subscription management with CLOSE would require | ||
| 174 | // lower-level WebSocket access | ||
| 171 | 175 | ||
| 172 | let filter = Filter::new().kind(Kind::TextNote).limit(1); | 176 | let filter = Filter::new().kind(Kind::TextNote).limit(1); |
| 173 | 177 | ||
| 174 | let _events = client | 178 | let _events = client |
| 175 | .subscribe(vec![filter], Some(std::time::Duration::from_secs(2))) | 179 | .subscribe(vec![filter], Some(std::time::Duration::from_secs(2))) |
| 176 | .await | 180 | .await |
| 177 | .map_err(|e| format!("Failed to subscribe: {}", e))?; | 181 | .map_err(|e| format!("Failed to subscribe: {}", e))?; |
| 178 | 182 | ||
| 179 | // If we got here, subscription worked | 183 | // If we got here, subscription worked |
| 180 | Ok(()) | 184 | Ok(()) |
| 181 | }) | 185 | }) |
| 182 | .await | 186 | .await |
| 183 | } | 187 | } |
| 184 | 188 | ||
| 185 | /// Test 5: Rejects events with invalid signatures | 189 | /// Test 5: Rejects events with invalid signatures |
diff --git a/grasp-audit/src/specs/grasp01/push_authorization.rs b/grasp-audit/src/specs/grasp01/push_authorization.rs index c06da0d..ec08032 100644 --- a/grasp-audit/src/specs/grasp01/push_authorization.rs +++ b/grasp-audit/src/specs/grasp01/push_authorization.rs | |||
| @@ -407,8 +407,12 @@ impl PushAuthorizationTests { | |||
| 407 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | 407 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { |
| 408 | Ok(r) => r, | 408 | Ok(r) => r, |
| 409 | Err(e) => { | 409 | Err(e) => { |
| 410 | return TestResult::new(test_name, "GRASP-01:git-http:30", "Push rejected without state event") | 410 | return TestResult::new( |
| 411 | .fail(format!("Failed to create repo: {}", e)) | 411 | test_name, |
| 412 | "GRASP-01:git-http:30", | ||
| 413 | "Push rejected without state event", | ||
| 414 | ) | ||
| 415 | .fail(format!("Failed to create repo: {}", e)) | ||
| 412 | } | 416 | } |
| 413 | }; | 417 | }; |
| 414 | 418 | ||
| @@ -427,8 +431,12 @@ impl PushAuthorizationTests { | |||
| 427 | let clone_path = match clone_repo(relay_domain, &npub, &repo_id) { | 431 | let clone_path = match clone_repo(relay_domain, &npub, &repo_id) { |
| 428 | Ok(p) => p, | 432 | Ok(p) => p, |
| 429 | Err(e) => { | 433 | Err(e) => { |
| 430 | return TestResult::new(test_name, "GRASP-01:git-http:30", "Push rejected without state event") | 434 | return TestResult::new( |
| 431 | .fail(&e) | 435 | test_name, |
| 436 | "GRASP-01:git-http:30", | ||
| 437 | "Push rejected without state event", | ||
| 438 | ) | ||
| 439 | .fail(&e) | ||
| 432 | } | 440 | } |
| 433 | }; | 441 | }; |
| 434 | let cleanup = || { | 442 | let cleanup = || { |
| @@ -437,8 +445,12 @@ impl PushAuthorizationTests { | |||
| 437 | 445 | ||
| 438 | if let Err(e) = create_commit(&clone_path, "Unauthorized commit") { | 446 | if let Err(e) = create_commit(&clone_path, "Unauthorized commit") { |
| 439 | cleanup(); | 447 | cleanup(); |
| 440 | return TestResult::new(test_name, "GRASP-01:git-http:30", "Push rejected without state event") | 448 | return TestResult::new( |
| 441 | .fail(&e); | 449 | test_name, |
| 450 | "GRASP-01:git-http:30", | ||
| 451 | "Push rejected without state event", | ||
| 452 | ) | ||
| 453 | .fail(&e); | ||
| 442 | } | 454 | } |
| 443 | 455 | ||
| 444 | // Do NOT publish state event - push should be rejected | 456 | // Do NOT publish state event - push should be rejected |
| @@ -446,14 +458,24 @@ impl PushAuthorizationTests { | |||
| 446 | cleanup(); | 458 | cleanup(); |
| 447 | 459 | ||
| 448 | match push_result { | 460 | match push_result { |
| 449 | Ok(false) => { | 461 | Ok(false) => TestResult::new( |
| 450 | TestResult::new(test_name, "GRASP-01:git-http:30", "Push rejected without state event").pass() | 462 | test_name, |
| 451 | } | 463 | "GRASP-01:git-http:30", |
| 452 | Ok(true) => TestResult::new(test_name, "GRASP-01:git-http:30", "Push rejected without state event") | 464 | "Push rejected without state event", |
| 453 | .fail("Push accepted but should be rejected"), | 465 | ) |
| 454 | Err(e) => { | 466 | .pass(), |
| 455 | TestResult::new(test_name, "GRASP-01:git-http:30", "Push rejected without state event").fail(&e) | 467 | Ok(true) => TestResult::new( |
| 456 | } | 468 | test_name, |
| 469 | "GRASP-01:git-http:30", | ||
| 470 | "Push rejected without state event", | ||
| 471 | ) | ||
| 472 | .fail("Push accepted but should be rejected"), | ||
| 473 | Err(e) => TestResult::new( | ||
| 474 | test_name, | ||
| 475 | "GRASP-01:git-http:30", | ||
| 476 | "Push rejected without state event", | ||
| 477 | ) | ||
| 478 | .fail(&e), | ||
| 457 | } | 479 | } |
| 458 | } | 480 | } |
| 459 | 481 | ||
| @@ -480,11 +502,18 @@ impl PushAuthorizationTests { | |||
| 480 | // The OwnerStateDataPushed fixture handles all stages: | 502 | // The OwnerStateDataPushed fixture handles all stages: |
| 481 | // Generate → Send → Verify → DataPush | 503 | // Generate → Send → Verify → DataPush |
| 482 | match ctx.get_fixture(FixtureKind::OwnerStateDataPushed).await { | 504 | match ctx.get_fixture(FixtureKind::OwnerStateDataPushed).await { |
| 483 | Ok(_state_event) => { | 505 | Ok(_state_event) => TestResult::new( |
| 484 | TestResult::new(test_name, "GRASP-01:git-http:30", "Push authorized with matching state").pass() | 506 | test_name, |
| 485 | } | 507 | "GRASP-01:git-http:30", |
| 486 | Err(e) => TestResult::new(test_name, "GRASP-01:git-http:30", "Push authorized with matching state") | 508 | "Push authorized with matching state", |
| 487 | .fail(format!("{}", e)), | 509 | ) |
| 510 | .pass(), | ||
| 511 | Err(e) => TestResult::new( | ||
| 512 | test_name, | ||
| 513 | "GRASP-01:git-http:30", | ||
| 514 | "Push authorized with matching state", | ||
| 515 | ) | ||
| 516 | .fail(format!("{}", e)), | ||
| 488 | } | 517 | } |
| 489 | } | 518 | } |
| 490 | 519 | ||
| @@ -868,8 +897,12 @@ impl PushAuthorizationTests { | |||
| 868 | // Send the rogue state event using the raw client to bypass AuditClient's key check | 897 | // Send the rogue state event using the raw client to bypass AuditClient's key check |
| 869 | if let Err(e) = client.client().send_event(&rogue_state).await { | 898 | if let Err(e) = client.client().send_event(&rogue_state).await { |
| 870 | cleanup(); | 899 | cleanup(); |
| 871 | return TestResult::new(test_name, "GRASP-01:git-http:30", "Non-maintainer state events ignored") | 900 | return TestResult::new( |
| 872 | .fail(format!("Failed to send rogue state event: {}", e)); | 901 | test_name, |
| 902 | "GRASP-01:git-http:30", | ||
| 903 | "Non-maintainer state events ignored", | ||
| 904 | ) | ||
| 905 | .fail(format!("Failed to send rogue state event: {}", e)); | ||
| 873 | } | 906 | } |
| 874 | 907 | ||
| 875 | // Wait for event to propagate | 908 | // Wait for event to propagate |
| @@ -1036,7 +1069,9 @@ impl PushAuthorizationTests { | |||
| 1036 | .await | 1069 | .await |
| 1037 | { | 1070 | { |
| 1038 | Ok(_pr_event) => TestResult::new(test_name, "GRASP-01:git-http:34", desc).pass(), | 1071 | Ok(_pr_event) => TestResult::new(test_name, "GRASP-01:git-http:34", desc).pass(), |
| 1039 | Err(e) => TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(format!("{}", e)), | 1072 | Err(e) => { |
| 1073 | TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(format!("{}", e)) | ||
| 1074 | } | ||
| 1040 | } | 1075 | } |
| 1041 | } | 1076 | } |
| 1042 | 1077 | ||
| @@ -1062,7 +1097,8 @@ impl PushAuthorizationTests { | |||
| 1062 | { | 1097 | { |
| 1063 | Ok(e) => e, | 1098 | Ok(e) => e, |
| 1064 | Err(e) => { | 1099 | Err(e) => { |
| 1065 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(format!("{}", e)); | 1100 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc) |
| 1101 | .fail(format!("{}", e)); | ||
| 1066 | } | 1102 | } |
| 1067 | }; | 1103 | }; |
| 1068 | 1104 | ||
| @@ -1072,7 +1108,8 @@ impl PushAuthorizationTests { | |||
| 1072 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | 1108 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { |
| 1073 | Ok(r) => r, | 1109 | Ok(r) => r, |
| 1074 | Err(e) => { | 1110 | Err(e) => { |
| 1075 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(format!("{}", e)); | 1111 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc) |
| 1112 | .fail(format!("{}", e)); | ||
| 1076 | } | 1113 | } |
| 1077 | }; | 1114 | }; |
| 1078 | 1115 | ||
| @@ -1146,7 +1183,8 @@ impl PushAuthorizationTests { | |||
| 1146 | { | 1183 | { |
| 1147 | Ok(e) => e, | 1184 | Ok(e) => e, |
| 1148 | Err(e) => { | 1185 | Err(e) => { |
| 1149 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(format!("{}", e)); | 1186 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc) |
| 1187 | .fail(format!("{}", e)); | ||
| 1150 | } | 1188 | } |
| 1151 | }; | 1189 | }; |
| 1152 | 1190 | ||
| @@ -1156,7 +1194,8 @@ impl PushAuthorizationTests { | |||
| 1156 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | 1194 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { |
| 1157 | Ok(r) => r, | 1195 | Ok(r) => r, |
| 1158 | Err(e) => { | 1196 | Err(e) => { |
| 1159 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(format!("{}", e)); | 1197 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc) |
| 1198 | .fail(format!("{}", e)); | ||
| 1160 | } | 1199 | } |
| 1161 | }; | 1200 | }; |
| 1162 | 1201 | ||
| @@ -1233,7 +1272,8 @@ impl PushAuthorizationTests { | |||
| 1233 | { | 1272 | { |
| 1234 | Ok(e) => e, | 1273 | Ok(e) => e, |
| 1235 | Err(e) => { | 1274 | Err(e) => { |
| 1236 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(format!("{}", e)); | 1275 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc) |
| 1276 | .fail(format!("{}", e)); | ||
| 1237 | } | 1277 | } |
| 1238 | }; | 1278 | }; |
| 1239 | 1279 | ||
| @@ -1243,7 +1283,8 @@ impl PushAuthorizationTests { | |||
| 1243 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | 1283 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { |
| 1244 | Ok(r) => r, | 1284 | Ok(r) => r, |
| 1245 | Err(e) => { | 1285 | Err(e) => { |
| 1246 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(format!("{}", e)); | 1286 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc) |
| 1287 | .fail(format!("{}", e)); | ||
| 1247 | } | 1288 | } |
| 1248 | }; | 1289 | }; |
| 1249 | 1290 | ||
diff --git a/src/config.rs b/src/config.rs index 8c6de05..7834a3f 100644 --- a/src/config.rs +++ b/src/config.rs | |||
| @@ -77,11 +77,19 @@ pub struct Config { | |||
| 77 | pub metrics_enabled: bool, | 77 | pub metrics_enabled: bool, |
| 78 | 78 | ||
| 79 | /// Connections per IP before flagging as potential abuse in metrics (display only, no rate limiting) | 79 | /// Connections per IP before flagging as potential abuse in metrics (display only, no rate limiting) |
| 80 | #[arg(long = "metrics-connection-per-ip-abuse-threshold", env = "NGIT_METRICS_CONNECTION_PER_IP_ABUSE_THRESHOLD", default_value_t = 10)] | 80 | #[arg( |
| 81 | long = "metrics-connection-per-ip-abuse-threshold", | ||
| 82 | env = "NGIT_METRICS_CONNECTION_PER_IP_ABUSE_THRESHOLD", | ||
| 83 | default_value_t = 10 | ||
| 84 | )] | ||
| 81 | pub metrics_connection_per_ip_abuse_threshold: u32, | 85 | pub metrics_connection_per_ip_abuse_threshold: u32, |
| 82 | 86 | ||
| 83 | /// Number of top bandwidth repos to track in metrics | 87 | /// Number of top bandwidth repos to track in metrics |
| 84 | #[arg(long = "metrics-top-n-repos", env = "NGIT_METRICS_TOP_N_REPOS", default_value_t = 10)] | 88 | #[arg( |
| 89 | long = "metrics-top-n-repos", | ||
| 90 | env = "NGIT_METRICS_TOP_N_REPOS", | ||
| 91 | default_value_t = 10 | ||
| 92 | )] | ||
| 85 | pub metrics_top_n_repos: usize, | 93 | pub metrics_top_n_repos: usize, |
| 86 | 94 | ||
| 87 | /// URL of bootstrap relay to sync from on startup (optional) | 95 | /// URL of bootstrap relay to sync from on startup (optional) |
| @@ -95,7 +103,11 @@ pub struct Config { | |||
| 95 | 103 | ||
| 96 | /// Interval in seconds for checking disconnected relays and attempting reconnection (default: 60) | 104 | /// Interval in seconds for checking disconnected relays and attempting reconnection (default: 60) |
| 97 | /// Set to lower value for faster reconnection testing | 105 | /// Set to lower value for faster reconnection testing |
| 98 | #[arg(long, env = "NGIT_SYNC_DISCONNECT_CHECK_INTERVAL_SECS", default_value_t = 60)] | 106 | #[arg( |
| 107 | long, | ||
| 108 | env = "NGIT_SYNC_DISCONNECT_CHECK_INTERVAL_SECS", | ||
| 109 | default_value_t = 60 | ||
| 110 | )] | ||
| 99 | pub sync_disconnect_check_interval_secs: u64, | 111 | pub sync_disconnect_check_interval_secs: u64, |
| 100 | 112 | ||
| 101 | /// Base backoff time in seconds for relay reconnection (default: 5) | 113 | /// Base backoff time in seconds for relay reconnection (default: 5) |
diff --git a/src/http/landing.rs b/src/http/landing.rs index 8ab4a68..5fc1e6e 100644 --- a/src/http/landing.rs +++ b/src/http/landing.rs | |||
| @@ -341,10 +341,7 @@ fn generate_hero_tags(nip11: &RelayInformationDocument) -> String { | |||
| 341 | 341 | ||
| 342 | // Add GRASP tags | 342 | // Add GRASP tags |
| 343 | for grasp in &nip11.supported_grasps { | 343 | for grasp in &nip11.supported_grasps { |
| 344 | html.push_str(&format!( | 344 | html.push_str(&format!(r#"<span class="tag tag-grasp">{}</span>"#, grasp)); |
| 345 | r#"<span class="tag tag-grasp">{}</span>"#, | ||
| 346 | grasp | ||
| 347 | )); | ||
| 348 | html.push('\n'); | 345 | html.push('\n'); |
| 349 | } | 346 | } |
| 350 | 347 | ||
diff --git a/src/http/mod.rs b/src/http/mod.rs index f584e03..91a6067 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs | |||
| @@ -509,7 +509,13 @@ pub async fn run_server( | |||
| 509 | loop { | 509 | loop { |
| 510 | let (socket, addr) = listener.accept().await?; | 510 | let (socket, addr) = listener.accept().await?; |
| 511 | let io = TokioIo::new(socket); | 511 | let io = TokioIo::new(socket); |
| 512 | let service = HttpService::new(relay.clone(), config.clone(), addr, database.clone(), metrics.clone()); | 512 | let service = HttpService::new( |
| 513 | relay.clone(), | ||
| 514 | config.clone(), | ||
| 515 | addr, | ||
| 516 | database.clone(), | ||
| 517 | metrics.clone(), | ||
| 518 | ); | ||
| 513 | 519 | ||
| 514 | tokio::spawn(async move { | 520 | tokio::spawn(async move { |
| 515 | if let Err(e) = http1::Builder::new() | 521 | if let Err(e) = http1::Builder::new() |
diff --git a/src/main.rs b/src/main.rs index 97a14eb..6d8b4dd 100644 --- a/src/main.rs +++ b/src/main.rs | |||
| @@ -37,7 +37,9 @@ async fn main() -> Result<()> { | |||
| 37 | // Initialize metrics if enabled | 37 | // Initialize metrics if enabled |
| 38 | let metrics = if config.metrics_enabled { | 38 | let metrics = if config.metrics_enabled { |
| 39 | info!("Metrics enabled on /metrics endpoint"); | 39 | info!("Metrics enabled on /metrics endpoint"); |
| 40 | Some(Arc::new(Metrics::new(config.metrics_connection_per_ip_abuse_threshold))) | 40 | Some(Arc::new(Metrics::new( |
| 41 | config.metrics_connection_per_ip_abuse_threshold, | ||
| 42 | ))) | ||
| 41 | } else { | 43 | } else { |
| 42 | info!("Metrics disabled"); | 44 | info!("Metrics disabled"); |
| 43 | None | 45 | None |
| @@ -65,7 +67,10 @@ async fn main() -> Result<()> { | |||
| 65 | ); | 67 | ); |
| 66 | 68 | ||
| 67 | if config.sync_bootstrap_relay_url.is_some() { | 69 | if config.sync_bootstrap_relay_url.is_some() { |
| 68 | info!("Starting proactive sync with bootstrap relay: {:?}", config.sync_bootstrap_relay_url); | 70 | info!( |
| 71 | "Starting proactive sync with bootstrap relay: {:?}", | ||
| 72 | config.sync_bootstrap_relay_url | ||
| 73 | ); | ||
| 69 | } else { | 74 | } else { |
| 70 | info!("Proactive sync enabled (will discover relays from stored announcements)"); | 75 | info!("Proactive sync enabled (will discover relays from stored announcements)"); |
| 71 | } | 76 | } |
diff --git a/src/metrics/bandwidth.rs b/src/metrics/bandwidth.rs index d2c53e8..d51af12 100644 --- a/src/metrics/bandwidth.rs +++ b/src/metrics/bandwidth.rs | |||
| @@ -80,7 +80,9 @@ impl BandwidthTracker { | |||
| 80 | &["repo"], | 80 | &["repo"], |
| 81 | ) | 81 | ) |
| 82 | .unwrap(); | 82 | .unwrap(); |
| 83 | registry.register(Box::new(top_repos_gauge.clone())).unwrap(); | 83 | registry |
| 84 | .register(Box::new(top_repos_gauge.clone())) | ||
| 85 | .unwrap(); | ||
| 84 | 86 | ||
| 85 | Self { | 87 | Self { |
| 86 | all_repos: DashMap::new(), | 88 | all_repos: DashMap::new(), |
| @@ -120,7 +122,12 @@ impl BandwidthTracker { | |||
| 120 | // Try to update the timestamp atomically to prevent concurrent refreshes | 122 | // Try to update the timestamp atomically to prevent concurrent refreshes |
| 121 | if self | 123 | if self |
| 122 | .last_refresh_nanos | 124 | .last_refresh_nanos |
| 123 | .compare_exchange(last_refresh, elapsed_nanos, Ordering::SeqCst, Ordering::Relaxed) | 125 | .compare_exchange( |
| 126 | last_refresh, | ||
| 127 | elapsed_nanos, | ||
| 128 | Ordering::SeqCst, | ||
| 129 | Ordering::Relaxed, | ||
| 130 | ) | ||
| 124 | .is_ok() | 131 | .is_ok() |
| 125 | { | 132 | { |
| 126 | self.refresh_top_n(); | 133 | self.refresh_top_n(); |
| @@ -298,4 +305,4 @@ mod tests { | |||
| 298 | // Refresh should not panic on empty data | 305 | // Refresh should not panic on empty data |
| 299 | tracker.refresh_top_n(); | 306 | tracker.refresh_top_n(); |
| 300 | } | 307 | } |
| 301 | } \ No newline at end of file | 308 | } |
diff --git a/src/metrics/connection.rs b/src/metrics/connection.rs index 6a7f406..2d42081 100644 --- a/src/metrics/connection.rs +++ b/src/metrics/connection.rs | |||
| @@ -25,7 +25,8 @@ use tracing::warn; | |||
| 25 | struct ConnectionInfo { | 25 | struct ConnectionInfo { |
| 26 | /// Number of active connections from this IP | 26 | /// Number of active connections from this IP |
| 27 | count: u32, | 27 | count: u32, |
| 28 | /// When the first connection from this IP was established | 28 | /// When the first connection from this IP was established (for future rate limiting) |
| 29 | #[allow(dead_code)] | ||
| 29 | first_seen: Instant, | 30 | first_seen: Instant, |
| 30 | /// Whether this IP has been flagged as potentially abusive | 31 | /// Whether this IP has been flagged as potentially abusive |
| 31 | flagged_as_abuse: bool, | 32 | flagged_as_abuse: bool, |
| @@ -48,16 +49,16 @@ struct ConnectionInfo { | |||
| 48 | pub struct ConnectionTracker { | 49 | pub struct ConnectionTracker { |
| 49 | /// Active connections per IP (INTERNAL ONLY - never exposed to metrics) | 50 | /// Active connections per IP (INTERNAL ONLY - never exposed to metrics) |
| 50 | connections: DashMap<IpAddr, ConnectionInfo>, | 51 | connections: DashMap<IpAddr, ConnectionInfo>, |
| 51 | 52 | ||
| 52 | /// Threshold for abuse flagging (connections per IP) | 53 | /// Threshold for abuse flagging (connections per IP) |
| 53 | abuse_threshold: u32, | 54 | abuse_threshold: u32, |
| 54 | 55 | ||
| 55 | /// Prometheus gauge: total active connections | 56 | /// Prometheus gauge: total active connections |
| 56 | active_connections: IntGauge, | 57 | active_connections: IntGauge, |
| 57 | 58 | ||
| 58 | /// Prometheus gauge: number of unique IPs connected | 59 | /// Prometheus gauge: number of unique IPs connected |
| 59 | unique_ips: IntGauge, | 60 | unique_ips: IntGauge, |
| 60 | 61 | ||
| 61 | /// Prometheus gauge: number of IPs flagged as potential abusers | 62 | /// Prometheus gauge: number of IPs flagged as potential abusers |
| 62 | flagged_abusers: IntGauge, | 63 | flagged_abusers: IntGauge, |
| 63 | } | 64 | } |
| @@ -70,29 +71,30 @@ impl ConnectionTracker { | |||
| 70 | /// * `abuse_threshold` - Number of connections from a single IP before flagging | 71 | /// * `abuse_threshold` - Number of connections from a single IP before flagging |
| 71 | /// * `registry` - Prometheus registry to register metrics with | 72 | /// * `registry` - Prometheus registry to register metrics with |
| 72 | pub fn new(abuse_threshold: u32, registry: &Registry) -> Self { | 73 | pub fn new(abuse_threshold: u32, registry: &Registry) -> Self { |
| 73 | let active_connections = IntGauge::with_opts( | 74 | let active_connections = IntGauge::with_opts(Opts::new( |
| 74 | Opts::new( | 75 | "ngit_websocket_connections_active", |
| 75 | "ngit_websocket_connections_active", | 76 | "Current active WebSocket connections", |
| 76 | "Current active WebSocket connections", | 77 | )) |
| 77 | ) | 78 | .unwrap(); |
| 78 | ).unwrap(); | 79 | registry |
| 79 | registry.register(Box::new(active_connections.clone())).unwrap(); | 80 | .register(Box::new(active_connections.clone())) |
| 80 | 81 | .unwrap(); | |
| 81 | let unique_ips = IntGauge::with_opts( | 82 | |
| 82 | Opts::new( | 83 | let unique_ips = IntGauge::with_opts(Opts::new( |
| 83 | "ngit_websocket_unique_ips", | 84 | "ngit_websocket_unique_ips", |
| 84 | "Number of unique IP addresses connected (NOT the IPs themselves)", | 85 | "Number of unique IP addresses connected (NOT the IPs themselves)", |
| 85 | ) | 86 | )) |
| 86 | ).unwrap(); | 87 | .unwrap(); |
| 87 | registry.register(Box::new(unique_ips.clone())).unwrap(); | 88 | registry.register(Box::new(unique_ips.clone())).unwrap(); |
| 88 | 89 | ||
| 89 | let flagged_abusers = IntGauge::with_opts( | 90 | let flagged_abusers = IntGauge::with_opts(Opts::new( |
| 90 | Opts::new( | 91 | "ngit_websocket_flagged_abusers", |
| 91 | "ngit_websocket_flagged_abusers", | 92 | "Number of IPs exceeding connection threshold", |
| 92 | "Number of IPs exceeding connection threshold", | 93 | )) |
| 93 | ) | 94 | .unwrap(); |
| 94 | ).unwrap(); | 95 | registry |
| 95 | registry.register(Box::new(flagged_abusers.clone())).unwrap(); | 96 | .register(Box::new(flagged_abusers.clone())) |
| 97 | .unwrap(); | ||
| 96 | 98 | ||
| 97 | Self { | 99 | Self { |
| 98 | connections: DashMap::new(), | 100 | connections: DashMap::new(), |
| @@ -140,7 +142,7 @@ impl ConnectionTracker { | |||
| 140 | 142 | ||
| 141 | // Update Prometheus metrics (aggregate counts only) | 143 | // Update Prometheus metrics (aggregate counts only) |
| 142 | self.active_connections.inc(); | 144 | self.active_connections.inc(); |
| 143 | 145 | ||
| 144 | if is_new_ip { | 146 | if is_new_ip { |
| 145 | self.unique_ips.inc(); | 147 | self.unique_ips.inc(); |
| 146 | } | 148 | } |
| @@ -334,4 +336,4 @@ mod tests { | |||
| 334 | assert_eq!(tracker.active_connections(), 0); | 336 | assert_eq!(tracker.active_connections(), 0); |
| 335 | assert_eq!(tracker.unique_ip_count(), 0); | 337 | assert_eq!(tracker.unique_ip_count(), 0); |
| 336 | } | 338 | } |
| 337 | } \ No newline at end of file | 339 | } |
diff --git a/src/metrics/mod.rs b/src/metrics/mod.rs index 736414f..5420dfd 100644 --- a/src/metrics/mod.rs +++ b/src/metrics/mod.rs | |||
| @@ -87,7 +87,8 @@ struct MetricsInner { | |||
| 87 | // === System Health Metrics === | 87 | // === System Health Metrics === |
| 88 | /// Server start time for uptime calculation | 88 | /// Server start time for uptime calculation |
| 89 | pub start_time: Instant, | 89 | pub start_time: Instant, |
| 90 | /// Build information gauge | 90 | /// Build information gauge (stored to prevent unregistration from Prometheus) |
| 91 | #[allow(dead_code)] | ||
| 91 | pub build_info: GaugeVec, | 92 | pub build_info: GaugeVec, |
| 92 | } | 93 | } |
| 93 | 94 | ||
| @@ -158,7 +159,10 @@ impl Metrics { | |||
| 158 | 159 | ||
| 159 | /// Start timing a git operation, returns a timer | 160 | /// Start timing a git operation, returns a timer |
| 160 | pub fn start_git_operation_timer(&self, operation: &str) -> GitOperationTimer { | 161 | pub fn start_git_operation_timer(&self, operation: &str) -> GitOperationTimer { |
| 161 | GitOperationTimer::new(self.inner.git_operation_duration.clone(), operation.to_string()) | 162 | GitOperationTimer::new( |
| 163 | self.inner.git_operation_duration.clone(), | ||
| 164 | operation.to_string(), | ||
| 165 | ) | ||
| 162 | } | 166 | } |
| 163 | 167 | ||
| 164 | /// Record bytes transferred for a git operation | 168 | /// Record bytes transferred for a git operation |
| @@ -266,13 +270,14 @@ impl MetricsInner { | |||
| 266 | } | 270 | } |
| 267 | 271 | ||
| 268 | // WebSocket metrics | 272 | // WebSocket metrics |
| 269 | let websocket_connections_total = Counter::with_opts( | 273 | let websocket_connections_total = Counter::with_opts(Opts::new( |
| 270 | Opts::new( | 274 | "ngit_websocket_connections_total", |
| 271 | "ngit_websocket_connections_total", | 275 | "Total WebSocket connections since startup", |
| 272 | "Total WebSocket connections since startup", | 276 | )) |
| 273 | ) | 277 | .unwrap(); |
| 274 | ).unwrap(); | 278 | REGISTRY |
| 275 | REGISTRY.register(Box::new(websocket_connections_total.clone())).unwrap(); | 279 | .register(Box::new(websocket_connections_total.clone())) |
| 280 | .unwrap(); | ||
| 276 | 281 | ||
| 277 | let websocket_connection_duration = Histogram::with_opts( | 282 | let websocket_connection_duration = Histogram::with_opts( |
| 278 | HistogramOpts::new( | 283 | HistogramOpts::new( |
| @@ -280,8 +285,11 @@ impl MetricsInner { | |||
| 280 | "Duration of WebSocket connections", | 285 | "Duration of WebSocket connections", |
| 281 | ) | 286 | ) |
| 282 | .buckets(vec![1.0, 5.0, 15.0, 30.0, 60.0, 300.0, 900.0, 3600.0]), | 287 | .buckets(vec![1.0, 5.0, 15.0, 30.0, 60.0, 300.0, 900.0, 3600.0]), |
| 283 | ).unwrap(); | 288 | ) |
| 284 | REGISTRY.register(Box::new(websocket_connection_duration.clone())).unwrap(); | 289 | .unwrap(); |
| 290 | REGISTRY | ||
| 291 | .register(Box::new(websocket_connection_duration.clone())) | ||
| 292 | .unwrap(); | ||
| 285 | 293 | ||
| 286 | let websocket_messages_received = CounterVec::new( | 294 | let websocket_messages_received = CounterVec::new( |
| 287 | Opts::new( | 295 | Opts::new( |
| @@ -289,8 +297,11 @@ impl MetricsInner { | |||
| 289 | "WebSocket messages received by type", | 297 | "WebSocket messages received by type", |
| 290 | ), | 298 | ), |
| 291 | &["type"], | 299 | &["type"], |
| 292 | ).unwrap(); | 300 | ) |
| 293 | REGISTRY.register(Box::new(websocket_messages_received.clone())).unwrap(); | 301 | .unwrap(); |
| 302 | REGISTRY | ||
| 303 | .register(Box::new(websocket_messages_received.clone())) | ||
| 304 | .unwrap(); | ||
| 294 | 305 | ||
| 295 | let websocket_messages_sent = CounterVec::new( | 306 | let websocket_messages_sent = CounterVec::new( |
| 296 | Opts::new( | 307 | Opts::new( |
| @@ -298,8 +309,11 @@ impl MetricsInner { | |||
| 298 | "WebSocket messages sent by type", | 309 | "WebSocket messages sent by type", |
| 299 | ), | 310 | ), |
| 300 | &["type"], | 311 | &["type"], |
| 301 | ).unwrap(); | 312 | ) |
| 302 | REGISTRY.register(Box::new(websocket_messages_sent.clone())).unwrap(); | 313 | .unwrap(); |
| 314 | REGISTRY | ||
| 315 | .register(Box::new(websocket_messages_sent.clone())) | ||
| 316 | .unwrap(); | ||
| 303 | 317 | ||
| 304 | // Git operation metrics | 318 | // Git operation metrics |
| 305 | let git_operations_total = CounterVec::new( | 319 | let git_operations_total = CounterVec::new( |
| @@ -308,8 +322,11 @@ impl MetricsInner { | |||
| 308 | "Git operations by type and status", | 322 | "Git operations by type and status", |
| 309 | ), | 323 | ), |
| 310 | &["operation", "status"], | 324 | &["operation", "status"], |
| 311 | ).unwrap(); | 325 | ) |
| 312 | REGISTRY.register(Box::new(git_operations_total.clone())).unwrap(); | 326 | .unwrap(); |
| 327 | REGISTRY | ||
| 328 | .register(Box::new(git_operations_total.clone())) | ||
| 329 | .unwrap(); | ||
| 313 | 330 | ||
| 314 | let git_operation_duration = HistogramVec::new( | 331 | let git_operation_duration = HistogramVec::new( |
| 315 | HistogramOpts::new( | 332 | HistogramOpts::new( |
| @@ -318,8 +335,11 @@ impl MetricsInner { | |||
| 318 | ) | 335 | ) |
| 319 | .buckets(vec![0.1, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0]), | 336 | .buckets(vec![0.1, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0]), |
| 320 | &["operation"], | 337 | &["operation"], |
| 321 | ).unwrap(); | 338 | ) |
| 322 | REGISTRY.register(Box::new(git_operation_duration.clone())).unwrap(); | 339 | .unwrap(); |
| 340 | REGISTRY | ||
| 341 | .register(Box::new(git_operation_duration.clone())) | ||
| 342 | .unwrap(); | ||
| 323 | 343 | ||
| 324 | let git_bytes_total = CounterVec::new( | 344 | let git_bytes_total = CounterVec::new( |
| 325 | Opts::new( | 345 | Opts::new( |
| @@ -327,8 +347,11 @@ impl MetricsInner { | |||
| 327 | "Total bytes transferred for git operations", | 347 | "Total bytes transferred for git operations", |
| 328 | ), | 348 | ), |
| 329 | &["direction"], | 349 | &["direction"], |
| 330 | ).unwrap(); | 350 | ) |
| 331 | REGISTRY.register(Box::new(git_bytes_total.clone())).unwrap(); | 351 | .unwrap(); |
| 352 | REGISTRY | ||
| 353 | .register(Box::new(git_bytes_total.clone())) | ||
| 354 | .unwrap(); | ||
| 332 | 355 | ||
| 333 | let git_push_authorization = CounterVec::new( | 356 | let git_push_authorization = CounterVec::new( |
| 334 | Opts::new( | 357 | Opts::new( |
| @@ -336,8 +359,11 @@ impl MetricsInner { | |||
| 336 | "Push authorization results", | 359 | "Push authorization results", |
| 337 | ), | 360 | ), |
| 338 | &["result"], | 361 | &["result"], |
| 339 | ).unwrap(); | 362 | ) |
| 340 | REGISTRY.register(Box::new(git_push_authorization.clone())).unwrap(); | 363 | .unwrap(); |
| 364 | REGISTRY | ||
| 365 | .register(Box::new(git_push_authorization.clone())) | ||
| 366 | .unwrap(); | ||
| 341 | 367 | ||
| 342 | // Nostr event metrics | 368 | // Nostr event metrics |
| 343 | let events_received_total = CounterVec::new( | 369 | let events_received_total = CounterVec::new( |
| @@ -346,8 +372,11 @@ impl MetricsInner { | |||
| 346 | "Nostr events received by kind", | 372 | "Nostr events received by kind", |
| 347 | ), | 373 | ), |
| 348 | &["kind"], | 374 | &["kind"], |
| 349 | ).unwrap(); | 375 | ) |
| 350 | REGISTRY.register(Box::new(events_received_total.clone())).unwrap(); | 376 | .unwrap(); |
| 377 | REGISTRY | ||
| 378 | .register(Box::new(events_received_total.clone())) | ||
| 379 | .unwrap(); | ||
| 351 | 380 | ||
| 352 | let events_stored_total = CounterVec::new( | 381 | let events_stored_total = CounterVec::new( |
| 353 | Opts::new( | 382 | Opts::new( |
| @@ -355,8 +384,11 @@ impl MetricsInner { | |||
| 355 | "Nostr events successfully stored by kind", | 384 | "Nostr events successfully stored by kind", |
| 356 | ), | 385 | ), |
| 357 | &["kind"], | 386 | &["kind"], |
| 358 | ).unwrap(); | 387 | ) |
| 359 | REGISTRY.register(Box::new(events_stored_total.clone())).unwrap(); | 388 | .unwrap(); |
| 389 | REGISTRY | ||
| 390 | .register(Box::new(events_stored_total.clone())) | ||
| 391 | .unwrap(); | ||
| 360 | 392 | ||
| 361 | let events_rejected_total = CounterVec::new( | 393 | let events_rejected_total = CounterVec::new( |
| 362 | Opts::new( | 394 | Opts::new( |
| @@ -364,31 +396,36 @@ impl MetricsInner { | |||
| 364 | "Nostr events rejected by kind and reason", | 396 | "Nostr events rejected by kind and reason", |
| 365 | ), | 397 | ), |
| 366 | &["kind", "reason"], | 398 | &["kind", "reason"], |
| 367 | ).unwrap(); | 399 | ) |
| 368 | REGISTRY.register(Box::new(events_rejected_total.clone())).unwrap(); | 400 | .unwrap(); |
| 401 | REGISTRY | ||
| 402 | .register(Box::new(events_rejected_total.clone())) | ||
| 403 | .unwrap(); | ||
| 369 | 404 | ||
| 370 | // Repository metrics | 405 | // Repository metrics |
| 371 | let repositories_total = Gauge::with_opts( | 406 | let repositories_total = Gauge::with_opts(Opts::new( |
| 372 | Opts::new( | 407 | "ngit_repositories_total", |
| 373 | "ngit_repositories_total", | 408 | "Total repositories hosted", |
| 374 | "Total repositories hosted", | 409 | )) |
| 375 | ) | 410 | .unwrap(); |
| 376 | ).unwrap(); | 411 | REGISTRY |
| 377 | REGISTRY.register(Box::new(repositories_total.clone())).unwrap(); | 412 | .register(Box::new(repositories_total.clone())) |
| 413 | .unwrap(); | ||
| 378 | 414 | ||
| 379 | // Build info | 415 | // Build info |
| 380 | let build_info = GaugeVec::new( | 416 | let build_info = GaugeVec::new( |
| 381 | Opts::new( | 417 | Opts::new("ngit_build_info", "Build information"), |
| 382 | "ngit_build_info", | ||
| 383 | "Build information", | ||
| 384 | ), | ||
| 385 | &["version", "commit"], | 418 | &["version", "commit"], |
| 386 | ).unwrap(); | 419 | ) |
| 420 | .unwrap(); | ||
| 387 | REGISTRY.register(Box::new(build_info.clone())).unwrap(); | 421 | REGISTRY.register(Box::new(build_info.clone())).unwrap(); |
| 388 | 422 | ||
| 389 | // Set build info gauge to 1 (it's just for labels) | 423 | // Set build info gauge to 1 (it's just for labels) |
| 390 | build_info | 424 | build_info |
| 391 | .with_label_values(&[env!("CARGO_PKG_VERSION"), option_env!("GIT_HASH").unwrap_or("unknown")]) | 425 | .with_label_values(&[ |
| 426 | env!("CARGO_PKG_VERSION"), | ||
| 427 | option_env!("GIT_HASH").unwrap_or("unknown"), | ||
| 428 | ]) | ||
| 392 | .set(1.0); | 429 | .set(1.0); |
| 393 | 430 | ||
| 394 | Self { | 431 | Self { |
| @@ -472,7 +509,7 @@ mod tests { | |||
| 472 | // Note: This test may fail if run with other tests due to global registry | 509 | // Note: This test may fail if run with other tests due to global registry |
| 473 | // In production, consider using a test-specific registry | 510 | // In production, consider using a test-specific registry |
| 474 | let metrics = Metrics::new(10); | 511 | let metrics = Metrics::new(10); |
| 475 | 512 | ||
| 476 | // Test that we can record metrics without panicking | 513 | // Test that we can record metrics without panicking |
| 477 | metrics.record_websocket_connection(); | 514 | metrics.record_websocket_connection(); |
| 478 | metrics.record_message_received("REQ"); | 515 | metrics.record_message_received("REQ"); |
| @@ -484,4 +521,4 @@ mod tests { | |||
| 484 | metrics.record_event_rejected(1, "invalid_signature"); | 521 | metrics.record_event_rejected(1, "invalid_signature"); |
| 485 | metrics.set_repositories_total(5); | 522 | metrics.set_repositories_total(5); |
| 486 | } | 523 | } |
| 487 | } \ No newline at end of file | 524 | } |
diff --git a/src/nostr/builder.rs b/src/nostr/builder.rs index 2284c18..c9bd1e1 100644 --- a/src/nostr/builder.rs +++ b/src/nostr/builder.rs | |||
| @@ -16,8 +16,8 @@ use crate::nostr::events::{ | |||
| 16 | KIND_REPOSITORY_STATE, | 16 | KIND_REPOSITORY_STATE, |
| 17 | }; | 17 | }; |
| 18 | use crate::nostr::policy::{ | 18 | use crate::nostr::policy::{ |
| 19 | AnnouncementPolicy, AnnouncementResult, PolicyContext, PrEventPolicy, RelatedEventPolicy, | 19 | AnnouncementPolicy, AnnouncementResult, PolicyContext, PrEventPolicy, ReferenceResult, |
| 20 | ReferenceResult, StatePolicy, StateResult, | 20 | RelatedEventPolicy, StatePolicy, StateResult, |
| 21 | }; | 21 | }; |
| 22 | 22 | ||
| 23 | /// Type alias for the shared database used by the relay | 23 | /// Type alias for the shared database used by the relay |
| @@ -77,7 +77,9 @@ impl Nip34WritePolicy { | |||
| 77 | match RepositoryAnnouncement::from_event(event.clone()) { | 77 | match RepositoryAnnouncement::from_event(event.clone()) { |
| 78 | Ok(announcement) => { | 78 | Ok(announcement) => { |
| 79 | // Try to create bare repository if it doesn't exist | 79 | // Try to create bare repository if it doesn't exist |
| 80 | if let Err(e) = self.announcement_policy.ensure_bare_repository(&announcement) | 80 | if let Err(e) = self |
| 81 | .announcement_policy | ||
| 82 | .ensure_bare_repository(&announcement) | ||
| 81 | { | 83 | { |
| 82 | tracing::warn!( | 84 | tracing::warn!( |
| 83 | "Failed to create bare repository for {}: {}", | 85 | "Failed to create bare repository for {}: {}", |
| @@ -145,22 +147,14 @@ impl Nip34WritePolicy { | |||
| 145 | Ok(_state) => { | 147 | Ok(_state) => { |
| 146 | // Process state alignment asynchronously | 148 | // Process state alignment asynchronously |
| 147 | if let Err(e) = self.state_policy.process_state_event(event).await { | 149 | if let Err(e) = self.state_policy.process_state_event(event).await { |
| 148 | tracing::warn!( | 150 | tracing::warn!("Failed to process state event {}: {}", event_id_str, e); |
| 149 | "Failed to process state event {}: {}", | ||
| 150 | event_id_str, | ||
| 151 | e | ||
| 152 | ); | ||
| 153 | } | 151 | } |
| 154 | 152 | ||
| 155 | tracing::debug!("Accepted repository state: {}", event_id_str); | 153 | tracing::debug!("Accepted repository state: {}", event_id_str); |
| 156 | PolicyResult::Accept | 154 | PolicyResult::Accept |
| 157 | } | 155 | } |
| 158 | Err(e) => { | 156 | Err(e) => { |
| 159 | tracing::warn!( | 157 | tracing::warn!("Failed to parse repository state {}: {}", event_id_str, e); |
| 160 | "Failed to parse repository state {}: {}", | ||
| 161 | event_id_str, | ||
| 162 | e | ||
| 163 | ); | ||
| 164 | // Still accept the event even if we can't parse it | 158 | // Still accept the event even if we can't parse it |
| 165 | // The validation passed, so it's structurally valid | 159 | // The validation passed, so it's structurally valid |
| 166 | PolicyResult::Accept | 160 | PolicyResult::Accept |
| @@ -348,4 +342,4 @@ pub fn create_relay(config: &Config) -> Result<RelayWithDatabase> { | |||
| 348 | database, | 342 | database, |
| 349 | write_policy, | 343 | write_policy, |
| 350 | }) | 344 | }) |
| 351 | } \ No newline at end of file | 345 | } |
diff --git a/src/nostr/policy/announcement.rs b/src/nostr/policy/announcement.rs index 8d30baf..353738b 100644 --- a/src/nostr/policy/announcement.rs +++ b/src/nostr/policy/announcement.rs | |||
| @@ -72,7 +72,10 @@ impl AnnouncementPolicy { | |||
| 72 | 72 | ||
| 73 | /// Create a bare git repository if it doesn't exist | 73 | /// Create a bare git repository if it doesn't exist |
| 74 | /// Path format: <git_data_path>/<npub>/<identifier>.git | 74 | /// Path format: <git_data_path>/<npub>/<identifier>.git |
| 75 | pub fn ensure_bare_repository(&self, announcement: &RepositoryAnnouncement) -> Result<(), String> { | 75 | pub fn ensure_bare_repository( |
| 76 | &self, | ||
| 77 | announcement: &RepositoryAnnouncement, | ||
| 78 | ) -> Result<(), String> { | ||
| 76 | let repo_path = self.ctx.git_data_path.join(announcement.repo_path()); | 79 | let repo_path = self.ctx.git_data_path.join(announcement.repo_path()); |
| 77 | 80 | ||
| 78 | // Check if repository already exists | 81 | // Check if repository already exists |
| @@ -154,4 +157,4 @@ impl AnnouncementPolicy { | |||
| 154 | 157 | ||
| 155 | Ok(false) | 158 | Ok(false) |
| 156 | } | 159 | } |
| 157 | } \ No newline at end of file | 160 | } |
diff --git a/src/nostr/policy/mod.rs b/src/nostr/policy/mod.rs index 6d67394..19db5f6 100644 --- a/src/nostr/policy/mod.rs +++ b/src/nostr/policy/mod.rs | |||
| @@ -5,7 +5,6 @@ | |||
| 5 | /// - `StatePolicy` - State event validation + ref alignment | 5 | /// - `StatePolicy` - State event validation + ref alignment |
| 6 | /// - `PrEventPolicy` - PR/PR Update validation | 6 | /// - `PrEventPolicy` - PR/PR Update validation |
| 7 | /// - `RelatedEventPolicy` - Forward/backward reference checking | 7 | /// - `RelatedEventPolicy` - Forward/backward reference checking |
| 8 | |||
| 9 | mod announcement; | 8 | mod announcement; |
| 10 | mod pr_event; | 9 | mod pr_event; |
| 11 | mod related; | 10 | mod related; |
| @@ -38,4 +37,4 @@ impl PolicyContext { | |||
| 38 | git_data_path: git_data_path.into(), | 37 | git_data_path: git_data_path.into(), |
| 39 | } | 38 | } |
| 40 | } | 39 | } |
| 41 | } \ No newline at end of file | 40 | } |
diff --git a/src/nostr/policy/pr_event.rs b/src/nostr/policy/pr_event.rs index fee9a2a..53da369 100644 --- a/src/nostr/policy/pr_event.rs +++ b/src/nostr/policy/pr_event.rs | |||
| @@ -195,4 +195,4 @@ impl PrEventPolicy { | |||
| 195 | Ok(None) | 195 | Ok(None) |
| 196 | } | 196 | } |
| 197 | } | 197 | } |
| 198 | } \ No newline at end of file | 198 | } |
diff --git a/src/nostr/policy/related.rs b/src/nostr/policy/related.rs index 1937ca7..7ce87db 100644 --- a/src/nostr/policy/related.rs +++ b/src/nostr/policy/related.rs | |||
| @@ -169,10 +169,7 @@ impl RelatedEventPolicy { | |||
| 169 | 169 | ||
| 170 | /// Check if any events exist in database | 170 | /// Check if any events exist in database |
| 171 | /// Returns the first matching event ID found, or None if none match | 171 | /// Returns the first matching event ID found, or None if none match |
| 172 | async fn find_accepted_event( | 172 | async fn find_accepted_event(&self, event_ids: &[EventId]) -> Result<Option<EventId>, String> { |
| 173 | &self, | ||
| 174 | event_ids: &[EventId], | ||
| 175 | ) -> Result<Option<EventId>, String> { | ||
| 176 | if event_ids.is_empty() { | 173 | if event_ids.is_empty() { |
| 177 | return Ok(None); | 174 | return Ok(None); |
| 178 | } | 175 | } |
| @@ -273,4 +270,4 @@ impl RelatedEventPolicy { | |||
| 273 | 270 | ||
| 274 | Ok(false) | 271 | Ok(false) |
| 275 | } | 272 | } |
| 276 | } \ No newline at end of file | 273 | } |
diff --git a/src/nostr/policy/state.rs b/src/nostr/policy/state.rs index 5692bd8..43349e2 100644 --- a/src/nostr/policy/state.rs +++ b/src/nostr/policy/state.rs | |||
| @@ -239,7 +239,10 @@ impl StatePolicy { | |||
| 239 | } | 239 | } |
| 240 | 240 | ||
| 241 | // Build repository path: <git_data_path>/<owner_npub>/<identifier>.git | 241 | // Build repository path: <git_data_path>/<owner_npub>/<identifier>.git |
| 242 | let repo_path = self.ctx.git_data_path.join(announcement.repo_path().clone()); | 242 | let repo_path = self |
| 243 | .ctx | ||
| 244 | .git_data_path | ||
| 245 | .join(announcement.repo_path().clone()); | ||
| 243 | owner_repos.push((announcement, repo_path)); | 246 | owner_repos.push((announcement, repo_path)); |
| 244 | } | 247 | } |
| 245 | 248 | ||
| @@ -416,4 +419,4 @@ impl StatePolicy { | |||
| 416 | 419 | ||
| 417 | result | 420 | result |
| 418 | } | 421 | } |
| 419 | } \ No newline at end of file | 422 | } |
diff --git a/src/sync/algorithms.rs b/src/sync/algorithms.rs index 7d87411..3063516 100644 --- a/src/sync/algorithms.rs +++ b/src/sync/algorithms.rs | |||
| @@ -65,9 +65,7 @@ pub fn derive_relay_targets( | |||
| 65 | 65 | ||
| 66 | for (repo_id, needs) in repo_index { | 66 | for (repo_id, needs) in repo_index { |
| 67 | for relay_url in &needs.relays { | 67 | for relay_url in &needs.relays { |
| 68 | let entry = relay_targets | 68 | let entry = relay_targets.entry(relay_url.clone()).or_default(); |
| 69 | .entry(relay_url.clone()) | ||
| 70 | .or_insert_with(RelaySyncNeeds::default); | ||
| 71 | 69 | ||
| 72 | entry.repos.insert(repo_id.clone()); | 70 | entry.repos.insert(repo_id.clone()); |
| 73 | entry.root_events.extend(needs.root_events.iter().cloned()); | 71 | entry.root_events.extend(needs.root_events.iter().cloned()); |
| @@ -586,4 +584,4 @@ mod tests { | |||
| 586 | ); | 584 | ); |
| 587 | assert_eq!(actions[0].relay_url, "wss://new-relay.com"); | 585 | assert_eq!(actions[0].relay_url, "wss://new-relay.com"); |
| 588 | } | 586 | } |
| 589 | } \ No newline at end of file | 587 | } |
diff --git a/src/sync/filters.rs b/src/sync/filters.rs index 02d580e..24e9bb2 100644 --- a/src/sync/filters.rs +++ b/src/sync/filters.rs | |||
| @@ -337,4 +337,4 @@ mod tests { | |||
| 337 | 337 | ||
| 338 | assert_eq!(filters.len(), 6); | 338 | assert_eq!(filters.len(), 6); |
| 339 | } | 339 | } |
| 340 | } \ No newline at end of file | 340 | } |
diff --git a/src/sync/health.rs b/src/sync/health.rs index f9a5f3a..0ae7dee 100644 --- a/src/sync/health.rs +++ b/src/sync/health.rs | |||
| @@ -206,11 +206,7 @@ impl RelayHealthTracker { | |||
| 206 | health.next_retry_at = Some(now + backoff); | 206 | health.next_retry_at = Some(now + backoff); |
| 207 | 207 | ||
| 208 | if old_state != HealthState::Degraded { | 208 | if old_state != HealthState::Degraded { |
| 209 | tracing::warn!( | 209 | tracing::warn!("Relay {} degraded, backoff {:?}", relay_url, backoff); |
| 210 | "Relay {} degraded, backoff {:?}", | ||
| 211 | relay_url, | ||
| 212 | backoff | ||
| 213 | ); | ||
| 214 | } else { | 210 | } else { |
| 215 | tracing::debug!( | 211 | tracing::debug!( |
| 216 | "Relay {} failure #{}, backoff {:?}", | 212 | "Relay {} failure #{}, backoff {:?}", |
| @@ -308,12 +304,17 @@ impl RelayHealthTracker { | |||
| 308 | 304 | ||
| 309 | /// Get all tracked relay URLs | 305 | /// Get all tracked relay URLs |
| 310 | pub fn get_tracked_relays(&self) -> Vec<String> { | 306 | pub fn get_tracked_relays(&self) -> Vec<String> { |
| 311 | self.health.iter().map(|entry| entry.key().clone()).collect() | 307 | self.health |
| 308 | .iter() | ||
| 309 | .map(|entry| entry.key().clone()) | ||
| 310 | .collect() | ||
| 312 | } | 311 | } |
| 313 | 312 | ||
| 314 | /// Get a clone of the health info for a relay | 313 | /// Get a clone of the health info for a relay |
| 315 | pub fn get_health(&self, relay_url: &str) -> Option<RelayHealth> { | 314 | pub fn get_health(&self, relay_url: &str) -> Option<RelayHealth> { |
| 316 | self.health.get(relay_url).map(|entry| entry.value().clone()) | 315 | self.health |
| 316 | .get(relay_url) | ||
| 317 | .map(|entry| entry.value().clone()) | ||
| 317 | } | 318 | } |
| 318 | } | 319 | } |
| 319 | 320 | ||
| @@ -369,7 +370,7 @@ mod tests { | |||
| 369 | fn test_backoff_increases_exponentially() { | 370 | fn test_backoff_increases_exponentially() { |
| 370 | let base = DEFAULT_BASE_BACKOFF_SECS; // 5 seconds | 371 | let base = DEFAULT_BASE_BACKOFF_SECS; // 5 seconds |
| 371 | let max = 3600u64; | 372 | let max = 3600u64; |
| 372 | 373 | ||
| 373 | // failure 1: 5s (base * 2^0 = 5) | 374 | // failure 1: 5s (base * 2^0 = 5) |
| 374 | assert_eq!( | 375 | assert_eq!( |
| 375 | RelayHealthTracker::get_backoff_duration(1, base, max), | 376 | RelayHealthTracker::get_backoff_duration(1, base, max), |
| @@ -498,4 +499,4 @@ mod tests { | |||
| 498 | let health = tracker.get_health("wss://nonexistent.example.com"); | 499 | let health = tracker.get_health("wss://nonexistent.example.com"); |
| 499 | assert!(health.is_none()); | 500 | assert!(health.is_none()); |
| 500 | } | 501 | } |
| 501 | } \ No newline at end of file | 502 | } |
diff --git a/src/sync/metrics.rs b/src/sync/metrics.rs index 411ff63..d917dc0 100644 --- a/src/sync/metrics.rs +++ b/src/sync/metrics.rs | |||
| @@ -207,7 +207,9 @@ impl SyncMetrics { | |||
| 207 | HealthState::Degraded => 2, | 207 | HealthState::Degraded => 2, |
| 208 | HealthState::Dead => 3, | 208 | HealthState::Dead => 3, |
| 209 | }; | 209 | }; |
| 210 | self.relay_status.with_label_values(&[relay]).set(state_value); | 210 | self.relay_status |
| 211 | .with_label_values(&[relay]) | ||
| 212 | .set(state_value); | ||
| 211 | } | 213 | } |
| 212 | 214 | ||
| 213 | /// Record relay failure count. | 215 | /// Record relay failure count. |
| @@ -259,9 +261,7 @@ impl SyncMetrics { | |||
| 259 | /// * `source` - The event source type (see [`record_event`](Self::record_event)) | 261 | /// * `source` - The event source type (see [`record_event`](Self::record_event)) |
| 260 | /// * `count` - Number of events to record | 262 | /// * `count` - Number of events to record |
| 261 | pub fn record_events(&self, source: &str, count: u64) { | 263 | pub fn record_events(&self, source: &str, count: u64) { |
| 262 | self.events_total | 264 | self.events_total.with_label_values(&[source]).inc_by(count); |
| 263 | .with_label_values(&[source]) | ||
| 264 | .inc_by(count); | ||
| 265 | } | 265 | } |
| 266 | 266 | ||
| 267 | /// Record a gap event filled during catchup. | 267 | /// Record a gap event filled during catchup. |
| @@ -451,4 +451,4 @@ mod tests { | |||
| 451 | let metrics2 = SyncMetrics::register(®istry); | 451 | let metrics2 = SyncMetrics::register(®istry); |
| 452 | assert!(metrics2.is_err()); | 452 | assert!(metrics2.is_err()); |
| 453 | } | 453 | } |
| 454 | } \ No newline at end of file | 454 | } |
diff --git a/src/sync/mod.rs b/src/sync/mod.rs index c4c3c7f..fb59b3c 100644 --- a/src/sync/mod.rs +++ b/src/sync/mod.rs | |||
| @@ -512,8 +512,8 @@ impl SyncManager { | |||
| 512 | }; | 512 | }; |
| 513 | 513 | ||
| 514 | // Check if relay supports NIP-77 negentropy AND negentropy is not disabled | 514 | // Check if relay supports NIP-77 negentropy AND negentropy is not disabled |
| 515 | let use_negentropy = !self.config.sync_disable_negentropy | 515 | let use_negentropy = |
| 516 | && connection.supports_negentropy().await; | 516 | !self.config.sync_disable_negentropy && connection.supports_negentropy().await; |
| 517 | 517 | ||
| 518 | // Unsubscribe all current subscriptions | 518 | // Unsubscribe all current subscriptions |
| 519 | connection.unsubscribe_all().await; | 519 | connection.unsubscribe_all().await; |
| @@ -1657,12 +1657,12 @@ impl SyncManager { | |||
| 1657 | 1657 | ||
| 1658 | let layer1_filters = 1; | 1658 | let layer1_filters = 1; |
| 1659 | let layer2_filters = if repo_count > 0 { | 1659 | let layer2_filters = if repo_count > 0 { |
| 1660 | ((repo_count + 99) / 100) * 3 | 1660 | repo_count.div_ceil(100) * 3 |
| 1661 | } else { | 1661 | } else { |
| 1662 | 0 | 1662 | 0 |
| 1663 | }; | 1663 | }; |
| 1664 | let layer3_filters = if event_count > 0 { | 1664 | let layer3_filters = if event_count > 0 { |
| 1665 | ((event_count + 99) / 100) * 3 | 1665 | event_count.div_ceil(100) * 3 |
| 1666 | } else { | 1666 | } else { |
| 1667 | 0 | 1667 | 0 |
| 1668 | }; | 1668 | }; |
diff --git a/src/sync/relay_connection.rs b/src/sync/relay_connection.rs index fae179b..4167a0c 100644 --- a/src/sync/relay_connection.rs +++ b/src/sync/relay_connection.rs | |||
| @@ -150,17 +150,21 @@ impl RelayConnection { | |||
| 150 | // | 150 | // |
| 151 | // See: nostr-sdk-0.44 Client::try_connect_relay documentation | 151 | // See: nostr-sdk-0.44 Client::try_connect_relay documentation |
| 152 | self.client | 152 | self.client |
| 153 | .try_connect_relay(&self.url, std::time::Duration::from_secs(connection_timeout_secs)) | 153 | .try_connect_relay( |
| 154 | &self.url, | ||
| 155 | std::time::Duration::from_secs(connection_timeout_secs), | ||
| 156 | ) | ||
| 154 | .await | 157 | .await |
| 155 | .map_err(|e| format!("Failed to connect to relay {}: {}", self.url, e))?; | 158 | .map_err(|e| format!("Failed to connect to relay {}: {}", self.url, e))?; |
| 156 | 159 | ||
| 157 | // Subscribe to Layer 1 (announcements) | 160 | // Subscribe to Layer 1 (announcements) |
| 158 | let filter = build_announcement_filter(since); | 161 | let filter = build_announcement_filter(since); |
| 159 | let output = self | 162 | let output = self.client.subscribe(filter, None).await.map_err(|e| { |
| 160 | .client | 163 | format!( |
| 161 | .subscribe(filter, None) | 164 | "Failed to subscribe to announcements on {}: {}", |
| 162 | .await | 165 | self.url, e |
| 163 | .map_err(|e| format!("Failed to subscribe to announcements on {}: {}", self.url, e))?; | 166 | ) |
| 167 | })?; | ||
| 164 | 168 | ||
| 165 | tracing::info!(url = %self.url, sub_id = %output.val, "Connected and subscribed to Layer 1 (announcements)"); | 169 | tracing::info!(url = %self.url, sub_id = %output.val, "Connected and subscribed to Layer 1 (announcements)"); |
| 166 | Ok(output.val) | 170 | Ok(output.val) |
| @@ -250,7 +254,8 @@ impl RelayConnection { | |||
| 250 | } | 254 | } |
| 251 | RelayMessage::Closed { message: msg, .. } => { | 255 | RelayMessage::Closed { message: msg, .. } => { |
| 252 | tracing::info!(relay = %url, message = %msg, "Relay closed subscription"); | 256 | tracing::info!(relay = %url, message = %msg, "Relay closed subscription"); |
| 253 | let _ = event_sender.send(RelayEvent::Closed(msg.to_string())).await; | 257 | let _ = |
| 258 | event_sender.send(RelayEvent::Closed(msg.to_string())).await; | ||
| 254 | break; | 259 | break; |
| 255 | } | 260 | } |
| 256 | _ => {} | 261 | _ => {} |
| @@ -421,7 +426,10 @@ impl RelayConnection { | |||
| 421 | /// - Relay doesn't actually support NIP-77 (despite claiming to) | 426 | /// - Relay doesn't actually support NIP-77 (despite claiming to) |
| 422 | /// - Network errors during reconciliation | 427 | /// - Network errors during reconciliation |
| 423 | /// - Timeout during sync | 428 | /// - Timeout during sync |
| 424 | pub async fn negentropy_sync_filter(&self, filter: Filter) -> Result<NegentropySyncResult, String> { | 429 | pub async fn negentropy_sync_filter( |
| 430 | &self, | ||
| 431 | filter: Filter, | ||
| 432 | ) -> Result<NegentropySyncResult, String> { | ||
| 425 | // Use nostr-sdk's sync method which handles the NEG-OPEN/NEG-MSG exchange | 433 | // Use nostr-sdk's sync method which handles the NEG-OPEN/NEG-MSG exchange |
| 426 | let sync_opts = SyncOptions::default(); | 434 | let sync_opts = SyncOptions::default(); |
| 427 | 435 | ||
diff --git a/src/sync/self_subscriber.rs b/src/sync/self_subscriber.rs index f83b081..e29e45b 100644 --- a/src/sync/self_subscriber.rs +++ b/src/sync/self_subscriber.rs | |||
| @@ -49,7 +49,12 @@ impl PendingUpdates { | |||
| 49 | } | 49 | } |
| 50 | 50 | ||
| 51 | /// Add or update a repo with its relays and root events | 51 | /// Add or update a repo with its relays and root events |
| 52 | fn add_repo(&mut self, repo_id: String, relays: HashSet<String>, root_events: HashSet<EventId>) { | 52 | fn add_repo( |
| 53 | &mut self, | ||
| 54 | repo_id: String, | ||
| 55 | relays: HashSet<String>, | ||
| 56 | root_events: HashSet<EventId>, | ||
| 57 | ) { | ||
| 53 | let entry = self.repos.entry(repo_id).or_insert_with(|| RepoSyncNeeds { | 58 | let entry = self.repos.entry(repo_id).or_insert_with(|| RepoSyncNeeds { |
| 54 | relays: HashSet::new(), | 59 | relays: HashSet::new(), |
| 55 | root_events: HashSet::new(), | 60 | root_events: HashSet::new(), |
| @@ -251,9 +256,9 @@ impl SelfSubscriber { | |||
| 251 | /// | 256 | /// |
| 252 | /// Returns true if any extracted relay URL contains our domain | 257 | /// Returns true if any extracted relay URL contains our domain |
| 253 | fn lists_our_relay(&self, event: &Event) -> bool { | 258 | fn lists_our_relay(&self, event: &Event) -> bool { |
| 254 | Self::extract_relay_urls(event).iter().any(|url| { | 259 | Self::extract_relay_urls(event) |
| 255 | url.contains(&self.relay_domain) || url == &self.own_relay_url | 260 | .iter() |
| 256 | }) | 261 | .any(|url| url.contains(&self.relay_domain) || url == &self.own_relay_url) |
| 257 | } | 262 | } |
| 258 | 263 | ||
| 259 | /// Main run loop | 264 | /// Main run loop |
| @@ -413,21 +418,21 @@ impl SelfSubscriber { | |||
| 413 | if let Some(repo_sync) = index.get_mut(&repo_ref) { | 418 | if let Some(repo_sync) = index.get_mut(&repo_ref) { |
| 414 | // Add event.id to root_events set in the index (immediate availability) | 419 | // Add event.id to root_events set in the index (immediate availability) |
| 415 | repo_sync.root_events.insert(event.id); | 420 | repo_sync.root_events.insert(event.id); |
| 416 | 421 | ||
| 417 | // Clone the relays before releasing the lock - Layer 3 filters need to be | 422 | // Clone the relays before releasing the lock - Layer 3 filters need to be |
| 418 | // sent to the same relays as Layer 2 filters for this repo | 423 | // sent to the same relays as Layer 2 filters for this repo |
| 419 | let relays = repo_sync.relays.clone(); | 424 | let relays = repo_sync.relays.clone(); |
| 420 | 425 | ||
| 421 | // Release lock before modifying pending | 426 | // Release lock before modifying pending |
| 422 | drop(index); | 427 | drop(index); |
| 423 | 428 | ||
| 424 | // Also add root event to pending - this ensures batch processing runs | 429 | // Also add root event to pending - this ensures batch processing runs |
| 425 | // and creates Layer 3 filters for events referencing this root event. | 430 | // and creates Layer 3 filters for events referencing this root event. |
| 426 | // CRITICAL: Include relays so derive_relay_targets knows where to send filters! | 431 | // CRITICAL: Include relays so derive_relay_targets knows where to send filters! |
| 427 | let mut root_events = HashSet::new(); | 432 | let mut root_events = HashSet::new(); |
| 428 | root_events.insert(event.id); | 433 | root_events.insert(event.id); |
| 429 | pending.add_repo(repo_ref.clone(), relays.clone(), root_events); | 434 | pending.add_repo(repo_ref.clone(), relays.clone(), root_events); |
| 430 | 435 | ||
| 431 | tracing::debug!( | 436 | tracing::debug!( |
| 432 | event_id = %event.id, | 437 | event_id = %event.id, |
| 433 | repo_ref = %repo_ref, | 438 | repo_ref = %repo_ref, |
| @@ -475,10 +480,12 @@ impl SelfSubscriber { | |||
| 475 | 480 | ||
| 476 | for (repo_id, needs) in updates { | 481 | for (repo_id, needs) in updates { |
| 477 | // Merge with existing entry or insert new | 482 | // Merge with existing entry or insert new |
| 478 | let entry = index.entry(repo_id.clone()).or_insert_with(|| RepoSyncNeeds { | 483 | let entry = index |
| 479 | relays: HashSet::new(), | 484 | .entry(repo_id.clone()) |
| 480 | root_events: HashSet::new(), | 485 | .or_insert_with(|| RepoSyncNeeds { |
| 481 | }); | 486 | relays: HashSet::new(), |
| 487 | root_events: HashSet::new(), | ||
| 488 | }); | ||
| 482 | entry.relays.extend(needs.relays); | 489 | entry.relays.extend(needs.relays); |
| 483 | entry.root_events.extend(needs.root_events); | 490 | entry.root_events.extend(needs.root_events); |
| 484 | 491 | ||
| @@ -556,7 +563,7 @@ fn clone_url_to_relay_url(clone_url: &str) -> Option<String> { | |||
| 556 | } else { | 563 | } else { |
| 557 | return None; | 564 | return None; |
| 558 | }; | 565 | }; |
| 559 | 566 | ||
| 560 | // Extract just the host:port part (everything before the first /) | 567 | // Extract just the host:port part (everything before the first /) |
| 561 | let host_port = rest.split('/').next()?; | 568 | let host_port = rest.split('/').next()?; |
| 562 | Some(format!("{}{}", ws_scheme, host_port)) | 569 | Some(format!("{}{}", ws_scheme, host_port)) |
| @@ -581,7 +588,7 @@ mod tests { | |||
| 581 | Some("ws://localhost:3000".to_string()) | 588 | Some("ws://localhost:3000".to_string()) |
| 582 | ); | 589 | ); |
| 583 | } | 590 | } |
| 584 | 591 | ||
| 585 | #[test] | 592 | #[test] |
| 586 | fn test_clone_url_to_relay_url_with_port() { | 593 | fn test_clone_url_to_relay_url_with_port() { |
| 587 | assert_eq!( | 594 | assert_eq!( |
| @@ -593,6 +600,9 @@ mod tests { | |||
| 593 | #[test] | 600 | #[test] |
| 594 | fn test_clone_url_to_relay_url_unsupported() { | 601 | fn test_clone_url_to_relay_url_unsupported() { |
| 595 | assert_eq!(clone_url_to_relay_url("git://example.com/repo.git"), None); | 602 | assert_eq!(clone_url_to_relay_url("git://example.com/repo.git"), None); |
| 596 | assert_eq!(clone_url_to_relay_url("ssh://git@example.com/repo.git"), None); | 603 | assert_eq!( |
| 604 | clone_url_to_relay_url("ssh://git@example.com/repo.git"), | ||
| 605 | None | ||
| 606 | ); | ||
| 597 | } | 607 | } |
| 598 | } \ No newline at end of file | 608 | } |
diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 9bbfb40..37ac3bb 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs | |||
| @@ -1,4 +1,6 @@ | |||
| 1 | //! Common test utilities | 1 | //! Common test utilities |
| 2 | #![allow(dead_code)] // Test helpers may not be used in all test configurations | ||
| 3 | #![allow(unused_imports)] // Re-exports may not be used in all test configurations | ||
| 2 | 4 | ||
| 3 | pub mod relay; | 5 | pub mod relay; |
| 4 | pub mod sync_helpers; | 6 | pub mod sync_helpers; |
diff --git a/tests/common/relay.rs b/tests/common/relay.rs index 2dd526b..55cc18e 100644 --- a/tests/common/relay.rs +++ b/tests/common/relay.rs | |||
| @@ -104,7 +104,11 @@ impl TestRelay { | |||
| 104 | } | 104 | } |
| 105 | 105 | ||
| 106 | /// Start relay with full options | 106 | /// Start relay with full options |
| 107 | async fn start_with_full_options(port: u16, bootstrap_relay_url: Option<String>, disable_negentropy: bool) -> Self { | 107 | async fn start_with_full_options( |
| 108 | port: u16, | ||
| 109 | bootstrap_relay_url: Option<String>, | ||
| 110 | disable_negentropy: bool, | ||
| 111 | ) -> Self { | ||
| 108 | let bind_address = format!("127.0.0.1:{}", port); | 112 | let bind_address = format!("127.0.0.1:{}", port); |
| 109 | let url = format!("ws://127.0.0.1:{}", port); | 113 | let url = format!("ws://127.0.0.1:{}", port); |
| 110 | 114 | ||
diff --git a/tests/common/sync_helpers.rs b/tests/common/sync_helpers.rs index 531ebe1..7fa0393 100644 --- a/tests/common/sync_helpers.rs +++ b/tests/common/sync_helpers.rs | |||
| @@ -173,7 +173,11 @@ impl TestClient { | |||
| 173 | /// # Returns | 173 | /// # Returns |
| 174 | /// * `Ok(Event)` - Signed event ready to send | 174 | /// * `Ok(Event)` - Signed event ready to send |
| 175 | /// * `Err(String)` - If signing fails | 175 | /// * `Err(String)` - If signing fails |
| 176 | pub fn build_layer2_issue_event(keys: &Keys, repo_coord: &str, title: &str) -> Result<Event, String> { | 176 | pub fn build_layer2_issue_event( |
| 177 | keys: &Keys, | ||
| 178 | repo_coord: &str, | ||
| 179 | title: &str, | ||
| 180 | ) -> Result<Event, String> { | ||
| 177 | build_layer2_issue_with_tag(keys, repo_coord, title, TagVariant::LowercaseA) | 181 | build_layer2_issue_with_tag(keys, repo_coord, title, TagVariant::LowercaseA) |
| 178 | } | 182 | } |
| 179 | 183 | ||
| @@ -256,10 +260,7 @@ pub fn build_layer3_comment_event( | |||
| 256 | // Choose tag based on kind (NIP-22 uses E, NIP-10 style uses e) | 260 | // Choose tag based on kind (NIP-22 uses E, NIP-10 style uses e) |
| 257 | let tag = if kind_num == KIND_COMMENT { | 261 | let tag = if kind_num == KIND_COMMENT { |
| 258 | // NIP-22 comment: uppercase 'E' tag | 262 | // NIP-22 comment: uppercase 'E' tag |
| 259 | Tag::custom( | 263 | Tag::custom(TagKind::custom("E"), vec![parent_event_id.to_hex()]) |
| 260 | TagKind::custom("E"), | ||
| 261 | vec![parent_event_id.to_hex()], | ||
| 262 | ) | ||
| 263 | } else { | 264 | } else { |
| 264 | // Kind 1 reply: lowercase 'e' tag with root marker (NIP-10) | 265 | // Kind 1 reply: lowercase 'e' tag with root marker (NIP-10) |
| 265 | Tag::custom( | 266 | Tag::custom( |
| @@ -299,10 +300,7 @@ pub fn build_layer3_comment_with_uppercase_e_tag( | |||
| 299 | parent_event_id: &EventId, | 300 | parent_event_id: &EventId, |
| 300 | content: &str, | 301 | content: &str, |
| 301 | ) -> Result<Event, String> { | 302 | ) -> Result<Event, String> { |
| 302 | let tag = Tag::custom( | 303 | let tag = Tag::custom(TagKind::custom("E"), vec![parent_event_id.to_hex()]); |
| 303 | TagKind::custom("E"), | ||
| 304 | vec![parent_event_id.to_hex()], | ||
| 305 | ); | ||
| 306 | 304 | ||
| 307 | EventBuilder::new(Kind::Custom(KIND_COMMENT), content) | 305 | EventBuilder::new(Kind::Custom(KIND_COMMENT), content) |
| 308 | .tags(vec![tag]) | 306 | .tags(vec![tag]) |
| @@ -316,10 +314,7 @@ pub fn build_layer3_quote_with_q_tag( | |||
| 316 | parent_event_id: &EventId, | 314 | parent_event_id: &EventId, |
| 317 | content: &str, | 315 | content: &str, |
| 318 | ) -> Result<Event, String> { | 316 | ) -> Result<Event, String> { |
| 319 | let tag = Tag::custom( | 317 | let tag = Tag::custom(TagKind::custom("q"), vec![parent_event_id.to_hex()]); |
| 320 | TagKind::custom("q"), | ||
| 321 | vec![parent_event_id.to_hex()], | ||
| 322 | ); | ||
| 323 | 318 | ||
| 324 | EventBuilder::new(Kind::Custom(1), content) | 319 | EventBuilder::new(Kind::Custom(1), content) |
| 325 | .tags(vec![tag]) | 320 | .tags(vec![tag]) |
| @@ -587,10 +582,7 @@ pub fn repo_coord(keys: &Keys, identifier: &str) -> String { | |||
| 587 | /// ``` | 582 | /// ``` |
| 588 | pub async fn fetch_metrics(relay_url: &str) -> Result<String, reqwest::Error> { | 583 | pub async fn fetch_metrics(relay_url: &str) -> Result<String, reqwest::Error> { |
| 589 | // Convert ws:// URL to http:// for metrics endpoint | 584 | // Convert ws:// URL to http:// for metrics endpoint |
| 590 | let http_url = relay_url | 585 | let http_url = relay_url.replace("ws://", "http://").replace("/", "") + "/metrics"; |
| 591 | .replace("ws://", "http://") | ||
| 592 | .replace("/", "") | ||
| 593 | + "/metrics"; | ||
| 594 | 586 | ||
| 595 | reqwest::get(&http_url).await?.text().await | 587 | reqwest::get(&http_url).await?.text().await |
| 596 | } | 588 | } |
| @@ -888,8 +880,8 @@ mod tests { | |||
| 888 | let keys = Keys::generate(); | 880 | let keys = Keys::generate(); |
| 889 | let coord = repo_coord(&keys, "my-repo"); | 881 | let coord = repo_coord(&keys, "my-repo"); |
| 890 | 882 | ||
| 891 | let event = build_layer2_issue_event(&keys, &coord, "Test Issue") | 883 | let event = |
| 892 | .expect("Should create event"); | 884 | build_layer2_issue_event(&keys, &coord, "Test Issue").expect("Should create event"); |
| 893 | 885 | ||
| 894 | // nostr-sdk 0.43: use field access | 886 | // nostr-sdk 0.43: use field access |
| 895 | assert_eq!(event.kind.as_u16(), KIND_ISSUE); | 887 | assert_eq!(event.kind.as_u16(), KIND_ISSUE); |
| @@ -937,8 +929,13 @@ mod tests { | |||
| 937 | let keys = Keys::generate(); | 929 | let keys = Keys::generate(); |
| 938 | let parent_id = EventId::all_zeros(); | 930 | let parent_id = EventId::all_zeros(); |
| 939 | 931 | ||
| 940 | let event = build_layer3_comment_event(&keys, &parent_id, "Test comment", Kind::Custom(KIND_COMMENT)) | 932 | let event = build_layer3_comment_event( |
| 941 | .expect("Should create event"); | 933 | &keys, |
| 934 | &parent_id, | ||
| 935 | "Test comment", | ||
| 936 | Kind::Custom(KIND_COMMENT), | ||
| 937 | ) | ||
| 938 | .expect("Should create event"); | ||
| 942 | 939 | ||
| 943 | assert_eq!(event.kind.as_u16(), KIND_COMMENT); | 940 | assert_eq!(event.kind.as_u16(), KIND_COMMENT); |
| 944 | 941 | ||
| @@ -980,8 +977,7 @@ mod tests { | |||
| 980 | 977 | ||
| 981 | let has_e_tag = event.tags.iter().any(|tag| { | 978 | let has_e_tag = event.tags.iter().any(|tag| { |
| 982 | let slice = tag.as_slice(); | 979 | let slice = tag.as_slice(); |
| 983 | slice.first().is_some_and(|t| t == "e") && | 980 | slice.first().is_some_and(|t| t == "e") && slice.get(3).is_some_and(|m| m == "root") |
| 984 | slice.get(3).is_some_and(|m| m == "root") | ||
| 985 | }); | 981 | }); |
| 986 | assert!(has_e_tag, "Should have 'e' tag with root marker"); | 982 | assert!(has_e_tag, "Should have 'e' tag with root marker"); |
| 987 | } | 983 | } |
| @@ -1038,7 +1034,10 @@ mod tests { | |||
| 1038 | fn test_parse_gauge_without_labels() { | 1034 | fn test_parse_gauge_without_labels() { |
| 1039 | let text = r#"ngit_sync_relays_tracked_total 3"#; | 1035 | let text = r#"ngit_sync_relays_tracked_total 3"#; |
| 1040 | let metrics = ParsedMetrics::parse(text); | 1036 | let metrics = ParsedMetrics::parse(text); |
| 1041 | assert_eq!(metrics.gauge("ngit_sync_relays_tracked_total", &[]), Some(3)); | 1037 | assert_eq!( |
| 1038 | metrics.gauge("ngit_sync_relays_tracked_total", &[]), | ||
| 1039 | Some(3) | ||
| 1040 | ); | ||
| 1042 | } | 1041 | } |
| 1043 | 1042 | ||
| 1044 | #[test] | 1043 | #[test] |
| @@ -1051,9 +1050,6 @@ mod tests { | |||
| 1051 | fn test_parse_metric_with_relay_url_label() { | 1050 | fn test_parse_metric_with_relay_url_label() { |
| 1052 | let text = r#"ngit_sync_relay_connected{relay="ws://127.0.0.1:12345"} 1"#; | 1051 | let text = r#"ngit_sync_relay_connected{relay="ws://127.0.0.1:12345"} 1"#; |
| 1053 | let metrics = ParsedMetrics::parse(text); | 1052 | let metrics = ParsedMetrics::parse(text); |
| 1054 | assert_eq!( | 1053 | assert_eq!(metrics.relay_connected("ws://127.0.0.1:12345"), Some(true)); |
| 1055 | metrics.relay_connected("ws://127.0.0.1:12345"), | ||
| 1056 | Some(true) | ||
| 1057 | ); | ||
| 1058 | } | 1054 | } |
| 1059 | } \ No newline at end of file | 1055 | } |
diff --git a/tests/nip77_negentropy.rs b/tests/nip77_negentropy.rs index c8e0b50..5293754 100644 --- a/tests/nip77_negentropy.rs +++ b/tests/nip77_negentropy.rs | |||
| @@ -45,13 +45,13 @@ async fn test_nip77_negentropy_sync_finds_events() { | |||
| 45 | let keys = Keys::generate(); | 45 | let keys = Keys::generate(); |
| 46 | 46 | ||
| 47 | // Create a repository announcement that will be accepted by the relay | 47 | // Create a repository announcement that will be accepted by the relay |
| 48 | let announcement = create_repo_announcement( | 48 | let announcement = create_repo_announcement(&keys, &[&relay.domain()], "test-repo-nip77"); |
| 49 | &keys, | ||
| 50 | &[&relay.domain()], | ||
| 51 | "test-repo-nip77", | ||
| 52 | ); | ||
| 53 | let event1_id = announcement.id; | 49 | let event1_id = announcement.id; |
| 54 | println!("Created event 1: {} (kind {})", event1_id, announcement.kind.as_u16()); | 50 | println!( |
| 51 | "Created event 1: {} (kind {})", | ||
| 52 | event1_id, | ||
| 53 | announcement.kind.as_u16() | ||
| 54 | ); | ||
| 55 | 55 | ||
| 56 | // Create a second event (issue referencing the repo) | 56 | // Create a second event (issue referencing the repo) |
| 57 | let repo_coord = format!( | 57 | let repo_coord = format!( |
| @@ -63,7 +63,11 @@ async fn test_nip77_negentropy_sync_finds_events() { | |||
| 63 | let issue = build_layer2_issue_event(&keys, &repo_coord, "Test issue for NIP-77") | 63 | let issue = build_layer2_issue_event(&keys, &repo_coord, "Test issue for NIP-77") |
| 64 | .expect("Failed to build issue event"); | 64 | .expect("Failed to build issue event"); |
| 65 | let event2_id = issue.id; | 65 | let event2_id = issue.id; |
| 66 | println!("Created event 2: {} (kind {})", event2_id, issue.kind.as_u16()); | 66 | println!( |
| 67 | "Created event 2: {} (kind {})", | ||
| 68 | event2_id, | ||
| 69 | issue.kind.as_u16() | ||
| 70 | ); | ||
| 67 | 71 | ||
| 68 | // 3. Send events to relay using TestClient | 72 | // 3. Send events to relay using TestClient |
| 69 | let publish_client = TestClient::new(relay.url(), keys.clone()) | 73 | let publish_client = TestClient::new(relay.url(), keys.clone()) |
| @@ -99,9 +103,10 @@ async fn test_nip77_negentropy_sync_finds_events() { | |||
| 99 | tokio::time::sleep(Duration::from_millis(500)).await; | 103 | tokio::time::sleep(Duration::from_millis(500)).await; |
| 100 | 104 | ||
| 101 | // 6. Perform negentropy sync with filter matching our events | 105 | // 6. Perform negentropy sync with filter matching our events |
| 102 | let filter = Filter::new() | 106 | let filter = Filter::new().author(keys.public_key()).kinds(vec![ |
| 103 | .author(keys.public_key()) | 107 | Kind::Custom(KIND_REPOSITORY_STATE), |
| 104 | .kinds(vec![Kind::Custom(KIND_REPOSITORY_STATE), Kind::Custom(KIND_ISSUE)]); | 108 | Kind::Custom(KIND_ISSUE), |
| 109 | ]); | ||
| 105 | 110 | ||
| 106 | println!("Starting negentropy sync with filter: {:?}", filter); | 111 | println!("Starting negentropy sync with filter: {:?}", filter); |
| 107 | 112 | ||
| @@ -177,7 +182,7 @@ async fn test_nip77_negentropy_sync_empty_result() { | |||
| 177 | 182 | ||
| 178 | // 3. Sync with filter that won't match anything | 183 | // 3. Sync with filter that won't match anything |
| 179 | let filter = Filter::new() | 184 | let filter = Filter::new() |
| 180 | .author(keys.public_key()) // Random new key, no events exist | 185 | .author(keys.public_key()) // Random new key, no events exist |
| 181 | .kind(Kind::Custom(KIND_REPOSITORY_STATE)); | 186 | .kind(Kind::Custom(KIND_REPOSITORY_STATE)); |
| 182 | 187 | ||
| 183 | println!("Starting negentropy sync with empty filter"); | 188 | println!("Starting negentropy sync with empty filter"); |
diff --git a/tests/sync.rs b/tests/sync.rs index 5b6b752..2e09fb8 100644 --- a/tests/sync.rs +++ b/tests/sync.rs | |||
| @@ -37,4 +37,4 @@ mod sync { | |||
| 37 | pub mod live_sync; | 37 | pub mod live_sync; |
| 38 | pub mod metrics; | 38 | pub mod metrics; |
| 39 | pub mod tag_variations; | 39 | pub mod tag_variations; |
| 40 | } \ No newline at end of file | 40 | } |
diff --git a/tests/sync/bootstrap.rs b/tests/sync/bootstrap.rs index 8a181c9..174fe28 100644 --- a/tests/sync/bootstrap.rs +++ b/tests/sync/bootstrap.rs | |||
| @@ -167,7 +167,8 @@ async fn test_relay_replays_events_after_restart() { | |||
| 167 | .kind(Kind::Custom(KIND_REPOSITORY_STATE)) | 167 | .kind(Kind::Custom(KIND_REPOSITORY_STATE)) |
| 168 | .author(keys.public_key()); | 168 | .author(keys.public_key()); |
| 169 | 169 | ||
| 170 | let synced_first = wait_for_event_on_relay(relay_b.url(), filter.clone(), Duration::from_secs(5)).await; | 170 | let synced_first = |
| 171 | wait_for_event_on_relay(relay_b.url(), filter.clone(), Duration::from_secs(5)).await; | ||
| 171 | println!("First sync check: {}", synced_first); | 172 | println!("First sync check: {}", synced_first); |
| 172 | 173 | ||
| 173 | // 8. Stop relay_b | 174 | // 8. Stop relay_b |
| @@ -193,7 +194,8 @@ async fn test_relay_replays_events_after_restart() { | |||
| 193 | // 12. Verify announcement is available on new relay_b | 194 | // 12. Verify announcement is available on new relay_b |
| 194 | // The announcement listed the OLD relay_b domain, but since relay_a still | 195 | // The announcement listed the OLD relay_b domain, but since relay_a still |
| 195 | // has the event, new relay_b should be able to sync it via bootstrap | 196 | // has the event, new relay_b should be able to sync it via bootstrap |
| 196 | let synced_after_restart = wait_for_event_on_relay(relay_b_new.url(), filter, Duration::from_secs(5)).await; | 197 | let synced_after_restart = |
| 198 | wait_for_event_on_relay(relay_b_new.url(), filter, Duration::from_secs(5)).await; | ||
| 197 | 199 | ||
| 198 | // 13. Cleanup | 200 | // 13. Cleanup |
| 199 | relay_b_new.stop().await; | 201 | relay_b_new.stop().await; |
| @@ -384,7 +386,8 @@ async fn test_history_sync_without_negentropy() { | |||
| 384 | relay_b_port, | 386 | relay_b_port, |
| 385 | Some(relay_a.url().into()), | 387 | Some(relay_a.url().into()), |
| 386 | true, // disable_negentropy = true | 388 | true, // disable_negentropy = true |
| 387 | ).await; | 389 | ) |
| 390 | .await; | ||
| 388 | println!( | 391 | println!( |
| 389 | "relay_b started at {} (domain: {}) - negentropy DISABLED, will do HISTORY sync", | 392 | "relay_b started at {} (domain: {}) - negentropy DISABLED, will do HISTORY sync", |
| 390 | relay_b.url(), | 393 | relay_b.url(), |
diff --git a/tests/sync/discovery.rs b/tests/sync/discovery.rs index 9e27f9e..ed3e9bb 100644 --- a/tests/sync/discovery.rs +++ b/tests/sync/discovery.rs | |||
| @@ -88,7 +88,8 @@ async fn test_discovers_layer3_via_layer2() { | |||
| 88 | ); | 88 | ); |
| 89 | 89 | ||
| 90 | // 6. Create a patch event (Layer 2) that references the announcement | 90 | // 6. Create a patch event (Layer 2) that references the announcement |
| 91 | let patch = create_event_referencing_repo(&keys, &repo_coord, KIND_PATCH, "Test patch proposal"); | 91 | let patch = |
| 92 | create_event_referencing_repo(&keys, &repo_coord, KIND_PATCH, "Test patch proposal"); | ||
| 92 | let patch_id = patch.id; | 93 | let patch_id = patch.id; |
| 93 | 94 | ||
| 94 | println!("Created patch {} (kind {})", patch_id, patch.kind.as_u16()); | 95 | println!("Created patch {} (kind {})", patch_id, patch.kind.as_u16()); |
| @@ -252,7 +253,8 @@ async fn test_layer2_discovery_with_chain() { | |||
| 252 | let issue_filter = Filter::new() | 253 | let issue_filter = Filter::new() |
| 253 | .kind(Kind::Custom(KIND_ISSUE)) | 254 | .kind(Kind::Custom(KIND_ISSUE)) |
| 254 | .author(keys.public_key()); | 255 | .author(keys.public_key()); |
| 255 | let issue_synced = wait_for_event_on_relay(relay_b.url(), issue_filter, Duration::from_secs(5)).await; | 256 | let issue_synced = |
| 257 | wait_for_event_on_relay(relay_b.url(), issue_filter, Duration::from_secs(5)).await; | ||
| 256 | 258 | ||
| 257 | println!("Sync result:"); | 259 | println!("Sync result:"); |
| 258 | println!(" Issue {} synced: {}", issue_id, issue_synced); | 260 | println!(" Issue {} synced: {}", issue_id, issue_synced); |
| @@ -296,7 +298,7 @@ async fn test_layer2_discovery_with_chain() { | |||
| 296 | #[tokio::test] | 298 | #[tokio::test] |
| 297 | async fn test_recursive_relay_discovery_syncs_announcement() { | 299 | async fn test_recursive_relay_discovery_syncs_announcement() { |
| 298 | // 1. Start all three relays | 300 | // 1. Start all three relays |
| 299 | 301 | ||
| 300 | // relay_b - will be the bootstrap relay, has announcement_x | 302 | // relay_b - will be the bootstrap relay, has announcement_x |
| 301 | let relay_b = TestRelay::start().await; | 303 | let relay_b = TestRelay::start().await; |
| 302 | println!( | 304 | println!( |
| @@ -344,7 +346,10 @@ async fn test_recursive_relay_discovery_syncs_announcement() { | |||
| 344 | "repo-y-ac-only", | 346 | "repo-y-ac-only", |
| 345 | ); | 347 | ); |
| 346 | let announcement_y_id = announcement_y.id; | 348 | let announcement_y_id = announcement_y.id; |
| 347 | println!("Created announcement_y {} listing A+C only", announcement_y_id); | 349 | println!( |
| 350 | "Created announcement_y {} listing A+C only", | ||
| 351 | announcement_y_id | ||
| 352 | ); | ||
| 348 | for tag in announcement_y.tags.iter() { | 353 | for tag in announcement_y.tags.iter() { |
| 349 | println!(" Tag: {:?}", tag.as_slice()); | 354 | println!(" Tag: {:?}", tag.as_slice()); |
| 350 | } | 355 | } |
| @@ -425,4 +430,4 @@ async fn test_recursive_relay_discovery_syncs_announcement() { | |||
| 425 | "announcement_y {} should have synced from discovered relay_c to relay_a (recursive discovery)", | 430 | "announcement_y {} should have synced from discovered relay_c to relay_a (recursive discovery)", |
| 426 | announcement_y_id | 431 | announcement_y_id |
| 427 | ); | 432 | ); |
| 428 | } \ No newline at end of file | 433 | } |
diff --git a/tests/sync/live_sync.rs b/tests/sync/live_sync.rs index ebe1c0b..7fa08a0 100644 --- a/tests/sync/live_sync.rs +++ b/tests/sync/live_sync.rs | |||
| @@ -229,7 +229,10 @@ async fn test_live_sync_layer3_events() { | |||
| 229 | .send_event(&comment) | 229 | .send_event(&comment) |
| 230 | .await | 230 | .await |
| 231 | .expect("Failed to send comment"); | 231 | .expect("Failed to send comment"); |
| 232 | println!("Layer 3 comment {} sent to relay_a BEFORE Layer 3 subscription established", comment_id); | 232 | println!( |
| 233 | "Layer 3 comment {} sent to relay_a BEFORE Layer 3 subscription established", | ||
| 234 | comment_id | ||
| 235 | ); | ||
| 233 | 236 | ||
| 234 | // 6. Now wait for issue to sync to relay_b (this triggers Layer 3 filter creation) | 237 | // 6. Now wait for issue to sync to relay_b (this triggers Layer 3 filter creation) |
| 235 | tokio::time::sleep(Duration::from_secs(2)).await; | 238 | tokio::time::sleep(Duration::from_secs(2)).await; |
| @@ -394,7 +397,7 @@ async fn test_live_sync_event_ordering() { | |||
| 394 | client_a | 397 | client_a |
| 395 | .send_event(&issue) | 398 | .send_event(&issue) |
| 396 | .await | 399 | .await |
| 397 | .expect(&format!("Failed to send issue {}", i)); | 400 | .unwrap_or_else(|_| panic!("Failed to send issue {}", i)); |
| 398 | 401 | ||
| 399 | // Delay between events to ensure different timestamps | 402 | // Delay between events to ensure different timestamps |
| 400 | tokio::time::sleep(Duration::from_millis(150)).await; | 403 | tokio::time::sleep(Duration::from_millis(150)).await; |
diff --git a/tests/sync/metrics.rs b/tests/sync/metrics.rs index 26d379d..14e1dfd 100644 --- a/tests/sync/metrics.rs +++ b/tests/sync/metrics.rs | |||
| @@ -32,7 +32,7 @@ async fn test_prometheus_format_valid() { | |||
| 32 | let relay = TestRelay::start().await; | 32 | let relay = TestRelay::start().await; |
| 33 | tokio::time::sleep(Duration::from_millis(500)).await; | 33 | tokio::time::sleep(Duration::from_millis(500)).await; |
| 34 | 34 | ||
| 35 | let metrics = fetch_metrics(&relay.url()) | 35 | let metrics = fetch_metrics(relay.url()) |
| 36 | .await | 36 | .await |
| 37 | .expect("Failed to fetch metrics"); | 37 | .expect("Failed to fetch metrics"); |
| 38 | 38 | ||
| @@ -67,7 +67,7 @@ async fn test_metrics_availability_during_sync() { | |||
| 67 | 67 | ||
| 68 | // Make multiple metrics requests while sync is active | 68 | // Make multiple metrics requests while sync is active |
| 69 | for i in 0..3 { | 69 | for i in 0..3 { |
| 70 | let metrics = fetch_metrics(&sync_relay.url()).await; | 70 | let metrics = fetch_metrics(sync_relay.url()).await; |
| 71 | assert!( | 71 | assert!( |
| 72 | metrics.is_ok(), | 72 | metrics.is_ok(), |
| 73 | "Metrics request {} should succeed during sync", | 73 | "Metrics request {} should succeed during sync", |
| @@ -135,7 +135,7 @@ async fn test_metric_values_are_numeric() { | |||
| 135 | let relay = TestRelay::start().await; | 135 | let relay = TestRelay::start().await; |
| 136 | tokio::time::sleep(Duration::from_millis(500)).await; | 136 | tokio::time::sleep(Duration::from_millis(500)).await; |
| 137 | 137 | ||
| 138 | let metrics = fetch_metrics(&relay.url()) | 138 | let metrics = fetch_metrics(relay.url()) |
| 139 | .await | 139 | .await |
| 140 | .expect("Should fetch metrics"); | 140 | .expect("Should fetch metrics"); |
| 141 | 141 | ||
diff --git a/tests/sync/tag_variations.rs b/tests/sync/tag_variations.rs index 273a573..41a6611 100644 --- a/tests/sync/tag_variations.rs +++ b/tests/sync/tag_variations.rs | |||
| @@ -57,11 +57,8 @@ async fn test_layer2_sync_with_lowercase_a_tag() { | |||
| 57 | 57 | ||
| 58 | // 2. Create and send repository announcement to both relays | 58 | // 2. Create and send repository announcement to both relays |
| 59 | let repo_id = "test-repo-tag-8a"; | 59 | let repo_id = "test-repo-tag-8a"; |
| 60 | let announcement = create_repo_announcement( | 60 | let announcement = |
| 61 | &keys, | 61 | create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id); |
| 62 | &[&relay_a.domain(), &relay_b.domain()], | ||
| 63 | repo_id, | ||
| 64 | ); | ||
| 65 | 62 | ||
| 66 | let client_a = TestClient::new(relay_a.url(), keys.clone()) | 63 | let client_a = TestClient::new(relay_a.url(), keys.clone()) |
| 67 | .await | 64 | .await |
| @@ -88,11 +85,16 @@ async fn test_layer2_sync_with_lowercase_a_tag() { | |||
| 88 | 85 | ||
| 89 | // 4. Create and send Layer 2 issue with lowercase 'a' tag | 86 | // 4. Create and send Layer 2 issue with lowercase 'a' tag |
| 90 | let repo_coordinate = repo_coord(&keys, repo_id); | 87 | let repo_coordinate = repo_coord(&keys, repo_id); |
| 91 | let issue = build_layer2_issue_event(&keys, &repo_coordinate, "Test Issue with lowercase a tag") | 88 | let issue = |
| 92 | .expect("Failed to create issue event"); | 89 | build_layer2_issue_event(&keys, &repo_coordinate, "Test Issue with lowercase a tag") |
| 90 | .expect("Failed to create issue event"); | ||
| 93 | let issue_id = issue.id; | 91 | let issue_id = issue.id; |
| 94 | 92 | ||
| 95 | println!("Created issue {} (kind {}) with lowercase 'a' tag", issue_id, issue.kind.as_u16()); | 93 | println!( |
| 94 | "Created issue {} (kind {}) with lowercase 'a' tag", | ||
| 95 | issue_id, | ||
| 96 | issue.kind.as_u16() | ||
| 97 | ); | ||
| 96 | for tag in issue.tags.iter() { | 98 | for tag in issue.tags.iter() { |
| 97 | println!(" Tag: {:?}", tag.as_slice()); | 99 | println!(" Tag: {:?}", tag.as_slice()); |
| 98 | } | 100 | } |
| @@ -154,11 +156,8 @@ async fn test_layer2_sync_with_uppercase_a_tag() { | |||
| 154 | 156 | ||
| 155 | // 2. Create and send repository announcement to both relays | 157 | // 2. Create and send repository announcement to both relays |
| 156 | let repo_id = "test-repo-tag-8b"; | 158 | let repo_id = "test-repo-tag-8b"; |
| 157 | let announcement = create_repo_announcement( | 159 | let announcement = |
| 158 | &keys, | 160 | create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id); |
| 159 | &[&relay_a.domain(), &relay_b.domain()], | ||
| 160 | repo_id, | ||
| 161 | ); | ||
| 162 | 161 | ||
| 163 | let client_a = TestClient::new(relay_a.url(), keys.clone()) | 162 | let client_a = TestClient::new(relay_a.url(), keys.clone()) |
| 164 | .await | 163 | .await |
| @@ -185,11 +184,19 @@ async fn test_layer2_sync_with_uppercase_a_tag() { | |||
| 185 | 184 | ||
| 186 | // 4. Create and send Layer 2 issue with uppercase 'A' tag | 185 | // 4. Create and send Layer 2 issue with uppercase 'A' tag |
| 187 | let repo_coordinate = repo_coord(&keys, repo_id); | 186 | let repo_coordinate = repo_coord(&keys, repo_id); |
| 188 | let issue = build_layer2_issue_with_uppercase_a_tag(&keys, &repo_coordinate, "Test Issue with uppercase A tag") | 187 | let issue = build_layer2_issue_with_uppercase_a_tag( |
| 189 | .expect("Failed to create issue event"); | 188 | &keys, |
| 189 | &repo_coordinate, | ||
| 190 | "Test Issue with uppercase A tag", | ||
| 191 | ) | ||
| 192 | .expect("Failed to create issue event"); | ||
| 190 | let issue_id = issue.id; | 193 | let issue_id = issue.id; |
| 191 | 194 | ||
| 192 | println!("Created issue {} (kind {}) with uppercase 'A' tag", issue_id, issue.kind.as_u16()); | 195 | println!( |
| 196 | "Created issue {} (kind {}) with uppercase 'A' tag", | ||
| 197 | issue_id, | ||
| 198 | issue.kind.as_u16() | ||
| 199 | ); | ||
| 193 | for tag in issue.tags.iter() { | 200 | for tag in issue.tags.iter() { |
| 194 | println!(" Tag: {:?}", tag.as_slice()); | 201 | println!(" Tag: {:?}", tag.as_slice()); |
| 195 | } | 202 | } |
| @@ -250,11 +257,8 @@ async fn test_layer2_sync_with_q_tag() { | |||
| 250 | 257 | ||
| 251 | // 2. Create and send repository announcement to both relays | 258 | // 2. Create and send repository announcement to both relays |
| 252 | let repo_id = "test-repo-tag-8c"; | 259 | let repo_id = "test-repo-tag-8c"; |
| 253 | let announcement = create_repo_announcement( | 260 | let announcement = |
| 254 | &keys, | 261 | create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id); |
| 255 | &[&relay_a.domain(), &relay_b.domain()], | ||
| 256 | repo_id, | ||
| 257 | ); | ||
| 258 | 262 | ||
| 259 | let client_a = TestClient::new(relay_a.url(), keys.clone()) | 263 | let client_a = TestClient::new(relay_a.url(), keys.clone()) |
| 260 | .await | 264 | .await |
| @@ -285,7 +289,11 @@ async fn test_layer2_sync_with_q_tag() { | |||
| 285 | .expect("Failed to create issue event"); | 289 | .expect("Failed to create issue event"); |
| 286 | let issue_id = issue.id; | 290 | let issue_id = issue.id; |
| 287 | 291 | ||
| 288 | println!("Created issue {} (kind {}) with 'q' tag", issue_id, issue.kind.as_u16()); | 292 | println!( |
| 293 | "Created issue {} (kind {}) with 'q' tag", | ||
| 294 | issue_id, | ||
| 295 | issue.kind.as_u16() | ||
| 296 | ); | ||
| 289 | for tag in issue.tags.iter() { | 297 | for tag in issue.tags.iter() { |
| 290 | println!(" Tag: {:?}", tag.as_slice()); | 298 | println!(" Tag: {:?}", tag.as_slice()); |
| 291 | } | 299 | } |
| @@ -350,11 +358,8 @@ async fn test_layer3_sync_with_lowercase_e_tag() { | |||
| 350 | 358 | ||
| 351 | // 2. Create and send repository announcement to both relays | 359 | // 2. Create and send repository announcement to both relays |
| 352 | let repo_id = "test-repo-tag-9a"; | 360 | let repo_id = "test-repo-tag-9a"; |
| 353 | let announcement = create_repo_announcement( | 361 | let announcement = |
| 354 | &keys, | 362 | create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id); |
| 355 | &[&relay_a.domain(), &relay_b.domain()], | ||
| 356 | repo_id, | ||
| 357 | ); | ||
| 358 | 363 | ||
| 359 | let client_a = TestClient::new(relay_a.url(), keys.clone()) | 364 | let client_a = TestClient::new(relay_a.url(), keys.clone()) |
| 360 | .await | 365 | .await |
| @@ -392,10 +397,9 @@ async fn test_layer3_sync_with_lowercase_e_tag() { | |||
| 392 | println!("Layer 2 issue {} sent to relay_a", issue_id); | 397 | println!("Layer 2 issue {} sent to relay_a", issue_id); |
| 393 | 398 | ||
| 394 | // 5. Wait for issue to sync to relay_b | 399 | // 5. Wait for issue to sync to relay_b |
| 395 | let issue_filter = Filter::new() | 400 | let issue_filter = Filter::new().kind(Kind::Custom(KIND_ISSUE)).id(issue_id); |
| 396 | .kind(Kind::Custom(KIND_ISSUE)) | 401 | let issue_synced = |
| 397 | .id(issue_id); | 402 | wait_for_event_on_relay(relay_b.url(), issue_filter, Duration::from_secs(5)).await; |
| 398 | let issue_synced = wait_for_event_on_relay(relay_b.url(), issue_filter, Duration::from_secs(5)).await; | ||
| 399 | println!("Issue synced to relay_b: {}", issue_synced); | 403 | println!("Issue synced to relay_b: {}", issue_synced); |
| 400 | assert!(issue_synced, "Layer 2 issue should sync first"); | 404 | assert!(issue_synced, "Layer 2 issue should sync first"); |
| 401 | 405 | ||
| @@ -412,7 +416,11 @@ async fn test_layer3_sync_with_lowercase_e_tag() { | |||
| 412 | .expect("Failed to create reply"); | 416 | .expect("Failed to create reply"); |
| 413 | let reply_id = reply.id; | 417 | let reply_id = reply.id; |
| 414 | 418 | ||
| 415 | println!("Created reply {} (kind {}) with lowercase 'e' tag", reply_id, reply.kind.as_u16()); | 419 | println!( |
| 420 | "Created reply {} (kind {}) with lowercase 'e' tag", | ||
| 421 | reply_id, | ||
| 422 | reply.kind.as_u16() | ||
| 423 | ); | ||
| 416 | for tag in reply.tags.iter() { | 424 | for tag in reply.tags.iter() { |
| 417 | println!(" Tag: {:?}", tag.as_slice()); | 425 | println!(" Tag: {:?}", tag.as_slice()); |
| 418 | } | 426 | } |
| @@ -428,11 +436,12 @@ async fn test_layer3_sync_with_lowercase_e_tag() { | |||
| 428 | 436 | ||
| 429 | // 7. Wait and verify reply syncs to relay_b | 437 | // 7. Wait and verify reply syncs to relay_b |
| 430 | let reply_filter = Filter::new() | 438 | let reply_filter = Filter::new() |
| 431 | .kind(Kind::TextNote) // Kind 1 | 439 | .kind(Kind::TextNote) // Kind 1 |
| 432 | .author(keys.public_key()) | 440 | .author(keys.public_key()) |
| 433 | .id(reply_id); | 441 | .id(reply_id); |
| 434 | 442 | ||
| 435 | let reply_synced = wait_for_event_on_relay(relay_b.url(), reply_filter, Duration::from_secs(5)).await; | 443 | let reply_synced = |
| 444 | wait_for_event_on_relay(relay_b.url(), reply_filter, Duration::from_secs(5)).await; | ||
| 436 | 445 | ||
| 437 | println!("Reply {} synced to relay_b: {}", reply_id, reply_synced); | 446 | println!("Reply {} synced to relay_b: {}", reply_id, reply_synced); |
| 438 | 447 | ||
| @@ -473,11 +482,8 @@ async fn test_layer3_sync_with_uppercase_e_tag() { | |||
| 473 | 482 | ||
| 474 | // 2. Create and send repository announcement to both relays | 483 | // 2. Create and send repository announcement to both relays |
| 475 | let repo_id = "test-repo-tag-9b"; | 484 | let repo_id = "test-repo-tag-9b"; |
| 476 | let announcement = create_repo_announcement( | 485 | let announcement = |
| 477 | &keys, | 486 | create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id); |
| 478 | &[&relay_a.domain(), &relay_b.domain()], | ||
| 479 | repo_id, | ||
| 480 | ); | ||
| 481 | 487 | ||
| 482 | let client_a = TestClient::new(relay_a.url(), keys.clone()) | 488 | let client_a = TestClient::new(relay_a.url(), keys.clone()) |
| 483 | .await | 489 | .await |
| @@ -515,10 +521,9 @@ async fn test_layer3_sync_with_uppercase_e_tag() { | |||
| 515 | println!("Layer 2 issue {} sent to relay_a", issue_id); | 521 | println!("Layer 2 issue {} sent to relay_a", issue_id); |
| 516 | 522 | ||
| 517 | // 5. Wait for issue to sync to relay_b | 523 | // 5. Wait for issue to sync to relay_b |
| 518 | let issue_filter = Filter::new() | 524 | let issue_filter = Filter::new().kind(Kind::Custom(KIND_ISSUE)).id(issue_id); |
| 519 | .kind(Kind::Custom(KIND_ISSUE)) | 525 | let issue_synced = |
| 520 | .id(issue_id); | 526 | wait_for_event_on_relay(relay_b.url(), issue_filter, Duration::from_secs(5)).await; |
| 521 | let issue_synced = wait_for_event_on_relay(relay_b.url(), issue_filter, Duration::from_secs(5)).await; | ||
| 522 | println!("Issue synced to relay_b: {}", issue_synced); | 527 | println!("Issue synced to relay_b: {}", issue_synced); |
| 523 | assert!(issue_synced, "Layer 2 issue should sync first"); | 528 | assert!(issue_synced, "Layer 2 issue should sync first"); |
| 524 | 529 | ||
| @@ -531,11 +536,16 @@ async fn test_layer3_sync_with_uppercase_e_tag() { | |||
| 531 | tokio::time::sleep(Duration::from_millis(500)).await; | 536 | tokio::time::sleep(Duration::from_millis(500)).await; |
| 532 | 537 | ||
| 533 | // 6. Create and send Layer 3 comment with uppercase 'E' tag (kind 1111) | 538 | // 6. Create and send Layer 3 comment with uppercase 'E' tag (kind 1111) |
| 534 | let comment = build_layer3_comment_with_uppercase_e_tag(&keys, &issue_id, "Comment with uppercase E tag") | 539 | let comment = |
| 535 | .expect("Failed to create comment"); | 540 | build_layer3_comment_with_uppercase_e_tag(&keys, &issue_id, "Comment with uppercase E tag") |
| 541 | .expect("Failed to create comment"); | ||
| 536 | let comment_id = comment.id; | 542 | let comment_id = comment.id; |
| 537 | 543 | ||
| 538 | println!("Created comment {} (kind {}) with uppercase 'E' tag", comment_id, comment.kind.as_u16()); | 544 | println!( |
| 545 | "Created comment {} (kind {}) with uppercase 'E' tag", | ||
| 546 | comment_id, | ||
| 547 | comment.kind.as_u16() | ||
| 548 | ); | ||
| 539 | for tag in comment.tags.iter() { | 549 | for tag in comment.tags.iter() { |
| 540 | println!(" Tag: {:?}", tag.as_slice()); | 550 | println!(" Tag: {:?}", tag.as_slice()); |
| 541 | } | 551 | } |
| @@ -551,13 +561,17 @@ async fn test_layer3_sync_with_uppercase_e_tag() { | |||
| 551 | 561 | ||
| 552 | // 7. Wait and verify comment syncs to relay_b | 562 | // 7. Wait and verify comment syncs to relay_b |
| 553 | let comment_filter = Filter::new() | 563 | let comment_filter = Filter::new() |
| 554 | .kind(Kind::Custom(KIND_COMMENT)) // Kind 1111 | 564 | .kind(Kind::Custom(KIND_COMMENT)) // Kind 1111 |
| 555 | .author(keys.public_key()) | 565 | .author(keys.public_key()) |
| 556 | .id(comment_id); | 566 | .id(comment_id); |
| 557 | 567 | ||
| 558 | let comment_synced = wait_for_event_on_relay(relay_b.url(), comment_filter, Duration::from_secs(5)).await; | 568 | let comment_synced = |
| 569 | wait_for_event_on_relay(relay_b.url(), comment_filter, Duration::from_secs(5)).await; | ||
| 559 | 570 | ||
| 560 | println!("Comment {} synced to relay_b: {}", comment_id, comment_synced); | 571 | println!( |
| 572 | "Comment {} synced to relay_b: {}", | ||
| 573 | comment_id, comment_synced | ||
| 574 | ); | ||
| 561 | 575 | ||
| 562 | // 8. Cleanup | 576 | // 8. Cleanup |
| 563 | relay_b.stop().await; | 577 | relay_b.stop().await; |
| @@ -596,11 +610,8 @@ async fn test_layer3_sync_with_q_tag() { | |||
| 596 | 610 | ||
| 597 | // 2. Create and send repository announcement to both relays | 611 | // 2. Create and send repository announcement to both relays |
| 598 | let repo_id = "test-repo-tag-9c"; | 612 | let repo_id = "test-repo-tag-9c"; |
| 599 | let announcement = create_repo_announcement( | 613 | let announcement = |
| 600 | &keys, | 614 | create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id); |
| 601 | &[&relay_a.domain(), &relay_b.domain()], | ||
| 602 | repo_id, | ||
| 603 | ); | ||
| 604 | 615 | ||
| 605 | let client_a = TestClient::new(relay_a.url(), keys.clone()) | 616 | let client_a = TestClient::new(relay_a.url(), keys.clone()) |
| 606 | .await | 617 | .await |
| @@ -638,10 +649,9 @@ async fn test_layer3_sync_with_q_tag() { | |||
| 638 | println!("Layer 2 issue {} sent to relay_a", issue_id); | 649 | println!("Layer 2 issue {} sent to relay_a", issue_id); |
| 639 | 650 | ||
| 640 | // 5. Wait for issue to sync to relay_b | 651 | // 5. Wait for issue to sync to relay_b |
| 641 | let issue_filter = Filter::new() | 652 | let issue_filter = Filter::new().kind(Kind::Custom(KIND_ISSUE)).id(issue_id); |
| 642 | .kind(Kind::Custom(KIND_ISSUE)) | 653 | let issue_synced = |
| 643 | .id(issue_id); | 654 | wait_for_event_on_relay(relay_b.url(), issue_filter, Duration::from_secs(5)).await; |
| 644 | let issue_synced = wait_for_event_on_relay(relay_b.url(), issue_filter, Duration::from_secs(5)).await; | ||
| 645 | println!("Issue synced to relay_b: {}", issue_synced); | 655 | println!("Issue synced to relay_b: {}", issue_synced); |
| 646 | assert!(issue_synced, "Layer 2 issue should sync first"); | 656 | assert!(issue_synced, "Layer 2 issue should sync first"); |
| 647 | 657 | ||
| @@ -658,7 +668,11 @@ async fn test_layer3_sync_with_q_tag() { | |||
| 658 | .expect("Failed to create quote"); | 668 | .expect("Failed to create quote"); |
| 659 | let quote_id = quote.id; | 669 | let quote_id = quote.id; |
| 660 | 670 | ||
| 661 | println!("Created quote {} (kind {}) with 'q' tag", quote_id, quote.kind.as_u16()); | 671 | println!( |
| 672 | "Created quote {} (kind {}) with 'q' tag", | ||
| 673 | quote_id, | ||
| 674 | quote.kind.as_u16() | ||
| 675 | ); | ||
| 662 | for tag in quote.tags.iter() { | 676 | for tag in quote.tags.iter() { |
| 663 | println!(" Tag: {:?}", tag.as_slice()); | 677 | println!(" Tag: {:?}", tag.as_slice()); |
| 664 | } | 678 | } |
| @@ -674,11 +688,12 @@ async fn test_layer3_sync_with_q_tag() { | |||
| 674 | 688 | ||
| 675 | // 7. Wait and verify quote syncs to relay_b | 689 | // 7. Wait and verify quote syncs to relay_b |
| 676 | let quote_filter = Filter::new() | 690 | let quote_filter = Filter::new() |
| 677 | .kind(Kind::TextNote) // Kind 1 | 691 | .kind(Kind::TextNote) // Kind 1 |
| 678 | .author(keys.public_key()) | 692 | .author(keys.public_key()) |
| 679 | .id(quote_id); | 693 | .id(quote_id); |
| 680 | 694 | ||
| 681 | let quote_synced = wait_for_event_on_relay(relay_b.url(), quote_filter, Duration::from_secs(5)).await; | 695 | let quote_synced = |
| 696 | wait_for_event_on_relay(relay_b.url(), quote_filter, Duration::from_secs(5)).await; | ||
| 682 | 697 | ||
| 683 | println!("Quote {} synced to relay_b: {}", quote_id, quote_synced); | 698 | println!("Quote {} synced to relay_b: {}", quote_id, quote_synced); |
| 684 | 699 | ||
| @@ -690,4 +705,4 @@ async fn test_layer3_sync_with_q_tag() { | |||
| 690 | quote_synced, | 705 | quote_synced, |
| 691 | "Layer 3 quote with 'q' tag should have synced to relay_b" | 706 | "Layer 3 quote with 'q' tag should have synced to relay_b" |
| 692 | ); | 707 | ); |
| 693 | } \ No newline at end of file | 708 | } |