gitignore_template_generator/http_client/
api.rs

1use crate::ProgramExit;
2pub use crate::http_client::impls::{
3    MockEndpointHttpClient, MockHttpClient, UreqHttpClient,
4};
5
6/// Http client trait to make HTTP calls.
7pub trait HttpClient {
8    /// Make a GET HTTP call to given url.
9    ///
10    /// # Arguments
11    /// * `url` - the url to which to make the HTTP call.
12    ///
13    /// # Returns
14    ///
15    /// A result containing the response body of HTTP call if successful, or
16    /// a [`ProgramExit`] on error (e.g. 4xx, network issues...).
17    fn get(&self, url: &str) -> Result<String, ProgramExit>;
18}
19
20#[cfg(test)]
21mod tests {
22    use mockito::Server;
23
24    use super::*;
25    use crate::{ExitKind, constant};
26
27    mod ureq_client {
28        use super::*;
29
30        mod get {
31            use super::*;
32
33            mod success {
34                use super::*;
35
36                #[test]
37                fn it_fetches_data_as_string_when_200_response() {
38                    let mut mock_server = Server::new();
39                    let mock_body = "gitignore template for rust";
40                    let mock_uri = "/api/rust";
41                    let mock = mock_server
42                        .mock("GET", mock_uri)
43                        .with_status(200)
44                        .with_body(mock_body)
45                        .create();
46
47                    let server_url = mock_server.url();
48                    let http_client = UreqHttpClient::default();
49
50                    let actual =
51                        http_client.get(&format!("{server_url}{mock_uri}"));
52                    let expected: Result<String, ProgramExit> =
53                        Ok(String::from(mock_body));
54
55                    mock.assert();
56                    assert_eq!(actual, expected);
57                }
58
59                #[test]
60                fn it_fetches_data_as_string_from_given_server_url_when_200() {
61                    let mut mock_server = Server::new();
62                    let mock_body = "gitignore template for rust";
63                    let mock_uri = "/api/rust";
64                    let mock = mock_server
65                        .mock("GET", mock_uri)
66                        .with_status(200)
67                        .with_body(mock_body)
68                        .create();
69
70                    let server_url = mock_server.url();
71                    let http_client = UreqHttpClient { server_url };
72
73                    let actual = http_client.get(mock_uri);
74                    let expected: Result<String, ProgramExit> =
75                        Ok(String::from(mock_body));
76
77                    mock.assert();
78                    assert_eq!(actual, expected);
79                }
80            }
81
82            mod failure {
83                use super::*;
84
85                #[test]
86                fn it_fails_with_api_call_error_when_400_response() {
87                    let mut mock_server = Server::new();
88                    let mock_body = "error response";
89                    let mock_uri = "/api/rust";
90                    let mock = mock_server
91                        .mock("GET", mock_uri)
92                        .with_status(400)
93                        .with_body(mock_body)
94                        .create();
95
96                    let server_url = mock_server.url();
97                    let http_client = UreqHttpClient::default();
98
99                    let actual =
100                        http_client.get(&format!("{server_url}{mock_uri}"));
101                    let expected: Result<String, ProgramExit> =
102                        Err(ProgramExit {
103                            message: constant::error_messages::API_CALL_FAILURE
104                                .replace(
105                                    "{error}",
106                                    constant::error_messages::HTTP_400,
107                                ),
108                            exit_status: constant::exit_status::GENERIC,
109                            styled_message: None,
110                            kind: ExitKind::Error,
111                        });
112
113                    mock.assert();
114                    assert_eq!(actual, expected);
115                }
116
117                #[test]
118                fn it_fails_with_body_parsing_error_when_invalid_body() {
119                    let mut mock_server = Server::new();
120                    let mock_body = vec![0, 159, 146, 150];
121                    let mock_uri = "/api/rust";
122                    let mock = mock_server
123                        .mock("GET", mock_uri)
124                        .with_status(200)
125                        .with_body(mock_body)
126                        .create();
127
128                    let server_url = mock_server.url();
129                    let http_client = UreqHttpClient::default();
130
131                    let actual =
132                        http_client.get(&format!("{server_url}{mock_uri}"));
133                    let expected: Result<String, ProgramExit> =
134                        Err(ProgramExit {
135                            message: String::from(
136                                constant::error_messages::BODY_PARSING_ISSUE,
137                            ),
138                            exit_status:
139                                constant::exit_status::BODY_PARSING_ISSUE,
140                            styled_message: None,
141                            kind: ExitKind::Error,
142                        });
143
144                    mock.assert();
145                    assert_eq!(actual, expected);
146                }
147            }
148        }
149    }
150
151    mod mock_client {
152        use super::*;
153
154        mod get {
155            use super::*;
156
157            mod success {
158                use super::*;
159
160                #[test]
161                fn it_returns_ok_mocked_response() {
162                    let result_content = "success response";
163                    let http_client = MockHttpClient {
164                        response: Ok(String::from(result_content)),
165                    };
166
167                    let actual = http_client.get("/api/rust");
168                    let expected: Result<String, ProgramExit> =
169                        Ok(String::from(result_content));
170
171                    assert_eq!(actual, expected);
172                }
173
174                #[test]
175                fn it_returns_error_mocked_response() {
176                    let result_content = "error response";
177                    let http_client = MockHttpClient {
178                        response: Err(ProgramExit {
179                            message: String::from(result_content),
180                            exit_status: constant::exit_status::GENERIC,
181                            styled_message: None,
182                            kind: ExitKind::Error,
183                        }),
184                    };
185
186                    let actual = http_client.get("/api/rust");
187                    let expected: Result<String, ProgramExit> =
188                        Err(ProgramExit {
189                            message: String::from(result_content),
190                            exit_status: constant::exit_status::GENERIC,
191                            styled_message: None,
192                            kind: ExitKind::Error,
193                        });
194
195                    assert_eq!(actual, expected);
196                }
197            }
198        }
199    }
200
201    mod mock_endpoint_http_client {
202        use super::*;
203
204        mod get {
205            use super::*;
206
207            mod success {
208                use std::collections::HashMap;
209
210                use super::*;
211
212                #[test]
213                fn it_returns_ok_mocked_response_for_given_url() {
214                    let rust_result_content = "success rust response";
215                    let python_result_content = "success python response";
216                    let http_client = MockEndpointHttpClient {
217                        response: HashMap::from([
218                            (
219                                "/api/rust",
220                                Ok(String::from(rust_result_content)),
221                            ),
222                            (
223                                "/api/python",
224                                Ok(String::from(python_result_content)),
225                            ),
226                        ]),
227                    };
228
229                    let actual = http_client.get("/api/rust");
230                    let expected: Result<String, ProgramExit> =
231                        Ok(String::from(rust_result_content));
232
233                    assert_eq!(actual, expected);
234                }
235
236                #[test]
237                fn it_returns_error_mocked_response_for_given_url() {
238                    let rust_result_content = "success rust response";
239                    let python_result_content = "success python response";
240                    let http_client = MockEndpointHttpClient {
241                        response: HashMap::from([
242                            (
243                                "/api/rust",
244                                Err(ProgramExit {
245                                    message: String::from(rust_result_content),
246                                    exit_status: constant::exit_status::GENERIC,
247                                    styled_message: None,
248                                    kind: ExitKind::Error,
249                                }),
250                            ),
251                            (
252                                "/api/python",
253                                Err(ProgramExit {
254                                    message: String::from(
255                                        python_result_content,
256                                    ),
257                                    exit_status: constant::exit_status::GENERIC,
258                                    styled_message: None,
259                                    kind: ExitKind::Error,
260                                }),
261                            ),
262                        ]),
263                    };
264
265                    let actual = http_client.get("/api/python");
266                    let expected: Result<String, ProgramExit> =
267                        Err(ProgramExit {
268                            message: String::from(python_result_content),
269                            exit_status: constant::exit_status::GENERIC,
270                            styled_message: None,
271                            kind: ExitKind::Error,
272                        });
273
274                    assert_eq!(actual, expected);
275                }
276            }
277        }
278    }
279}