1use std::{borrow::Cow, net::SocketAddr, rc::Rc};
2
3use actix_http::{test::TestRequest as HttpTestRequest, Request};
4use serde::Serialize;
5
6#[cfg(feature = "cookies")]
7use crate::cookie::{Cookie, CookieJar};
8use crate::{
9 app_service::AppInitServiceState,
10 config::AppConfig,
11 data::Data,
12 dev::{Extensions, Path, Payload, ResourceDef, Service, Url},
13 http::{
14 header::{ContentType, TryIntoHeaderPair},
15 Method, Uri, Version,
16 },
17 rmap::ResourceMap,
18 service::{ServiceRequest, ServiceResponse},
19 test,
20 web::Bytes,
21 HttpRequest, HttpResponse,
22};
23
24pub struct TestRequest {
62 req: HttpTestRequest,
63 rmap: ResourceMap,
64 config: AppConfig,
65 path: Path<Url>,
66 peer_addr: Option<SocketAddr>,
67 app_data: Extensions,
68 #[cfg(feature = "cookies")]
69 cookies: CookieJar,
70}
71
72impl Default for TestRequest {
73 fn default() -> TestRequest {
74 TestRequest {
75 req: HttpTestRequest::default(),
76 rmap: ResourceMap::new(ResourceDef::new("")),
77 config: AppConfig::default(),
78 path: Path::new(Url::new(Uri::default())),
79 peer_addr: None,
80 app_data: Extensions::new(),
81 #[cfg(feature = "cookies")]
82 cookies: CookieJar::new(),
83 }
84 }
85}
86
87#[allow(clippy::wrong_self_convention)]
88impl TestRequest {
89 pub fn with_uri(uri: &str) -> TestRequest {
91 TestRequest::default().uri(uri)
92 }
93
94 pub fn get() -> TestRequest {
96 TestRequest::default().method(Method::GET)
97 }
98
99 pub fn post() -> TestRequest {
101 TestRequest::default().method(Method::POST)
102 }
103
104 pub fn put() -> TestRequest {
106 TestRequest::default().method(Method::PUT)
107 }
108
109 pub fn patch() -> TestRequest {
111 TestRequest::default().method(Method::PATCH)
112 }
113
114 pub fn query() -> TestRequest {
117 TestRequest::default().method(Method::from_bytes(b"QUERY").unwrap())
118 }
119
120 pub fn delete() -> TestRequest {
122 TestRequest::default().method(Method::DELETE)
123 }
124
125 pub fn version(mut self, ver: Version) -> Self {
127 self.req.version(ver);
128 self
129 }
130
131 pub fn method(mut self, meth: Method) -> Self {
133 self.req.method(meth);
134 self
135 }
136
137 pub fn uri(mut self, path: &str) -> Self {
139 self.req.uri(path);
140 self
141 }
142
143 pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self {
145 self.req.insert_header(header);
146 self
147 }
148
149 pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self {
151 self.req.append_header(header);
152 self
153 }
154
155 #[cfg(feature = "cookies")]
157 pub fn cookie(mut self, cookie: Cookie<'_>) -> Self {
158 self.cookies.add(cookie.into_owned());
159 self
160 }
161
162 pub fn param(
173 mut self,
174 name: impl Into<Cow<'static, str>>,
175 value: impl Into<Cow<'static, str>>,
176 ) -> Self {
177 self.path.add_static(name, value);
178 self
179 }
180
181 pub fn peer_addr(mut self, addr: SocketAddr) -> Self {
183 self.peer_addr = Some(addr);
184 self
185 }
186
187 pub fn set_payload(mut self, data: impl Into<Bytes>) -> Self {
189 self.req.set_payload(data);
190 self
191 }
192
193 pub fn set_form(mut self, data: impl Serialize) -> Self {
197 let bytes = serde_urlencoded::to_string(&data)
198 .expect("Failed to serialize test data as a urlencoded form");
199 self.req.set_payload(bytes);
200 self.req.insert_header(ContentType::form_url_encoded());
201 self
202 }
203
204 pub fn set_json(mut self, data: impl Serialize) -> Self {
208 let bytes = serde_json::to_string(&data).expect("Failed to serialize test data to json");
209 self.req.set_payload(bytes);
210 self.req.insert_header(ContentType::json());
211 self
212 }
213
214 pub fn app_data<T: 'static>(mut self, data: T) -> Self {
218 self.app_data.insert(data);
219 self
220 }
221
222 #[doc(hidden)]
226 pub fn data<T: 'static>(mut self, data: T) -> Self {
227 self.app_data.insert(Data::new(data));
228 self
229 }
230
231 #[cfg(test)]
233 pub(crate) fn rmap(mut self, rmap: ResourceMap) -> Self {
234 self.rmap = rmap;
235 self
236 }
237
238 fn finish(&mut self) -> Request {
242 #[allow(unused_mut)]
244 let mut req = self.req.finish();
245
246 #[cfg(feature = "cookies")]
247 {
248 use actix_http::header::{HeaderValue, COOKIE};
249
250 let cookie: String = self
251 .cookies
252 .delta()
253 .map(|c| c.stripped().encoded().to_string())
255 .collect::<Vec<_>>()
256 .join("; ");
257
258 if !cookie.is_empty() {
259 req.headers_mut()
260 .insert(COOKIE, HeaderValue::from_str(&cookie).unwrap());
261 }
262 }
263
264 req
265 }
266
267 pub fn to_request(mut self) -> Request {
269 let mut req = self.finish();
270 req.head_mut().peer_addr = self.peer_addr;
271 req
272 }
273
274 pub fn to_srv_request(mut self) -> ServiceRequest {
276 let (mut head, payload) = self.finish().into_parts();
277 head.peer_addr = self.peer_addr;
278 self.path.get_mut().update(&head.uri);
279
280 let app_state =
281 AppInitServiceState::new_without_pool(Rc::new(self.rmap), self.config.clone());
282
283 ServiceRequest::new(
284 HttpRequest::new(
285 self.path,
286 head,
287 app_state,
288 Rc::new(self.app_data),
289 None,
290 Default::default(),
291 ),
292 payload,
293 )
294 }
295
296 pub fn to_srv_response<B>(self, res: HttpResponse<B>) -> ServiceResponse<B> {
298 self.to_srv_request().into_response(res)
299 }
300
301 pub fn to_http_request(mut self) -> HttpRequest {
303 let (mut head, _) = self.finish().into_parts();
304 head.peer_addr = self.peer_addr;
305 self.path.get_mut().update(&head.uri);
306
307 let app_state =
308 AppInitServiceState::new_without_pool(Rc::new(self.rmap), self.config.clone());
309
310 HttpRequest::new(
311 self.path,
312 head,
313 app_state,
314 Rc::new(self.app_data),
315 None,
316 Default::default(),
317 )
318 }
319
320 pub fn to_http_parts(mut self) -> (HttpRequest, Payload) {
322 let (mut head, payload) = self.finish().into_parts();
323 head.peer_addr = self.peer_addr;
324 self.path.get_mut().update(&head.uri);
325
326 let app_state =
327 AppInitServiceState::new_without_pool(Rc::new(self.rmap), self.config.clone());
328
329 let req = HttpRequest::new(
330 self.path,
331 head,
332 app_state,
333 Rc::new(self.app_data),
334 None,
335 Default::default(),
336 );
337
338 (req, payload)
339 }
340
341 pub async fn send_request<S, B, E>(self, app: &S) -> S::Response
343 where
344 S: Service<Request, Response = ServiceResponse<B>, Error = E>,
345 E: std::fmt::Debug,
346 {
347 let req = self.to_request();
348 test::call_service(app, req).await
349 }
350
351 #[cfg(test)]
352 pub fn set_server_hostname(&mut self, host: &str) {
353 self.config.set_host(host)
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use std::{sync::Arc, time::SystemTime};
360
361 use super::*;
362 use crate::{http::header, test::init_service, web, App, Error, Responder};
363
364 #[actix_rt::test]
365 async fn test_request_app_data_is_dropped() {
366 fn assert_dropped(make_request: impl FnOnce(TestRequest)) {
367 let data = Arc::new(());
368 let weak = Arc::downgrade(&data);
369 make_request(TestRequest::default().app_data(Data::new(data)));
370 assert!(weak.upgrade().is_none());
371 }
372
373 assert_dropped(|req| drop(req.to_srv_request()));
374 assert_dropped(|req| drop(req.to_http_request()));
375 assert_dropped(|req| drop(req.to_http_parts()));
376 }
377
378 #[actix_rt::test]
379 async fn test_basics() {
380 let req = TestRequest::default()
381 .version(Version::HTTP_2)
382 .insert_header(header::ContentType::json())
383 .insert_header(header::Date(SystemTime::now().into()))
384 .param("test", "123")
385 .data(10u32)
386 .app_data(20u64)
387 .peer_addr("127.0.0.1:8081".parse().unwrap())
388 .to_http_request();
389 assert!(req.headers().contains_key(header::CONTENT_TYPE));
390 assert!(req.headers().contains_key(header::DATE));
391 assert_eq!(
392 req.head().peer_addr,
393 Some("127.0.0.1:8081".parse().unwrap())
394 );
395 assert_eq!(&req.match_info()["test"], "123");
396 assert_eq!(req.version(), Version::HTTP_2);
397 let data = req.app_data::<Data<u32>>().unwrap();
398 assert!(req.app_data::<Data<u64>>().is_none());
399 assert_eq!(*data.get_ref(), 10);
400
401 assert!(req.app_data::<u32>().is_none());
402 let data = req.app_data::<u64>().unwrap();
403 assert_eq!(*data, 20);
404 }
405
406 #[actix_rt::test]
407 async fn test_send_request() {
408 let app = init_service(
409 App::new().service(
410 web::resource("/index.html")
411 .route(web::get().to(|| HttpResponse::Ok().body("welcome!"))),
412 ),
413 )
414 .await;
415
416 let resp = TestRequest::get()
417 .uri("/index.html")
418 .send_request(&app)
419 .await;
420
421 let result = test::read_body(resp).await;
422 assert_eq!(result, Bytes::from_static(b"welcome!"));
423 }
424
425 #[actix_rt::test]
426 async fn test_async_with_block() {
427 async fn async_with_block() -> Result<HttpResponse, Error> {
428 let res = web::block(move || Some(4usize).ok_or("wrong")).await;
429
430 match res {
431 Ok(value) => Ok(HttpResponse::Ok()
432 .content_type("text/plain")
433 .body(format!("Async with block value: {:?}", value))),
434 Err(_) => panic!("Unexpected"),
435 }
436 }
437
438 let app =
439 init_service(App::new().service(web::resource("/index.html").to(async_with_block)))
440 .await;
441
442 let req = TestRequest::post().uri("/index.html").to_request();
443 let res = app.call(req).await.unwrap();
444 assert!(res.status().is_success());
445 }
446
447 #[allow(deprecated)]
449 #[actix_rt::test]
450 async fn test_server_data() {
451 async fn handler(data: web::Data<usize>) -> impl Responder {
452 assert_eq!(**data, 10);
453 HttpResponse::Ok()
454 }
455
456 let app = init_service(
457 App::new()
458 .data(10usize)
459 .service(web::resource("/index.html").to(handler)),
460 )
461 .await;
462
463 let req = TestRequest::post().uri("/index.html").to_request();
464 let res = app.call(req).await.unwrap();
465 assert!(res.status().is_success());
466 }
467}