Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the show endpoint in the httpd crate #175

Merged
merged 4 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ your business applications :smile:.

| Package | Description | Version |
|----------------|-----------------------------------------------------------------|------------|
| [coffee_core](../coffee_core/) | Package containing the main implementation of Coffee plugin manager | pre_release |
| [coffee_cmd](../coffee_cmd/) | Package providing CLI to the Coffee plugin manager | pre_release |
| [coffee_github](../coffee_github/) | GitHub interface to the Coffee plugin manager | pre_release |
| [coffee_lib](../coffee_lib/) | The core library to the Coffee plugin ecosystem | pre_release |
| [coffee_storage](../coffee_storage/) | The local storage model package for the Coffee plugin manager | pre_release |
| [coffee_httpd](../coffee_httpd/) | HTTP deamon that expose the public API of coffee | under development |
| [coffee_plugin](../coffee_plugin) | Core Lightning plugin that allo to interact with coffee | under development |
| [coffee_core](/coffee_core/) | Package containing the main implementation of Coffee plugin manager | pre_release |
| [coffee_cmd](/coffee_cmd/) | Package providing CLI to the Coffee plugin manager | pre_release |
| [coffee_github](/coffee_github/) | GitHub interface to the Coffee plugin manager | pre_release |
| [coffee_lib](/coffee_lib/) | The core library to the Coffee plugin ecosystem | pre_release |
| [coffee_storage](/coffee_storage/) | The local storage model package for the Coffee plugin manager | pre_release |
| [coffee_httpd](/coffee_httpd/) | HTTP daemon that expose the public API of coffee | under development |
| [coffee_plugin](/coffee_plugin) | Core Lightning plugin that allow to interact with coffee | under development |

## How to contribute

Expand Down
13 changes: 13 additions & 0 deletions coffee_httpd/src/httpd/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ macro_rules! handle_httpd_response {
))),
}
};
($result:expr) => {
match $result {
Ok(val) => {
let val = serde_json::to_value(val).map_err(|err| {
actix_web::error::ErrorInternalServerError(format!("Failure: {err}"))
})?;
Ok(Json(val))
}
Err(err) => Err(actix_web::error::ErrorInternalServerError(format!(
"Failure: {err}"
))),
}
};
}

pub use handle_httpd_response;
38 changes: 14 additions & 24 deletions coffee_httpd/src/httpd/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub async fn run_httpd<T: ToSocketAddrs>(
.service(coffee_remote_add)
.service(coffee_remote_rm)
.service(coffee_remote_list)
.service(coffee_show)
.with_json_spec_at("/api/v1")
.build()
})
Expand Down Expand Up @@ -112,17 +113,7 @@ async fn coffee_remove(
async fn coffee_list(data: web::Data<AppState>) -> Result<Json<Value>, Error> {
let mut coffee = data.coffee.lock().await;
let result = coffee.list().await;
match result {
Ok(coffee_list) => {
let val = serde_json::to_value(coffee_list).map_err(|err| {
actix_web::error::ErrorInternalServerError(format!("coffee list error: {err}"))
})?;
Ok(Json(val))
}
Err(err) => Err(actix_web::error::ErrorInternalServerError(format!(
"coffee list error: {err}"
))),
}
handle_httpd_response!(result)
}

#[api_v2_operation]
Expand Down Expand Up @@ -163,19 +154,18 @@ async fn coffee_remote_list(data: web::Data<AppState>) -> Result<Json<Value>, Er
let mut coffee = data.coffee.lock().await;
let result = coffee.list_remotes().await;

match result {
Ok(coffee_remotes) => {
let val = serde_json::to_value(coffee_remotes).map_err(|err| {
actix_web::error::ErrorInternalServerError(format!(
"Failed to list remote repositories: {err}"
))
})?;
Ok(Json(val))
}
Err(err) => Err(actix_web::error::ErrorInternalServerError(format!(
"Failed to list remote repositories: {err}"
))),
}
handle_httpd_response!(result)
}

#[api_v2_operation]
#[get("/show")]
async fn coffee_show(data: web::Data<AppState>, body: Json<Show>) -> Result<Json<Value>, Error> {
let plugin = &body.plugin;

let mut coffee = data.coffee.lock().await;
let result = coffee.show(plugin).await;

handle_httpd_response!(result)
}

