1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
pub use mockito;
pub use serde::Deserialize;
pub use serde_yaml;

#[derive(Deserialize)]
pub struct MockDefinition {
    pub request: MockRequest,
    pub response: MockResponse,
}

#[derive(Deserialize)]
pub struct MockRequest {
    pub method: String,
    pub path: Option<String>,
    pub query: Option<Vec<MockQuery>>,
    pub headers: Option<Vec<MockHeader>>,
}

#[derive(Deserialize)]
pub struct MockQuery {
    pub parameter: String,
    pub value: String,
    pub regex: Option<bool>,
}

#[derive(Deserialize)]
pub struct MockHeader {
    pub header: String,
    pub value: String,
}

#[derive(Deserialize)]
pub struct MockResponse {
    pub headers: Option<Vec<MockHeader>>,
    pub body: Option<String>,
}

/// Generate mockito mocks using declarative (yml) definition.
#[macro_export]
macro_rules! mock_server {
    ($file:expr) => {{
        use std::path::Path;
        use $crate::mockito::{mock, Matcher};
        use $crate::serde_yaml;
        use $crate::*;

        let mocks_path = Path::new(env!("CARGO_MANIFEST_DIR")).join($file);

        let file = std::fs::read(&mocks_path).expect("definition file's not found!");

        let definitions: Vec<MockDefinition> =
            serde_yaml::from_slice(&file).expect("failed to parse definition file");

        let mocks = definitions
            .into_iter()
            .map(|MockDefinition { request, response }| {
                let path = request.path.map(Matcher::Regex).unwrap_or(Matcher::Any);

                let mut mock = mock(&request.method, path);

                if let Some(headers) = request.headers {
                    for MockHeader { header, value } in headers {
                        mock = mock.match_header(&header, &value[..]);
                    }
                }

                if let Some(query_params) = request.query {
                    for MockQuery {
                        parameter,
                        value,
                        regex,
                    } in query_params
                    {
                        if let Some(true) = regex {
                            mock = mock
                                .match_query(Matcher::Regex(format!("{}={}", parameter, value)));
                        } else {
                            mock = mock
                                .match_query(Matcher::UrlEncoded(parameter.into(), value.into()));
                        }
                    }
                }

                if let Some(body) = response.body {
                    mock = mock.with_body_from_file(
                        mocks_path
                            .parent()
                            .expect("couldn't extract file parent")
                            .join(body),
                    );
                }

                if let Some(headers) = response.headers {
                    for MockHeader { header, value } in headers {
                        let value = value.replace("SERVER_URL", &mockito::server_url());

                        mock = mock.with_header(&header, &value[..]);
                    }
                }

                mock.create()
            })
            .collect::<Vec<_>>();

        (mockito::server_url(), mocks)
    }};
}

#[cfg(test)]
mod test {
    #[tokio::test]
    async fn test_regex_matching() {
        let (server, _mocks) = super::mock_server!("test/basic.yml");

        let client = reqwest::Client::new();

        let resp = client
            .get(format!("{}/{}", server, "v2/hash/manifests/hash/"))
            .header(
                "Accept",
                "application/vnd.docker.distribution.manifest.v2+json",
            )
            .send()
            .await
            .expect("failed to make a request")
            .json::<serde_json::Value>()
            .await
            .expect("failed to parse the response");

        assert_eq!(resp["config"]["size"], 6668);
    }

    #[tokio::test]
    async fn test_query_regex_matching() {
        let (server, _mocks) = super::mock_server!("test/basic.yml");

        let client = reqwest::Client::new();

        let resp = client
            .get(format!("{}/{}", server, "somesite"))
            .query(&[("arguments", "lorem, ipsum, dolor")])
            .header(
                "Accept",
                "application/vnd.docker.distribution.manifest.v2+json",
            )
            .send()
            .await
            .expect("failed to make a request")
            .text()
            .await
            .expect("failed to parse the response");

        assert_eq!(resp, "Ich bin nicht gut fuer dich");
    }
}