Skip to main content

rightsize_modules/
wiremock.rs

1//! A WireMock server container for stubbing HTTP dependencies in integration tests.
2//!
3//! ### Readiness — verified against a real 3.13.2 boot
4//!
5//! WireMock 3.x ships a dedicated `/__admin/health` endpoint (unlike some older 2.x
6//! builds, where `/__admin/mappings` was the only reliable 200). Verified directly
7//! against a real container:
8//!
9//! ```text
10//! $ curl http://127.0.0.1:<port>/__admin/health
11//! {"status":"healthy","message":"Wiremock is ok","version":"3.13.2","uptimeInSeconds":9,...}
12//! ```
13//!
14//! so this module waits on that endpoint rather than falling back to
15//! `/__admin/mappings`.
16//!
17//! No control characters were found in the image's baked env (checked via
18//! `docker image inspect`), and no `with_memory_limit` override was
19//! needed — the JVM boots comfortably on msb's default ~450M microVM RAM (observed
20//! ~5.5s IT round-trip on msb; a small embedded-Jetty app, not a JVM-heavy cluster
21//! like Pinot — no memory-ladder escalation was needed).
22//!
23//! This is the Rust ecosystem's missing piece too: there is no first-class
24//! in-process WireMock story for Rust integration tests, so this module fills a
25//! real gap.
26
27use rightsize::{Container, ContainerGuard, Result, Wait};
28
29const PORT: u16 = 8080;
30
31/// A WireMock stub-server container.
32pub struct WireMockContainer(Container);
33
34impl WireMockContainer {
35    /// Builds a container from the pinned default image (`wiremock/wiremock:3.13.2`).
36    pub fn new() -> Self {
37        Self::with_image("wiremock/wiremock:3.13.2")
38    }
39
40    /// Builds a container from a caller-chosen image.
41    pub fn with_image(image: &str) -> Self {
42        Self(
43            Container::new(image)
44                .with_exposed_ports(&[PORT])
45                .waiting_for(Wait::for_http("/__admin/health").for_port(PORT)),
46        )
47    }
48
49    /// Boots the container.
50    pub async fn start(self) -> Result<WireMockGuard> {
51        Ok(WireMockGuard(self.0.start().await?))
52    }
53}
54
55impl Default for WireMockContainer {
56    fn default() -> Self {
57        Self::new()
58    }
59}
60
61/// The running guard for a [`WireMockContainer`].
62pub struct WireMockGuard(ContainerGuard);
63
64impl WireMockGuard {
65    /// The stub server's base URI (mount stubbed paths under this).
66    pub fn base_url(&self) -> String {
67        format!(
68            "http://{}:{}",
69            self.0.host(),
70            self.0.get_mapped_port(PORT).unwrap()
71        )
72    }
73
74    /// The `/__admin` management API's base URI (stub CRUD, request journal, health).
75    pub fn admin_url(&self) -> String {
76        format!("{}/__admin", self.base_url())
77    }
78
79    /// Stops and removes the container, releasing its host port.
80    pub async fn stop(self) -> Result<()> {
81        self.0.stop().await
82    }
83}
84
85impl std::ops::Deref for WireMockGuard {
86    type Target = ContainerGuard;
87    fn deref(&self) -> &ContainerGuard {
88        &self.0
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn with_image_smoke() {
98        let _ = WireMockContainer::new();
99        let _ = WireMockContainer::with_image("wiremock/wiremock:3.13.2");
100    }
101}