mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-05 15:08:24 +00:00
Replacement fixtures must not depend on crossing a wall-clock second or finding a lucky lower event ID. The same-second history test previously searched up to 100,000 nonces against a random predecessor, which can still fail when that predecessor has a sufficiently low ID. Add checked timestamp advancement and a re-signing helper that preserves fixture payloads and audit tags. Order recovery announcements after signed deletion cutoffs, repeated announcements after their prior revision, and recovery states after the parked announcement. Use explicit predecessor ordering in sync invitation, ownership replacement and state-fetch tests; remove sleeps whose only purpose was timestamp spacing. Preserve waits that exercise hot-cache expiry and explicit history/conflict timestamps. Return the original audit fixture's signed state through a companion helper without changing its Git commit or existing callers. History revisions can therefore follow that exact initial state. Build two nonce-bearing candidates at one timestamp and sort their IDs to exercise larger-to-smaller replacement and persistence across restart without mining. Document these fixture rules. This changes test construction only. It does not change production event ordering, introduce a shared clock, or import ngit's publishing/mining policy. The caller remains responsible for supplying the relevant predecessor or cutoff; timestamp overflow and signer changes fail explicitly. Validation: recovery and replaceable-history suites, full ngit-grasp suite, all-target workspace Clippy with warnings denied, formatting and diff checks. Assisted-by: GPT-6
54 lines
2.0 KiB
Rust
54 lines
2.0 KiB
Rust
//! Explicit timestamps for replacement fixtures; no wall-clock waits or ID mining.
|
|
|
|
use nostr_sdk::prelude::*;
|
|
|
|
/// Advance beyond a predecessor or deletion cutoff, including future timestamps.
|
|
/// The caller supplies the relevant predecessor; unrelated event coordinates do
|
|
/// not share a global clock.
|
|
pub fn timestamp_after(predecessor: Timestamp) -> Timestamp {
|
|
Timestamp::from_secs(
|
|
predecessor
|
|
.as_secs()
|
|
.checked_add(1)
|
|
.expect("fixture timestamp overflow"),
|
|
)
|
|
}
|
|
|
|
/// Re-sign a fabricated event after its predecessor, retaining its payload and
|
|
/// audit tags. Use explicit timestamps instead for historical/conflict cases.
|
|
pub fn event_after(event: Event, keys: &Keys, predecessor: Timestamp) -> Event {
|
|
assert_eq!(event.pubkey, keys.public_key(), "fixture signer changed");
|
|
EventBuilder::new(event.kind, event.content)
|
|
.tags(event.tags.to_vec())
|
|
.custom_created_at(timestamp_after(predecessor))
|
|
.finalize(keys)
|
|
.expect("sign ordered fixture event")
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn replacement_follows_future_predecessor_and_preserves_payload() {
|
|
let keys = Keys::generate();
|
|
let original = EventBuilder::new(Kind::RepoState, "state")
|
|
.tags([Tag::identifier("repo"), Tag::custom("t", ["audit-fixture"])])
|
|
.finalize(&keys)
|
|
.unwrap();
|
|
let future = Timestamp::from_secs(Timestamp::now().as_secs() + 100);
|
|
let replacement = event_after(original.clone(), &keys, future);
|
|
assert_eq!(replacement.created_at.as_secs(), future.as_secs() + 1);
|
|
assert_eq!(replacement.kind, original.kind);
|
|
assert_eq!(replacement.content, original.content);
|
|
assert_eq!(replacement.tags, original.tags);
|
|
replacement.verify().unwrap();
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic(expected = "fixture timestamp overflow")]
|
|
fn timestamp_overflow_is_explicit() {
|
|
timestamp_after(Timestamp::from_secs(u64::MAX));
|
|
}
|
|
}
|