Skip to main content

spire_core/testing/
visit.rs

1use std::fmt;
2use std::sync::Arc;
3
4use super::MockSite;
5use crate::backend::Backend;
6use crate::dataset::{Data, Dataset, Datasets};
7use crate::frontier::queue::Queue;
8use crate::frontier::seed::Seed;
9use crate::frontier::{Frontier, InMemFrontier};
10use crate::worker::{Outcome, Worker};
11use crate::{Context, Result};
12
13/// One request to a [`MockSite`], run through a worker without crawling the
14/// links it queues.
15///
16/// Created by [`MockSite::visit`]. After [`run`](Self::run), the worker's
17/// effects can be checked: the [`Outcome`] it returned, the seeds it
18/// [`queued`](Self::queued), and the items it wrote to its datasets.
19///
20/// The queue has no facades: no deduplication, filters or depth limits, so
21/// every link the worker follows is kept.
22pub struct Visit {
23    site: MockSite,
24    seed: Seed,
25    frontier: Arc<InMemFrontier>,
26    datasets: Arc<Datasets>,
27    extensions: http::Extensions,
28}
29
30impl Visit {
31    pub(super) fn new(site: MockSite, seed: Seed) -> Self {
32        Self {
33            site,
34            seed,
35            frontier: Arc::new(InMemFrontier::fifo()),
36            datasets: Arc::default(),
37            extensions: http::Extensions::new(),
38        }
39    }
40
41    /// Adds a service to each visited request, replacing the same type.
42    #[must_use]
43    pub fn with_extension<T: Clone + Send + Sync + 'static>(mut self, value: T) -> Self {
44        self.extensions.insert(value);
45        self
46    }
47
48    /// Registers `dataset` for items of type `T`.
49    #[must_use]
50    pub fn with_dataset<T, D>(mut self, dataset: D) -> Self
51    where
52        T: Send + 'static,
53        D: Dataset<T>,
54    {
55        Arc::make_mut(&mut self.datasets).insert(dataset);
56        self
57    }
58
59    /// Fetches the request and runs `worker` on it, returning its outcome.
60    ///
61    /// Each run is a new request: running twice fetches twice, and adds to
62    /// the same queue and datasets.
63    pub async fn run<W: Worker<<MockSite as Backend>::Session>>(&self, worker: &W) -> Outcome {
64        let client = match self.site.connect().await {
65            Ok(client) => client,
66            Err(error) => return Outcome::Fail(error),
67        };
68        let mut request = self.seed.clone().into_request();
69        request.extensions_mut().extend(self.extensions.clone());
70        let queue = Queue::new(Arc::clone(&self.frontier)).child_of(&request);
71        let cx = Context::from_parts(request, client, queue, Arc::clone(&self.datasets));
72        worker.handle(cx).await
73    }
74
75    /// Returns the seeds queued so far, in order.
76    #[must_use]
77    pub fn queued(&self) -> Vec<Seed> {
78        self.frontier.snapshot().unwrap_or_default()
79    }
80
81    /// Returns the dataset for items of type `T`.
82    ///
83    /// # Errors
84    ///
85    /// Returns a [`Dataset`](crate::ErrorKind::Dataset) error if no dataset
86    /// is registered for `T`.
87    pub fn dataset<T: Send + 'static>(&self) -> Result<Data<T>> {
88        self.datasets.get()
89    }
90}
91
92impl fmt::Debug for Visit {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        f.debug_struct("Visit")
95            .field("seed", &self.seed)
96            .field("queued", &self.frontier.size_hint())
97            .finish_non_exhaustive()
98    }
99}