testcontainers_modules/valkey/
mod.rs1use std::{borrow::Cow, collections::BTreeMap};
2
3use testcontainers::{
4 core::{ContainerPort, WaitFor},
5 CopyDataSource, CopyToContainer, Image,
6};
7
8const NAME: &str = "valkey/valkey";
9const TAG: &str = "8.0.2-alpine";
10
11pub const VALKEY_PORT: ContainerPort = ContainerPort::Tcp(6379);
13
14#[derive(Debug, Default, Clone)]
46pub struct Valkey {
47 env_vars: BTreeMap<String, String>,
48 tag: Option<String>,
49 copy_to_container: Vec<CopyToContainer>,
50}
51
52impl Valkey {
53 pub fn latest() -> Self {
65 Self {
66 tag: Some("latest".to_string()),
67 ..Default::default()
68 }
69 }
70
71 pub fn with_valkey_extra_flags(self, valkey_extra_flags: &str) -> Self {
86 let mut env_vars = self.env_vars;
87 env_vars.insert(
88 "VALKEY_EXTRA_FLAGS".to_string(),
89 valkey_extra_flags.to_string(),
90 );
91 Self {
92 env_vars,
93 tag: self.tag,
94 copy_to_container: self.copy_to_container,
95 }
96 }
97
98 pub fn with_valkey_conf(self, valky_conf: impl Into<CopyDataSource>) -> Self {
113 let mut copy_to_container = self.copy_to_container;
114 copy_to_container.push(CopyToContainer::new(
115 valky_conf.into(),
116 "/usr/local/etc/valkey/valkey.conf",
117 ));
118 Self {
119 env_vars: self.env_vars,
120 tag: self.tag,
121 copy_to_container,
122 }
123 }
124}
125
126impl Image for Valkey {
127 fn name(&self) -> &str {
128 NAME
129 }
130
131 fn tag(&self) -> &str {
132 self.tag.as_deref().unwrap_or(TAG)
133 }
134
135 fn ready_conditions(&self) -> Vec<WaitFor> {
136 vec![WaitFor::message_on_stdout("Ready to accept connections")]
137 }
138
139 fn env_vars(
140 &self,
141 ) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
142 &self.env_vars
143 }
144
145 fn copy_to_sources(&self) -> impl IntoIterator<Item = &CopyToContainer> {
146 &self.copy_to_container
147 }
148
149 fn cmd(&self) -> impl IntoIterator<Item = impl Into<Cow<'_, str>>> {
150 if !self.copy_to_container.is_empty() {
151 vec!["valkey-server", "/usr/local/etc/valkey/valkey.conf"]
152 } else {
153 Vec::new()
154 }
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use std::collections::HashMap;
161
162 use redis::Commands;
163 use testcontainers::Image;
164
165 use crate::{
166 testcontainers::runners::SyncRunner,
167 valkey::{Valkey, TAG, VALKEY_PORT},
168 };
169
170 #[test]
171 fn valkey_fetch_an_integer() -> Result<(), Box<dyn std::error::Error + 'static>> {
172 let _ = pretty_env_logger::try_init();
173 let node = Valkey::default().start()?;
174
175 let tag = node.image().tag.clone();
176 assert_eq!(None, tag);
177 let tag_from_method = node.image().tag();
178 assert_eq!(TAG, tag_from_method);
179 assert_eq!(0, node.image().copy_to_container.len());
180
181 let host_ip = node.get_host()?;
182 let host_port = node.get_host_port_ipv4(VALKEY_PORT)?;
183 let url = format!("redis://{host_ip}:{host_port}");
184 let client = redis::Client::open(url.as_ref()).unwrap();
185 let mut con = client.get_connection().unwrap();
186
187 con.set::<_, _, ()>("my_key", 42).unwrap();
188 let result: i64 = con.get("my_key").unwrap();
189 assert_eq!(42, result);
190 Ok(())
191 }
192
193 #[test]
194 fn valkey_latest() -> Result<(), Box<dyn std::error::Error + 'static>> {
195 let _ = pretty_env_logger::try_init();
196 let node = Valkey::latest().start()?;
197
198 let tag = node.image().tag.clone();
199 assert_eq!(Some("latest".to_string()), tag);
200 let tag_from_method = node.image().tag();
201 assert_eq!("latest", tag_from_method);
202 assert_eq!(0, node.image().copy_to_container.len());
203
204 let host_ip = node.get_host()?;
205 let host_port = node.get_host_port_ipv4(VALKEY_PORT)?;
206 let url = format!("redis://{host_ip}:{host_port}");
207 let client = redis::Client::open(url.as_ref()).unwrap();
208 let mut con = client.get_connection().unwrap();
209
210 con.set::<_, _, ()>("my_key", 42).unwrap();
211 let result: i64 = con.get("my_key").unwrap();
212 assert_eq!(42, result);
213 Ok(())
214 }
215
216 #[test]
217 fn valkey_extra_flags() -> Result<(), Box<dyn std::error::Error + 'static>> {
218 let _ = pretty_env_logger::try_init();
219 let node = Valkey::default()
220 .with_valkey_extra_flags("--maxmemory 2mb")
221 .start()?;
222 let tag = node.image().tag.clone();
223 assert_eq!(None, tag);
224 let tag_from_method = node.image().tag();
225 assert_eq!(TAG, tag_from_method);
226 assert_eq!(0, node.image().copy_to_container.len());
227
228 let host_ip = node.get_host()?;
229 let host_port = node.get_host_port_ipv4(VALKEY_PORT)?;
230 let url = format!("redis://{host_ip}:{host_port}");
231
232 let client = redis::Client::open(url.as_ref()).unwrap();
233 let mut con = client.get_connection().unwrap();
234 let max_memory: HashMap<String, isize> = redis::cmd("CONFIG")
235 .arg("GET")
236 .arg("maxmemory")
237 .query(&mut con)
238 .unwrap();
239 let max = *max_memory.get("maxmemory").unwrap();
240 assert_eq!(2097152, max);
241 Ok(())
242 }
243
244 #[test]
245 fn valkey_conf() -> Result<(), Box<dyn std::error::Error + 'static>> {
246 let _ = pretty_env_logger::try_init();
247 let node = Valkey::default()
248 .with_valkey_conf("maxmemory 2mb".to_string().into_bytes())
249 .start()?;
250 let tag = node.image().tag.clone();
251 assert_eq!(None, tag);
252 let tag_from_method = node.image().tag();
253 assert_eq!(TAG, tag_from_method);
254 assert_eq!(1, node.image().copy_to_container.len());
255
256 let host_ip = node.get_host()?;
257 let host_port = node.get_host_port_ipv4(VALKEY_PORT)?;
258 let url = format!("redis://{host_ip}:{host_port}");
259
260 let client = redis::Client::open(url.as_ref()).unwrap();
261 let mut con = client.get_connection().unwrap();
262 let max_memory: HashMap<String, isize> = redis::cmd("CONFIG")
263 .arg("GET")
264 .arg("maxmemory")
265 .query(&mut con)
266 .unwrap();
267 let max = *max_memory.get("maxmemory").unwrap();
268 assert_eq!(2097152, max);
269 Ok(())
270 }
271}