From 0b509cdbb096a5c1e3f4610af4f66ddd1758d78f Mon Sep 17 00:00:00 2001 From: Kevin Zou Date: Tue, 24 Oct 2023 17:22:38 -0400 Subject: [PATCH] record/replay/passthrough functionality --- .generator/src/generator/templates/api_mod.j2 | 96 --------------- .../src/generator/templates/common_mod.j2 | 9 ++ .../src/generator/templates/configuration.j2 | 5 +- .../generator/templates/function_mappings.j2 | 4 +- Cargo.toml | 3 + src/datadog/configuration.rs | 5 +- src/datadog/mod.rs | 9 ++ ...ly-account-returns-CREATED-response.frozen | 1 + ...stly-account-returns-CREATED-response.json | 67 +++++++++++ ...-Fastly-account-returns-OK-response.frozen | 1 + ...et-Fastly-account-returns-OK-response.json | 95 +++++++++++++++ ...Fastly-accounts-returns-OK-response.frozen | 1 + ...t-Fastly-accounts-returns-OK-response.json | 95 +++++++++++++++ ...-Fastly-account-returns-OK-response.frozen | 1 + ...te-Fastly-account-returns-OK-response.json | 101 ++++++++++++++++ tests/scenarios/fixtures.rs | 111 +++++++++++++++--- tests/scenarios/function_mappings.rs | 42 ++----- 17 files changed, 493 insertions(+), 153 deletions(-) delete mode 100644 .generator/src/generator/templates/api_mod.j2 create mode 100644 tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Add-Fastly-account-returns-CREATED-response.frozen create mode 100644 tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Add-Fastly-account-returns-CREATED-response.json create mode 100644 tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Get-Fastly-account-returns-OK-response.frozen create mode 100644 tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Get-Fastly-account-returns-OK-response.json create mode 100644 tests/scenarios/cassettes/v2/Feature_Fastly_Integration/List-Fastly-accounts-returns-OK-response.frozen create mode 100644 tests/scenarios/cassettes/v2/Feature_Fastly_Integration/List-Fastly-accounts-returns-OK-response.json create mode 100644 tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Update-Fastly-account-returns-OK-response.frozen create mode 100644 tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Update-Fastly-account-returns-OK-response.json diff --git a/.generator/src/generator/templates/api_mod.j2 b/.generator/src/generator/templates/api_mod.j2 deleted file mode 100644 index 980159af0..000000000 --- a/.generator/src/generator/templates/api_mod.j2 +++ /dev/null @@ -1,96 +0,0 @@ -use std::error; -use std::fmt; - -#[derive(Debug, Clone)] -pub struct ResponseContent { - pub status: reqwest::StatusCode, - pub content: String, - pub entity: Option, -} - -#[derive(Debug)] -pub enum Error { - Reqwest(reqwest::Error), - Serde(serde_json::Error), - Io(std::io::Error), - ResponseError(ResponseContent), -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let (module, e) = match self { - Error::Reqwest(e) => ("reqwest", e.to_string()), - Error::Serde(e) => ("serde", e.to_string()), - Error::Io(e) => ("IO", e.to_string()), - Error::ResponseError(e) => ("response", format!("status code {}", e.status)), - }; - write!(f, "error in {}: {}", module, e) - } -} - -impl error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - Some(match self { - Error::Reqwest(e) => e, - Error::Serde(e) => e, - Error::Io(e) => e, - Error::ResponseError(_) => return None, - }) - } -} - -impl From for Error { - fn from(e: reqwest::Error) -> Self { - Error::Reqwest(e) - } -} - -impl From for Error { - fn from(e: serde_json::Error) -> Self { - Error::Serde(e) - } -} - -impl From for Error { - fn from(e: std::io::Error) -> Self { - Error::Io(e) - } -} - -pub fn urlencode>(s: T) -> String { - ::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect() -} - -pub fn parse_deep_object(prefix: &str, value: &serde_json::Value) -> Vec<(String, String)> { - if let serde_json::Value::Object(object) = value { - let mut params = vec![]; - - for (key, value) in object { - match value { - serde_json::Value::Object(_) => params.append(&mut parse_deep_object( - &format!("{}[{}]", prefix, key), - value, - )), - serde_json::Value::Array(array) => { - for (i, value) in array.iter().enumerate() { - params.append(&mut parse_deep_object( - &format!("{}[{}][{}]", prefix, key, i), - value, - )); - } - }, - serde_json::Value::String(s) => params.push((format!("{}[{}]", prefix, key), s.clone())), - _ => params.push((format!("{}[{}]", prefix, key), value.to_string())), - } - } - return params; - } - unimplemented!("Only objects are supported with style=deepObject") -} - -{%- for name, operations in all_operations|sort %} -{%- set classname = name + " api" %} -pub mod {{ classname | snake_case}}; -{%- endfor %} - -pub mod configuration; diff --git a/.generator/src/generator/templates/common_mod.j2 b/.generator/src/generator/templates/common_mod.j2 index 980159af0..19b9a3b2e 100644 --- a/.generator/src/generator/templates/common_mod.j2 +++ b/.generator/src/generator/templates/common_mod.j2 @@ -11,6 +11,7 @@ pub struct ResponseContent { #[derive(Debug)] pub enum Error { Reqwest(reqwest::Error), + ReqwestMiddleware(reqwest_middleware::Error), Serde(serde_json::Error), Io(std::io::Error), ResponseError(ResponseContent), @@ -20,6 +21,7 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (module, e) = match self { Error::Reqwest(e) => ("reqwest", e.to_string()), + Error::ReqwestMiddleware(e) => ("reqwest_middleware", e.to_string()), Error::Serde(e) => ("serde", e.to_string()), Error::Io(e) => ("IO", e.to_string()), Error::ResponseError(e) => ("response", format!("status code {}", e.status)), @@ -32,6 +34,7 @@ impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { Some(match self { Error::Reqwest(e) => e, + Error::ReqwestMiddleware(e) => e, Error::Serde(e) => e, Error::Io(e) => e, Error::ResponseError(_) => return None, @@ -45,6 +48,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: reqwest_middleware::Error) -> Self { + Error::ReqwestMiddleware(e) + } +} + impl From for Error { fn from(e: serde_json::Error) -> Self { Error::Serde(e) diff --git a/.generator/src/generator/templates/configuration.j2 b/.generator/src/generator/templates/configuration.j2 index c758d13ea..62f201e26 100644 --- a/.generator/src/generator/templates/configuration.j2 +++ b/.generator/src/generator/templates/configuration.j2 @@ -6,7 +6,7 @@ use std::env; pub struct Configuration { pub base_path: String, pub user_agent: Option, - pub client: reqwest::Client, + pub client: reqwest_middleware::ClientWithMiddleware, {%- set authMethods = openapi.security %} {%- if authMethods %} {%- for authMethod in authMethods %} @@ -29,6 +29,7 @@ impl Configuration { impl Default for Configuration { fn default() -> Self { + let http_client = reqwest_middleware::ClientBuilder::new(reqwest::Client::new()); Configuration { base_path: "https://api.datadoghq.com".to_owned(), user_agent: Some(format!( @@ -38,7 +39,7 @@ impl Default for Configuration { env::consts::OS, env::consts::ARCH, )), - client: reqwest::Client::new(), + client: http_client.build(), {%- set authMethods = openapi.security %} {%- if authMethods %} {%- for authMethod in authMethods %} diff --git a/.generator/src/generator/templates/function_mappings.j2 b/.generator/src/generator/templates/function_mappings.j2 index 90b07b2b4..9c9912a41 100644 --- a/.generator/src/generator/templates/function_mappings.j2 +++ b/.generator/src/generator/templates/function_mappings.j2 @@ -75,10 +75,8 @@ fn test_{{ operation['operationId'] | snake_case }}(world: &mut DatadogWorld, _p Ok(response) => response, Err(error) => { return match error { - Error::Reqwest(e) => panic!("reqwest error: {}", e), - Error::Serde(e) => panic!("serde error: {}", e), - Error::Io(e) => panic!("io error: {}", e), Error::ResponseError(e) => world.response.code = e.status.as_u16(), + _ => panic!("error parsing response: {}", error), }; } }; diff --git a/Cargo.toml b/Cargo.toml index c5663bc99..f8033f0e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ url = "^2.2" uuid = { version = "^1.0", features = ["serde"] } rustc_version = "0.4.0" log = "0.4.20" +reqwest-middleware = "0.1.6" [dependencies.reqwest] version = "^0.11" features = ["json", "multipart"] @@ -28,6 +29,8 @@ handlebars = "4.4.0" regex = "1.9.5" sha256 = "1.4.0" futures = "0.3.28" +rvcr = { git = "https://github.com/nkzou/rvcr.git", rev = "a3fc79e" } +chrono = "0.4.31" [[test]] name = "main" diff --git a/src/datadog/configuration.rs b/src/datadog/configuration.rs index bc9adadc4..a4037869b 100644 --- a/src/datadog/configuration.rs +++ b/src/datadog/configuration.rs @@ -8,7 +8,7 @@ use std::env; pub struct Configuration { pub base_path: String, pub user_agent: Option, - pub client: reqwest::Client, + pub client: reqwest_middleware::ClientWithMiddleware, pub api_key_auth: Option, pub app_key_auth: Option, } @@ -21,6 +21,7 @@ impl Configuration { impl Default for Configuration { fn default() -> Self { + let http_client = reqwest_middleware::ClientBuilder::new(reqwest::Client::new()); Configuration { base_path: "https://api.datadoghq.com".to_owned(), user_agent: Some(format!( @@ -30,7 +31,7 @@ impl Default for Configuration { env::consts::OS, env::consts::ARCH, )), - client: reqwest::Client::new(), + client: http_client.build(), api_key_auth: env::var("DD_API_KEY").ok(), app_key_auth: env::var("DD_APP_KEY").ok(), } diff --git a/src/datadog/mod.rs b/src/datadog/mod.rs index 6a6d2c1f9..805146b8b 100644 --- a/src/datadog/mod.rs +++ b/src/datadog/mod.rs @@ -11,6 +11,7 @@ pub struct ResponseContent { #[derive(Debug)] pub enum Error { Reqwest(reqwest::Error), + ReqwestMiddleware(reqwest_middleware::Error), Serde(serde_json::Error), Io(std::io::Error), ResponseError(ResponseContent), @@ -20,6 +21,7 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (module, e) = match self { Error::Reqwest(e) => ("reqwest", e.to_string()), + Error::ReqwestMiddleware(e) => ("reqwest_middleware", e.to_string()), Error::Serde(e) => ("serde", e.to_string()), Error::Io(e) => ("IO", e.to_string()), Error::ResponseError(e) => ("response", format!("status code {}", e.status)), @@ -32,6 +34,7 @@ impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { Some(match self { Error::Reqwest(e) => e, + Error::ReqwestMiddleware(e) => e, Error::Serde(e) => e, Error::Io(e) => e, Error::ResponseError(_) => return None, @@ -45,6 +48,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: reqwest_middleware::Error) -> Self { + Error::ReqwestMiddleware(e) + } +} + impl From for Error { fn from(e: serde_json::Error) -> Self { Error::Serde(e) diff --git a/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Add-Fastly-account-returns-CREATED-response.frozen b/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Add-Fastly-account-returns-CREATED-response.frozen new file mode 100644 index 000000000..f0b6e19ec --- /dev/null +++ b/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Add-Fastly-account-returns-CREATED-response.frozen @@ -0,0 +1 @@ +2023-01-19T15:15:56.412Z \ No newline at end of file diff --git a/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Add-Fastly-account-returns-CREATED-response.json b/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Add-Fastly-account-returns-CREATED-response.json new file mode 100644 index 000000000..0affacb28 --- /dev/null +++ b/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Add-Fastly-account-returns-CREATED-response.json @@ -0,0 +1,67 @@ +{ + "http_interactions": [ + { + "recorded_at": "Thu, 19 Jan 2023 15:15:56 GMT", + "request": { + "body": { + "encoding": null, + "string": "{\"data\":{\"attributes\":{\"api_key\":\"TestAddFastlyaccountreturnsCREATEDresponse1674141356\",\"name\":\"Test-Add_Fastly_account_returns_CREATED_response-1674141356\",\"services\":[]},\"type\":\"fastly-accounts\"}}" + }, + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json" + ] + }, + "method": "post", + "uri": "https://api.datadoghq.com/api/v2/integrations/fastly/accounts" + }, + "response": { + "body": { + "encoding": null, + "string": "{\"data\":{\"attributes\":{\"services\":[],\"name\":\"Test-Add_Fastly_account_returns_CREATED_response-1674141356\"},\"type\":\"fastly-accounts\",\"id\":\"0427b05b6f56f454ca1477aa8df5e75d\"}}\n" + }, + "headers": { + "Content-Type": [ + "application/json" + ] + }, + "status": { + "code": 201, + "message": "Created" + } + } + }, + { + "recorded_at": "Thu, 19 Jan 2023 15:15:56 GMT", + "request": { + "body": "", + "headers": { + "Accept": [ + "*/*" + ] + }, + "method": "delete", + "uri": "https://api.datadoghq.com/api/v2/integrations/fastly/accounts/0427b05b6f56f454ca1477aa8df5e75d" + }, + "response": { + "body": { + "encoding": null, + "string": "" + }, + "headers": { + "Content-Type": [ + "text/html; charset=utf-8" + ] + }, + "status": { + "code": 204, + "message": "No Content" + } + } + } + ], + "recorded_with": "VCR 6.0.0" +} diff --git a/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Get-Fastly-account-returns-OK-response.frozen b/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Get-Fastly-account-returns-OK-response.frozen new file mode 100644 index 000000000..2ee7e978b --- /dev/null +++ b/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Get-Fastly-account-returns-OK-response.frozen @@ -0,0 +1 @@ +2023-03-13T10:11:14.475Z \ No newline at end of file diff --git a/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Get-Fastly-account-returns-OK-response.json b/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Get-Fastly-account-returns-OK-response.json new file mode 100644 index 000000000..d3c4aaef2 --- /dev/null +++ b/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Get-Fastly-account-returns-OK-response.json @@ -0,0 +1,95 @@ +{ + "http_interactions": [ + { + "recorded_at": "Mon, 13 Mar 2023 10:11:14 GMT", + "request": { + "body": { + "encoding": null, + "string": "{\"data\":{\"attributes\":{\"api_key\":\"TestGetFastlyaccountreturnsOKresponse1678702274\",\"name\":\"Test-Get_Fastly_account_returns_OK_response-1678702274\",\"services\":[]},\"type\":\"fastly-accounts\"}}" + }, + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json" + ] + }, + "method": "post", + "uri": "https://api.datadoghq.com/api/v2/integrations/fastly/accounts" + }, + "response": { + "body": { + "encoding": null, + "string": "{\"data\":{\"type\":\"fastly-accounts\",\"id\":\"6d8f2860f9f3e953fb46d554b9a19627\",\"attributes\":{\"services\":[],\"name\":\"Test-Get_Fastly_account_returns_OK_response-1678702274\"}}}\n" + }, + "headers": { + "Content-Type": [ + "application/json" + ] + }, + "status": { + "code": 201, + "message": "Created" + } + } + }, + { + "recorded_at": "Mon, 13 Mar 2023 10:11:14 GMT", + "request": { + "body": "", + "headers": { + "Accept": [ + "application/json" + ] + }, + "method": "get", + "uri": "https://api.datadoghq.com/api/v2/integrations/fastly/accounts/6d8f2860f9f3e953fb46d554b9a19627" + }, + "response": { + "body": { + "encoding": null, + "string": "{\"data\":{\"type\":\"fastly-accounts\",\"id\":\"6d8f2860f9f3e953fb46d554b9a19627\",\"attributes\":{\"name\":\"Test-Get_Fastly_account_returns_OK_response-1678702274\",\"services\":[]}}}\n" + }, + "headers": { + "Content-Type": [ + "application/json" + ] + }, + "status": { + "code": 200, + "message": "OK" + } + } + }, + { + "recorded_at": "Mon, 13 Mar 2023 10:11:14 GMT", + "request": { + "body": "", + "headers": { + "Accept": [ + "*/*" + ] + }, + "method": "delete", + "uri": "https://api.datadoghq.com/api/v2/integrations/fastly/accounts/6d8f2860f9f3e953fb46d554b9a19627" + }, + "response": { + "body": { + "encoding": null, + "string": "" + }, + "headers": { + "Content-Type": [ + "text/html; charset=utf-8" + ] + }, + "status": { + "code": 204, + "message": "No Content" + } + } + } + ], + "recorded_with": "VCR 6.0.0" +} diff --git a/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/List-Fastly-accounts-returns-OK-response.frozen b/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/List-Fastly-accounts-returns-OK-response.frozen new file mode 100644 index 000000000..1e15453e8 --- /dev/null +++ b/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/List-Fastly-accounts-returns-OK-response.frozen @@ -0,0 +1 @@ +2023-03-13T10:10:50.453Z \ No newline at end of file diff --git a/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/List-Fastly-accounts-returns-OK-response.json b/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/List-Fastly-accounts-returns-OK-response.json new file mode 100644 index 000000000..622732efb --- /dev/null +++ b/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/List-Fastly-accounts-returns-OK-response.json @@ -0,0 +1,95 @@ +{ + "http_interactions": [ + { + "recorded_at": "Mon, 13 Mar 2023 10:10:50 GMT", + "request": { + "body": { + "encoding": null, + "string": "{\"data\":{\"attributes\":{\"api_key\":\"TestListFastlyaccountsreturnsOKresponse1678702250\",\"name\":\"Test-List_Fastly_accounts_returns_OK_response-1678702250\",\"services\":[]},\"type\":\"fastly-accounts\"}}" + }, + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json" + ] + }, + "method": "post", + "uri": "https://api.datadoghq.com/api/v2/integrations/fastly/accounts" + }, + "response": { + "body": { + "encoding": null, + "string": "{\"data\":{\"type\":\"fastly-accounts\",\"attributes\":{\"name\":\"Test-List_Fastly_accounts_returns_OK_response-1678702250\",\"services\":[]},\"id\":\"07ec97dd43cd794c847ecf15cb25eb1c\"}}\n" + }, + "headers": { + "Content-Type": [ + "application/json" + ] + }, + "status": { + "code": 201, + "message": "Created" + } + } + }, + { + "recorded_at": "Mon, 13 Mar 2023 10:10:50 GMT", + "request": { + "body": "", + "headers": { + "Accept": [ + "application/json" + ] + }, + "method": "get", + "uri": "https://api.datadoghq.com/api/v2/integrations/fastly/accounts" + }, + "response": { + "body": { + "encoding": null, + "string": "{\"data\":[{\"type\":\"fastly-accounts\",\"attributes\":{\"name\":\"Test-List_Fastly_accounts_returns_OK_response-1678702250\",\"services\":[]},\"id\":\"07ec97dd43cd794c847ecf15cb25eb1c\"}]}\n" + }, + "headers": { + "Content-Type": [ + "application/json" + ] + }, + "status": { + "code": 200, + "message": "OK" + } + } + }, + { + "recorded_at": "Mon, 13 Mar 2023 10:10:50 GMT", + "request": { + "body": "", + "headers": { + "Accept": [ + "*/*" + ] + }, + "method": "delete", + "uri": "https://api.datadoghq.com/api/v2/integrations/fastly/accounts/07ec97dd43cd794c847ecf15cb25eb1c" + }, + "response": { + "body": { + "encoding": null, + "string": "" + }, + "headers": { + "Content-Type": [ + "text/html; charset=utf-8" + ] + }, + "status": { + "code": 204, + "message": "No Content" + } + } + } + ], + "recorded_with": "VCR 6.0.0" +} diff --git a/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Update-Fastly-account-returns-OK-response.frozen b/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Update-Fastly-account-returns-OK-response.frozen new file mode 100644 index 000000000..64bd5f8bd --- /dev/null +++ b/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Update-Fastly-account-returns-OK-response.frozen @@ -0,0 +1 @@ +2023-03-13T10:10:17.626Z \ No newline at end of file diff --git a/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Update-Fastly-account-returns-OK-response.json b/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Update-Fastly-account-returns-OK-response.json new file mode 100644 index 000000000..74a2bd6f1 --- /dev/null +++ b/tests/scenarios/cassettes/v2/Feature_Fastly_Integration/Update-Fastly-account-returns-OK-response.json @@ -0,0 +1,101 @@ +{ + "http_interactions": [ + { + "recorded_at": "Mon, 13 Mar 2023 10:10:17 GMT", + "request": { + "body": { + "encoding": null, + "string": "{\"data\":{\"attributes\":{\"api_key\":\"TestUpdateFastlyaccountreturnsOKresponse1678702217\",\"name\":\"Test-Update_Fastly_account_returns_OK_response-1678702217\",\"services\":[]},\"type\":\"fastly-accounts\"}}" + }, + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json" + ] + }, + "method": "post", + "uri": "https://api.datadoghq.com/api/v2/integrations/fastly/accounts" + }, + "response": { + "body": { + "encoding": null, + "string": "{\"data\":{\"type\":\"fastly-accounts\",\"id\":\"e37e834ae856fa24a2924973fdc7c276\",\"attributes\":{\"services\":[],\"name\":\"Test-Update_Fastly_account_returns_OK_response-1678702217\"}}}\n" + }, + "headers": { + "Content-Type": [ + "application/json" + ] + }, + "status": { + "code": 201, + "message": "Created" + } + } + }, + { + "recorded_at": "Mon, 13 Mar 2023 10:10:17 GMT", + "request": { + "body": { + "encoding": null, + "string": "{\"data\":{\"attributes\":{\"api_key\":\"update-secret\"},\"type\":\"fastly-accounts\"}}" + }, + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json" + ] + }, + "method": "patch", + "uri": "https://api.datadoghq.com/api/v2/integrations/fastly/accounts/e37e834ae856fa24a2924973fdc7c276" + }, + "response": { + "body": { + "encoding": null, + "string": "{\"data\":{\"type\":\"fastly-accounts\",\"id\":\"e37e834ae856fa24a2924973fdc7c276\",\"attributes\":{\"services\":[],\"name\":\"Test-Update_Fastly_account_returns_OK_response-1678702217\"}}}\n" + }, + "headers": { + "Content-Type": [ + "application/json" + ] + }, + "status": { + "code": 200, + "message": "OK" + } + } + }, + { + "recorded_at": "Mon, 13 Mar 2023 10:10:17 GMT", + "request": { + "body": "", + "headers": { + "Accept": [ + "*/*" + ] + }, + "method": "delete", + "uri": "https://api.datadoghq.com/api/v2/integrations/fastly/accounts/e37e834ae856fa24a2924973fdc7c276" + }, + "response": { + "body": { + "encoding": null, + "string": "" + }, + "headers": { + "Content-Type": [ + "text/html; charset=utf-8" + ] + }, + "status": { + "code": 204, + "message": "No Content" + } + } + } + ], + "recorded_with": "VCR 6.0.0" +} diff --git a/tests/scenarios/fixtures.rs b/tests/scenarios/fixtures.rs index 16ceff5da..61e0aa9d2 100644 --- a/tests/scenarios/fixtures.rs +++ b/tests/scenarios/fixtures.rs @@ -1,4 +1,5 @@ use crate::scenarios::function_mappings::{collect_function_calls, initialize_api_instance, ApiInstances}; +use chrono::DateTime; use cucumber::{ event::ScenarioFinished, gherkin::{Feature, Rule, Scenario}, @@ -6,13 +7,18 @@ use cucumber::{ }; use datadog_api_client::datadog::configuration::Configuration; use handlebars::Handlebars; +use log::debug; use regex::Regex; +use reqwest_middleware::ClientBuilder; +use rvcr::{VCRMiddleware, VCRMode}; use serde_json::{json, Value}; use sha256::digest; use std::{ collections::HashMap, - fs::{read_to_string, File}, + env, + fs::{create_dir_all, read_to_string, File}, io::BufReader, + path::PathBuf, time::SystemTime, }; @@ -64,37 +70,107 @@ pub async fn before_scenario(feature: &Feature, _rule: Option<&Rule>, scenario: let undo_file = File::open(format!("tests/scenarios/features/v{}/undo.json", world.api_version)).unwrap(); world.undo_map = serde_json::from_reader(BufReader::new(undo_file)).unwrap(); - let mut config = Configuration::new(); - config.api_key_auth = Some("00000000000000000000000000000000".to_string()); - config.app_key_auth = Some("0000000000000000000000000000000000000000".to_string()); - world.config = config; - let non_alnum_re = Regex::new(r"[^A-Za-z0-9]+").unwrap(); - let prefix = match true { - true => "Test-Rust", - false => "Test", + let escaped_filename = non_alnum_re.replace_all(scenario.name.as_str(), "-").to_string(); + let filename = match escaped_filename.len() > 100 { + true => escaped_filename[..100].to_string(), + false => escaped_filename, }; - let now = SystemTime::now() + let mut prefix = "Test".to_string(); + let mut cassette_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + cassette_dir.push(format!( + "tests/scenarios/cassettes/v{}/Feature_{}", + world.api_version, + feature.name.replace(' ', "_") + )); + create_dir_all(&cassette_dir).expect("failed to create cassette directory"); + let mut cassette = cassette_dir.clone(); + cassette.push(format!("{}.json", filename)); + let mut freeze = cassette_dir.clone(); + freeze.push(format!("{}.frozen", filename)); + let mut config = Configuration::new(); + let mut frozen_time = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs(); + let vcr_client_builder = ClientBuilder::new(reqwest::Client::new()); + config.client = match env::var("RECORD").unwrap_or("false".to_string()).as_str() { + "none" => { + prefix.push_str("-Rust"); + vcr_client_builder.build() + } + "true" => { + // let _ = remove_file(cassette.clone()); + // let _ = remove_file(freeze.clone()); + // let mut freeze_file = File::create(freeze).expect("failed to write freeze file"); + // freeze_file + // .write_all( + // DateTime::to_rfc3339( + // &DateTime::from_timestamp(frozen_time as i64, 0) + // .expect("failed to convert timestamp to datetime"), + // ) + // .as_bytes(), + // ) + // .expect("failed to write freeze file"); + // let middleware: VCRMiddleware = VCRMiddleware::try_from(cassette) + // .expect("Failed to initialize rVCR middleware") + // .with_mode(VCRMode::Record) + // .with_modify_request(|req| { + // req.headers.remove_entry("dd-api-key"); + // req.headers.remove_entry("dd-application-key"); + // }) + // .with_modify_response(|res| { + // res.headers.remove_entry("content-security-policy"); + // }); + // vcr_client_builder.with(middleware).build() + panic!("sdk's shouldn't be recording, that's the spec repo's job."); + } + _ => { + frozen_time = + DateTime::parse_from_rfc3339(read_to_string(freeze).expect("Failed to read freeze file").as_str()) + .expect("Failed to parse freeze file time") + .signed_duration_since(DateTime::UNIX_EPOCH) + .num_seconds() as u64; + debug!("{}", frozen_time); + let middleware: VCRMiddleware = VCRMiddleware::try_from(cassette) + .expect("Failed to initialize rVCR middleware") + .with_mode(VCRMode::Replay) + .with_modify_request(|req| { + req.headers.remove_entry("dd-api-key"); + req.headers.remove_entry("dd-application-key"); + }) + .with_modify_response(|res| { + res.headers.remove_entry("content-security-policy"); + }) + .with_request_matcher(|vcr_req, req| { + vcr_req.uri.to_string() == req.uri.to_string() + && vcr_req.body.string == req.body.string + && vcr_req.method == req.method + }); + vcr_client_builder.with(middleware).build() + } + }; + config.api_key_auth = Some("00000000000000000000000000000000".to_string()); + config.app_key_auth = Some("0000000000000000000000000000000000000000".to_string()); + world.config = config; + let escaped_name = non_alnum_re.replace_all(scenario.name.as_str(), "_").to_string(); let name = match escaped_name.len() > 100 { true => escaped_name[..100].to_string(), false => escaped_name, }; - let unique = format!("{}-{}-{}", prefix, name, now); + let unique = format!("{}-{}-{}", prefix, name, frozen_time); let unique_alnum = non_alnum_re.replace_all(unique.as_str(), "").to_string(); world.fixtures = json!({ "unique": unique, - "unique_lower": unique.to_ascii_lowercase(), + "unique_lowe: Stringr": unique.to_ascii_lowercase(), "unique_upper": unique.to_ascii_uppercase(), "unique_alnum": unique_alnum, "unique_lower_alnum": unique_alnum.to_ascii_lowercase(), "unique_upper_alnum": unique_alnum.to_ascii_uppercase(), "unique_hash": digest(unique)[..16], - "now": now, + "now": frozen_time, }); } @@ -114,12 +190,12 @@ pub async fn after_scenario( #[given(expr = "a valid \"apiKeyAuth\" key in the system")] fn valid_apikey_auth(world: &mut DatadogWorld) { - world.config.api_key_auth = std::env::var("DD_TEST_CLIENT_API_KEY").ok(); + world.config.api_key_auth = env::var("DD_TEST_CLIENT_API_KEY").ok(); } #[given(expr = "a valid \"appKeyAuth\" key in the system")] fn valid_appkey_auth(world: &mut DatadogWorld) { - world.config.app_key_auth = std::env::var("DD_TEST_CLIENT_APP_KEY").ok(); + world.config.app_key_auth = env::var("DD_TEST_CLIENT_APP_KEY").ok(); } #[given(expr = "an instance of {string} API")] @@ -169,9 +245,7 @@ fn given_resource_in_system(world: &mut DatadogWorld, given_key: String) { world.function_mappings.get(&operation_id).unwrap()(world, &given_parameters); match build_undo(world, &operation_id) { - Ok(Some(undo)) => { - world.undo_operations.push(undo); - } + Ok(Some(undo)) => world.undo_operations.push(undo), Ok(None) => {} Err(err) => panic!("{err}"), } @@ -256,7 +330,6 @@ fn lookup(path: &String, object: &Value) -> Option { for (_, [idx]) in index_re.captures_iter(&json_pointer.clone()).map(|c| c.extract()) { json_pointer = index_re.replace(&json_pointer, format!("/{idx}")).to_string(); } - return object.pointer(&json_pointer).cloned(); } diff --git a/tests/scenarios/function_mappings.rs b/tests/scenarios/function_mappings.rs index 6e1640c19..7b15f202d 100644 --- a/tests/scenarios/function_mappings.rs +++ b/tests/scenarios/function_mappings.rs @@ -20,7 +20,7 @@ pub fn initialize_api_instance(world: &mut DatadogWorld, api: String) { Some(FastlyIntegrationAPI::with_config(world.config.clone())); } } - _ => panic!("'{api}' API instance tag not found"), + _ => panic!("{api} API instance not found"), } } @@ -67,10 +67,8 @@ fn test_list_fastly_accounts(world: &mut DatadogWorld, _parameters: &HashMap response, Err(error) => { return match error { - Error::Reqwest(e) => panic!("reqwest error: {}", e), - Error::Serde(e) => panic!("serde error: {}", e), - Error::Io(e) => panic!("io error: {}", e), Error::ResponseError(e) => world.response.code = e.status.as_u16(), + _ => panic!("error parsing response: {}", error), }; } }; @@ -91,10 +89,8 @@ fn test_create_fastly_account(world: &mut DatadogWorld, _parameters: &HashMap response, Err(error) => { return match error { - Error::Reqwest(e) => panic!("reqwest error: {}", e), - Error::Serde(e) => panic!("serde error: {}", e), - Error::Io(e) => panic!("io error: {}", e), Error::ResponseError(e) => world.response.code = e.status.as_u16(), + _ => panic!("error parsing response: {}", error), }; } }; @@ -115,10 +111,8 @@ fn test_delete_fastly_account(world: &mut DatadogWorld, _parameters: &HashMap response, Err(error) => { return match error { - Error::Reqwest(e) => panic!("reqwest error: {}", e), - Error::Serde(e) => panic!("serde error: {}", e), - Error::Io(e) => panic!("io error: {}", e), Error::ResponseError(e) => world.response.code = e.status.as_u16(), + _ => panic!("error parsing response: {}", error), }; } }; @@ -139,10 +133,8 @@ fn test_get_fastly_account(world: &mut DatadogWorld, _parameters: &HashMap response, Err(error) => { return match error { - Error::Reqwest(e) => panic!("reqwest error: {}", e), - Error::Serde(e) => panic!("serde error: {}", e), - Error::Io(e) => panic!("io error: {}", e), Error::ResponseError(e) => world.response.code = e.status.as_u16(), + _ => panic!("error parsing response: {}", error), }; } }; @@ -164,10 +156,8 @@ fn test_update_fastly_account(world: &mut DatadogWorld, _parameters: &HashMap response, Err(error) => { return match error { - Error::Reqwest(e) => panic!("reqwest error: {}", e), - Error::Serde(e) => panic!("serde error: {}", e), - Error::Io(e) => panic!("io error: {}", e), Error::ResponseError(e) => world.response.code = e.status.as_u16(), + _ => panic!("error parsing response: {}", error), }; } }; @@ -188,10 +178,8 @@ fn test_list_fastly_services(world: &mut DatadogWorld, _parameters: &HashMap response, Err(error) => { return match error { - Error::Reqwest(e) => panic!("reqwest error: {}", e), - Error::Serde(e) => panic!("serde error: {}", e), - Error::Io(e) => panic!("io error: {}", e), Error::ResponseError(e) => world.response.code = e.status.as_u16(), + _ => panic!("error parsing response: {}", error), }; } }; @@ -213,10 +201,8 @@ fn test_create_fastly_service(world: &mut DatadogWorld, _parameters: &HashMap response, Err(error) => { return match error { - Error::Reqwest(e) => panic!("reqwest error: {}", e), - Error::Serde(e) => panic!("serde error: {}", e), - Error::Io(e) => panic!("io error: {}", e), Error::ResponseError(e) => world.response.code = e.status.as_u16(), + _ => panic!("error parsing response: {}", error), }; } }; @@ -238,10 +224,8 @@ fn test_delete_fastly_service(world: &mut DatadogWorld, _parameters: &HashMap response, Err(error) => { return match error { - Error::Reqwest(e) => panic!("reqwest error: {}", e), - Error::Serde(e) => panic!("serde error: {}", e), - Error::Io(e) => panic!("io error: {}", e), Error::ResponseError(e) => world.response.code = e.status.as_u16(), + _ => panic!("error parsing response: {}", error), }; } }; @@ -263,10 +247,8 @@ fn test_get_fastly_service(world: &mut DatadogWorld, _parameters: &HashMap response, Err(error) => { return match error { - Error::Reqwest(e) => panic!("reqwest error: {}", e), - Error::Serde(e) => panic!("serde error: {}", e), - Error::Io(e) => panic!("io error: {}", e), Error::ResponseError(e) => world.response.code = e.status.as_u16(), + _ => panic!("error parsing response: {}", error), }; } }; @@ -289,10 +271,8 @@ fn test_update_fastly_service(world: &mut DatadogWorld, _parameters: &HashMap response, Err(error) => { return match error { - Error::Reqwest(e) => panic!("reqwest error: {}", e), - Error::Serde(e) => panic!("serde error: {}", e), - Error::Io(e) => panic!("io error: {}", e), Error::ResponseError(e) => world.response.code = e.status.as_u16(), + _ => panic!("error parsing response: {}", error), }; } };