From 8840c85bced7cd31a9d0942f7a4f0177d60dbf7b Mon Sep 17 00:00:00 2001 From: aaravm Date: Tue, 9 Jul 2024 12:22:25 +0530 Subject: [PATCH] choose ci files only --- .gitignore | 8 -- Cargo.toml | 32 ----- LICENSE | 201 -------------------------- build-models.sh | 77 ---------- lib/src/configuration.rs | 59 -------- lib/src/lib.rs | 11 -- lib/src/serviceinfo/mod.rs | 62 -------- lib/src/tes/mod.rs | 284 ------------------------------------- lib/src/tes/model.rs | 27 ---- lib/src/test_utils.rs | 32 ----- lib/src/transport.rs | 130 ----------------- nextest.toml | 15 -- run-tests.sh | 22 --- rustfmt.toml | 4 - tests/Readme.md | 7 - tests/grape.tes | 85 ----------- tests/sample.tes | 16 --- 17 files changed, 1072 deletions(-) delete mode 100644 .gitignore delete mode 100644 Cargo.toml delete mode 100644 LICENSE delete mode 100755 build-models.sh delete mode 100644 lib/src/configuration.rs delete mode 100644 lib/src/lib.rs delete mode 100644 lib/src/serviceinfo/mod.rs delete mode 100644 lib/src/tes/mod.rs delete mode 100644 lib/src/tes/model.rs delete mode 100644 lib/src/test_utils.rs delete mode 100644 lib/src/transport.rs delete mode 100644 nextest.toml delete mode 100755 run-tests.sh delete mode 100644 rustfmt.toml delete mode 100644 tests/Readme.md delete mode 100644 tests/grape.tes delete mode 100644 tests/sample.tes diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 04b8825..0000000 --- a/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -target/ -debug/ -Cargo.lock -openapitools.json -lib/src/**/models/ -*.log -funnel-work-dir/ -funnel/ diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index e1ed8f7..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "ga4gh-sdk" -version = "0.1.0" -authors = ["Aarav Mehta ", "Pavel Nikonorov", "ELIXIR Cloud & AAI "] -edition = "2021" -description = "Generic SDK and CLI for GA4GH API services" -license = "Apache-2.0" -repository = "https://github.com/elixir-cloud-aai/ga4gh-sdk.git" - -[dependencies] -tokio = { version = "1", features = ["full"] } -serde = "^1.0" -serde_derive = "^1.0" -serde_json = "^1.0" -url = "^2.2" -uuid = { version = "^1.0", features = ["serde", "v4"] } -log = "0.4" -env_logger = "0.9" -once_cell = "1.8.0" - -[dependencies.reqwest] -version = "^0.11" -features = ["json", "multipart"] - -[dev-dependencies] -mockito = "0.31" -mockall = "0.10.2" -cargo-nextest = "0.9.30" - -[lib] -name = "ga4gh_sdk" -path = "lib/src/lib.rs" \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 261eeb9..0000000 --- a/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/build-models.sh b/build-models.sh deleted file mode 100755 index ec122d0..0000000 --- a/build-models.sh +++ /dev/null @@ -1,77 +0,0 @@ -# Exit immediately if a command exits with a non-zero status. -set -e - -# Ensure the OpenAPI Generator JAR file is set up -mkdir -p ~/bin/openapitools -OPENAPI_GENERATOR_JAR=~/bin/openapitools/openapi-generator-cli.jar -if [ ! -f "$OPENAPI_GENERATOR_JAR" ]; then - curl -L https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/7.7.0/openapi-generator-cli-7.7.0.jar -o "$OPENAPI_GENERATOR_JAR" -fi - -get_git_repo_name() { - # Extract the URL of the remote "origin" - url=$(git config --get remote.origin.url) - - # Extract the repository name from the URL - repo_name=$(basename -s .git "$url") - - echo "$repo_name" -} - -SCRIPT_DIR="$(pwd)" - -generate_openapi_models() { - # Parameters - OPENAPI_SPEC_PATH="$1" - API_NAME="$2" - DESTINATION_DIR="$3" - - # Define the temporary output directory for the OpenAPI generator - TEMP_OUTPUT_DIR=$(mktemp -d) - - # Remove the temporary directory at the end of the script - trap 'rm -rf "$TEMP_OUTPUT_DIR"' EXIT - - # Run the OpenAPI generator CLI using the JAR file - java -jar "$OPENAPI_GENERATOR_JAR" generate -g rust \ - -i "$OPENAPI_SPEC_PATH" \ - -o "$TEMP_OUTPUT_DIR" \ - --additional-properties=useSingleRequestParameter=true - - # Check if the generation was successful - if [ $? -ne 0 ]; then - echo "OpenAPI generation failed. Check the verbose output for details." - exit 1 - fi - - # Remove the openapitools.json file - rm -f ./openapitools.json - - echo "TEMP_OUTPUT_DIR is $TEMP_OUTPUT_DIR" - - # Modify the import statements in each generated file - SED_RULE="s/use crate::models;/#![allow(unused_imports)]\n#![allow(clippy::empty_docs)]\nuse crate::$API_NAME::models;/" - for file in $(find "$TEMP_OUTPUT_DIR" -name '*.rs'); do - if [[ "$OSTYPE" == "darwin"* ]]; then - # macOS (BSD) sed syntax - sed -i '' "$SED_RULE" "$file" - else - # Linux (GNU) sed syntax - sed -i "$SED_RULE" "$file" - fi - done - - rm -rf "$DESTINATION_DIR/models" - mkdir -p "$DESTINATION_DIR" - cp -r "$TEMP_OUTPUT_DIR/src/models" "$DESTINATION_DIR" - - echo "OpenAPI generation complete. Models copied to $DESTINATION_DIR" -} - -generate_openapi_models \ - "https://raw.githubusercontent.com/ga4gh-discovery/ga4gh-service-info/develop/service-info.yaml" \ - "serviceinfo" "$SCRIPT_DIR/lib/src/serviceinfo/" - -generate_openapi_models \ - "https://raw.githubusercontent.com/ga4gh/task-execution-schemas/develop/openapi/task_execution_service.openapi.yaml" \ - "tes" "$SCRIPT_DIR/lib/src/tes/" \ No newline at end of file diff --git a/lib/src/configuration.rs b/lib/src/configuration.rs deleted file mode 100644 index c823b2c..0000000 --- a/lib/src/configuration.rs +++ /dev/null @@ -1,59 +0,0 @@ -#[derive(Debug, Clone)] -pub struct Configuration { - pub base_path: String, - pub user_agent: Option, - pub basic_auth: Option, - pub oauth_access_token: Option, - pub bearer_access_token: Option, - pub api_key: Option, -} - -// Check whether defining BasicAuth works like this or not, else revert to the basic definition commented out -#[derive(Debug, Clone)] -pub struct BasicAuth { - pub username: String, - pub password: Option, -} -// pub type BasicAuth = (String, Option); - -#[derive(Debug, Clone)] -pub struct ApiKey { - pub prefix: Option, - pub key: String, -} - -impl Configuration { - pub fn new( - base_path: String, - user_agent: Option, - basic_auth: Option, - oauth_access_token: Option, - ) -> Self { - Configuration { - base_path, - user_agent, - basic_auth, - oauth_access_token, - bearer_access_token: None, - api_key: None, - } - } - - pub fn set_base_path(&mut self, base_path: &str) -> &mut Self { - self.base_path = base_path.to_string(); - self - } -} - -impl Default for Configuration { - fn default() -> Self { - Configuration { - base_path: "localhost".to_owned(), - user_agent: Some("GA4GH SDK".to_owned()), - basic_auth: None, - oauth_access_token: None, - bearer_access_token: None, - api_key: None, - } - } -} diff --git a/lib/src/lib.rs b/lib/src/lib.rs deleted file mode 100644 index 012b141..0000000 --- a/lib/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -#[allow(unused_imports)] -#[macro_use] -extern crate serde_derive; - -#[cfg(test)] -mod test_utils; - -pub mod configuration; -pub mod serviceinfo; -pub mod tes; -pub mod transport; diff --git a/lib/src/serviceinfo/mod.rs b/lib/src/serviceinfo/mod.rs deleted file mode 100644 index dbf532f..0000000 --- a/lib/src/serviceinfo/mod.rs +++ /dev/null @@ -1,62 +0,0 @@ -pub mod models; -use crate::configuration::Configuration; -use crate::transport::Transport; - -#[derive(Clone)] -pub struct ServiceInfo { - transport: Transport, -} - -impl ServiceInfo { - pub fn new(config: &Configuration) -> Result> { - let transport = &Transport::new(config); - let instance = ServiceInfo { - transport: transport.clone(), - }; - Ok(instance) - } - - pub async fn get(&self) -> Result> { - let response = self.transport.get("/service-info", None).await; - match response { - Ok(response_body) => match serde_json::from_str::(&response_body) { - Ok(service) => Ok(service), - Err(e) => { - log::error!("Failed to deserialize response: {}", e); - Err(e.into()) - } - }, - Err(e) => { - log::error!("Error: {}", e); - Err(e) - } - } - } -} - -#[cfg(test)] -mod tests { - use crate::configuration::Configuration; - use crate::serviceinfo::ServiceInfo; - use crate::test_utils::{ensure_funnel_running, setup}; - use tokio; - - #[tokio::test] - async fn test_get_service_info_from_funnel() { - setup(); - let mut config = Configuration::default(); - let funnel_url = ensure_funnel_running().await; - config.set_base_path(&funnel_url); - let service_info = ServiceInfo::new(&config).unwrap(); - - // Call get_service_info and print the result - match service_info.get().await { - Ok(service) => { - println!("Service Info: {:?}", service); - } - Err(e) => { - println!("Failed to get service info: {}", e); - } - } - } -} diff --git a/lib/src/tes/mod.rs b/lib/src/tes/mod.rs deleted file mode 100644 index 3ae5823..0000000 --- a/lib/src/tes/mod.rs +++ /dev/null @@ -1,284 +0,0 @@ -pub mod models; -pub mod model; -use crate::configuration::Configuration; -use crate::serviceinfo::models::Service; -use crate::serviceinfo::ServiceInfo; -use crate::tes::models::TesListTasksResponse; -use crate::tes::models::TesState; -use crate::tes::models::TesTask; -use crate::transport::Transport; -use crate::tes::model::ListTasksParams; -use serde_json; -use serde_json::from_str; -use serde_json::json; -use serde::Serialize; -use serde_json::Value; - -fn serialize_to_json(item: T) -> Value { - serde_json::to_value(&item).unwrap() -} - -pub fn urlencode>(s: T) -> String { - ::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect() -} - -#[derive(Debug)] -pub struct Task { - id: String, - transport: Transport, -} - -impl Task { - pub fn new(id: String, transport: Transport) -> Self { - Task { id, transport } - } - - pub async fn status(&self) -> Result> { - let task_id = &self.id; - let view = "FULL"; - let url = format!("/tasks/{}?view={}", task_id, view); - // let params = [("view", view)]; - // let params_value = serde_json::json!(params); - // let response = self.transport.get(&url, Some(params_value)).await; - let response = self.transport.get(&url, None).await; - match response { - Ok(resp_str) => { - let task: TesTask = from_str(&resp_str)?; - Ok(task.state.unwrap()) - } - Err(e) => { - let err_msg = format!("HTTP request failed: {}", e); - eprintln!("{}", err_msg); - Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, err_msg))) - } - } - } - - pub async fn cancel(&self) -> Result> { - let id = &self.id; - let id = &urlencode(id); - let url = format!("/tasks/{}:cancel", id); - let response = self.transport.post(&url, None).await; - match response { - Ok(resp_str) => { - let parsed_json = serde_json::from_str::(&resp_str); - match parsed_json { - Ok(json) => Ok(json), - Err(e) => Err(format!("Failed to parse JSON: {}", e).into()), - } - } - Err(e) => Err(format!("HTTP request failed: {}", e).into()), - } - } -} -#[derive(Debug)] -pub struct TES { - #[allow(dead_code)] - config: Configuration, // not used yet - service: Result>, - transport: Transport, -} - -impl TES { - pub async fn new(config: &Configuration) -> Result> { - let transport = Transport::new(config); - let service_info = ServiceInfo::new(config)?; - - let resp = service_info.get().await; - - let instance = TES { - config: config.clone(), - transport, - service: resp, - }; - - instance.check()?; // Propagate the error if check() fails - Ok(instance) - } - - fn check(&self) -> Result<(), String> { - let resp = &self.service; - match resp.as_ref() { - Ok(service) if service.r#type.artifact == "tes" => Ok(()), - Ok(_) => Err("The endpoint is not an instance of TES".into()), - Err(_) => Err("Error accessing the service".into()), - } - } - - pub async fn create( - &self, - task: TesTask, /*, params: models::TesTask*/ - ) -> Result> { - // First, check if the service is of TES class - self.check().map_err(|e| { - log::error!("Service check failed: {}", e); - e - })?; - // todo: version in url based on serviceinfo or user config - let response = self - .transport - .post("/ga4gh/tes/v1/tasks", Some(json!(task))) - .await; - match response { - Ok(response_body) => { - let v: serde_json::Value = serde_json::from_str(&response_body)?; - - // Access the `id` field - let task_id = v - .get("id") - .and_then(|v| v.as_str()) - .unwrap_or_default() - .trim_matches('"') - .to_string(); - - let task = Task { - id: task_id, - transport: self.transport.clone(), - }; - Ok(task) - } - Err(e) => Err(Box::new(std::io::Error::new( - std::io::ErrorKind::Other, - format!("Failed to post task: {}", e), - ))), - } - } - - pub async fn get(&self, view: &str, id: &str) -> Result> { - let task_id = id; - let url = format!("/tasks/{}?view={}", task_id, view); - // let params = [("view", view)]; - // let params_value = serde_json::json!(params); - // let response = self.transport.get(&url, Some(params_value)).await; - let response = self.transport.get(&url, None).await; - match response { - Ok(resp_str) => { - let task: TesTask = from_str(&resp_str)?; - Ok(task) - } - Err(e) => Err(e), - } - } - pub async fn list_tasks( - &self, - params: Option, - ) -> Result> { - let params_value = params.map(serialize_to_json); - - // println!("{:?}",params_value); - // Make the request with or without parameters based on the presence of params - let response = if let Some(params_value) = params_value { - self.transport.get("/tasks", Some(params_value)).await - } else { - self.transport.get("/tasks", None).await - }; - - match response { - Ok(resp_str) => { - let task: TesListTasksResponse = from_str(&resp_str)?; - Ok(task) - } - Err(e) => { - eprintln!("HTTP request failed: {:?}", e); - Err(Box::new(std::io::Error::new( - std::io::ErrorKind::Other, - format!("HTTP request failed: {:?}", e), - ))) - } - } - } -} - -#[cfg(test)] -mod tests { - use crate::configuration::Configuration; - use crate::tes::models::TesTask; - use crate::tes::ListTasksParams; - use crate::tes::Task; - use crate::tes::TesState; - use crate::tes::TES; - use crate::test_utils::{ensure_funnel_running, setup}; - // use crate::tes::models::TesCreateTaskResponse; - - async fn create_task() -> Result<(Task, TES), Box> { - // setup(); – should be run once in the test function - let mut config = Configuration::default(); - let funnel_url = ensure_funnel_running().await; - config.set_base_path(&funnel_url); - let tes = match TES::new(&config).await { - Ok(tes) => tes, - Err(e) => { - println!("Error creating TES instance: {:?}", e); - return Err(e); - } - }; - let file_path = "./tests/sample.tes".to_string(); - let task_json = std::fs::read_to_string(file_path).expect("Unable to read file"); - let task: TesTask = serde_json::from_str(&task_json).expect("JSON was not well-formatted"); - - let task = tes.create(task).await?; - Ok((task, tes)) - } - - #[tokio::test] - async fn test_task_create() { - setup(); - let (task, _tes) = create_task().await.expect("Failed to create task"); - assert!(!task.id.is_empty(), "Task ID should not be empty"); // double check if it's a correct assertion - } - - #[tokio::test] - async fn test_task_status() { - setup(); - - let (task, _tes) = create_task().await.expect("Failed to create task"); - assert!(!task.id.is_empty(), "Task ID should not be empty"); - - let status = task.status().await; - match status { - Ok(state) => { - assert!( - matches!(state, TesState::Initializing | TesState::Queued | TesState::Running), - "Unexpected state: {:?}", - state - ); - } - Err(err) => { - panic!("Task status returned an error: {:?}", err); - } - } - } - - #[tokio::test] - async fn test_cancel_task() { - setup(); - - let (task, _tes) = &create_task().await.expect("Failed to create task"); - assert!(!task.id.is_empty(), "Task ID should not be empty"); // double check if it's a correct assertion - - let cancel = task.cancel().await; - assert!(cancel.is_ok()); - } - - #[tokio::test] - async fn test_list_task() { - setup(); - - let (task, tes) = &create_task().await.expect("Failed to create task"); - assert!(!task.id.is_empty(), "Task ID should not be empty"); // double check if it's a correct assertion - - let params: ListTasksParams = ListTasksParams { - name_prefix: None, - state: None, - tag_key: None, - tag_value: None, - page_size: None, - page_token: None, - view: Some("BASIC".to_string()), - }; - - let list = tes.list_tasks(Some(params)).await; - assert!(list.is_ok()); - println!("{:?}", list); - } -} diff --git a/lib/src/tes/model.rs b/lib/src/tes/model.rs deleted file mode 100644 index a67190d..0000000 --- a/lib/src/tes/model.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::tes::models; - -/// struct for passing parameters to the method [`list_tasks`] -#[derive(Serialize, Clone, Debug)] -pub struct ListTasksParams { - /// OPTIONAL. Filter the list to include tasks where the name matches this prefix. If unspecified, no task name filtering is done. - #[serde(skip_serializing_if = "Option::is_none")] - pub name_prefix: Option, - /// OPTIONAL. Filter tasks by state. If unspecified, no task state filtering is done. - #[serde(skip_serializing_if = "Option::is_none")] - pub state: Option, - /// OPTIONAL. Provide key tag to filter. The field tag_key is an array of key values, and will be zipped with an optional tag_value array. So the query: ``` ?tag_key=foo1&tag_value=bar1&tag_key=foo2&tag_value=bar2 ``` Should be constructed into the structure { \"foo1\" : \"bar1\", \"foo2\" : \"bar2\"} ``` ?tag_key=foo1 ``` Should be constructed into the structure {\"foo1\" : \"\"} If the tag_value is empty, it will be treated as matching any possible value. If a tag value is provided, both the tag's key and value must be exact matches for a task to be returned. Filter Tags Match? ---------------------------------------------------------------------- {\"foo\": \"bar\"} {\"foo\": \"bar\"} Yes {\"foo\": \"bar\"} {\"foo\": \"bat\"} No {\"foo\": \"\"} {\"foo\": \"\"} Yes {\"foo\": \"bar\", \"baz\": \"bat\"} {\"foo\": \"bar\", \"baz\": \"bat\"} Yes {\"foo\": \"bar\"} {\"foo\": \"bar\", \"baz\": \"bat\"} Yes {\"foo\": \"bar\", \"baz\": \"bat\"} {\"foo\": \"bar\"} No {\"foo\": \"\"} {\"foo\": \"bar\"} Yes {\"foo\": \"\"} {} No - #[serde(skip_serializing_if = "Option::is_none")] - pub tag_key: Option>, - /// OPTIONAL. The companion value field for tag_key - #[serde(skip_serializing_if = "Option::is_none")] - pub tag_value: Option>, - /// Optional number of tasks to return in one page. Must be less than 2048. Defaults to 256. - #[serde(skip_serializing_if = "Option::is_none")] - pub page_size: Option, - /// OPTIONAL. Page token is used to retrieve the next page of results. If unspecified, returns the first page of results. The value can be found in the `next_page_token` field of the last returned result of ListTasks - #[serde(skip_serializing_if = "Option::is_none")] - pub page_token: Option, - /// OPTIONAL. Affects the fields included in the returned Task messages. `MINIMAL`: Task message will include ONLY the fields: - `tesTask.Id` - `tesTask.State` `BASIC`: Task message will include all fields EXCEPT: - `tesTask.ExecutorLog.stdout` - `tesTask.ExecutorLog.stderr` - `tesInput.content` - `tesTaskLog.system_logs` `FULL`: Task message includes all fields. - #[serde(skip_serializing_if = "Option::is_none")] - pub view: Option, -} diff --git a/lib/src/test_utils.rs b/lib/src/test_utils.rs deleted file mode 100644 index 62763f5..0000000 --- a/lib/src/test_utils.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::env; -use std::process::Command; -use std::str; -use std::sync::Once; - -pub const FUNNEL_HOST: &str = "http://localhost"; -pub const FUNNEL_PORT: u16 = 8000; -pub static INIT: Once = Once::new(); - -pub fn setup() { - INIT.call_once(|| { - env::set_var("RUST_LOG", "debug"); - env_logger::init(); - }); -} - -pub async fn ensure_funnel_running() -> String { - let output = Command::new("sh") - .arg("-c") - .arg("ps aux | grep '[f]unnel server run'") - .output() - .expect("Failed to execute command"); - - let output_str = str::from_utf8(&output.stdout).unwrap(); - - if output_str.is_empty() { - panic!("Funnel is not running."); - } - - let funnel_url = format!("{}:{}", FUNNEL_HOST, FUNNEL_PORT); - funnel_url -} diff --git a/lib/src/transport.rs b/lib/src/transport.rs deleted file mode 100644 index 36389b9..0000000 --- a/lib/src/transport.rs +++ /dev/null @@ -1,130 +0,0 @@ -use crate::configuration::Configuration; -use log::error; -use reqwest::Client; -use serde_json::Value; -use std::error::Error; - -// note: could implement custom certs handling, such as in-TEE generated ephemerial certs -#[derive(Clone, Debug)] -pub struct Transport { - pub config: Configuration, - pub client: reqwest::Client, -} - -impl Transport { - pub fn new(config: &Configuration) -> Self { - Transport { - config: config.clone(), - client: Client::new(), - } - } - - async fn request( - &self, - method: reqwest::Method, - endpoint: &str, - data: Option, - params: Option, - ) -> Result> { - let full_url = format!("{}{}", self.config.base_path, endpoint); - let url = reqwest::Url::parse(&full_url).map_err(|_| { - error!("Invalid endpoint (shouldn't contain base url): {}", endpoint); - Box::new(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid endpoint")) as Box - })?; - - let mut request_builder = self.client.request(method, url).header( - reqwest::header::USER_AGENT, - self.config.user_agent.clone().unwrap_or_default(), - ); - - if let Some(ref params_value) = params { - // Validate or log params_value before setting it as query parameters - if params_value.is_object() { - request_builder = request_builder.query(params_value); - } else { - error!("params_value is not an object and cannot be used as query parameters: {:?}", params_value); - return Err(Box::new(std::io::Error::new(std::io::ErrorKind::InvalidInput, "params_value must be an object"))); - } - } - - if let Some(ref data) = data { - request_builder = request_builder.json(&data); - } - - let resp = request_builder.send().await.map_err(|e| { - eprintln!("HTTP request failed: {}", e); - e - })?; - - let status = resp.status(); - let content = resp.text().await.map_err(|e| format!("Failed to read response text: {}", e))?; - - if status.is_success() { - Ok(content) - } else { - Err(Box::new(std::io::Error::new( - std::io::ErrorKind::Other, - format!("Request failed with status: {}. Response: {}", status, content), - ))) - } - } - - pub async fn get( - &self, - endpoint: &str, - params: Option, - ) -> Result> { - self.request(reqwest::Method::GET, endpoint, None, params) - .await - } - - pub async fn post( - &self, - endpoint: &str, - data: Option, - ) -> Result> { - self.request(reqwest::Method::POST, endpoint, data, None) - .await - } - - pub async fn put(&self, endpoint: &str, data: Value) -> Result> { - self.request(reqwest::Method::PUT, endpoint, Some(data), None) - .await - } - - pub async fn delete(&self, endpoint: &str) -> Result> { - self.request(reqwest::Method::DELETE, endpoint, None, None) - .await - } - - // other HTTP methods can be added here -} - -#[cfg(test)] -mod tests { - use crate::configuration::Configuration; - use crate::test_utils::setup; - use crate::transport::Transport; - use mockito::mock; - - #[tokio::test] - async fn test_request() { - setup(); - let base_url = &mockito::server_url(); - // effectively no sense in testing various responses, as it's reqwest's responsibility - // we should test Transport's methods - let _m = mock("GET", "/test") - .with_status(200) - .with_header("content-type", "application/json") - .with_body(r#"{"message": "success"}"#) - .create(); - - let config = Configuration::new(base_url.clone(),None, None, None); - let transport = Transport::new(&config.clone()); - let response = transport.get("/test", None).await; - - assert!(response.is_ok()); - let body = response.unwrap(); - assert_eq!(body, r#"{"message": "success"}"#); - } -} diff --git a/nextest.toml b/nextest.toml deleted file mode 100644 index 02d1453..0000000 --- a/nextest.toml +++ /dev/null @@ -1,15 +0,0 @@ -[profile.default] -retries = 1 - -[script-pre-commands] -[[profile.default]] -commands = [ - { cmd = "sh", args = ["-c", "if ! ps aux | grep '[f]unnel server run'; then echo 'Funnel server is not running. Starting it now...'; export PATH=$PATH:~/go/bin; funnel server run --Server.HostName=localhost --Server.HTTPPort=8000 > funnel.log 2>&1 & fi"] }, - { cmd = "sh", args = ["-c", "while ! curl -s http://localhost:8000/healthz > /dev/null; do echo 'Waiting for Funnel server...'; sleep 1; done; echo 'Funnel server is running.'"] } -] - -[script-post-commands] -[[profile.default]] -commands = [ - # Add any post-test teardown commands here if needed -] diff --git a/run-tests.sh b/run-tests.sh deleted file mode 100755 index 4361c36..0000000 --- a/run-tests.sh +++ /dev/null @@ -1,22 +0,0 @@ -# Check if a "funnel" process is already running -if ! ps aux | grep '[f]unnel server run'; then - # If it's not running, start it - echo "Funnel server is not running. Starting it now..." - export PATH=$PATH:~/go/bin - funnel server run --Server.HostName=localhost --Server.HTTPPort=8000 > funnel.log 2>&1 & -else - echo "Funnel server is already running." -fi - -# Wait for the Funnel server to start -echo "Waiting for Funnel server to start..." -while ! curl -s http://localhost:8000/healthz > /dev/null -do - echo "Waiting for Funnel server..." - sleep 1 -done -echo "Funnel server is running." - -# Run the tests -RUST_BACKTRACE=1 RUST_LOG=debug cargo test - diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index 62437b6..0000000 --- a/rustfmt.toml +++ /dev/null @@ -1,4 +0,0 @@ -ignore = [ - "lib/serviceinfo/models", - "lib/tes/models", -] \ No newline at end of file diff --git a/tests/Readme.md b/tests/Readme.md deleted file mode 100644 index 21ba62f..0000000 --- a/tests/Readme.md +++ /dev/null @@ -1,7 +0,0 @@ -# A folder for adding the files being used in unit tests - - -sample.tes: This is a sample file, taken from the [funnel docs](https://ohsu-comp-bio.github.io/funnel/docs/tasks/), and this file is being used in the file lib/src/tes/mod.rs - - -grape.tes: a sample file containing JSON task data for the GA4GH [Task Execution Service](https://github.com/ga4gh/task-execution-schemas), which can be used in the file lib/src/tes/mod.rs instead of sample.tes. Notably, it has placeholders like "${AWS_ACCESS_KEY_ID}" which is out of the standard and implies implementing a pre-processor, might be useful to note and implement in future as it avoids storing credentials in such .tes files diff --git a/tests/grape.tes b/tests/grape.tes deleted file mode 100644 index 2cceaf6..0000000 --- a/tests/grape.tes +++ /dev/null @@ -1,85 +0,0 @@ -{ - "name": "GRAPE", - "resources": { - "disk_gb": 200 - }, - "volumes": [ - "/vol/a/" - ], - "executors": [ - { - "image": "amazon/aws-cli", - "command": [ - "aws", - "s3", - "cp", - "${INPUT}", - "/vol/a/input.vcf.gz" - ], - "env": { - "AWS_ACCESS_KEY_ID": "${AWS_ACCESS_KEY_ID}", - "AWS_SECRET_ACCESS_KEY": "${AWS_SECRET_ACCESS_KEY}", - "AWS_REGION": "${AWS_REGION}" - } - }, - { - "image": "genxnetwork/grape", - "command": [ - "python", - "launcher.py", - "reference", - "--use-bundle", - "--ref-directory", - "/vol/a/media/ref", - "--real-run" - ] - }, - { - "image": "genxnetwork/grape", - "command": [ - "python", - "launcher.py", - "preprocess", - "--ref-directory", - "/vol/a/media/ref", - "--vcf-file", - "/vol/a/input.vcf.gz", - "--directory", - "/vol/a/media/data", - "--assembly", - "hg37", - "--real-run" - ] - }, - { - "image": "genxnetwork/grape", - "command": [ - "python", - "launcher.py", - "find", - "--flow", - "ibis", - "--ref-directory", - "/vol/a/media/ref", - "--directory", - "/vol/a/media/data", - "--real-run" - ] - }, - { - "image": "amazon/aws-cli", - "command": [ - "aws", - "s3", - "cp", - "/vol/a/media/data/results/relatives.tsv", - "${OUTPUT}" - ], - "env": { - "AWS_ACCESS_KEY_ID": "${AWS_ACCESS_KEY_ID}", - "AWS_SECRET_ACCESS_KEY": "${AWS_SECRET_ACCESS_KEY}", - "AWS_REGION": "${AWS_REGION}" - } - } - ] -} \ No newline at end of file diff --git a/tests/sample.tes b/tests/sample.tes deleted file mode 100644 index e663c3a..0000000 --- a/tests/sample.tes +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "Hello world", - "inputs": [{ - "url": "s3://funnel-bucket/hello.txt", - "path": "/inputs/hello.txt" - }], - "outputs": [{ - "url": "s3://funnel-bucket/output.txt", - "path": "/outputs/stdout" - }], - "executors": [{ - "image": "alpine", - "command": ["cat", "/inputs/hello.txt"], - "stdout": "/outputs/stdout" - }] -}