testcontainers_modules/dynamodb_local/
mod.rs

1use testcontainers::{core::WaitFor, Image};
2
3const NAME: &str = "amazon/dynamodb-local";
4const TAG: &str = "2.0.0";
5const DEFAULT_WAIT: u64 = 3000;
6
7/// Module to work with [`DynamoDB Local`] inside of tests.
8///
9/// Starts an instance of DynamoDB Local based on the official [`DynamoDB Local docker image`].
10///
11/// This module provides a local DynamoDB instance for testing purposes, which is compatible
12/// with the AWS DynamoDB API.
13/// The container exposes port `8000` by default.
14///
15/// # Example
16/// ```
17/// use testcontainers_modules::{dynamodb_local::DynamoDb, testcontainers::runners::AsyncRunner};
18///
19/// # async fn example() -> Result<(), Box<dyn std::error::Error + 'static>> {
20/// let dynamodb_instance = DynamoDb::default().start().await?;
21/// let host = dynamodb_instance.get_host().await?;
22/// let port = dynamodb_instance.get_host_port_ipv4(8000).await?;
23///
24/// // Use the DynamoDB endpoint at http://{host}:{port}
25/// # Ok(())
26/// # }
27/// ```
28///
29/// [`DynamoDB Local`]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.html
30/// [`DynamoDB Local docker image`]: https://hub.docker.com/r/amazon/dynamodb-local
31#[derive(Default, Debug, Clone)]
32pub struct DynamoDb {
33    /// (remove if there is another variable)
34    /// Field is included to prevent this struct to be a unit struct.
35    /// This allows extending functionality (and thus further variables) without breaking changes
36    _priv: (),
37}
38
39impl Image for DynamoDb {
40    fn name(&self) -> &str {
41        NAME
42    }
43
44    fn tag(&self) -> &str {
45        TAG
46    }
47
48    fn ready_conditions(&self) -> Vec<WaitFor> {
49        vec![
50            WaitFor::message_on_stdout(
51                "Initializing DynamoDB Local with the following configuration",
52            ),
53            WaitFor::millis(DEFAULT_WAIT),
54        ]
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use std::fmt::Display;
61
62    use aws_config::{meta::region::RegionProviderChain, BehaviorVersion};
63    use aws_sdk_dynamodb::{
64        config::Credentials,
65        types::{
66            AttributeDefinition, KeySchemaElement, KeyType, ProvisionedThroughput,
67            ScalarAttributeType,
68        },
69        Client,
70    };
71    use testcontainers::core::IntoContainerPort;
72
73    use crate::{dynamodb_local::DynamoDb, testcontainers::runners::AsyncRunner};
74
75    #[tokio::test]
76    async fn dynamodb_local_create_table() -> Result<(), Box<dyn std::error::Error + 'static>> {
77        let _ = pretty_env_logger::try_init();
78        let node = DynamoDb::default().start().await?;
79        let host = node.get_host().await?;
80        let host_port = node.get_host_port_ipv4(8000.tcp()).await?;
81
82        let table_name = "books".to_string();
83
84        let key_schema = KeySchemaElement::builder()
85            .attribute_name("title".to_string())
86            .key_type(KeyType::Hash)
87            .build()
88            .unwrap();
89
90        let attribute_def = AttributeDefinition::builder()
91            .attribute_name("title".to_string())
92            .attribute_type(ScalarAttributeType::S)
93            .build()
94            .unwrap();
95
96        let provisioned_throughput = ProvisionedThroughput::builder()
97            .read_capacity_units(10)
98            .write_capacity_units(5)
99            .build()
100            .unwrap();
101
102        let dynamodb = build_dynamodb_client(host, host_port).await;
103        let create_table_result = dynamodb
104            .create_table()
105            .table_name(table_name)
106            .key_schema(key_schema)
107            .attribute_definitions(attribute_def)
108            .provisioned_throughput(provisioned_throughput)
109            .send()
110            .await;
111        assert!(create_table_result.is_ok());
112
113        let req = dynamodb.list_tables().limit(10);
114        let list_tables_result = req.send().await.unwrap();
115
116        assert_eq!(list_tables_result.table_names().len(), 1);
117        Ok(())
118    }
119
120    async fn build_dynamodb_client(host: impl Display, host_port: u16) -> Client {
121        let endpoint_uri = format!("http://{host}:{host_port}");
122        let region_provider = RegionProviderChain::default_provider().or_else("us-east-1");
123        let creds = Credentials::new("fakeKey", "fakeSecret", None, None, "test");
124
125        let shared_config = aws_config::defaults(BehaviorVersion::latest())
126            .region(region_provider)
127            .endpoint_url(endpoint_uri)
128            .credentials_provider(creds)
129            .load()
130            .await;
131
132        Client::new(&shared_config)
133    }
134}