From dfeed22e8d861ff54bb683ad74f6ca8942a78d84 Mon Sep 17 00:00:00 2001 From: Kevin Swiber Date: Mon, 2 Oct 2023 12:16:04 -0700 Subject: [PATCH] Refactoring tests. --- .cargo/config.toml | 2 + Cargo.toml | 1 - justfile | 4 +- src/lib.rs | 265 +++++++++++++++++++++++++++++++++++-- src/openapi/mod.rs | 2 +- src/postman/mod.rs | 6 +- tests/integration_tests.rs | 76 +++++------ tests/unit_tests.rs | 185 -------------------------- 8 files changed, 299 insertions(+), 242 deletions(-) create mode 100644 .cargo/config.toml delete mode 100644 tests/unit_tests.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..4ec2f3b --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.wasm32-unknown-unknown] +runner = 'wasm-bindgen-test-runner' diff --git a/Cargo.toml b/Cargo.toml index c187ecd..3152f9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,6 @@ gloo-utils = { version = "0.2", features = ["serde"] } wasm-bindgen = "0.2" wee_alloc = { version = "0.4.5", optional = true } - [target.'cfg(target_arch = "wasm32")'.dev-dependencies] js-sys = "0.3" wasm-bindgen-test = "0.3.0" diff --git a/justfile b/justfile index 37f6f7f..473e1b3 100644 --- a/justfile +++ b/justfile @@ -31,12 +31,10 @@ fmt-check: clippy: cargo clippy -- -D warnings -test: build fmt-check clippy test-lib test-unit test-integration test-wasm-node test-wasm-chrome test-wasm-firefox +test: build fmt-check clippy test-lib test-integration test-wasm-node test-wasm-chrome test-wasm-firefox test-lib: cargo test --lib -test-unit: - cargo test --test unit_tests test-integration: cargo test --test integration_tests test-wasm-firefox: diff --git a/src/lib.rs b/src/lib.rs index 79f4739..f6aa520 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,6 @@ pub struct TranspileOptions { pub format: TargetFormat, } -#[cfg(not(target_arch = "wasm32"))] pub fn from_path(filename: &str, options: TranspileOptions) -> Result { let collection = std::fs::read_to_string(filename)?; from_str(&collection, options) @@ -46,6 +45,18 @@ pub fn from_str(collection: &str, options: TranspileOptions) -> Result { Ok(oas_definition) } +#[cfg(target_arch = "wasm32")] +pub fn from_str(collection: &str, options: TranspileOptions) -> Result { + let postman_spec: postman::Spec = serde_json::from_str(collection)?; + let oas_spec = Transpiler::transpile(postman_spec); + match options.format { + TargetFormat::Json => openapi::to_json(&oas_spec).map_err(|err| err.into()), + TargetFormat::Yaml => Err(anyhow::anyhow!( + "YAML is not supported for WebAssembly. Please convert from YAML to JSON." + )), + } +} + // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global // allocator. #[cfg(feature = "wee_alloc")] @@ -100,6 +111,10 @@ struct TranspileState<'a> { } impl<'a> Transpiler<'a> { + pub fn new(variable_map: &'a BTreeMap) -> Self { + Self { variable_map } + } + pub fn transpile(spec: postman::Spec) -> openapi::OpenApi { let description = extract_description(&spec.info.description); @@ -700,11 +715,11 @@ impl<'a> Transpiler<'a> { value: Some(example_val), }; - if let openapi3::MediaTypeExample::Examples { examples: ex } = examples { - let mut ex2 = ex.clone(); - ex2.insert(name.to_string(), ObjectOrReference::Object(example)); + if let openapi3::MediaTypeExample::Examples { examples: mut ex } = examples + { + ex.insert(name.to_string(), ObjectOrReference::Object(example)); content.examples = - Some(openapi3::MediaTypeExample::Examples { examples: ex2 }); + Some(openapi3::MediaTypeExample::Examples { examples: ex }); } *content = content.clone(); } @@ -746,11 +761,11 @@ impl<'a> Transpiler<'a> { value: Some(oas_obj), }; - if let openapi3::MediaTypeExample::Examples { examples: ex } = examples { - let mut ex2 = ex.clone(); - ex2.insert(name.to_string(), ObjectOrReference::Object(example)); + if let openapi3::MediaTypeExample::Examples { examples: mut ex } = examples + { + ex.insert(name.to_string(), ObjectOrReference::Object(example)); content.examples = - Some(openapi3::MediaTypeExample::Examples { examples: ex2 }); + Some(openapi3::MediaTypeExample::Examples { examples: ex }); } } } @@ -1161,3 +1176,235 @@ fn extract_description(description: &Option) -> Optio None => None, } } + +#[cfg(not(target_arch = "wasm32"))] +#[cfg(test)] +mod tests { + use super::*; + use openapi::v3_0::{MediaTypeExample, ObjectOrReference, Parameter, Schema}; + use openapi::OpenApi; + use postman::Spec; + + #[test] + fn test_extract_description() { + let description = Some(postman::DescriptionUnion::String("test".to_string())); + assert_eq!(extract_description(&description), Some("test".to_string())); + + let description = Some(postman::DescriptionUnion::Description( + postman::Description { + content: Some("test".to_string()), + ..postman::Description::default() + }, + )); + assert_eq!(extract_description(&description), Some("test".to_string())); + + let description = None; + assert_eq!(extract_description(&description), None); + } + + #[test] + fn test_generate_path_parameters() { + let empty_map = BTreeMap::<_, _>::new(); + let transpiler = Transpiler::new(&empty_map); + let postman_variables = Some(vec![postman::Variable { + key: Some("test".to_string()), + value: Some(serde_json::Value::String("test_value".to_string())), + description: None, + ..postman::Variable::default() + }]); + let path_params = ["/test/".to_string(), "{{test_value}}".to_string()]; + let params = transpiler.generate_path_parameters(&path_params, &postman_variables); + assert_eq!(params.unwrap().len(), 1); + } + + #[test] + fn test_generate_query_parameters() { + let empty_map = BTreeMap::<_, _>::new(); + let transpiler = Transpiler::new(&empty_map); + let query_params = vec![postman::QueryParam { + key: Some("test".to_string()), + value: Some("{{test}}".to_string()), + description: None, + ..postman::QueryParam::default() + }]; + let params = transpiler.generate_query_parameters(&query_params); + assert_eq!(params.unwrap().len(), 1); + } + + #[test] + fn it_preserves_order_on_paths() { + let spec: Spec = serde_json::from_str(get_fixture("echo.postman.json").as_ref()).unwrap(); + let oas = Transpiler::transpile(spec); + let ordered_paths = [ + "/get", + "/post", + "/put", + "/patch", + "/delete", + "/headers", + "/response-headers", + "/basic-auth", + "/digest-auth", + "/auth/hawk", + "/oauth1", + "/cookies/set", + "/cookies", + "/cookies/delete", + "/status/200", + "/stream/5", + "/delay/2", + "/encoding/utf8", + "/gzip", + "/deflate", + "/ip", + "/time/now", + "/time/valid", + "/time/format", + "/time/unit", + "/time/add", + "/time/subtract", + "/time/start", + "/time/object", + "/time/before", + "/time/after", + "/time/between", + "/time/leap", + "/transform/collection", + "/{method}/hello", + ]; + let OpenApi::V3_0(s) = oas; + let keys = s.paths.keys().enumerate(); + for (i, k) in keys { + assert_eq!(k, ordered_paths[i]) + } + } + + #[test] + fn it_uses_the_correct_content_type_for_form_urlencoded_data() { + let spec: Spec = serde_json::from_str(get_fixture("echo.postman.json").as_ref()).unwrap(); + let oas = Transpiler::transpile(spec); + match oas { + OpenApi::V3_0(oas) => { + let b = oas + .paths + .get("/post") + .unwrap() + .post + .as_ref() + .unwrap() + .request_body + .as_ref() + .unwrap(); + if let ObjectOrReference::Object(b) = b { + assert!(b.content.contains_key("application/x-www-form-urlencoded")); + } + } + } + } + + #[test] + fn it_generates_headers_from_the_request() { + let spec: Spec = serde_json::from_str(get_fixture("echo.postman.json").as_ref()).unwrap(); + let oas = Transpiler::transpile(spec); + match oas { + OpenApi::V3_0(oas) => { + let params = oas + .paths + .get("/headers") + .unwrap() + .get + .as_ref() + .unwrap() + .parameters + .as_ref() + .unwrap(); + let header = params + .iter() + .find(|p| { + if let ObjectOrReference::Object(p) = p { + p.location == "header" + } else { + false + } + }) + .unwrap(); + let expected = ObjectOrReference::Object(Parameter { + name: "my-sample-header".to_owned(), + location: "header".to_owned(), + description: Some("My Sample Header".to_owned()), + schema: Some(Schema { + schema_type: Some("string".to_owned()), + example: Some(serde_json::Value::String( + "Lorem ipsum dolor sit amet".to_owned(), + )), + ..Schema::default() + }), + ..Parameter::default() + }); + assert_eq!(header, &expected); + } + } + } + + #[test] + fn it_generates_root_path_when_no_path_exists_in_collection() { + let spec: Spec = + serde_json::from_str(get_fixture("only-root-path.postman.json").as_ref()).unwrap(); + let oas = Transpiler::transpile(spec); + match oas { + OpenApi::V3_0(oas) => { + assert!(oas.paths.contains_key("/")); + } + } + } + + #[test] + fn it_parses_graphql_request_bodies() { + let spec: Spec = + serde_json::from_str(get_fixture("graphql.postman.json").as_ref()).unwrap(); + let oas = Transpiler::transpile(spec); + match oas { + OpenApi::V3_0(oas) => { + let body = oas + .paths + .get("/") + .unwrap() + .post + .as_ref() + .unwrap() + .request_body + .as_ref() + .unwrap(); + + if let ObjectOrReference::Object(body) = body { + assert!(body.content.contains_key("application/json")); + let content = body.content.get("application/json").unwrap(); + let schema = content.schema.as_ref().unwrap(); + if let ObjectOrReference::Object(schema) = schema { + let props = schema.properties.as_ref().unwrap(); + assert!(props.contains_key("query")); + assert!(props.contains_key("variables")); + } + let examples = content.examples.as_ref().unwrap(); + if let MediaTypeExample::Example { example } = examples { + let example: serde_json::Map = + serde_json::from_value(example.clone()).unwrap(); + assert!(example.contains_key("query")); + assert!(example.contains_key("variables")); + } + } + } + } + } + + fn get_fixture(filename: &str) -> String { + use std::fs; + + let filename: std::path::PathBuf = + [env!("CARGO_MANIFEST_DIR"), "./tests/fixtures/", filename] + .iter() + .collect(); + let file = filename.into_os_string().into_string().unwrap(); + fs::read_to_string(file).unwrap() + } +} diff --git a/src/openapi/mod.rs b/src/openapi/mod.rs index a4b0638..eb66d80 100644 --- a/src/openapi/mod.rs +++ b/src/openapi/mod.rs @@ -67,8 +67,8 @@ pub fn to_json(spec: &OpenApi) -> Result { Ok(serde_json::to_string_pretty(spec)?) } -#[cfg(test)] #[cfg(not(target_arch = "wasm32"))] +#[cfg(test)] mod tests { use super::*; use std::{ diff --git a/src/postman/mod.rs b/src/postman/mod.rs index e0c7a00..f7fba76 100644 --- a/src/postman/mod.rs +++ b/src/postman/mod.rs @@ -194,7 +194,7 @@ pub struct GraphQlBodyClass { pub variables: Option, } -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] pub struct QueryParam { #[serde(rename = "description")] pub description: Option, @@ -210,7 +210,7 @@ pub struct QueryParam { pub value: Option, } -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] pub struct Description { /// The content of the description goes here, as a raw string. #[serde(rename = "content")] @@ -234,7 +234,7 @@ pub struct Description { /// Using variables in your Postman requests eliminates the need to duplicate requests, which /// can save a lot of time. Variables can be defined, and referenced to from any part of a /// request. -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] pub struct Variable { #[serde(rename = "description")] pub description: Option, diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index ec2434f..a04d593 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,44 +1,40 @@ -#[cfg(test)] -mod integration_tests { - macro_rules! test_fixture { - ($name:ident, $filename:expr) => { - #[test] - #[cfg(not(target_arch = "wasm32"))] - fn $name() { - let filename = get_fixture($filename); - let options = postman2openapi::TranspileOptions::default(); - match postman2openapi::from_path(&filename, options) { - Ok(_oas) => assert!(true), - Err(err) => assert!(false, "{:?}", err), - } +macro_rules! test_fixture { + ($name:ident, $filename:expr) => { + #[test] + fn $name() { + let filename = get_fixture($filename); + let options = postman2openapi::TranspileOptions { + format: postman2openapi::TargetFormat::Json, + }; + match postman2openapi::from_path(&filename, options) { + Ok(_oas) => assert!(true), + Err(err) => assert!(false, "{:?}", err), } - }; - } + } + }; +} - test_fixture!(it_parses_github_api_collection, "github.postman.json"); - test_fixture!(it_parses_postman_api_collection, "postman-api.postman.json"); - test_fixture!(it_parses_pdf_co_collection, "pdfco.postman.json"); - test_fixture!(it_parses_postman_echo_collection, "echo.postman.json"); - test_fixture!(it_parses_twitter_api_collection, "twitter-api.postman.json"); - test_fixture!(it_parses_fastly_api_collection, "fastly.postman.json"); - test_fixture!(it_parses_users_api_collection, "users.postman.json"); - test_fixture!(it_parses_graphql_api_collection, "graphql.postman.json"); - test_fixture!(it_parses_todo_api_collection, "todo.postman.json"); - test_fixture!( - it_parses_gotomeeting_api_collection, - "gotomeeting.postman.json" - ); - test_fixture!( - it_parses_calculator_soap_collection, - "calculator-soap.postman.json" - ); +test_fixture!(it_parses_github_api_collection, "github.postman.json"); +test_fixture!(it_parses_postman_api_collection, "postman-api.postman.json"); +test_fixture!(it_parses_pdf_co_collection, "pdfco.postman.json"); +test_fixture!(it_parses_postman_echo_collection, "echo.postman.json"); +test_fixture!(it_parses_twitter_api_collection, "twitter-api.postman.json"); +test_fixture!(it_parses_fastly_api_collection, "fastly.postman.json"); +test_fixture!(it_parses_users_api_collection, "users.postman.json"); +test_fixture!(it_parses_graphql_api_collection, "graphql.postman.json"); +test_fixture!(it_parses_todo_api_collection, "todo.postman.json"); +test_fixture!( + it_parses_gotomeeting_api_collection, + "gotomeeting.postman.json" +); +test_fixture!( + it_parses_calculator_soap_collection, + "calculator-soap.postman.json" +); - #[cfg(not(target_arch = "wasm32"))] - fn get_fixture(filename: &str) -> String { - let filename: std::path::PathBuf = - [env!("CARGO_MANIFEST_DIR"), "./tests/fixtures/", filename] - .iter() - .collect(); - filename.into_os_string().into_string().unwrap() - } +fn get_fixture(filename: &str) -> String { + let filename: std::path::PathBuf = [env!("CARGO_MANIFEST_DIR"), "./tests/fixtures/", filename] + .iter() + .collect(); + filename.into_os_string().into_string().unwrap() } diff --git a/tests/unit_tests.rs b/tests/unit_tests.rs deleted file mode 100644 index f980c30..0000000 --- a/tests/unit_tests.rs +++ /dev/null @@ -1,185 +0,0 @@ -#[cfg(test)] -#[cfg(not(target_arch = "wasm32"))] -mod unit_tests { - use postman2openapi::openapi::v3_0::{MediaTypeExample, ObjectOrReference, Parameter, Schema}; - use postman2openapi::openapi::OpenApi; - use postman2openapi::postman::Spec; - use postman2openapi::Transpiler; - - #[test] - fn it_preserves_order_on_paths() { - let spec: Spec = serde_json::from_str(get_fixture("echo.postman.json").as_ref()).unwrap(); - let oas = Transpiler::transpile(spec); - let ordered_paths = [ - "/get", - "/post", - "/put", - "/patch", - "/delete", - "/headers", - "/response-headers", - "/basic-auth", - "/digest-auth", - "/auth/hawk", - "/oauth1", - "/cookies/set", - "/cookies", - "/cookies/delete", - "/status/200", - "/stream/5", - "/delay/2", - "/encoding/utf8", - "/gzip", - "/deflate", - "/ip", - "/time/now", - "/time/valid", - "/time/format", - "/time/unit", - "/time/add", - "/time/subtract", - "/time/start", - "/time/object", - "/time/before", - "/time/after", - "/time/between", - "/time/leap", - "/transform/collection", - "/{method}/hello", - ]; - let OpenApi::V3_0(s) = oas; - let keys = s.paths.keys().enumerate(); - for (i, k) in keys { - assert_eq!(k, ordered_paths[i]) - } - } - - #[test] - fn it_uses_the_correct_content_type_for_form_urlencoded_data() { - let spec: Spec = serde_json::from_str(get_fixture("echo.postman.json").as_ref()).unwrap(); - let oas = Transpiler::transpile(spec); - match oas { - OpenApi::V3_0(oas) => { - let b = oas - .paths - .get("/post") - .unwrap() - .post - .as_ref() - .unwrap() - .request_body - .as_ref() - .unwrap(); - if let ObjectOrReference::Object(b) = b { - assert!(b.content.contains_key("application/x-www-form-urlencoded")); - } - } - } - } - - #[test] - fn it_generates_headers_from_the_request() { - let spec: Spec = serde_json::from_str(get_fixture("echo.postman.json").as_ref()).unwrap(); - let oas = Transpiler::transpile(spec); - match oas { - OpenApi::V3_0(oas) => { - let params = oas - .paths - .get("/headers") - .unwrap() - .get - .as_ref() - .unwrap() - .parameters - .as_ref() - .unwrap(); - let header = params - .iter() - .find(|p| { - if let ObjectOrReference::Object(p) = p { - p.location == "header" - } else { - false - } - }) - .unwrap(); - let expected = ObjectOrReference::Object(Parameter { - name: "my-sample-header".to_owned(), - location: "header".to_owned(), - description: Some("My Sample Header".to_owned()), - schema: Some(Schema { - schema_type: Some("string".to_owned()), - example: Some(serde_json::Value::String( - "Lorem ipsum dolor sit amet".to_owned(), - )), - ..Schema::default() - }), - ..Parameter::default() - }); - assert_eq!(header, &expected); - } - } - } - - #[test] - fn it_generates_root_path_when_no_path_exists_in_collection() { - let spec: Spec = - serde_json::from_str(get_fixture("only-root-path.postman.json").as_ref()).unwrap(); - let oas = Transpiler::transpile(spec); - match oas { - OpenApi::V3_0(oas) => { - assert!(oas.paths.contains_key("/")); - } - } - } - - #[test] - fn it_parses_graphql_request_bodies() { - let spec: Spec = - serde_json::from_str(get_fixture("graphql.postman.json").as_ref()).unwrap(); - let oas = Transpiler::transpile(spec); - match oas { - OpenApi::V3_0(oas) => { - let body = oas - .paths - .get("/") - .unwrap() - .post - .as_ref() - .unwrap() - .request_body - .as_ref() - .unwrap(); - - if let ObjectOrReference::Object(body) = body { - assert!(body.content.contains_key("application/json")); - let content = body.content.get("application/json").unwrap(); - let schema = content.schema.as_ref().unwrap(); - if let ObjectOrReference::Object(schema) = schema { - let props = schema.properties.as_ref().unwrap(); - assert!(props.contains_key("query")); - assert!(props.contains_key("variables")); - } - let examples = content.examples.as_ref().unwrap(); - if let MediaTypeExample::Example { example } = examples { - let example: serde_json::Map = - serde_json::from_value(example.clone()).unwrap(); - assert!(example.contains_key("query")); - assert!(example.contains_key("variables")); - } - } - } - } - } - - fn get_fixture(filename: &str) -> String { - use std::fs; - - let filename: std::path::PathBuf = - [env!("CARGO_MANIFEST_DIR"), "./tests/fixtures/", filename] - .iter() - .collect(); - let file = filename.into_os_string().into_string().unwrap(); - fs::read_to_string(file).unwrap() - } -}