testcontainers_modules/mongo/
mod.rs

1use 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// not having docs here is currently allowed to address the missing docs problem one place at a time. Helping us by documenting just one of these places helps other devs tremendously
11#[derive(Default, Debug, Clone)]
12enum InstanceKind {
13    #[default]
14    Standalone,
15    ReplSet,
16}
17
18#[allow(missing_docs)]
19// not having docs here is currently allowed to address the missing docs problem one place at a time. Helping us by documenting just one of these places helps other devs tremendously
20#[derive(Default, Debug, Clone)]
21pub struct Mongo {
22    kind: InstanceKind,
23}
24
25impl Mongo {
26    // not having docs here is currently allowed to address the missing docs problem one place at a time. Helping us by documenting just one of these places helps other devs tremendously
27    #[allow(missing_docs)]
28    pub fn new() -> Self {
29        Self {
30            kind: InstanceKind::Standalone,
31        }
32    }
33    // not having docs here is currently allowed to address the missing docs problem one place at a time. Helping us by documenting just one of these places helps other devs tremendously
34    #[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}