1#![forbid(unsafe_code)]
3
4mod builder;
5mod database;
6
7pub use builder::TestClientBuilder;
8pub use database::{TestDatabase, TestDatabaseError};
9pub use http;
11
12use http::{HeaderMap, Method, Request, StatusCode, header};
13use serde::{Serialize, de::DeserializeOwned};
14use siderite_core::lifespan::ManagedLifespan;
15use siderite_core::{App, Body, BodyError, RouterService, ServerError};
16use std::sync::{Arc, Mutex, PoisonError};
17use thiserror::Error;
18use tower::ServiceExt;
19
20#[derive(Debug, Error)]
22#[non_exhaustive]
23pub enum TestClientError {
24 #[error("invalid request: {0}")]
26 Request(#[from] http::Error),
27 #[error("cannot serialize request body: {0}")]
29 Serialize(#[from] serde_json::Error),
30 #[error(transparent)]
32 Body(#[from] BodyError),
33}
34
35#[derive(Debug, Clone)]
37pub struct TestResponse {
38 pub status: StatusCode,
40 pub headers: HeaderMap,
42 pub body: Vec<u8>,
44}
45
46impl TestResponse {
47 pub fn json<T: DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
52 serde_json::from_slice(&self.body)
53 }
54
55 pub fn text(&self) -> String {
57 String::from_utf8_lossy(&self.body).into_owned()
58 }
59
60 pub fn content_type(&self) -> Option<&str> {
62 self.headers.get(header::CONTENT_TYPE)?.to_str().ok()
63 }
64}
65
66#[derive(Debug, Clone)]
68pub struct TestClient {
69 service: RouterService,
70 lifespan: Arc<Mutex<Option<ManagedLifespan>>>,
71}
72
73impl TestClient {
74 pub fn builder(app: App) -> TestClientBuilder {
77 TestClientBuilder::new(app)
78 }
79
80 pub fn try_new(app: App) -> Result<Self, siderite_core::ServerError> {
86 Ok(Self {
87 service: app.into_router_service()?,
88 lifespan: Arc::default(),
89 })
90 }
91
92 pub fn new(app: App) -> Self {
98 match Self::try_new(app) {
99 Ok(client) => client,
100 Err(err) => panic!("invalid app configuration: {err}"),
101 }
102 }
103
104 pub async fn start(app: App) -> Result<Self, ServerError> {
114 let (service, lifespan) = app.into_service_with_lifespan()?;
115 let mut lifespan = lifespan.supervise()?;
116 lifespan.ready().await?;
117 Ok(Self {
118 service,
119 lifespan: Arc::new(Mutex::new(Some(lifespan))),
120 })
121 }
122
123 pub async fn shutdown(&self) -> Result<(), ServerError> {
129 let taken = self
130 .lifespan
131 .lock()
132 .unwrap_or_else(PoisonError::into_inner)
133 .take();
134 match taken {
135 Some(lifespan) => lifespan.shutdown().await,
136 None => Ok(()),
137 }
138 }
139
140 pub async fn get(&self, path: &str) -> Result<TestResponse, TestClientError> {
145 let req = Request::builder()
146 .method(Method::GET)
147 .uri(path)
148 .body(Body::empty())?;
149 self.send(req).await
150 }
151
152 pub async fn delete(&self, path: &str) -> Result<TestResponse, TestClientError> {
157 let req = Request::builder()
158 .method(Method::DELETE)
159 .uri(path)
160 .body(Body::empty())?;
161 self.send(req).await
162 }
163
164 pub async fn post_json<T: Serialize>(
169 &self,
170 path: &str,
171 payload: &T,
172 ) -> Result<TestResponse, TestClientError> {
173 self.post_raw(path, "application/json", serde_json::to_vec(payload)?)
174 .await
175 }
176
177 pub async fn post_raw(
182 &self,
183 path: &str,
184 content_type: &str,
185 body: Vec<u8>,
186 ) -> Result<TestResponse, TestClientError> {
187 let req = Request::builder()
188 .method(Method::POST)
189 .uri(path)
190 .header(header::CONTENT_TYPE, content_type)
191 .body(Body::from(body))?;
192 self.send(req).await
193 }
194
195 pub async fn send(&self, req: Request<Body>) -> Result<TestResponse, TestClientError> {
200 let Ok(response) = self.service.clone().oneshot(req).await;
201 let (parts, body) = response.into_parts();
202 Ok(TestResponse {
203 status: parts.status,
204 headers: parts.headers,
205 body: body.into_bytes_limited(usize::MAX).await?,
206 })
207 }
208}