1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
use testcontainers::{core::WaitFor, Image};

const NAME: &str = "amazon/dynamodb-local";
const TAG: &str = "2.0.0";
const DEFAULT_WAIT: u64 = 3000;

#[derive(Default, Debug)]
pub struct DynamoDb;

impl Image for DynamoDb {
    type Args = ();

    fn name(&self) -> String {
        NAME.to_owned()
    }

    fn tag(&self) -> String {
        TAG.to_owned()
    }

    fn ready_conditions(&self) -> Vec<WaitFor> {
        vec![
            WaitFor::message_on_stdout(
                "Initializing DynamoDB Local with the following configuration",
            ),
            WaitFor::millis(DEFAULT_WAIT),
        ]
    }
}

#[cfg(test)]
mod tests {
    use aws_config::{meta::region::RegionProviderChain, BehaviorVersion};
    use aws_sdk_dynamodb::{
        config::Credentials,
        types::{
            AttributeDefinition, KeySchemaElement, KeyType, ProvisionedThroughput,
            ScalarAttributeType,
        },
        Client,
    };
    use testcontainers::clients;

    use crate::dynamodb_local::DynamoDb;

    #[tokio::test]
    async fn dynamodb_local_create_table() {
        let _ = pretty_env_logger::try_init();
        let docker = clients::Cli::default();
        let node = docker.run(DynamoDb);
        let host_port = node.get_host_port_ipv4(8000);

        let table_name = "books".to_string();

        let key_schema = KeySchemaElement::builder()
            .attribute_name("title".to_string())
            .key_type(KeyType::Hash)
            .build()
            .unwrap();

        let attribute_def = AttributeDefinition::builder()
            .attribute_name("title".to_string())
            .attribute_type(ScalarAttributeType::S)
            .build()
            .unwrap();

        let provisioned_throughput = ProvisionedThroughput::builder()
            .read_capacity_units(10)
            .write_capacity_units(5)
            .build()
            .unwrap();

        let dynamodb = build_dynamodb_client(host_port).await;
        let create_table_result = dynamodb
            .create_table()
            .table_name(table_name)
            .key_schema(key_schema)
            .attribute_definitions(attribute_def)
            .provisioned_throughput(provisioned_throughput)
            .send()
            .await;
        assert!(create_table_result.is_ok());

        let req = dynamodb.list_tables().limit(10);
        let list_tables_result = req.send().await.unwrap();

        assert_eq!(list_tables_result.table_names().len(), 1);
    }

    async fn build_dynamodb_client(host_port: u16) -> Client {
        let endpoint_uri = format!("http://127.0.0.1:{host_port}");
        let region_provider = RegionProviderChain::default_provider().or_else("us-east-1");
        let creds = Credentials::new("fakeKey", "fakeSecret", None, None, "test");

        let shared_config = aws_config::defaults(BehaviorVersion::latest())
            .region(region_provider)
            .endpoint_url(endpoint_uri)
            .credentials_provider(creds)
            .load()
            .await;

        Client::new(&shared_config)
    }
}