tokiotest_httpserver/
lib.rs1#![doc = include_str!("../README.md")]
2pub mod handler;
3
4use std::collections::BinaryHeap;
5use std::future::Future;
6use std::net::{SocketAddr};
7use test_context::AsyncTestContext;
8use tokio::sync::oneshot::{Receiver, Sender};
9use tokio::task::JoinHandle;
10use hyper::{Server, StatusCode, Uri};
11use hyper::service::{make_service_fn, service_fn};
12use lazy_static::lazy_static;
13use std::sync::Mutex;
14use std::sync::Arc;
15use queues::{Queue, IsQueue, queue};
16use crate::handler::{default_handle, HandlerCallback};
17use std::env;
18
19pub type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
20pub static TOKIOTEST_HTTP_PORT_ENV: &str = "TOKIOTEST_HTTP_PORT";
21
22lazy_static! {
23 static ref PORTS: Mutex<BinaryHeap<u16>> = Mutex::new(BinaryHeap::from((12300u16..12400u16).collect::<Vec<u16>>()));
24}
25
26pub fn take_port() -> u16 {
27 PORTS.lock().unwrap().pop().unwrap()
28}
29pub fn release_port(port: u16) {
30 PORTS.lock().unwrap().push(port)
31}
32
33#[allow(dead_code)]
34pub struct HttpTestContext {
35 pub port: u16,
36 pub handlers: Arc<Mutex<Queue<HandlerCallback>>>,
37 server_handler: JoinHandle<Result<(), hyper::Error>>,
38 sender: Sender<()>,
39}
40
41impl HttpTestContext {
42 pub fn add(&mut self, handler: HandlerCallback) {
43 self.handlers.lock().unwrap().add(handler).unwrap();
44 }
45
46 pub fn uri(&self, path: &str) -> Uri {
47 format!("http://{}:{}{}", "localhost", self.port, path).parse::<Uri>().unwrap()
48 }
49}
50
51pub async fn run_service(addr: SocketAddr, rx: Receiver<()>,
52 handlers: Arc<Mutex<Queue<HandlerCallback>>>) -> impl Future<Output = Result<(), hyper::Error>> {
53
54 let new_service = make_service_fn(move |_| {
55 let cloned_handlers = handlers.clone();
56 async {
57 Ok::<_, Error>(service_fn(move |req| {
58 match cloned_handlers.lock() {
59 Ok(mut handlers_rw) => {
60 match handlers_rw.remove() {
61 Ok(handler) => { handler(req) }
62 Err(_err) => { Box::pin(default_handle(req)) }
63 }
64 }
65 Err(_err_lock) => Box::pin(default_handle(req))
66 }
67 }))
68 }
69 });
70 Server::bind(&addr).serve(new_service).with_graceful_shutdown(async { rx.await.ok(); })
71}
72
73#[async_trait::async_trait]
74impl AsyncTestContext for HttpTestContext {
75 async fn setup() -> HttpTestContext {
76 let port: u16 = match env::var(TOKIOTEST_HTTP_PORT_ENV) {
77 Ok(port_str) => port_str.parse::<u16>().unwrap(),
78 Err(_e) => take_port()
79 };
80 let addr = SocketAddr::new("127.0.0.1".parse().unwrap(), port);
81 let (sender, receiver) = tokio::sync::oneshot::channel::<()>();
82 let handlers: Arc<Mutex<Queue<HandlerCallback>>> = Arc::new(Mutex::new(queue![]));
83 let server_handler = tokio::spawn(run_service(addr, receiver, handlers.clone()).await);
84 HttpTestContext {
85 server_handler,
86 sender,
87 port,
88 handlers
89 }
90 }
91
92 async fn teardown(self) {
93 let _ = self.sender.send(()).unwrap();
94 let _ = tokio::join!(self.server_handler);
95 release_port(self.port);
96 }
97}
98
99#[cfg(test)]
100mod test {
101 use hyper::{StatusCode, Method, Request, Body, HeaderMap, Client};
102 use crate::{HttpTestContext};
103 use test_context::test_context;
104 use crate::handler::HandlerBuilder;
105
106 #[test_context(HttpTestContext)]
107 #[tokio::test]
108 async fn test_get_without_expect_should_send_500(ctx: &mut HttpTestContext) {
109 let resp = Client::new().get(ctx.uri("/whatever")).await.unwrap();
110 assert_eq!(500, resp.status());
111 }
112
113 #[test_context(HttpTestContext)]
114 #[tokio::test]
115 async fn test_get_respond_404(ctx: &mut HttpTestContext) {
116 ctx.add(HandlerBuilder::new("/unknown").status_code(StatusCode::NOT_FOUND).build());
117
118 let resp = Client::new().get(ctx.uri("/unknown")).await.unwrap();
119
120 assert_eq!(404, resp.status());
121 }
122
123 #[test_context(HttpTestContext)]
124 #[tokio::test]
125 async fn test_get_endpoint(ctx: &mut HttpTestContext) {
126 ctx.add(HandlerBuilder::new("/foo").status_code(StatusCode::OK).build());
127
128 let resp = Client::new().get(ctx.uri("/foo")).await.unwrap();
129 assert_eq!(200, resp.status());
130
131 let resp = Client::new().get(ctx.uri("/foo")).await.unwrap();
132 assert_eq!(500, resp.status());
133 }
134
135 #[test_context(HttpTestContext)]
136 #[tokio::test]
137 async fn test_get_with_headers(ctx: &mut HttpTestContext) {
138 let mut headers = HeaderMap::new();
139 headers.append("foo", "bar".parse().unwrap());
140 ctx.add(HandlerBuilder::new("/headers").status_code(StatusCode::OK).headers(headers.clone()).build());
141 ctx.add(HandlerBuilder::new("/headers").status_code(StatusCode::OK).headers(headers).build());
142
143 let resp = Client::new().get(ctx.uri("/headers")).await.unwrap();
144 assert_eq!(500, resp.status());
145
146 let req = Request::builder().method(Method::GET).uri(ctx.uri("/headers")).header("foo", "bar").body(Body::empty()).unwrap();
147 let resp = Client::new().request(req).await.unwrap();
148 assert_eq!(200, resp.status());
149 }
150
151 #[test_context(HttpTestContext)]
152 #[tokio::test]
153 async fn test_post_endpoint(ctx: &mut HttpTestContext) {
154 ctx.add(HandlerBuilder::new("/bar")
155 .status_code(StatusCode::OK)
156 .method(Method::POST).build());
157
158 let req = Request::builder()
159 .method(Method::POST)
160 .uri(ctx.uri("/bar"))
161 .body(Body::from("foo=bar"))
162 .expect("request builder");
163
164 let resp = Client::new().request(req).await.unwrap();
165
166 assert_eq!(200, resp.status());
167 }
168}