Skip to content

Commit 31eca53

Browse files
committed
test(integration/subscription_processing): add testcase to verify last_synced timestamp update behavior
1 parent 9e53602 commit 31eca53

File tree

4 files changed

+183
-0
lines changed

4 files changed

+183
-0
lines changed

src/integration_tests/scenarios/subscription_processing.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ impl Scenario for SubscriptionProcessingScenario {
7171
.execute(&mut self.context)
7272
.await?;
7373

74+
// Test 6: Verify timestamp policy using a single builder-style test case
75+
VerifyLastSyncedTimestampTestCase::for_account_follow_event()
76+
.execute(&mut self.context)
77+
.await?;
78+
VerifyLastSyncedTimestampTestCase::for_global_metadata_event()
79+
.execute(&mut self.context)
80+
.await?;
81+
7482
Ok(())
7583
}
7684
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
pub mod publish_subscription_update;
2+
pub mod verify_last_synced_timestamp;
23

34
pub use publish_subscription_update::*;
5+
pub use verify_last_synced_timestamp::*;
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
use crate::integration_tests::core::*;
2+
use crate::WhitenoiseError;
3+
use async_trait::async_trait;
4+
use nostr_sdk::prelude::*;
5+
use std::time::Duration;
6+
7+
pub struct VerifyLastSyncedTimestampTestCase {
8+
mode: Mode,
9+
}
10+
11+
enum Mode {
12+
AccountFollowEvent,
13+
GlobalMetadataEvent,
14+
}
15+
16+
impl VerifyLastSyncedTimestampTestCase {
17+
pub fn for_account_follow_event() -> Self {
18+
Self {
19+
mode: Mode::AccountFollowEvent,
20+
}
21+
}
22+
23+
pub fn for_global_metadata_event() -> Self {
24+
Self {
25+
mode: Mode::GlobalMetadataEvent,
26+
}
27+
}
28+
29+
async fn baseline(
30+
&self,
31+
context: &ScenarioContext,
32+
pubkey: PublicKey,
33+
) -> Result<Option<chrono::DateTime<chrono::Utc>>, WhitenoiseError> {
34+
let fresh = context.whitenoise.find_account_by_pubkey(&pubkey).await?;
35+
Ok(fresh.last_synced_at)
36+
}
37+
38+
async fn assert_advanced(
39+
&self,
40+
context: &mut ScenarioContext,
41+
pubkey: PublicKey,
42+
before: Option<chrono::DateTime<chrono::Utc>>,
43+
description: &str,
44+
) -> Result<(), WhitenoiseError> {
45+
retry(
46+
50,
47+
Duration::from_millis(50),
48+
|| async {
49+
let refreshed = context.whitenoise.find_account_by_pubkey(&pubkey).await?;
50+
match (before, refreshed.last_synced_at) {
51+
(None, Some(_)) => Ok(()),
52+
(Some(b), Some(a)) if a > b => Ok(()),
53+
_ => Err(WhitenoiseError::Other(anyhow::anyhow!(
54+
"last_synced_at not advanced yet"
55+
))),
56+
}
57+
},
58+
description,
59+
)
60+
.await
61+
}
62+
63+
async fn assert_unchanged(
64+
&self,
65+
context: &mut ScenarioContext,
66+
pubkey: PublicKey,
67+
before: Option<chrono::DateTime<chrono::Utc>>,
68+
description: &str,
69+
) -> Result<(), WhitenoiseError> {
70+
retry(
71+
50,
72+
Duration::from_millis(50),
73+
|| async {
74+
let after = context
75+
.whitenoise
76+
.find_account_by_pubkey(&pubkey)
77+
.await?
78+
.last_synced_at;
79+
if after == before {
80+
Ok(())
81+
} else {
82+
Err(WhitenoiseError::Other(anyhow::anyhow!(
83+
"last_synced_at advanced on global-only event"
84+
)))
85+
}
86+
},
87+
description,
88+
)
89+
.await
90+
}
91+
92+
async fn publish_account_follow_event(
93+
&self,
94+
context: &ScenarioContext,
95+
pubkey: PublicKey,
96+
) -> Result<(), WhitenoiseError> {
97+
let account_owned = context.whitenoise.find_account_by_pubkey(&pubkey).await?;
98+
let nsec = context
99+
.whitenoise
100+
.export_account_nsec(&account_owned)
101+
.await?;
102+
let keys = Keys::parse(&nsec)?;
103+
let client = create_test_client(&context.dev_relays, keys).await?;
104+
let contact = Keys::generate().public_key();
105+
publish_follow_list(&client, &[contact]).await?;
106+
tokio::time::sleep(tokio::time::Duration::from_millis(600)).await;
107+
client.disconnect().await;
108+
Ok(())
109+
}
110+
111+
async fn publish_global_metadata_event(
112+
&self,
113+
context: &ScenarioContext,
114+
) -> Result<(), WhitenoiseError> {
115+
let ext = Keys::generate();
116+
let client = create_test_client(&context.dev_relays, ext).await?;
117+
let metadata = Metadata {
118+
name: Some("No-op for account sync".to_string()),
119+
..Default::default()
120+
};
121+
client
122+
.send_event_builder(EventBuilder::metadata(&metadata))
123+
.await?;
124+
tokio::time::sleep(tokio::time::Duration::from_millis(600)).await;
125+
client.disconnect().await;
126+
Ok(())
127+
}
128+
}
129+
130+
#[async_trait]
131+
impl TestCase for VerifyLastSyncedTimestampTestCase {
132+
async fn run(&self, context: &mut ScenarioContext) -> Result<(), WhitenoiseError> {
133+
let pubkey = { context.get_account("subscription_test_account")?.pubkey };
134+
let before = self.baseline(context, pubkey).await?;
135+
match self.mode {
136+
Mode::AccountFollowEvent => {
137+
self.publish_account_follow_event(context, pubkey).await?;
138+
self.assert_advanced(
139+
context,
140+
pubkey,
141+
before,
142+
"wait last_synced_at advance on account follow event",
143+
)
144+
.await?;
145+
}
146+
Mode::GlobalMetadataEvent => {
147+
self.publish_global_metadata_event(context).await?;
148+
self.assert_unchanged(
149+
context,
150+
pubkey,
151+
before,
152+
"ensure last_synced_at unchanged on global metadata",
153+
)
154+
.await?;
155+
}
156+
}
157+
158+
Ok(())
159+
}
160+
}

src/whitenoise/accounts.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,19 @@ impl Whitenoise {
814814

815815
// Compute per-account since with a 10s lookback buffer when available
816816
let since = account.since_timestamp(10);
817+
match since {
818+
Some(ts) => tracing::debug!(
819+
target: "whitenoise::setup_subscriptions",
820+
"Computed per-account since={}s (10s buffer) for {}",
821+
ts.as_u64(),
822+
account.pubkey.to_hex()
823+
),
824+
None => tracing::debug!(
825+
target: "whitenoise::setup_subscriptions",
826+
"Computed per-account since=None (unsynced) for {}",
827+
account.pubkey.to_hex()
828+
),
829+
}
817830

818831
let keys = self
819832
.secrets_store

0 commit comments

Comments
 (0)