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#[allow(missing_docs)]
8// 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
9#[derive(Default, Debug, Clone)]
10pub struct DynamoDb {
11    /// (remove if there is another variable)
12    /// Field is included to prevent this struct to be a unit struct.
13    /// This allows extending functionality (and thus further variables) without breaking changes
14    _priv: (),
15}
16
17impl Image for DynamoDb {
18    fn name(&self) -> &str {
19        NAME
20    }
21
22    fn tag(&self) -> &str {
23        TAG
24    }
25
26    fn ready_conditions(&self) -> Vec<WaitFor> {
27        vec![
28            WaitFor::message_on_stdout(
29                "Initializing DynamoDB Local with the following configuration",
30            ),
31            WaitFor::millis(DEFAULT_WAIT),
32        ]
33    }
34}
35
36#[cfg(test)]
37mod tests {
38    use std::fmt::Display;
39
40    use aws_config::{meta::region::RegionProviderChain, BehaviorVersion};
41    use aws_sdk_dynamodb::{
42        config::Credentials,
43        types::{
44            AttributeDefinition, KeySchemaElement, KeyType, ProvisionedThroughput,
45            ScalarAttributeType,
46        },
47        Client,
48    };
49    use testcontainers::core::IntoContainerPort;
50
51    use crate::{dynamodb_local::DynamoDb, testcontainers::runners::AsyncRunner};
52
53    #[tokio::test]
54    async fn dynamodb_local_create_table() -> Result<(), Box<dyn std::error::Error + 'static>> {
55        let _ = pretty_env_logger::try_init();
56        let node = DynamoDb::default().start().await?;
57        let host = node.get_host().await?;
58        let host_port = node.get_host_port_ipv4(8000.tcp()).await?;
59
60        let table_name = "books".to_string();
61
62        let key_schema = KeySchemaElement::builder()
63            .attribute_name("title".to_string())
64            .key_type(KeyType::Hash)
65            .build()
66            .unwrap();
67
68        let attribute_def = AttributeDefinition::builder()
69            .attribute_name("title".to_string())
70            .attribute_type(ScalarAttributeType::S)
71            .build()
72            .unwrap();
73
74        let provisioned_throughput = ProvisionedThroughput::builder()
75            .read_capacity_units(10)
76            .write_capacity_units(5)
77            .build()
78            .unwrap();
79
80        let dynamodb = build_dynamodb_client(host, host_port).await;
81        let create_table_result = dynamodb
82            .create_table()
83            .table_name(table_name)
84            .key_schema(key_schema)
85            .attribute_definitions(attribute_def)
86            .provisioned_throughput(provisioned_throughput)
87            .send()
88            .await;
89        assert!(create_table_result.is_ok());
90
91        let req = dynamodb.list_tables().limit(10);
92        let list_tables_result = req.send().await.unwrap();
93
94        assert_eq!(list_tables_result.table_names().len(), 1);
95        Ok(())
96    }
97
98    async fn build_dynamodb_client(host: impl Display, host_port: u16) -> Client {
99        let endpoint_uri = format!("http://{host}:{host_port}");
100        let region_provider = RegionProviderChain::default_provider().or_else("us-east-1");
101        let creds = Credentials::new("fakeKey", "fakeSecret", None, None, "test");
102
103        let shared_config = aws_config::defaults(BehaviorVersion::latest())
104            .region(region_provider)
105            .endpoint_url(endpoint_uri)
106            .credentials_provider(creds)
107            .load()
108            .await;
109
110        Client::new(&shared_config)
111    }
112}