fix csrf tests w/ custom test client

This commit is contained in:
azdle 2025-07-23 12:15:08 -05:00
parent 8f18d71743
commit b6111fb8fa
5 changed files with 404 additions and 57 deletions

281
Cargo.lock generated
View file

@ -148,7 +148,7 @@ dependencies = [
"rustc-hash",
"serde",
"serde_derive",
"syn",
"syn 2.0.101",
]
[[package]]
@ -184,7 +184,7 @@ checksum = "34921de3d57974069bad483fdfe0ec65d88c4ff892edd1ab4d8b03be0dda1b9b"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
]
[[package]]
@ -195,7 +195,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
]
[[package]]
@ -306,7 +306,7 @@ checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
]
[[package]]
@ -351,6 +351,21 @@ dependencies = [
"serde",
]
[[package]]
name = "bit-set"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bitflags"
version = "2.9.0"
@ -707,7 +722,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
]
[[package]]
@ -873,6 +888,16 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "futf"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843"
dependencies = [
"mac",
"new_debug_unreachable",
]
[[package]]
name = "futures"
version = "0.3.31"
@ -939,7 +964,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
]
[[package]]
@ -1149,6 +1174,20 @@ dependencies = [
"windows-link",
]
[[package]]
name = "html5ever"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7"
dependencies = [
"log",
"mac",
"markup5ever",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "http"
version = "1.3.1"
@ -1601,6 +1640,12 @@ version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "mac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
[[package]]
name = "maik"
version = "0.2.0"
@ -1615,6 +1660,32 @@ dependencies = [
"wg",
]
[[package]]
name = "markup5ever"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016"
dependencies = [
"log",
"phf",
"phf_codegen",
"string_cache",
"string_cache_codegen",
"tendril",
]
[[package]]
name = "markup5ever_rcdom"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9521dd6750f8e80ee6c53d65e2e4656d7de37064f3a7a5d2d11d05df93839c2"
dependencies = [
"html5ever",
"markup5ever",
"tendril",
"xml5ever",
]
[[package]]
name = "matchers"
version = "0.1.0"
@ -1689,6 +1760,12 @@ dependencies = [
"tempfile",
]
[[package]]
name = "new_debug_unreachable"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
name = "nom"
version = "8.0.0"
@ -1805,7 +1882,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
]
[[package]]
@ -1944,7 +2021,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
]
[[package]]
@ -1958,6 +2035,63 @@ dependencies = [
"sha2",
]
[[package]]
name = "phf"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
dependencies = [
"phf_shared 0.10.0",
]
[[package]]
name = "phf_codegen"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
dependencies = [
"phf_generator 0.10.0",
"phf_shared 0.10.0",
]
[[package]]
name = "phf_generator"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
dependencies = [
"phf_shared 0.10.0",
"rand 0.8.5",
]
[[package]]
name = "phf_generator"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [
"phf_shared 0.11.3",
"rand 0.8.5",
]
[[package]]
name = "phf_shared"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
dependencies = [
"siphasher 0.3.11",
]
[[package]]
name = "phf_shared"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [
"siphasher 1.0.1",
]
[[package]]
name = "pin-project"
version = "1.1.10"
@ -1975,7 +2109,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
]
[[package]]
@ -2068,6 +2202,12 @@ dependencies = [
"zerocopy",
]
[[package]]
name = "precomputed-hash"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "proc-macro2"
version = "1.0.95"
@ -2468,6 +2608,17 @@ dependencies = [
"libc",
]
[[package]]
name = "select"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5910c1d91bd7e6e178c0f8eb9e4ad01f814064b4a1c0ae3c906224a3cbf12879"
dependencies = [
"bit-set",
"html5ever",
"markup5ever_rcdom",
]
[[package]]
name = "serde"
version = "1.0.219"
@ -2485,7 +2636,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
]
[[package]]
@ -2518,7 +2669,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
]
[[package]]
@ -2615,6 +2766,18 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "siphasher"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "siphasher"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "slab"
version = "0.4.9"
@ -2721,7 +2884,7 @@ dependencies = [
"quote",
"sqlx-core",
"sqlx-macros-core",
"syn",
"syn 2.0.101",
]
[[package]]
@ -2744,7 +2907,7 @@ dependencies = [
"sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite",
"syn",
"syn 2.0.101",
"tempfile",
"tokio",
"url",
@ -2878,6 +3041,31 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "string_cache"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f"
dependencies = [
"new_debug_unreachable",
"parking_lot",
"phf_shared 0.11.3",
"precomputed-hash",
"serde",
]
[[package]]
name = "string_cache_codegen"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0"
dependencies = [
"phf_generator 0.11.3",
"phf_shared 0.11.3",
"proc-macro2",
"quote",
]
[[package]]
name = "stringprep"
version = "0.1.5"
@ -2895,6 +3083,17 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.101"
@ -2923,7 +3122,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
]
[[package]]
@ -2971,6 +3170,17 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "tendril"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0"
dependencies = [
"futf",
"mac",
"utf-8",
]
[[package]]
name = "test-log"
version = "0.2.17"
@ -2989,7 +3199,7 @@ checksum = "888d0c3c6db53c0fdab160d2ed5e12ba745383d3e85813f2ea0f2b1475ab553f"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
]
[[package]]
@ -3009,7 +3219,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
]
[[package]]
@ -3113,7 +3323,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
]
[[package]]
@ -3325,7 +3535,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
]
[[package]]
@ -3461,6 +3671,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf8_iter"
version = "1.0.4"
@ -3547,7 +3763,7 @@ dependencies = [
"log",
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
"wasm-bindgen-shared",
]
@ -3582,7 +3798,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -3681,7 +3897,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
]
[[package]]
@ -3692,7 +3908,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
]
[[package]]
@ -3985,6 +4201,17 @@ dependencies = [
"rustix",
]
[[package]]
name = "xml5ever"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4034e1d05af98b51ad7214527730626f019682d797ba38b51689212118d8e650"
dependencies = [
"log",
"mac",
"markup5ever",
]
[[package]]
name = "yaml-rust2"
version = "0.10.1"
@ -4025,7 +4252,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
"synstructure",
]
@ -4051,8 +4278,10 @@ dependencies = [
"rand 0.9.1",
"regex",
"reqwest",
"select",
"serde",
"serde_json",
"serde_urlencoded",
"sqlx",
"tar",
"test-log",
@ -4085,7 +4314,7 @@ checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
]
[[package]]
@ -4105,7 +4334,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
"synstructure",
]
@ -4145,5 +4374,5 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.101",
]

