From 45ae6598518fa1e161adfd1001fdc9e7b6fe8813 Mon Sep 17 00:00:00 2001 From: yfaming Date: Sun, 18 Jan 2026 11:54:37 +0800 Subject: [PATCH] add tests --- src/config.rs | 61 ++++++++++++++++++++++++++++-- src/http_server.rs | 94 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 3 deletions(-) diff --git a/src/config.rs b/src/config.rs index 142a095..961bcec 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,20 +1,20 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct Config { pub server: ServerConfig, pub users: Vec, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct ServerConfig { pub domain: String, pub listen_addr: String, pub log_dir: String, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct UserConfig { pub name: String, pub nwcs: Vec, @@ -37,3 +37,58 @@ impl Config { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + + fn load_config_from_str(contents: &str) -> Result { + let config: Config = toml::from_str(contents)?; + config.validate()?; + Ok(config) + } + + #[test] + fn load_valid_config() -> Result<()> { + let contents = r#" +[server] +domain = "example.com" +listen_addr = "127.0.0.1:8080" +log_dir = "/tmp/thor" + +[[users]] +name = "alice" +nwcs = ["nwc://example"] +"#; + let config = load_config_from_str(contents)?; + assert_eq!(config.server.domain, "example.com"); + assert_eq!(config.server.listen_addr, "127.0.0.1:8080"); + assert_eq!(config.server.log_dir, "/tmp/thor"); + assert_eq!(config.users.len(), 1); + assert_eq!(config.users[0].name, "alice"); + assert_eq!(config.users[0].nwcs, vec!["nwc://example".to_string()]); + Ok(()) + } + + #[test] + fn load_config_rejects_empty_nwcs() { + let contents = r#" +[server] +domain = "example.com" +listen_addr = "127.0.0.1:8080" +log_dir = "/tmp/thor" + +[[users]] +name = "alice" +nwcs = [] +"#; + let res = load_config_from_str(contents); + assert!(res.is_err()); + + let err = res.unwrap_err(); + assert!( + err.to_string().contains("user alice has no NWC configured"), + "unexpected error: {err}" + ); + } +} diff --git a/src/http_server.rs b/src/http_server.rs index a7eeba4..c3c41d2 100644 --- a/src/http_server.rs +++ b/src/http_server.rs @@ -176,3 +176,97 @@ pub struct InvoiceResponse { struct Amount { amount: u64, } + +#[cfg(test)] +mod tests { + use super::*; + use axum::response::IntoResponse; + use std::collections::HashMap; + + struct DummyCreator { + result: std::result::Result, + } + + #[async_trait::async_trait] + impl InvoiceCreator for DummyCreator { + async fn create_invoice( + &self, + _amount_msat: u64, + _description_hash: &str, + ) -> Result { + match &self.result { + Ok(invoice) => Ok(invoice.clone()), + Err(msg) => Err(anyhow::anyhow!("{msg}")), + } + } + } + + fn create_app_state(user: &str, creators: Vec>) -> AppState { + let mut users = HashMap::new(); + users.insert(user.to_string(), creators); + AppState { + domain: "example.com".to_string(), + users, + } + } + + #[tokio::test] + async fn get_lnurlp_info_unknown_user_returns_bad_request() { + let state = Arc::new(AppState { + domain: "example.com".to_string(), + users: HashMap::new(), + }); + let res = get_lnurlp_info(State(state), Path("alice".to_string())).await; + assert!(res.is_err()); + let response = res.unwrap_err().into_response(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + } + + #[test] + fn generate_metadata_includes_identifier() -> Result<()> { + let creator = Box::new(DummyCreator { + result: Ok("lnbc1test".to_string()), + }); + let state = create_app_state("alice", vec![creator]); + let metadata = generate_metadata(&state, "alice")?; + let parsed: Vec> = serde_json::from_str(&metadata).unwrap(); + assert!(parsed.iter().any(|entry| { + entry.len() == 2 && entry[0] == "text/identifier" && entry[1] == "alice@example.com" + })); + Ok(()) + } + + #[tokio::test] + async fn create_invoice_rejects_zero_amount() { + let creator = Box::new(DummyCreator { + result: Ok("lnbc1test".to_string()), + }); + let state = Arc::new(create_app_state("alice", vec![creator])); + let err = create_invoice( + State(state), + Path("alice".to_string()), + Query(Amount { amount: 0 }), + ) + .await + .unwrap_err(); + let response = err.into_response(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + } + + #[tokio::test] + async fn create_invoice_returns_invoice() { + let creator = Box::new(DummyCreator { + result: Ok("lnbc1test".to_string()), + }); + let state = Arc::new(create_app_state("alice", vec![creator])); + let response = create_invoice( + State(state), + Path("alice".to_string()), + Query(Amount { amount: 1500 }), + ) + .await + .unwrap(); + assert_eq!(response.0.pr, "lnbc1test"); + assert!(response.0.routes.is_empty()); + } +}