1mod protocol;
5
6use http::header::{CONTENT_TYPE, COOKIE};
7pub use protocol::{
8 EmptyTable, FilterOption, Row, RowActions, SESSION_COOKIE, body_bytes, body_string,
9 cookie_header, empty_table, field_error, filter_options, form_body, input_value,
10 multipart_body, notification, response_cookies, row_actions, rows, session_cookie_value,
11 set_cookie_header,
12};
13use tablo_core::PanelUser;
14use topcoat::router::{Body, Router};
15
16#[derive(Clone)]
18pub struct TestClient<'a> {
19 router: &'a Router,
20 cookies: Vec<(String, String)>,
21 tenant: Option<uuid::Uuid>,
22}
23
24impl<'a> TestClient<'a> {
25 pub fn new(router: &'a Router) -> Self {
26 Self {
27 router,
28 cookies: Vec::new(),
29 tenant: None,
30 }
31 }
32
33 pub fn cookie(&self, name: &str, value: &str) -> Self {
36 let mut client = self.clone();
37 match client.cookies.iter_mut().find(|(kept, _)| kept == name) {
38 Some(existing) => existing.1 = value.to_string(),
39 None => client.cookies.push((name.to_string(), value.to_string())),
40 }
41 client
42 }
43
44 pub fn cookies(&self, cookies: &[(String, String)]) -> Self {
47 let mut client = self.clone();
48 for (name, value) in cookies {
49 match client.cookies.iter_mut().find(|(kept, _)| kept == name) {
50 Some(existing) => existing.1 = value.clone(),
51 None => client.cookies.push((name.clone(), value.clone())),
52 }
53 }
54 client
55 }
56
57 pub fn csrf(&self, token: &str) -> Self {
59 self.cookie(tablo_core::csrf::COOKIE_NAME, token)
60 }
61
62 pub async fn sign_in(&self, db: &toasty::Db, panel: &str, user: &impl PanelUser) -> Self {
87 let token = tablo_core::auth::mint_session(&mut db.clone(), panel, user)
88 .await
89 .expect("record the test session");
90 self.cookie(SESSION_COOKIE, &token)
91 }
92
93 pub fn tenant(&self, tenant: uuid::Uuid) -> Self {
96 let mut client = self.clone();
97 client.tenant = Some(tenant);
98 client
99 }
100
101 pub async fn get(&self, uri: &str) -> http::Response<Body> {
102 self.router
103 .handle(self.request(http::Method::GET, uri))
104 .await
105 }
106
107 pub async fn post_form(&self, uri: &str, body: String) -> http::Response<Body> {
108 let mut request = self.request(http::Method::POST, uri);
109 request.headers_mut().insert(
110 CONTENT_TYPE,
111 http::HeaderValue::from_static("application/x-www-form-urlencoded"),
112 );
113 *request.body_mut() = Body::from(body);
114 self.router.handle(request).await
115 }
116
117 pub async fn submit(&self, uri: &str, body: &str) -> http::Response<Body> {
120 let token = uuid::Uuid::new_v4().to_string();
121 let field = form_body(&[("csrf_token", &token)]);
122 let body = if body.is_empty() {
123 field
124 } else {
125 format!("{body}&{field}")
126 };
127 self.csrf(&token).post_form(uri, body).await
128 }
129
130 pub async fn post_multipart(
131 &self,
132 uri: &str,
133 boundary: &str,
134 body: String,
135 ) -> http::Response<Body> {
136 let mut request = self.request(http::Method::POST, uri);
137 request.headers_mut().insert(
138 CONTENT_TYPE,
139 format!("multipart/form-data; boundary={boundary}")
140 .parse()
141 .expect("multipart content type"),
142 );
143 *request.body_mut() = Body::from(body);
144 self.router.handle(request).await
145 }
146
147 pub async fn post_json(&self, uri: &str, body: String, identity: &str) -> http::Response<Body> {
149 let mut request = self.request(http::Method::POST, uri);
150 request.headers_mut().insert(
151 CONTENT_TYPE,
152 http::HeaderValue::from_static("application/json"),
153 );
154 request.headers_mut().insert(
155 topcoat::router::request::IDENTITY_HEADER,
156 http::HeaderValue::from_str(identity).expect("identity header"),
157 );
158 *request.body_mut() = Body::from(body);
159 self.router.handle(request).await
160 }
161
162 fn request(&self, method: http::Method, uri: &str) -> http::Request<Body> {
163 let mut builder = http::Request::builder().method(method).uri(uri);
164 if let Some(jar) = cookie_header(
165 self.cookies
166 .iter()
167 .map(|(name, value)| (name.as_str(), value.as_str())),
168 ) {
169 builder = builder.header(COOKIE, jar);
170 }
171 let (mut parts, body) = builder.body(Body::empty()).unwrap().into_parts();
172 if let Some(tenant) = self.tenant {
173 parts.extensions.insert(tablo_core::Tenant(tenant));
174 }
175 http::Request::from_parts(parts, body)
176 }
177}