View file

@ -42,6 +42,8 @@ http-body-util = "0.1.3"
maik = "0.2.0"
regex = "1.11.1"
reqwest = { version = "0.12", features = ["cookies"] }
select = "0.6.1"
serde_urlencoded = "0.7.1"
test-log = { version = "0.2.12", default-features = false, features = ["trace"] }
[profile.dev.package.sqlx-macros]

View file

@ -63,6 +63,7 @@ impl fmt::Debug for SignupForm {
f.debug_struct("SignupForm")
.field("email", &self.email)
.field("password", &"REDACTED")
.field("csrf-token", &self.csrf_token)
.finish()
}
}

View file

@ -7,13 +7,13 @@ use test_log::test as traced;
#[traced(tokio::test)]
async fn login_succeeds_with_valid_credentials() -> Result<()> {
let server = TestServer::spawn().await;
let client = reqwest::Client::builder().cookie_store(true).build()?;
let mut client = server.browser_client();
client.get_csrf_token().await?;
// Signup
let resp = client
.post(server.url("/auth/signup"))
.header("Content-Type", "application/x-www-form-urlencoded")
.body("email=admin&password=hunter2")
.post("/auth/signup")
.csrf_form(&[("email", "admin"), ("password", "hunter2")])
.send()
.await?;
@ -21,16 +21,19 @@ async fn login_succeeds_with_valid_credentials() -> Result<()> {
// Login
let resp = client
.post(server.url("/auth/login"))
.header("Content-Type", "application/x-www-form-urlencoded")
.body("email=admin&password=hunter2")
.post("/auth/login")
.csrf_form(&[("email", "admin"), ("password", "hunter2")])
.send()
.await?;
assert_eq!(resp.status(), 200, "login succeeds");
// Logout
let resp = client.post(server.url("/auth/logout")).send().await?;
let resp = client
.post("/auth/logout")
.csrf_form::<[(&str, &str)]>(&[])
.send()
.await?;
assert_eq!(resp.status(), 200, "logout succeeds");
@ -40,13 +43,13 @@ async fn login_succeeds_with_valid_credentials() -> Result<()> {
#[traced(tokio::test)]
async fn login_fails_with_invalid_credentials() -> Result<()> {
let server = TestServer::spawn().await;
let client = reqwest::Client::new();
let mut client = server.browser_client();
client.get_csrf_token().await?;
// Signup
let resp = client
.post(server.url("/auth/signup"))
.header("Content-Type", "application/x-www-form-urlencoded")
.body("email=admin&password=hunter2")
.post("/auth/signup")
.csrf_form(&[("email", "admin"), ("password", "hunter2")])
.send()
.await?;
@ -54,20 +57,15 @@ async fn login_fails_with_invalid_credentials() -> Result<()> {
// Login
let resp = client
.post(server.url("/auth/login"))
.header("Content-Type", "application/x-www-form-urlencoded")
.body("email=admin&password=hunter3")
.post("/auth/login")
.csrf_form(&[("email", "admin"), ("password", "hunter3")])
.send()
.await?;
assert_ne!(
assert_eq!(
resp.status(),
200,
"login suceeded with invalid credentials"
);
assert!(
resp.headers().get("Set-Cookie").is_none(),
"auth cookie was set for invalid crednetials"
401,
"login didn't reject invalid credentials"
);
server.shutdown().await
@ -76,11 +74,12 @@ async fn login_fails_with_invalid_credentials() -> Result<()> {
#[traced(tokio::test)]
async fn login_rejects_missing_credentials() -> Result<()> {
let server = TestServer::spawn().await;
let client = reqwest::Client::new();
let mut client = server.browser_client();
client.get_csrf_token().await?;
let resp = client
.post(server.url("/auth/login"))
.header("Content-Type", "application/x-www-form-urlencoded")
.body("email=&password=")
.post("/auth/login")
.csrf_form(&[("email", ""), ("password", "")])
.send()
.await?;
@ -89,10 +88,6 @@ async fn login_rejects_missing_credentials() -> Result<()> {
401,
"login didn't reject missing credentials"
);
assert!(
resp.headers().get("Set-Cookie").is_none(),
"auth cookie was set for missing crednetials"
);
server.shutdown().await
}

View file

@ -1,4 +1,5 @@
use anyhow::Result;
use anyhow::{Context as _, Result};
use axum::http;
use bollard::query_parameters::{CreateImageOptionsBuilder, ListContainersOptionsBuilder};
use bollard::secret::{
ContainerCreateBody, ContainerInspectResponse, ContainerState, CreateImageInfo, Health,
@ -8,6 +9,11 @@ use bollard::Docker;
use futures_util::{FutureExt, StreamExt as _};
use rand::distr::slice::Choose;
use rand::{rng, Rng};
use reqwest::header::{HeaderName, HeaderValue};
use reqwest::Body;
use select::document::Document;
use select::predicate::Attr;
use serde::Serialize;
use sqlx::migrate::MigrateDatabase;
use sqlx::{Connection, PgConnection};
use std::net::SocketAddr;
@ -74,6 +80,11 @@ impl TestServer {
format!("http://{}{path}", self.addr)
}
/// Construct a browser-like client.
pub fn browser_client(&self) -> BrowserClient {
BrowserClient::new(self.addr)
}
/// Request a graceful shutdown and return imideately.
pub async fn start_shutdown(&self) -> Result<()> {
self.server_task_handle.abort();
@ -90,6 +101,115 @@ impl TestServer {
}
}
pub struct BrowserClient {
inner: reqwest::Client,
server: SocketAddr,
csrf_token: Option<String>,
}
impl BrowserClient {
fn new(server: SocketAddr) -> BrowserClient {
let inner = reqwest::Client::builder()
.cookie_store(true)
.build()
.expect("build reqwest client");
BrowserClient {
inner,
server,
csrf_token: None,
}
}
/// format a URL for the given path
fn url(&self, path: &str) -> String {
println!("url: http://{}{path}", self.server);
format!("http://{}{path}", self.server)
}
pub fn post<U: AsRef<str>>(&self, url: U) -> RequestBuilder {
RequestBuilder::new(
self.inner.post(self.url(url.as_ref())),
self.csrf_token.clone(),
)
}
pub async fn get_csrf_token(&mut self) -> Result<String> {
// Any page with a CSRF-cookie will do, tokens are valid for a session.
let resp = self.inner.get(&self.url("/auth/signup")).send().await?;
assert_eq!(resp.status(), 200, "get CSRF page");
let signup_page = resp.text().await.context("recv signup page body")?;
let document = Document::from(signup_page.as_str());
let csrf_node = document
.find(Attr("name", "csrf-token"))
.next()
.context("find csrf node")?;
let csrf_token = csrf_node
.attr("value")
.context("get csrf token from node")?
.to_string();
self.csrf_token = Some(csrf_token.clone());
Ok(csrf_token)
}
}
pub struct RequestBuilder {
inner: reqwest::RequestBuilder,
csrf_token: Option<String>,
}
impl RequestBuilder {
fn new(request: reqwest::RequestBuilder, csrf_token: Option<String>) -> RequestBuilder {
RequestBuilder {
inner: request,
csrf_token,
}
}
pub fn header<K, V>(mut self, key: K, value: V) -> Self
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
{
self.inner = self.inner.header(key, value);
self
}
pub fn form<T: Serialize + ?Sized>(mut self, form: &T) -> RequestBuilder {
self.inner = self.inner.form(form);
self
}
pub fn csrf_form<T: Serialize + ?Sized>(mut self, form: &T) -> RequestBuilder {
let body_str = serde_urlencoded::to_string(form).unwrap();
let full_body_str = format!(
"{body_str}&csrf-token={}",
self.csrf_token.as_ref().unwrap()
);
self.inner = self
.inner
.body(full_body_str)
.header("Content-Type", "application/x-www-form-urlencoded");
self
}
pub fn body<T: Into<Body>>(mut self, body: T) -> Self {
self.inner = self.inner.body(body);
self
}
pub async fn send(self) -> Result<reqwest::Response, reqwest::Error> {
self.inner.send().await
}
}
const TEST_DB_IMAGE_NAME: &str = "postgres";
const TEST_DB_SUPERUSER: &str = "postgres";
const TEST_DB_SUPERUSER_PASS: &str = "password";