Skip to content

chore(dafny): Add Update and delete test #1942

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Jun 20, 2025
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
482e76b
Update and delete test:
rishav-karanjit Jun 11, 2025
7210baf
auto commit
rishav-karanjit Jun 11, 2025
9353e17
auto commit
rishav-karanjit Jun 12, 2025
8757192
auto commit
rishav-karanjit Jun 12, 2025
83a739f
Merge branch 'main' into rishav/addUpdateDeleteTest
rishav-karanjit Jun 12, 2025
f185c1e
auto commit
rishav-karanjit Jun 12, 2025
cd82fb9
Merge branch 'rishav/addUpdateDeleteTest' of https://github.com/aws/a…
rishav-karanjit Jun 12, 2025
9582321
auto commit
rishav-karanjit Jun 12, 2025
b8c918e
auto commit
rishav-karanjit Jun 13, 2025
c578e37
Merge branch 'main' into rishav/addUpdateDeleteTest
rishav-karanjit Jun 13, 2025
eeee4b3
Add ReturnValues
rishav-karanjit Jun 16, 2025
4a29b02
Add ReturnValues
rishav-karanjit Jun 16, 2025
fa7b783
Merge branch 'rishav/addUpdateDeleteTest' of https://github.com/aws/a…
rishav-karanjit Jun 16, 2025
2a9c145
auto commit
rishav-karanjit Jun 16, 2025
e7c6dd3
remove commented code
rishav-karanjit Jun 16, 2025
cda4f5e
formatting
rishav-karanjit Jun 16, 2025
956f4a8
Update TestVectors/dafny/DDBEncryption/src/TestVectors.dfy
rishav-karanjit Jun 16, 2025
93af3cc
WithoutConditionExpressionCase
rishav-karanjit Jun 16, 2025
db23d9e
Merge branch 'rishav/addUpdateDeleteTest' of https://github.com/aws/a…
rishav-karanjit Jun 16, 2025
b79963f
BasicIoTestDeleteItemWithoutConditionExpression
rishav-karanjit Jun 16, 2025
ad60db0
auto commit
rishav-karanjit Jun 17, 2025
13a5649
auto commit
rishav-karanjit Jun 17, 2025
ca0b0fb
verify
rishav-karanjit Jun 17, 2025
f222c89
Update TestVectors/dafny/DDBEncryption/src/TestVectors.dfy
rishav-karanjit Jun 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions TestVectors/dafny/DDBEncryption/src/TestVectors.dfy
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,12 @@ module {:options "-functionSyntax:4"} DdbEncryptionTestVectors {
BasicIoTestBatchWriteItem(c1, c2, globalRecords);
BasicIoTestPutItem(c1, c2, globalRecords);
BasicIoTestTransactWriteItems(c1, c2, globalRecords);
BasicIoTestUpdateItem(c1, c2, globalRecords, "One");
BasicIoTestUpdateItem(c1, c2, globalRecords, "Two");
BasicIoTestDeleteItem(c1, c2, globalRecords, "One", "Uno");
BasicIoTestDeleteItem(c1, c2, globalRecords, "Two", "Dos");
BasicIoTestDeleteItemWithoutConditionExpression(c1, c2, globalRecords, "One", "Uno");
BasicIoTestDeleteItemWithoutConditionExpression(c1, c2, globalRecords, "Two", "Dos");
BasicIoTestExecuteStatement(c1, c2);
BasicIoTestExecuteTransaction(c1, c2);
BasicIoTestBatchExecuteStatement(c1, c2);
Expand Down Expand Up @@ -844,6 +850,108 @@ module {:options "-functionSyntax:4"} DdbEncryptionTestVectors {
BasicIoTestTransactGetItems(rClient, records);
}

method BasicIoTestUpdateItem(writeConfig : TableConfig, readConfig : TableConfig, records : seq<Record>, attributeToUpdate: DDB.AttributeName)
{
var wClient, rClient := SetupTestTable(writeConfig, readConfig);
WriteAllRecords(wClient, records);
// Update each record by appending "updated" to the partition key
for i := 0 to |records| {
var newValue := "updated";
// Create an update expression to update the partition key
var updateExpr := "SET #att = :val";
expect attributeToUpdate in writeConfig.config.attributeActionsOnEncrypt, "`attributeToUpdate` not in attributeActionsOnEncrypt";
var exprAttrNames := map["#att" := attributeToUpdate];
var exprAttrValues := map[":val" := DDB.AttributeValue.S(newValue)];
expect HashName in records[i].item, "`HashName` is not in records.";
var updateInput := DDB.UpdateItemInput(
TableName := TableName,
Key := map[HashName := records[i].item[HashName]],
UpdateExpression := Some(updateExpr),
ExpressionAttributeNames := Some(exprAttrNames),
ExpressionAttributeValues := Some(exprAttrValues),
ReturnValues := None,
ReturnConsumedCapacity := None,
ReturnItemCollectionMetrics := None,
ConditionExpression := None
);
var updateResult := wClient.UpdateItem(updateInput);
if writeConfig.config.attributeActionsOnEncrypt[attributeToUpdate] == SE.ENCRYPT_AND_SIGN || writeConfig.config.attributeActionsOnEncrypt[attributeToUpdate] == SE.SIGN_ONLY {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also check for SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT here? I think yes, even if the item under test doesn't have that attribute

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT is not here because delete operation is not forbidden on signed attributes. Its only forbidden when the condition attribute contains encrypted attribute. https://github.com/aws/aws-database-encryption-sdk-dynamodb/blob/main/specification/dynamodb-encryption-client/ddb-support.md#testconditionexpression

expect updateResult.Failure?, "UpdateItem should have failed for signed item.";
// This error is of type DynamoDbEncryptionTransformsException
// but AWS SDK wraps it into its own type for which customers should be unwrapping.
// In test vectors, we still have to change the error from AWS SDK to dafny so it turns out to be OpaqueWithText.
expect updateResult.error.OpaqueWithText?, "Error should have been of type OpaqueWithText";
var hasDynamoDbEncryptionTransformsException? := String.HasSubString(updateResult.error.objMessage, "Update Expressions forbidden on signed attributes");
expect hasDynamoDbEncryptionTransformsException?.Some?, "Error might is not be of type DynamoDbEncryptionTransformsException";
} else {
expect updateResult.Success?;
}
}
}

method BasicIoTestDeleteItem(writeConfig : TableConfig, readConfig : TableConfig, records : seq<Record>, attributeToDelete: DDB.AttributeName, expectedAttributeValue: string)
{
var wClient, rClient := SetupTestTable(writeConfig, readConfig);
WriteAllRecords(wClient, records);
// Try to delete records with a condition expression with condition to
// delete records if the record has an attribute attributeToDelete with value expectedAttributeValue
for i := 0 to |records| {
// Set up condition expression to only delete if Two = expectedAttributeValue
var conditionExpr := "#attr = :val";
var exprAttrNames := map["#attr" := attributeToDelete];
var exprAttrValues := map[":val" := DDB.AttributeValue.S(expectedAttributeValue)];
expect HashName in records[i].item, "`HashName` is not in records.";
var deleteInput := DDB.DeleteItemInput(
TableName := TableName,
Key := map[HashName := records[i].item[HashName]],
ConditionExpression := Some(conditionExpr),
Comment on lines +904 to +907
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these are meant to be basic smoke tests, I think it's worth adding a case where we don't specify a condition expression at all -- is that feasible/worth testing?

(I think the expected result would be that the delete passes)

ExpressionAttributeNames := Some(exprAttrNames),
ExpressionAttributeValues := Some(exprAttrValues),
ReturnValues := Some(DDB.ReturnValue.ALL_OLD)
);
var deleteResult := wClient.DeleteItem(deleteInput);
expect attributeToDelete in writeConfig.config.attributeActionsOnEncrypt, "`attributeToDelete` not found in attributeActionsOnEncrypt of config.";
if writeConfig.config.attributeActionsOnEncrypt[attributeToDelete] == SE.ENCRYPT_AND_SIGN {
expect deleteResult.Failure?, "DeleteItem should have failed.";
// This error is of type DynamoDbEncryptionTransformsException
// but AWS SDK wraps it into its own type for which customers should be unwrapping.
// In test vectors, we still have to change the error from AWS SDK to dafny so it turns out to be OpaqueWithText.
expect deleteResult.error.OpaqueWithText?, "Error should have been of type OpaqueWithText";
var hasDynamoDbEncryptionTransformsException? := String.HasSubString(deleteResult.error.objMessage, "Condition Expressions forbidden on encrypted attributes");
expect hasDynamoDbEncryptionTransformsException?.Some?, "Error might is not be of type DynamoDbEncryptionTransformsException";
} else if attributeToDelete in records[i].item && records[i].item[attributeToDelete].S? && records[i].item[attributeToDelete].S == expectedAttributeValue {
expect deleteResult.Success?, "DeleteItem should have succeeded.";
expect deleteResult.value.Attributes.Some?, "DeleteItemOutput should have had some attribute because ReturnValues was set as `ALL_OLD` in DeleteItemInput";
expect HashName in deleteResult.value.Attributes.value, "Deleted item does not have right partition key:" + HashName;
expect deleteResult.value.Attributes.value[HashName] == records[i].item[HashName], "Wrong item was deleted.";
} else {
expect deleteResult.Failure?, "DeleteItem should have failed.";
expect deleteResult.error.ConditionalCheckFailedException?, "DeleteItem should have failed with ConditionalCheckFailedException";
}
}
}

method BasicIoTestDeleteItemWithoutConditionExpression(writeConfig : TableConfig, readConfig : TableConfig, records : seq<Record>, attributeToDelete: DDB.AttributeName, expectedAttributeValue: string)
{
var wClient, rClient := SetupTestTable(writeConfig, readConfig);
WriteAllRecords(wClient, records);
for i := 0 to |records| {
expect HashName in records[i].item, "`HashName` is not in records.";
var deleteInputWithoutConditionExpression := DDB.DeleteItemInput(
TableName := TableName,
Key := map[HashName := records[i].item[HashName]],
ReturnValues := Some(DDB.ReturnValue.ALL_OLD)
);
var deleteResultForWithoutConditionExpressionCase := wClient.DeleteItem(deleteInputWithoutConditionExpression);
expect deleteResultForWithoutConditionExpressionCase.Success?, "DeleteItem should have succeeded.";
expect deleteResultForWithoutConditionExpressionCase.value.Attributes.Some?, "DeleteItemOutput should have had some attribute because ReturnValues was set as `ALL_OLD` in DeleteItemInput";
if attributeToDelete in records[i].item {
expect HashName in deleteResultForWithoutConditionExpressionCase.value.Attributes.value, "Deleted item does not have right partition key:" + HashName;
expect deleteResultForWithoutConditionExpressionCase.value.Attributes.value[HashName] == records[i].item[HashName], "Wrong item was deleted.";
}
}
}

method BasicIoTestExecuteStatement(writeConfig : TableConfig, readConfig : TableConfig)
{
var wClient, rClient := SetupTestTable(writeConfig, readConfig);
Expand Down
Loading