1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
use ::anyhow::Context;
use ::anyhow::Result;
use ::axum::routing::IntoMakeService;
use ::axum::Router;
use ::cookie::Cookie;
use ::cookie::CookieJar;
use ::hyper::http::Method;
use ::std::sync::Arc;
use ::std::sync::Mutex;
use crate::TestRequest;
use crate::TestServerConfig;
mod inner_test_server;
pub(crate) use self::inner_test_server::*;
use std::net::SocketAddr;
///
/// The `TestServer` represents your application, running as a web server,
/// and you can make web requests to your application.
///
/// For most people's needs, this is where to start when writing a test.
/// This allows you Allowing you to create new requests that will go to this server.
///
/// You can make a request against the `TestServer` by calling the
/// `get`, `post`, `put`, `delete`, and `patch` methods (you can also use `method`).
///
#[derive(Debug)]
pub struct TestServer {
socket_address: SocketAddr,
inner: Arc<Mutex<InnerTestServer>>,
}
impl TestServer {
/// This will take the given app, and run it.
/// It will use a randomly selected port for running.
///
/// This is the same as creating a new `TestServer` with a configuration,
/// and passing `TestServerConfig::default()`.
pub fn new(app: IntoMakeService<Router>) -> Result<Self> {
Self::new_with_config(app, TestServerConfig::default())
}
/// This very similar to `TestServer::new()`,
/// however you can customise some of the configuration.
/// This includes which port to run on, or default settings.
///
/// See the `TestServerConfig` for more information on each configuration setting.
pub fn new_with_config(
app: IntoMakeService<Router>,
options: TestServerConfig,
) -> Result<Self> {
let socket_address = options.build_socket_address()?;
let new_config = TestServerConfig {
socket_address: Some(socket_address),
..options
};
let inner_test_server = InnerTestServer::new(app, new_config)?;
let inner_mutex = Mutex::new(inner_test_server);
let inner = Arc::new(inner_mutex);
let this = Self {
inner,
socket_address,
};
Ok(this)
}
/// Returns the address for the test server.
///
/// By default this will be something like `0.0.0.0:1234`,
/// where `1234` is a randomly assigned port numbr.
pub fn server_address(&self) -> String {
format!("http://{}", self.socket_address)
}
/// Clears all of the cookies stored internally.
pub fn clear_cookies(&mut self) {
InnerTestServer::clear_cookies(&mut self.inner)
.with_context(|| format!("Trying to clear_cookies"))
.unwrap()
}
/// Adds extra cookies to be used on *all* future requests.
///
/// Any cookies which have the same name as the new cookies,
/// will get replaced.
pub fn add_cookies(&mut self, cookies: CookieJar) {
InnerTestServer::add_cookies(&mut self.inner, cookies)
.with_context(|| format!("Trying to add_cookies"))
.unwrap()
}
/// Adds a cookie to be included on *all* future requests.
///
/// If a cookie with the same name already exists,
/// then it will be replaced.
pub fn add_cookie(&mut self, cookie: Cookie) {
InnerTestServer::add_cookie(&mut self.inner, cookie)
.with_context(|| format!("Trying to add_cookie"))
.unwrap()
}
/// Creates a HTTP GET request to the path.
pub fn get(&self, path: &str) -> TestRequest {
self.method(Method::GET, path)
}
/// Creates a HTTP POST request to the given path.
pub fn post(&self, path: &str) -> TestRequest {
self.method(Method::POST, path)
}
/// Creates a HTTP PATCH request to the path.
pub fn patch(&self, path: &str) -> TestRequest {
self.method(Method::PATCH, path)
}
/// Creates a HTTP PUT request to the path.
pub fn put(&self, path: &str) -> TestRequest {
self.method(Method::PUT, path)
}
/// Creates a HTTP DELETE request to the path.
pub fn delete(&self, path: &str) -> TestRequest {
self.method(Method::DELETE, path)
}
/// Creates a HTTP request, to the path given, using the given method.
pub fn method(&self, method: Method, path: &str) -> TestRequest {
let debug_method = method.clone();
InnerTestServer::send(&self.inner, method, path)
.with_context(|| {
format!(
"Trying to create internal request for {} {}",
debug_method, path
)
})
.unwrap()
}
}
#[cfg(test)]
mod server_address {
use super::*;
use ::axum::Router;
#[tokio::test]
async fn it_should_return_address_used_from_config() {
let socket_address = SocketAddr::from(([127, 0, 0, 1], 3000));
let config = TestServerConfig {
socket_address: Some(socket_address),
..TestServerConfig::default()
};
// Build an application with a route.
let app = Router::new().into_make_service();
let server = TestServer::new_with_config(app, config).expect("Should create test server");
assert_eq!(server.server_address(), "http://127.0.0.1:3000")
}
}