upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/grasp-audit
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-02-26 15:42:09 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-02-26 15:42:09 +0000
commit9d86cf15f0275ffeee4519bd054e3b61dc8992ac (patch)
tree65b5d5ffb2a11b5ecd05d01e63fb5a4a0f8b6e06 /grasp-audit
parenta2ecfc5a63311570f0f90c7ee40117e289639cb8 (diff)
chore: apply cargo fmt and fix clippy warnings
Fix pre-existing clippy lints: - &PathBuf -> &Path in audit_cleanup.rs - too_many_arguments on process_newly_available_git_data, process_purgatory_announcements, and HttpService::new - clone_on_copy for PublicKey (Copy type) in purgatory cleanup loop
Diffstat (limited to 'grasp-audit')
-rw-r--r--grasp-audit/src/bin/grasp-audit.rs13
-rw-r--r--grasp-audit/src/fixtures.rs16
-rw-r--r--grasp-audit/src/probe.rs190
-rw-r--r--grasp-audit/src/specs/grasp01/purgatory.rs4
4 files changed, 143 insertions, 80 deletions
diff --git a/grasp-audit/src/bin/grasp-audit.rs b/grasp-audit/src/bin/grasp-audit.rs
index 305e5eb..ab835e7 100644
--- a/grasp-audit/src/bin/grasp-audit.rs
+++ b/grasp-audit/src/bin/grasp-audit.rs
@@ -132,7 +132,11 @@ async fn main() -> Result<()> {
132 println!("\n[Run {}]", run); 132 println!("\n[Run {}]", run);
133 } 133 }
134 let report = grasp_audit::probe::run_probe( 134 let report = grasp_audit::probe::run_probe(
135 &relay, keys.clone(), read_only, timeout, overall_secs, 135 &relay,
136 keys.clone(),
137 read_only,
138 timeout,
139 overall_secs,
136 ) 140 )
137 .await; 141 .await;
138 if json { 142 if json {
@@ -144,10 +148,9 @@ async fn main() -> Result<()> {
144 tokio::time::sleep(Duration::from_secs(interval)).await; 148 tokio::time::sleep(Duration::from_secs(interval)).await;
145 } 149 }
146 } else { 150 } else {
147 let report = grasp_audit::probe::run_probe( 151 let report =
148 &relay, keys, read_only, timeout, overall_secs, 152 grasp_audit::probe::run_probe(&relay, keys, read_only, timeout, overall_secs)
149 ) 153 .await;
150 .await;
151 if json { 154 if json {
152 report.print_json(); 155 report.print_json();
153 } else { 156 } else {
diff --git a/grasp-audit/src/fixtures.rs b/grasp-audit/src/fixtures.rs
index 4678790..d09c36b 100644
--- a/grasp-audit/src/fixtures.rs
+++ b/grasp-audit/src/fixtures.rs
@@ -967,7 +967,9 @@ impl<'a> TestContext<'a> {
967 FixtureKind::PREvent2Served => self.build_pr_event_2_served().await, 967 FixtureKind::PREvent2Served => self.build_pr_event_2_served().await,
968 968
969 FixtureKind::PurgatoryValidRepoSent => self.build_purgatory_valid_repo_sent().await, 969 FixtureKind::PurgatoryValidRepoSent => self.build_purgatory_valid_repo_sent().await,
970 FixtureKind::PurgatoryOwnerStateDataPushed => self.build_purgatory_owner_state_data_pushed().await, 970 FixtureKind::PurgatoryOwnerStateDataPushed => {
971 self.build_purgatory_owner_state_data_pushed().await
972 }
971 973
972 FixtureKind::OwnerStateDataPushed => self.build_owner_state_data_pushed().await, 974 FixtureKind::OwnerStateDataPushed => self.build_owner_state_data_pushed().await,
973 975
@@ -1147,7 +1149,10 @@ impl<'a> TestContext<'a> {
1147 Ok(h) => h, 1149 Ok(h) => h,
1148 Err(e) => { 1150 Err(e) => {
1149 cleanup(&clone_path); 1151 cleanup(&clone_path);
1150 return Err(anyhow::anyhow!("Failed to create deterministic commit: {}", e)); 1152 return Err(anyhow::anyhow!(
1153 "Failed to create deterministic commit: {}",
1154 e
1155 ));
1151 } 1156 }
1152 }; 1157 };
1153 1158
@@ -1186,7 +1191,12 @@ impl<'a> TestContext<'a> {
1186 DETERMINISTIC_COMMIT_HASH 1191 DETERMINISTIC_COMMIT_HASH
1187 )); 1192 ));
1188 } 1193 }
1189 Err(e) => return Err(anyhow::anyhow!("PurgatoryOwnerStateDataPushed push error: {}", e)), 1194 Err(e) => {
1195 return Err(anyhow::anyhow!(
1196 "PurgatoryOwnerStateDataPushed push error: {}",
1197 e
1198 ))
1199 }
1190 } 1200 }
1191 1201
1192 // ============================================================ 1202 // ============================================================
diff --git a/grasp-audit/src/probe.rs b/grasp-audit/src/probe.rs
index ecbbcc9..8dad1d4 100644
--- a/grasp-audit/src/probe.rs
+++ b/grasp-audit/src/probe.rs
@@ -167,10 +167,7 @@ fn now_iso8601() -> String {
167 let mo = if mp < 10 { mp + 3 } else { mp - 9 }; // month [1, 12] 167 let mo = if mp < 10 { mp + 3 } else { mp - 9 }; // month [1, 12]
168 let yr = if mo <= 2 { y + 1 } else { y }; 168 let yr = if mo <= 2 { y + 1 } else { y };
169 169
170 format!( 170 format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z", yr, mo, d, h, m, s)
171 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
172 yr, mo, d, h, m, s
173 )
174} 171}
175 172
176// ============================================================ 173// ============================================================
@@ -246,8 +243,7 @@ pub async fn run_probe(
246 error: Some(error_msg), 243 error: Some(error_msg),
247 }); 244 });
248 // Skip all subsequent checks 245 // Skip all subsequent checks
249 let already: std::collections::HashSet<&str> = 246 let already: std::collections::HashSet<&str> = $checks.iter().map(|c| c.name).collect();
250 $checks.iter().map(|c| c.name).collect();
251 for name in ALL_CHECK_NAMES { 247 for name in ALL_CHECK_NAMES {
252 if !already.contains(name) { 248 if !already.contains(name) {
253 $checks.push(skipped(name, "overall timeout")); 249 $checks.push(skipped(name, "overall timeout"));
@@ -303,8 +299,8 @@ pub async fn run_probe(
303 let clone_url = format!("{}/{}/{}.git", http_base, npub, repo_id); 299 let clone_url = format!("{}/{}/{}.git", http_base, npub, repo_id);
304 300
305 // Create temp dir for local repo 301 // Create temp dir for local repo
306 let local_repo_path = std::env::temp_dir() 302 let local_repo_path =
307 .join(format!("grasp-probe-{}", uuid::Uuid::new_v4())); 303 std::env::temp_dir().join(format!("grasp-probe-{}", uuid::Uuid::new_v4()));
308 304
309 // Initialise local repo (offline) 305 // Initialise local repo (offline)
310 let init_result = init_local_repo(&local_repo_path, &clone_url); 306 let init_result = init_local_repo(&local_repo_path, &clone_url);
@@ -368,7 +364,14 @@ pub async fn run_probe(
368 // Step 1: connect_websocket 364 // Step 1: connect_websocket
369 // ============================================================ 365 // ============================================================
370 if Instant::now() >= deadline { 366 if Instant::now() >= deadline {
371 deadline_return!(relay_url, timestamp, total_start, overall_secs, checks, "connect_websocket"); 367 deadline_return!(
368 relay_url,
369 timestamp,
370 total_start,
371 overall_secs,
372 checks,
373 "connect_websocket"
374 );
372 } 375 }
373 let step1_start = Instant::now(); 376 let step1_start = Instant::now();
374 let client_result = tokio::time::timeout( 377 let client_result = tokio::time::timeout(
@@ -426,12 +429,21 @@ pub async fn run_probe(
426 // ============================================================ 429 // ============================================================
427 { 430 {
428 if Instant::now() >= deadline { 431 if Instant::now() >= deadline {
429 deadline_return!(relay_url, timestamp, total_start, overall_secs, checks, "nip11_fetch"); 432 deadline_return!(
433 relay_url,
434 timestamp,
435 total_start,
436 overall_secs,
437 checks,
438 "nip11_fetch"
439 );
430 } 440 }
431 let step2_start = Instant::now(); 441 let step2_start = Instant::now();
432 let http_client = reqwest::Client::new(); 442 let http_client = reqwest::Client::new();
433 let nip11_result = tokio::time::timeout( 443 let nip11_result = tokio::time::timeout(
434 deadline.saturating_duration_since(Instant::now()).min(Duration::from_secs(timeout_secs)), 444 deadline
445 .saturating_duration_since(Instant::now())
446 .min(Duration::from_secs(timeout_secs)),
435 http_client 447 http_client
436 .get(&http_base) 448 .get(&http_base)
437 .header("Accept", "application/nostr+json") 449 .header("Accept", "application/nostr+json")
@@ -443,24 +455,20 @@ pub async fn run_probe(
443 455
444 match nip11_result { 456 match nip11_result {
445 Ok(Ok(resp)) if resp.status().is_success() => { 457 Ok(Ok(resp)) if resp.status().is_success() => {
446 let detail = resp 458 let detail = resp.json::<serde_json::Value>().await.ok().map(|v| {
447 .json::<serde_json::Value>() 459 let name = v.get("name").and_then(|n| n.as_str()).unwrap_or("unknown");
448 .await 460 // software is typically a repo URL; take the last path segment
449 .ok() 461 let software = v
450 .map(|v| { 462 .get("software")
451 let name = v.get("name").and_then(|n| n.as_str()).unwrap_or("unknown"); 463 .and_then(|s| s.as_str())
452 // software is typically a repo URL; take the last path segment 464 .map(|s| s.trim_end_matches('/').rsplit('/').next().unwrap_or(s))
453 let software = v 465 .unwrap_or("unknown");
454 .get("software") 466 let version = v
455 .and_then(|s| s.as_str()) 467 .get("version")
456 .map(|s| s.trim_end_matches('/').rsplit('/').next().unwrap_or(s)) 468 .and_then(|ver| ver.as_str())
457 .unwrap_or("unknown"); 469 .unwrap_or("unknown");
458 let version = v 470 format!("{} ({} v{})", name, software, version)
459 .get("version") 471 });
460 .and_then(|ver| ver.as_str())
461 .unwrap_or("unknown");
462 format!("{} ({} v{})", name, software, version)
463 });
464 checks.push(ProbeCheck { 472 checks.push(ProbeCheck {
465 name: "nip11_fetch", 473 name: "nip11_fetch",
466 passed: true, 474 passed: true,
@@ -509,7 +517,14 @@ pub async fn run_probe(
509 let mut write_succeeded = false; 517 let mut write_succeeded = false;
510 518
511 if Instant::now() >= deadline { 519 if Instant::now() >= deadline {
512 deadline_return!(relay_url, timestamp, total_start, overall_secs, checks, "publish_events"); 520 deadline_return!(
521 relay_url,
522 timestamp,
523 total_start,
524 overall_secs,
525 checks,
526 "publish_events"
527 );
513 } 528 }
514 529
515 if read_only { 530 if read_only {
@@ -558,10 +573,7 @@ pub async fn run_probe(
558 error: Some(e.to_string()), 573 error: Some(e.to_string()),
559 }); 574 });
560 // Skip steps 4 and 5; step 6 will use fallback 575 // Skip steps 4 and 5; step 6 will use fallback
561 checks.push(skipped( 576 checks.push(skipped("git_repo_initialised", "publish_events failed"));
562 "git_repo_initialised",
563 "publish_events failed",
564 ));
565 checks.push(skipped("git_push", "publish_events failed")); 577 checks.push(skipped("git_push", "publish_events failed"));
566 } 578 }
567 } 579 }
@@ -571,7 +583,14 @@ pub async fn run_probe(
571 // ============================================================ 583 // ============================================================
572 if write_succeeded { 584 if write_succeeded {
573 if Instant::now() >= deadline { 585 if Instant::now() >= deadline {
574 deadline_return!(relay_url, timestamp, total_start, overall_secs, checks, "git_repo_initialised"); 586 deadline_return!(
587 relay_url,
588 timestamp,
589 total_start,
590 overall_secs,
591 checks,
592 "git_repo_initialised"
593 );
575 } 594 }
576 let step4_start = Instant::now(); 595 let step4_start = Instant::now();
577 let poll_url = format!("{}/info/refs?service=git-upload-pack", clone_url); 596 let poll_url = format!("{}/info/refs?service=git-upload-pack", clone_url);
@@ -624,7 +643,14 @@ pub async fn run_probe(
624 // ============================================================ 643 // ============================================================
625 if write_succeeded { 644 if write_succeeded {
626 if Instant::now() >= deadline { 645 if Instant::now() >= deadline {
627 deadline_return!(relay_url, timestamp, total_start, overall_secs, checks, "git_push"); 646 deadline_return!(
647 relay_url,
648 timestamp,
649 total_start,
650 overall_secs,
651 checks,
652 "git_push"
653 );
628 } 654 }
629 let step5_start = Instant::now(); 655 let step5_start = Instant::now();
630 let push_result = try_push(&local_repo_path); 656 let push_result = try_push(&local_repo_path);
@@ -690,8 +716,14 @@ pub async fn run_probe(
690 continue; 716 continue;
691 } 717 }
692 let mut parts = content.splitn(2, ' '); 718 let mut parts = content.splitn(2, ' ');
693 let hash = match parts.next() { Some(h) if h.len() == 40 => h, _ => continue }; 719 let hash = match parts.next() {
694 let refname = match parts.next() { Some(r) => r.trim(), None => continue }; 720 Some(h) if h.len() == 40 => h,
721 _ => continue,
722 };
723 let refname = match parts.next() {
724 Some(r) => r.trim(),
725 None => continue,
726 };
695 // Skip refs/nostr/* — only branches (refs/heads/*) and tags (refs/tags/*) 727 // Skip refs/nostr/* — only branches (refs/heads/*) and tags (refs/tags/*)
696 if refname.starts_with("refs/nostr/") { 728 if refname.starts_with("refs/nostr/") {
697 continue; 729 continue;
@@ -705,14 +737,23 @@ pub async fn run_probe(
705 // ---- Write path ---- 737 // ---- Write path ----
706 // Step 6a: git_fetch_refs — just verify the endpoint returns 200 738 // Step 6a: git_fetch_refs — just verify the endpoint returns 200
707 if Instant::now() >= deadline { 739 if Instant::now() >= deadline {
708 deadline_return!(relay_url, timestamp, total_start, overall_secs, checks, "git_fetch_refs"); 740 deadline_return!(
741 relay_url,
742 timestamp,
743 total_start,
744 overall_secs,
745 checks,
746 "git_fetch_refs"
747 );
709 } 748 }
710 let refs_url = format!("{}/info/refs?service=git-upload-pack", clone_url); 749 let refs_url = format!("{}/info/refs?service=git-upload-pack", clone_url);
711 let http_client = reqwest::Client::new(); 750 let http_client = reqwest::Client::new();
712 751
713 let step6_start = Instant::now(); 752 let step6_start = Instant::now();
714 let refs_result = tokio::time::timeout( 753 let refs_result = tokio::time::timeout(
715 deadline.saturating_duration_since(Instant::now()).min(Duration::from_secs(timeout_secs)), 754 deadline
755 .saturating_duration_since(Instant::now())
756 .min(Duration::from_secs(timeout_secs)),
716 http_client.get(&refs_url).send(), 757 http_client.get(&refs_url).send(),
717 ) 758 )
718 .await; 759 .await;
@@ -833,14 +874,23 @@ pub async fn run_probe(
833 874
834 // In read-only mode: first check that at least one announcement exists 875 // In read-only mode: first check that at least one announcement exists
835 if Instant::now() >= deadline { 876 if Instant::now() >= deadline {
836 deadline_return!(relay_url, timestamp, total_start, overall_secs, checks, "serves_latest_announcement"); 877 deadline_return!(
878 relay_url,
879 timestamp,
880 total_start,
881 overall_secs,
882 checks,
883 "serves_latest_announcement"
884 );
837 } 885 }
838 let filter = Filter::new().kind(Kind::GitRepoAnnouncement).limit(1); 886 let filter = Filter::new().kind(Kind::GitRepoAnnouncement).limit(1);
839 let existing = client 887 let existing = client
840 .client() 888 .client()
841 .fetch_events( 889 .fetch_events(
842 filter, 890 filter,
843 deadline.saturating_duration_since(Instant::now()).min(Duration::from_secs(5)), 891 deadline
892 .saturating_duration_since(Instant::now())
893 .min(Duration::from_secs(5)),
844 ) 894 )
845 .await 895 .await
846 .unwrap_or_default(); 896 .unwrap_or_default();
@@ -909,18 +959,25 @@ pub async fn run_probe(
909 .find(|t| t.kind() == TagKind::custom("clone")) 959 .find(|t| t.kind() == TagKind::custom("clone"))
910 .and_then(|t| t.content()) 960 .and_then(|t| t.content())
911 .map(|s| s.to_string()) 961 .map(|s| s.to_string())
912 .unwrap_or_else(|| { 962 .unwrap_or_else(|| format!("{}/{}/{}.git", http_base, ann_npub, ann_id));
913 format!("{}/{}/{}.git", http_base, ann_npub, ann_id)
914 });
915 963
916 if Instant::now() >= deadline { 964 if Instant::now() >= deadline {
917 deadline_return!(relay_url, timestamp, total_start, overall_secs, checks, "git_fetch_refs"); 965 deadline_return!(
966 relay_url,
967 timestamp,
968 total_start,
969 overall_secs,
970 checks,
971 "git_fetch_refs"
972 );
918 } 973 }
919 let step6_start = Instant::now(); 974 let step6_start = Instant::now();
920 let refs_url = format!("{}/info/refs?service=git-upload-pack", fetch_url); 975 let refs_url = format!("{}/info/refs?service=git-upload-pack", fetch_url);
921 let http_client = reqwest::Client::new(); 976 let http_client = reqwest::Client::new();
922 let refs_result = tokio::time::timeout( 977 let refs_result = tokio::time::timeout(
923 deadline.saturating_duration_since(Instant::now()).min(Duration::from_secs(timeout_secs)), 978 deadline
979 .saturating_duration_since(Instant::now())
980 .min(Duration::from_secs(timeout_secs)),
924 http_client.get(&refs_url).send(), 981 http_client.get(&refs_url).send(),
925 ) 982 )
926 .await; 983 .await;
@@ -935,7 +992,7 @@ pub async fn run_probe(
935 passed: true, 992 passed: true,
936 skipped: false, 993 skipped: false,
937 duration_ms: step6_ms, 994 duration_ms: step6_ms,
938 detail: None, 995 detail: None,
939 error: None, 996 error: None,
940 }); 997 });
941 Some(body) 998 Some(body)
@@ -946,7 +1003,7 @@ pub async fn run_probe(
946 passed: false, 1003 passed: false,
947 skipped: false, 1004 skipped: false,
948 duration_ms: step6_ms, 1005 duration_ms: step6_ms,
949 detail: None, 1006 detail: None,
950 error: Some(format!("HTTP {}", resp.status())), 1007 error: Some(format!("HTTP {}", resp.status())),
951 }); 1008 });
952 None 1009 None
@@ -957,7 +1014,7 @@ pub async fn run_probe(
957 passed: false, 1014 passed: false,
958 skipped: false, 1015 skipped: false,
959 duration_ms: step6_ms, 1016 duration_ms: step6_ms,
960 detail: None, 1017 detail: None,
961 error: Some(e.to_string()), 1018 error: Some(e.to_string()),
962 }); 1019 });
963 None 1020 None
@@ -968,7 +1025,7 @@ pub async fn run_probe(
968 passed: false, 1025 passed: false,
969 skipped: false, 1026 skipped: false,
970 duration_ms: step6_ms, 1027 duration_ms: step6_ms,
971 detail: None, 1028 detail: None,
972 error: Some("timeout".to_string()), 1029 error: Some("timeout".to_string()),
973 }); 1030 });
974 None 1031 None
@@ -981,10 +1038,7 @@ pub async fn run_probe(
981 // including recursive maintainer chains), then compare against git refs. 1038 // including recursive maintainer chains), then compare against git refs.
982 match refs_body_fallback { 1039 match refs_body_fallback {
983 None => { 1040 None => {
984 checks.push(skipped( 1041 checks.push(skipped("git_refs_match_state", "git_fetch_refs failed"));
985 "git_refs_match_state",
986 "git_fetch_refs failed",
987 ));
988 } 1042 }
989 Some(body) => { 1043 Some(body) => {
990 let fetched_refs = parse_refs(&body); 1044 let fetched_refs = parse_refs(&body);
@@ -992,19 +1046,19 @@ pub async fn run_probe(
992 // Fetch all state events for this repo_id from the relay. 1046 // Fetch all state events for this repo_id from the relay.
993 // The relay only serves authorized state events (owner + full 1047 // The relay only serves authorized state events (owner + full
994 // recursive maintainer chain already resolved by the relay). 1048 // recursive maintainer chain already resolved by the relay).
995 let state_filter = Filter::new() 1049 let state_filter = Filter::new().kind(Kind::RepoState).custom_tag(
996 .kind(Kind::RepoState) 1050 nostr_sdk::prelude::SingleLetterTag::lowercase(
997 .custom_tag( 1051 nostr_sdk::prelude::Alphabet::D,
998 nostr_sdk::prelude::SingleLetterTag::lowercase( 1052 ),
999 nostr_sdk::prelude::Alphabet::D, 1053 ann_id.clone(),
1000 ), 1054 );
1001 ann_id.clone(),
1002 );
1003 let state_events = client 1055 let state_events = client
1004 .client() 1056 .client()
1005 .fetch_events( 1057 .fetch_events(
1006 state_filter, 1058 state_filter,
1007 deadline.saturating_duration_since(Instant::now()).min(Duration::from_secs(5)), 1059 deadline
1060 .saturating_duration_since(Instant::now())
1061 .min(Duration::from_secs(5)),
1008 ) 1062 )
1009 .await 1063 .await
1010 .unwrap_or_default(); 1064 .unwrap_or_default();
@@ -1046,7 +1100,8 @@ pub async fn run_probe(
1046 Some(h) => h.to_string(), 1100 Some(h) => h.to_string(),
1047 None => continue, 1101 None => continue,
1048 }; 1102 };
1049 let prev_ts = latest_ts.get(kind_str.as_ref()).copied().unwrap_or(0); 1103 let prev_ts =
1104 latest_ts.get(kind_str.as_ref()).copied().unwrap_or(0);
1050 if ts >= prev_ts { 1105 if ts >= prev_ts {
1051 expected.insert(kind_str.to_string(), hash); 1106 expected.insert(kind_str.to_string(), hash);
1052 latest_ts.insert(kind_str.to_string(), ts); 1107 latest_ts.insert(kind_str.to_string(), ts);
@@ -1103,10 +1158,7 @@ pub async fn run_probe(
1103 detail: None, 1158 detail: None,
1104 error: Some("no repositories found on relay".to_string()), 1159 error: Some("no repositories found on relay".to_string()),
1105 }); 1160 });
1106 checks.push(skipped( 1161 checks.push(skipped("git_refs_match_state", "no announcement found"));
1107 "git_refs_match_state",
1108 "no announcement found",
1109 ));
1110 } 1162 }
1111 } 1163 }
1112 } 1164 }
diff --git a/grasp-audit/src/specs/grasp01/purgatory.rs b/grasp-audit/src/specs/grasp01/purgatory.rs
index 0686da8..fdc1e32 100644
--- a/grasp-audit/src/specs/grasp01/purgatory.rs
+++ b/grasp-audit/src/specs/grasp01/purgatory.rs
@@ -54,9 +54,7 @@ impl PurgatoryTests {
54 54
55 // Deletion event tests (NIP-09) 55 // Deletion event tests (NIP-09)
56 results.add(Self::test_deletion_by_event_id_removes_purgatory_state_event(client).await); 56 results.add(Self::test_deletion_by_event_id_removes_purgatory_state_event(client).await);
57 results.add( 57 results.add(Self::test_deletion_by_coordinate_removes_purgatory_state_event(client).await);
58 Self::test_deletion_by_coordinate_removes_purgatory_state_event(client).await,
59 );
60 58
61 // PR purgatory tests 59 // PR purgatory tests
62 results.add(Self::test_pr_event_accepted_into_purgatory_and_isnt_served(client).await); 60 results.add(Self::test_pr_event_accepted_into_purgatory_and_isnt_served(client).await);