fn pending_publish_changes(core: &AppCore) -> u64 {
core.app_store
.shared()
.lock()
.unwrap()
.query_row("SELECT total_changes()", [], |row| row.get(0))
.unwrap()
}
#[test]
fn pending_publish_replays_do_not_rewrite_offline_outbox() {
let transport_keys = Keys::generate();
let owner = Keys::generate();
let device = Keys::generate();
let (mut core, updates, _dir) =
logged_in_test_core_with_updates("pending-publish-replay", &owner, &device);
let effects = (0..32)
.map(|index| {
ProtocolEffect::Publish(ProtocolPublish {
authored_at_secs: None,
event: EventBuilder::new(
Kind::from(MESSAGE_EVENT_KIND as u16),
format!("pending control {index} {}", "x".repeat(4096)),
)
.tag(nostr::Tag::public_key(device.public_key()))
.tag(nostr::Tag::parse(["v", "2"]).unwrap())
.sign_with_keys(&transport_keys)
.unwrap(),
chat_id: format!("pending-chat-{index}"),
inner_event_id: None,
})
})
.collect::<Vec<_>>();
core.process_protocol_engine_effects(effects.clone());
assert_eq!(core.pending_relay_publishes.len(), 32);
let original = core
.app_store
.load_pending_relay_publishes(&owner.public_key().to_hex())
.unwrap();
assert!(original.iter().all(|item| item.attempt_count == 1));
let writes = pending_publish_changes(&core);
let _ = updates.try_iter().count();
for _ in 0..12 {
core.process_protocol_engine_effects(effects.clone());
}
assert_eq!(
pending_publish_changes(&core),
writes,
"already-durable retry effects must not rewrite the outbox or failure state"
);
assert_eq!(
serde_json::to_value(
core.app_store
.load_pending_relay_publishes(&owner.public_key().to_hex())
.unwrap()
)
.unwrap(),
serde_json::to_value(original).unwrap()
);
assert!(
!updates
.try_iter()
.any(|update| matches!(update, AppUpdate::NearbyPublishedEvent { .. })),
"duplicate effects must use the existing paced nearby retry path"
);
}
#[test]
fn pending_publish_replay_preserves_attempts_and_enriches_metadata() {
let transport_keys = Keys::generate();
let owner = Keys::generate();
let device = Keys::generate();
let (mut core, _, _dir) =
logged_in_test_core_with_updates("pending-publish-metadata", &owner, &device);
let event = EventBuilder::new(Kind::from(MESSAGE_EVENT_KIND as u16), "queued")
.tag(nostr::Tag::public_key(device.public_key()))
.tag(nostr::Tag::parse(["v", "2"]).unwrap())
.sign_with_keys(&transport_keys)
.unwrap();
let id = event.id.to_hex();
core.publish_runtime_event(event.clone(), APPCORE_PROTOCOL_LABEL, None);
let mut pending = core.pending_relay_publishes[&id].clone();
pending.attempt_count = 7;
pending.last_error = Some("publish attempt in progress".into());
core.app_store
.upsert_pending_relay_publish(&pending)
.unwrap();
core.pending_relay_publishes
.insert(id.clone(), pending.clone());
core.pending_relay_publish_inflight.insert(id.clone());
core.relay_transport_runtime.publish_drain_in_flight = true;
core.relay_transport_runtime.publish_drain_started_at = Some(Instant::now());
core.logged_in.as_mut().unwrap().relay_urls =
relay_urls_from_strings(&["ws://127.0.0.1:1".into()]);
let chat = Keys::generate().public_key().to_hex();
core.push_outgoing_message_with_id(
"inner".into(),
&chat,
"body".into(),
unix_now().get(),
None,
DeliveryState::Queued,
);
assert!(core.publish_protocol_event(ProtocolPublish {
authored_at_secs: None,
event: event.clone(),
chat_id: chat.clone(),
inner_event_id: Some("inner".into()),
}));
let enriched = &core.pending_relay_publishes[&id];
assert_eq!(enriched.attempt_count, 7);
assert_eq!(enriched.last_error, pending.last_error);
assert_eq!(enriched.event_json, pending.event_json);
assert_eq!(enriched.chat_id.as_deref(), Some(chat.as_str()));
assert_eq!(enriched.inner_event_id.as_deref(), Some("inner"));
assert!(core.pending_relay_publish_inflight.contains(&id));
assert!(core.threads[&chat].messages[0]
.delivery_trace
.outer_event_ids
.contains(&id));
let writes = pending_publish_changes(&core);
assert!(core.publish_runtime_event(event, APPCORE_PROTOCOL_LABEL, None));
assert_eq!(pending_publish_changes(&core), writes);
assert_eq!(
core.pending_relay_publishes[&id].inner_event_id.as_deref(),
Some("inner")
);
let restored = core
.app_store
.load_pending_relay_publishes(&owner.public_key().to_hex())
.unwrap();
assert_eq!(restored[0].attempt_count, 7);
assert_eq!(restored[0].inner_event_id.as_deref(), Some("inner"));
}
#[test]
fn pending_identity_publication_advertises_without_resetting_durable_retries() {
let owner = Keys::generate();
let device = Keys::generate();
let (mut core, updates, _dir) =
logged_in_test_core_with_updates("pending-identity-republish", &owner, &device);
let now = unix_now().get();
core.app_keys.insert(
owner.public_key().to_hex(),
known_app_keys_from_ndr(
owner.public_key(),
&AppKeys::new(vec![DeviceEntry::new(device.public_key(), now)]),
now,
),
);
core.publish_local_identity_artifacts();
let identities = core.build_local_identity_artifacts().1;
assert_eq!(identities.len(), 2, "roster and local invitation");
for (_, event) in &identities {
let id = event.id.to_hex();
let pending = core.pending_relay_publishes.get_mut(&id).unwrap();
pending.attempt_count = 7;
pending.last_error = Some("publish attempt in progress".into());
core.app_store
.upsert_pending_relay_publish(pending)
.unwrap();
core.pending_relay_publish_inflight.insert(id);
}
core.relay_transport_runtime.publish_drain_in_flight = true;
let started = Instant::now();
core.relay_transport_runtime.publish_drain_started_at = Some(started);
core.relay_transport_runtime.nearby_replay_started_at = Some(started);
let original = core.pending_relay_publishes.clone();
let inflight = core.pending_relay_publish_inflight.clone();
let writes = pending_publish_changes(&core);
drain_app_updates(&updates);
core.publish_local_identity_artifacts();
let advertised = updates
.try_iter()
.filter_map(|update| match update {
AppUpdate::NearbyPublishedEvent { event_json, .. } => {
Some(serde_json::from_str::<Event>(&event_json).unwrap().id)
}
_ => None,
})
.collect::<Vec<_>>();
assert_eq!(advertised.len(), identities.len());
for (_, event) in &identities {
assert!(advertised.contains(&event.id));
}
assert_eq!(pending_publish_changes(&core), writes);
assert_eq!(core.pending_relay_publishes, original);
assert_eq!(core.pending_relay_publish_inflight, inflight);
assert!(core.relay_transport_runtime.publish_drain_in_flight);
assert_eq!(
core.relay_transport_runtime.publish_drain_started_at,
Some(started)
);
assert_eq!(
core.relay_transport_runtime.nearby_replay_started_at,
Some(started)
);
for _ in 0..12 {
for (label, event) in &identities {
assert!(core.publish_runtime_event(event.clone(), label, None));
}
}
assert_eq!(pending_publish_changes(&core), writes);
assert_eq!(core.pending_relay_publishes, original);
assert!(!updates
.try_iter()
.any(|update| matches!(update, AppUpdate::NearbyPublishedEvent { .. })));
}