libdeadmock 0.1.6

API Mocking and Virtualization
// Copyright (c) 2018 libdeadmock developers
//
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. All files in the project carrying such notice may not be copied,
// modified, or distributed except according to those terms.

//! `libdeadmock` request/response mapping
use crate::config::{Request, Response};
use getset::{Getters, MutGetters};
use serde_derive::{Deserialize, Serialize};
use std::cmp::{Ord, Ordering};
use std::fmt;

/// `libdeadmock` mapping configuration
#[derive(
    Clone, Debug, Default, Deserialize, Eq, Getters, Hash, MutGetters, PartialEq, Serialize,
)]
pub struct Mapping {
    /// The name of this mapping.
    #[get = "pub"]
    name: String,
    /// The priority of this mapping.  Lower takes priority over higher in the case of multiple matches.
    #[get = "pub"]
    priority: u8,
    /// The request matching configuration.
    #[get = "pub"]
    request: Request,
    /// The response configuration.
    #[get = "pub"]
    response: Response,
}

impl Ord for Mapping {
    fn cmp(&self, other: &Self) -> Ordering {
        self.priority.cmp(&other.priority)
    }
}

impl PartialOrd for Mapping {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl fmt::Display for Mapping {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let out = serde_json::to_string_pretty(self).map_err(|_| fmt::Error)?;
        writeln!(f);
        write!(f, "{}", out)
    }
}

