1use bsv::wallet::interfaces::WalletInterface;
2use serde::Deserialize;
3
4use crate::client::{check_status_error, MessageBoxClient};
5use crate::error::MessageBoxError;
6use crate::types::{MessageBoxPermission, MessageBoxQuote, SetPermissionParams};
7
8#[derive(Deserialize)]
13struct GetPermissionResponse {
14 permission: Option<MessageBoxPermission>,
15}
16
17#[derive(Deserialize)]
18struct ListPermissionsResponse {
19 permissions: Vec<MessageBoxPermission>,
20}
21
22#[derive(Deserialize)]
23struct QuoteResponse {
24 quote: QuoteBody,
25}
26
27#[derive(Deserialize)]
28struct QuoteBody {
29 #[serde(rename = "recipientFee")]
30 recipient_fee: i64,
31 #[serde(rename = "deliveryFee")]
32 delivery_fee: i64,
33}
34
35impl<W: WalletInterface + Clone + 'static + Send + Sync> MessageBoxClient<W> {
40 pub async fn set_message_box_permission(
45 &self,
46 params: SetPermissionParams,
47 override_host: Option<&str>,
48 ) -> Result<(), MessageBoxError> {
49 self.assert_initialized().await?;
50
51 let base = override_host.unwrap_or_else(|| self.host());
52 let body_bytes = serde_json::to_vec(¶ms)?;
53 let url = format!("{base}/permissions/set");
54 let response = self.post_json(&url, body_bytes).await?;
55 check_status_error(&response.body)?;
56
57 Ok(())
58 }
59
60 pub async fn get_message_box_permission(
66 &self,
67 recipient: &str,
68 message_box: &str,
69 sender: Option<&str>,
70 override_host: Option<&str>,
71 ) -> Result<Option<MessageBoxPermission>, MessageBoxError> {
72 self.assert_initialized().await?;
73
74 let base = override_host.unwrap_or_else(|| self.host());
75 let mut url = format!(
77 "{base}/permissions/get?recipient={}&messageBox={}",
78 recipient, message_box
79 );
80 if let Some(s) = sender {
81 url.push_str(&format!("&sender={s}"));
82 }
83
84 let response = self.get_json(&url).await?;
85 check_status_error(&response.body)?;
86
87 let parsed: GetPermissionResponse = serde_json::from_slice(&response.body)?;
88 Ok(parsed.permission)
89 }
90
91 pub async fn list_message_box_permissions(
97 &self,
98 message_box: Option<&str>,
99 limit: Option<u32>,
100 offset: Option<u32>,
101 override_host: Option<&str>,
102 ) -> Result<Vec<MessageBoxPermission>, MessageBoxError> {
103 self.assert_initialized().await?;
104
105 let base = override_host.unwrap_or_else(|| self.host());
106 let mut url = format!("{base}/permissions/list");
107 let mut params: Vec<String> = Vec::new();
108
109 if let Some(mb) = message_box {
111 params.push(format!("message_box={mb}"));
112 }
113 if let Some(l) = limit {
114 params.push(format!("limit={l}"));
115 }
116 if let Some(o) = offset {
117 params.push(format!("offset={o}"));
118 }
119
120 if !params.is_empty() {
121 url.push('?');
122 url.push_str(¶ms.join("&"));
123 }
124
125 let response = self.get_json(&url).await?;
126 check_status_error(&response.body)?;
127
128 let parsed: ListPermissionsResponse = serde_json::from_slice(&response.body)?;
129 Ok(parsed.permissions)
130 }
131
132 pub async fn get_message_box_quote(
138 &self,
139 recipient: &str,
140 message_box: &str,
141 override_host: Option<&str>,
142 ) -> Result<MessageBoxQuote, MessageBoxError> {
143 self.assert_initialized().await?;
144
145 let base = override_host.unwrap_or_else(|| self.host());
146 let url = format!(
148 "{base}/permissions/quote?recipient={}&messageBox={}",
149 recipient, message_box
150 );
151
152 let response = self.get_json(&url).await?;
153 check_status_error(&response.body)?;
154
155 let delivery_agent_identity_key = response
158 .headers
159 .get("x-bsv-auth-identity-key")
160 .cloned()
161 .ok_or_else(|| MessageBoxError::MissingHeader("x-bsv-auth-identity-key".into()))?;
162
163 let parsed: QuoteResponse = serde_json::from_slice(&response.body)?;
164
165 Ok(MessageBoxQuote {
166 delivery_fee: parsed.quote.delivery_fee,
167 recipient_fee: parsed.quote.recipient_fee,
168 delivery_agent_identity_key,
169 })
170 }
171
172 pub async fn allow_notifications_from_peer(
179 &self,
180 sender: &str,
181 recipient_fee: i64,
182 override_host: Option<&str>,
183 ) -> Result<(), MessageBoxError> {
184 self.set_message_box_permission(
185 SetPermissionParams {
186 message_box: "notifications".to_string(),
187 sender: Some(sender.to_string()),
188 recipient_fee,
189 },
190 override_host,
191 )
192 .await
193 }
194
195 pub async fn deny_notifications_from_peer(
198 &self,
199 sender: &str,
200 override_host: Option<&str>,
201 ) -> Result<(), MessageBoxError> {
202 self.set_message_box_permission(
203 SetPermissionParams {
204 message_box: "notifications".to_string(),
205 sender: Some(sender.to_string()),
206 recipient_fee: -1,
207 },
208 override_host,
209 )
210 .await
211 }
212
213 pub async fn check_peer_notification_status(
219 &self,
220 peer: &str,
221 override_host: Option<&str>,
222 ) -> Result<Option<MessageBoxPermission>, MessageBoxError> {
223 let recipient = self.get_identity_key().await?;
224 self.get_message_box_permission(&recipient, "notifications", Some(peer), override_host)
225 .await
226 }
227
228 pub async fn list_peer_notifications(
230 &self,
231 override_host: Option<&str>,
232 ) -> Result<Vec<MessageBoxPermission>, MessageBoxError> {
233 self.list_message_box_permissions(Some("notifications"), None, None, override_host)
234 .await
235 }
236
237 pub async fn send_notification(
243 &self,
244 recipient: &str,
245 body: &str,
246 override_host: Option<&str>,
247 ) -> Result<String, MessageBoxError> {
248 match override_host {
249 Some(host) => {
250 self.send_message_to_host(
251 host,
252 recipient,
253 "notifications",
254 body,
255 false,
256 true,
257 None,
258 None,
259 )
260 .await
261 }
262 None => {
263 self.send_message(recipient, "notifications", body, false, true, None, None)
264 .await
265 }
266 }
267 }
268
269 pub async fn get_message_box_quote_multi(
275 &self,
276 recipients: &[&str],
277 message_box: &str,
278 override_host: Option<&str>,
279 ) -> Result<crate::types::MessageBoxMultiQuote, MessageBoxError> {
280 use crate::types::{MessageBoxMultiQuote, RecipientQuote};
281 use std::collections::HashMap;
282
283 let mut quotes_by_recipient: Vec<RecipientQuote> = Vec::new();
284 let mut blocked_recipients: Vec<String> = Vec::new();
285 let mut delivery_agent_identity_key_by_host: HashMap<String, String> = HashMap::new();
286 let mut total_delivery_fee: i64 = 0;
287 let mut total_recipient_fee: i64 = 0;
288
289 for recipient in recipients {
290 let host = if let Some(h) = override_host {
292 h.to_string()
293 } else {
294 self.resolve_host_for_recipient(recipient)
295 .await
296 .unwrap_or_else(|_| self.host().to_string())
297 };
298
299 let url = format!(
302 "{host}/permissions/quote?recipient={}&messageBox={}",
303 recipient, message_box
304 );
305
306 let response = match self.get_json(&url).await {
307 Ok(r) => r,
308 Err(e) => {
309 blocked_recipients.push(recipient.to_string());
311 quotes_by_recipient.push(RecipientQuote {
312 recipient: recipient.to_string(),
313 message_box: message_box.to_string(),
314 delivery_fee: 0,
315 recipient_fee: 0,
316 status: format!("error: {e}"),
317 });
318 continue;
319 }
320 };
321
322 if let Some(key) = response.headers.get("x-bsv-auth-identity-key") {
324 delivery_agent_identity_key_by_host.insert(host.clone(), key.clone());
325 }
326
327 let parsed = match serde_json::from_slice::<QuoteResponse>(&response.body) {
328 Ok(p) => p,
329 Err(_) => {
330 blocked_recipients.push(recipient.to_string());
331 quotes_by_recipient.push(RecipientQuote {
332 recipient: recipient.to_string(),
333 message_box: message_box.to_string(),
334 delivery_fee: 0,
335 recipient_fee: 0,
336 status: "parse_error".to_string(),
337 });
338 continue;
339 }
340 };
341
342 let delivery_fee = parsed.quote.delivery_fee;
343 let recipient_fee = parsed.quote.recipient_fee;
344 let status = if recipient_fee < 0 {
345 "blocked".to_string()
346 } else if recipient_fee == 0 && delivery_fee == 0 {
347 "always_allow".to_string()
348 } else {
349 "payment_required".to_string()
350 };
351
352 if recipient_fee < 0 {
353 blocked_recipients.push(recipient.to_string());
354 } else {
355 total_delivery_fee += delivery_fee;
356 total_recipient_fee += recipient_fee;
357 }
358
359 quotes_by_recipient.push(RecipientQuote {
360 recipient: recipient.to_string(),
361 message_box: message_box.to_string(),
362 delivery_fee,
363 recipient_fee,
364 status,
365 });
366 }
367
368 Ok(MessageBoxMultiQuote {
369 quotes_by_recipient,
370 totals: Some(crate::types::SendListTotals {
371 delivery_fees: total_delivery_fee,
372 recipient_fees: total_recipient_fee,
373 total_for_payable_recipients: total_delivery_fee + total_recipient_fee,
374 }),
375 blocked_recipients,
376 delivery_agent_identity_key_by_host,
377 })
378 }
379
380 pub async fn send_notification_to_recipients(
385 &self,
386 recipients: &[&str],
387 body: &str,
388 override_host: Option<&str>,
389 ) -> Result<crate::types::SendListResult, MessageBoxError> {
390 use crate::types::SendListParams;
391
392 let params = SendListParams {
393 recipients: recipients.iter().map(|s| s.to_string()).collect(),
394 message_box: "notifications".to_string(),
395 body: body.to_string(),
396 skip_encryption: Some(false),
397 };
398
399 self.send_message_to_recipients(¶ms, override_host)
400 .await
401 }
402}
403
404#[cfg(test)]
409mod tests {
410 use super::*;
411 use crate::types::{MessageBoxPermission, MessageBoxQuote, SetPermissionParams};
412 use bsv::primitives::private_key::PrivateKey;
413 use bsv::wallet::error::WalletError;
414 use bsv::wallet::interfaces::*;
415 use bsv::wallet::proto_wallet::ProtoWallet;
416 use std::sync::Arc;
417
418 #[derive(Clone)]
423 struct ArcWallet(Arc<ProtoWallet>);
424
425 impl ArcWallet {
426 fn new() -> Self {
427 let key = PrivateKey::from_random().expect("random key");
428 ArcWallet(Arc::new(ProtoWallet::new(key)))
429 }
430 }
431
432 #[async_trait::async_trait]
433 impl WalletInterface for ArcWallet {
434 async fn create_action(
435 &self,
436 args: CreateActionArgs,
437 orig: Option<&str>,
438 ) -> Result<CreateActionResult, WalletError> {
439 self.0.create_action(args, orig).await
440 }
441 async fn sign_action(
442 &self,
443 args: SignActionArgs,
444 orig: Option<&str>,
445 ) -> Result<SignActionResult, WalletError> {
446 self.0.sign_action(args, orig).await
447 }
448 async fn abort_action(
449 &self,
450 args: AbortActionArgs,
451 orig: Option<&str>,
452 ) -> Result<AbortActionResult, WalletError> {
453 self.0.abort_action(args, orig).await
454 }
455 async fn list_actions(
456 &self,
457 args: ListActionsArgs,
458 orig: Option<&str>,
459 ) -> Result<ListActionsResult, WalletError> {
460 self.0.list_actions(args, orig).await
461 }
462 async fn internalize_action(
463 &self,
464 args: InternalizeActionArgs,
465 orig: Option<&str>,
466 ) -> Result<InternalizeActionResult, WalletError> {
467 self.0.internalize_action(args, orig).await
468 }
469 async fn list_outputs(
470 &self,
471 args: ListOutputsArgs,
472 orig: Option<&str>,
473 ) -> Result<ListOutputsResult, WalletError> {
474 self.0.list_outputs(args, orig).await
475 }
476 async fn relinquish_output(
477 &self,
478 args: RelinquishOutputArgs,
479 orig: Option<&str>,
480 ) -> Result<RelinquishOutputResult, WalletError> {
481 self.0.relinquish_output(args, orig).await
482 }
483 async fn get_public_key(
484 &self,
485 args: GetPublicKeyArgs,
486 orig: Option<&str>,
487 ) -> Result<GetPublicKeyResult, WalletError> {
488 self.0.get_public_key(args, orig).await
489 }
490 async fn reveal_counterparty_key_linkage(
491 &self,
492 args: RevealCounterpartyKeyLinkageArgs,
493 orig: Option<&str>,
494 ) -> Result<RevealCounterpartyKeyLinkageResult, WalletError> {
495 self.0.reveal_counterparty_key_linkage(args, orig).await
496 }
497 async fn reveal_specific_key_linkage(
498 &self,
499 args: RevealSpecificKeyLinkageArgs,
500 orig: Option<&str>,
501 ) -> Result<RevealSpecificKeyLinkageResult, WalletError> {
502 self.0.reveal_specific_key_linkage(args, orig).await
503 }
504 async fn encrypt(
505 &self,
506 args: EncryptArgs,
507 orig: Option<&str>,
508 ) -> Result<EncryptResult, WalletError> {
509 self.0.encrypt(args, orig).await
510 }
511 async fn decrypt(
512 &self,
513 args: DecryptArgs,
514 orig: Option<&str>,
515 ) -> Result<DecryptResult, WalletError> {
516 self.0.decrypt(args, orig).await
517 }
518 async fn create_hmac(
519 &self,
520 args: CreateHmacArgs,
521 orig: Option<&str>,
522 ) -> Result<CreateHmacResult, WalletError> {
523 self.0.create_hmac(args, orig).await
524 }
525 async fn verify_hmac(
526 &self,
527 args: VerifyHmacArgs,
528 orig: Option<&str>,
529 ) -> Result<VerifyHmacResult, WalletError> {
530 self.0.verify_hmac(args, orig).await
531 }
532 async fn create_signature(
533 &self,
534 args: CreateSignatureArgs,
535 orig: Option<&str>,
536 ) -> Result<CreateSignatureResult, WalletError> {
537 self.0.create_signature(args, orig).await
538 }
539 async fn verify_signature(
540 &self,
541 args: VerifySignatureArgs,
542 orig: Option<&str>,
543 ) -> Result<VerifySignatureResult, WalletError> {
544 self.0.verify_signature(args, orig).await
545 }
546 async fn acquire_certificate(
547 &self,
548 args: AcquireCertificateArgs,
549 orig: Option<&str>,
550 ) -> Result<Certificate, WalletError> {
551 self.0.acquire_certificate(args, orig).await
552 }
553 async fn list_certificates(
554 &self,
555 args: ListCertificatesArgs,
556 orig: Option<&str>,
557 ) -> Result<ListCertificatesResult, WalletError> {
558 self.0.list_certificates(args, orig).await
559 }
560 async fn prove_certificate(
561 &self,
562 args: ProveCertificateArgs,
563 orig: Option<&str>,
564 ) -> Result<ProveCertificateResult, WalletError> {
565 self.0.prove_certificate(args, orig).await
566 }
567 async fn relinquish_certificate(
568 &self,
569 args: RelinquishCertificateArgs,
570 orig: Option<&str>,
571 ) -> Result<RelinquishCertificateResult, WalletError> {
572 self.0.relinquish_certificate(args, orig).await
573 }
574 async fn discover_by_identity_key(
575 &self,
576 args: DiscoverByIdentityKeyArgs,
577 orig: Option<&str>,
578 ) -> Result<DiscoverCertificatesResult, WalletError> {
579 self.0.discover_by_identity_key(args, orig).await
580 }
581 async fn discover_by_attributes(
582 &self,
583 args: DiscoverByAttributesArgs,
584 orig: Option<&str>,
585 ) -> Result<DiscoverCertificatesResult, WalletError> {
586 self.0.discover_by_attributes(args, orig).await
587 }
588 async fn is_authenticated(
589 &self,
590 orig: Option<&str>,
591 ) -> Result<AuthenticatedResult, WalletError> {
592 self.0.is_authenticated(orig).await
593 }
594 async fn wait_for_authentication(
595 &self,
596 orig: Option<&str>,
597 ) -> Result<AuthenticatedResult, WalletError> {
598 self.0.wait_for_authentication(orig).await
599 }
600 async fn get_height(&self, orig: Option<&str>) -> Result<GetHeightResult, WalletError> {
601 self.0.get_height(orig).await
602 }
603 async fn get_header_for_height(
604 &self,
605 args: GetHeaderArgs,
606 orig: Option<&str>,
607 ) -> Result<GetHeaderResult, WalletError> {
608 self.0.get_header_for_height(args, orig).await
609 }
610 async fn get_network(&self, orig: Option<&str>) -> Result<GetNetworkResult, WalletError> {
611 self.0.get_network(orig).await
612 }
613 async fn get_version(&self, orig: Option<&str>) -> Result<GetVersionResult, WalletError> {
614 self.0.get_version(orig).await
615 }
616 }
617
618 fn make_client(host: &str) -> MessageBoxClient<ArcWallet> {
619 MessageBoxClient::new(
620 host.to_string(),
621 ArcWallet::new(),
622 None,
623 bsv::services::overlay_tools::Network::Mainnet,
624 )
625 }
626
627 #[test]
635 fn set_permission_post_body_is_camel_case() {
636 let params = SetPermissionParams {
637 message_box: "payment_inbox".to_string(),
638 sender: Some("03abc".to_string()),
639 recipient_fee: 100,
640 };
641 let json = serde_json::to_string(¶ms).unwrap();
642 assert!(
643 json.contains("\"messageBox\""),
644 "messageBox must be camelCase"
645 );
646 assert!(
647 json.contains("\"recipientFee\""),
648 "recipientFee must be camelCase"
649 );
650 assert!(
651 json.contains("\"sender\""),
652 "sender must be present when Some"
653 );
654 assert!(!json.contains("message_box"), "no snake_case leakage");
655 assert!(!json.contains("recipient_fee"), "no snake_case leakage");
656 }
657
658 #[test]
662 fn get_permission_url_uses_camel_case_query_params() {
663 let host = "https://example.com";
665 let recipient = "03recipient";
666 let message_box = "inbox";
667 let sender = Some("03sender");
668
669 let mut url = format!(
670 "{}/permissions/get?recipient={}&messageBox={}",
671 host, recipient, message_box
672 );
673 if let Some(s) = sender {
674 url.push_str(&format!("&sender={s}"));
675 }
676
677 assert!(
678 url.contains("messageBox=inbox"),
679 "must use camelCase messageBox"
680 );
681 assert!(!url.contains("message_box"), "must not use snake_case");
682 assert!(
683 url.contains("recipient=03recipient"),
684 "recipient param present"
685 );
686 assert!(
687 url.contains("sender=03sender"),
688 "sender param present when Some"
689 );
690 }
691
692 #[test]
694 fn get_permission_url_omits_sender_when_none() {
695 let host = "https://example.com";
696 let recipient = "03recipient";
697 let message_box = "inbox";
698 let sender: Option<&str> = None;
699
700 let mut url = format!(
701 "{}/permissions/get?recipient={}&messageBox={}",
702 host, recipient, message_box
703 );
704 if let Some(s) = sender {
705 url.push_str(&format!("&sender={s}"));
706 }
707
708 assert!(!url.contains("sender"), "sender param absent when None");
709 assert!(url.contains("messageBox=inbox"), "messageBox present");
710 }
711
712 #[test]
717 fn list_permissions_url_uses_snake_case_message_box_param() {
718 let host = "https://example.com";
719 let message_box = Some("notifications");
720
721 let mut url = format!("{}/permissions/list", host);
722 let mut params: Vec<String> = Vec::new();
723
724 if let Some(mb) = message_box {
726 params.push(format!("message_box={mb}"));
727 }
728 if !params.is_empty() {
729 url.push('?');
730 url.push_str(¶ms.join("&"));
731 }
732
733 assert!(
734 url.contains("message_box=notifications"),
735 "must use snake_case message_box key: {}",
736 url
737 );
738 assert!(
739 !url.contains("messageBox"),
740 "must NOT use camelCase messageBox in list endpoint: {}",
741 url
742 );
743 }
744
745 #[test]
747 fn quote_url_uses_camel_case_message_box_param() {
748 let host = "https://example.com";
749 let url = format!("{}/permissions/quote?recipient=03r&messageBox=inbox", host);
750 assert!(
751 url.contains("messageBox=inbox"),
752 "quote endpoint uses camelCase"
753 );
754 assert!(!url.contains("message_box"), "not snake_case");
755 }
756
757 #[test]
765 fn missing_header_produces_missing_header_error() {
766 use std::collections::HashMap;
768 let headers: HashMap<String, String> = HashMap::new();
769
770 let result = headers
771 .get("x-bsv-auth-identity-key")
772 .cloned()
773 .ok_or_else(|| MessageBoxError::MissingHeader("x-bsv-auth-identity-key".into()));
774
775 assert!(result.is_err(), "must error when header absent");
776 assert!(
777 matches!(result.unwrap_err(), MessageBoxError::MissingHeader(_)),
778 "error must be MissingHeader variant"
779 );
780 }
781
782 #[test]
784 fn present_header_is_extracted_correctly() {
785 use std::collections::HashMap;
786 let mut headers: HashMap<String, String> = HashMap::new();
787 headers.insert(
788 "x-bsv-auth-identity-key".to_string(),
789 "03deadbeef".to_string(),
790 );
791
792 let result: Result<String, MessageBoxError> = headers
793 .get("x-bsv-auth-identity-key")
794 .cloned()
795 .ok_or_else(|| MessageBoxError::MissingHeader("x-bsv-auth-identity-key".into()));
796
797 assert!(result.is_ok());
798 assert_eq!(result.unwrap(), "03deadbeef");
799 }
800
801 #[test]
808 fn get_permission_response_parses_wrapped_body() {
809 let raw = r#"{"permission": {"messageBox": "inbox", "recipientFee": 0, "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z"}}"#;
810 let parsed: GetPermissionResponse = serde_json::from_str(raw).unwrap();
811 let perm = parsed.permission.unwrap();
812 assert_eq!(perm.message_box, "inbox");
813 assert_eq!(perm.recipient_fee, 0);
814 }
815
816 #[test]
818 fn get_permission_response_parses_null_as_none() {
819 let raw = r#"{"permission": null}"#;
820 let parsed: GetPermissionResponse = serde_json::from_str(raw).unwrap();
821 assert!(parsed.permission.is_none());
822 }
823
824 #[test]
826 fn list_permissions_response_parses_wrapped_body() {
827 let raw = r#"{"permissions": [{"message_box": "inbox", "recipient_fee": 100, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z"}]}"#;
829 let parsed: ListPermissionsResponse = serde_json::from_str(raw).unwrap();
830 assert_eq!(parsed.permissions.len(), 1);
831 assert_eq!(parsed.permissions[0].message_box, "inbox");
832 assert_eq!(parsed.permissions[0].recipient_fee, 100);
833 }
834
835 #[test]
837 fn quote_response_parses_wrapped_body() {
838 let raw = r#"{"quote": {"recipientFee": 50, "deliveryFee": 10}}"#;
839 let parsed: QuoteResponse = serde_json::from_str(raw).unwrap();
840 assert_eq!(parsed.quote.recipient_fee, 50);
841 assert_eq!(parsed.quote.delivery_fee, 10);
842 }
843
844 #[test]
850 fn allow_notifications_params_uses_notifications_box() {
851 let params = SetPermissionParams {
853 message_box: "notifications".to_string(),
854 sender: Some("03peer".to_string()),
855 recipient_fee: 0,
856 };
857 assert_eq!(params.message_box, "notifications");
858 assert_eq!(params.sender, Some("03peer".to_string()));
859 }
860
861 #[test]
863 fn deny_notifications_params_uses_negative_one_fee() {
864 let params = SetPermissionParams {
866 message_box: "notifications".to_string(),
867 sender: Some("03peer".to_string()),
868 recipient_fee: -1,
869 };
870 assert_eq!(params.recipient_fee, -1);
871 assert_eq!(params.message_box, "notifications");
872 }
873
874 #[test]
877 fn deny_fee_produces_blocked_status() {
878 let perm = MessageBoxPermission {
879 sender: Some("03peer".to_string()),
880 message_box: "notifications".to_string(),
881 recipient_fee: -1,
882 created_at: "2024-01-01".to_string(),
883 updated_at: "2024-01-01".to_string(),
884 };
885 assert_eq!(perm.status(), "blocked");
886 }
887
888 #[test]
890 fn quote_constructed_from_header_and_body() {
891 let quote = MessageBoxQuote {
892 delivery_fee: 10,
893 recipient_fee: 50,
894 delivery_agent_identity_key: "03agent".to_string(),
895 };
896 assert_eq!(quote.delivery_fee, 10);
897 assert_eq!(quote.recipient_fee, 50);
898 assert_eq!(quote.delivery_agent_identity_key, "03agent");
899 }
900
901 #[test]
903 fn client_with_permissions_compiles() {
904 let _client = make_client("https://example.com");
905 }
906}