testcontainers_modules/trufflesuite_ganachecli/
mod.rs

1use std::borrow::Cow;
2
3use testcontainers::{
4    core::{ContainerPort, WaitFor},
5    Image,
6};
7
8const NAME: &str = "trufflesuite/ganache-cli";
9const TAG: &str = "v6.1.3";
10
11/// Port that the [`Ganache CLI`] container has internally.
12/// Can be rebound externally via [`testcontainers::core::ImageExt::with_mapped_port`]
13///
14/// [Ganache CLI]: https://github.com/trufflesuite/ganache
15pub const GANACHE_CLI_PORT: ContainerPort = ContainerPort::Tcp(8545);
16
17/// # Module to work with the [`Ganache CLI`] inside of tests.
18///
19/// Starts an instance of Meilisearch.
20/// This module is based on the official [`trufflesuite/ganache-cli` docker image] documented in the [documentation].
21///
22/// # Example
23/// ```
24/// use testcontainers_modules::{
25///     testcontainers::runners::SyncRunner, trufflesuite_ganachecli,
26///     trufflesuite_ganachecli::GANACHE_CLI_PORT,
27/// };
28///
29/// let instance = trufflesuite_ganachecli::GanacheCli::default()
30///     .start()
31///     .unwrap();
32/// let url = format!(
33///     "http://{host_ip}:{host_port}",
34///     host_ip = instance.get_host().unwrap(),
35///     host_port = instance.get_host_port_ipv4(GANACHE_CLI_PORT).unwrap()
36/// );
37/// // do something with the started GanacheCli instance..
38/// ```
39///
40/// [Ganache CLI]: https://github.com/trufflesuite/ganache
41/// [documentation]: https://github.com/trufflesuite/ganache?tab=readme-ov-file#documentation
42/// [`trufflesuite/ganache-cli` docker image]: https://hub.docker.com/r/trufflesuite/ganache-cli/
43#[derive(Debug, Default, Clone)]
44pub struct GanacheCli {
45    cmd: GanacheCliCmd,
46}
47
48/// Options to pass to the `ganache-cli` command
49#[derive(Debug, Clone)]
50pub struct GanacheCliCmd {
51    /// Specify the network id ganache-core will use to identify itself (defaults to the current time or the network id of the forked blockchain if configured)
52    pub network_id: u32,
53    /// Specify the number of accounts to generate at startup
54    pub number_of_accounts: u32,
55    /// Use a bip39 mnemonic phrase for generating a PRNG seed, which is in turn used for hierarchical deterministic (HD) account generation.
56    pub mnemonic: String,
57}
58
59impl Default for GanacheCliCmd {
60    fn default() -> Self {
61        GanacheCliCmd {
62            network_id: 42,
63            number_of_accounts: 7,
64            mnemonic: "supersecure".to_string(),
65        }
66    }
67}
68
69impl IntoIterator for &GanacheCliCmd {
70    type Item = String;
71    type IntoIter = <Vec<String> as IntoIterator>::IntoIter;
72
73    fn into_iter(self) -> Self::IntoIter {
74        let mut args = Vec::new();
75
76        if !self.mnemonic.is_empty() {
77            args.push("-m".to_string());
78            args.push(self.mnemonic.to_string());
79        }
80
81        args.push("-a".to_string());
82        args.push(self.number_of_accounts.to_string());
83        args.push("-i".to_string());
84        args.push(self.network_id.to_string());
85
86        args.into_iter()
87    }
88}
89
90impl Image for GanacheCli {
91    fn name(&self) -> &str {
92        NAME
93    }
94
95    fn tag(&self) -> &str {
96        TAG
97    }
98
99    fn expose_ports(&self) -> &[ContainerPort] {
100        &[GANACHE_CLI_PORT]
101    }
102
103    fn ready_conditions(&self) -> Vec<WaitFor> {
104        vec![WaitFor::message_on_stdout("Listening on localhost:")]
105    }
106
107    fn cmd(&self) -> impl IntoIterator<Item = impl Into<Cow<'_, str>>> {
108        &self.cmd
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use testcontainers::runners::SyncRunner;
115
116    use super::*;
117
118    #[test]
119    fn trufflesuite_ganachecli_listaccounts() -> Result<(), Box<dyn std::error::Error + 'static>> {
120        let _ = pretty_env_logger::try_init();
121        let node = GanacheCli::default().start()?;
122        let host_ip = node.get_host()?;
123        let host_port = node.get_host_port_ipv4(GANACHE_CLI_PORT)?;
124
125        let response = reqwest::blocking::Client::new()
126            .post(format!("http://{host_ip}:{host_port}"))
127            .body(
128                serde_json::json!({
129                    "jsonrpc": "2.0",
130                    "method": "net_version",
131                    "params": [],
132                    "id": 1
133                })
134                .to_string(),
135            )
136            .header("content-type", "application/json")
137            .send()
138            .unwrap();
139
140        let response = response.text().unwrap();
141        let response: serde_json::Value = serde_json::from_str(&response).unwrap();
142
143        assert_eq!(response["result"], "42");
144        Ok(())
145    }
146}