coverage.rs (47616B)
1 #[path = "../src/test_fixtures.rs"] 2 mod test_fixtures; 3 4 use nostr::{Event, EventBuilder, Keys, PublicKey, RelayUrl, SecretKey, Timestamp, UnsignedEvent}; 5 use radroots_nostr_connect::prelude::{ 6 RADROOTS_NOSTR_CONNECT_PENDING_CONNECTION_ERROR, RadrootsNostrConnectError, 7 RadrootsNostrConnectMethod, RadrootsNostrConnectPendingConnectionPollOutcome, 8 RadrootsNostrConnectPermission, RadrootsNostrConnectPermissions, RadrootsNostrConnectRequest, 9 RadrootsNostrConnectRequestMessage, RadrootsNostrConnectResponse, 10 RadrootsNostrConnectResponseEnvelope, RadrootsNostrConnectUri, 11 }; 12 use serde_json::{Value, json}; 13 use std::str::FromStr; 14 use test_fixtures::{ 15 APP_PRIMARY_HTTPS, CDN_PRIMARY_HTTPS, FIXTURE_ALICE, RELAY_PRIMARY_WSS, RELAY_SECONDARY_WSS, 16 RELAY_TERTIARY_WSS, 17 }; 18 19 fn test_public_key() -> PublicKey { 20 PublicKey::parse(FIXTURE_ALICE.public_key_hex).expect("public key") 21 } 22 23 fn test_keys() -> Keys { 24 let secret_key = SecretKey::from_hex(FIXTURE_ALICE.secret_key_hex).expect("secret key"); 25 Keys::new(secret_key) 26 } 27 28 fn encode_uri_component(value: &str) -> String { 29 url::form_urlencoded::byte_serialize(value.as_bytes()).collect() 30 } 31 32 fn logo_url() -> String { 33 format!("{CDN_PRIMARY_HTTPS}/logo.png") 34 } 35 36 fn unsigned_event() -> UnsignedEvent { 37 serde_json::from_value(json!({ 38 "pubkey": test_public_key().to_hex(), 39 "created_at": 1714078911u64, 40 "kind": 1u16, 41 "tags": [], 42 "content": "hello" 43 })) 44 .expect("unsigned event") 45 } 46 47 fn signed_event() -> Event { 48 EventBuilder::text_note("hello world") 49 .custom_created_at(Timestamp::from(1_714_078_911)) 50 .sign_with_keys(&test_keys()) 51 .expect("sign event") 52 } 53 54 fn relay(value: &str) -> RelayUrl { 55 RelayUrl::parse(value).expect("relay") 56 } 57 58 #[test] 59 fn error_method_and_permission_surfaces_cover_public_paths() { 60 let json_error = serde_json::from_str::<Value>("{").expect_err("invalid json"); 61 assert!(matches!( 62 RadrootsNostrConnectError::from(json_error), 63 RadrootsNostrConnectError::Json(message) if !message.is_empty() 64 )); 65 66 let methods = [ 67 (RadrootsNostrConnectMethod::Connect, "connect"), 68 (RadrootsNostrConnectMethod::GetPublicKey, "get_public_key"), 69 ( 70 RadrootsNostrConnectMethod::GetSessionCapability, 71 "get_session_capability", 72 ), 73 (RadrootsNostrConnectMethod::SignEvent, "sign_event"), 74 (RadrootsNostrConnectMethod::Nip04Encrypt, "nip04_encrypt"), 75 (RadrootsNostrConnectMethod::Nip04Decrypt, "nip04_decrypt"), 76 (RadrootsNostrConnectMethod::Nip44Encrypt, "nip44_encrypt"), 77 (RadrootsNostrConnectMethod::Nip44Decrypt, "nip44_decrypt"), 78 (RadrootsNostrConnectMethod::Ping, "ping"), 79 (RadrootsNostrConnectMethod::SwitchRelays, "switch_relays"), 80 ]; 81 for (method, raw) in methods { 82 assert_eq!(method.as_str(), raw); 83 assert_eq!(method.to_string(), raw); 84 assert_eq!( 85 RadrootsNostrConnectMethod::from_str(raw).expect("parse method"), 86 method 87 ); 88 } 89 assert_eq!( 90 RadrootsNostrConnectMethod::from_str("publish_note").expect("custom method"), 91 RadrootsNostrConnectMethod::Custom("publish_note".to_owned()) 92 ); 93 assert!(matches!( 94 RadrootsNostrConnectMethod::from_str(" "), 95 Err(RadrootsNostrConnectError::InvalidMethod(value)) if value == " " 96 )); 97 assert_eq!( 98 serde_json::from_str::<RadrootsNostrConnectMethod>("\"do_work\"") 99 .expect("deserialize custom method"), 100 RadrootsNostrConnectMethod::Custom("do_work".to_owned()) 101 ); 102 assert!( 103 serde_json::from_str::<RadrootsNostrConnectMethod>("123") 104 .expect_err("non-string method") 105 .to_string() 106 .contains("invalid type") 107 ); 108 assert!( 109 serde_json::from_str::<RadrootsNostrConnectMethod>("\"\"") 110 .expect_err("blank method") 111 .to_string() 112 .contains("invalid NIP-46 method") 113 ); 114 115 let simple = RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Ping); 116 assert_eq!(simple.to_string(), "ping"); 117 let parameterized = RadrootsNostrConnectPermission::with_parameter( 118 RadrootsNostrConnectMethod::SignEvent, 119 "1059", 120 ); 121 assert_eq!(parameterized.to_string(), "sign_event:1059"); 122 assert_eq!( 123 RadrootsNostrConnectPermission::from_str("sign_event:1059").expect("parse permission"), 124 parameterized 125 ); 126 assert!(matches!( 127 RadrootsNostrConnectPermission::from_str(" "), 128 Err(RadrootsNostrConnectError::InvalidPermission(value)) if value == " " 129 )); 130 assert!(matches!( 131 RadrootsNostrConnectPermission::from_str("sign_event:"), 132 Err(RadrootsNostrConnectError::InvalidPermission(value)) if value == "sign_event:" 133 )); 134 assert!(matches!( 135 RadrootsNostrConnectPermission::from_str(" :kind"), 136 Err(RadrootsNostrConnectError::InvalidMethod(_)) 137 )); 138 139 let empty = RadrootsNostrConnectPermissions::new(); 140 assert!(empty.is_empty()); 141 assert!(empty.as_slice().is_empty()); 142 assert!(empty.clone().into_vec().is_empty()); 143 assert_eq!( 144 RadrootsNostrConnectPermissions::from_str(" ").expect("empty permissions"), 145 empty 146 ); 147 148 let permissions = RadrootsNostrConnectPermissions::from(vec![ 149 RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Nip44Encrypt), 150 RadrootsNostrConnectPermission::with_parameter(RadrootsNostrConnectMethod::SignEvent, "13"), 151 ]); 152 assert_eq!(permissions.to_string(), "nip44_encrypt,sign_event:13"); 153 assert_eq!( 154 serde_json::to_string(&permissions).expect("serialize permissions"), 155 "\"nip44_encrypt,sign_event:13\"" 156 ); 157 assert_eq!( 158 serde_json::from_str::<RadrootsNostrConnectPermissions>("\"nip44_encrypt,sign_event:13\"") 159 .expect("deserialize permissions"), 160 permissions 161 ); 162 assert!( 163 serde_json::from_str::<RadrootsNostrConnectPermissions>("123") 164 .expect_err("non-string permissions") 165 .to_string() 166 .contains("invalid type") 167 ); 168 assert!(matches!( 169 RadrootsNostrConnectPermissions::from_str("sign_event:,ping"), 170 Err(RadrootsNostrConnectError::InvalidPermission(value)) if value == "sign_event:" 171 )); 172 173 let all_sign_events = 174 RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::SignEvent); 175 assert!(all_sign_events.matches_sign_event_kind(30402)); 176 assert!(all_sign_events.matches_request(&RadrootsNostrConnectMethod::SignEvent, None)); 177 assert!(!all_sign_events.matches_request(&RadrootsNostrConnectMethod::Ping, None)); 178 179 let numeric_sign_event = RadrootsNostrConnectPermission::with_parameter( 180 RadrootsNostrConnectMethod::SignEvent, 181 "30402", 182 ); 183 let kind_prefixed_sign_event = RadrootsNostrConnectPermission::with_parameter( 184 RadrootsNostrConnectMethod::SignEvent, 185 "kind:30402", 186 ); 187 assert!(numeric_sign_event.matches_sign_event_kind(30402)); 188 assert!(kind_prefixed_sign_event.matches_sign_event_kind(30402)); 189 assert!( 190 numeric_sign_event 191 .matches_request(&RadrootsNostrConnectMethod::SignEvent, Some("kind:30402")) 192 ); 193 assert!(!numeric_sign_event.matches_sign_event_kind(3040)); 194 assert!( 195 !RadrootsNostrConnectPermission::with_parameter( 196 RadrootsNostrConnectMethod::SignEvent, 197 "130402" 198 ) 199 .matches_sign_event_kind(30402) 200 ); 201 assert!( 202 !RadrootsNostrConnectPermission::with_parameter( 203 RadrootsNostrConnectMethod::SignEvent, 204 "not-a-kind" 205 ) 206 .matches_request( 207 &RadrootsNostrConnectMethod::SignEvent, 208 Some("also-not-a-kind") 209 ) 210 ); 211 212 let typed_permissions = RadrootsNostrConnectPermissions::from(vec![ 213 RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Ping), 214 kind_prefixed_sign_event, 215 ]); 216 assert!(typed_permissions.allows_request(&RadrootsNostrConnectMethod::Ping, None)); 217 assert!(typed_permissions.allows_sign_event_kind(30402)); 218 assert!(!typed_permissions.allows_sign_event_kind(0)); 219 } 220 221 #[test] 222 fn uri_surface_covers_rendering_ignored_queries_and_error_paths() { 223 let bunker = RadrootsNostrConnectUri::parse(&format!( 224 "bunker://{}?relay={}&foo=bar", 225 FIXTURE_ALICE.public_key_hex, 226 encode_uri_component(RELAY_PRIMARY_WSS), 227 )) 228 .expect("parse bunker"); 229 let bunker_rendered = bunker.to_string(); 230 assert!(bunker_rendered.contains(&format!( 231 "relay={}", 232 encode_uri_component(RELAY_PRIMARY_WSS) 233 ))); 234 assert!(!bunker_rendered.contains("secret=")); 235 236 let minimal_client: RadrootsNostrConnectUri = format!( 237 "nostrconnect://{}?relay={}&secret=shared", 238 FIXTURE_ALICE.public_key_hex, 239 encode_uri_component(RELAY_PRIMARY_WSS), 240 ) 241 .parse() 242 .expect("parse minimal client"); 243 let minimal_client_rendered = minimal_client.to_string(); 244 assert!(minimal_client_rendered.contains("secret=shared")); 245 assert!(!minimal_client_rendered.contains("perms=")); 246 assert!(!minimal_client_rendered.contains("name=")); 247 assert!(!minimal_client_rendered.contains("url=")); 248 assert!(!minimal_client_rendered.contains("image=")); 249 250 let metadata_client = RadrootsNostrConnectUri::parse(&format!( 251 "nostrconnect://{}?relay={}&secret=shared&perms=ping&name=myc&url={}&image={}&ignored=value", 252 FIXTURE_ALICE.public_key_hex, 253 encode_uri_component(RELAY_PRIMARY_WSS), 254 encode_uri_component(APP_PRIMARY_HTTPS), 255 encode_uri_component(&logo_url()), 256 )) 257 .expect("parse metadata client"); 258 let metadata_rendered = metadata_client.to_string(); 259 assert!(metadata_rendered.contains("perms=ping")); 260 assert!(metadata_rendered.contains("name=myc")); 261 assert!(metadata_rendered.contains(&format!( 262 "url={}", 263 encode_uri_component(&format!("{APP_PRIMARY_HTTPS}/")) 264 ))); 265 assert!(metadata_rendered.contains(&format!("image={}", encode_uri_component(&logo_url())))); 266 267 assert!(matches!( 268 RadrootsNostrConnectUri::parse("not a uri"), 269 Err(RadrootsNostrConnectError::InvalidUrl { .. }) 270 )); 271 assert!(matches!( 272 RadrootsNostrConnectUri::parse( 273 "nostrconnect:///path?relay=wss%3A%2F%2Frelay.example.com&secret=abc" 274 ), 275 Err(RadrootsNostrConnectError::MissingPublicKey) 276 )); 277 assert!(matches!( 278 RadrootsNostrConnectUri::parse(&format!("bunker://{}", FIXTURE_ALICE.public_key_hex)), 279 Err(RadrootsNostrConnectError::MissingRelay) 280 )); 281 assert!(matches!( 282 RadrootsNostrConnectUri::parse(&format!( 283 "nostrconnect://{}?secret=abc", 284 FIXTURE_ALICE.public_key_hex 285 )), 286 Err(RadrootsNostrConnectError::MissingRelay) 287 )); 288 assert!(matches!( 289 RadrootsNostrConnectUri::parse(&format!( 290 "nostrconnect://{}?relay={}", 291 FIXTURE_ALICE.public_key_hex, 292 encode_uri_component(RELAY_PRIMARY_WSS), 293 )), 294 Err(RadrootsNostrConnectError::MissingSecret) 295 )); 296 assert!(matches!( 297 RadrootsNostrConnectUri::parse("https://example.com"), 298 Err(RadrootsNostrConnectError::InvalidUriScheme(value)) if value == "https" 299 )); 300 assert!(matches!( 301 RadrootsNostrConnectUri::parse( 302 "nostrconnect://bad-key?relay=wss%3A%2F%2Frelay.example.com&secret=abc" 303 ), 304 Err(RadrootsNostrConnectError::InvalidPublicKey { .. }) 305 )); 306 assert!(matches!( 307 RadrootsNostrConnectUri::parse(&format!( 308 "nostrconnect://{}?relay=http%3A%2F%2Frelay.example.com&secret=abc", 309 FIXTURE_ALICE.public_key_hex 310 )), 311 Err(RadrootsNostrConnectError::InvalidRelayUrl { .. }) 312 )); 313 assert!(matches!( 314 RadrootsNostrConnectUri::parse(&format!( 315 "nostrconnect://{}?relay={}&secret=abc&url=not-a-url", 316 FIXTURE_ALICE.public_key_hex, 317 encode_uri_component(RELAY_PRIMARY_WSS), 318 )), 319 Err(RadrootsNostrConnectError::InvalidUrl { value, .. }) if value == "not-a-url" 320 )); 321 assert!(matches!( 322 RadrootsNostrConnectUri::parse("bunker://bad-key?relay=wss%3A%2F%2Frelay.example.com"), 323 Err(RadrootsNostrConnectError::InvalidPublicKey { .. }) 324 )); 325 assert!(matches!( 326 RadrootsNostrConnectUri::parse(&format!( 327 "bunker://{}?relay=http%3A%2F%2Frelay.example.com", 328 FIXTURE_ALICE.public_key_hex 329 )), 330 Err(RadrootsNostrConnectError::InvalidRelayUrl { .. }) 331 )); 332 assert!(matches!( 333 RadrootsNostrConnectUri::parse(&format!( 334 "nostrconnect://{}?relay={}&secret=abc&perms=sign_event%3A", 335 FIXTURE_ALICE.public_key_hex, 336 encode_uri_component(RELAY_PRIMARY_WSS), 337 )), 338 Err(RadrootsNostrConnectError::InvalidPermission(value)) if value == "sign_event:" 339 )); 340 assert!(matches!( 341 RadrootsNostrConnectUri::parse(&format!( 342 "nostrconnect://{}?relay={}&secret=abc&image=not-a-url", 343 FIXTURE_ALICE.public_key_hex, 344 encode_uri_component(RELAY_PRIMARY_WSS), 345 )), 346 Err(RadrootsNostrConnectError::InvalidUrl { value, .. }) if value == "not-a-url" 347 )); 348 } 349 350 #[test] 351 fn request_surface_covers_variant_methods_serialization_and_validation() { 352 let ping_permission = 353 RadrootsNostrConnectPermissions::from(vec![RadrootsNostrConnectPermission::new( 354 RadrootsNostrConnectMethod::Ping, 355 )]); 356 357 let requests = vec![ 358 ( 359 RadrootsNostrConnectRequest::Connect { 360 remote_signer_public_key: test_public_key(), 361 secret: None, 362 requested_permissions: RadrootsNostrConnectPermissions::default(), 363 }, 364 RadrootsNostrConnectMethod::Connect, 365 vec![test_public_key().to_hex()], 366 ), 367 ( 368 RadrootsNostrConnectRequest::Connect { 369 remote_signer_public_key: test_public_key(), 370 secret: None, 371 requested_permissions: ping_permission.clone(), 372 }, 373 RadrootsNostrConnectMethod::Connect, 374 vec![test_public_key().to_hex(), String::new(), "ping".to_owned()], 375 ), 376 ( 377 RadrootsNostrConnectRequest::GetPublicKey, 378 RadrootsNostrConnectMethod::GetPublicKey, 379 Vec::new(), 380 ), 381 ( 382 RadrootsNostrConnectRequest::GetSessionCapability, 383 RadrootsNostrConnectMethod::GetSessionCapability, 384 Vec::new(), 385 ), 386 ( 387 RadrootsNostrConnectRequest::SignEvent(unsigned_event()), 388 RadrootsNostrConnectMethod::SignEvent, 389 vec![serde_json::to_string(&unsigned_event()).expect("serialize unsigned event")], 390 ), 391 ( 392 RadrootsNostrConnectRequest::Nip04Encrypt { 393 public_key: test_public_key(), 394 plaintext: "hello".to_owned(), 395 }, 396 RadrootsNostrConnectMethod::Nip04Encrypt, 397 vec![test_public_key().to_hex(), "hello".to_owned()], 398 ), 399 ( 400 RadrootsNostrConnectRequest::Nip04Decrypt { 401 public_key: test_public_key(), 402 ciphertext: "cipher".to_owned(), 403 }, 404 RadrootsNostrConnectMethod::Nip04Decrypt, 405 vec![test_public_key().to_hex(), "cipher".to_owned()], 406 ), 407 ( 408 RadrootsNostrConnectRequest::Nip44Encrypt { 409 public_key: test_public_key(), 410 plaintext: "hello".to_owned(), 411 }, 412 RadrootsNostrConnectMethod::Nip44Encrypt, 413 vec![test_public_key().to_hex(), "hello".to_owned()], 414 ), 415 ( 416 RadrootsNostrConnectRequest::Nip44Decrypt { 417 public_key: test_public_key(), 418 ciphertext: "cipher".to_owned(), 419 }, 420 RadrootsNostrConnectMethod::Nip44Decrypt, 421 vec![test_public_key().to_hex(), "cipher".to_owned()], 422 ), 423 ( 424 RadrootsNostrConnectRequest::Ping, 425 RadrootsNostrConnectMethod::Ping, 426 Vec::new(), 427 ), 428 ( 429 RadrootsNostrConnectRequest::SwitchRelays, 430 RadrootsNostrConnectMethod::SwitchRelays, 431 Vec::new(), 432 ), 433 ( 434 RadrootsNostrConnectRequest::Custom { 435 method: RadrootsNostrConnectMethod::Custom("publish_note".to_owned()), 436 params: vec!["one".to_owned(), "two".to_owned()], 437 }, 438 RadrootsNostrConnectMethod::Custom("publish_note".to_owned()), 439 vec!["one".to_owned(), "two".to_owned()], 440 ), 441 ]; 442 for (request, method, params) in requests { 443 assert_eq!(request.method(), method); 444 assert_eq!(request.to_params(), params); 445 } 446 447 assert_eq!( 448 RadrootsNostrConnectRequest::from_parts( 449 RadrootsNostrConnectMethod::Connect, 450 vec![test_public_key().to_hex()], 451 ) 452 .expect("connect without secret or perms"), 453 RadrootsNostrConnectRequest::Connect { 454 remote_signer_public_key: test_public_key(), 455 secret: None, 456 requested_permissions: RadrootsNostrConnectPermissions::default(), 457 } 458 ); 459 assert_eq!( 460 RadrootsNostrConnectRequest::from_parts( 461 RadrootsNostrConnectMethod::Connect, 462 vec![test_public_key().to_hex(), String::new(), "ping".to_owned()], 463 ) 464 .expect("connect with empty secret"), 465 RadrootsNostrConnectRequest::Connect { 466 remote_signer_public_key: test_public_key(), 467 secret: None, 468 requested_permissions: RadrootsNostrConnectPermissions::from(vec![ 469 RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Ping), 470 ]), 471 } 472 ); 473 assert_eq!( 474 RadrootsNostrConnectRequest::from_parts( 475 RadrootsNostrConnectMethod::GetPublicKey, 476 Vec::new(), 477 ) 478 .expect("get_public_key from parts"), 479 RadrootsNostrConnectRequest::GetPublicKey 480 ); 481 assert_eq!( 482 RadrootsNostrConnectRequest::from_parts( 483 RadrootsNostrConnectMethod::GetSessionCapability, 484 Vec::new(), 485 ) 486 .expect("get_session_capability from parts"), 487 RadrootsNostrConnectRequest::GetSessionCapability 488 ); 489 assert_eq!( 490 RadrootsNostrConnectRequest::from_parts( 491 RadrootsNostrConnectMethod::Nip04Encrypt, 492 vec![test_public_key().to_hex(), "hello".to_owned()], 493 ) 494 .expect("nip04 encrypt from parts"), 495 RadrootsNostrConnectRequest::Nip04Encrypt { 496 public_key: test_public_key(), 497 plaintext: "hello".to_owned(), 498 } 499 ); 500 assert_eq!( 501 RadrootsNostrConnectRequest::from_parts( 502 RadrootsNostrConnectMethod::Nip04Decrypt, 503 vec![test_public_key().to_hex(), "cipher".to_owned()], 504 ) 505 .expect("nip04 decrypt from parts"), 506 RadrootsNostrConnectRequest::Nip04Decrypt { 507 public_key: test_public_key(), 508 ciphertext: "cipher".to_owned(), 509 } 510 ); 511 assert_eq!( 512 RadrootsNostrConnectRequest::from_parts( 513 RadrootsNostrConnectMethod::Nip44Encrypt, 514 vec![test_public_key().to_hex(), "hello".to_owned()], 515 ) 516 .expect("nip44 encrypt from parts"), 517 RadrootsNostrConnectRequest::Nip44Encrypt { 518 public_key: test_public_key(), 519 plaintext: "hello".to_owned(), 520 } 521 ); 522 assert_eq!( 523 RadrootsNostrConnectRequest::from_parts( 524 RadrootsNostrConnectMethod::Nip44Decrypt, 525 vec![test_public_key().to_hex(), "cipher".to_owned()], 526 ) 527 .expect("nip44 decrypt from parts"), 528 RadrootsNostrConnectRequest::Nip44Decrypt { 529 public_key: test_public_key(), 530 ciphertext: "cipher".to_owned(), 531 } 532 ); 533 assert_eq!( 534 RadrootsNostrConnectRequest::from_parts(RadrootsNostrConnectMethod::Ping, Vec::new()) 535 .expect("ping from parts"), 536 RadrootsNostrConnectRequest::Ping 537 ); 538 assert_eq!( 539 RadrootsNostrConnectRequest::from_parts( 540 RadrootsNostrConnectMethod::SwitchRelays, 541 Vec::new(), 542 ) 543 .expect("switch relays from parts"), 544 RadrootsNostrConnectRequest::SwitchRelays 545 ); 546 547 for (method, params, expected_error) in [ 548 ( 549 RadrootsNostrConnectMethod::GetPublicKey, 550 vec!["oops".to_owned()], 551 "no params", 552 ), 553 ( 554 RadrootsNostrConnectMethod::GetSessionCapability, 555 vec!["oops".to_owned()], 556 "no params", 557 ), 558 ( 559 RadrootsNostrConnectMethod::SignEvent, 560 Vec::new(), 561 "exactly 1 param", 562 ), 563 ( 564 RadrootsNostrConnectMethod::Nip04Encrypt, 565 vec!["only-one".to_owned()], 566 "exactly 2 params", 567 ), 568 ( 569 RadrootsNostrConnectMethod::Nip04Decrypt, 570 vec!["only-one".to_owned()], 571 "exactly 2 params", 572 ), 573 ( 574 RadrootsNostrConnectMethod::Nip44Encrypt, 575 vec!["only-one".to_owned()], 576 "exactly 2 params", 577 ), 578 ( 579 RadrootsNostrConnectMethod::Nip44Decrypt, 580 vec!["only-one".to_owned()], 581 "exactly 2 params", 582 ), 583 ( 584 RadrootsNostrConnectMethod::Ping, 585 vec!["oops".to_owned()], 586 "no params", 587 ), 588 ( 589 RadrootsNostrConnectMethod::SwitchRelays, 590 vec!["oops".to_owned()], 591 "no params", 592 ), 593 ] { 594 assert!(matches!( 595 RadrootsNostrConnectRequest::from_parts(method, params), 596 Err(RadrootsNostrConnectError::InvalidParams { expected, .. }) if expected == expected_error 597 )); 598 } 599 assert!(matches!( 600 RadrootsNostrConnectRequest::from_parts(RadrootsNostrConnectMethod::Connect, Vec::new()), 601 Err(RadrootsNostrConnectError::InvalidParams { expected, received, .. }) 602 if expected == "1 to 3 params" && received == 0 603 )); 604 assert!(matches!( 605 RadrootsNostrConnectRequest::from_parts( 606 RadrootsNostrConnectMethod::Connect, 607 vec!["bad-key".to_owned()], 608 ), 609 Err(RadrootsNostrConnectError::InvalidPublicKey { .. }) 610 )); 611 assert!(matches!( 612 RadrootsNostrConnectRequest::from_parts( 613 RadrootsNostrConnectMethod::Connect, 614 vec![test_public_key().to_hex(), "secret".to_owned(), "sign_event:".to_owned()], 615 ), 616 Err(RadrootsNostrConnectError::InvalidPermission(value)) if value == "sign_event:" 617 )); 618 assert!(matches!( 619 RadrootsNostrConnectRequest::from_parts( 620 RadrootsNostrConnectMethod::Connect, 621 vec![ 622 test_public_key().to_hex(), 623 "secret".to_owned(), 624 "ping".to_owned(), 625 "extra".to_owned(), 626 ], 627 ), 628 Err(RadrootsNostrConnectError::InvalidParams { expected, received, .. }) 629 if expected == "1 to 3 params" && received == 4 630 )); 631 assert!(matches!( 632 RadrootsNostrConnectRequest::from_parts( 633 RadrootsNostrConnectMethod::SignEvent, 634 vec!["not-json".to_owned()], 635 ), 636 Err(RadrootsNostrConnectError::InvalidRequestPayload { .. }) 637 )); 638 assert!(matches!( 639 RadrootsNostrConnectRequest::from_parts( 640 RadrootsNostrConnectMethod::Nip04Encrypt, 641 vec!["bad-key".to_owned(), "hello".to_owned()], 642 ), 643 Err(RadrootsNostrConnectError::InvalidPublicKey { .. }) 644 )); 645 assert!(matches!( 646 RadrootsNostrConnectRequest::from_parts( 647 RadrootsNostrConnectMethod::Nip04Decrypt, 648 vec!["bad-key".to_owned(), "cipher".to_owned()], 649 ), 650 Err(RadrootsNostrConnectError::InvalidPublicKey { .. }) 651 )); 652 assert!(matches!( 653 RadrootsNostrConnectRequest::from_parts( 654 RadrootsNostrConnectMethod::Nip44Encrypt, 655 vec!["bad-key".to_owned(), "hello".to_owned()], 656 ), 657 Err(RadrootsNostrConnectError::InvalidPublicKey { .. }) 658 )); 659 assert!(matches!( 660 RadrootsNostrConnectRequest::from_parts( 661 RadrootsNostrConnectMethod::Nip44Decrypt, 662 vec!["bad-key".to_owned(), "cipher".to_owned()], 663 ), 664 Err(RadrootsNostrConnectError::InvalidPublicKey { .. }) 665 )); 666 667 let custom_message = RadrootsNostrConnectRequestMessage::new( 668 "req-custom", 669 RadrootsNostrConnectRequest::Custom { 670 method: RadrootsNostrConnectMethod::Custom("publish_note".to_owned()), 671 params: vec!["a".to_owned()], 672 }, 673 ); 674 let encoded = serde_json::to_string(&custom_message).expect("serialize custom request"); 675 let decoded: RadrootsNostrConnectRequestMessage = 676 serde_json::from_str(&encoded).expect("deserialize custom request"); 677 assert_eq!(decoded, custom_message); 678 assert!( 679 serde_json::from_str::<RadrootsNostrConnectRequestMessage>("{") 680 .expect_err("invalid request message json") 681 .to_string() 682 .contains("EOF") 683 ); 684 assert!( 685 serde_json::from_str::<RadrootsNostrConnectRequestMessage>( 686 "{\"id\":\"req\",\"method\":\"get_public_key\",\"params\":[\"oops\"]}", 687 ) 688 .expect_err("invalid request params") 689 .to_string() 690 .contains("invalid parameter count") 691 ); 692 } 693 694 #[test] 695 fn response_surface_covers_success_and_error_paths() { 696 let event = signed_event(); 697 let remote_session_capability = 698 radroots_nostr_connect::prelude::RadrootsNostrConnectRemoteSessionCapability { 699 user_public_key: test_public_key(), 700 relays: vec![relay(RELAY_PRIMARY_WSS), relay(RELAY_SECONDARY_WSS)], 701 permissions: RadrootsNostrConnectPermissions::from(vec![ 702 RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Ping), 703 RadrootsNostrConnectPermission::with_parameter( 704 RadrootsNostrConnectMethod::SignEvent, 705 "kind:1", 706 ), 707 ]), 708 }; 709 let cases = vec![ 710 ( 711 RadrootsNostrConnectResponse::ConnectAcknowledged, 712 RadrootsNostrConnectMethod::Connect, 713 RadrootsNostrConnectResponse::ConnectAcknowledged, 714 ), 715 ( 716 RadrootsNostrConnectResponse::ConnectSecretEcho("secret".to_owned()), 717 RadrootsNostrConnectMethod::Connect, 718 RadrootsNostrConnectResponse::ConnectSecretEcho("secret".to_owned()), 719 ), 720 ( 721 RadrootsNostrConnectResponse::UserPublicKey(test_public_key()), 722 RadrootsNostrConnectMethod::GetPublicKey, 723 RadrootsNostrConnectResponse::UserPublicKey(test_public_key()), 724 ), 725 ( 726 RadrootsNostrConnectResponse::PendingConnection, 727 RadrootsNostrConnectMethod::GetSessionCapability, 728 RadrootsNostrConnectResponse::PendingConnection, 729 ), 730 ( 731 RadrootsNostrConnectResponse::RemoteSessionCapability( 732 remote_session_capability.clone(), 733 ), 734 RadrootsNostrConnectMethod::GetSessionCapability, 735 RadrootsNostrConnectResponse::RemoteSessionCapability( 736 remote_session_capability.clone(), 737 ), 738 ), 739 ( 740 RadrootsNostrConnectResponse::SignedEvent(event.clone()), 741 RadrootsNostrConnectMethod::SignEvent, 742 RadrootsNostrConnectResponse::SignedEvent(event.clone()), 743 ), 744 ( 745 RadrootsNostrConnectResponse::Pong, 746 RadrootsNostrConnectMethod::Ping, 747 RadrootsNostrConnectResponse::Pong, 748 ), 749 ( 750 RadrootsNostrConnectResponse::Nip04Encrypt("cipher".to_owned()), 751 RadrootsNostrConnectMethod::Nip04Encrypt, 752 RadrootsNostrConnectResponse::Nip04Encrypt("cipher".to_owned()), 753 ), 754 ( 755 RadrootsNostrConnectResponse::Nip04Decrypt("plain".to_owned()), 756 RadrootsNostrConnectMethod::Nip04Decrypt, 757 RadrootsNostrConnectResponse::Nip04Decrypt("plain".to_owned()), 758 ), 759 ( 760 RadrootsNostrConnectResponse::Nip44Encrypt("cipher".to_owned()), 761 RadrootsNostrConnectMethod::Nip44Encrypt, 762 RadrootsNostrConnectResponse::Nip44Encrypt("cipher".to_owned()), 763 ), 764 ( 765 RadrootsNostrConnectResponse::Nip44Decrypt("plain".to_owned()), 766 RadrootsNostrConnectMethod::Nip44Decrypt, 767 RadrootsNostrConnectResponse::Nip44Decrypt("plain".to_owned()), 768 ), 769 ( 770 RadrootsNostrConnectResponse::RelayList(vec![ 771 relay(RELAY_SECONDARY_WSS), 772 relay(RELAY_TERTIARY_WSS), 773 ]), 774 RadrootsNostrConnectMethod::SwitchRelays, 775 RadrootsNostrConnectResponse::RelayList(vec![ 776 relay(RELAY_SECONDARY_WSS), 777 relay(RELAY_TERTIARY_WSS), 778 ]), 779 ), 780 ( 781 RadrootsNostrConnectResponse::RelayListUnchanged, 782 RadrootsNostrConnectMethod::SwitchRelays, 783 RadrootsNostrConnectResponse::RelayListUnchanged, 784 ), 785 ]; 786 for (response, method, expected) in cases { 787 let envelope = response.into_envelope("req").expect("serialize response"); 788 let parsed = 789 RadrootsNostrConnectResponse::from_envelope(&method, envelope).expect("parse response"); 790 assert_eq!(parsed, expected); 791 } 792 793 let error_envelope = RadrootsNostrConnectResponse::Error { 794 result: Some(json!("partial")), 795 error: "denied".to_owned(), 796 } 797 .into_envelope("req-error") 798 .expect("serialize error response"); 799 assert_eq!(error_envelope.error.as_deref(), Some("denied")); 800 801 let custom_envelope = RadrootsNostrConnectResponse::Custom { 802 result: Some(json!({"ok": true})), 803 error: Some("warning".to_owned()), 804 } 805 .into_envelope("req-custom") 806 .expect("serialize custom response"); 807 assert_eq!(custom_envelope.error.as_deref(), Some("warning")); 808 809 let auth_envelope = 810 RadrootsNostrConnectResponse::AuthUrl("https://auth.example.com/challenge".to_owned()) 811 .into_envelope("req-auth") 812 .expect("serialize auth_url"); 813 assert_eq!( 814 RadrootsNostrConnectResponse::from_envelope( 815 &RadrootsNostrConnectMethod::SignEvent, 816 auth_envelope, 817 ) 818 .expect("parse auth_url"), 819 RadrootsNostrConnectResponse::AuthUrl("https://auth.example.com/challenge".to_owned()) 820 ); 821 822 assert_eq!( 823 RadrootsNostrConnectResponse::from_envelope( 824 &RadrootsNostrConnectMethod::Custom("publish_note".to_owned()), 825 RadrootsNostrConnectResponseEnvelope { 826 id: "req-custom".to_owned(), 827 result: Some(json!("ok")), 828 error: None, 829 }, 830 ) 831 .expect("parse custom response without error"), 832 RadrootsNostrConnectResponse::Custom { 833 result: Some(json!("ok")), 834 error: None, 835 } 836 ); 837 assert_eq!( 838 RadrootsNostrConnectResponse::from_envelope( 839 &RadrootsNostrConnectMethod::Custom("publish_note".to_owned()), 840 RadrootsNostrConnectResponseEnvelope { 841 id: "req-custom".to_owned(), 842 result: Some(json!({"ok": true})), 843 error: Some("warning".to_owned()), 844 }, 845 ) 846 .expect("parse custom response"), 847 RadrootsNostrConnectResponse::Custom { 848 result: Some(json!({"ok": true})), 849 error: Some("warning".to_owned()), 850 } 851 ); 852 assert_eq!( 853 RadrootsNostrConnectResponse::from_envelope( 854 &RadrootsNostrConnectMethod::GetPublicKey, 855 RadrootsNostrConnectResponseEnvelope { 856 id: "req-pending".to_owned(), 857 result: None, 858 error: Some(RADROOTS_NOSTR_CONNECT_PENDING_CONNECTION_ERROR.to_owned()), 859 }, 860 ) 861 .expect("parse typed pending response"), 862 RadrootsNostrConnectResponse::PendingConnection 863 ); 864 assert_eq!( 865 RadrootsNostrConnectResponse::from_envelope( 866 &RadrootsNostrConnectMethod::GetSessionCapability, 867 RadrootsNostrConnectResponseEnvelope { 868 id: "req-pending-capability".to_owned(), 869 result: None, 870 error: Some(RADROOTS_NOSTR_CONNECT_PENDING_CONNECTION_ERROR.to_owned()), 871 }, 872 ) 873 .expect("parse typed pending capability response"), 874 RadrootsNostrConnectResponse::PendingConnection 875 ); 876 assert_eq!( 877 RadrootsNostrConnectResponse::from_envelope( 878 &RadrootsNostrConnectMethod::GetPublicKey, 879 RadrootsNostrConnectResponseEnvelope { 880 id: "req-nonpending-public-key".to_owned(), 881 result: None, 882 error: Some("denied".to_owned()), 883 }, 884 ) 885 .expect("parse non-pending public key error"), 886 RadrootsNostrConnectResponse::Error { 887 result: None, 888 error: "denied".to_owned(), 889 } 890 ); 891 assert_eq!( 892 RadrootsNostrConnectResponse::from_envelope( 893 &RadrootsNostrConnectMethod::GetSessionCapability, 894 RadrootsNostrConnectResponseEnvelope { 895 id: "req-capability-error-with-result".to_owned(), 896 result: Some(json!({"code": "retry"})), 897 error: Some("denied".to_owned()), 898 }, 899 ) 900 .expect("parse capability error with result"), 901 RadrootsNostrConnectResponse::Error { 902 result: Some(json!({"code": "retry"})), 903 error: "denied".to_owned(), 904 } 905 ); 906 assert!(matches!( 907 RadrootsNostrConnectResponse::from_envelope( 908 &RadrootsNostrConnectMethod::GetSessionCapability, 909 RadrootsNostrConnectResponseEnvelope { 910 id: "req-capability-invalid-result".to_owned(), 911 result: Some(json!({"permissions": "ping"})), 912 error: None, 913 }, 914 ), 915 Err(RadrootsNostrConnectError::InvalidResponsePayload { method, .. }) 916 if method == "get_session_capability" 917 )); 918 assert_eq!( 919 RadrootsNostrConnectResponse::from_envelope( 920 &RadrootsNostrConnectMethod::GetSessionCapability, 921 RadrootsNostrConnectResponseEnvelope { 922 id: "req-capability-string-result".to_owned(), 923 result: Some(json!( 924 serde_json::to_string(&remote_session_capability) 925 .expect("serialize remote session capability") 926 )), 927 error: None, 928 }, 929 ) 930 .expect("parse stringified capability result"), 931 RadrootsNostrConnectResponse::RemoteSessionCapability(remote_session_capability.clone(),) 932 ); 933 assert!(matches!( 934 RadrootsNostrConnectResponse::from_envelope( 935 &RadrootsNostrConnectMethod::GetSessionCapability, 936 RadrootsNostrConnectResponseEnvelope { 937 id: "req-capability-invalid-string".to_owned(), 938 result: Some(json!("{")), 939 error: None, 940 }, 941 ), 942 Err(RadrootsNostrConnectError::InvalidResponsePayload { method, .. }) 943 if method == "get_session_capability" 944 )); 945 assert_eq!( 946 RadrootsNostrConnectResponse::from_envelope( 947 &RadrootsNostrConnectMethod::Ping, 948 RadrootsNostrConnectResponseEnvelope { 949 id: "req-error".to_owned(), 950 result: Some(json!("partial")), 951 error: Some("denied".to_owned()), 952 }, 953 ) 954 .expect("parse error response"), 955 RadrootsNostrConnectResponse::Error { 956 result: Some(json!("partial")), 957 error: "denied".to_owned(), 958 } 959 ); 960 assert_eq!( 961 RadrootsNostrConnectResponse::from_envelope( 962 &RadrootsNostrConnectMethod::SignEvent, 963 RadrootsNostrConnectResponseEnvelope { 964 id: "req-event".to_owned(), 965 result: Some(serde_json::to_value(&event).expect("event value")), 966 error: None, 967 }, 968 ) 969 .expect("parse object event"), 970 RadrootsNostrConnectResponse::SignedEvent(event) 971 ); 972 assert_eq!( 973 RadrootsNostrConnectResponse::from_envelope( 974 &RadrootsNostrConnectMethod::SwitchRelays, 975 RadrootsNostrConnectResponseEnvelope { 976 id: "req-switch".to_owned(), 977 result: Some(json!("null")), 978 error: None, 979 }, 980 ) 981 .expect("parse string null"), 982 RadrootsNostrConnectResponse::RelayListUnchanged 983 ); 984 assert_eq!( 985 RadrootsNostrConnectResponse::from_envelope( 986 &RadrootsNostrConnectMethod::SwitchRelays, 987 RadrootsNostrConnectResponseEnvelope { 988 id: "req-switch".to_owned(), 989 result: Some(json!(format!("[\"{RELAY_SECONDARY_WSS}\"]"))), 990 error: None, 991 }, 992 ) 993 .expect("parse stringified relay list"), 994 RadrootsNostrConnectResponse::RelayList(vec![relay(RELAY_SECONDARY_WSS)]) 995 ); 996 997 assert!(matches!( 998 RadrootsNostrConnectResponse::AuthUrl("not-a-url".to_owned()).into_envelope("req"), 999 Err(RadrootsNostrConnectError::InvalidUrl { value, .. }) if value == "not-a-url" 1000 )); 1001 assert!(matches!( 1002 RadrootsNostrConnectResponse::from_envelope( 1003 &RadrootsNostrConnectMethod::SignEvent, 1004 RadrootsNostrConnectResponseEnvelope { 1005 id: "req-auth".to_owned(), 1006 result: Some(json!("auth_url")), 1007 error: Some("not-a-url".to_owned()), 1008 }, 1009 ), 1010 Err(RadrootsNostrConnectError::InvalidUrl { value, .. }) if value == "not-a-url" 1011 )); 1012 assert!(matches!( 1013 RadrootsNostrConnectResponse::from_envelope( 1014 &RadrootsNostrConnectMethod::GetPublicKey, 1015 RadrootsNostrConnectResponseEnvelope { 1016 id: "req-key".to_owned(), 1017 result: Some(json!("bad-key")), 1018 error: None, 1019 }, 1020 ), 1021 Err(RadrootsNostrConnectError::InvalidPublicKey { .. }) 1022 )); 1023 assert!(matches!( 1024 RadrootsNostrConnectResponse::from_envelope( 1025 &RadrootsNostrConnectMethod::Connect, 1026 RadrootsNostrConnectResponseEnvelope { 1027 id: "req-connect".to_owned(), 1028 result: None, 1029 error: None, 1030 }, 1031 ), 1032 Err(RadrootsNostrConnectError::MissingResult) 1033 )); 1034 assert!(matches!( 1035 RadrootsNostrConnectResponse::from_envelope( 1036 &RadrootsNostrConnectMethod::GetPublicKey, 1037 RadrootsNostrConnectResponseEnvelope { 1038 id: "req-key".to_owned(), 1039 result: None, 1040 error: None, 1041 }, 1042 ), 1043 Err(RadrootsNostrConnectError::MissingResult) 1044 )); 1045 assert!(matches!( 1046 RadrootsNostrConnectResponse::from_envelope( 1047 &RadrootsNostrConnectMethod::Ping, 1048 RadrootsNostrConnectResponseEnvelope { 1049 id: "req-ping".to_owned(), 1050 result: Some(json!("nope")), 1051 error: None, 1052 }, 1053 ), 1054 Err(RadrootsNostrConnectError::InvalidResponsePayload { .. }) 1055 )); 1056 assert!(matches!( 1057 RadrootsNostrConnectResponse::from_envelope( 1058 &RadrootsNostrConnectMethod::Ping, 1059 RadrootsNostrConnectResponseEnvelope { 1060 id: "req-ping".to_owned(), 1061 result: None, 1062 error: None, 1063 }, 1064 ), 1065 Err(RadrootsNostrConnectError::MissingResult) 1066 )); 1067 assert!(matches!( 1068 RadrootsNostrConnectResponse::from_envelope( 1069 &RadrootsNostrConnectMethod::Nip04Encrypt, 1070 RadrootsNostrConnectResponseEnvelope { 1071 id: "req-nip04".to_owned(), 1072 result: Some(json!(5)), 1073 error: None, 1074 }, 1075 ), 1076 Err(RadrootsNostrConnectError::InvalidResponsePayload { .. }) 1077 )); 1078 assert!(matches!( 1079 RadrootsNostrConnectResponse::from_envelope( 1080 &RadrootsNostrConnectMethod::Nip04Encrypt, 1081 RadrootsNostrConnectResponseEnvelope { 1082 id: "req-nip04".to_owned(), 1083 result: None, 1084 error: None, 1085 }, 1086 ), 1087 Err(RadrootsNostrConnectError::MissingResult) 1088 )); 1089 assert!(matches!( 1090 RadrootsNostrConnectResponse::from_envelope( 1091 &RadrootsNostrConnectMethod::SignEvent, 1092 RadrootsNostrConnectResponseEnvelope { 1093 id: "req-event".to_owned(), 1094 result: Some(json!("not-json")), 1095 error: None, 1096 }, 1097 ), 1098 Err(RadrootsNostrConnectError::InvalidResponsePayload { .. }) 1099 )); 1100 assert!(matches!( 1101 RadrootsNostrConnectResponse::from_envelope( 1102 &RadrootsNostrConnectMethod::SignEvent, 1103 RadrootsNostrConnectResponseEnvelope { 1104 id: "req-event".to_owned(), 1105 result: Some(json!(5)), 1106 error: None, 1107 }, 1108 ), 1109 Err(RadrootsNostrConnectError::InvalidResponsePayload { .. }) 1110 )); 1111 assert!(matches!( 1112 RadrootsNostrConnectResponse::from_envelope( 1113 &RadrootsNostrConnectMethod::SignEvent, 1114 RadrootsNostrConnectResponseEnvelope { 1115 id: "req-event".to_owned(), 1116 result: None, 1117 error: None, 1118 }, 1119 ), 1120 Err(RadrootsNostrConnectError::MissingResult) 1121 )); 1122 assert!(matches!( 1123 RadrootsNostrConnectResponse::from_envelope( 1124 &RadrootsNostrConnectMethod::Nip04Decrypt, 1125 RadrootsNostrConnectResponseEnvelope { 1126 id: "req-nip04d".to_owned(), 1127 result: None, 1128 error: None, 1129 }, 1130 ), 1131 Err(RadrootsNostrConnectError::MissingResult) 1132 )); 1133 assert!(matches!( 1134 RadrootsNostrConnectResponse::from_envelope( 1135 &RadrootsNostrConnectMethod::Nip44Encrypt, 1136 RadrootsNostrConnectResponseEnvelope { 1137 id: "req-nip44e".to_owned(), 1138 result: None, 1139 error: None, 1140 }, 1141 ), 1142 Err(RadrootsNostrConnectError::MissingResult) 1143 )); 1144 assert!(matches!( 1145 RadrootsNostrConnectResponse::from_envelope( 1146 &RadrootsNostrConnectMethod::Nip44Decrypt, 1147 RadrootsNostrConnectResponseEnvelope { 1148 id: "req-nip44d".to_owned(), 1149 result: None, 1150 error: None, 1151 }, 1152 ), 1153 Err(RadrootsNostrConnectError::MissingResult) 1154 )); 1155 assert!(matches!( 1156 RadrootsNostrConnectResponse::from_envelope( 1157 &RadrootsNostrConnectMethod::SwitchRelays, 1158 RadrootsNostrConnectResponseEnvelope { 1159 id: "req-switch".to_owned(), 1160 result: Some(json!("[invalid")), 1161 error: None, 1162 }, 1163 ), 1164 Err(RadrootsNostrConnectError::InvalidResponsePayload { .. }) 1165 )); 1166 assert!(matches!( 1167 RadrootsNostrConnectResponse::from_envelope( 1168 &RadrootsNostrConnectMethod::SwitchRelays, 1169 RadrootsNostrConnectResponseEnvelope { 1170 id: "req-switch".to_owned(), 1171 result: Some(json!([1])), 1172 error: None, 1173 }, 1174 ), 1175 Err(RadrootsNostrConnectError::InvalidResponsePayload { .. }) 1176 )); 1177 assert!(matches!( 1178 RadrootsNostrConnectResponse::from_envelope( 1179 &RadrootsNostrConnectMethod::SwitchRelays, 1180 RadrootsNostrConnectResponseEnvelope { 1181 id: "req-switch".to_owned(), 1182 result: Some(json!(["http://relay.example.com"])), 1183 error: None, 1184 }, 1185 ), 1186 Err(RadrootsNostrConnectError::InvalidRelayUrl { .. }) 1187 )); 1188 assert!(matches!( 1189 RadrootsNostrConnectResponse::from_envelope( 1190 &RadrootsNostrConnectMethod::SwitchRelays, 1191 RadrootsNostrConnectResponseEnvelope { 1192 id: "req-switch".to_owned(), 1193 result: Some(json!(5)), 1194 error: None, 1195 }, 1196 ), 1197 Err(RadrootsNostrConnectError::InvalidResponsePayload { .. }) 1198 )); 1199 } 1200 1201 #[test] 1202 fn pending_connection_poll_outcome_uses_typed_variants() { 1203 let remote_session_capability = 1204 radroots_nostr_connect::prelude::RadrootsNostrConnectRemoteSessionCapability { 1205 user_public_key: test_public_key(), 1206 relays: vec![relay(RELAY_PRIMARY_WSS), relay(RELAY_SECONDARY_WSS)], 1207 permissions: RadrootsNostrConnectPermissions::from(vec![ 1208 RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Ping), 1209 RadrootsNostrConnectPermission::with_parameter( 1210 RadrootsNostrConnectMethod::SignEvent, 1211 "kind:1", 1212 ), 1213 ]), 1214 }; 1215 1216 assert_eq!( 1217 RadrootsNostrConnectResponse::PendingConnection.into_pending_connection_poll_outcome(), 1218 RadrootsNostrConnectPendingConnectionPollOutcome::PendingApproval 1219 ); 1220 1221 assert_eq!( 1222 RadrootsNostrConnectResponse::UserPublicKey(test_public_key()) 1223 .into_pending_connection_poll_outcome(), 1224 RadrootsNostrConnectPendingConnectionPollOutcome::Approved(test_public_key()) 1225 ); 1226 assert_eq!( 1227 RadrootsNostrConnectResponse::RemoteSessionCapability(remote_session_capability.clone()) 1228 .into_pending_connection_poll_outcome(), 1229 RadrootsNostrConnectPendingConnectionPollOutcome::ApprovedCapability( 1230 remote_session_capability 1231 ) 1232 ); 1233 1234 assert_eq!( 1235 RadrootsNostrConnectResponse::Error { 1236 result: Some(json!("partial")), 1237 error: "rejected".to_owned(), 1238 } 1239 .into_pending_connection_poll_outcome(), 1240 RadrootsNostrConnectPendingConnectionPollOutcome::Rejected { 1241 message: "rejected".to_owned(), 1242 } 1243 ); 1244 assert_eq!( 1245 RadrootsNostrConnectResponse::Error { 1246 result: None, 1247 error: RADROOTS_NOSTR_CONNECT_PENDING_CONNECTION_ERROR.to_owned(), 1248 } 1249 .into_pending_connection_poll_outcome(), 1250 RadrootsNostrConnectPendingConnectionPollOutcome::PendingApproval 1251 ); 1252 1253 assert_eq!( 1254 RadrootsNostrConnectResponse::AuthUrl("https://auth.example.com/challenge".to_owned()) 1255 .into_pending_connection_poll_outcome(), 1256 RadrootsNostrConnectPendingConnectionPollOutcome::AuthChallenge { 1257 url: "https://auth.example.com/challenge".to_owned(), 1258 } 1259 ); 1260 1261 assert!(matches!( 1262 RadrootsNostrConnectResponse::Pong.into_pending_connection_poll_outcome(), 1263 RadrootsNostrConnectPendingConnectionPollOutcome::UnexpectedResponse { response } 1264 if response == "Pong" 1265 )); 1266 }