1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 package software.amazon.awssdk.enhanced.dynamodb.internal.client;
17 
18 import static org.hamcrest.MatcherAssert.assertThat;
19 import static org.hamcrest.Matchers.containsInAnyOrder;
20 import static org.hamcrest.Matchers.is;
21 import static org.hamcrest.Matchers.sameInstance;
22 import static org.mockito.Mockito.verify;
23 import static software.amazon.awssdk.enhanced.dynamodb.internal.AttributeValues.stringValue;
24 
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Optional;
28 import java.util.stream.Collectors;
29 import org.junit.Test;
30 import org.junit.runner.RunWith;
31 import org.mockito.ArgumentCaptor;
32 import org.mockito.Mock;
33 import org.mockito.Mockito;
34 import org.mockito.junit.MockitoJUnitRunner;
35 import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClientExtension;
36 import software.amazon.awssdk.enhanced.dynamodb.Key;
37 import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
38 import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItem;
39 import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItemWithIndices;
40 import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItemWithSort;
41 import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.SecondaryIndexBean;
42 import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.SecondaryIndexMatchingTableKeyBean;
43 import software.amazon.awssdk.enhanced.dynamodb.model.CreateTableEnhancedRequest;
44 import software.amazon.awssdk.enhanced.dynamodb.model.EnhancedGlobalSecondaryIndex;
45 import software.amazon.awssdk.enhanced.dynamodb.model.EnhancedLocalSecondaryIndex;
46 import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
47 
48 @RunWith(MockitoJUnitRunner.class)
49 public class DefaultDynamoDbTableTest {
50     private static final String TABLE_NAME = "table-name";
51 
52     @Mock
53     private DynamoDbClient mockDynamoDbClient;
54 
55     @Mock
56     private DynamoDbEnhancedClientExtension mockDynamoDbEnhancedClientExtension;
57 
58     @Test
index_constructsCorrectMappedIndex()59     public void index_constructsCorrectMappedIndex() {
60         DefaultDynamoDbTable<FakeItemWithIndices> dynamoDbMappedTable =
61             new DefaultDynamoDbTable<>(mockDynamoDbClient,
62                                        mockDynamoDbEnhancedClientExtension,
63                                        FakeItemWithIndices.getTableSchema(),
64                                        TABLE_NAME);
65 
66         DefaultDynamoDbIndex<FakeItemWithIndices> dynamoDbMappedIndex = dynamoDbMappedTable.index("gsi_1");
67 
68         assertThat(dynamoDbMappedIndex.dynamoDbClient(), is(sameInstance(mockDynamoDbClient)));
69         assertThat(dynamoDbMappedIndex.mapperExtension(), is(sameInstance(mockDynamoDbEnhancedClientExtension)));
70         assertThat(dynamoDbMappedIndex.tableSchema(), is(sameInstance(FakeItemWithIndices.getTableSchema())));
71         assertThat(dynamoDbMappedIndex.indexName(), is("gsi_1"));
72     }
73 
74     @Test(expected = IllegalArgumentException.class)
index_invalidIndex_throwsIllegalArgumentException()75     public void index_invalidIndex_throwsIllegalArgumentException() {
76         DefaultDynamoDbTable<FakeItemWithIndices> dynamoDbMappedTable =
77             new DefaultDynamoDbTable<>(mockDynamoDbClient,
78                                        mockDynamoDbEnhancedClientExtension,
79                                        FakeItemWithIndices.getTableSchema(),
80                                        TABLE_NAME);
81 
82         dynamoDbMappedTable.index("invalid");
83     }
84 
85     @Test
keyFrom_primaryIndex_partitionAndSort()86     public void keyFrom_primaryIndex_partitionAndSort() {
87         FakeItemWithSort item = FakeItemWithSort.createUniqueFakeItemWithSort();
88         DefaultDynamoDbTable<FakeItemWithSort> dynamoDbMappedIndex =
89             new DefaultDynamoDbTable<>(mockDynamoDbClient,
90                                        mockDynamoDbEnhancedClientExtension,
91                                        FakeItemWithSort.getTableSchema(),
92                                        "test_table");
93 
94         Key key = dynamoDbMappedIndex.keyFrom(item);
95 
96         assertThat(key.partitionKeyValue(), is(stringValue(item.getId())));
97         assertThat(key.sortKeyValue(), is(Optional.of(stringValue(item.getSort()))));
98     }
99 
100     @Test
keyFrom_primaryIndex_partitionOnly()101     public void keyFrom_primaryIndex_partitionOnly() {
102         FakeItem item = FakeItem.createUniqueFakeItem();
103         DefaultDynamoDbTable<FakeItem> dynamoDbMappedIndex =
104             new DefaultDynamoDbTable<>(mockDynamoDbClient,
105                                        mockDynamoDbEnhancedClientExtension,
106                                        FakeItem.getTableSchema(),
107                                        "test_table");
108 
109         Key key = dynamoDbMappedIndex.keyFrom(item);
110 
111         assertThat(key.partitionKeyValue(), is(stringValue(item.getId())));
112         assertThat(key.sortKeyValue(), is(Optional.empty()));
113     }
114 
115     @Test
keyFrom_primaryIndex_partitionAndNullSort()116     public void keyFrom_primaryIndex_partitionAndNullSort() {
117         FakeItemWithSort item = FakeItemWithSort.createUniqueFakeItemWithoutSort();
118         DefaultDynamoDbTable<FakeItemWithSort> dynamoDbMappedIndex =
119             new DefaultDynamoDbTable<>(mockDynamoDbClient,
120                                        mockDynamoDbEnhancedClientExtension,
121                                        FakeItemWithSort.getTableSchema(),
122                                        "test_table");
123 
124         Key key = dynamoDbMappedIndex.keyFrom(item);
125 
126         assertThat(key.partitionKeyValue(), is(stringValue(item.getId())));
127         assertThat(key.sortKeyValue(), is(Optional.empty()));
128     }
129 
130     @Test
createTable_doesNotTreatPrimaryIndexAsAnyOfSecondaryIndexes()131     public void createTable_doesNotTreatPrimaryIndexAsAnyOfSecondaryIndexes() {
132         DefaultDynamoDbTable<FakeItem> dynamoDbMappedIndex =
133             Mockito.spy(new DefaultDynamoDbTable<>(mockDynamoDbClient,
134                                                    mockDynamoDbEnhancedClientExtension,
135                                                    FakeItem.getTableSchema(),
136                                                    "test_table"));
137 
138         dynamoDbMappedIndex.createTable();
139 
140         CreateTableEnhancedRequest request = captureCreateTableRequest(dynamoDbMappedIndex);
141 
142         assertThat(request.localSecondaryIndices().size(), is(0));
143         assertThat(request.globalSecondaryIndices().size(), is(0));
144     }
145 
146     @Test
createTable_groupsSecondaryIndexesExistingInTableSchema()147     public void createTable_groupsSecondaryIndexesExistingInTableSchema() {
148         DefaultDynamoDbTable<FakeItemWithIndices> dynamoDbMappedIndex =
149             Mockito.spy(new DefaultDynamoDbTable<>(mockDynamoDbClient,
150                                                    mockDynamoDbEnhancedClientExtension,
151                                                    FakeItemWithIndices.getTableSchema(),
152                                                    "test_table"));
153 
154         dynamoDbMappedIndex.createTable();
155 
156         CreateTableEnhancedRequest request = captureCreateTableRequest(dynamoDbMappedIndex);
157 
158         assertThat(request.localSecondaryIndices().size(), is(1));
159         Iterator<EnhancedLocalSecondaryIndex> lsiIterator = request.localSecondaryIndices().iterator();
160         assertThat(lsiIterator.next().indexName(), is("lsi_1"));
161 
162         assertThat(request.globalSecondaryIndices().size(), is(2));
163         List<String> globalIndicesNames = request.globalSecondaryIndices().stream()
164                                                  .map(EnhancedGlobalSecondaryIndex::indexName)
165                                                  .collect(Collectors.toList());
166         assertThat(globalIndicesNames, containsInAnyOrder("gsi_1", "gsi_2"));
167     }
168 
169     @Test
createTable_groupsSecondaryIndexesExistingInTableSchema_fromBeanTableSchema()170     public void createTable_groupsSecondaryIndexesExistingInTableSchema_fromBeanTableSchema() {
171         DefaultDynamoDbTable<SecondaryIndexBean> dynamoDbMappedIndex =
172             Mockito.spy(new DefaultDynamoDbTable<>(mockDynamoDbClient,
173                                                    mockDynamoDbEnhancedClientExtension,
174                                                    TableSchema.fromBean(SecondaryIndexBean.class),
175                                                    "test_table"));
176 
177         dynamoDbMappedIndex.createTable();
178 
179         CreateTableEnhancedRequest request = captureCreateTableRequest(dynamoDbMappedIndex);
180 
181         assertThat(request.localSecondaryIndices().size(), is(1));
182         assertThat(request.localSecondaryIndices().iterator().next().indexName(), is("lsi"));
183 
184         assertThat(request.globalSecondaryIndices().size(), is(1));
185         assertThat(request.globalSecondaryIndices().iterator().next().indexName(), is("gsi"));
186     }
187 
188     @Test
createTable_allowsGsiWithSamePartitionKeyAsDefaultPartitionKey_fromBeanTableSchema()189     public void createTable_allowsGsiWithSamePartitionKeyAsDefaultPartitionKey_fromBeanTableSchema() {
190         DefaultDynamoDbTable<SecondaryIndexMatchingTableKeyBean> dynamoDbMappedIndex =
191             Mockito.spy(new DefaultDynamoDbTable<>(mockDynamoDbClient,
192                                                    mockDynamoDbEnhancedClientExtension,
193                                                    TableSchema.fromBean(SecondaryIndexMatchingTableKeyBean.class),
194                                                    "test_table"));
195 
196         dynamoDbMappedIndex.createTable();
197 
198         CreateTableEnhancedRequest request = captureCreateTableRequest(dynamoDbMappedIndex);
199 
200         assertThat(request.localSecondaryIndices().size(), is(0));
201         assertThat(request.globalSecondaryIndices().size(), is(1));
202         assertThat(request.globalSecondaryIndices().iterator().next().indexName(), is("gsi"));
203     }
204 
captureCreateTableRequest(DefaultDynamoDbTable<T> index)205     private static <T> CreateTableEnhancedRequest captureCreateTableRequest(DefaultDynamoDbTable<T> index) {
206         ArgumentCaptor<CreateTableEnhancedRequest> createTableOperationCaptor =
207             ArgumentCaptor.forClass(CreateTableEnhancedRequest.class);
208         verify(index).createTable(createTableOperationCaptor.capture());
209         return createTableOperationCaptor.getValue();
210     }
211 }
212