#[cfg(test)]
crate mod test {
    use super::Mapping;
    use crate::config::request::test::{full_request, partial_request};
    use crate::config::response::test::{full_response, partial_response};
    use std::collections::BTreeMap;

    const EMPTY_MAPPING: &str = r#"{"name":"","priority":0,"request":{},"response":{}}"#;
    const PARTIAL_MAPPING: &str = r#"{"name":"Test","priority":10,"request":{"method":"GET","url":"http://a.url.com"},"response":{"status":200,"headers":[{"key":"Content-Type","value":"application/json"}],"proxy_base_url":"http://cdcproxy.kroger.com"}}"#;
    const FULL_MAPPING_JSON: &str = r#"{"name":"Test","priority":10,"request":{"method":"GET","method_pattern":"P.*","url":"http://a.url.com","url_pattern":".*jasonozias.*","headers":[{"key":"Content-Type","value":"application/json"}],"headers_pattern":[{"key":{"left":"Accept","right":null},"value":{"left":null,"right":"*"}},{"key":{"left":"Content-Type","right":null},"value":{"left":null,"right":"*"}}],"header":{"key":"Content-Type","value":"application/json"},"header_pattern":{"key":{"left":"Content-Type","right":null},"value":{"left":null,"right":"^application/.*"}}},"response":{"status":200,"headers":[{"key":"Content-Type","value":"application/json"}],"body_file_name":"test.json","proxy_base_url":"http://cdcproxy.kroger.com","additional_proxy_request_headers":[{"key":"Authorization","value":"Basic abcdef123"}]}}"#;
    const FULL_MAPPING_TOML: &str = r#"name = "Test"
priority = 10

[request]
method = "GET"
method_pattern = "P.*"
url = "http://a.url.com"
url_pattern = ".*jasonozias.*"

[[request.headers]]
key = "Content-Type"
value = "application/json"

[[request.headers_pattern]]
[request.headers_pattern.key]
left = "Accept"

[request.headers_pattern.value]
right = "*"

[[request.headers_pattern]]
[request.headers_pattern.key]
left = "Content-Type"

[request.headers_pattern.value]
right = "*"

[request.header]
key = "Content-Type"
value = "application/json"
[request.header_pattern.key]
left = "Content-Type"

[request.header_pattern.value]
right = "^application/.*"

[response]
body_file_name = "test.json"
proxy_base_url = "http://cdcproxy.kroger.com"
status = 200

[[response.additional_proxy_request_headers]]
key = "Authorization"
value = "Basic abcdef123"

[[response.headers]]
key = "Content-Type"
value = "application/json"
"#;
    const BAD_MAPPING_JSON: &str = r#"{"priority":"abc"}"#;

    crate fn partial_mapping() -> Mapping {
        let mut mapping = Mapping::default();
        mapping.name = "Test".to_string();
        mapping.priority = 10;
        mapping.request = partial_request();
        mapping.response = partial_response();
        mapping
    }

    fn full_mapping() -> Mapping {
        let mut mapping = partial_mapping();
        mapping.request = full_request();
        mapping.response = full_response();
        mapping
    }

    #[test]
    fn order() {
        let mut first = Mapping::default();
        first.priority = 5;

        let mut second = Mapping::default();
        second.priority = 1;

        let mut third = Mapping::default();
        third.priority = 3;

        let mut mappings = BTreeMap::new();
        if let Some(_value) = mappings.insert(first, "first") {
            assert!(false, "There was already an entry!");
        }
        if let Some(_value) = mappings.insert(second, "second") {
            assert!(false, "There was already an entry!");
        }
        if let Some(_value) = mappings.insert(third, "third") {
            assert!(false, "There was already an entry!");
        }

        let priorities: Vec<(u8, &str)> = mappings.iter().map(|(k, v)| (k.priority, *v)).collect();
        assert_eq!(priorities, vec![(1, "second"), (3, "third"), (5, "first")]);
    }

    #[test]
    fn serialize_empty_mapping() {
        if let Ok(serialized) = serde_json::to_string(&Mapping::default()) {
            assert_eq!(serialized, EMPTY_MAPPING);
        } else {
            assert!(false, "Serialization not expected to fail!");
        }
    }

    #[test]
    fn serialize_partial_mapping() {
        if let Ok(serialized) = serde_json::to_string(&partial_mapping()) {
            assert_eq!(serialized, PARTIAL_MAPPING);
        } else {
            assert!(false, "Serialization not expected to fail!");
        }
    }

    #[test]
    fn serialize_full_mapping_json() {
        if let Ok(serialized) = serde_json::to_string(&full_mapping()) {
            assert_eq!(serialized, FULL_MAPPING_JSON);
        } else {
            assert!(false, "Serialization not expected to fail!");
        }
    }

    #[test]
    fn serialize_full_mapping_toml() {
        match toml::Value::try_from(&full_mapping()) {
            Ok(serialized) => assert_eq!(format!("{}", serialized), FULL_MAPPING_TOML),
            Err(e) => assert!(false, e.to_string()),
        }
    }

    #[test]
    fn deserialize_empty_mapping() {
        if let Ok(deserialized) = serde_json::from_str::<Mapping>(EMPTY_MAPPING) {
            assert_eq!(deserialized, Mapping::default());
        } else {
            assert!(
                false,
                "Expected deserialization of string into Mapping to succeed!"
            );
        }
    }

    #[test]
    fn deserialize_partial_mapping() {
        if let Ok(deserialized) = serde_json::from_str::<Mapping>(PARTIAL_MAPPING) {
            assert_eq!(deserialized, partial_mapping());
        } else {
            assert!(
                false,
                "Expected deserialization of string into Mapping to succeed!"
            );
        }
    }

    #[test]
    fn deserialize_full_mapping_json() {
        if let Ok(deserialized) = serde_json::from_str::<Mapping>(FULL_MAPPING_JSON) {
            assert_eq!(deserialized, full_mapping());
        } else {
            assert!(
                false,
                "Expected deserialization of string into Mapping to succeed!"
            );
        }
    }

    #[test]
    fn deserialize_full_mapping_toml() {
        if let Ok(deserialized) = toml::from_str::<Mapping>(FULL_MAPPING_TOML) {
            assert_eq!(deserialized, full_mapping());
        } else {
            assert!(
                false,
                "Expected deserialization of string into Mapping to succeed!"
            );
        }
    }

    #[test]
    fn deserialize_bad_mapping() {
        assert!(
            serde_json::from_str::<Mapping>(BAD_MAPPING_JSON).is_err(),
            "Expected the deserialization to fail!"
        );
    }
}