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 tls: false,
54 },
55 }
56 }
57
58 pub fn get(&self, path: &str) -> TestRequest<'_, A> {
60 TestRequest::new(METHOD.get.to_string(), path, self)
61 }
62
63 pub fn post(&self, path: &str) -> TestRequest<'_, A> {
65 TestRequest::new(METHOD.post.to_string(), path, self)
66 }
67
68 pub fn put(&self, path: &str) -> TestRequest<'_, A> {
70 TestRequest::new(METHOD.put.to_string(), path, self)
71 }
72
73 pub fn patch(&self, path: &str) -> TestRequest<'_, A> {
75 TestRequest::new(METHOD.patch.to_string(), path, self)
76 }
77
78 pub fn delete(&self, path: &str) -> TestRequest<'_, A> {
80 TestRequest::new(METHOD.delete.to_string(), path, self)
81 }
82
83 pub fn options(&self, path: &str) -> TestRequest<'_, A> {
85 TestRequest::new(METHOD.options.to_string(), path, self)
86 }
87}
88
89pub struct TestRequest<'a, A: Application> {
91 method: String,
92 path: String,
93 headers: Vec<Header>,
94 body: Vec<u8>,
95 client: &'a TestClient<A>,
96}
97
98impl<'a, A: Application> TestRequest<'a, A> {
99 fn new(method: String, path: &str, client: &'a TestClient<A>) -> Self {
100 TestRequest {
101 method,
102 path: path.to_string(),
103 headers: vec![],
104 body: Default::default(),
105 client,
106 }
107 }
108
109 pub fn header(mut self, name: &str, value: &str) -> Self {
111 self.headers.push(Header { name: name.to_string().into(), value: value.to_string().into() });
112 self
113 }
114
115 pub fn body_bytes(mut self, body: Vec<u8>) -> Self {
117 self.body = body;
118 self
119 }
120
121 pub fn body_text(mut self, text: &str) -> Self {
123 self.body = text.as_bytes().to_vec();
124 self
125 }
126
127 pub fn body_json(mut self, json: &str) -> Self {
130 self.headers.push(Header { name: Header::_CONTENT_TYPE.into(), value: "application/json".into() });
131 self.body = json.as_bytes().to_vec();
132 self
133 }
134
135 pub fn send(self) -> TestResponse {
137 let request = Request {
138 method: self.method,
139 request_uri: self.path,
140 http_version: VERSION.http_1_1.to_string(),
141 headers: self.headers,
142 body: self.body.into(),
143 };
144
145 let response = self.client.app.execute(&request, &self.client.connection)
146 .unwrap_or_else(|msg| {
147 let dummy = Request {
148 method: "GET".to_string(),
149 request_uri: "/".to_string(),
150 http_version: VERSION.http_1_1.to_string(),
151 headers: vec![],
152 body: Default::default(),
153 };
154 let header_list = Header::get_header_list(&dummy);
155 let body = msg.into_bytes();
156 let cr = Range::get_content_range(body, MimeType::TEXT_PLAIN.to_string());
157 Response::get_response(
158 STATUS_CODE_REASON_PHRASE.n500_internal_server_error,
159 Some(header_list),
160 Some(vec![cr]),
161 )
162 });
163
164 TestResponse::from_response(response)
165 }
166}
167
168pub struct TestResponse {
170 status: i16,
171 reason: String,
172 headers: Vec<Header>,
173 body: Vec<u8>,
174}
175
176impl TestResponse {
177 fn from_response(mut r: Response) -> Self {
178 let body: Vec<u8> = r.content_range_list.iter()
179 .flat_map(|cr| cr.body.iter().copied())
180 .collect();
181
182 if r.content_range_list.len() == 1 {
185 let content_range = r.content_range_list.get(0).unwrap();
186 r.headers.push(Header {
187 name: Header::_CONTENT_TYPE.into(),
188 value: content_range.content_type.to_string().into(),
189 });
190 if let Some(value) = crate::response::content_range_header_value(r.status_code, content_range) {
191 r.headers.push(Header {
192 name: Header::_CONTENT_RANGE.into(),
193 value: value.into(),
194 });
195 }
196 r.headers.push(Header {
197 name: Header::_CONTENT_LENGTH.into(),
198 value: content_range.body.len().to_string().into(),
199 });
200 }
201
202 TestResponse {
203 status: r.status_code,
204 reason: r.reason_phrase,
205 headers: r.headers,
206 body,
207 }
208 }
209
210 pub fn status(&self) -> i16 {
212 self.status
213 }
214
215 pub fn reason(&self) -> &str {
217 &self.reason
218 }
219
220 pub fn header(&self, name: &str) -> Option<&str> {
222 let lower = name.to_lowercase();
223 self.headers
224 .iter()
225 .find(|h| h.name.to_lowercase() == lower)
226 .map(|h| h.value.as_ref())
227 }
228
229 pub fn headers(&self) -> &[Header] {
231 &self.headers
232 }
233
234 pub fn body_bytes(&self) -> &[u8] {
236 &self.body
237 }
238
239 pub fn body_text(&self) -> &str {
241 std::str::from_utf8(&self.body).expect("response body is not valid UTF-8")
242 }
243
244 pub fn is_success(&self) -> bool {
246 (200..300).contains(&self.status)
247 }
248}