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};
12use crate::symbol::SYMBOL;
13
14pub 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 },
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
83pub struct TestRequest<'a, A: Application> {
85 method: String,
86 path: String,
87 headers: Vec<Header>,
88 body: Vec<u8>,
89 client: &'a TestClient<A>,
90}
91
92impl<'a, A: Application> TestRequest<'a, A> {
93 fn new(method: String, path: &str, client: &'a TestClient<A>) -> Self {
94 TestRequest {
95 method,
96 path: path.to_string(),
97 headers: vec![],
98 body: vec![],
99 client,
100 }
101 }
102
103 pub fn header(mut self, name: &str, value: &str) -> Self {
105 self.headers.push(Header { name: name.to_string(), value: value.to_string() });
106 self
107 }
108
109 pub fn body_bytes(mut self, body: Vec<u8>) -> Self {
111 self.body = body;
112 self
113 }
114
115 pub fn body_text(mut self, text: &str) -> Self {
117 self.body = text.as_bytes().to_vec();
118 self
119 }
120
121 pub fn send(self) -> TestResponse {
123 let request = Request {
124 method: self.method,
125 request_uri: self.path,
126 http_version: VERSION.http_1_1.to_string(),
127 headers: self.headers,
128 body: self.body,
129 };
130
131 let response = self.client.app.execute(&request, &self.client.connection)
132 .unwrap_or_else(|msg| {
133 let dummy = Request {
134 method: "GET".to_string(),
135 request_uri: "/".to_string(),
136 http_version: VERSION.http_1_1.to_string(),
137 headers: vec![],
138 body: vec![],
139 };
140 let header_list = Header::get_header_list(&dummy);
141 let body = msg.into_bytes();
142 let cr = Range::get_content_range(body, MimeType::TEXT_PLAIN.to_string());
143 Response::get_response(
144 STATUS_CODE_REASON_PHRASE.n500_internal_server_error,
145 Some(header_list),
146 Some(vec![cr]),
147 )
148 });
149
150 TestResponse::from_response(response)
151 }
152}
153
154pub struct TestResponse {
156 status: i16,
157 reason: String,
158 headers: Vec<Header>,
159 body: Vec<u8>,
160}
161
162impl TestResponse {
163 fn from_response(mut r: Response) -> Self {
164 let body: Vec<u8> = r.content_range_list.iter()
165 .flat_map(|cr| cr.body.iter().copied())
166 .collect();
167
168 if r.content_range_list.len() == 1 {
171 let content_range = r.content_range_list.get(0).unwrap();
172 r.headers.push(Header {
173 name: Header::_CONTENT_TYPE.to_string(),
174 value: content_range.content_type.to_string(),
175 });
176 let content_range_header_value = [
177 Range::BYTES,
178 SYMBOL.whitespace,
179 &content_range.range.start.to_string(),
180 SYMBOL.hyphen,
181 &content_range.range.end.to_string(),
182 SYMBOL.slash,
183 &content_range.size,
184 ].join("");
185 r.headers.push(Header {
186 name: Header::_CONTENT_RANGE.to_string(),
187 value: content_range_header_value,
188 });
189 r.headers.push(Header {
190 name: Header::_CONTENT_LENGTH.to_string(),
191 value: content_range.body.len().to_string(),
192 });
193 }
194
195 TestResponse {
196 status: r.status_code,
197 reason: r.reason_phrase,
198 headers: r.headers,
199 body,
200 }
201 }
202
203 pub fn status(&self) -> i16 {
205 self.status
206 }
207
208 pub fn reason(&self) -> &str {
210 &self.reason
211 }
212
213 pub fn header(&self, name: &str) -> Option<&str> {
215 let lower = name.to_lowercase();
216 self.headers
217 .iter()
218 .find(|h| h.name.to_lowercase() == lower)
219 .map(|h| h.value.as_str())
220 }
221
222 pub fn headers(&self) -> &[Header] {
224 &self.headers
225 }
226
227 pub fn body_bytes(&self) -> &[u8] {
229 &self.body
230 }
231
232 pub fn body_text(&self) -> &str {
234 std::str::from_utf8(&self.body).expect("response body is not valid UTF-8")
235 }
236
237 pub fn is_success(&self) -> bool {
239 (200..300).contains(&self.status)
240 }
241}