1#[cfg(test)]
2mod tests;
3
4use crate::application::Application;
5use crate::header::Header;
6use crate::http::VERSION;
7use crate::mime_type::MimeType;
8use crate::range::Range;
9use crate::request::{METHOD, Request};
10use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
11use crate::server::{Address, ConnectionInfo};
12
13pub struct TestClient<A: Application> {
39 app: A,
40 connection: ConnectionInfo,
41}
42
43impl<A: Application> TestClient<A> {
44 pub fn new(app: A) -> Self {
47 TestClient {
48 app,
49 connection: ConnectionInfo {
50 client: Address { ip: "127.0.0.1".to_string(), port: 12345 },
51 server: Address { ip: "127.0.0.1".to_string(), port: 7878 },
52 request_size: 16000,
53 sni_hostname: None,
54 tls: false,
55 },
56 }
57 }
58
59 pub fn get(&self, path: &str) -> TestRequest<'_, A> {
61 TestRequest::new(METHOD.get.to_string(), path, self)
62 }
63
64 pub fn post(&self, path: &str) -> TestRequest<'_, A> {
66 TestRequest::new(METHOD.post.to_string(), path, self)
67 }
68
69 pub fn put(&self, path: &str) -> TestRequest<'_, A> {
71 TestRequest::new(METHOD.put.to_string(), path, self)
72 }
73
74 pub fn patch(&self, path: &str) -> TestRequest<'_, A> {
76 TestRequest::new(METHOD.patch.to_string(), path, self)
77 }
78
79 pub fn delete(&self, path: &str) -> TestRequest<'_, A> {
81 TestRequest::new(METHOD.delete.to_string(), path, self)
82 }
83
84 pub fn options(&self, path: &str) -> TestRequest<'_, A> {
86 TestRequest::new(METHOD.options.to_string(), path, self)
87 }
88}
89
90pub struct TestRequest<'a, A: Application> {
92 method: String,
93 path: String,
94 headers: Vec<Header>,
95 body: Vec<u8>,
96 client: &'a TestClient<A>,
97}
98
99impl<'a, A: Application> TestRequest<'a, A> {
100 fn new(method: String, path: &str, client: &'a TestClient<A>) -> Self {
101 TestRequest {
102 method,
103 path: path.to_string(),
104 headers: vec![],
105 body: Default::default(),
106 client,
107 }
108 }
109
110 pub fn header(mut self, name: &str, value: &str) -> Self {
112 self.headers.push(Header { name: name.to_string().into(), value: value.to_string().into() });
113 self
114 }
115
116 pub fn body_bytes(mut self, body: Vec<u8>) -> Self {
118 self.body = body;
119 self
120 }
121
122 pub fn body_text(mut self, text: &str) -> Self {
124 self.body = text.as_bytes().to_vec();
125 self
126 }
127
128 pub fn body_json(mut self, json: &str) -> Self {
131 self.headers.push(Header { name: Header::_CONTENT_TYPE.into(), value: "application/json".into() });
132 self.body = json.as_bytes().to_vec();
133 self
134 }
135
136 pub fn send(self) -> TestResponse {
138 let request = Request {
139 method: self.method,
140 request_uri: self.path,
141 http_version: VERSION.http_1_1.to_string(),
142 headers: self.headers,
143 body: self.body.into(),
144 };
145
146 let response = self.client.app.execute(&request, &self.client.connection)
147 .unwrap_or_else(|msg| {
148 let dummy = Request {
149 method: "GET".to_string(),
150 request_uri: "/".to_string(),
151 http_version: VERSION.http_1_1.to_string(),
152 headers: vec![],
153 body: Default::default(),
154 };
155 let header_list = Header::get_header_list(&dummy);
156 let body = msg.into_bytes();
157 let cr = Range::get_content_range(body, MimeType::TEXT_PLAIN.to_string());
158 Response::get_response(
159 STATUS_CODE_REASON_PHRASE.n500_internal_server_error,
160 Some(header_list),
161 Some(vec![cr]),
162 )
163 });
164
165 TestResponse::from_response(response)
166 }
167}
168
169pub struct TestResponse {
171 status: i16,
172 reason: String,
173 headers: Vec<Header>,
174 body: Vec<u8>,
175}
176
177impl TestResponse {
178 fn from_response(mut r: Response) -> Self {
179 let body: Vec<u8> = r.content_range_list.iter()
180 .flat_map(|cr| cr.body.iter().copied())
181 .collect();
182
183 if r.content_range_list.len() == 1 {
186 let content_range = r.content_range_list.get(0).unwrap();
187 r.headers.push(Header {
188 name: Header::_CONTENT_TYPE.into(),
189 value: content_range.content_type.to_string().into(),
190 });
191 if let Some(value) = crate::response::content_range_header_value(r.status_code, content_range) {
192 r.headers.push(Header {
193 name: Header::_CONTENT_RANGE.into(),
194 value: value.into(),
195 });
196 }
197 r.headers.push(Header {
198 name: Header::_CONTENT_LENGTH.into(),
199 value: content_range.body.len().to_string().into(),
200 });
201 }
202
203 TestResponse {
204 status: r.status_code,
205 reason: r.reason_phrase,
206 headers: r.headers,
207 body,
208 }
209 }
210
211 pub fn status(&self) -> i16 {
213 self.status
214 }
215
216 pub fn reason(&self) -> &str {
218 &self.reason
219 }
220
221 pub fn header(&self, name: &str) -> Option<&str> {
223 let lower = name.to_lowercase();
224 self.headers
225 .iter()
226 .find(|h| h.name.to_lowercase() == lower)
227 .map(|h| h.value.as_ref())
228 }
229
230 pub fn headers(&self) -> &[Header] {
232 &self.headers
233 }
234
235 pub fn body_bytes(&self) -> &[u8] {
237 &self.body
238 }
239
240 pub fn body_text(&self) -> &str {
242 std::str::from_utf8(&self.body).expect("response body is not valid UTF-8")
243 }
244
245 pub fn is_success(&self) -> bool {
247 (200..300).contains(&self.status)
248 }
249}