use std::{collections::BTreeMap, sync::Arc};
use anyhow::{Context, Result, anyhow};
use nostr_sdk::prelude::*;
use rstest::*;
use test_harness::{
CloneLogin, Harness, KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, PublishPatchSeriesOpts,
PublishRepoOpts, Repo, event_branch_name_tag, tag_value,
};
use tokio::sync::OnceCell;
const IDENTIFIER: &str = "git-push-pr-patch-update-force-to-pr";
const BIG_FILE_BYTES: usize = 100 * 1024;
async fn assert_patch_event(harness: &Harness, commit_oid: &str) -> Result<()> {
let events = harness
.grasp("repo")
.events(Filter::new().kind(Kind::GitPatch))
.await?;
anyhow::ensure!(
events
.iter()
.any(|event| tag_value(event, "commit").as_deref() == Some(commit_oid)),
"successful push returned before GitPatch commit={commit_oid} was queryable; \
observed event ids: {:?}",
events
.iter()
.map(|event| event.id.to_hex())
.collect::<Vec<_>>(),
);
Ok(())
}
async fn assert_default_relay_event(
harness: &Harness,
kind: Kind,
event_id: EventId,
commit_oid: &str,
) -> Result<()> {
let events = harness
.relay("default")
.events(Filter::new().kind(kind))
.await?;
anyhow::ensure!(
events.iter().any(|event| {
event.id == event_id && tag_value(event, "c").as_deref() == Some(commit_oid)
}),
"successful push returned before default relay {kind} event {event_id} with \
commit={commit_oid} was queryable; observed events: {:?}",
events
.iter()
.map(|event| format!("id={} commit={:?}", event.id, tag_value(event, "c"),))
.collect::<Vec<_>>(),
);
Ok(())
}
async fn find_pr_ref(
harness: &Harness,
published: &test_harness::PublishedRepo,
kind: Kind,
branch_name: Option<&str>,
expected_oid: &str,
) -> Result<Event> {
let events = harness
.grasp("repo")
.events(Filter::new().kind(kind))
.await?;
let observed_events = events
.iter()
.map(|event| {
format!(
"id={} branch={:?} commit={:?}",
event.id,
event_branch_name_tag(event),
tag_value(event, "c"),
)
})
.collect::<Vec<_>>();
let event = events
.into_iter()
.find(|event| {
tag_value(event, "c").as_deref() == Some(expected_oid)
&& (branch_name.is_none() || event_branch_name_tag(event).as_deref() == branch_name)
})
.ok_or_else(|| {
anyhow!(
"successful push returned before {kind} event for branch={branch_name:?}, \
commit={expected_oid} was queryable; observed events: {observed_events:?}",
)
})?;
let ref_oid = harness
.grasp("repo")
.read_nostr_ref(&published.maintainer_npub, IDENTIFIER, &event.id.to_hex())
.await
.with_context(|| {
format!(
"successful push returned before GRASP ref for {kind} event {} was queryable",
event.id
)
})?;
anyhow::ensure!(
ref_oid == expected_oid,
"successful push returned with GRASP ref for {kind} event {} at {ref_oid}, \
expected {expected_oid}",
event.id,
);
Ok(event)
}
async fn assert_remote_ref(repo: &Repo, remote_ref: &str, expected_oid: &str) -> Result<()> {
repo.git_ok(["fetch", "origin"], "git fetch origin for proposal ref")
.await?;
let snapshot = repo.snapshot()?;
anyhow::ensure!(
snapshot.refs.get(remote_ref).map(String::as_str) == Some(expected_oid),
"successful push returned with {remote_ref} at {:?}, expected {expected_oid}",
snapshot.refs.get(remote_ref),
);
Ok(())
}
async fn ls_remote_ref(
harness: &Harness,
published: &test_harness::PublishedRepo,
expected_ref: &str,
expected_oid: &str,
) -> Result<BTreeMap<String, String>> {
let clone = harness
.clone_published_repo(published, CloneLogin::None)
.await
.context("failed to create fresh clone for proposal ref")?;
clone
.git_ok(
["config", "--local", "nostr.auto-pr-branches", "true"],
"enable automatic PR branches for ls-remote compatibility check",
)
.await?;
let output = clone
.git(["ls-remote", "origin"])
.output()
.await
.context("failed to spawn git ls-remote origin for proposal ref")?;
let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
let refs = stdout
.lines()
.filter(|line| !line.is_empty() && !line.starts_with("ref: "))
.filter_map(|line| line.split_once('\t'))
.map(|(oid, name)| (name.to_string(), oid.to_string()))
.collect::<BTreeMap<_, _>>();
anyhow::ensure!(
output.status.success() && refs.get(expected_ref).map(String::as_str) == Some(expected_oid),
"successful push returned before ls-remote advertised {expected_ref}={expected_oid}; \
status={:?}; refs={refs:?}; stderr={}",
output.status,
String::from_utf8_lossy(&output.stderr),
);
Ok(refs)
}
struct Snapshot {
all_patch_events: Vec<Event>,
pr_events: Vec<Event>,
pr_event: Event,
pr_update_count_after_upgrade: usize,
pr_update_event: Event,
pr_update_count_final: usize,
original_root_patch_id: EventId,
original_root_patch_pubkey: PublicKey,
original_branch_name: String,
amended_tip_oid: String,
followup_tip_oid: String,
maintainer_pubkey: PublicKey,
nostr_clone_ls_refs: BTreeMap<String, String>,
}
static SNAPSHOT: OnceCell<Arc<Snapshot>> = OnceCell::const_new();
#[fixture]
async fn snapshot() -> Arc<Snapshot> {
SNAPSHOT
.get_or_init(|| async {
Arc::new(
capture_snapshot().await.expect(
"git_push_pr::patch_update_force_to_pr fixture: capture_snapshot failed",
),
)
})
.await
.clone()
}
async fn capture_snapshot() -> Result<Snapshot> {
let harness = Harness::builder(
env!("CARGO_BIN_EXE_ngit"),
env!("CARGO_BIN_EXE_git-remote-nostr"),
)
.with_relay("default")
.with_grasp_server("repo")
.build()
.await?;
let (_publisher, published) = harness
.publish_repo(PublishRepoOpts {
display_name: Some("patch-update-force-to-pr maintainer".into()),
identifier: Some(IDENTIFIER.into()),
..Default::default()
})
.await?;
let maintainer_keys = Keys::parse(&published.maintainer_nsec)
.context("published.maintainer_nsec is not a valid key")?;
let maintainer_pubkey = maintainer_keys.public_key();
let series = harness
.publish_patch_series(&published, PublishPatchSeriesOpts::default())
.await?;
let original_root_patch = series
.patch_events
.iter()
.find(|e| event_branch_name_tag(e).as_deref() == Some(series.branch_name.as_str()))
.with_context(|| {
format!(
"no patch event with branch-name={:?} in publish_patch_series output; \
has the harness changed how it tags the series root?",
series.branch_name,
)
})?;
let original_root_patch_id = original_root_patch.id;
let original_root_patch_pubkey = original_root_patch.pubkey;
let shorthand = &original_root_patch.id.to_hex()[..8];
let remote_branch = format!("pr/{}({})", series.branch_name, shorthand);
let maintainer_clone = harness
.clone_published_repo(&published, CloneLogin::AsMaintainer)
.await?;
maintainer_clone
.git_ok(
["config", "--local", "nostr.auto-pr-branches", "true"],
"enable automatic PR branches for raw remote-ref checkout",
)
.await?;
maintainer_clone
.git_ok(["fetch", "origin"], "fetch automatic PR branches")
.await?;
let snap = maintainer_clone
.snapshot()
.context("snapshotting maintainer clone before checkout")?;
let remote_ref = format!("refs/remotes/origin/{remote_branch}");
snap.refs.get(&remote_ref).with_context(|| {
format!(
"{remote_ref} missing from maintainer clone after `git clone` — \
list.rs no longer advertises patch proposals as pr/<branch>(<shorthand>) \
for non-author viewers? Available refs: {:?}",
snap.refs.keys().collect::<Vec<_>>(),
)
})?;
maintainer_clone
.git_ok(
["checkout", &remote_branch],
&format!("git checkout {remote_branch}"),
)
.await?;
std::fs::write(
maintainer_clone.dir().join("maintainer-update.md"),
"maintainer follow-up\n",
)
.context("failed to write maintainer-update.md")?;
maintainer_clone
.git_ok(
["add", "maintainer-update.md"],
"git add maintainer-update.md",
)
.await?;
maintainer_clone
.git_ok(
["commit", "-m", "follow-up on patch series", "--no-gpg-sign"],
"git commit maintainer-update.md",
)
.await?;
let first_push_tip_oid = maintainer_clone
.rev_parse("HEAD")
.await
.context("rev-parse HEAD before first push")?;
maintainer_clone
.nostr_push(["origin", &remote_branch])
.await
.context("nostr_push of maintainer follow-up (first push) failed")?;
assert_patch_event(&harness, &first_push_tip_oid).await?;
let big_content: String = "x".repeat(BIG_FILE_BYTES);
std::fs::write(
maintainer_clone.dir().join("maintainer-update.md"),
&big_content,
)
.context("failed to write supersized maintainer-update.md")?;
maintainer_clone
.git_ok(
["add", "maintainer-update.md"],
"git add maintainer-update.md (amend step)",
)
.await?;
maintainer_clone
.git_ok(
[
"commit",
"--amend",
"-m",
"amended follow-up on patch series (big)",
"--no-gpg-sign",
],
"git commit --amend",
)
.await?;
let amended_tip_oid = maintainer_clone
.rev_parse("HEAD")
.await
.context("rev-parse HEAD after amend")?;
maintainer_clone
.nostr_push(["-f", "origin", &remote_branch])
.await
.context("nostr_push -f (force push after big-content amend) failed")?;
let pr_event_after_upgrade = find_pr_ref(
&harness,
&published,
KIND_PULL_REQUEST,
Some(&series.branch_name),
&amended_tip_oid,
)
.await?;
assert_default_relay_event(
&harness,
KIND_PULL_REQUEST,
pr_event_after_upgrade.id,
&amended_tip_oid,
)
.await?;
let remote_ref = format!("refs/remotes/origin/{remote_branch}");
assert_remote_ref(&maintainer_clone, &remote_ref, &amended_tip_oid).await?;
let pr_update_count_after_upgrade = harness
.grasp("repo")
.events(Filter::new().kind(KIND_PULL_REQUEST_UPDATE))
.await?
.len();
std::fs::write(
maintainer_clone.dir().join("followup.md"),
"second maintainer follow-up after the PR upgrade\n",
)
.context("failed to write followup.md")?;
maintainer_clone
.git_ok(["add", "followup.md"], "git add followup.md")
.await?;
maintainer_clone
.git_ok(
[
"commit",
"-m",
"follow-up after PR upgrade",
"--no-gpg-sign",
],
"git commit followup.md",
)
.await?;
let followup_tip_oid = maintainer_clone
.rev_parse("HEAD")
.await
.context("rev-parse HEAD after follow-up commit")?;
maintainer_clone
.nostr_push(["origin", &remote_branch])
.await
.context("nostr_push of follow-up commit (post-upgrade) failed")?;
let pr_update_event = find_pr_ref(
&harness,
&published,
KIND_PULL_REQUEST_UPDATE,
None,
&followup_tip_oid,
)
.await?;
anyhow::ensure!(
pr_update_event.created_at > pr_event_after_upgrade.created_at
|| (pr_update_event.created_at == pr_event_after_upgrade.created_at
&& pr_update_event.id < pr_event_after_upgrade.id),
"PR update must sort after its upgrade root under NIP-01: root={} at {}, update={} at {}",
pr_event_after_upgrade.id,
pr_event_after_upgrade.created_at,
pr_update_event.id,
pr_update_event.created_at,
);
assert_default_relay_event(
&harness,
KIND_PULL_REQUEST_UPDATE,
pr_update_event.id,
&followup_tip_oid,
)
.await?;
let all_patch_events = harness
.grasp("repo")
.events(Filter::new().kind(Kind::GitPatch))
.await?;
let pr_events = harness
.grasp("repo")
.events(Filter::new().kind(KIND_PULL_REQUEST))
.await?;
let pr_update_events = harness
.grasp("repo")
.events(Filter::new().kind(KIND_PULL_REQUEST_UPDATE))
.await?;
let pr_update_count_final = pr_update_events.len();
let pr_event = pr_events
.iter()
.find(|e| event_branch_name_tag(e).as_deref() == Some(series.branch_name.as_str()))
.cloned()
.ok_or_else(|| {
anyhow!(
"no KIND_PULL_REQUEST event on GRASP with branch-name={:?} after the \
force push; all PR event ids: {:?}",
series.branch_name,
pr_events.iter().map(|e| e.id.to_hex()).collect::<Vec<_>>(),
)
})?;
anyhow::ensure!(
pr_event.id == pr_event_after_upgrade.id,
"PR event changed between upgrade readiness and final snapshot: {} != {}",
pr_event_after_upgrade.id,
pr_event.id,
);
let pr_update_event_from_snapshot = pr_update_events
.iter()
.find(|e| tag_value(e, "c").as_deref() == Some(followup_tip_oid.as_str()))
.cloned()
.ok_or_else(|| {
anyhow!(
"no KIND_PULL_REQUEST_UPDATE event on GRASP with c={:?} after the \
follow-up push; all update event ids: {:?}",
followup_tip_oid,
pr_update_events
.iter()
.map(|e| e.id.to_hex())
.collect::<Vec<_>>(),
)
})?;
anyhow::ensure!(
pr_update_event_from_snapshot.id == pr_update_event.id,
"PR update event changed between readiness and final snapshot: {} != {}",
pr_update_event.id,
pr_update_event_from_snapshot.id,
);
let expected_advertised_ref = format!("refs/heads/{remote_branch}");
let nostr_clone_ls_refs = ls_remote_ref(
&harness,
&published,
&expected_advertised_ref,
&followup_tip_oid,
)
.await?;
Ok(Snapshot {
all_patch_events,
pr_events,
pr_event,
pr_update_count_after_upgrade,
pr_update_event,
pr_update_count_final,
original_root_patch_id,
original_root_patch_pubkey,
original_branch_name: series.branch_name.clone(),
amended_tip_oid,
followup_tip_oid,
maintainer_pubkey,
nostr_clone_ls_refs,
})
}
#[rstest]
#[tokio::test]
async fn three_patch_events_total(#[future] snapshot: Arc<Snapshot>) -> Result<()> {
let s = snapshot.await;
assert_eq!(
s.all_patch_events.len(),
3,
"expected exactly 3 Kind::GitPatch events on GRASP \
(2 original + 1 first push; force push must emit a PR not patches); \
got {} (event ids: {:?})",
s.all_patch_events.len(),
s.all_patch_events
.iter()
.map(|e| e.id.to_hex())
.collect::<Vec<_>>(),
);
Ok(())
}
#[rstest]
#[tokio::test]
async fn one_pr_event(#[future] snapshot: Arc<Snapshot>) -> Result<()> {
let s = snapshot.await;
assert_eq!(
s.pr_events.len(),
1,
"expected exactly one KIND_PULL_REQUEST event on GRASP after the \
size-triggered force push; got {} (event ids: {:?})",
s.pr_events.len(),
s.pr_events
.iter()
.map(|e| e.id.to_hex())
.collect::<Vec<_>>(),
);
Ok(())
}
#[rstest]
#[tokio::test]
async fn zero_pr_update_events_after_upgrade(#[future] snapshot: Arc<Snapshot>) -> Result<()> {
let s = snapshot.await;
assert_eq!(
s.pr_update_count_after_upgrade, 0,
"expected zero KIND_PULL_REQUEST_UPDATE events on GRASP immediately \
after the patch→PR upgrade force push; got {}",
s.pr_update_count_after_upgrade,
);
Ok(())
}
#[rstest]
#[tokio::test]
async fn pr_event_e_tag_references_original_root(#[future] snapshot: Arc<Snapshot>) -> Result<()> {
let s = snapshot.await;
let e_values: Vec<String> = s
.pr_event
.tags
.iter()
.filter(|t| t.as_slice().first().map(String::as_str) == Some("e"))
.filter_map(|t| t.as_slice().get(1).cloned())
.collect();
assert!(
e_values.contains(&s.original_root_patch_id.to_hex()),
"PR event should carry an `e` tag with the original root patch id {:?}; \
got e values: {:?}",
s.original_root_patch_id.to_hex(),
e_values,
);
Ok(())
}
#[rstest]
#[tokio::test]
async fn pr_event_p_tag_references_original_root_author(
#[future] snapshot: Arc<Snapshot>,
) -> Result<()> {
let s = snapshot.await;
let p_values: Vec<String> = s
.pr_event
.tags
.iter()
.filter(|t| t.as_slice().first().map(String::as_str) == Some("p"))
.filter_map(|t| t.as_slice().get(1).cloned())
.collect();
assert!(
p_values.contains(&s.original_root_patch_pubkey.to_hex()),
"PR event should carry a `p` tag with the original root patch author \
{:?}; got p values: {:?}",
s.original_root_patch_pubkey.to_hex(),
p_values,
);
Ok(())
}
#[rstest]
#[tokio::test]
async fn pr_event_branch_name_matches_original_series(
#[future] snapshot: Arc<Snapshot>,
) -> Result<()> {
let s = snapshot.await;
assert_eq!(
tag_value(&s.pr_event, "branch-name").as_deref(),
Some(s.original_branch_name.as_str()),
"PR event branch-name tag should be {:?} (carried over from the \
original root patch's cover letter); got {:?}",
s.original_branch_name,
tag_value(&s.pr_event, "branch-name"),
);
Ok(())
}
#[rstest]
#[tokio::test]
async fn pr_event_c_tag_is_amended_tip(#[future] snapshot: Arc<Snapshot>) -> Result<()> {
let s = snapshot.await;
assert_eq!(
tag_value(&s.pr_event, "c").as_deref(),
Some(s.amended_tip_oid.as_str()),
"PR event c tag should equal amended tip OID {:?}; got {:?}",
s.amended_tip_oid,
tag_value(&s.pr_event, "c"),
);
Ok(())
}
#[rstest]
#[tokio::test]
async fn pr_event_authored_by_maintainer(#[future] snapshot: Arc<Snapshot>) -> Result<()> {
let s = snapshot.await;
assert_eq!(
s.pr_event.pubkey,
s.maintainer_pubkey,
"upgrade PR event should be authored by the maintainer ({}) who ran \
the force push; got {}",
s.maintainer_pubkey.to_hex(),
s.pr_event.pubkey.to_hex(),
);
Ok(())
}
#[rstest]
#[tokio::test]
async fn pr_update_event_exists(#[future] snapshot: Arc<Snapshot>) -> Result<()> {
let s = snapshot.await;
assert_eq!(
s.pr_update_count_final, 1,
"expected exactly one KIND_PULL_REQUEST_UPDATE event on GRASP after \
the follow-up push; got {}",
s.pr_update_count_final,
);
Ok(())
}
#[rstest]
#[tokio::test]
async fn no_new_patch_events_from_followup(#[future] snapshot: Arc<Snapshot>) -> Result<()> {
let s = snapshot.await;
assert_eq!(
s.all_patch_events.len(),
3,
"expected exactly 3 Kind::GitPatch events on GRASP after the follow-up \
push (no new patches; the follow-up should be a PR-update, not a \
patch); got {} (event ids: {:?})",
s.all_patch_events.len(),
s.all_patch_events
.iter()
.map(|e| e.id.to_hex())
.collect::<Vec<_>>(),
);
Ok(())
}
#[rstest]
#[tokio::test]
async fn still_only_one_pr_event_after_followup(#[future] snapshot: Arc<Snapshot>) -> Result<()> {
let s = snapshot.await;
assert_eq!(
s.pr_events.len(),
1,
"expected exactly one KIND_PULL_REQUEST event on GRASP after the \
follow-up push (the upgrade PR is the unique thread root); got {} \
(event ids: {:?})",
s.pr_events.len(),
s.pr_events
.iter()
.map(|e| e.id.to_hex())
.collect::<Vec<_>>(),
);
Ok(())
}
#[rstest]
#[tokio::test]
async fn pr_update_event_e_tag_references_pr_event(
#[future] snapshot: Arc<Snapshot>,
) -> Result<()> {
let s = snapshot.await;
let e_values: Vec<String> = s
.pr_update_event
.tags
.iter()
.filter(|t| t.as_slice().first().map(String::as_str) == Some("E"))
.filter_map(|t| t.as_slice().get(1).cloned())
.collect();
assert!(
e_values.contains(&s.pr_event.id.to_hex()),
"PR-update event should carry an `E` tag with the upgrade PR event id \
{:?}; got E values: {:?}",
s.pr_event.id.to_hex(),
e_values,
);
Ok(())
}
#[rstest]
#[tokio::test]
async fn pr_update_event_e_tag_does_not_reference_original_patch_root(
#[future] snapshot: Arc<Snapshot>,
) -> Result<()> {
let s = snapshot.await;
let e_values: Vec<String> = s
.pr_update_event
.tags
.iter()
.filter(|t| t.as_slice().first().map(String::as_str) == Some("E"))
.filter_map(|t| t.as_slice().get(1).cloned())
.collect();
assert!(
!e_values.contains(&s.original_root_patch_id.to_hex()),
"PR-update event should NOT carry an `E` tag with the original patch \
root id {:?} (it should reference the upgrade PR event, not the \
pre-upgrade patch root); got E values: {:?}",
s.original_root_patch_id.to_hex(),
e_values,
);
Ok(())
}
#[rstest]
#[tokio::test]
async fn pr_update_event_p_tag_references_pr_event_author(
#[future] snapshot: Arc<Snapshot>,
) -> Result<()> {
let s = snapshot.await;
let p_values: Vec<String> = s
.pr_update_event
.tags
.iter()
.filter(|t| t.as_slice().first().map(String::as_str) == Some("P"))
.filter_map(|t| t.as_slice().get(1).cloned())
.collect();
assert!(
p_values.contains(&s.pr_event.pubkey.to_hex()),
"PR-update event should carry a `P` tag with the upgrade PR event \
author {:?}; got P values: {:?}",
s.pr_event.pubkey.to_hex(),
p_values,
);
Ok(())
}
#[rstest]
#[tokio::test]
async fn pr_update_event_c_tag_is_followup_tip(#[future] snapshot: Arc<Snapshot>) -> Result<()> {
let s = snapshot.await;
assert_eq!(
tag_value(&s.pr_update_event, "c").as_deref(),
Some(s.followup_tip_oid.as_str()),
"PR-update event c tag should equal the follow-up tip OID {:?}; got {:?}",
s.followup_tip_oid,
tag_value(&s.pr_update_event, "c"),
);
Ok(())
}
#[rstest]
#[tokio::test]
async fn pr_update_event_authored_by_maintainer(#[future] snapshot: Arc<Snapshot>) -> Result<()> {
let s = snapshot.await;
assert_eq!(
s.pr_update_event.pubkey,
s.maintainer_pubkey,
"PR-update event should be authored by the maintainer ({}) who ran \
the follow-up push; got {}",
s.maintainer_pubkey.to_hex(),
s.pr_update_event.pubkey.to_hex(),
);
Ok(())
}
fn branch_shaped_pr_refs<'a>(
map: &'a std::collections::BTreeMap<String, String>,
branch: &str,
) -> Vec<(&'a String, &'a String)> {
let needle_prefix = format!("pr/{branch}(");
map.iter()
.filter(|(name, _)| {
let suffix = name
.strip_prefix("refs/heads/")
.or_else(|| name.strip_prefix("refs/"))
.unwrap_or(name.as_str());
suffix.starts_with(&needle_prefix) && suffix.ends_with(')')
})
.collect()
}
#[rstest]
#[tokio::test]
async fn fresh_clone_exactly_one_pr_branch(#[future] snapshot: Arc<Snapshot>) -> Result<()> {
let s = snapshot.await;
let pr_branch_refs = branch_shaped_pr_refs(&s.nostr_clone_ls_refs, &s.original_branch_name);
assert_eq!(
pr_branch_refs.len(),
2,
"expected exactly two branch-shaped proposal advertisements \
(one each under `refs/pr/` and `refs/heads/pr/` for `{}`) in the \
fresh clone; got {} (full ls-remote map: {:#?})",
s.original_branch_name,
pr_branch_refs.len(),
s.nostr_clone_ls_refs,
);
Ok(())
}
#[rstest]
#[tokio::test]
async fn fresh_clone_pr_branch_uses_original_root_shorthand_and_latest_tip(
#[future] snapshot: Arc<Snapshot>,
) -> Result<()> {
let s = snapshot.await;
let pr_branch_refs = branch_shaped_pr_refs(&s.nostr_clone_ls_refs, &s.original_branch_name);
let (ref_name, oid) = pr_branch_refs.first().ok_or_else(|| {
anyhow!(
"no branch-shaped proposal advertisement in fresh clone ls-remote; \
full map: {:#?}",
s.nostr_clone_ls_refs,
)
})?;
let original_shorthand = &s.original_root_patch_id.to_hex()[..8];
let expected_suffix = format!("pr/{}({})", s.original_branch_name, original_shorthand);
assert!(
ref_name.ends_with(&expected_suffix),
"branch-shaped proposal advertisement {:?} should end in {:?} (using \
the original root patch id shorthand, not the PR upgrade event \
shorthand); full ls-remote map: {:#?}",
ref_name,
expected_suffix,
s.nostr_clone_ls_refs,
);
assert_eq!(
oid.as_str(),
s.followup_tip_oid.as_str(),
"branch-shaped proposal advertisement {:?} should resolve to the \
follow-up tip OID {:?} (read from the PR-update event's `c` tag); \
got {:?}",
ref_name,
s.followup_tip_oid,
oid,
);
Ok(())
}
#[rstest]
#[tokio::test]
async fn fresh_clone_does_not_advertise_pr_event_shorthand_branch(
#[future] snapshot: Arc<Snapshot>,
) -> Result<()> {
let s = snapshot.await;
let pr_event_shorthand = &s.pr_event.id.to_hex()[..8];
let bad_suffix = format!("pr/{}({})", s.original_branch_name, pr_event_shorthand);
let offending: Vec<&String> = s
.nostr_clone_ls_refs
.keys()
.filter(|name| name.ends_with(&bad_suffix))
.collect();
assert!(
offending.is_empty(),
"fresh clone should NOT advertise any ref ending in {:?} — the PR \
upgrade event must not appear as an independent proposal root. \
Offending refs: {:?}\nFull ls-remote map: {:#?}",
bad_suffix,
offending,
s.nostr_clone_ls_refs,
);
Ok(())
}