Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/nostr/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1764,10 +1764,16 @@ mod tests {
let content = NotificationContentBuilder::new(&server_keys)
.with_apns_token("deadbeef12345678deadbeef12345678deadbeef12345678deadbeef12345678")
.build();
// One hour beyond the skew tolerance, not +1s: `process()` re-reads
// `Timestamp::now()` (1-second granularity) when it validates freshness,
// so a whole-second wall-clock tick between here and that read would
// collapse a +1s margin onto the exact threshold (`>` becomes `==`) and
// spuriously accept the event under load. A large margin exercises the
// same "created_at exceeds now + skew" rejection path, drift-proof.
let future_created_at = Timestamp::from_secs(
Timestamp::now()
.as_secs()
.saturating_add(DEFAULT_MAX_NOTIFICATION_FUTURE_SKEW_SECS + 1),
.saturating_add(DEFAULT_MAX_NOTIFICATION_FUTURE_SKEW_SECS + 3600),
);
let event = GiftWrapBuilder::new(server_keys.clone(), sender_keys)
.build_with_created_at(&content, future_created_at)
Expand Down
29 changes: 23 additions & 6 deletions src/shutdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,21 +315,31 @@ mod tests {

#[tokio::test]
async fn wait_for_signal_or_trigger_returns_on_internal_trigger() {
use std::future::Future;
use std::task::{Context, Waker};

let handler = ShutdownHandler::new();
let trigger = handler.trigger_handle();

let wait = handler.wait_for_signal_or_trigger();
tokio::pin!(wait);

// No signal and no trigger yet: the wait must still be pending.
let pending = timeout(Duration::from_millis(10), &mut wait).await;
assert!(pending.is_err());
// No signal and no trigger yet: the wait must still be pending. Poll
// once with a no-op waker instead of racing a wall-clock timeout — the
// first poll subscribes the receiver and registers the signal handlers,
// and with neither fired the future is deterministically `Pending`.
let mut cx = Context::from_waker(Waker::noop());
assert!(
wait.as_mut().poll(&mut cx).is_pending(),
"wait must be pending before any signal or trigger"
);

trigger.trigger();

let reason = timeout(Duration::from_secs(1), &mut wait)
.await
.expect("internal trigger must complete the shutdown wait");
// The trigger set the watch to `true`; awaiting the same future now
// resolves deterministically (a hang here would be a real bug the outer
// test harness surfaces, not a timing flake).
let reason = wait.await;
assert_eq!(reason, ShutdownReason::InternalTrigger);
}

Expand Down Expand Up @@ -382,6 +392,13 @@ mod tests {
assert_eq!(signal, ShutdownSignal::Sigterm);
}

#[test]
fn shutdown_signal_name_labels_each_variant() {
// These strings appear in the operator-facing shutdown logs.
assert_eq!(ShutdownSignal::CtrlC.name(), "Ctrl+C");
assert_eq!(ShutdownSignal::Sigterm.name(), "SIGTERM");
}

#[cfg(unix)]
#[tokio::test]
async fn wait_for_shutdown_signal_receives_sigterm() {
Expand Down