Skip to main content

tablo_test/
lib.rs

1//! Drives a built `Router` without a socket, carrying cookies, CSRF token, and
2//! tenant, and reads values back out of its responses.
3
4mod protocol;
5
6use http::header::{CONTENT_TYPE, COOKIE};
7pub use protocol::{
8    FilterOption, Row, RowActions, SESSION_COOKIE, body_bytes, body_string, cookie_header,
9    field_error, filter_options, form_body, input_value, multipart_body, response_cookies,
10    row_actions, rows, session_cookie_value, set_cookie_header,
11};
12use topcoat::router::{Body, Router};
13
14/// Sends requests carrying cookies and tenant.
15#[derive(Clone)]
16pub struct TestClient<'a> {
17    router: &'a Router,
18    cookies: Vec<(String, String)>,
19    tenant: Option<uuid::Uuid>,
20}
21
22impl<'a> TestClient<'a> {
23    pub fn new(router: &'a Router) -> Self {
24        Self {
25            router,
26            cookies: Vec::new(),
27            tenant: None,
28        }
29    }
30
31    /// Attaches a cookie to every request; a later value for the same name
32    /// replaces the earlier one.
33    pub fn cookie(&self, name: &str, value: &str) -> Self {
34        let mut client = self.clone();
35        match client.cookies.iter_mut().find(|(kept, _)| kept == name) {
36            Some(existing) => existing.1 = value.to_string(),
37            None => client.cookies.push((name.to_string(), value.to_string())),
38        }
39        client
40    }
41
42    /// Attaches every `(name, value)` pair, replacing earlier values for the
43    /// same name.
44    pub fn cookies(&self, cookies: &[(String, String)]) -> Self {
45        let mut client = self.clone();
46        for (name, value) in cookies {
47            match client.cookies.iter_mut().find(|(kept, _)| kept == name) {
48                Some(existing) => existing.1 = value.clone(),
49                None => client.cookies.push((name.clone(), value.clone())),
50            }
51        }
52        client
53    }
54
55    /// Attach the CSRF cookie the form's `csrf_token` field must match.
56    pub fn csrf(&self, token: &str) -> Self {
57        self.cookie(tablo_core::csrf::COOKIE_NAME, token)
58    }
59
60    /// Scopes the request to another tenant, taking precedence over the
61    /// logged-in user's tenant.
62    pub fn tenant(&self, tenant: uuid::Uuid) -> Self {
63        let mut client = self.clone();
64        client.tenant = Some(tenant);
65        client
66    }
67
68    pub async fn get(&self, uri: &str) -> http::Response<Body> {
69        self.router
70            .handle(self.request(http::Method::GET, uri))
71            .await
72    }
73
74    pub async fn post_form(&self, uri: &str, body: String) -> http::Response<Body> {
75        let mut request = self.request(http::Method::POST, uri);
76        request.headers_mut().insert(
77            CONTENT_TYPE,
78            http::HeaderValue::from_static("application/x-www-form-urlencoded"),
79        );
80        *request.body_mut() = Body::from(body);
81        self.router.handle(request).await
82    }
83
84    pub async fn post_multipart(
85        &self,
86        uri: &str,
87        boundary: &str,
88        body: String,
89    ) -> http::Response<Body> {
90        let mut request = self.request(http::Method::POST, uri);
91        request.headers_mut().insert(
92            CONTENT_TYPE,
93            format!("multipart/form-data; boundary={boundary}")
94                .parse()
95                .expect("multipart content type"),
96        );
97        *request.body_mut() = Body::from(body);
98        self.router.handle(request).await
99    }
100
101    /// Posts JSON with the page identity header the browser runtime sends.
102    pub async fn post_json(&self, uri: &str, body: String, identity: &str) -> http::Response<Body> {
103        let mut request = self.request(http::Method::POST, uri);
104        request.headers_mut().insert(
105            CONTENT_TYPE,
106            http::HeaderValue::from_static("application/json"),
107        );
108        request.headers_mut().insert(
109            topcoat::router::request::IDENTITY_HEADER,
110            http::HeaderValue::from_str(identity).expect("identity header"),
111        );
112        *request.body_mut() = Body::from(body);
113        self.router.handle(request).await
114    }
115
116    fn request(&self, method: http::Method, uri: &str) -> http::Request<Body> {
117        let mut builder = http::Request::builder().method(method).uri(uri);
118        if let Some(jar) = cookie_header(
119            self.cookies
120                .iter()
121                .map(|(name, value)| (name.as_str(), value.as_str())),
122        ) {
123            builder = builder.header(COOKIE, jar);
124        }
125        let (mut parts, body) = builder.body(Body::empty()).unwrap().into_parts();
126        if let Some(tenant) = self.tenant {
127            parts.extensions.insert(tablo_core::Tenant(tenant));
128        }
129        http::Request::from_parts(parts, body)
130    }
131}