mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-06 07:28:23 +00:00
test(deletion): add deep and shared-subgraph cascade coverage
This commit is contained in:
@@ -240,3 +240,100 @@ async fn test_announcement_cascade_deletes_served_pr_chain_kinds() {
|
||||
"announcement deletion should cascade across PR-chain dependent kinds; survivors: {survivors:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_announcement_cascade_deletes_deep_mixed_reference_chain() {
|
||||
let relay = TestRelay::start().await;
|
||||
let client = AuditClient::new(relay.url(), AuditConfig::isolated())
|
||||
.await
|
||||
.expect("create audit client");
|
||||
let keys = client.keys().clone();
|
||||
|
||||
let (announcement, repo_id) = publish_served_repo(&client, "cascade-deep-mixed-chain").await;
|
||||
let coordinate = announcement_coordinate(&announcement, &repo_id);
|
||||
|
||||
// Root-dependent issue and patch via `a` tags.
|
||||
let issue = client
|
||||
.create_issue(&announcement, "Deep chain issue", "issue body", vec![])
|
||||
.expect("build issue");
|
||||
let patch = client
|
||||
.event_builder(Kind::from(1617), "deep chain patch")
|
||||
.tag(Tag::custom("a", vec![coordinate.clone()]))
|
||||
.build(&keys)
|
||||
.expect("build patch");
|
||||
|
||||
// Mid-chain descendants mix `e` and `q` references.
|
||||
let comment = client
|
||||
.event_builder(Kind::from(1111), "comment on issue")
|
||||
.tag(Tag::custom("e", vec![issue.id.to_hex()]))
|
||||
.build(&keys)
|
||||
.expect("build issue comment");
|
||||
let quote = client
|
||||
.event_builder(Kind::from(1111), "quote patch and comment")
|
||||
.tag(Tag::custom("q", vec![patch.id.to_hex()]))
|
||||
.tag(Tag::custom("e", vec![comment.id.to_hex()]))
|
||||
.build(&keys)
|
||||
.expect("build quote event");
|
||||
|
||||
// Deep leaf status references quote by `q` and issue by `e`.
|
||||
let status = client
|
||||
.event_builder(Kind::from(1630), "deep chain status")
|
||||
.tag(Tag::custom("q", vec![quote.id.to_hex()]))
|
||||
.tag(Tag::custom("e", vec![issue.id.to_hex()]))
|
||||
.build(&keys)
|
||||
.expect("build deep status");
|
||||
|
||||
for event in [&issue, &patch, &comment, "e, &status] {
|
||||
client
|
||||
.send_event(event.clone())
|
||||
.await
|
||||
.expect("relay should accept deep-chain event");
|
||||
}
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(350)).await;
|
||||
|
||||
let deep_chain = [
|
||||
("announcement", announcement.id),
|
||||
("issue", issue.id),
|
||||
("patch", patch.id),
|
||||
("comment", comment.id),
|
||||
("quote", quote.id),
|
||||
("status", status.id),
|
||||
];
|
||||
|
||||
for (label, id) in deep_chain {
|
||||
assert!(
|
||||
client
|
||||
.is_event_on_relay(id)
|
||||
.await
|
||||
.expect("query event before deletion"),
|
||||
"{label} ({id}) must be served before deletion"
|
||||
);
|
||||
}
|
||||
|
||||
let deletion = build_deletion(&client, &[], std::slice::from_ref(&coordinate));
|
||||
client
|
||||
.send_event(deletion)
|
||||
.await
|
||||
.expect("relay should accept announcement deletion");
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(700)).await;
|
||||
|
||||
let mut survivors = Vec::new();
|
||||
for (label, id) in deep_chain {
|
||||
let served = client
|
||||
.is_event_on_relay(id)
|
||||
.await
|
||||
.expect("query event after deletion");
|
||||
if served {
|
||||
survivors.push(format!("{label} ({id})"));
|
||||
}
|
||||
}
|
||||
|
||||
relay.stop().await;
|
||||
|
||||
assert!(
|
||||
survivors.is_empty(),
|
||||
"announcement deletion should cascade through deep mixed-tag chains; survivors: {survivors:?}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -534,3 +534,136 @@ async fn test_three_maintainers_subset_anchors_survive_middle_deletion() {
|
||||
"announcement B coordinate must not be served after deletion"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_shared_subgraph_survives_when_one_anchor_path_removed() {
|
||||
let relay = TestRelay::start().await;
|
||||
let client_a = AuditClient::new(relay.url(), AuditConfig::isolated())
|
||||
.await
|
||||
.expect("create maintainer A client");
|
||||
let client_b =
|
||||
AuditClient::new_with_keys(relay.url(), AuditConfig::isolated(), Keys::generate())
|
||||
.await
|
||||
.expect("create maintainer B client");
|
||||
|
||||
let (announcement_a, repo_id) = publish_served_repo_with_maintainers(
|
||||
&client_a,
|
||||
"multi-shared-subgraph",
|
||||
&[client_b.public_key().to_hex()],
|
||||
)
|
||||
.await;
|
||||
let announcement_b = publish_served_announcement_for_identifier(&client_b, &repo_id).await;
|
||||
let coordinate_a = announcement_coordinate(&announcement_a, &repo_id);
|
||||
let coordinate_b = announcement_coordinate(&announcement_b, &repo_id);
|
||||
|
||||
// Top-level dependents under different anchoring conditions.
|
||||
let top_shared = client_a
|
||||
.event_builder(Kind::from(1617), "top shared A+B")
|
||||
.tag(Tag::custom("a", vec![coordinate_a.clone()]))
|
||||
.tag(Tag::custom("a", vec![coordinate_b.clone()]))
|
||||
.build(client_a.keys())
|
||||
.expect("build top shared event");
|
||||
let top_unique_a = client_a
|
||||
.event_builder(Kind::from(1617), "top unique A")
|
||||
.tag(Tag::custom("a", vec![coordinate_a.clone()]))
|
||||
.build(client_a.keys())
|
||||
.expect("build top unique A event");
|
||||
|
||||
// Shared descendant references both tops and should survive through top_shared.
|
||||
let shared_descendant = client_a
|
||||
.event_builder(Kind::from(1111), "shared descendant")
|
||||
.tag(Tag::custom("e", vec![top_shared.id.to_hex()]))
|
||||
.tag(Tag::custom("e", vec![top_unique_a.id.to_hex()]))
|
||||
.build(client_a.keys())
|
||||
.expect("build shared descendant");
|
||||
|
||||
// Unique branch descendant should be deleted with top_unique_a.
|
||||
let unique_descendant = client_a
|
||||
.event_builder(Kind::from(1111), "unique descendant")
|
||||
.tag(Tag::custom("e", vec![top_unique_a.id.to_hex()]))
|
||||
.build(client_a.keys())
|
||||
.expect("build unique descendant");
|
||||
|
||||
for event in [&top_shared, &top_unique_a, &shared_descendant, &unique_descendant] {
|
||||
client_a
|
||||
.send_event(event.clone())
|
||||
.await
|
||||
.expect("relay should accept shared-subgraph event");
|
||||
}
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(350)).await;
|
||||
|
||||
for (label, id) in [
|
||||
("announcement_a", announcement_a.id),
|
||||
("announcement_b", announcement_b.id),
|
||||
("top_shared", top_shared.id),
|
||||
("top_unique_a", top_unique_a.id),
|
||||
("shared_descendant", shared_descendant.id),
|
||||
("unique_descendant", unique_descendant.id),
|
||||
] {
|
||||
assert!(
|
||||
client_a
|
||||
.is_event_on_relay(id)
|
||||
.await
|
||||
.expect("query event before deletion"),
|
||||
"{label} ({id}) must be served before deletion"
|
||||
);
|
||||
}
|
||||
|
||||
let deletion_a = build_deletion(&client_a, &[], std::slice::from_ref(&coordinate_a));
|
||||
client_a
|
||||
.send_event(deletion_a)
|
||||
.await
|
||||
.expect("relay should accept announcement A deletion");
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(700)).await;
|
||||
|
||||
let announcement_a_survived = client_a
|
||||
.is_event_on_relay(announcement_a.id)
|
||||
.await
|
||||
.expect("query announcement A after deletion");
|
||||
let announcement_b_survived = client_b
|
||||
.is_event_on_relay(announcement_b.id)
|
||||
.await
|
||||
.expect("query announcement B after deletion");
|
||||
let top_shared_survived = client_a
|
||||
.is_event_on_relay(top_shared.id)
|
||||
.await
|
||||
.expect("query top shared after deletion");
|
||||
let top_unique_a_survived = client_a
|
||||
.is_event_on_relay(top_unique_a.id)
|
||||
.await
|
||||
.expect("query top unique A after deletion");
|
||||
let shared_descendant_survived = client_a
|
||||
.is_event_on_relay(shared_descendant.id)
|
||||
.await
|
||||
.expect("query shared descendant after deletion");
|
||||
let unique_descendant_survived = client_a
|
||||
.is_event_on_relay(unique_descendant.id)
|
||||
.await
|
||||
.expect("query unique descendant after deletion");
|
||||
|
||||
relay.stop().await;
|
||||
|
||||
assert!(
|
||||
!announcement_a_survived,
|
||||
"announcement A must be hard-deleted"
|
||||
);
|
||||
assert!(announcement_b_survived, "announcement B must survive");
|
||||
assert!(
|
||||
top_shared_survived,
|
||||
"top shared event must survive because anchor B remains"
|
||||
);
|
||||
assert!(
|
||||
!top_unique_a_survived,
|
||||
"top unique A event must be deleted after A deletion"
|
||||
);
|
||||
assert!(
|
||||
shared_descendant_survived,
|
||||
"shared descendant must survive through still-anchored top_shared path"
|
||||
);
|
||||
assert!(
|
||||
!unique_descendant_survived,
|
||||
"unique descendant must be deleted with the A-only branch"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user