Skip to content

Commit 8437ad5

Browse files
arminsabouriDanGould
authored andcommitted
Add test coverage for SessionHistory and replays
This commit adds test coverage for `replay_receiver_event_log` and the session history object produced by replaying the logs. The tests persist session event logs and replay them to verify that the resulting state matches the expected state.
1 parent fde0bcd commit 8437ad5

File tree

1 file changed

+182
-0
lines changed

1 file changed

+182
-0
lines changed

payjoin/src/receive/v2/persist.rs

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,12 @@ pub enum SessionEvent {
165165
#[cfg(test)]
166166
mod 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

Comments
 (0)