@@ -165,8 +165,12 @@ pub enum SessionEvent {
165165#[ cfg( test) ]
166166mod tests {
167167 use super :: * ;
168+ use crate :: persist:: test_utils:: InMemoryTestPersister ;
168169 use crate :: receive:: v1:: test:: unchecked_proposal_from_test_vector;
169170 use crate :: receive:: v2:: test:: SHARED_CONTEXT ;
171+ use crate :: receive:: v2:: {
172+ PayjoinProposal , ProvisionalProposal , UncheckedProposal , WithContext ,
173+ } ;
170174
171175 #[ test]
172176 fn test_session_event_serialization_roundtrip ( ) {
@@ -214,4 +218,182 @@ mod tests {
214218 assert_eq ! ( event, deserialized) ;
215219 }
216220 }
221+
222+ struct SessionHistoryExpectedOutcome {
223+ psbt_with_contributed_inputs : Option < bitcoin:: Psbt > ,
224+ }
225+
226+ struct SessionHistoryTest {
227+ events : Vec < SessionEvent > ,
228+ expected_session_history : SessionHistoryExpectedOutcome ,
229+ expected_receiver_state : ReceiverTypeState ,
230+ }
231+
232+ fn run_session_history_test ( test : SessionHistoryTest ) {
233+ let persister = InMemoryTestPersister :: < SessionEvent > :: default ( ) ;
234+ for event in test. events {
235+ persister. save_event ( & event) . expect ( "In memory persister shouldn't fail" ) ;
236+ }
237+
238+ let ( receiver, session_history) =
239+ replay_event_log ( & persister) . expect ( "In memory persister shouldn't fail" ) ;
240+ assert_eq ! ( receiver, test. expected_receiver_state) ;
241+ assert_eq ! (
242+ session_history. psbt_with_contributed_inputs( ) ,
243+ test. expected_session_history. psbt_with_contributed_inputs
244+ ) ;
245+ }
246+
247+ #[ test]
248+ fn test_replaying_session_creation ( ) {
249+ let session_context = SHARED_CONTEXT . clone ( ) ;
250+ let test = SessionHistoryTest {
251+ events : vec ! [ SessionEvent :: Created ( session_context. clone( ) ) ] ,
252+ expected_session_history : SessionHistoryExpectedOutcome {
253+ psbt_with_contributed_inputs : None ,
254+ } ,
255+ expected_receiver_state : ReceiverTypeState :: WithContext ( Receiver {
256+ state : WithContext { context : session_context } ,
257+ } ) ,
258+ } ;
259+ run_session_history_test ( test) ;
260+ }
261+
262+ #[ test]
263+ fn test_replaying_unchecked_proposal ( ) {
264+ let session_context = SHARED_CONTEXT . clone ( ) ;
265+
266+ let test = SessionHistoryTest {
267+ events : vec ! [
268+ SessionEvent :: Created ( session_context. clone( ) ) ,
269+ SessionEvent :: UncheckedProposal ( ( unchecked_proposal_from_test_vector( ) , None ) ) ,
270+ ] ,
271+ expected_session_history : SessionHistoryExpectedOutcome {
272+ psbt_with_contributed_inputs : None ,
273+ } ,
274+ expected_receiver_state : ReceiverTypeState :: UncheckedProposal ( Receiver {
275+ state : UncheckedProposal {
276+ v1 : unchecked_proposal_from_test_vector ( ) ,
277+ context : session_context,
278+ } ,
279+ } ) ,
280+ } ;
281+ run_session_history_test ( test) ;
282+ }
283+
284+ #[ test]
285+ fn test_replaying_unchecked_proposal_with_reply_key ( ) {
286+ let session_context = SHARED_CONTEXT . clone ( ) ;
287+
288+ let test = SessionHistoryTest {
289+ events : vec ! [
290+ SessionEvent :: Created ( session_context. clone( ) ) ,
291+ SessionEvent :: UncheckedProposal ( (
292+ unchecked_proposal_from_test_vector( ) ,
293+ session_context. e. clone( ) ,
294+ ) ) ,
295+ ] ,
296+ expected_session_history : SessionHistoryExpectedOutcome {
297+ psbt_with_contributed_inputs : None ,
298+ } ,
299+ expected_receiver_state : ReceiverTypeState :: UncheckedProposal ( Receiver {
300+ state : UncheckedProposal {
301+ v1 : unchecked_proposal_from_test_vector ( ) ,
302+ context : session_context,
303+ } ,
304+ } ) ,
305+ } ;
306+ run_session_history_test ( test) ;
307+ }
308+
309+ #[ test]
310+ fn test_contributed_inputs ( ) {
311+ let session_context = SHARED_CONTEXT . clone ( ) ;
312+ let mut events = vec ! [ ] ;
313+
314+ let unchecked_proposal = unchecked_proposal_from_test_vector ( ) ;
315+ let maybe_inputs_owned = unchecked_proposal. clone ( ) . assume_interactive_receiver ( ) ;
316+ let maybe_inputs_seen = maybe_inputs_owned
317+ . clone ( )
318+ . check_inputs_not_owned ( |_| Ok ( false ) )
319+ . expect ( "No inputs should be owned" ) ;
320+ let outputs_unknown = maybe_inputs_seen
321+ . clone ( )
322+ . check_no_inputs_seen_before ( |_| Ok ( false ) )
323+ . expect ( "No inputs should be seen before" ) ;
324+ let wants_outputs = outputs_unknown
325+ . clone ( )
326+ . identify_receiver_outputs ( |_| Ok ( true ) )
327+ . expect ( "Outputs should be identified" ) ;
328+ let wants_inputs = wants_outputs. clone ( ) . commit_outputs ( ) ;
329+ let provisional_proposal = wants_inputs. clone ( ) . commit_inputs ( ) ;
330+
331+ events. push ( SessionEvent :: Created ( session_context. clone ( ) ) ) ;
332+ events. push ( SessionEvent :: UncheckedProposal ( ( unchecked_proposal, None ) ) ) ;
333+ events. push ( SessionEvent :: MaybeInputsOwned ( maybe_inputs_owned) ) ;
334+ events. push ( SessionEvent :: MaybeInputsSeen ( maybe_inputs_seen) ) ;
335+ events. push ( SessionEvent :: OutputsUnknown ( outputs_unknown) ) ;
336+ events. push ( SessionEvent :: WantsOutputs ( wants_outputs) ) ;
337+ events. push ( SessionEvent :: WantsInputs ( wants_inputs) ) ;
338+ events. push ( SessionEvent :: ProvisionalProposal ( provisional_proposal. clone ( ) ) ) ;
339+
340+ let test = SessionHistoryTest {
341+ events,
342+ expected_session_history : SessionHistoryExpectedOutcome {
343+ psbt_with_contributed_inputs : Some ( provisional_proposal. payjoin_psbt . clone ( ) ) ,
344+ } ,
345+ expected_receiver_state : ReceiverTypeState :: ProvisionalProposal ( Receiver {
346+ state : ProvisionalProposal { v1 : provisional_proposal, context : session_context } ,
347+ } ) ,
348+ } ;
349+ run_session_history_test ( test) ;
350+ }
351+
352+ #[ test]
353+ fn test_payjoin_proposal ( ) {
354+ let session_context = SHARED_CONTEXT . clone ( ) ;
355+ let mut events = vec ! [ ] ;
356+
357+ let unchecked_proposal = unchecked_proposal_from_test_vector ( ) ;
358+ let maybe_inputs_owned = unchecked_proposal. clone ( ) . assume_interactive_receiver ( ) ;
359+ let maybe_inputs_seen = maybe_inputs_owned
360+ . clone ( )
361+ . check_inputs_not_owned ( |_| Ok ( false ) )
362+ . expect ( "No inputs should be owned" ) ;
363+ let outputs_unknown = maybe_inputs_seen
364+ . clone ( )
365+ . check_no_inputs_seen_before ( |_| Ok ( false ) )
366+ . expect ( "No inputs should be seen before" ) ;
367+ let wants_outputs = outputs_unknown
368+ . clone ( )
369+ . identify_receiver_outputs ( |_| Ok ( true ) )
370+ . expect ( "Outputs should be identified" ) ;
371+ let wants_inputs = wants_outputs. clone ( ) . commit_outputs ( ) ;
372+ let provisional_proposal = wants_inputs. clone ( ) . commit_inputs ( ) ;
373+ let payjoin_proposal = provisional_proposal
374+ . clone ( )
375+ . finalize_proposal ( |psbt| Ok ( psbt. clone ( ) ) , None , None )
376+ . expect ( "Payjoin proposal should be finalized" ) ;
377+
378+ events. push ( SessionEvent :: Created ( session_context. clone ( ) ) ) ;
379+ events. push ( SessionEvent :: UncheckedProposal ( ( unchecked_proposal, None ) ) ) ;
380+ events. push ( SessionEvent :: MaybeInputsOwned ( maybe_inputs_owned) ) ;
381+ events. push ( SessionEvent :: MaybeInputsSeen ( maybe_inputs_seen) ) ;
382+ events. push ( SessionEvent :: OutputsUnknown ( outputs_unknown) ) ;
383+ events. push ( SessionEvent :: WantsOutputs ( wants_outputs) ) ;
384+ events. push ( SessionEvent :: WantsInputs ( wants_inputs) ) ;
385+ events. push ( SessionEvent :: ProvisionalProposal ( provisional_proposal. clone ( ) ) ) ;
386+ events. push ( SessionEvent :: PayjoinProposal ( payjoin_proposal. clone ( ) ) ) ;
387+
388+ let test = SessionHistoryTest {
389+ events,
390+ expected_session_history : SessionHistoryExpectedOutcome {
391+ psbt_with_contributed_inputs : Some ( provisional_proposal. payjoin_psbt . clone ( ) ) ,
392+ } ,
393+ expected_receiver_state : ReceiverTypeState :: PayjoinProposal ( Receiver {
394+ state : PayjoinProposal { v1 : payjoin_proposal, context : session_context } ,
395+ } ) ,
396+ } ;
397+ run_session_history_test ( test) ;
398+ }
217399}
0 commit comments