1use crate::prelude::*;
2use bitcoin::hashes::sha256::Hash as Sha256Hash;
3use bitcoin::hashes::Hash;
4use bitcoin::key::Secp256k1;
5use bitcoin::secp256k1::Message as BitcoinMessage;
6use nostr_sdk::prelude::*;
7#[cfg(feature = "sqlx")]
8use sqlx::FromRow;
9#[cfg(feature = "sqlx")]
10use sqlx_crud::SqlxCrud;
11
12use std::fmt;
13use uuid::Uuid;
14
15#[derive(Debug, Deserialize, Serialize, Clone)]
17pub struct Peer {
18 pub pubkey: String,
19 pub reputation: Option<UserInfo>,
20}
21
22impl Peer {
23 pub fn new(pubkey: String, reputation: Option<UserInfo>) -> Self {
24 Self { pubkey, reputation }
25 }
26
27 pub fn from_json(json: &str) -> Result<Self, ServiceError> {
28 serde_json::from_str(json).map_err(|_| ServiceError::MessageSerializationError)
29 }
30
31 pub fn as_json(&self) -> Result<String, ServiceError> {
32 serde_json::to_string(&self).map_err(|_| ServiceError::MessageSerializationError)
33 }
34}
35
36#[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone)]
38#[serde(rename_all = "kebab-case")]
39pub enum Action {
40 NewOrder,
41 TakeSell,
42 TakeBuy,
43 PayInvoice,
44 FiatSent,
45 FiatSentOk,
46 Release,
47 Released,
48 Cancel,
49 Canceled,
50 CooperativeCancelInitiatedByYou,
51 CooperativeCancelInitiatedByPeer,
52 DisputeInitiatedByYou,
53 DisputeInitiatedByPeer,
54 CooperativeCancelAccepted,
55 BuyerInvoiceAccepted,
56 PurchaseCompleted,
57 HoldInvoicePaymentAccepted,
58 HoldInvoicePaymentSettled,
59 HoldInvoicePaymentCanceled,
60 WaitingSellerToPay,
61 WaitingBuyerInvoice,
62 AddInvoice,
63 BuyerTookOrder,
64 Rate,
65 RateUser,
66 RateReceived,
67 CantDo,
68 Dispute,
69 AdminCancel,
70 AdminCanceled,
71 AdminSettle,
72 AdminSettled,
73 AdminAddSolver,
74 AdminTakeDispute,
75 AdminTookDispute,
76 PaymentFailed,
77 InvoiceUpdated,
78 SendDm,
79 TradePubkey,
80 RestoreSession,
81}
82
83impl fmt::Display for Action {
84 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
85 write!(f, "{self:?}")
86 }
87}
88
89#[derive(Debug, Clone, Deserialize, Serialize)]
91#[serde(rename_all = "kebab-case")]
92pub enum Message {
93 Order(MessageKind),
94 Dispute(MessageKind),
95 CantDo(MessageKind),
96 Rate(MessageKind),
97 Dm(MessageKind),
98 Restore(MessageKind),
99}
100
101impl Message {
102 pub fn new_order(
104 id: Option<Uuid>,
105 request_id: Option<u64>,
106 trade_index: Option<i64>,
107 action: Action,
108 payload: Option<Payload>,
109 ) -> Self {
110 let kind = MessageKind::new(id, request_id, trade_index, action, payload);
111 Self::Order(kind)
112 }
113
114 pub fn new_dispute(
116 id: Option<Uuid>,
117 request_id: Option<u64>,
118 trade_index: Option<i64>,
119 action: Action,
120 payload: Option<Payload>,
121 ) -> Self {
122 let kind = MessageKind::new(id, request_id, trade_index, action, payload);
123
124 Self::Dispute(kind)
125 }
126
127 pub fn new_restore(payload: Option<Payload>) -> Self {
128 let kind = MessageKind::new(None, None, None, Action::RestoreSession, payload);
129 Self::Restore(kind)
130 }
131
132 pub fn cant_do(id: Option<Uuid>, request_id: Option<u64>, payload: Option<Payload>) -> Self {
134 let kind = MessageKind::new(id, request_id, None, Action::CantDo, payload);
135
136 Self::CantDo(kind)
137 }
138
139 pub fn new_dm(
141 id: Option<Uuid>,
142 request_id: Option<u64>,
143 action: Action,
144 payload: Option<Payload>,
145 ) -> Self {
146 let kind = MessageKind::new(id, request_id, None, action, payload);
147
148 Self::Dm(kind)
149 }
150
151 pub fn from_json(json: &str) -> Result<Self, ServiceError> {
153 serde_json::from_str(json).map_err(|_| ServiceError::MessageSerializationError)
154 }
155
156 pub fn as_json(&self) -> Result<String, ServiceError> {
158 serde_json::to_string(&self).map_err(|_| ServiceError::MessageSerializationError)
159 }
160
161 pub fn get_inner_message_kind(&self) -> &MessageKind {
163 match self {
164 Message::Dispute(k)
165 | Message::Order(k)
166 | Message::CantDo(k)
167 | Message::Rate(k)
168 | Message::Dm(k)
169 | Message::Restore(k) => k,
170 }
171 }
172
173 pub fn inner_action(&self) -> Option<Action> {
175 match self {
176 Message::Dispute(a)
177 | Message::Order(a)
178 | Message::CantDo(a)
179 | Message::Rate(a)
180 | Message::Dm(a)
181 | Message::Restore(a) => Some(a.get_action()),
182 }
183 }
184
185 pub fn verify(&self) -> bool {
187 match self {
188 Message::Order(m)
189 | Message::Dispute(m)
190 | Message::CantDo(m)
191 | Message::Rate(m)
192 | Message::Dm(m)
193 | Message::Restore(m) => m.verify(),
194 }
195 }
196
197 pub fn sign(message: String, keys: &Keys) -> Signature {
198 let hash: Sha256Hash = Sha256Hash::hash(message.as_bytes());
199 let hash = hash.to_byte_array();
200 let message: BitcoinMessage = BitcoinMessage::from_digest(hash);
201
202 keys.sign_schnorr(&message)
203 }
204
205 pub fn verify_signature(message: String, pubkey: PublicKey, sig: Signature) -> bool {
206 let hash: Sha256Hash = Sha256Hash::hash(message.as_bytes());
208 let hash = hash.to_byte_array();
209 let message: BitcoinMessage = BitcoinMessage::from_digest(hash);
210
211 let secp = Secp256k1::verification_only();
213 if let Ok(xonlykey) = pubkey.xonly() {
215 xonlykey.verify(&secp, &message, &sig).is_ok()
216 } else {
217 false
218 }
219 }
220}
221
222#[derive(Debug, Clone, Deserialize, Serialize)]
224pub struct MessageKind {
225 pub version: u8,
227 pub request_id: Option<u64>,
229 pub trade_index: Option<i64>,
231 #[serde(skip_serializing_if = "Option::is_none")]
233 pub id: Option<Uuid>,
234 pub action: Action,
236 pub payload: Option<Payload>,
238}
239
240type Amount = i64;
241
242#[derive(Debug, Deserialize, Serialize, Clone)]
244pub struct PaymentFailedInfo {
245 pub payment_attempts: u32,
247 pub payment_retries_interval: u32,
249}
250
251#[cfg_attr(feature = "sqlx", derive(FromRow, SqlxCrud))]
254#[derive(Debug, Deserialize, Serialize, Clone)]
255pub struct RestoredOrderHelper {
256 pub id: Uuid,
257 pub status: String,
258 pub master_buyer_pubkey: Option<String>,
259 pub master_seller_pubkey: Option<String>,
260 pub trade_index_buyer: Option<i64>,
261 pub trade_index_seller: Option<i64>,
262}
263
264#[cfg_attr(feature = "sqlx", derive(FromRow, SqlxCrud))]
268#[derive(Debug, Deserialize, Serialize, Clone)]
269pub struct RestoredDisputeHelper {
270 pub dispute_id: Uuid,
271 pub order_id: Uuid,
272 pub dispute_status: String,
273 pub master_buyer_pubkey: Option<String>,
274 pub master_seller_pubkey: Option<String>,
275 pub trade_index_buyer: Option<i64>,
276 pub trade_index_seller: Option<i64>,
277}
278
279#[cfg_attr(feature = "sqlx", derive(FromRow, SqlxCrud))]
281#[derive(Debug, Deserialize, Serialize, Clone)]
282pub struct RestoredOrdersInfo {
283 pub order_id: Uuid,
285 pub trade_index: i64,
287 pub status: String,
289}
290
291#[cfg_attr(feature = "sqlx", derive(FromRow, SqlxCrud))]
293#[derive(Debug, Deserialize, Serialize, Clone)]
294pub struct RestoredDisputesInfo {
295 pub dispute_id: Uuid,
297 pub order_id: Uuid,
299 pub trade_index: i64,
301 pub status: String,
303}
304
305#[derive(Debug, Deserialize, Serialize, Clone, Default)]
307pub struct RestoreSessionInfo {
308 #[serde(rename = "orders")]
310 pub restore_orders: Vec<RestoredOrdersInfo>,
311 #[serde(rename = "disputes")]
313 pub restore_disputes: Vec<RestoredDisputesInfo>,
314}
315
316#[derive(Debug, Deserialize, Serialize, Clone)]
318#[serde(rename_all = "snake_case")]
319pub enum Payload {
320 Order(SmallOrder),
322 PaymentRequest(Option<SmallOrder>, String, Option<Amount>),
324 TextMessage(String),
326 Peer(Peer),
328 RatingUser(u8),
330 Amount(Amount),
332 Dispute(Uuid, Option<SolverDisputeInfo>),
334 CantDo(Option<CantDoReason>),
336 NextTrade(String, u32),
340 PaymentFailed(PaymentFailedInfo),
342 RestoreData(RestoreSessionInfo),
344}
345
346#[allow(dead_code)]
347impl MessageKind {
348 pub fn new(
350 id: Option<Uuid>,
351 request_id: Option<u64>,
352 trade_index: Option<i64>,
353 action: Action,
354 payload: Option<Payload>,
355 ) -> Self {
356 Self {
357 version: PROTOCOL_VER,
358 request_id,
359 trade_index,
360 id,
361 action,
362 payload,
363 }
364 }
365 pub fn from_json(json: &str) -> Result<Self, ServiceError> {
367 serde_json::from_str(json).map_err(|_| ServiceError::MessageSerializationError)
368 }
369 pub fn as_json(&self) -> Result<String, ServiceError> {
371 serde_json::to_string(&self).map_err(|_| ServiceError::MessageSerializationError)
372 }
373
374 pub fn get_action(&self) -> Action {
376 self.action.clone()
377 }
378
379 pub fn get_next_trade_key(&self) -> Result<Option<(String, u32)>, ServiceError> {
381 match &self.payload {
382 Some(Payload::NextTrade(key, index)) => Ok(Some((key.to_string(), *index))),
383 None => Ok(None),
384 _ => Err(ServiceError::InvalidPayload),
385 }
386 }
387
388 pub fn get_rating(&self) -> Result<u8, ServiceError> {
389 if let Some(Payload::RatingUser(v)) = self.payload.to_owned() {
390 if !(MIN_RATING..=MAX_RATING).contains(&v) {
391 return Err(ServiceError::InvalidRatingValue);
392 }
393 Ok(v)
394 } else {
395 Err(ServiceError::InvalidRating)
396 }
397 }
398
399 pub fn verify(&self) -> bool {
401 match &self.action {
402 Action::NewOrder => matches!(&self.payload, Some(Payload::Order(_))),
403 Action::PayInvoice | Action::AddInvoice => {
404 if self.id.is_none() {
405 return false;
406 }
407 matches!(&self.payload, Some(Payload::PaymentRequest(_, _, _)))
408 }
409 Action::TakeSell
410 | Action::TakeBuy
411 | Action::FiatSent
412 | Action::FiatSentOk
413 | Action::Release
414 | Action::Released
415 | Action::Dispute
416 | Action::AdminCancel
417 | Action::AdminCanceled
418 | Action::AdminSettle
419 | Action::AdminSettled
420 | Action::Rate
421 | Action::RateReceived
422 | Action::AdminTakeDispute
423 | Action::AdminTookDispute
424 | Action::DisputeInitiatedByYou
425 | Action::DisputeInitiatedByPeer
426 | Action::WaitingBuyerInvoice
427 | Action::PurchaseCompleted
428 | Action::HoldInvoicePaymentAccepted
429 | Action::HoldInvoicePaymentSettled
430 | Action::HoldInvoicePaymentCanceled
431 | Action::WaitingSellerToPay
432 | Action::BuyerTookOrder
433 | Action::BuyerInvoiceAccepted
434 | Action::CooperativeCancelInitiatedByYou
435 | Action::CooperativeCancelInitiatedByPeer
436 | Action::CooperativeCancelAccepted
437 | Action::Cancel
438 | Action::InvoiceUpdated
439 | Action::AdminAddSolver
440 | Action::SendDm
441 | Action::TradePubkey
442 | Action::Canceled => {
443 if self.id.is_none() {
444 return false;
445 }
446 true
447 }
448 Action::PaymentFailed => {
449 if self.id.is_none() {
450 return false;
451 }
452 matches!(&self.payload, Some(Payload::PaymentFailed(_)))
453 }
454 Action::RateUser => {
455 matches!(&self.payload, Some(Payload::RatingUser(_)))
456 }
457 Action::CantDo => {
458 matches!(&self.payload, Some(Payload::CantDo(_)))
459 }
460 Action::RestoreSession => {
461 if self.id.is_some() || self.request_id.is_some() || self.trade_index.is_some() {
462 return false;
463 }
464 matches!(&self.payload, None | Some(Payload::RestoreData(_)))
465 }
466 }
467 }
468
469 pub fn get_order(&self) -> Option<&SmallOrder> {
470 if self.action != Action::NewOrder {
471 return None;
472 }
473 match &self.payload {
474 Some(Payload::Order(o)) => Some(o),
475 _ => None,
476 }
477 }
478
479 pub fn get_payment_request(&self) -> Option<String> {
480 if self.action != Action::TakeSell
481 && self.action != Action::AddInvoice
482 && self.action != Action::NewOrder
483 {
484 return None;
485 }
486 match &self.payload {
487 Some(Payload::PaymentRequest(_, pr, _)) => Some(pr.to_owned()),
488 Some(Payload::Order(ord)) => ord.buyer_invoice.to_owned(),
489 _ => None,
490 }
491 }
492
493 pub fn get_amount(&self) -> Option<Amount> {
494 if self.action != Action::TakeSell && self.action != Action::TakeBuy {
495 return None;
496 }
497 match &self.payload {
498 Some(Payload::PaymentRequest(_, _, amount)) => *amount,
499 Some(Payload::Amount(amount)) => Some(*amount),
500 _ => None,
501 }
502 }
503
504 pub fn get_payload(&self) -> Option<&Payload> {
505 self.payload.as_ref()
506 }
507
508 pub fn has_trade_index(&self) -> (bool, i64) {
509 if let Some(index) = self.trade_index {
510 return (true, index);
511 }
512 (false, 0)
513 }
514
515 pub fn trade_index(&self) -> i64 {
516 if let Some(index) = self.trade_index {
517 return index;
518 }
519 0
520 }
521}
522
523#[cfg(test)]
524mod test {
525 use crate::message::{Action, Message, MessageKind, Payload, Peer};
526 use crate::user::UserInfo;
527 use nostr_sdk::Keys;
528 use uuid::uuid;
529
530 #[test]
531 fn test_peer_with_reputation() {
532 let reputation = UserInfo {
534 rating: 4.5,
535 reviews: 10,
536 operating_days: 30,
537 };
538 let peer = Peer::new(
539 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
540 Some(reputation.clone()),
541 );
542
543 assert_eq!(
545 peer.pubkey,
546 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8"
547 );
548 assert!(peer.reputation.is_some());
549 let peer_reputation = peer.reputation.clone().unwrap();
550 assert_eq!(peer_reputation.rating, 4.5);
551 assert_eq!(peer_reputation.reviews, 10);
552 assert_eq!(peer_reputation.operating_days, 30);
553
554 let json = peer.as_json().unwrap();
556 let deserialized_peer = Peer::from_json(&json).unwrap();
557 assert_eq!(deserialized_peer.pubkey, peer.pubkey);
558 assert!(deserialized_peer.reputation.is_some());
559 let deserialized_reputation = deserialized_peer.reputation.unwrap();
560 assert_eq!(deserialized_reputation.rating, 4.5);
561 assert_eq!(deserialized_reputation.reviews, 10);
562 assert_eq!(deserialized_reputation.operating_days, 30);
563 }
564
565 #[test]
566 fn test_peer_without_reputation() {
567 let peer = Peer::new(
569 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
570 None,
571 );
572
573 assert_eq!(
575 peer.pubkey,
576 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8"
577 );
578 assert!(peer.reputation.is_none());
579
580 let json = peer.as_json().unwrap();
582 let deserialized_peer = Peer::from_json(&json).unwrap();
583 assert_eq!(deserialized_peer.pubkey, peer.pubkey);
584 assert!(deserialized_peer.reputation.is_none());
585 }
586
587 #[test]
588 fn test_peer_in_message() {
589 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
590
591 let reputation = UserInfo {
593 rating: 4.5,
594 reviews: 10,
595 operating_days: 30,
596 };
597 let peer_with_reputation = Peer::new(
598 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
599 Some(reputation),
600 );
601 let payload_with_reputation = Payload::Peer(peer_with_reputation);
602 let message_with_reputation = Message::Order(MessageKind::new(
603 Some(uuid),
604 Some(1),
605 Some(2),
606 Action::FiatSentOk,
607 Some(payload_with_reputation),
608 ));
609
610 assert!(message_with_reputation.verify());
612 let message_json = message_with_reputation.as_json().unwrap();
613 let deserialized_message = Message::from_json(&message_json).unwrap();
614 assert!(deserialized_message.verify());
615
616 let peer_without_reputation = Peer::new(
618 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
619 None,
620 );
621 let payload_without_reputation = Payload::Peer(peer_without_reputation);
622 let message_without_reputation = Message::Order(MessageKind::new(
623 Some(uuid),
624 Some(1),
625 Some(2),
626 Action::FiatSentOk,
627 Some(payload_without_reputation),
628 ));
629
630 assert!(message_without_reputation.verify());
632 let message_json = message_without_reputation.as_json().unwrap();
633 let deserialized_message = Message::from_json(&message_json).unwrap();
634 assert!(deserialized_message.verify());
635 }
636
637 #[test]
638 fn test_payment_failed_payload() {
639 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
640
641 let payment_failed_info = crate::message::PaymentFailedInfo {
643 payment_attempts: 3,
644 payment_retries_interval: 60,
645 };
646
647 let payload = Payload::PaymentFailed(payment_failed_info);
648 let message = Message::Order(MessageKind::new(
649 Some(uuid),
650 Some(1),
651 Some(2),
652 Action::PaymentFailed,
653 Some(payload),
654 ));
655
656 assert!(message.verify());
658
659 let message_json = message.as_json().unwrap();
661
662 let deserialized_message = Message::from_json(&message_json).unwrap();
664 assert!(deserialized_message.verify());
665
666 if let Message::Order(kind) = deserialized_message {
668 if let Some(Payload::PaymentFailed(info)) = kind.payload {
669 assert_eq!(info.payment_attempts, 3);
670 assert_eq!(info.payment_retries_interval, 60);
671 } else {
672 panic!("Expected PaymentFailed payload");
673 }
674 } else {
675 panic!("Expected Order message");
676 }
677 }
678
679 #[test]
680 fn test_message_payload_signature() {
681 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
682 let peer = Peer::new(
683 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
684 None, );
686 let payload = Payload::Peer(peer);
687 let test_message = Message::Order(MessageKind::new(
688 Some(uuid),
689 Some(1),
690 Some(2),
691 Action::FiatSentOk,
692 Some(payload),
693 ));
694 assert!(test_message.verify());
695 let test_message_json = test_message.as_json().unwrap();
696 let trade_keys =
698 Keys::parse("110e43647eae221ab1da33ddc17fd6ff423f2b2f49d809b9ffa40794a2ab996c")
699 .unwrap();
700 let sig = Message::sign(test_message_json.clone(), &trade_keys);
701
702 assert!(Message::verify_signature(
703 test_message_json,
704 trade_keys.public_key(),
705 sig
706 ));
707 }
708
709 #[test]
710 fn test_restore_session_message() {
711 let restore_request_message = Message::Restore(MessageKind::new(
713 None,
714 None,
715 None,
716 Action::RestoreSession,
717 None,
718 ));
719
720 assert!(restore_request_message.verify());
722 assert_eq!(
723 restore_request_message.inner_action(),
724 Some(Action::RestoreSession)
725 );
726
727 let message_json = restore_request_message.as_json().unwrap();
729 let deserialized_message = Message::from_json(&message_json).unwrap();
730 assert!(deserialized_message.verify());
731 assert_eq!(
732 deserialized_message.inner_action(),
733 Some(Action::RestoreSession)
734 );
735
736 let restored_orders = vec![
738 crate::message::RestoredOrdersInfo {
739 order_id: uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23"),
740 trade_index: 1,
741 status: "active".to_string(),
742 },
743 crate::message::RestoredOrdersInfo {
744 order_id: uuid!("408e1272-d5f4-47e6-bd97-3504baea9c24"),
745 trade_index: 2,
746 status: "success".to_string(),
747 },
748 ];
749
750 let restored_disputes = vec![crate::message::RestoredDisputesInfo {
751 dispute_id: uuid!("508e1272-d5f4-47e6-bd97-3504baea9c25"),
752 order_id: uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23"),
753 trade_index: 1,
754 status: "initiated".to_string(),
755 }];
756
757 let restore_session_info = crate::message::RestoreSessionInfo {
758 restore_orders: restored_orders.clone(),
759 restore_disputes: restored_disputes.clone(),
760 };
761
762 let restore_data_payload = Payload::RestoreData(restore_session_info);
763 let restore_data_message = Message::Restore(MessageKind::new(
764 None,
765 None,
766 None,
767 Action::RestoreSession,
768 Some(restore_data_payload),
769 ));
770
771 assert!(restore_data_message.verify());
773 assert_eq!(
774 restore_data_message.inner_action(),
775 Some(Action::RestoreSession)
776 );
777
778 let message_json = restore_data_message.as_json().unwrap();
780 let deserialized_message = Message::from_json(&message_json).unwrap();
781 assert!(deserialized_message.verify());
782 assert_eq!(
783 deserialized_message.inner_action(),
784 Some(Action::RestoreSession)
785 );
786
787 if let Message::Restore(kind) = deserialized_message {
789 if let Some(Payload::RestoreData(info)) = kind.payload {
790 assert_eq!(info.restore_orders.len(), 2);
791 assert_eq!(info.restore_disputes.len(), 1);
792
793 assert_eq!(
795 info.restore_orders[0].order_id,
796 uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23")
797 );
798 assert_eq!(info.restore_orders[0].trade_index, 1);
799 assert_eq!(info.restore_orders[0].status, "active");
800
801 assert_eq!(
803 info.restore_orders[1].order_id,
804 uuid!("408e1272-d5f4-47e6-bd97-3504baea9c24")
805 );
806 assert_eq!(info.restore_orders[1].trade_index, 2);
807 assert_eq!(info.restore_orders[1].status, "success");
808
809 assert_eq!(
811 info.restore_disputes[0].dispute_id,
812 uuid!("508e1272-d5f4-47e6-bd97-3504baea9c25")
813 );
814 assert_eq!(
815 info.restore_disputes[0].order_id,
816 uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23")
817 );
818 assert_eq!(info.restore_disputes[0].trade_index, 1);
819 assert_eq!(info.restore_disputes[0].status, "initiated");
820 } else {
821 panic!("Expected RestoreData payload");
822 }
823 } else {
824 panic!("Expected Restore message");
825 }
826 }
827
828 #[test]
829 fn test_restore_session_message_validation() {
830 let restore_request_message = Message::Restore(MessageKind::new(
832 None,
833 None,
834 None,
835 Action::RestoreSession,
836 None, ));
838
839 assert!(restore_request_message.verify());
841
842 let wrong_payload = Payload::TextMessage("wrong payload".to_string());
844 let wrong_message = Message::Restore(MessageKind::new(
845 None,
846 None,
847 None,
848 Action::RestoreSession,
849 Some(wrong_payload),
850 ));
851
852 assert!(!wrong_message.verify());
854
855 let with_id = Message::Restore(MessageKind::new(
857 Some(uuid!("00000000-0000-0000-0000-000000000001")),
858 None,
859 None,
860 Action::RestoreSession,
861 None,
862 ));
863 assert!(!with_id.verify());
864
865 let with_request_id = Message::Restore(MessageKind::new(
867 None,
868 Some(42),
869 None,
870 Action::RestoreSession,
871 None,
872 ));
873 assert!(!with_request_id.verify());
874
875 let with_trade_index = Message::Restore(MessageKind::new(
877 None,
878 None,
879 Some(7),
880 Action::RestoreSession,
881 None,
882 ));
883 assert!(!with_trade_index.verify());
884 }
885
886 #[test]
887 fn test_restore_session_message_constructor() {
888 let restore_request_message = Message::new_restore(None);
890
891 assert!(matches!(restore_request_message, Message::Restore(_)));
892 assert!(restore_request_message.verify());
893 assert_eq!(
894 restore_request_message.inner_action(),
895 Some(Action::RestoreSession)
896 );
897
898 let restore_session_info = crate::message::RestoreSessionInfo {
900 restore_orders: vec![],
901 restore_disputes: vec![],
902 };
903 let restore_data_message =
904 Message::new_restore(Some(Payload::RestoreData(restore_session_info)));
905
906 assert!(matches!(restore_data_message, Message::Restore(_)));
907 assert!(restore_data_message.verify());
908 assert_eq!(
909 restore_data_message.inner_action(),
910 Some(Action::RestoreSession)
911 );
912 }
913}