testcontainers_modules/mongo/
mod.rs1use testcontainers::{
2 core::{CmdWaitFor, ExecCommand, WaitFor},
3 Image,
4};
5
6const NAME: &str = "mongo";
7const TAG: &str = "5.0.6";
8
9#[allow(missing_docs)]
10#[derive(Default, Debug, Clone)]
12enum InstanceKind {
13 #[default]
14 Standalone,
15 ReplSet,
16}
17
18#[allow(missing_docs)]
19#[derive(Default, Debug, Clone)]
21pub struct Mongo {
22 kind: InstanceKind,
23}
24
25impl Mongo {
26 #[allow(missing_docs)]
28 pub fn new() -> Self {
29 Self {
30 kind: InstanceKind::Standalone,
31 }
32 }
33 #[allow(missing_docs)]
35 pub fn repl_set() -> Self {
36 Self {
37 kind: InstanceKind::ReplSet,
38 }
39 }
40}
41
42impl Image for Mongo {
43 fn name(&self) -> &str {
44 NAME
45 }
46
47 fn tag(&self) -> &str {
48 TAG
49 }
50
51 fn ready_conditions(&self) -> Vec<WaitFor> {
52 vec![WaitFor::message_on_stdout("Waiting for connections")]
53 }
54
55 fn cmd(&self) -> impl IntoIterator<Item = impl Into<std::borrow::Cow<'_, str>>> {
56 match self.kind {
57 InstanceKind::Standalone => Vec::<String>::new(),
58 InstanceKind::ReplSet => vec!["--replSet".to_string(), "rs".to_string()],
59 }
60 }
61
62 fn exec_after_start(
63 &self,
64 _: testcontainers::core::ContainerState,
65 ) -> Result<Vec<ExecCommand>, testcontainers::TestcontainersError> {
66 match self.kind {
67 InstanceKind::Standalone => Ok(Default::default()),
68 InstanceKind::ReplSet => Ok(vec![ExecCommand::new(vec![
69 "mongosh".to_string(),
70 "--quiet".to_string(),
71 "--eval".to_string(),
72 "'rs.initiate()'".to_string(),
73 ])
74 .with_cmd_ready_condition(CmdWaitFor::message_on_stdout(
75 "Using a default configuration for the set",
76 ))
77 .with_container_ready_conditions(vec![WaitFor::message_on_stdout(
78 "Rebuilding PrimaryOnlyService due to stepUp",
79 )])]),
80 }
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use mongodb::*;
87 use testcontainers::{core::IntoContainerPort, runners::AsyncRunner};
88
89 use crate::mongo;
90
91 #[tokio::test]
92 async fn mongo_fetch_document() -> Result<(), Box<dyn std::error::Error + 'static>> {
93 let _ = pretty_env_logger::try_init();
94 let node = mongo::Mongo::default().start().await?;
95 let host_ip = node.get_host().await?;
96 let host_port = node.get_host_port_ipv4(27017.tcp()).await?;
97 let url = format!("mongodb://{host_ip}:{host_port}/");
98
99 let client: Client = Client::with_uri_str(&url).await.unwrap();
100 let db = client.database("some_db");
101 let coll = db.collection("some_coll");
102
103 let insert_one_result = coll.insert_one(bson::doc! { "x": 42 }).await.unwrap();
104 assert!(!insert_one_result
105 .inserted_id
106 .as_object_id()
107 .unwrap()
108 .to_hex()
109 .is_empty());
110
111 let find_one_result: bson::Document = coll
112 .find_one(bson::doc! { "x": 42 })
113 .await
114 .unwrap()
115 .unwrap();
116 assert_eq!(42, find_one_result.get_i32("x").unwrap());
117
118 Ok(())
119 }
120
121 #[tokio::test]
122 async fn mongo_repl_set_fetch_document() -> Result<(), Box<dyn std::error::Error + 'static>> {
123 let _ = pretty_env_logger::try_init();
124 let node = mongo::Mongo::repl_set().start().await?;
125 let host_ip = node.get_host().await?;
126 let host_port = node.get_host_port_ipv4(27017).await?;
127 let url = format!("mongodb://{host_ip}:{host_port}/?directConnection=true",);
128
129 let client: Client = Client::with_uri_str(url).await?;
130 let db = client.database("some_db");
131 let coll = db.collection("some-coll");
132
133 let mut session = client.start_session().await?;
134 session.start_transaction().await?;
135
136 let insert_one_result = coll
137 .insert_one(bson::doc! { "x": 42 })
138 .session(&mut session)
139 .await?;
140 assert!(!insert_one_result
141 .inserted_id
142 .as_object_id()
143 .unwrap()
144 .to_hex()
145 .is_empty());
146 session.commit_transaction().await?;
147
148 let find_one_result: bson::Document = coll
149 .find_one(bson::doc! { "x": 42 })
150 .await
151 .unwrap()
152 .unwrap();
153
154 assert_eq!(42, find_one_result.get_i32("x").unwrap());
155 Ok(())
156 }
157}