@@ -165,3 +165,94 @@ async fn desktop_profile_authenticated_owner_query_and_private_storage() {
165165 }
166166 }
167167}
168+
169+ #[ tokio:: test]
170+ #[ ignore = "requires Postgres" ]
171+ async fn aged_desktop_profile_retries_through_production_ingest_without_resigning ( ) {
172+ let mut state = bridge_handler_test_state ( )
173+ . await
174+ . expect ( "test infrastructure" ) ;
175+ Arc :: make_mut ( & mut Arc :: get_mut ( & mut state) . unwrap ( ) . config ) . require_auth_token = true ;
176+ let host = format ! ( "desktop-retry-{}.example" , uuid:: Uuid :: new_v4( ) . simple( ) ) ;
177+ state. db . ensure_configured_community ( & host) . await . unwrap ( ) ;
178+ let owner = Keys :: generate ( ) ;
179+ let outsider = Keys :: generate ( ) ;
180+ let profile = buzz_core:: desktop_profile:: DesktopProfile :: new (
181+ format ! ( "wss://{host}" ) ,
182+ uuid:: Uuid :: new_v4 ( ) . simple ( ) . to_string ( ) ,
183+ )
184+ . unwrap ( ) ;
185+ let prepared = profile. sign ( & owner) . unwrap ( ) ;
186+ // Model bytes committed during yesterday's offline first launch. Neither
187+ // the first submission nor its duplicate is re-dated or re-signed below.
188+ let now = Timestamp :: now ( ) . as_secs ( ) ;
189+ let aged = EventBuilder :: new ( prepared. kind , & prepared. content )
190+ . tags ( prepared. tags . iter ( ) . cloned ( ) )
191+ . custom_created_at ( Timestamp :: from ( now - 86_400 ) )
192+ . sign_with_keys ( & owner)
193+ . unwrap ( ) ;
194+ let raw = json ! ( aged) ;
195+ for _ in 0 ..2 {
196+ let ( status, result) = post ( & state, & host, "/events" , & owner, raw. clone ( ) , true ) . await ;
197+ assert_eq ! ( status, StatusCode :: OK , "{result}" ) ;
198+ assert_eq ! ( result[ "accepted" ] , true , "{result}" ) ;
199+ let ( status, rows) = post (
200+ & state,
201+ & host,
202+ "/query" ,
203+ & owner,
204+ json ! ( [ { "kinds" : [ KIND_DESKTOP_PROFILE ] , "authors" : [ owner. public_key( ) . to_hex( ) ] , "ids" : [ aged. id. to_hex( ) ] } ] ) ,
205+ true ,
206+ )
207+ . await ;
208+ assert_eq ! ( status, StatusCode :: OK , "{rows}" ) ;
209+ assert_eq ! ( rows. as_array( ) . unwrap( ) . len( ) , 1 ) ;
210+ for field in [
211+ "id" ,
212+ "pubkey" ,
213+ "kind" ,
214+ "created_at" ,
215+ "tags" ,
216+ "content" ,
217+ "sig" ,
218+ ] {
219+ assert_eq ! ( rows[ 0 ] [ field] , raw[ field] , "stored {field} changed" ) ;
220+ }
221+ let stored: nostr:: Event = serde_json:: from_value ( rows[ 0 ] . clone ( ) ) . unwrap ( ) ;
222+ assert_eq ! (
223+ buzz_core:: desktop_profile:: DesktopProfile :: read(
224+ & stored,
225+ & owner,
226+ & format!( "wss://{host}" )
227+ )
228+ . unwrap( ) ,
229+ profile
230+ ) ;
231+ }
232+ // The age exception grants no signer authority and bypasses no envelope or
233+ // signature checks. These calls use the real HTTP -> shared ingest path.
234+ let ( status, result) = post ( & state, & host, "/events" , & outsider, raw. clone ( ) , true ) . await ;
235+ assert_eq ! ( status, StatusCode :: FORBIDDEN , "{result}" ) ;
236+ let mut corrupt = raw. clone ( ) ;
237+ corrupt[ "content" ] = json ! ( format!( "{}x" , aged. content) ) ;
238+ let ( status, result) = post ( & state, & host, "/events" , & owner, corrupt, true ) . await ;
239+ assert_eq ! ( status, StatusCode :: BAD_REQUEST , "{result}" ) ;
240+ let invalid = EventBuilder :: new ( aged. kind , & aged. content )
241+ . tag ( Tag :: identifier ( "invalid-coordinate" ) )
242+ . custom_created_at ( aged. created_at )
243+ . sign_with_keys ( & owner)
244+ . unwrap ( ) ;
245+ let future = EventBuilder :: new ( aged. kind , & aged. content )
246+ . tags ( aged. tags . iter ( ) . cloned ( ) )
247+ . custom_created_at ( Timestamp :: from ( now + 86_400 ) )
248+ . sign_with_keys ( & owner)
249+ . unwrap ( ) ;
250+ let ordinary = EventBuilder :: text_note ( "old ordinary event" )
251+ . custom_created_at ( aged. created_at )
252+ . sign_with_keys ( & owner)
253+ . unwrap ( ) ;
254+ for rejected in [ invalid, future, ordinary] {
255+ let ( status, result) = post ( & state, & host, "/events" , & owner, json ! ( rejected) , true ) . await ;
256+ assert_eq ! ( status, StatusCode :: BAD_REQUEST , "{result}" ) ;
257+ }
258+ }
0 commit comments