// this is just a hack to support swagger UI with https://paperclip-rs.github.io/paperclip/
Expand Down
5 changes: 5 additions & 0 deletions coffee_lib/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ pub mod request {
pub struct RemoteRm {
pub repository_name: String,
}

#[derive(Debug, Deserialize, Apiv2Schema, Serialize)]
pub struct Show {
pub plugin: String,
}
}

// Definition of the response types.
Expand Down
1 change: 1 addition & 0 deletions coffee_testing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ impl CoffeeTesting {
}

/// Coffee HTTPD testing manager.
#[derive(Debug)]
pub struct CoffeeHTTPDTesting {
root_path: Arc<TempDir>,
httpd_pid: tokio::process::Child,
Expand Down
240 changes: 240 additions & 0 deletions tests/src/coffee_httpd_integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,243 @@ pub async fn httpd_init_add_remote() {

cln.stop().await.unwrap();
}

#[tokio::test(flavor = "multi_thread")]
#[ntest::timeout(560000)]
pub async fn httpd_add_remove_plugins() {
init();

let mut cln = Node::tmp("regtest").await.unwrap();
let lightning_dir = cln.rpc().getinfo().unwrap().ligthning_dir;
let lightning_dir = lightning_dir.strip_suffix("/regtest").unwrap();
let manager = CoffeeHTTPDTesting::tmp(lightning_dir.to_string()).await;
assert!(manager.is_ok(), "{:?}", manager);
let manager = manager.unwrap();
log::info!("lightning path: {lightning_dir}");
let url = manager.url();
log::info!("base url: {url}");
let client = reqwest::Client::new();

// Define the request body to be sent to the /remote/add endpoint
let remote_add_request = RemoteAdd {
repository_name: "lightningd".to_string(),
repository_url: "https://github.com/lightningd/plugins.git".to_string(),
};

let response = client
.post(format!("{}/remote/add", url))
.json(&remote_add_request)
.send()
.await;
assert!(response.is_ok(), "{:?}", response);
let response = response.unwrap();

// Check the response status code, log the body.
assert!(response.status().is_success());
let body = response.text().await.unwrap();
log::info!("/remote/add response: {}", body);

// Define the request body to be sent to the /show endpoint
let show_request = Show {
plugin: "helpme".to_string(),
};

let response = client
.get(format!("{}/show", url))
.json(&show_request)
.send()
.await;
assert!(response.is_ok(), "{:?}", response);
let response = response.unwrap();

// Check the response status code, log the body.
assert!(response.status().is_success());
let body = response.text().await.unwrap();
log::info!("/show response: {}", body);

// Parse the response body
let response_json = serde_json::from_str(&body);
assert!(response_json.is_ok(), "{:?}", response_json);
let response_json: serde_json::Value = response_json.unwrap();

// Extract the `readme` field from the response JSON
let readme = response_json["readme"].as_str();
assert!(readme.is_some(), "{:?}", readme);
let readme = readme.unwrap();

// Assert that the `readme` starts with the expected content
assert!(readme.starts_with("# Helpme plugin"), "{:?}", readme);

// Define the request body to be sent to the /install endpoint
let install_request = Install {
plugin: "summary".to_string(),
try_dynamic: false,
};

let response = client
.post(format!("{}/install", url))
.json(&install_request)
.send()
.await;
assert!(response.is_ok(), "{:?}", response);
let response = response.unwrap();

// Check the response status code, log the body.
let body = response.text().await.unwrap();
log::info!("/install response: {}", body);

// Define the request body to be sent to the /install endpoint
let install_request = Install {
plugin: "helpme".to_string(),
try_dynamic: false,
};

let response = client
.post(format!("{}/install", url))
.json(&install_request)
.send()
.await;
assert!(response.is_ok(), "{:?}", response);
let response = response.unwrap();

// Check the response status code, log the body.
let body = response.text().await.unwrap();
log::info!("/install response: {}", body);

let body = reqwest::get(format!("{}/remote/list", url)).await;
assert!(body.is_ok(), "{:?}", body);
let body = body.unwrap().json::<serde_json::Value>().await;
assert!(body.is_ok(), "{:?}", body);
let body = body.unwrap();

// Log the response body
log::info!("/remote/list response: {}", body);

// Assert that the "lightningd" remote repository exists in the response
let remotes = body["remotes"].as_array();
assert!(remotes.is_some(), "{:?}", remotes);
let remotes = remotes.unwrap();
assert!(
remotes
.iter()
.any(|repo| repo["local_name"] == "lightningd"),
"lightningd remote repository not found in the response"
);

let body = reqwest::get(format!("{}/list", url)).await;
assert!(body.is_ok(), "{:?}", body);
let body = body.unwrap().json::<serde_json::Value>().await;
assert!(body.is_ok(), "{:?}", body);
let body = body.unwrap();

// Log the response body
log::info!("/list response: {}", body);

// Assert that the "helpme" and "summary" plugin exist in the response
let plugins = body["plugins"].as_array();
assert!(plugins.is_some(), "{:?}", plugins);
let plugins = plugins.unwrap();
assert!(
plugins.iter().any(|plugin| plugin["name"] == "helpme"),
"helpme plugin not found in the response"
);
assert!(
plugins.iter().any(|plugin| plugin["name"] == "summary"),
"summary plugin not found in the response"
);

// Define the request body to be sent
let plugin_remove_request = Remove {
plugin: "summary".to_string(),
};

let response = client
.post(format!("{}/remove", url))
.json(&plugin_remove_request)
.send()
.await;
assert!(response.is_ok(), "{:?}", response);
let response = response.unwrap();

// Check the response status code, log the body.
assert!(response.status().is_success());
let body = response.text().await.unwrap();
log::info!("Response body: {}", body);

let body = reqwest::get(format!("{}/list", url)).await;
assert!(body.is_ok(), "{:?}", body);
let body = body.unwrap().json::<serde_json::Value>().await;
assert!(body.is_ok(), "{:?}", body);
let body = body.unwrap();

// Log the response body
log::info!("/list response: {}", body);

// Assert that the "summary" plugin was removed
let plugins = body["plugins"].as_array();
assert!(plugins.is_some(), "{:?}", plugins);
let plugins = plugins.unwrap();
assert!(
!(plugins.iter().any(|plugin| plugin["name"] == "summary")),
"summary plugin is found in the list response while it should have been removed"
);

// Define the request body to be sent
let remote_rm_request = RemoteRm {
repository_name: "lightningd".to_string(),
};

// This should also remove the helpme plugin
let response = client
.post(format!("{}/remote/rm", url))
.json(&remote_rm_request)
.send()
.await;
assert!(response.is_ok(), "{:?}", response);
let response = response.unwrap();

// Check the response status code, log the body.
assert!(response.status().is_success());
let body = response.text().await.unwrap();
log::info!("/remote/rm response: {}", body);

let body = reqwest::get(format!("{}/list", url)).await;
assert!(body.is_ok(), "{:?}", body);
let body = body.unwrap().json::<serde_json::Value>().await;
assert!(body.is_ok(), "{:?}", body);
let body = body.unwrap();

// Log the response body
log::info!("/list response: {}", body);

// Assert that the "helpme" plugin was removed
let plugins = body["plugins"].as_array();
assert!(plugins.is_some(), "{:?}", plugins);
let plugins = plugins.unwrap();
assert!(
!(plugins.iter().any(|plugin| plugin["name"] == "helpme")),
"helpme plugin is found in the list response while it should have been removed"
);

let body = reqwest::get(format!("{}/remote/list", url)).await;
assert!(body.is_ok(), "{:?}", body);
let body = body.unwrap().json::<serde_json::Value>().await;
assert!(body.is_ok(), "{:?}", body);
let body = body.unwrap();

// Log the response body
log::info!("/remote/list response: {}", body);

// Assert that the "lightningd" remote repository doesn't exist in the response
let remotes = body["remotes"].as_array();
assert!(remotes.is_some(), "{:?}", remotes);
let remotes = remotes.unwrap();
assert!(
!(remotes
.iter()
.any(|repo| repo["local_name"] == "lightningd")),
"lightningd remote repository is found in the response while it should have been removed"
);

cln.stop().await.unwrap();
}
Loading