Skip to main content

spire_core/testing/
page.rs

1use std::time::Duration;
2
3use ::http::header::{CONTENT_TYPE, IntoHeaderName};
4use ::http::{HeaderMap, HeaderValue, StatusCode, Uri};
5use bytes::Bytes;
6
7/// A response a [`MockSite`](super::MockSite) serves.
8#[derive(Debug, Clone)]
9pub struct MockPage {
10    pub(super) status: StatusCode,
11    pub(super) headers: HeaderMap,
12    pub(super) body: Bytes,
13    /// Where the page redirects to, if it does.
14    pub(super) location: Option<Uri>,
15    /// How long the page takes to answer.
16    pub(super) latency: Duration,
17}
18
19impl MockPage {
20    /// Creates an empty page with `status`.
21    #[must_use]
22    pub fn status(status: StatusCode) -> Self {
23        Self {
24            status,
25            headers: HeaderMap::new(),
26            body: Bytes::new(),
27            location: None,
28            latency: Duration::ZERO,
29        }
30    }
31
32    /// Creates a `200 OK` HTML page.
33    #[must_use]
34    pub fn html(body: impl Into<Bytes>) -> Self {
35        Self::content("text/html; charset=utf-8", body)
36    }
37
38    /// Creates a `200 OK` plain text page.
39    #[must_use]
40    pub fn text(body: impl Into<Bytes>) -> Self {
41        Self::content("text/plain; charset=utf-8", body)
42    }
43
44    /// Creates a `200 OK` JSON page.
45    #[must_use]
46    pub fn json(body: impl Into<Bytes>) -> Self {
47        Self::content("application/json", body)
48    }
49
50    /// Creates a `200 OK` XML page, e.g. a sitemap.
51    #[must_use]
52    pub fn xml(body: impl Into<Bytes>) -> Self {
53        Self::content("application/xml", body)
54    }
55
56    fn content(content_type: &'static str, body: impl Into<Bytes>) -> Self {
57        Self::status(StatusCode::OK)
58            .with_header(CONTENT_TYPE, HeaderValue::from_static(content_type))
59            .with_body(body)
60    }
61
62    /// Creates a redirect to `location`, which the site follows as an HTTP
63    /// client would: the response is the page at `location`, served from
64    /// that URI.
65    ///
66    /// # Panics
67    ///
68    /// Panics if `location` is not a valid absolute URI.
69    #[must_use]
70    pub fn redirect(location: &str) -> Self {
71        let mut page = Self::status(StatusCode::FOUND);
72        page.location = Some(super::site::parse(location));
73        page
74    }
75
76    /// Replaces the status.
77    #[must_use]
78    pub const fn with_status(mut self, status: StatusCode) -> Self {
79        self.status = status;
80        self
81    }
82
83    /// Inserts a header, replacing any previous value.
84    #[must_use]
85    pub fn with_header(mut self, name: impl IntoHeaderName, value: HeaderValue) -> Self {
86        self.headers.insert(name, value);
87        self
88    }
89
90    /// Takes `latency` to answer, e.g. to test timeouts or adaptive host
91    /// delays. Tests with paused time, `#[tokio::test(start_paused = true)]`,
92    /// skip the wait.
93    #[must_use]
94    pub const fn with_latency(mut self, latency: Duration) -> Self {
95        self.latency = latency;
96        self
97    }
98
99    /// Replaces the body.
100    #[must_use]
101    pub fn with_body(mut self, body: impl Into<Bytes>) -> Self {
102        self.body = body.into();
103        self
104    }
105}