View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2012-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.jcabi.dynamo;
6   
7   import com.jcabi.immutable.Array;
8   import java.io.IOException;
9   import java.util.Collections;
10  import java.util.NoSuchElementException;
11  import org.hamcrest.MatcherAssert;
12  import org.hamcrest.Matchers;
13  import org.junit.jupiter.api.Assertions;
14  import org.junit.jupiter.api.Test;
15  import org.mockito.Mockito;
16  import software.amazon.awssdk.core.exception.SdkClientException;
17  import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
18  import software.amazon.awssdk.services.dynamodb.model.AttributeAction;
19  import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
20  import software.amazon.awssdk.services.dynamodb.model.AttributeValueUpdate;
21  import software.amazon.awssdk.services.dynamodb.model.ConsumedCapacity;
22  import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
23  import software.amazon.awssdk.services.dynamodb.model.GetItemResponse;
24  import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
25  import software.amazon.awssdk.services.dynamodb.model.UpdateItemResponse;
26  
27  /**
28   * Test case for {@link AwsItem}.
29   * @since 0.21
30   */
31  @SuppressWarnings("PMD.TooManyMethods")
32  final class AwsItemTest {
33  
34      @Test
35      void comparesToItself() {
36          final Credentials creds = new Credentials.Simple("key", "secret");
37          final AwsTable table = new AwsTable(
38              creds, new Region.Simple(creds), "table-name"
39          );
40          final AwsFrame frame = new AwsFrame(creds, table, table.name());
41          MatcherAssert.assertThat(
42              "should equal to itself",
43              new AwsItem(
44                  creds, frame, table.name(),
45                  new Attributes(), new Array<>()
46              ),
47              Matchers.equalTo(
48                  new AwsItem(
49                      creds, frame, table.name(),
50                      new Attributes(), new Array<>()
51                  )
52              )
53          );
54      }
55  
56      @Test
57      void checksPreloadedAttribute() throws IOException {
58          MatcherAssert.assertThat(
59              "did not find pre-loaded attribute",
60              new AwsItem(
61                  Mockito.mock(Credentials.class),
62                  Mockito.mock(AwsFrame.class),
63                  "t\u00e9st-tbl",
64                  new Attributes().with(
65                      "k\u00e9y",
66                      AttributeValue.builder().s("v\u00e1l").build()
67                  ),
68                  new Array<>("k\u00e9y")
69              ).has("k\u00e9y"),
70              Matchers.is(true)
71          );
72      }
73  
74      @Test
75      void returnsPreloadedAttribute() throws IOException {
76          MatcherAssert.assertThat(
77              "did not return pre-loaded attribute value",
78              new AwsItem(
79                  Mockito.mock(Credentials.class),
80                  Mockito.mock(AwsFrame.class),
81                  "t\u00e9st-tbl",
82                  new Attributes().with(
83                      "n\u00e4me",
84                      AttributeValue.builder().s("d\u00e4ta").build()
85                  ),
86                  new Array<>("n\u00e4me")
87              ).get("n\u00e4me"),
88              Matchers.equalTo(
89                  AttributeValue.builder().s("d\u00e4ta").build()
90              )
91          );
92      }
93  
94      @Test
95      void fetchesAttributeFromDynamo() throws IOException {
96          final Credentials creds = Mockito.mock(Credentials.class);
97          final DynamoDbClient aws = Mockito.mock(DynamoDbClient.class);
98          Mockito.doReturn(aws).when(creds).aws();
99          final String attr = "\u00e4ttr";
100         Mockito.doReturn(
101             GetItemResponse.builder()
102                 .item(
103                     Collections.singletonMap(
104                         attr,
105                         AttributeValue.builder().s("r\u00e9sult").build()
106                     )
107                 )
108                 .consumedCapacity(
109                     ConsumedCapacity.builder().capacityUnits(1.0).build()
110                 )
111                 .build()
112         ).when(aws).getItem(Mockito.any(GetItemRequest.class));
113         MatcherAssert.assertThat(
114             "did not fetch attribute from DynamoDB",
115             new AwsItem(
116                 creds, Mockito.mock(AwsFrame.class), "f\u00e9tch-tbl",
117                 new Attributes().with(
118                     "pk",
119                     AttributeValue.builder().s("f\u00e9tch-pk").build()
120                 ),
121                 new Array<>("pk")
122             ).get(attr),
123             Matchers.equalTo(
124                 AttributeValue.builder().s("r\u00e9sult").build()
125             )
126         );
127     }
128 
129     @Test
130     void checksExistenceViaDynamo() throws IOException {
131         final Credentials creds = Mockito.mock(Credentials.class);
132         final DynamoDbClient aws = Mockito.mock(DynamoDbClient.class);
133         Mockito.doReturn(aws).when(creds).aws();
134         final String attr = "ch\u00e9ck";
135         Mockito.doReturn(
136             GetItemResponse.builder()
137                 .item(
138                     Collections.singletonMap(
139                         attr,
140                         AttributeValue.builder().s("pr\u00e9sent").build()
141                     )
142                 )
143                 .consumedCapacity(
144                     ConsumedCapacity.builder().capacityUnits(1.0).build()
145                 )
146                 .build()
147         ).when(aws).getItem(Mockito.any(GetItemRequest.class));
148         MatcherAssert.assertThat(
149             "did not detect attribute existence via DynamoDB",
150             new AwsItem(
151                 creds, Mockito.mock(AwsFrame.class), "h\u00e4s-tbl",
152                 new Attributes().with(
153                     "pk",
154                     AttributeValue.builder().s("h\u00e4s-pk").build()
155                 ),
156                 new Array<>("pk")
157             ).has(attr),
158             Matchers.is(true)
159         );
160     }
161 
162     @Test
163     void updatesAttribute() throws IOException {
164         final Credentials creds = Mockito.mock(Credentials.class);
165         final DynamoDbClient aws = Mockito.mock(DynamoDbClient.class);
166         Mockito.doReturn(aws).when(creds).aws();
167         final String attr = "\u00fcpd";
168         Mockito.doReturn(
169             UpdateItemResponse.builder()
170                 .attributes(
171                     Collections.singletonMap(
172                         attr,
173                         AttributeValue.builder().s("n\u00e9w").build()
174                     )
175                 )
176                 .consumedCapacity(
177                     ConsumedCapacity.builder().capacityUnits(1.0).build()
178                 )
179                 .build()
180         ).when(aws).updateItem(Mockito.any(UpdateItemRequest.class));
181         MatcherAssert.assertThat(
182             "did not return updated attributes",
183             new AwsItem(
184                 creds, Mockito.mock(AwsFrame.class), "\u00fcpd-tbl",
185                 new Attributes().with(
186                     "pk",
187                     AttributeValue.builder().s("\u00fcpd-pk").build()
188                 ),
189                 new Array<>("pk")
190             ).put(
191                 attr,
192                 AttributeValueUpdate.builder()
193                     .value(
194                         AttributeValue.builder().s("n\u00e9w").build()
195                     )
196                     .action(AttributeAction.PUT)
197                     .build()
198             ),
199             Matchers.hasEntry(
200                 Matchers.equalTo(attr),
201                 Matchers.equalTo(
202                     AttributeValue.builder().s("n\u00e9w").build()
203                 )
204             )
205         );
206     }
207 
208     @Test
209     void throwsOnAbsentAttribute() throws IOException {
210         final DynamoDbClient aws = Mockito.mock(DynamoDbClient.class);
211         Mockito.doReturn(AwsItemTest.empty())
212             .when(aws).getItem(Mockito.any(GetItemRequest.class));
213         final Item item = new AwsItem(
214             AwsItemTest.mocked(aws),
215             Mockito.mock(AwsFrame.class), "abs\u00e9nt-tbl",
216             new Attributes().with(
217                 "pk",
218                 AttributeValue.builder().s("abs\u00e9nt-pk").build()
219             ),
220             new Array<>("pk")
221         );
222         Assertions.assertThrows(
223             NoSuchElementException.class,
224             () -> item.get("m\u00efssing")
225         );
226     }
227 
228     @Test
229     void wrapsExceptionOnGet() {
230         final DynamoDbClient aws = Mockito.mock(DynamoDbClient.class);
231         Mockito.doThrow(SdkClientException.create("b\u00f6om"))
232             .when(aws).getItem(Mockito.any(GetItemRequest.class));
233         final Item item = new AwsItem(
234             AwsItemTest.mocked(aws),
235             Mockito.mock(AwsFrame.class), "g\u00e9t-err",
236             new Attributes().with(
237                 "pk",
238                 AttributeValue.builder().s("g\u00e9t-pk").build()
239             ),
240             new Array<>("pk")
241         );
242         Assertions.assertThrows(
243             IOException.class,
244             () -> item.get("f\u00e4il")
245         );
246     }
247 
248     @Test
249     void wrapsExceptionOnHas() {
250         final DynamoDbClient aws = Mockito.mock(DynamoDbClient.class);
251         Mockito.doThrow(SdkClientException.create("b\u00f6om"))
252             .when(aws).getItem(Mockito.any(GetItemRequest.class));
253         final Item item = new AwsItem(
254             AwsItemTest.mocked(aws),
255             Mockito.mock(AwsFrame.class), "h\u00e4s-err",
256             new Attributes().with(
257                 "pk",
258                 AttributeValue.builder().s("h\u00e4s-pk").build()
259             ),
260             new Array<>("pk")
261         );
262         Assertions.assertThrows(
263             IOException.class,
264             () -> item.has("f\u00e4il")
265         );
266     }
267 
268     @Test
269     void wrapsExceptionOnPut() {
270         final DynamoDbClient aws = Mockito.mock(DynamoDbClient.class);
271         Mockito.doThrow(SdkClientException.create("b\u00f6om"))
272             .when(aws).updateItem(Mockito.any(UpdateItemRequest.class));
273         final Item item = new AwsItem(
274             AwsItemTest.mocked(aws),
275             Mockito.mock(AwsFrame.class), "p\u00fct-err",
276             new Attributes().with(
277                 "pk",
278                 AttributeValue.builder().s("p\u00fct-pk").build()
279             ),
280             new Array<>("pk")
281         );
282         Assertions.assertThrows(
283             IOException.class,
284             () -> item.put(
285                 new AttributeUpdates().with(
286                     "f\u00e4il",
287                     AttributeValueUpdate.builder()
288                         .value(
289                             AttributeValue.builder().s("v\u00e1l").build()
290                         )
291                         .action(AttributeAction.PUT)
292                         .build()
293                 )
294             )
295         );
296     }
297 
298     @Test
299     void distinguishesDifferentItems() {
300         final Credentials creds = new Credentials.Simple(
301             "k\u00e9y1", "s\u00e9cret1"
302         );
303         final AwsTable first = new AwsTable(
304             creds, new Region.Simple(creds), "t\u00e4ble-one"
305         );
306         final AwsTable second = new AwsTable(
307             creds, new Region.Simple(creds), "t\u00e4ble-two"
308         );
309         MatcherAssert.assertThat(
310             "items with different tables should not be equal",
311             new AwsItem(
312                 creds,
313                 new AwsFrame(creds, first, first.name()),
314                 first.name(),
315                 new Attributes(),
316                 new Array<>()
317             ),
318             Matchers.not(
319                 Matchers.equalTo(
320                     new AwsItem(
321                         creds,
322                         new AwsFrame(creds, second, second.name()),
323                         second.name(),
324                         new Attributes(),
325                         new Array<>()
326                     )
327                 )
328             )
329         );
330     }
331 
332     @Test
333     void returnsFrame() {
334         MatcherAssert.assertThat(
335             "did not return frame instance",
336             new AwsItem(
337                 Mockito.mock(Credentials.class),
338                 Mockito.mock(AwsFrame.class),
339                 "fr\u00e4me-tbl",
340                 new Attributes(),
341                 new Array<>()
342             ).frame(),
343             Matchers.instanceOf(Frame.class)
344         );
345     }
346 
347     /**
348      * Creates a Credentials mock that returns the given DynamoDbClient.
349      * @param aws DynamoDbClient mock
350      * @return Credentials mock
351      */
352     private static Credentials mocked(final DynamoDbClient aws) {
353         final Credentials creds = Mockito.mock(Credentials.class);
354         Mockito.doReturn(aws).when(creds).aws();
355         return creds;
356     }
357 
358     /**
359      * Creates an empty GetItemResponse with consumed capacity.
360      * @return GetItemResponse with no items
361      */
362     private static GetItemResponse empty() {
363         return GetItemResponse.builder()
364             .item(Collections.emptyMap())
365             .consumedCapacity(
366                 ConsumedCapacity.builder().capacityUnits(1.0).build()
367             )
368             .build();
369     }
370 
371 }