lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

coverage.rs (48816B)


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