rust_web_server/test_client/
mod.rs1#[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> {
38 app: A,
39 connection: ConnectionInfo,
40}
41
42impl<A: Application> TestClient<A> {
43 pub fn new(app: A) -> Self {
46 TestClient {
47 app,
48 connection: ConnectionInfo {
49 client: Address { ip: "127.0.0.1".to_string(), port: 12345 },
50 server: Address { ip: "127.0.0.1".to_string(), port: 7878 },
51 request_size: 16000,
52 sni_hostname: None,
53 },
54 }
55 }
56
57 pub fn get(&self, path: &str) -> TestRequest<'_, A> {
59 TestRequest::new(METHOD.get.to_string(), path, self)
60 }
61
62 pub fn post(&self, path: &str) -> TestRequest<'_, A> {
64 TestRequest::new(METHOD.post.to_string(), path, self)
65 }
66
67 pub fn put(&self, path: &str) -> TestRequest<'_, A> {
69 TestRequest::new(METHOD.put.to_string(), path, self)
70 }
71
72 pub fn patch(&self, path: &str) -> TestRequest<'_, A> {
74 TestRequest::new(METHOD.patch.to_string(), path, self)
75 }
76
77 pub fn delete(&self, path: &str) -> TestRequest<'_, A> {
79 TestRequest::new(METHOD.delete.to_string(), path, self)
80 }
81
82 pub fn options(&self, path: &str) -> TestRequest<'_, A> {
84 TestRequest::new(METHOD.options.to_string(), path, self)
85 }
86}
87
88pub struct TestRequest<'a, A: Application> {
90 method: String,
91 path: String,
92 headers: Vec<Header>,
93 body: Vec<u8>,
94 client: &'a TestClient<A>,
95}
96
97impl<'a, A: Application> TestRequest<'a, A> {
98 fn new(method: String, path: &str, client: &'a TestClient<A>) -> Self {
99 TestRequest {
100 method,
101 path: path.to_string(),
102 headers: vec![],
103 body: vec![],
104 client,
105 }
106 }
107
108 pub fn header(mut self, name: &str, value: &str) -> Self {
110 self.headers.push(Header { name: name.to_string(), value: value.to_string() });
111 self
112 }
113
114 pub fn body_bytes(mut self, body: Vec<u8>) -> Self {
116 self.body = body;
117 self
118 }
119
120 pub fn body_text(mut self, text: &str) -> Self {
122 self.body = text.as_bytes().to_vec();
123 self
124 }
125
126 pub fn send(self) -> TestResponse {
128 let request = Request {
129 method: self.method,
130 request_uri: self.path,
131 http_version: VERSION.http_1_1.to_string(),
132 headers: self.headers,
133 body: self.body,
134 };
135
136 let response = self.client.app.execute(&request, &self.client.connection)
137 .unwrap_or_else(|msg| {
138 let dummy = Request {
139 method: "GET".to_string(),
140 request_uri: "/".to_string(),
141 http_version: VERSION.http_1_1.to_string(),
142 headers: vec![],
143 body: vec![],
144 };
145 let header_list = Header::get_header_list(&dummy);
146 let body = msg.into_bytes();
147 let cr = Range::get_content_range(body, MimeType::TEXT_PLAIN.to_string());
148 Response::get_response(
149 STATUS_CODE_REASON_PHRASE.n500_internal_server_error,
150 Some(header_list),
151 Some(vec![cr]),
152 )
153 });
154
155 TestResponse::from_response(response)
156 }
157}
158
159pub struct TestResponse {
161 status: i16,
162 reason: String,
163 headers: Vec<Header>,
164 body: Vec<u8>,
165}
166
167impl TestResponse {
168 fn from_response(mut r: Response) -> Self {
169 let body: Vec<u8> = r.content_range_list.iter()
170 .flat_map(|cr| cr.body.iter().copied())
171 .collect();
172
173 if r.content_range_list.len() == 1 {
176 let content_range = r.content_range_list.get(0).unwrap();
177 r.headers.push(Header {
178 name: Header::_CONTENT_TYPE.to_string(),
179 value: content_range.content_type.to_string(),
180 });
181 if let Some(value) = crate::response::content_range_header_value(r.status_code, content_range) {
182 r.headers.push(Header {
183 name: Header::_CONTENT_RANGE.to_string(),
184 value,
185 });
186 }
187 r.headers.push(Header {
188 name: Header::_CONTENT_LENGTH.to_string(),
189 value: content_range.body.len().to_string(),
190 });
191 }
192
193 TestResponse {
194 status: r.status_code,
195 reason: r.reason_phrase,
196 headers: r.headers,
197 body,
198 }
199 }
200
201 pub fn status(&self) -> i16 {
203 self.status
204 }
205
206 pub fn reason(&self) -> &str {
208 &self.reason
209 }
210
211 pub fn header(&self, name: &str) -> Option<&str> {
213 let lower = name.to_lowercase();
214 self.headers
215 .iter()
216 .find(|h| h.name.to_lowercase() == lower)
217 .map(|h| h.value.as_str())
218 }
219
220 pub fn headers(&self) -> &[Header] {
222 &self.headers
223 }
224
225 pub fn body_bytes(&self) -> &[u8] {
227 &self.body
228 }
229
230 pub fn body_text(&self) -> &str {
232 std::str::from_utf8(&self.body).expect("response body is not valid UTF-8")
233 }
234
235 pub fn is_success(&self) -> bool {
237 (200..300).contains(&self.status)
238 }
239}