pdk_test/services/
httpbin.rs

1// Copyright (c) 2025, Salesforce, Inc.,
2// All rights reserved.
3// For full license text, see the LICENSE.txt file
4
5//! HTTP testing service for integration testing
6//!
7//! This module provides predefined types and configurations for HTTPBin services
8//! in integration tests. HTTPBin is a simple HTTP request and response service
9//! useful for testing HTTP clients and APIs.
10//!
11
12use std::time::Duration;
13
14use crate::config::{Config, ContainerConfig};
15use crate::container::Container;
16use crate::error::TestError;
17use crate::image::Image;
18use crate::port::{Port, PortAccess, PortVisibility};
19use crate::probe::{MessageProbe, MessageSource};
20use crate::service::Service;
21
22const PORT: Port = 80;
23const SCHEMA: &str = "http";
24const HTTPBIN_IMAGE_NAME: &str = "kennethreitz/httpbin";
25
26/// Struct to manage the HTTPBin configuration.
27#[derive(Clone)]
28pub struct HttpBinConfig {
29    hostname: String,
30    version: String,
31    image_name: String,
32    visibility: PortVisibility,
33    timeout: Duration,
34}
35
36impl HttpBinConfig {
37    /// Get the hostname of the HTTPBin configuration.
38    pub fn hostname(&self) -> &str {
39        &self.hostname
40    }
41
42    /// Get the version of the HTTPBin configuration.
43    pub fn version(&self) -> &str {
44        &self.version
45    }
46
47    /// Get the image name of the HTTPBin configuration.
48    pub fn image_name(&self) -> &str {
49        &self.image_name
50    }
51
52    /// Get the visibility of the HTTPBin configuration.
53    pub fn visibility(&self) -> PortVisibility {
54        self.visibility
55    }
56
57    /// Get the timeout of the HTTPBin configuration.
58    pub fn timeout(&self) -> Duration {
59        self.timeout
60    }
61
62    /// Create a new HTTPBin configuration.
63    pub fn new() -> Self {
64        Self {
65            hostname: "backend".to_string(),
66            version: "latest".to_string(),
67            image_name: HTTPBIN_IMAGE_NAME.to_string(),
68            timeout: Duration::from_secs(30),
69            visibility: Default::default(),
70        }
71    }
72
73    /// Create a new HTTPBin configuration builder.
74    pub fn builder() -> HttpBinConfigBuilder {
75        HttpBinConfigBuilder::new()
76    }
77}
78
79/// Builder for [`HttpBinConfig`].
80pub struct HttpBinConfigBuilder {
81    config: HttpBinConfig,
82}
83
84impl HttpBinConfigBuilder {
85    fn new() -> Self {
86        Self {
87            config: HttpBinConfig::new(),
88        }
89    }
90
91    /// Set the hostname of the HTTPBin configuration.
92    pub fn hostname<T: Into<String>>(self, hostname: T) -> Self {
93        Self {
94            config: HttpBinConfig {
95                hostname: hostname.into(),
96                ..self.config
97            },
98        }
99    }
100
101    /// Set the version of the HTTPBin configuration.
102    pub fn version<T: Into<String>>(self, version: T) -> Self {
103        Self {
104            config: HttpBinConfig {
105                version: version.into(),
106                ..self.config
107            },
108        }
109    }
110
111    /// Set the image name of the HTTPBin configuration.
112    pub fn image_name<T: Into<String>>(self, image_name: T) -> Self {
113        Self {
114            config: HttpBinConfig {
115                image_name: image_name.into(),
116                ..self.config
117            },
118        }
119    }
120
121    /// Set the visibility of the HTTPBin configuration.
122    pub fn visibility(self, visibility: PortVisibility) -> Self {
123        Self {
124            config: HttpBinConfig {
125                visibility,
126                ..self.config
127            },
128        }
129    }
130
131    /// Set the timeout of the HTTPBin configuration.
132    pub fn timeout(self, timeout: Duration) -> Self {
133        Self {
134            config: HttpBinConfig {
135                timeout,
136                ..self.config
137            },
138        }
139    }
140
141    /// Build the HTTPBin configuration.
142    pub fn build(self) -> HttpBinConfig {
143        self.config
144    }
145}
146
147impl Default for HttpBinConfig {
148    fn default() -> Self {
149        Self::new()
150    }
151}
152
153impl Config for HttpBinConfig {
154    fn hostname(&self) -> &str {
155        &self.hostname
156    }
157
158    fn port(&self) -> Port {
159        PORT
160    }
161
162    fn schema(&self) -> &str {
163        SCHEMA
164    }
165
166    fn to_container_config(&self) -> Result<ContainerConfig, TestError> {
167        Ok(ContainerConfig::builder(
168            self.hostname.clone(),
169            Image::from_repository(self.image_name()).with_version(self.version()),
170        )
171        .ports([PortAccess::new(PORT, self.visibility)])
172        .readiness(
173            MessageProbe::builder("Using worker: gevent")
174                .timeout(Duration::from_secs(30))
175                .source(MessageSource::StdErr)
176                .build(),
177        )
178        .build())
179    }
180}
181
182/// Represents a `HttpBin` service instance.
183pub struct HttpBin {
184    socket: Option<String>,
185    address: Option<String>,
186}
187
188impl HttpBin {
189    /// Returns the socket for configuring the HttpBin service.
190    pub fn socket(&self) -> Option<&str> {
191        self.socket.as_deref()
192    }
193
194    /// Returns the address for configuring the HttpBin service.
195    pub fn address(&self) -> Option<&str> {
196        self.address.as_deref()
197    }
198}
199
200impl Service for HttpBin {
201    type Config = HttpBinConfig;
202
203    fn new(_config: &Self::Config, container: &Container) -> Self {
204        let socket = container.socket(PORT).map(String::from);
205        let address = socket.as_ref().map(|s| format!("http://{s}"));
206        Self { socket, address }
207    }
208}