diff --git a/sdk/tables/azure-data-tables/src/serializers.cpp b/sdk/tables/azure-data-tables/src/serializers.cpp index f2b16ae87..e01cf71c5 100644 --- a/sdk/tables/azure-data-tables/src/serializers.cpp +++ b/sdk/tables/azure-data-tables/src/serializers.cpp @@ -527,7 +527,21 @@ namespace Azure { namespace Data { namespace Tables { namespace _detail { { Models::TableEntity tableEntity{}; - auto properties = json.get>(); + std::map properties; + for (auto it = json.items().begin(); it != json.items().end(); ++it) + { + const std::string& key = it.key(); + const auto& value = it.value(); + if (value.is_string()) + { + properties[key] = value.get(); + } + else + { + properties[key] = value.dump(); + } + } + std::vector erasable; for (auto property : properties) { diff --git a/sdk/tables/azure-data-tables/test/ut/serializers_test.cpp b/sdk/tables/azure-data-tables/test/ut/serializers_test.cpp index 7678ecd52..25a185e92 100644 --- a/sdk/tables/azure-data-tables/test/ut/serializers_test.cpp +++ b/sdk/tables/azure-data-tables/test/ut/serializers_test.cpp @@ -87,5 +87,451 @@ namespace Azure { namespace Data { namespace Test { EXPECT_EQ(data.Cors[0].AllowedHeaders, "*"); EXPECT_EQ(data.Cors[0].ExposedHeaders, "*"); } + TEST_F(SerializersTest, DeserializeEntitySimpleProperties) + { + auto json = R"({ + "PartitionKey": "p1", + "RowKey": "r1", + "Name": "Test Name", + "Age": "30" + })"_json; + + auto entity = Serializers::DeserializeEntity(json); + + EXPECT_EQ(entity.GetPartitionKey().Value, "p1"); + EXPECT_EQ(entity.GetRowKey().Value, "r1"); + EXPECT_EQ(entity.Properties["Name"].Value, "Test Name"); + EXPECT_EQ(entity.Properties["Age"].Value, "30"); + } + + TEST_F(SerializersTest, DeserializeEntityWithOdataType) + { + auto json = R"({ + "PartitionKey": "p2", + "RowKey": "r2", + "Completed": "true", + "Completed@odata.type": "Edm.Boolean", + "Score": "9.5", + "Score@odata.type": "Edm.Double" + })"_json; + + auto entity = Serializers::DeserializeEntity(json); + + EXPECT_EQ(entity.GetPartitionKey().Value, "p2"); + EXPECT_EQ(entity.GetRowKey().Value, "r2"); + EXPECT_EQ(entity.Properties["Completed"].Value, "true"); + EXPECT_EQ(entity.Properties["Completed"].Type.Value().ToString(), "Edm.Boolean"); + EXPECT_EQ(entity.Properties["Score"].Value, "9.5"); + EXPECT_EQ(entity.Properties["Score"].Type.Value().ToString(), "Edm.Double"); + } + + TEST_F(SerializersTest, DeserializeEntityWithNonStringType) + { + auto json = R"({ + "PartitionKey": "p2", + "RowKey": "r2", + "Completed": "true", + "Completed@odata.type": "Edm.Boolean", + "Score": "9.5", + "Score@odata.type": "Edm.Double", + "Age": 30 + })"_json; + + auto entity = Serializers::DeserializeEntity(json); + + EXPECT_EQ(entity.GetPartitionKey().Value, "p2"); + EXPECT_EQ(entity.GetRowKey().Value, "r2"); + EXPECT_EQ(entity.Properties["Completed"].Value, "true"); + EXPECT_EQ(entity.Properties["Completed"].Type.Value().ToString(), "Edm.Boolean"); + EXPECT_EQ(entity.Properties["Score"].Value, "9.5"); + EXPECT_EQ(entity.Properties["Score"].Type.Value().ToString(), "Edm.Double"); + EXPECT_EQ(entity.Properties["Age"].Value, "30"); + } + TEST_F(SerializersTest, DeserializeEntityMissingProperties) + { + auto json = R"({ + "PartitionKey": "p3", + "RowKey": "r3" + })"_json; + + auto entity = Serializers::DeserializeEntity(json); + + EXPECT_EQ(entity.GetPartitionKey().Value, "p3"); + EXPECT_EQ(entity.GetRowKey().Value, "r3"); + EXPECT_FALSE(entity.Properties.empty()); + } + + TEST_F(SerializersTest, CreateEntity) + { + TableEntity entity; + entity.SetPartitionKey("partition1"); + entity.SetRowKey("row1"); + entity.Properties["Name"] = TableEntityProperty("John Doe"); + + auto serialized = Serializers::CreateEntity(entity); + auto expectedJson = R"({"Name":"John Doe","PartitionKey":"partition1","RowKey":"row1"})"; + EXPECT_EQ(serialized, expectedJson); + } + + TEST_F(SerializersTest, MergeEntity) + { + TableEntity entity; + entity.SetPartitionKey("partition2"); + entity.SetRowKey("row2"); + entity.Properties["Status"] = TableEntityProperty("Active"); + + auto serialized = Serializers::MergeEntity(entity); + auto expectedJson = R"({"PartitionKey":"partition2","RowKey":"row2","Status":"Active"})"; + EXPECT_EQ(serialized, expectedJson); + } + + TEST_F(SerializersTest, UpdateEntity) + { + TableEntity entity; + entity.SetPartitionKey("partition3"); + entity.SetRowKey("row3"); + + auto serialized = Serializers::UpdateEntity(entity); + auto expectedJson = R"({"PartitionKey":"partition3","RowKey":"row3"})"; + EXPECT_EQ(serialized, expectedJson); + } + + TEST_F(SerializersTest, Create) + { + std::string tableName = "MyTable"; + auto serialized = Serializers::Create(tableName); + auto expectedJson = R"({"TableName":"MyTable"})"; + EXPECT_EQ(serialized, expectedJson); + } + + TEST_F(SerializersTest, SetAccessPolicyComplex) + { + TableAccessPolicy policy; + SignedIdentifier identifier1; + identifier1.Id = "user1"; + identifier1.StartsOn = Azure::DateTime(2023, 1, 1, 0, 0, 0); + identifier1.ExpiresOn = Azure::DateTime(2023, 1, 2, 0, 0, 0); + identifier1.Permissions = "r"; + policy.SignedIdentifiers.emplace_back(identifier1); + + SignedIdentifier identifier2; + identifier2.Id = "user2"; + identifier2.StartsOn = Azure::DateTime(2023, 1, 3, 0, 0, 0); + identifier2.ExpiresOn = Azure::DateTime(2023, 1, 4, 0, 0, 0); + identifier2.Permissions = "rw"; + policy.SignedIdentifiers.emplace_back(identifier2); + + auto serialized = Serializers::SetAccessPolicy(policy); + auto data = Serializers::TableAccessPolicyFromXml( + std::vector(serialized.begin(), serialized.end())); + + EXPECT_EQ(data.SignedIdentifiers.size(), 2); + EXPECT_EQ(data.SignedIdentifiers[0].Id, "user1"); + EXPECT_EQ(data.SignedIdentifiers[0].Permissions, "r"); + EXPECT_EQ(data.SignedIdentifiers[0].StartsOn.Value(), Azure::DateTime(2023, 1, 1, 0, 0, 0)); + EXPECT_EQ(data.SignedIdentifiers[0].ExpiresOn.Value(), Azure::DateTime(2023, 1, 2, 0, 0, 0)); + + EXPECT_EQ(data.SignedIdentifiers[1].Id, "user2"); + EXPECT_EQ(data.SignedIdentifiers[1].Permissions, "rw"); + EXPECT_EQ(data.SignedIdentifiers[1].StartsOn.Value(), Azure::DateTime(2023, 1, 3, 0, 0, 0)); + EXPECT_EQ(data.SignedIdentifiers[1].ExpiresOn.Value(), Azure::DateTime(2023, 1, 4, 0, 0, 0)); + } + + TEST_F(SerializersTest, DeserializeEntityWithMixedTypes) + { + auto json = R"({ + "PartitionKey": "p4", + "RowKey": "r4", + "Name": "Jane Doe", + "IsActive": "true", + "IsActive@odata.type": "Edm.Boolean", + "Salary": "5000.00", + "Salary@odata.type": "Edm.Double" + })"_json; + + auto entity = Serializers::DeserializeEntity(json); + + EXPECT_EQ(entity.GetPartitionKey().Value, "p4"); + EXPECT_EQ(entity.GetRowKey().Value, "r4"); + EXPECT_EQ(entity.Properties["Name"].Value, "Jane Doe"); + EXPECT_EQ(entity.Properties["IsActive"].Value, "true"); + EXPECT_EQ(entity.Properties["IsActive"].Type.Value().ToString(), "Edm.Boolean"); + EXPECT_EQ(entity.Properties["Salary"].Value, "5000.00"); + EXPECT_EQ(entity.Properties["Salary"].Type.Value().ToString(), "Edm.Double"); + } + + TEST_F(SerializersTest, DeserializeEntityWithComplexTypes) + { + auto json = R"({ + "PartitionKey": "p5", + "RowKey": "r5", + "Metadata": "{\"author\":\"John Doe\",\"year\":2023}", + "Metadata@odata.type": "Edm.String" + })"_json; + + auto entity = Serializers::DeserializeEntity(json); + + EXPECT_EQ(entity.GetPartitionKey().Value, "p5"); + EXPECT_EQ(entity.GetRowKey().Value, "r5"); + EXPECT_EQ(entity.Properties["Metadata"].Value, "{\"author\":\"John Doe\",\"year\":2023}"); + EXPECT_EQ(entity.Properties["Metadata"].Type.Value().ToString(), "Edm.String"); + } + + TEST_F(SerializersTest, DeserializeEntityWithNullValues) + { + auto json = R"({ + "PartitionKey": "p6", + "RowKey": "r6", + "Description": null, + "Description@odata.type": "Edm.String" + })"_json; + + auto entity = Serializers::DeserializeEntity(json); + + EXPECT_EQ(entity.GetPartitionKey().Value, "p6"); + EXPECT_EQ(entity.GetRowKey().Value, "r6"); + EXPECT_EQ(entity.Properties["Description"].Value, "null"); + EXPECT_EQ(entity.Properties["Description"].Type.Value().ToString(), "Edm.String"); + } + + TEST_F(SerializersTest, SetServicePropertiesBasic) + { + Models::SetServicePropertiesOptions options; + options.ServiceProperties.Logging.Version = "2.0"; + options.ServiceProperties.Logging.Delete = false; + options.ServiceProperties.Logging.Read = false; + options.ServiceProperties.Logging.Write = false; + options.ServiceProperties.Logging.RetentionPolicyDefinition.IsEnabled = false; + + auto serialized = Serializers::SetServiceProperties(options); + EXPECT_NE( + serialized.find("2.0falsefalsefalse"), + std::string::npos); + EXPECT_NE( + serialized.find("false"), + std::string::npos); + } + + TEST_F(SerializersTest, SetServicePropertiesAllEnabled) + { + Models::SetServicePropertiesOptions options; + options.ServiceProperties.Logging.Version = "2.0"; + options.ServiceProperties.Logging.Delete = true; + options.ServiceProperties.Logging.Read = true; + options.ServiceProperties.Logging.Write = true; + options.ServiceProperties.Logging.RetentionPolicyDefinition.IsEnabled = true; + options.ServiceProperties.Logging.RetentionPolicyDefinition.DataRetentionInDays = 7; + + auto serialized = Serializers::SetServiceProperties(options); + EXPECT_NE( + serialized.find("2.0truetruetrue"), + std::string::npos); + EXPECT_NE( + serialized.find("true7"), + std::string::npos); + } + + TEST_F(SerializersTest, SetServicePropertiesWithCors) + { + Models::SetServicePropertiesOptions options; + Models::CorsRule corsRule; + corsRule.AllowedOrigins = "http://example.com"; + corsRule.AllowedMethods = "GET, POST"; + corsRule.AllowedHeaders = "x-ms-meta-*"; + corsRule.ExposedHeaders = "x-ms-meta-data*"; + corsRule.MaxAgeInSeconds = 3600; + options.ServiceProperties.Cors.push_back(corsRule); + + auto serialized = Serializers::SetServiceProperties(options); + EXPECT_NE(serialized.find(""), std::string::npos); + EXPECT_NE( + serialized.find("http://example.com"), std::string::npos); + EXPECT_NE(serialized.find("GET, POST"), std::string::npos); + EXPECT_NE(serialized.find("x-ms-meta-*"), std::string::npos); + EXPECT_NE( + serialized.find("x-ms-meta-data*"), std::string::npos); + EXPECT_NE(serialized.find("3600"), std::string::npos); + } + + TEST_F(SerializersTest, SetServicePropertiesHourMetrics) + { + Models::SetServicePropertiesOptions options; + options.ServiceProperties.HourMetrics.Version = "1.0"; + options.ServiceProperties.HourMetrics.IsEnabled = true; + options.ServiceProperties.HourMetrics.IncludeApis = true; + options.ServiceProperties.HourMetrics.RetentionPolicyDefinition.IsEnabled = true; + options.ServiceProperties.HourMetrics.RetentionPolicyDefinition.DataRetentionInDays = 7; + + auto serialized = Serializers::SetServiceProperties(options); + EXPECT_NE( + serialized.find("1.0truetrue"), + std::string::npos); + EXPECT_NE( + serialized.find("true7"), + std::string::npos); + } + + TEST_F(SerializersTest, SetServicePropertiesMinuteMetrics) + { + Models::SetServicePropertiesOptions options; + options.ServiceProperties.MinuteMetrics.Version = "1.0"; + options.ServiceProperties.MinuteMetrics.IsEnabled = true; + options.ServiceProperties.MinuteMetrics.IncludeApis = false; + options.ServiceProperties.MinuteMetrics.RetentionPolicyDefinition.IsEnabled = true; + options.ServiceProperties.MinuteMetrics.RetentionPolicyDefinition.DataRetentionInDays = 7; + + auto serialized = Serializers::SetServiceProperties(options); + EXPECT_NE( + serialized.find("1.0truefalse"), + std::string::npos); + EXPECT_NE( + serialized.find("true7"), + std::string::npos); + } + + TEST_F(SerializersTest, SetAccessPolicyEmpty) + { + TableAccessPolicy policy; + auto serialized = Serializers::SetAccessPolicy(policy); + auto data = Serializers::TableAccessPolicyFromXml( + std::vector(serialized.begin(), serialized.end())); + EXPECT_TRUE(data.SignedIdentifiers.empty()); + } + + TEST_F(SerializersTest, SetAccessPolicySingleIdentifier) + { + TableAccessPolicy policy; + SignedIdentifier identifier; + identifier.Id = "singleId"; + identifier.StartsOn = Azure::DateTime(2023, 5, 1, 0, 0, 0); + identifier.ExpiresOn = Azure::DateTime(2023, 5, 2, 0, 0, 0); + identifier.Permissions = "rw"; + policy.SignedIdentifiers.emplace_back(identifier); + + auto serialized = Serializers::SetAccessPolicy(policy); + auto data = Serializers::TableAccessPolicyFromXml( + std::vector(serialized.begin(), serialized.end())); + + ASSERT_EQ(data.SignedIdentifiers.size(), 1); + EXPECT_EQ(data.SignedIdentifiers[0].Id, "singleId"); + EXPECT_EQ(data.SignedIdentifiers[0].Permissions, "rw"); + EXPECT_EQ(data.SignedIdentifiers[0].StartsOn.Value(), Azure::DateTime(2023, 5, 1, 0, 0, 0)); + EXPECT_EQ(data.SignedIdentifiers[0].ExpiresOn.Value(), Azure::DateTime(2023, 5, 2, 0, 0, 0)); + } + + TEST_F(SerializersTest, SetAccessPolicyMultipleIdentifiers) + { + TableAccessPolicy policy; + SignedIdentifier identifier1, identifier2; + identifier1.Id = "id1"; + identifier1.StartsOn = Azure::DateTime(2023, 6, 1, 0, 0, 0); + identifier1.ExpiresOn = Azure::DateTime(2023, 6, 2, 0, 0, 0); + identifier1.Permissions = "r"; + policy.SignedIdentifiers.emplace_back(identifier1); + + identifier2.Id = "id2"; + identifier2.StartsOn = Azure::DateTime(2023, 6, 3, 0, 0, 0); + identifier2.ExpiresOn = Azure::DateTime(2023, 6, 4, 0, 0, 0); + identifier2.Permissions = "w"; + policy.SignedIdentifiers.emplace_back(identifier2); + + auto serialized = Serializers::SetAccessPolicy(policy); + auto data = Serializers::TableAccessPolicyFromXml( + std::vector(serialized.begin(), serialized.end())); + + ASSERT_EQ(data.SignedIdentifiers.size(), 2); + EXPECT_EQ(data.SignedIdentifiers[0].Id, "id1"); + EXPECT_EQ(data.SignedIdentifiers[0].Permissions, "r"); + EXPECT_EQ(data.SignedIdentifiers[0].StartsOn.Value(), Azure::DateTime(2023, 6, 1, 0, 0, 0)); + EXPECT_EQ(data.SignedIdentifiers[0].ExpiresOn.Value(), Azure::DateTime(2023, 6, 2, 0, 0, 0)); + + EXPECT_EQ(data.SignedIdentifiers[1].Id, "id2"); + EXPECT_EQ(data.SignedIdentifiers[1].Permissions, "w"); + EXPECT_EQ(data.SignedIdentifiers[1].StartsOn.Value(), Azure::DateTime(2023, 6, 3, 0, 0, 0)); + EXPECT_EQ(data.SignedIdentifiers[1].ExpiresOn.Value(), Azure::DateTime(2023, 6, 4, 0, 0, 0)); + } + + TEST_F(SerializersTest, TableAccessPolicyFromXml_EmptyXml) + { + std::string xml = ""; + auto data = Serializers::TableAccessPolicyFromXml(std::vector(xml.begin(), xml.end())); + EXPECT_TRUE(data.SignedIdentifiers.empty()); + } + + TEST_F(SerializersTest, TableAccessPolicyFromXml_SingleSignedIdentifier) + { + std::string xml = R"( + + testId + + 2023-01-01T00:00:00Z + 2023-01-02T00:00:00Z + r + + + )"; + auto data = Serializers::TableAccessPolicyFromXml(std::vector(xml.begin(), xml.end())); + ASSERT_EQ(data.SignedIdentifiers.size(), 1); + EXPECT_EQ(data.SignedIdentifiers[0].Id, "testId"); + EXPECT_EQ(data.SignedIdentifiers[0].StartsOn.Value(), Azure::DateTime(2023, 1, 1, 0, 0, 0)); + EXPECT_EQ(data.SignedIdentifiers[0].ExpiresOn.Value(), Azure::DateTime(2023, 1, 2, 0, 0, 0)); + EXPECT_EQ(data.SignedIdentifiers[0].Permissions, "r"); + } + + TEST_F(SerializersTest, TableAccessPolicyFromXml_MultipleSignedIdentifiers) + { + std::string xml = R"( + + testId1 + + 2023-01-01T00:00:00Z + 2023-01-02T00:00:00Z + r + + + + testId2 + + 2023-02-01T00:00:00Z + 2023-02-02T00:00:00Z + rw + + + )"; + auto data = Serializers::TableAccessPolicyFromXml(std::vector(xml.begin(), xml.end())); + ASSERT_EQ(data.SignedIdentifiers.size(), 2); + EXPECT_EQ(data.SignedIdentifiers[0].Id, "testId1"); + EXPECT_EQ(data.SignedIdentifiers[0].Permissions, "r"); + EXPECT_EQ(data.SignedIdentifiers[1].Id, "testId2"); + EXPECT_EQ(data.SignedIdentifiers[1].Permissions, "rw"); + } + + TEST_F(SerializersTest, TableAccessPolicyFromXml_InvalidXml) + { + std::string xml = ""; + auto data = Serializers::TableAccessPolicyFromXml(std::vector(xml.begin(), xml.end())); + EXPECT_TRUE(data.SignedIdentifiers.empty()); + } + + TEST_F(SerializersTest, TableAccessPolicyFromXml_MissingPermissions) + { + std::string xml = R"( + + testId + + 2023-01-01T00:00:00Z + 2023-01-02T00:00:00Z + + + )"; + auto data = Serializers::TableAccessPolicyFromXml(std::vector(xml.begin(), xml.end())); + ASSERT_EQ(data.SignedIdentifiers.size(), 1); + EXPECT_EQ(data.SignedIdentifiers[0].Id, "testId"); + EXPECT_TRUE(data.SignedIdentifiers[0].Permissions.empty()); + } }}} // namespace Azure::Data::Test