Skip to content

Commit b883857

Browse files
committed
Improve mcp test coverage in workspace
Use the workspace-built MCP binary directly in tests and add focused helper coverage for argument parsing so regressions are caught without requiring a live server. Co-Authored-By: HAL 9000
1 parent 8ae0856 commit b883857

2 files changed

Lines changed: 83 additions & 29 deletions

File tree

ldk-server-mcp/src/tools/handlers.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,3 +842,82 @@ pub async fn handle_graph_get_node(client: &LdkServerClient, args: Value) -> Res
842842
.map_err(|e| e.message.clone())?;
843843
serde_json::to_value(response).map_err(|e| format!("Failed to serialize response: {e}"))
844844
}
845+
846+
#[cfg(test)]
847+
mod tests {
848+
use super::{
849+
build_bolt11_invoice_description, build_channel_config, build_update_channel_config,
850+
parse_page_token,
851+
};
852+
use ldk_server_client::ldk_server_grpc::types::bolt11_invoice_description;
853+
use serde_json::json;
854+
855+
#[test]
856+
fn parse_page_token_rejects_invalid_formats() {
857+
assert_eq!(
858+
parse_page_token("missing-colon").unwrap_err(),
859+
"Page token must be in format 'token:index'"
860+
);
861+
assert_eq!(parse_page_token("token:not-a-number").unwrap_err(), "Invalid page token index");
862+
}
863+
864+
#[test]
865+
fn build_bolt11_invoice_description_rejects_conflicting_fields() {
866+
let err = build_bolt11_invoice_description(&json!({
867+
"description": "desc",
868+
"description_hash": "hash"
869+
}))
870+
.unwrap_err();
871+
872+
assert_eq!(err, "Only one of description or description_hash can be set");
873+
}
874+
875+
#[test]
876+
fn build_bolt11_invoice_description_supports_direct_and_hash_modes() {
877+
let direct =
878+
build_bolt11_invoice_description(&json!({ "description": "desc" })).unwrap().unwrap();
879+
assert!(matches!(
880+
direct.kind,
881+
Some(bolt11_invoice_description::Kind::Direct(ref value)) if value == "desc"
882+
));
883+
884+
let hash = build_bolt11_invoice_description(&json!({ "description_hash": "hash" }))
885+
.unwrap()
886+
.unwrap();
887+
assert!(matches!(
888+
hash.kind,
889+
Some(bolt11_invoice_description::Kind::Hash(ref value)) if value == "hash"
890+
));
891+
}
892+
893+
#[test]
894+
fn build_channel_config_rejects_conflicting_dust_exposure_modes() {
895+
let err = build_channel_config(&json!({
896+
"max_dust_htlc_exposure_fixed_limit_msat": 1,
897+
"max_dust_htlc_exposure_fee_rate_multiplier": 2
898+
}))
899+
.unwrap_err();
900+
901+
assert_eq!(
902+
err,
903+
"Only one of max_dust_htlc_exposure_fixed_limit_msat or max_dust_htlc_exposure_fee_rate_multiplier can be set"
904+
);
905+
}
906+
907+
#[test]
908+
fn build_channel_config_returns_none_when_no_fields_are_set() {
909+
assert!(build_channel_config(&json!({})).unwrap().is_none());
910+
}
911+
912+
#[test]
913+
fn build_update_channel_config_defaults_to_empty_config() {
914+
let channel_config = build_update_channel_config(&json!({})).unwrap();
915+
916+
assert_eq!(channel_config.forwarding_fee_proportional_millionths, None);
917+
assert_eq!(channel_config.forwarding_fee_base_msat, None);
918+
assert_eq!(channel_config.cltv_expiry_delta, None);
919+
assert_eq!(channel_config.force_close_avoidance_max_fee_satoshis, None);
920+
assert_eq!(channel_config.accept_underpaying_htlcs, None);
921+
assert_eq!(channel_config.max_dust_htlc_exposure, None);
922+
}
923+
}

ldk-server-mcp/tests/integration.rs

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
// licenses.
99

1010
use std::io::{BufRead, BufReader, Write};
11-
use std::process::{Command, Stdio};
1211

1312
use serde_json::{json, Value};
1413

@@ -61,29 +60,6 @@ fn test_cert_path() -> String {
6160
.to_string()
6261
}
6362

64-
fn cargo_bin_path() -> String {
65-
let output = Command::new("cargo")
66-
.args(["build", "--message-format=json"])
67-
.stderr(Stdio::piped())
68-
.stdout(Stdio::piped())
69-
.output()
70-
.expect("Failed to build binary");
71-
72-
let stdout = String::from_utf8(output.stdout).unwrap();
73-
for line in stdout.lines() {
74-
if let Ok(msg) = serde_json::from_str::<Value>(line) {
75-
if msg.get("reason").and_then(|r| r.as_str()) == Some("compiler-artifact")
76-
&& msg.get("target").and_then(|t| t.get("name")).and_then(|n| n.as_str())
77-
== Some("ldk-server-mcp")
78-
&& msg.get("executable").and_then(|e| e.as_str()).is_some()
79-
{
80-
return msg["executable"].as_str().unwrap().to_string();
81-
}
82-
}
83-
}
84-
panic!("Could not find compiled binary path");
85-
}
86-
8763
struct McpProcess {
8864
child: std::process::Child,
8965
stdin: std::process::ChildStdin,
@@ -92,14 +68,13 @@ struct McpProcess {
9268

9369
impl McpProcess {
9470
fn spawn() -> Self {
95-
let bin = cargo_bin_path();
96-
let mut child = Command::new(&bin)
71+
let mut child = std::process::Command::new(env!("CARGO_BIN_EXE_ldk-server-mcp"))
9772
.env("LDK_BASE_URL", "localhost:19999")
9873
.env("LDK_API_KEY", "deadbeef")
9974
.env("LDK_TLS_CERT_PATH", test_cert_path())
100-
.stdin(Stdio::piped())
101-
.stdout(Stdio::piped())
102-
.stderr(Stdio::piped())
75+
.stdin(std::process::Stdio::piped())
76+
.stdout(std::process::Stdio::piped())
77+
.stderr(std::process::Stdio::piped())
10378
.spawn()
10479
.expect("Failed to spawn MCP process");
10580

0 commit comments

Comments
 (0)