diff --git a/Cargo.lock b/Cargo.lock index e89c5aa6b04..7152d0ade6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,6 +108,17 @@ version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee67c11feeac938fae061b232e38e0b6d94f97a9df10e6271319325ac4c56a86" +[[package]] +name = "assert-json-diff" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4259cbe96513d2f1073027a259fc2ca917feb3026a5a8d984e3628e490255cc0" +dependencies = [ + "extend", + "serde", + "serde_json", +] + [[package]] name = "async-compression" version = "0.3.7" @@ -293,6 +304,7 @@ dependencies = [ "lazy_static", "lettre", "license-exprs", + "mockito", "oauth2", "parking_lot", "rand 0.7.3", @@ -427,6 +439,17 @@ dependencies = [ "syn", ] +[[package]] +name = "colored" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + [[package]] name = "comrak" version = "0.9.0" @@ -746,6 +769,12 @@ dependencies = [ "migrations_macros", ] +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + [[package]] name = "digest" version = "0.8.1" @@ -776,6 +805,12 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +[[package]] +name = "dtoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e" + [[package]] name = "encode_unicode" version = "0.3.6" @@ -797,6 +832,18 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" +[[package]] +name = "extend" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47da3a72ec598d9c8937a7ebca8962a5c7a1f28444e38c2b33c771ba3f55f05" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -1614,6 +1661,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "mockito" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36a0eb7e686b49b02c1cb87c14b8e2a05de0d36c6eee0293653d0a875906d499" +dependencies = [ + "assert-json-diff", + "colored", + "difference", + "httparse", + "lazy_static", + "log", + "rand 0.7.3", + "regex", + "serde_json", + "serde_urlencoded 0.6.1", +] + [[package]] name = "native-tls" version = "0.2.7" @@ -2227,7 +2292,7 @@ dependencies = [ "rustls", "serde", "serde_json", - "serde_urlencoded", + "serde_urlencoded 0.7.0", "tokio", "tokio-native-tls", "tokio-rustls", @@ -2528,6 +2593,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" +dependencies = [ + "dtoa", + "itoa", + "serde", + "url", +] + [[package]] name = "serde_urlencoded" version = "0.7.0" diff --git a/Cargo.toml b/Cargo.toml index 78ba486a16c..4abc8f0305b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,7 @@ conduit-test = "0.9.0-alpha.4" diesel_migrations = { version = "1.3.0", features = ["postgres"] } hyper-tls = "0.5" lazy_static = "1.0" +mockito = "0.28" tokio = "1" tower-service = "0.3.0" diff --git a/src/admin/on_call.rs b/src/admin/on_call.rs index bc180b32bf1..47cbceaeebf 100644 --- a/src/admin/on_call.rs +++ b/src/admin/on_call.rs @@ -25,11 +25,17 @@ impl Event { /// If the variant is `Trigger`, this will page whoever is on call /// (potentially waking them up at 3 AM). pub fn send(self) -> Result<()> { + #[cfg(not(test))] + let base_url = "https://events.pagerduty.com"; + #[cfg(test)] + let base_url = mockito::server_url(); + let api_token = dotenv::var("PAGERDUTY_API_TOKEN")?; let service_key = dotenv::var("PAGERDUTY_INTEGRATION_KEY")?; + let url = format!("{}/generic/2010-04-15/create_event.json", base_url); let response = Client::new() - .post("https://events.pagerduty.com/generic/2010-04-15/create_event.json") + .post(&url) .header(header::ACCEPT, "application/vnd.pagerduty+json;version=2") .header(header::AUTHORIZATION, format!("Token token={}", api_token)) .json(&FullEvent { @@ -66,3 +72,75 @@ struct InvalidEvent { message: String, errors: Vec, } + +#[cfg(test)] +mod tests { + use super::Event; + use mockito::{mock, Matcher}; + use std::env; + + #[test] + fn test_send() { + // set environment variables for this test + env::set_var("PAGERDUTY_API_TOKEN", "secret123"); + env::set_var("PAGERDUTY_INTEGRATION_KEY", "crates-io-service-key"); + + // setup the pagerduty API endpoint mock + let response_body = json!({ + "description": "possible spam attack underway", + "event_type": "trigger", + "incident_key": "spam_attack", + "service_key": "crates-io-service-key" + }); + + let mock = mock("POST", "/generic/2010-04-15/create_event.json") + .match_header("Accept", "application/vnd.pagerduty+json;version=2") + .match_header("Authorization", "Token token=secret123") + .match_header("Content-Type", "application/json") + .match_body(Matcher::Json(response_body)) + .with_status(200) + .create(); + + // create and send the event + let event = Event::Trigger { + incident_key: Some("spam_attack".into()), + description: "possible spam attack underway".into(), + }; + + let result = event.send(); + + // check that the mock endpoint was triggered + mock.assert(); + assert_ok!(result); + } + + #[test] + fn test_send_with_400_error() { + // set environment variables for this test + env::set_var("PAGERDUTY_API_TOKEN", "secret123"); + env::set_var("PAGERDUTY_INTEGRATION_KEY", "crates-io-service-key"); + + // setup the pagerduty API endpoint mock + let request_body = json!({ + "message": "oops", + "errors": ["something", "went", "wrong"], + }); + + let mock = mock("POST", "/generic/2010-04-15/create_event.json") + .with_status(400) + .with_body(request_body.to_string()) + .create(); + + // create and send the event + let event = Event::Trigger { + incident_key: Some("spam_attack".into()), + description: "possible spam attack underway".into(), + }; + + let result = event.send(); + + // check that the mock endpoint was triggered + mock.assert(); + assert_err!(result); + } +}