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 delete() -> TestRequest {
116 TestRequest::default().method(Method::DELETE)
117 }
118
119 pub fn version(mut self, ver: Version) -> Self {
121 self.req.version(ver);
122 self
123 }
124
125 pub fn method(mut self, meth: Method) -> Self {
127 self.req.method(meth);
128 self
129 }
130
131 pub fn uri(mut self, path: &str) -> Self {
133 self.req.uri(path);
134 self
135 }
136
137 pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self {
139 self.req.insert_header(header);
140 self
141 }
142
143 pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self {
145 self.req.append_header(header);
146 self
147 }
148
149 #[cfg(feature = "cookies")]
151 pub fn cookie(mut self, cookie: Cookie<'_>) -> Self {
152 self.cookies.add(cookie.into_owned());
153 self
154 }
155
156 pub fn param(
167 mut self,
168 name: impl Into<Cow<'static, str>>,
169 value: impl Into<Cow<'static, str>>,
170 ) -> Self {
171 self.path.add_static(name, value);
172 self
173 }
174
175 pub fn peer_addr(mut self, addr: SocketAddr) -> Self {
177 self.peer_addr = Some(addr);
178 self
179 }
180
181 pub fn set_payload(mut self, data: impl Into<Bytes>) -> Self {
183 self.req.set_payload(data);
184 self
185 }
186
187 pub fn set_form(mut self, data: impl Serialize) -> Self {
191 let bytes = serde_urlencoded::to_string(&data)
192 .expect("Failed to serialize test data as a urlencoded form");
193 self.req.set_payload(bytes);
194 self.req.insert_header(ContentType::form_url_encoded());
195 self
196 }
197
198 pub fn set_json(mut self, data: impl Serialize) -> Self {
202 let bytes = serde_json::to_string(&data).expect("Failed to serialize test data to json");
203 self.req.set_payload(bytes);
204 self.req.insert_header(ContentType::json());
205 self
206 }
207
208 pub fn app_data<T: 'static>(mut self, data: T) -> Self {
212 self.app_data.insert(data);
213 self
214 }
215
216 #[doc(hidden)]
220 pub fn data<T: 'static>(mut self, data: T) -> Self {
221 self.app_data.insert(Data::new(data));
222 self
223 }
224
225 #[cfg(test)]
227 pub(crate) fn rmap(mut self, rmap: ResourceMap) -> Self {
228 self.rmap = rmap;
229 self
230 }
231
232 fn finish(&mut self) -> Request {
236 #[allow(unused_mut)]
238 let mut req = self.req.finish();
239
240 #[cfg(feature = "cookies")]
241 {
242 use actix_http::header::{HeaderValue, COOKIE};
243
244 let cookie: String = self
245 .cookies
246 .delta()
247 .map(|c| c.stripped().encoded().to_string())
249 .collect::<Vec<_>>()
250 .join("; ");
251
252 if !cookie.is_empty() {
253 req.headers_mut()
254 .insert(COOKIE, HeaderValue::from_str(&cookie).unwrap());
255 }
256 }
257
258 req
259 }
260
261 pub fn to_request(mut self) -> Request {
263 let mut req = self.finish();
264 req.head_mut().peer_addr = self.peer_addr;
265 req
266 }
267
268 pub fn to_srv_request(mut self) -> ServiceRequest {
270 let (mut head, payload) = self.finish().into_parts();
271 head.peer_addr = self.peer_addr;
272 self.path.get_mut().update(&head.uri);
273
274 let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone());
275
276 ServiceRequest::new(
277 HttpRequest::new(
278 self.path,
279 head,
280 app_state,
281 Rc::new(self.app_data),
282 None,
283 Default::default(),
284 ),
285 payload,
286 )
287 }
288
289 pub fn to_srv_response<B>(self, res: HttpResponse<B>) -> ServiceResponse<B> {
291 self.to_srv_request().into_response(res)
292 }
293
294 pub fn to_http_request(mut self) -> HttpRequest {
296 let (mut head, _) = self.finish().into_parts();
297 head.peer_addr = self.peer_addr;
298 self.path.get_mut().update(&head.uri);
299
300 let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone());
301
302 HttpRequest::new(
303 self.path,
304 head,
305 app_state,
306 Rc::new(self.app_data),
307 None,
308 Default::default(),
309 )
310 }
311
312 pub fn to_http_parts(mut self) -> (HttpRequest, Payload) {
314 let (mut head, payload) = self.finish().into_parts();
315 head.peer_addr = self.peer_addr;
316 self.path.get_mut().update(&head.uri);
317
318 let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone());
319
320 let req = HttpRequest::new(
321 self.path,
322 head,
323 app_state,
324 Rc::new(self.app_data),
325 None,
326 Default::default(),
327 );
328
329 (req, payload)
330 }
331
332 pub async fn send_request<S, B, E>(self, app: &S) -> S::Response
334 where
335 S: Service<Request, Response = ServiceResponse<B>, Error = E>,
336 E: std::fmt::Debug,
337 {
338 let req = self.to_request();
339 test::call_service(app, req).await
340 }
341
342 #[cfg(test)]
343 pub fn set_server_hostname(&mut self, host: &str) {
344 self.config.set_host(host)
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use std::time::SystemTime;
351
352 use super::*;
353 use crate::{http::header, test::init_service, web, App, Error, Responder};
354
355 #[actix_rt::test]
356 async fn test_basics() {
357 let req = TestRequest::default()
358 .version(Version::HTTP_2)
359 .insert_header(header::ContentType::json())
360 .insert_header(header::Date(SystemTime::now().into()))
361 .param("test", "123")
362 .data(10u32)
363 .app_data(20u64)
364 .peer_addr("127.0.0.1:8081".parse().unwrap())
365 .to_http_request();
366 assert!(req.headers().contains_key(header::CONTENT_TYPE));
367 assert!(req.headers().contains_key(header::DATE));
368 assert_eq!(
369 req.head().peer_addr,
370 Some("127.0.0.1:8081".parse().unwrap())
371 );
372 assert_eq!(&req.match_info()["test"], "123");
373 assert_eq!(req.version(), Version::HTTP_2);
374 let data = req.app_data::<Data<u32>>().unwrap();
375 assert!(req.app_data::<Data<u64>>().is_none());
376 assert_eq!(*data.get_ref(), 10);
377
378 assert!(req.app_data::<u32>().is_none());
379 let data = req.app_data::<u64>().unwrap();
380 assert_eq!(*data, 20);
381 }
382
383 #[actix_rt::test]
384 async fn test_send_request() {
385 let app = init_service(
386 App::new().service(
387 web::resource("/index.html")
388 .route(web::get().to(|| HttpResponse::Ok().body("welcome!"))),
389 ),
390 )
391 .await;
392
393 let resp = TestRequest::get()
394 .uri("/index.html")
395 .send_request(&app)
396 .await;
397
398 let result = test::read_body(resp).await;
399 assert_eq!(result, Bytes::from_static(b"welcome!"));
400 }
401
402 #[actix_rt::test]
403 async fn test_async_with_block() {
404 async fn async_with_block() -> Result<HttpResponse, Error> {
405 let res = web::block(move || Some(4usize).ok_or("wrong")).await;
406
407 match res {
408 Ok(value) => Ok(HttpResponse::Ok()
409 .content_type("text/plain")
410 .body(format!("Async with block value: {:?}", value))),
411 Err(_) => panic!("Unexpected"),
412 }
413 }
414
415 let app =
416 init_service(App::new().service(web::resource("/index.html").to(async_with_block)))
417 .await;
418
419 let req = TestRequest::post().uri("/index.html").to_request();
420 let res = app.call(req).await.unwrap();
421 assert!(res.status().is_success());
422 }
423
424 #[allow(deprecated)]
426 #[actix_rt::test]
427 async fn test_server_data() {
428 async fn handler(data: web::Data<usize>) -> impl Responder {
429 assert_eq!(**data, 10);
430 HttpResponse::Ok()
431 }
432
433 let app = init_service(
434 App::new()
435 .data(10usize)
436 .service(web::resource("/index.html").to(handler)),
437 )
438 .await;
439
440 let req = TestRequest::post().uri("/index.html").to_request();
441 let res = app.call(req).await.unwrap();
442 assert!(res.status().is_success());
443 }
444}