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}