@@ -384,6 +384,8 @@ module {:options "-functionSyntax:4"} DdbEncryptionTestVectors {
384384 BasicIoTestBatchWriteItem (c1, c2, globalRecords);
385385 BasicIoTestPutItem (c1, c2, globalRecords);
386386 BasicIoTestTransactWriteItems (c1, c2, globalRecords);
387+ BasicIoTestUpdateItem (c1, c2, globalRecords);
388+ BasicIoTestDeleteItem (c1, c2, globalRecords);
387389 BasicIoTestExecuteStatement (c1, c2);
388390 }
389391 }
@@ -835,6 +837,108 @@ module {:options "-functionSyntax:4"} DdbEncryptionTestVectors {
835837 BasicIoTestTransactGetItems (rClient, records);
836838 }
837839
840+ method BasicIoTestUpdateItem (writeConfig : TableConfig , readConfig : TableConfig , records : seq <Record >)
841+ {
842+ var wClient :- expect newGazelle (writeConfig);
843+ var rClient :- expect newGazelle (readConfig);
844+ WriteAllRecords (wClient, records);
845+
846+ // Update each record by appending "-updated" to the partition key
847+ for i := 0 to |records| {
848+ :- expect Need (HashName in records[i].item, "");
849+
850+ // Get the current value of the partition key
851+ var currentValue;
852+ currentValue := records[i]. item[HashName]. N;
853+
854+ // Append "-updated" to the current value
855+ var newValue := currentValue + "- updated";
856+
857+ // Create an update expression to update the partition key
858+ var updateExpr := "SET #pk = :val";
859+ var exprAttrNames := map ["#pk" := HashName];
860+ var exprAttrValues := map [":val" := DDB. AttributeValue. S (newValue)];
861+
862+ var updateInput := DDB. UpdateItemInput (
863+ TableName := TableName,
864+ Key := map[HashName := records[i].item[HashName]],
865+ UpdateExpression := Some(updateExpr),
866+ ExpressionAttributeNames := Some (exprAttrNames),
867+ ExpressionAttributeValues := Some (exprAttrValues),
868+ ReturnValues := None,
869+ ReturnConsumedCapacity := None,
870+ ReturnItemCollectionMetrics := None,
871+ ConditionExpression := None
872+ );
873+
874+ var updateSignedItemResult := wClient. UpdateItem (updateInput);
875+
876+ expect updateSignedItemResult. Failure?, "UpdateItem should have failed for signed item. ";
877+ // This error is of type DynamoDbEncryptionTransformsException
878+ // but AWS SDK wraps it into its own type for which customers should be unwrapping.
879+ // In test vectors, we still have to change the error from AWS SDK to dafny so it turns out to be OpaqueWithText.
880+ expect updateSignedItemResult. error. OpaqueWithText?, "Error should have been of type OpaqueWithText";
881+ var hasDynamoDbEncryptionTransformsException? := String. HasSubString (updateSignedItemResult.error.objMessage, "Update Expressions forbidden on signed attributes");
882+ expect hasDynamoDbEncryptionTransformsException?. Some?;
883+ }
884+ }
885+
886+ method BasicIoTestDeleteItem (writeConfig : TableConfig , readConfig : TableConfig , records : seq <Record >)
887+ {
888+ var wClient :- expect newGazelle (writeConfig);
889+ var rClient :- expect newGazelle (readConfig);
890+ WriteAllRecords (wClient, records);
891+
892+ // Only delete half of the records to verify selective deletion
893+ var deleteCount := |records| / 2;
894+
895+ for i := 0 to deleteCount {
896+ :- expect Need (HashName in records[i].item, "");
897+
898+ var deleteInput := DDB. DeleteItemInput (
899+ TableName := TableName,
900+ Key := map[HashName := records[i].item[HashName]],
901+ Expected := None,
902+ ReturnValues := None,
903+ ReturnConsumedCapacity := None,
904+ ReturnItemCollectionMetrics := None,
905+ ConditionExpression := None,
906+ ExpressionAttributeNames := None,
907+ ExpressionAttributeValues := None
908+ );
909+
910+ var _ :- expect wClient. DeleteItem (deleteInput);
911+ }
912+
913+ // Verify deletions were successful
914+ for i := 0 to |records| {
915+ :- expect Need (HashName in records[i].item, "");
916+
917+ var getInput := DDB. GetItemInput (
918+ TableName := TableName,
919+ Key := map[HashName := records[i].item[HashName]],
920+ AttributesToGet := None,
921+ ConsistentRead := None,
922+ ReturnConsumedCapacity := None,
923+ ProjectionExpression := None,
924+ ExpressionAttributeNames := None
925+ );
926+
927+ var out := rClient. GetItem (getInput);
928+
929+ if i < deleteCount {
930+ // Deleted items should not be found
931+ expect out. Success?;
932+ expect out. value. Item. None?, "Item " + String. Base10Int2String (i) + " should have been deleted";
933+ } else {
934+ // Non-deleted items should still exist
935+ expect out. Success?;
936+ expect out. value. Item. Some?;
937+ expect NormalizeItem (out.value.Item.value) == NormalizeItem (records[i].item);
938+ }
939+ }
940+ }
941+
838942 method BasicIoTestExecuteStatement (writeConfig : TableConfig , readConfig : TableConfig )
839943 {
840944 var wClient :- expect newGazelle (writeConfig);
0 commit comments