From 6d8128c90bdf1f600bb44c5ff92dc8bb19808aa8 Mon Sep 17 00:00:00 2001 From: Lucas Sunsi Abreu Date: Mon, 4 Dec 2023 08:10:30 -0300 Subject: [PATCH] Fix and add tests for LUD-09 --- Cargo.toml | 4 + README.md | 20 ++--- src/core/pay_request.rs | 182 +++++++++++++++++++++------------------- tests/lud06.rs | 2 +- tests/lud09.rs | 88 +++++++++++++++++++ tests/lud11.rs | 2 +- tests/lud12.rs | 2 +- 7 files changed, 203 insertions(+), 97 deletions(-) create mode 100644 tests/lud09.rs diff --git a/Cargo.toml b/Cargo.toml index 31d5490..4ddd938 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,10 @@ suspicious = "deny" name = "lud06" required-features = ["client", "server"] +[[test]] +name = "lud09" +required-features = ["client", "server"] + [[test]] name = "lud11" required-features = ["client", "server"] diff --git a/README.md b/README.md index 4f25bfb..3e2b5b4 100644 --- a/README.md +++ b/README.md @@ -9,26 +9,26 @@ This library works as a toolkit so you can serve and make your LNURL requests wi ## Current support -- [LUD-01](https://github.com/lnurl/luds/blob/luds/01.md): ✅ core ✅ client ⚠️ server ⚠️ tests -- [LUD-02](https://github.com/lnurl/luds/blob/luds/02.md): ⚠️ core ⚠️ client 🆘 server 🆘 tests -- [LUD-03](https://github.com/lnurl/luds/blob/luds/03.md): ⚠️ core ⚠️ client 🆘 server 🆘 tests +- [LUD-01](https://github.com/lnurl/luds/blob/luds/01.md): ✅ core ✅ client ✅ server ⚠️ tests +- [LUD-02](https://github.com/lnurl/luds/blob/luds/02.md): ✅ core ⚠️ client 🆘 server 🆘 tests +- [LUD-03](https://github.com/lnurl/luds/blob/luds/03.md): ✅ core ⚠️ client 🆘 server 🆘 tests - [LUD-04](https://github.com/lnurl/luds/blob/luds/04.md): 🆘 core 🆘 client 🆘 server 🆘 tests - [LUD-05](https://github.com/lnurl/luds/blob/luds/05.md): 🆘 core 🆘 client 🆘 server 🆘 tests -- [LUD-06](https://github.com/lnurl/luds/blob/luds/06.md): ✅ core ⚠️ client ⚠️ server ⚠️ tests +- [LUD-06](https://github.com/lnurl/luds/blob/luds/06.md): ✅ core ✅ client ✅ server ✅ tests - [LUD-07](https://github.com/lnurl/luds/blob/luds/07.md): 🆘 core 🆘 client 🆘 server 🆘 tests - [LUD-08](https://github.com/lnurl/luds/blob/luds/08.md): 🆘 core 🆘 client 🆘 server 🆘 tests -- [LUD-09](https://github.com/lnurl/luds/blob/luds/09.md): ⚠️ core ⚠️ client 🆘 server 🆘 tests +- [LUD-09](https://github.com/lnurl/luds/blob/luds/09.md): ✅ core ✅ client ✅ server ✅ tests - [LUD-10](https://github.com/lnurl/luds/blob/luds/10.md): 🆘 core 🆘 client 🆘 server 🆘 tests -- [LUD-11](https://github.com/lnurl/luds/blob/luds/11.md): ⚠️ core ⚠️ client 🆘 server 🆘 tests -- [LUD-12](https://github.com/lnurl/luds/blob/luds/12.md): ⚠️ core ⚠️ client 🆘 server 🆘 tests +- [LUD-11](https://github.com/lnurl/luds/blob/luds/11.md): ✅ core ✅ client ✅ server ✅ tests +- [LUD-12](https://github.com/lnurl/luds/blob/luds/12.md): ✅ core ✅ client ✅ server ✅ tests - [LUD-13](https://github.com/lnurl/luds/blob/luds/13.md): 🆘 core 🆘 client 🆘 server 🆘 tests - [LUD-14](https://github.com/lnurl/luds/blob/luds/14.md): 🆘 core 🆘 client 🆘 server 🆘 tests - [LUD-15](https://github.com/lnurl/luds/blob/luds/15.md): 🆘 core 🆘 client 🆘 server 🆘 tests -- [LUD-16](https://github.com/lnurl/luds/blob/luds/16.md): ⚠️ core ⚠️ client 🆘 server 🆘 tests -- [LUD-17](https://github.com/lnurl/luds/blob/luds/17.md): ⚠️ core ⚠️ client ⚠️ server ⚠️ tests +- [LUD-16](https://github.com/lnurl/luds/blob/luds/16.md): ✅ core ⚠️ client 🆘 server 🆘 tests +- [LUD-17](https://github.com/lnurl/luds/blob/luds/17.md): ✅ core ⚠️ client ⚠️ server ⚠️ tests - [LUD-18](https://github.com/lnurl/luds/blob/luds/18.md): 🆘 core 🆘 client 🆘 server 🆘 tests - [LUD-19](https://github.com/lnurl/luds/blob/luds/19.md): 🆘 core 🆘 client 🆘 server 🆘 tests -- [LUD-20](https://github.com/lnurl/luds/blob/luds/20.md): ⚠️ core ⚠️ client 🆘 server 🆘 tests +- [LUD-20](https://github.com/lnurl/luds/blob/luds/20.md): ✅ core ✅ client ✅ server ⚠️ tests - ###### Soon. ™ diff --git a/src/core/pay_request.rs b/src/core/pay_request.rs index dc35737..01f5299 100644 --- a/src/core/pay_request.rs +++ b/src/core/pay_request.rs @@ -5,7 +5,6 @@ pub struct PayRequest { pub callback: url::Url, pub short_description: String, pub long_description: Option, - pub success_action: Option, pub jpeg: Option>, pub png: Option>, pub comment_size: u64, @@ -13,12 +12,6 @@ pub struct PayRequest { pub max: u64, } -#[derive(Clone, Debug)] -pub enum SuccessAction { - Url(url::Url, String), - Message(String), -} - impl std::str::FromStr for PayRequest { type Err = &'static str; @@ -28,18 +21,8 @@ impl std::str::FromStr for PayRequest { let p: de::QueryResponse = miniserde::json::from_str(s).map_err(|_| "deserialize failed")?; - let comment_size = p.comment_allowed.unwrap_or(0); - let success_action = p - .success_action - .and_then(|sa| match sa.get("tag")? as &str { - "message" => Some(SuccessAction::Message(sa.get("message")?.to_owned())), - "url" => { - let url = url::Url::parse(sa.get("url")?).ok()?; - Some(SuccessAction::Url(url, sa.get("description")?.to_owned())) - } - _ => None, - }); + let comment_size = p.comment_allowed.unwrap_or(0); let metadata = miniserde::json::from_str::>(&p.metadata) .map_err(|_| "deserialize metadata failed")?; @@ -83,7 +66,6 @@ impl std::str::FromStr for PayRequest { max: p.max_sendable, short_description, long_description, - success_action, comment_size, jpeg, png, @@ -113,22 +95,6 @@ impl std::fmt::Display for PayRequest { .collect::>(), ); - let success_action = self.success_action.as_ref().map(|sa| { - let mut map = std::collections::BTreeMap::new(); - - match sa { - SuccessAction::Message(m) => { - map.insert("message", m.into()); - } - SuccessAction::Url(u, d) => { - map.insert("description", d.into()); - map.insert("url", u.to_string().into()); - } - } - - map - }); - f.write_str(&miniserde::json::to_string(&ser::QueryResponse { tag: TAG, metadata, @@ -136,7 +102,6 @@ impl std::fmt::Display for PayRequest { min_sendable: self.min, max_sendable: self.max, comment_allowed: self.comment_size, - success_action, })) } } @@ -164,6 +129,13 @@ impl PayRequest { pub struct CallbackResponse { pub pr: String, pub disposable: bool, + pub success_action: Option, +} + +#[derive(Clone, Debug)] +pub enum SuccessAction { + Url(url::Url, String), + Message(String), } impl std::str::FromStr for CallbackResponse { @@ -173,16 +145,47 @@ impl std::str::FromStr for CallbackResponse { let a: de::CallbackResponse = miniserde::json::from_str(s).map_err(|_| "deserialize failed")?; + let success_action = a + .success_action + .and_then(|sa| match sa.get("tag")? as &str { + "message" => Some(SuccessAction::Message(sa.get("message")?.to_owned())), + "url" => { + let url = url::Url::parse(sa.get("url")?).ok()?; + Some(SuccessAction::Url(url, sa.get("description")?.to_owned())) + } + _ => None, + }); + Ok(Self { pr: a.pr, disposable: a.disposable.unwrap_or(true), + success_action, }) } } impl std::fmt::Display for CallbackResponse { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let success_action = self.success_action.as_ref().map(|sa| { + let mut map = std::collections::BTreeMap::new(); + + match sa { + SuccessAction::Message(m) => { + map.insert("tag", "message".into()); + map.insert("message", m.into()); + } + SuccessAction::Url(u, d) => { + map.insert("tag", "url".into()); + map.insert("description", d.into()); + map.insert("url", u.to_string().into()); + } + } + + map + }); + let cr = ser::CallbackResponse { + success_action, disposable: self.disposable, pr: &self.pr, }; @@ -207,14 +210,14 @@ mod ser { pub max_sendable: u64, #[serde(rename = "commentAllowed")] pub comment_allowed: u64, - #[serde(rename = "successAction")] - pub success_action: Option>>, } #[derive(Serialize)] pub(super) struct CallbackResponse<'a> { pub pr: &'a str, pub disposable: bool, + #[serde(rename = "successAction")] + pub success_action: Option>>, } } @@ -233,14 +236,14 @@ mod de { pub max_sendable: u64, #[serde(rename = "commentAllowed")] pub comment_allowed: Option, - #[serde(rename = "successAction")] - pub success_action: Option>, } #[derive(Deserialize)] pub(super) struct CallbackResponse { pub pr: String, pub disposable: Option, + #[serde(rename = "successAction")] + pub success_action: Option>, } } @@ -266,7 +269,6 @@ mod tests { assert_eq!(parsed.comment_size, 0); assert!(parsed.long_description.is_none()); - assert!(parsed.success_action.is_none()); assert!(parsed.jpeg.is_none()); assert!(parsed.png.is_none()); } @@ -322,71 +324,83 @@ mod tests { } #[test] - fn parse_success_actions() { + fn callback() { let input = r#" { "callback": "https://yuri?o=callback", "metadata": "[[\"text/plain\", \"boneco do steve magal\"]]", "maxSendable": 315, - "minSendable": 314, - "successAction": { - "tag": "message", - "message": "obrigado!" - } + "minSendable": 314 } "#; let parsed = input.parse::().expect("parse"); - let Some(super::SuccessAction::Message(m)) = parsed.success_action else { - panic!("bad success action"); - }; - assert_eq!(m, "obrigado!"); + assert_eq!( + parsed.clone().callback("comentario", 314).to_string(), + "https://yuri/?o=callback&comment=comentario&amount=314" + ); + assert_eq!( + parsed.callback("", 314).to_string(), + "https://yuri/?o=callback&amount=314" + ); + } + + #[test] + fn callback_parse_base() { let input = r#" - { - "callback": "https://yuri?o=callback", - "metadata": "[[\"text/plain\", \"boneco do steve magal\"]]", - "maxSendable": 315, - "minSendable": 314, - "successAction": { - "tag": "url", - "description": "valeu demais", - "url": "http://uerre.ele" - } - } + { "pr": "pierre" } "#; - let parsed = input.parse::().expect("parse"); - let Some(super::SuccessAction::Url(u, d)) = parsed.success_action else { - panic!("bad success action"); - }; + let parsed = input.parse::().expect("parse"); + assert!(parsed.success_action.is_none()); + assert_eq!(parsed.pr, "pierre"); + assert!(parsed.disposable); + } - assert_eq!(u.to_string(), "http://uerre.ele/"); - assert_eq!(d, "valeu demais"); + #[test] + fn callback_parse_disposable() { + let input = r#" + { "pr": "", "disposable": true } + "#; + + let parsed = input.parse::().expect("parse"); + assert!(parsed.disposable); + + let input = r#" + { "pr": "", "disposable": false } + "#; + + let parsed = input.parse::().expect("parse"); + assert!(!parsed.disposable); } #[test] - fn callback() { + fn callback_parse_success_actions() { let input = r#" - { - "callback": "https://yuri?o=callback", - "metadata": "[[\"text/plain\", \"boneco do steve magal\"]]", - "maxSendable": 315, - "minSendable": 314 - } + { "pr": "", "successAction": { "tag": "message", "message": "obrigado!" } } "#; - let parsed = input.parse::().expect("parse"); + let parsed = input.parse::().expect("parse"); - assert_eq!( - parsed.clone().callback("comentario", 314).to_string(), - "https://yuri/?o=callback&comment=comentario&amount=314" - ); + let Some(super::SuccessAction::Message(m)) = parsed.success_action else { + panic!("bad success action"); + }; - assert_eq!( - parsed.callback("", 314).to_string(), - "https://yuri/?o=callback&amount=314" - ); + assert_eq!(m, "obrigado!"); + + let input = r#" + { "pr": "", "successAction": { "tag": "url", "description": "valeu demais", "url": "http://eh.nois" } } + "#; + + let parsed = input.parse::().expect("parse"); + + let Some(super::SuccessAction::Url(u, d)) = parsed.success_action else { + panic!("bad success action"); + }; + + assert_eq!(u.to_string(), "http://eh.nois/"); + assert_eq!(d, "valeu demais"); } } diff --git a/tests/lud06.rs b/tests/lud06.rs index 07e41c4..c880b49 100644 --- a/tests/lud06.rs +++ b/tests/lud06.rs @@ -18,7 +18,6 @@ async fn test() { callback, short_description: String::from("today i become death"), long_description: Some(String::from("the destroyer of worlds")), - success_action: None, jpeg: None, png: None, comment_size: 0, @@ -31,6 +30,7 @@ async fn test() { Ok(lnurlkit::core::pay_request::CallbackResponse { pr: format!("pierre:{amount}"), disposable: false, + success_action: None, }) }, ) diff --git a/tests/lud09.rs b/tests/lud09.rs new file mode 100644 index 0000000..fa4a33b --- /dev/null +++ b/tests/lud09.rs @@ -0,0 +1,88 @@ +#[tokio::test] +async fn test() { + let listener = tokio::net::TcpListener::bind("0.0.0.0:0") + .await + .expect("net"); + + let addr = listener.local_addr().expect("addr"); + + let query_url = format!("http://{addr}/lnurlp"); + let callback_url = url::Url::parse(&format!("http://{addr}/lnurlp/callback")).expect("url"); + + let router = lnurlkit::server::Server::default() + .pay_request( + move || { + let callback = callback_url.clone(); + async { + Ok(lnurlkit::core::pay_request::PayRequest { + callback, + short_description: String::new(), + long_description: None, + jpeg: None, + png: None, + comment_size: 0, + min: 314, + max: 315, + }) + } + }, + move |(amount, comment): (u64, Option)| async move { + Ok(lnurlkit::core::pay_request::CallbackResponse { + pr: String::new(), + disposable: false, + success_action: if amount == 0 { + None + } else if amount == 1 { + Some(lnurlkit::core::pay_request::SuccessAction::Message( + comment.unwrap_or_default(), + )) + } else { + Some(lnurlkit::core::pay_request::SuccessAction::Url( + url::Url::parse("http://u.rl").expect("url"), + comment.unwrap_or_default(), + )) + }, + }) + }, + ) + .build(); + + tokio::spawn(async move { + axum::serve(listener, router).await.expect("serve"); + }); + + let client = lnurlkit::client::Client::default(); + + let lnurl = bech32::encode( + "lnurl", + bech32::ToBase32::to_base32(&query_url), + bech32::Variant::Bech32, + ) + .expect("lnurl"); + + let queried = client.query(&lnurl).await.expect("query"); + let lnurlkit::client::Query::PayRequest(pr) = queried else { + panic!("not pay request"); + }; + + let invoice = pr.clone().callback("", 0).await.expect("callback"); + assert!(invoice.success_action.is_none()); + + let invoice = pr.clone().callback("mensagem", 1).await.expect("callback"); + + let Some(lnurlkit::core::pay_request::SuccessAction::Message(m)) = invoice.success_action + else { + panic!("bad success action"); + }; + + assert_eq!(m, "mensagem"); + + let invoice = pr.callback("descricao", 2).await.expect("callback"); + + let Some(lnurlkit::core::pay_request::SuccessAction::Url(u, d)) = invoice.success_action else { + panic!("bad success action"); + }; + + assert_eq!(u.to_string(), "http://u.rl/"); + assert_eq!(d, "descricao"); +} diff --git a/tests/lud11.rs b/tests/lud11.rs index 9f28def..9fd2bcd 100644 --- a/tests/lud11.rs +++ b/tests/lud11.rs @@ -18,7 +18,6 @@ async fn test() { callback, short_description: String::new(), long_description: None, - success_action: None, jpeg: None, png: None, comment_size: 0, @@ -31,6 +30,7 @@ async fn test() { Ok(lnurlkit::core::pay_request::CallbackResponse { pr: String::new(), disposable: amount % 2 == 0, + success_action: None, }) }, ) diff --git a/tests/lud12.rs b/tests/lud12.rs index 48a9f3a..bd533ec 100644 --- a/tests/lud12.rs +++ b/tests/lud12.rs @@ -18,7 +18,6 @@ async fn test() { callback, short_description: String::new(), long_description: None, - success_action: None, jpeg: None, png: None, comment_size: 140, @@ -31,6 +30,7 @@ async fn test() { Ok(lnurlkit::core::pay_request::CallbackResponse { pr: format!("pierre:{comment:?}"), disposable: false, + success_action: None, }) }, )