From 1d5ab5e6cedbc594427e12cae87bc580ed398d92 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Wed, 10 May 2023 15:54:10 +0200 Subject: [PATCH 01/14] Fixed the http attribute handling within ODataJsonLightBatchPayloadItemPropertiesCache.cs --- ...sonLightBatchPayloadItemPropertiesCache.cs | 92 +++++++++++++--- .../ODataJsonLightBatchReaderTests.cs | 103 ++++++++++++++++++ 2 files changed, 177 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index 0180311144..19f927e4ed 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -227,14 +227,26 @@ private void ScanJsonProperties() case PropertyNameMethod: case PropertyNameUrl: { - jsonProperties.Add(propertyName, this.jsonReader.ReadStringValue()); + string propertyValueString = this.jsonReader.ReadStringValue(); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueString); + } } break; case PropertyNameStatus: { - jsonProperties.Add(propertyName, this.jsonReader.ReadPrimitiveValue()); + object propertyValueObject = this.jsonReader.ReadPrimitiveValue(); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueObject); + } } break; @@ -250,7 +262,11 @@ private void ScanJsonProperties() this.jsonReader.ReadEndArray(); - jsonProperties.Add(propertyName, dependsOnIds); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, dependsOnIds); + } } break; @@ -267,7 +283,8 @@ private void ScanJsonProperties() while (this.jsonReader.NodeType != JsonNodeType.EndObject) { string headerName = this.jsonReader.ReadPropertyName(); - string headerValue = this.jsonReader.ReadPrimitiveValue().ToString(); + object headerValueObject = this.jsonReader.ReadPrimitiveValue(); + string headerValue = headerValueObject?.ToString(); // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) @@ -275,12 +292,20 @@ private void ScanJsonProperties() contentTypeHeader = headerValue; } - headers.Add(headerName, headerValue); + // Skip duplicate http headers + if (!headers.ContainsKeyOrdinal(headerName)) + { + headers.Add(headerName, headerValue); + } } this.jsonReader.ReadEndObject(); - jsonProperties.Add(propertyName, headers); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, headers); + } if (!this.isStreamPopulated && bodyContentStream != null) { @@ -294,7 +319,12 @@ private void ScanJsonProperties() case PropertyNameBody: { bodyContentStream = CreateJsonPayloadBodyContentStream(contentTypeHeader); - jsonProperties.Add(propertyName, bodyContentStream); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, bodyContentStream); + } } break; @@ -365,16 +395,24 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() case PropertyNameAtomicityGroup: case PropertyNameMethod: case PropertyNameUrl: - jsonProperties.Add( - propertyName, - await this.asynchronousJsonReader.ReadStringValueAsync().ConfigureAwait(false)); + string propertyValueString = await this.asynchronousJsonReader.ReadStringValueAsync().ConfigureAwait(false); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueString); + } break; case PropertyNameStatus: - jsonProperties.Add( - propertyName, - await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)); + object propertyValueObject = await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueObject); + } break; @@ -390,7 +428,11 @@ await this.asynchronousJsonReader.ReadStartArrayAsync() await this.asynchronousJsonReader.ReadEndArrayAsync() .ConfigureAwait(false); - jsonProperties.Add(propertyName, dependsOnIds); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, dependsOnIds); + } break; @@ -407,7 +449,8 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() { string headerName = await this.asynchronousJsonReader.ReadPropertyNameAsync() .ConfigureAwait(false); - string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)).ToString(); + object headerValueObject = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)); + string headerValue = headerValueObject?.ToString(); // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) @@ -415,13 +458,21 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() contentTypeHeader = headerValue; } - headers.Add(headerName, headerValue); + // Skip duplicate http headers + if (!headers.ContainsKeyOrdinal(headerName)) + { + headers.Add(headerName, headerValue); + } } await this.asynchronousJsonReader.ReadEndObjectAsync() .ConfigureAwait(false); - jsonProperties.Add(propertyName, headers); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, headers); + } if (!this.isStreamPopulated && bodyContentStream != null) { @@ -435,7 +486,12 @@ await bodyContentStream.PopulateCachedBodyContentAsync(contentTypeHeader) case PropertyNameBody: bodyContentStream = await CreateJsonPayloadBodyContentStreamAsync(contentTypeHeader) .ConfigureAwait(false); - jsonProperties.Add(propertyName, bodyContentStream); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, bodyContentStream); + } break; diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs index e17e1b77e4..4156457e0f 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs @@ -363,6 +363,109 @@ await DoReadAsync( isResponse: false); } + [Fact] + public async Task ReadBatchRequestWithDuplicateAttributesAsync() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"GET\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\"}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + await SetupJsonLightBatchReaderAndRunTestAsync( + payload, + async (jsonLightBatchReader) => + { + try + { + while (await jsonLightBatchReader.ReadAsync()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + } + } + + Assert.True(true, "The test was successful, because no ArgumentException was thrown"); + } + catch (ArgumentException ex) + { + Assert.True(false, ex.Message); + } + }, + isResponse: false); + } + + [Fact] + public async Task ReadBatchRequestWithDuplicateHeadersAsync() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"duplicate-header\":\"value1\",\"duplicate-header\":\"value2\"}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + await SetupJsonLightBatchReaderAndRunTestAsync( + payload, + async (jsonLightBatchReader) => + { + try + { + while (await jsonLightBatchReader.ReadAsync()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + } + } + + Assert.True(true, "The test was successful, because no ArgumentException was thrown"); + } + catch (ArgumentException ex) + { + Assert.True(false, ex.Message); + } + }, + isResponse: false); + } + + [Fact] + public async Task ReadBatchRequestWithNullHeadersAsync() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"null-header\":null}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + await SetupJsonLightBatchReaderAndRunTestAsync( + payload, + async (jsonLightBatchReader) => + { + try + { + while (await jsonLightBatchReader.ReadAsync()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + } + } + + Assert.True(true, "The test was successful, because no ArgumentException was thrown"); + } + catch (ArgumentException ex) + { + Assert.True(false, ex.Message); + } + }, + isResponse: false); + } + [Fact] public async Task ReadBatchResponseAsync() { From 56384796b6dd1331fad0c45b772fcf639f9f983f Mon Sep 17 00:00:00 2001 From: Sam Xu Date: Thu, 11 May 2023 21:36:57 -0700 Subject: [PATCH 02/14] fixes #2659: Support declared Edm.Untyped property serialization using ODataPrimitiveValue, etc (#2664) * fixes #2659: Support declared Edm.Untyped property serialization using ODataPrimitiveValue, etc * Fixes the failing tests --- src/Microsoft.OData.Core/TypeNameOracle.cs | 5 +- src/Microsoft.OData.Core/ValidationUtils.cs | 7 + src/Microsoft.OData.Core/WriterValidator.cs | 23 ++- ...htEntryAndFeedSerializerUndecalredTests.cs | 190 +++++++++++++++++- 4 files changed, 215 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.OData.Core/TypeNameOracle.cs b/src/Microsoft.OData.Core/TypeNameOracle.cs index 4cc1b4b465..142ae5968c 100644 --- a/src/Microsoft.OData.Core/TypeNameOracle.cs +++ b/src/Microsoft.OData.Core/TypeNameOracle.cs @@ -318,7 +318,10 @@ private static IEdmTypeReference ResolveTypeFromMetadataAndValue(IEdmTypeReferen return typeReferenceFromMetadata; } - Debug.Assert(typeReferenceFromValue.TypeKind() == typeReferenceFromMetadata.TypeKind(), "typeReferenceFromValue.TypeKind() == typeReferenceFromMetadata.TypeKind()"); + if (!typeReferenceFromMetadata.IsUntyped()) + { + Debug.Assert(typeReferenceFromValue.TypeKind() == typeReferenceFromMetadata.TypeKind(), "typeReferenceFromValue.TypeKind() == typeReferenceFromMetadata.TypeKind()"); + } writerValidator.ValidateTypeReference(typeReferenceFromMetadata, typeReferenceFromValue); diff --git a/src/Microsoft.OData.Core/ValidationUtils.cs b/src/Microsoft.OData.Core/ValidationUtils.cs index 0f0d661533..09c45b2e44 100644 --- a/src/Microsoft.OData.Core/ValidationUtils.cs +++ b/src/Microsoft.OData.Core/ValidationUtils.cs @@ -302,6 +302,13 @@ internal static void ValidateIsExpectedPrimitiveType(object value, IEdmPrimitive } Debug.Assert(valuePrimitiveTypeReference.IsEquivalentTo(EdmLibraryExtensions.GetPrimitiveTypeReference(value.GetType())), "The value and valuePrimitiveTypeReference don't match."); + + // If the expected type is 'Edm.Untyped', we don't need to verify the value type. + if (expectedTypeReference.IsUntyped()) + { + return; + } + if (!expectedTypeReference.IsODataPrimitiveTypeKind() && !expectedTypeReference.IsODataTypeDefinitionTypeKind()) { // non-primitive type found for primitive value. diff --git a/src/Microsoft.OData.Core/WriterValidator.cs b/src/Microsoft.OData.Core/WriterValidator.cs index 6246e9b7dc..32cd12579d 100644 --- a/src/Microsoft.OData.Core/WriterValidator.cs +++ b/src/Microsoft.OData.Core/WriterValidator.cs @@ -151,8 +151,12 @@ public virtual void ValidateTypeReference(IEdmTypeReference typeReferenceFromMet { if (settings.ThrowIfTypeConflictsWithMetadata) { + if (typeReferenceFromMetadata.IsUntyped()) + { + ; // do nothing here + } // Make sure the types are the same - if (typeReferenceFromValue.IsODataPrimitiveTypeKind()) + else if (typeReferenceFromValue.IsODataPrimitiveTypeKind()) { // Primitive types must match exactly except for nullability ValidationUtils.ValidateMetadataPrimitiveType(typeReferenceFromMetadata, @@ -172,14 +176,17 @@ public virtual void ValidateTypeReference(IEdmTypeReference typeReferenceFromMet } else if (typeReferenceFromMetadata.IsCollection()) { - // Collection types must match exactly. - if (!typeReferenceFromMetadata.Definition.IsElementTypeEquivalentTo( - typeReferenceFromValue.Definition)) + if (!typeReferenceFromMetadata.AsCollection().ElementType().IsUntyped()) { - throw new ODataException( - Strings.ValidationUtils_IncompatibleType( - typeReferenceFromValue.FullName(), - typeReferenceFromMetadata.FullName())); + // Collection types must match exactly. + if (!typeReferenceFromMetadata.Definition.IsElementTypeEquivalentTo( + typeReferenceFromValue.Definition)) + { + throw new ODataException( + Strings.ValidationUtils_IncompatibleType( + typeReferenceFromValue.FullName(), + typeReferenceFromMetadata.FullName())); + } } } else diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs index 641d82b10f..43a27d0b57 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs @@ -35,6 +35,8 @@ public ODataJsonLightEntryAndFeedSerializerUndeclaredTests() this.serverEntityType = new EdmEntityType("Server.NS", "ServerEntityType"); this.serverModel.AddElement(this.serverEntityType); this.serverEntityType.AddKeys(this.serverEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + this.serverEntityType.AddStructuralProperty("Data", EdmCoreModel.Instance.GetUntyped()); + this.serverEntityType.AddStructuralProperty("Infos", new EdmCollectionTypeReference(new EdmCollectionType(EdmCoreModel.Instance.GetUntyped()))); this.serverEntityType.AddStructuralProperty("Address", new EdmComplexTypeReference(addressType, true)); // open entity type @@ -48,6 +50,11 @@ public ODataJsonLightEntryAndFeedSerializerUndeclaredTests() this.serverEntitySet = container.AddEntitySet("serverEntitySet", this.serverEntityType); this.serverOpenEntitySet = container.AddEntitySet("serverOpenEntitySet", this.serverOpenEntityType); this.serverModel.AddElement(container); + + EdmEnumType enumType = new EdmEnumType("Server.NS", "EnumType"); + enumType.AddMember(new EdmEnumMember(enumType, "Red", new EdmEnumMemberValue(1))); + enumType.AddMember(new EdmEnumMember(enumType, "Green", new EdmEnumMemberValue(2))); + this.serverModel.AddElement(enumType); } private ODataMessageWriterSettings writerSettings = new ODataMessageWriterSettings @@ -55,8 +62,189 @@ public ODataJsonLightEntryAndFeedSerializerUndeclaredTests() Validations = ~ValidationKinds.ThrowOnUndeclaredPropertyForNonOpenType }; - #region non-open entity's property unknown name + known value type + #region Declared Untyped Properties + [Fact] + public void WriteResourceDeclaredSingleUntypedProperty_WorksForUntypedValue() + { + var property = new ODataProperty { Name = "Data", Value = new ODataUntypedValue { RawValue = "\"#lje324$$\"" } }; + string result = WriteDeclaredUntypedProperty(property); + Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Data\":\"#lje324$$\"}", result); + } + + [Fact] + public void WriteResourceDeclaredSingleUntypedProperty_WorksForPrimitiveValue() + { + // String is one of default type + var property = new ODataProperty { Name = "Data", Value = new ODataPrimitiveValue("41") }; + string result = WriteDeclaredUntypedProperty(property); + Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Data@odata.type\":\"#String\",\"Data\":\"41\"}", result); + + // not-default type + property = new ODataProperty { Name = "Data", Value = new ODataPrimitiveValue(41) }; + result = WriteDeclaredUntypedProperty(property); + Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Data@odata.type\":\"#Int32\",\"Data\":41}", result); + } + + [Fact] + public void WriteResourceDeclaredSingleUntypedProperty_WorksForEnumValue() + { + // Without type name + var property = new ODataProperty { Name = "Data", Value = new ODataEnumValue("AnyMem") }; + string result = WriteDeclaredUntypedProperty(property); + Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Data\":\"AnyMem\"}", result); + + // with type name + property = new ODataProperty { Name = "Data", Value = new ODataEnumValue("Green", "Server.NS.EnumType") }; + result = WriteDeclaredUntypedProperty(property); + Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Data\":\"Green\"}", result); + } + + [Fact] + public void WriteResourceDeclaredSingleUntypedProperty_WorksForBinaryStreamValue() + { + // With type name + // ODataBinaryStreamValue + var stream = new MemoryStream(); + var writer = new BinaryWriter(stream); + writer.Write("1234567890"); + writer.Flush(); + stream.Position = 0; + + var property = new ODataProperty + { + Name = "Data", + Value = new ODataBinaryStreamValue(stream) + }; + + string result = WriteDeclaredUntypedProperty(property); + Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Data\":\"CjEyMzQ1Njc4OTA=\"}", result); + } + + [Fact] + public void WriteResourceDeclaredSingleUntypedProperty_WorksForCollectionValue() + { + // With type name + var property = new ODataProperty + { + Name = "Data", + Value = new ODataCollectionValue + { + TypeName = "Collection(Edm.String)", + Items = new[] + { + "abc", + "xyz" + } + } + }; + + string result = WriteDeclaredUntypedProperty(property); + Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Data\":[\"abc\",\"xyz\"]}", result); + + // without type name + property = new ODataProperty + { + Name = "Data", + Value = new ODataCollectionValue + { + Items = new object[] + { + "abc", + null, + 42 + } + } + }; + + result = WriteDeclaredUntypedProperty(property); + Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Data\":[\"abc\",null,42]}", result); + } + [Fact] + public void WriteResourceDeclaredCollectionUntypedProperty_ThrowsForNonCollectionValue() + { + // With type name + var property = new ODataProperty + { + Name = "Infos", + Value = new ODataPrimitiveValue(42) + }; + + Action test = () => WriteDeclaredUntypedProperty(property); + ODataException exception = Assert.Throws(test); + Assert.Equal(Strings.ValidationUtils_NonPrimitiveTypeForPrimitiveValue("Collection(Edm.Untyped)"), exception.Message); + } + + [Fact] + public void WriteResourceDeclaredCollectionUntypedProperty_WorksForCollectionValue() + { + var property = new ODataProperty + { + Name = "Infos", + Value = new ODataCollectionValue + { + Items = new object[] + { + "abc", + null, + 42 + } + } + }; + + string result = WriteDeclaredUntypedProperty(property); + Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Infos\":[\"abc\",null,42]}", result); + } + + private string WriteDeclaredUntypedProperty(ODataProperty untypedProperty) + { + var entry = new ODataResource + { + TypeName = "Server.NS.ServerEntityType", + Properties = new[] + { + untypedProperty + } + }; + + return this.WriteEntryPayload(this.serverEntitySet, this.serverEntityType, + writer => + { + writer.WriteStart(entry); + writer.WriteEnd(); + }); + } + + [Fact] + public void WriteResourceDeclaredUntypedProperty_WorksForNestedResourceInfo() + { + string result = WriteEntryPayload(this.serverEntitySet, this.serverEntityType, + writer => + { + writer.WriteStart(new ODataResource()); + + writer.WriteStart(new ODataNestedResourceInfo { Name = "Data", IsCollection = true }); + writer.WriteStart(new ODataResourceSet()); + writer.WriteStart(new ODataResource { TypeName = "Edm.Untyped" }); + writer.WriteEnd(); + writer.WriteEnd(); + writer.WriteEnd(); + + writer.WriteStart(new ODataNestedResourceInfo { Name = "Infos", IsCollection = true }); + writer.WriteStart(new ODataResourceSet()); + writer.WriteStart(resource: null); + writer.WriteEnd(); + writer.Write(new ODataPrimitiveValue(32)); + writer.WriteEnd(); + writer.WriteEnd(); + writer.WriteEnd(); + }); + + Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Data\":[{}],\"Infos\":[null,32]}", result); + } + #endregion + + #region non-open entity's property unknown name + known value type [Fact] public void WriteEntryUndeclaredPropertiesTest() { From 51b46766c82bd3d75f112386de991e20fe401be9 Mon Sep 17 00:00:00 2001 From: Sam Xu Date: Mon, 15 May 2023 15:18:22 -0700 Subject: [PATCH 03/14] fixes #2661. Write the nested resource within nested collection in collection (#2666) * fixes #2661. Write the nested resource within nested collection in collection * fixes the failing test --- src/Microsoft.OData.Core/ODataWriterCore.cs | 18 ++++++--- ...htEntryAndFeedSerializerUndecalredTests.cs | 39 +++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.OData.Core/ODataWriterCore.cs b/src/Microsoft.OData.Core/ODataWriterCore.cs index 9a0031e3a6..c68e810c07 100644 --- a/src/Microsoft.OData.Core/ODataWriterCore.cs +++ b/src/Microsoft.OData.Core/ODataWriterCore.cs @@ -2932,7 +2932,7 @@ private void PromoteNestedResourceInfoScope(ODataItem content) NestedResourceInfoScope newScope = previousScope.Clone(WriterState.NestedResourceInfoWithContent); this.scopeStack.Push(newScope); - if (newScope.ItemType == null && content != null && !SkipWriting && !(content is ODataPrimitiveValue)) + if ((newScope.ItemType == null || newScope.ItemType.IsUntyped()) && content != null && !SkipWriting && !(content is ODataPrimitiveValue)) { ODataPrimitiveValue primitiveValue = content as ODataPrimitiveValue; if (primitiveValue != null) @@ -2941,10 +2941,18 @@ private void PromoteNestedResourceInfoScope(ODataItem content) } else { - ODataResourceBase resource = content as ODataResourceBase; - newScope.ResourceType = resource != null - ? GetResourceType(resource) - : GetResourceSetType(content as ODataResourceSetBase); + if (content is ODataResourceBase resource) + { + newScope.ResourceType = string.IsNullOrEmpty(resource.TypeName) ? + EdmUntypedStructuredType.Instance : + GetResourceType(resource); + } + else if (content is ODataResourceSetBase resourceSet) + { + newScope.ResourceType = string.IsNullOrEmpty(resourceSet.TypeName) ? + EdmUntypedStructuredType.Instance : + GetResourceSetType(resourceSet); + } } } } diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs index 43a27d0b57..cc50cdde31 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs @@ -196,6 +196,45 @@ public void WriteResourceDeclaredCollectionUntypedProperty_WorksForCollectionVal Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Infos\":[\"abc\",null,42]}", result); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void WriteResourceCollectionUntypedProperty_WorksResourceInNestedCollectionOfCollection2(bool isOpen) + { + string typeName = isOpen ? "Server.NS.ServerOpenEntityType" : "Server.NS.ServerEntityType"; + EdmEntitySet entitySet = isOpen ? this.serverOpenEntitySet : this.serverEntitySet; + EdmEntityType entityType = isOpen ? this.serverOpenEntityType : this.serverEntityType; + string propertyName = isOpen ? "AnyDynamic" : "Infos"; + + string actual = WriteEntryPayload(entitySet, entityType, + writer => + { + writer.WriteStart(new ODataResource { TypeName = typeName }); + writer.WriteStart(new ODataNestedResourceInfo { Name = propertyName, IsCollection = true }); + writer.WriteStart(new ODataResourceSet { TypeName = "Collection(Edm.Untyped)" }); + writer.WriteStart(new ODataResourceSet()); + writer.WriteStart(new ODataResource + { + TypeName = "Edm.Untyped", + Properties = new ODataProperty[] + { + new ODataProperty { Name = "FirstName", Value = "Kerry"} + } + }); + writer.WriteEnd(); // End of "Edm.Untyped" + writer.WriteEnd(); + writer.WriteEnd(); // End of "Infos" / AnyDynamic + writer.WriteEnd(); + writer.WriteEnd(); + }); + + string result = isOpen ? + "{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverOpenEntitySet/$entity\",\"AnyDynamic\":[[{\"FirstName\":\"Kerry\"}]]}" : + "{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Infos\":[[{\"FirstName\":\"Kerry\"}]]}"; + + Assert.Equal(result, actual); + } + private string WriteDeclaredUntypedProperty(ODataProperty untypedProperty) { var entry = new ODataResource From 9bd1a214eaaa8ee3c1845ff833e135a9faae2de4 Mon Sep 17 00:00:00 2001 From: Sam Xu Date: Mon, 15 May 2023 14:39:01 -0700 Subject: [PATCH 04/14] Update the release version to 7.15.6 --- .../PublicAPI/net45/PublicAPI.Shipped.txt | 10 ++++++++++ .../PublicAPI/net45/PublicAPI.Unshipped.txt | 10 ---------- .../PublicAPI/netstandard2.0/PublicAPI.Shipped.txt | 10 ++++++++++ .../PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt | 10 ---------- tools/CustomMSBuild/Versioning.props | 2 +- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Microsoft.OData.Client/PublicAPI/net45/PublicAPI.Shipped.txt b/src/Microsoft.OData.Client/PublicAPI/net45/PublicAPI.Shipped.txt index 72bef56a45..d71cef5a80 100644 --- a/src/Microsoft.OData.Client/PublicAPI/net45/PublicAPI.Shipped.txt +++ b/src/Microsoft.OData.Client/PublicAPI/net45/PublicAPI.Shipped.txt @@ -1,3 +1,13 @@ +Microsoft.OData.Client.DataServiceClientResponsePipelineConfiguration.OnDeltaFeedEnded(System.Action action) -> Microsoft.OData.Client.DataServiceClientResponsePipelineConfiguration +Microsoft.OData.Client.DataServiceClientResponsePipelineConfiguration.OnDeltaFeedStarted(System.Action action) -> Microsoft.OData.Client.DataServiceClientResponsePipelineConfiguration +Microsoft.OData.Client.ReadingDeltaFeedArgs +Microsoft.OData.Client.ReadingDeltaFeedArgs.DeltaFeed.get -> Microsoft.OData.ODataDeltaResourceSet +Microsoft.OData.Client.ReadingDeltaFeedArgs.ReadingDeltaFeedArgs(Microsoft.OData.ODataDeltaResourceSet deltaFeed) -> void +Microsoft.OData.Client.SaveChangesOptions.BulkUpdate = 128 -> Microsoft.OData.Client.SaveChangesOptions +Microsoft.OData.Client.SendingRequest2EventArgs.IsBulkUpdate.get -> bool +virtual Microsoft.OData.Client.DataServiceContext.BulkUpdate(params T[] objects) -> Microsoft.OData.Client.DataServiceResponse +virtual Microsoft.OData.Client.DataServiceContext.BulkUpdateAsync(params T[] objects) -> System.Threading.Tasks.Task +virtual Microsoft.OData.Client.DataServiceContext.BulkUpdateAsync(System.Threading.CancellationToken cancellationToken, params T[] objects) -> System.Threading.Tasks.Task virtual Microsoft.OData.Client.DataServiceContext.AutoNullPropagation.get -> bool virtual Microsoft.OData.Client.DataServiceContext.AutoNullPropagation.set -> void abstract Microsoft.OData.Client.ALinq.UriParser.PathSegmentToken.Accept(Microsoft.OData.Client.ALinq.UriParser.IPathSegmentTokenVisitor visitor) -> void diff --git a/src/Microsoft.OData.Client/PublicAPI/net45/PublicAPI.Unshipped.txt b/src/Microsoft.OData.Client/PublicAPI/net45/PublicAPI.Unshipped.txt index 70ba02966a..e69de29bb2 100644 --- a/src/Microsoft.OData.Client/PublicAPI/net45/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OData.Client/PublicAPI/net45/PublicAPI.Unshipped.txt @@ -1,10 +0,0 @@ -Microsoft.OData.Client.DataServiceClientResponsePipelineConfiguration.OnDeltaFeedEnded(System.Action action) -> Microsoft.OData.Client.DataServiceClientResponsePipelineConfiguration -Microsoft.OData.Client.DataServiceClientResponsePipelineConfiguration.OnDeltaFeedStarted(System.Action action) -> Microsoft.OData.Client.DataServiceClientResponsePipelineConfiguration -Microsoft.OData.Client.ReadingDeltaFeedArgs -Microsoft.OData.Client.ReadingDeltaFeedArgs.DeltaFeed.get -> Microsoft.OData.ODataDeltaResourceSet -Microsoft.OData.Client.ReadingDeltaFeedArgs.ReadingDeltaFeedArgs(Microsoft.OData.ODataDeltaResourceSet deltaFeed) -> void -Microsoft.OData.Client.SaveChangesOptions.BulkUpdate = 128 -> Microsoft.OData.Client.SaveChangesOptions -Microsoft.OData.Client.SendingRequest2EventArgs.IsBulkUpdate.get -> bool -virtual Microsoft.OData.Client.DataServiceContext.BulkUpdate(params T[] objects) -> Microsoft.OData.Client.DataServiceResponse -virtual Microsoft.OData.Client.DataServiceContext.BulkUpdateAsync(params T[] objects) -> System.Threading.Tasks.Task -virtual Microsoft.OData.Client.DataServiceContext.BulkUpdateAsync(System.Threading.CancellationToken cancellationToken, params T[] objects) -> System.Threading.Tasks.Task \ No newline at end of file diff --git a/src/Microsoft.OData.Client/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt b/src/Microsoft.OData.Client/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt index 72bef56a45..d71cef5a80 100644 --- a/src/Microsoft.OData.Client/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt +++ b/src/Microsoft.OData.Client/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt @@ -1,3 +1,13 @@ +Microsoft.OData.Client.DataServiceClientResponsePipelineConfiguration.OnDeltaFeedEnded(System.Action action) -> Microsoft.OData.Client.DataServiceClientResponsePipelineConfiguration +Microsoft.OData.Client.DataServiceClientResponsePipelineConfiguration.OnDeltaFeedStarted(System.Action action) -> Microsoft.OData.Client.DataServiceClientResponsePipelineConfiguration +Microsoft.OData.Client.ReadingDeltaFeedArgs +Microsoft.OData.Client.ReadingDeltaFeedArgs.DeltaFeed.get -> Microsoft.OData.ODataDeltaResourceSet +Microsoft.OData.Client.ReadingDeltaFeedArgs.ReadingDeltaFeedArgs(Microsoft.OData.ODataDeltaResourceSet deltaFeed) -> void +Microsoft.OData.Client.SaveChangesOptions.BulkUpdate = 128 -> Microsoft.OData.Client.SaveChangesOptions +Microsoft.OData.Client.SendingRequest2EventArgs.IsBulkUpdate.get -> bool +virtual Microsoft.OData.Client.DataServiceContext.BulkUpdate(params T[] objects) -> Microsoft.OData.Client.DataServiceResponse +virtual Microsoft.OData.Client.DataServiceContext.BulkUpdateAsync(params T[] objects) -> System.Threading.Tasks.Task +virtual Microsoft.OData.Client.DataServiceContext.BulkUpdateAsync(System.Threading.CancellationToken cancellationToken, params T[] objects) -> System.Threading.Tasks.Task virtual Microsoft.OData.Client.DataServiceContext.AutoNullPropagation.get -> bool virtual Microsoft.OData.Client.DataServiceContext.AutoNullPropagation.set -> void abstract Microsoft.OData.Client.ALinq.UriParser.PathSegmentToken.Accept(Microsoft.OData.Client.ALinq.UriParser.IPathSegmentTokenVisitor visitor) -> void diff --git a/src/Microsoft.OData.Client/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/src/Microsoft.OData.Client/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 7e2cf2df44..e69de29bb2 100644 --- a/src/Microsoft.OData.Client/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OData.Client/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,10 +0,0 @@ -Microsoft.OData.Client.DataServiceClientResponsePipelineConfiguration.OnDeltaFeedEnded(System.Action action) -> Microsoft.OData.Client.DataServiceClientResponsePipelineConfiguration -Microsoft.OData.Client.DataServiceClientResponsePipelineConfiguration.OnDeltaFeedStarted(System.Action action) -> Microsoft.OData.Client.DataServiceClientResponsePipelineConfiguration -Microsoft.OData.Client.ReadingDeltaFeedArgs -Microsoft.OData.Client.ReadingDeltaFeedArgs.DeltaFeed.get -> Microsoft.OData.ODataDeltaResourceSet -Microsoft.OData.Client.ReadingDeltaFeedArgs.ReadingDeltaFeedArgs(Microsoft.OData.ODataDeltaResourceSet deltaFeed) -> void -Microsoft.OData.Client.SaveChangesOptions.BulkUpdate = 128 -> Microsoft.OData.Client.SaveChangesOptions -Microsoft.OData.Client.SendingRequest2EventArgs.IsBulkUpdate.get -> bool -virtual Microsoft.OData.Client.DataServiceContext.BulkUpdate(params T[] objects) -> Microsoft.OData.Client.DataServiceResponse -virtual Microsoft.OData.Client.DataServiceContext.BulkUpdateAsync(params T[] objects) -> System.Threading.Tasks.Task -virtual Microsoft.OData.Client.DataServiceContext.BulkUpdateAsync(System.Threading.CancellationToken cancellationToken, params T[] objects) -> System.Threading.Tasks.Task diff --git a/tools/CustomMSBuild/Versioning.props b/tools/CustomMSBuild/Versioning.props index d58f5a42e3..9c533ecfac 100644 --- a/tools/CustomMSBuild/Versioning.props +++ b/tools/CustomMSBuild/Versioning.props @@ -3,7 +3,7 @@ 7 - 15 + 16 0 From 3842542efa42e3ac86b4ee8ccc57071444a96061 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Wed, 10 May 2023 15:54:10 +0200 Subject: [PATCH 05/14] Fixed the http attribute handling within ODataJsonLightBatchPayloadItemPropertiesCache.cs --- ...sonLightBatchPayloadItemPropertiesCache.cs | 92 +++++++++++++--- .../ODataJsonLightBatchReaderTests.cs | 103 ++++++++++++++++++ 2 files changed, 177 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index 0180311144..19f927e4ed 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -227,14 +227,26 @@ private void ScanJsonProperties() case PropertyNameMethod: case PropertyNameUrl: { - jsonProperties.Add(propertyName, this.jsonReader.ReadStringValue()); + string propertyValueString = this.jsonReader.ReadStringValue(); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueString); + } } break; case PropertyNameStatus: { - jsonProperties.Add(propertyName, this.jsonReader.ReadPrimitiveValue()); + object propertyValueObject = this.jsonReader.ReadPrimitiveValue(); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueObject); + } } break; @@ -250,7 +262,11 @@ private void ScanJsonProperties() this.jsonReader.ReadEndArray(); - jsonProperties.Add(propertyName, dependsOnIds); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, dependsOnIds); + } } break; @@ -267,7 +283,8 @@ private void ScanJsonProperties() while (this.jsonReader.NodeType != JsonNodeType.EndObject) { string headerName = this.jsonReader.ReadPropertyName(); - string headerValue = this.jsonReader.ReadPrimitiveValue().ToString(); + object headerValueObject = this.jsonReader.ReadPrimitiveValue(); + string headerValue = headerValueObject?.ToString(); // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) @@ -275,12 +292,20 @@ private void ScanJsonProperties() contentTypeHeader = headerValue; } - headers.Add(headerName, headerValue); + // Skip duplicate http headers + if (!headers.ContainsKeyOrdinal(headerName)) + { + headers.Add(headerName, headerValue); + } } this.jsonReader.ReadEndObject(); - jsonProperties.Add(propertyName, headers); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, headers); + } if (!this.isStreamPopulated && bodyContentStream != null) { @@ -294,7 +319,12 @@ private void ScanJsonProperties() case PropertyNameBody: { bodyContentStream = CreateJsonPayloadBodyContentStream(contentTypeHeader); - jsonProperties.Add(propertyName, bodyContentStream); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, bodyContentStream); + } } break; @@ -365,16 +395,24 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() case PropertyNameAtomicityGroup: case PropertyNameMethod: case PropertyNameUrl: - jsonProperties.Add( - propertyName, - await this.asynchronousJsonReader.ReadStringValueAsync().ConfigureAwait(false)); + string propertyValueString = await this.asynchronousJsonReader.ReadStringValueAsync().ConfigureAwait(false); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueString); + } break; case PropertyNameStatus: - jsonProperties.Add( - propertyName, - await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)); + object propertyValueObject = await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueObject); + } break; @@ -390,7 +428,11 @@ await this.asynchronousJsonReader.ReadStartArrayAsync() await this.asynchronousJsonReader.ReadEndArrayAsync() .ConfigureAwait(false); - jsonProperties.Add(propertyName, dependsOnIds); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, dependsOnIds); + } break; @@ -407,7 +449,8 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() { string headerName = await this.asynchronousJsonReader.ReadPropertyNameAsync() .ConfigureAwait(false); - string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)).ToString(); + object headerValueObject = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)); + string headerValue = headerValueObject?.ToString(); // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) @@ -415,13 +458,21 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() contentTypeHeader = headerValue; } - headers.Add(headerName, headerValue); + // Skip duplicate http headers + if (!headers.ContainsKeyOrdinal(headerName)) + { + headers.Add(headerName, headerValue); + } } await this.asynchronousJsonReader.ReadEndObjectAsync() .ConfigureAwait(false); - jsonProperties.Add(propertyName, headers); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, headers); + } if (!this.isStreamPopulated && bodyContentStream != null) { @@ -435,7 +486,12 @@ await bodyContentStream.PopulateCachedBodyContentAsync(contentTypeHeader) case PropertyNameBody: bodyContentStream = await CreateJsonPayloadBodyContentStreamAsync(contentTypeHeader) .ConfigureAwait(false); - jsonProperties.Add(propertyName, bodyContentStream); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, bodyContentStream); + } break; diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs index e17e1b77e4..4156457e0f 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs @@ -363,6 +363,109 @@ await DoReadAsync( isResponse: false); } + [Fact] + public async Task ReadBatchRequestWithDuplicateAttributesAsync() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"GET\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\"}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + await SetupJsonLightBatchReaderAndRunTestAsync( + payload, + async (jsonLightBatchReader) => + { + try + { + while (await jsonLightBatchReader.ReadAsync()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + } + } + + Assert.True(true, "The test was successful, because no ArgumentException was thrown"); + } + catch (ArgumentException ex) + { + Assert.True(false, ex.Message); + } + }, + isResponse: false); + } + + [Fact] + public async Task ReadBatchRequestWithDuplicateHeadersAsync() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"duplicate-header\":\"value1\",\"duplicate-header\":\"value2\"}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + await SetupJsonLightBatchReaderAndRunTestAsync( + payload, + async (jsonLightBatchReader) => + { + try + { + while (await jsonLightBatchReader.ReadAsync()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + } + } + + Assert.True(true, "The test was successful, because no ArgumentException was thrown"); + } + catch (ArgumentException ex) + { + Assert.True(false, ex.Message); + } + }, + isResponse: false); + } + + [Fact] + public async Task ReadBatchRequestWithNullHeadersAsync() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"null-header\":null}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + await SetupJsonLightBatchReaderAndRunTestAsync( + payload, + async (jsonLightBatchReader) => + { + try + { + while (await jsonLightBatchReader.ReadAsync()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + } + } + + Assert.True(true, "The test was successful, because no ArgumentException was thrown"); + } + catch (ArgumentException ex) + { + Assert.True(false, ex.Message); + } + }, + isResponse: false); + } + [Fact] public async Task ReadBatchResponseAsync() { From f50a630943e2c04c75c6bff356e6039f21242157 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Mon, 15 May 2023 14:14:12 +0200 Subject: [PATCH 06/14] Switched http attribute test assert true to false to highlight test failure due to an Exception --- .../JsonLight/ODataJsonLightBatchReaderTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs index 4156457e0f..5d8d743626 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs @@ -392,7 +392,7 @@ await SetupJsonLightBatchReaderAndRunTestAsync( } catch (ArgumentException ex) { - Assert.True(false, ex.Message); + Assert.False(true, ex.Message); } }, isResponse: false); @@ -426,7 +426,7 @@ await SetupJsonLightBatchReaderAndRunTestAsync( } catch (ArgumentException ex) { - Assert.True(false, ex.Message); + Assert.False(true, ex.Message); } }, isResponse: false); @@ -460,7 +460,7 @@ await SetupJsonLightBatchReaderAndRunTestAsync( } catch (ArgumentException ex) { - Assert.True(false, ex.Message); + Assert.False(true, ex.Message); } }, isResponse: false); From 8a70688a2ff410d86de779f2d4a61df2442be2ad Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Mon, 19 Jun 2023 18:44:15 +0200 Subject: [PATCH 07/14] Fixed duplicate json property handling ODataJsonLightBatchPayloadItemPropertiesCache, throwing an exception for duplicate properties, except header properties. --- ...sonLightBatchPayloadItemPropertiesCache.cs | 97 ++++++------------- .../Microsoft.OData.Core.cs | 1 + .../Parameterized.Microsoft.OData.Core.cs | 8 ++ .../ODataJsonLightBatchReaderTests.cs | 35 ------- 4 files changed, 40 insertions(+), 101 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index 19f927e4ed..ddc713c302 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -220,6 +220,12 @@ private void ScanJsonProperties() // Convert to upper case to support case-insensitive request property names string propertyName = Normalize(this.jsonReader.ReadPropertyName()); + // Throw an ODataException, if a duplicate json property was detected + if (jsonProperties.ContainsKey(propertyName)) + { + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch(propertyName)); + } + switch (propertyName) { case PropertyNameId: @@ -227,26 +233,14 @@ private void ScanJsonProperties() case PropertyNameMethod: case PropertyNameUrl: { - string propertyValueString = this.jsonReader.ReadStringValue(); - - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, propertyValueString); - } + jsonProperties.Add(propertyName, this.jsonReader.ReadStringValue()); } break; case PropertyNameStatus: { - object propertyValueObject = this.jsonReader.ReadPrimitiveValue(); - - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, propertyValueObject); - } + jsonProperties.Add(propertyName, this.jsonReader.ReadPrimitiveValue()); } break; @@ -262,11 +256,7 @@ private void ScanJsonProperties() this.jsonReader.ReadEndArray(); - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, dependsOnIds); - } + jsonProperties.Add(propertyName, dependsOnIds); } break; @@ -292,20 +282,19 @@ private void ScanJsonProperties() contentTypeHeader = headerValue; } - // Skip duplicate http headers if (!headers.ContainsKeyOrdinal(headerName)) { headers.Add(headerName, headerValue); } + else + { + headers[propertyName] = headerValue; + } } this.jsonReader.ReadEndObject(); - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, headers); - } + jsonProperties.Add(propertyName, headers); if (!this.isStreamPopulated && bodyContentStream != null) { @@ -319,12 +308,7 @@ private void ScanJsonProperties() case PropertyNameBody: { bodyContentStream = CreateJsonPayloadBodyContentStream(contentTypeHeader); - - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, bodyContentStream); - } + jsonProperties.Add(propertyName, bodyContentStream); } break; @@ -389,30 +373,28 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() // Convert to upper case to support case-insensitive request property names string propertyName = Normalize(await this.asynchronousJsonReader.ReadPropertyNameAsync().ConfigureAwait(false)); + // Throw an ODataException, if a duplicate json property was detected + if (jsonProperties.ContainsKey(propertyName)) + { + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch(propertyName)); + } + switch (propertyName) { case PropertyNameId: case PropertyNameAtomicityGroup: case PropertyNameMethod: case PropertyNameUrl: - string propertyValueString = await this.asynchronousJsonReader.ReadStringValueAsync().ConfigureAwait(false); - - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, propertyValueString); - } + jsonProperties.Add( + propertyName, + await this.asynchronousJsonReader.ReadStringValueAsync().ConfigureAwait(false)); break; case PropertyNameStatus: - object propertyValueObject = await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false); - - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, propertyValueObject); - } + jsonProperties.Add( + propertyName, + await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)); break; @@ -428,11 +410,7 @@ await this.asynchronousJsonReader.ReadStartArrayAsync() await this.asynchronousJsonReader.ReadEndArrayAsync() .ConfigureAwait(false); - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, dependsOnIds); - } + jsonProperties.Add(propertyName, dependsOnIds); break; @@ -458,21 +436,13 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() contentTypeHeader = headerValue; } - // Skip duplicate http headers - if (!headers.ContainsKeyOrdinal(headerName)) - { - headers.Add(headerName, headerValue); - } + headers[headerName] = headerValue; } await this.asynchronousJsonReader.ReadEndObjectAsync() .ConfigureAwait(false); - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, headers); - } + jsonProperties.Add(propertyName, headers); if (!this.isStreamPopulated && bodyContentStream != null) { @@ -486,12 +456,7 @@ await bodyContentStream.PopulateCachedBodyContentAsync(contentTypeHeader) case PropertyNameBody: bodyContentStream = await CreateJsonPayloadBodyContentStreamAsync(contentTypeHeader) .ConfigureAwait(false); - - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, bodyContentStream); - } + jsonProperties.Add(propertyName, bodyContentStream); break; diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs index d1412751c7..e2eaa291ac 100644 --- a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs @@ -193,6 +193,7 @@ internal sealed class TextRes { internal const string ODataBatchReaderStream_MultiByteEncodingsNotSupported = "ODataBatchReaderStream_MultiByteEncodingsNotSupported"; internal const string ODataBatchReaderStream_UnexpectedEndOfInput = "ODataBatchReaderStream_UnexpectedEndOfInput"; internal const string ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached = "ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached"; + internal const string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch"; internal const string ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch"; internal const string ODataJsonLightBatchBodyContentReaderStream_UnexpectedNodeType = "ODataJsonLightBatchBodyContentReaderStream_UnexpectedNodeType"; internal const string ODataJsonLightBatchBodyContentReaderStream_UnsupportedContentTypeInHeader = "ODataJsonLightBatchBodyContentReaderStream_UnsupportedContentTypeInHeader"; diff --git a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs index fa27bc4b55..bcee9f8429 100644 --- a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs @@ -1647,6 +1647,14 @@ internal static string ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitRea return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached, p0); } + /// + /// A string like "Duplicate property name '{0}' for message in batch." + /// + internal static string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch(object p0) + { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch, p0); + } + /// /// A string like "Unknown property name '{0}' for message in batch." /// diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs index 5d8d743626..7fb8a14a11 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs @@ -363,41 +363,6 @@ await DoReadAsync( isResponse: false); } - [Fact] - public async Task ReadBatchRequestWithDuplicateAttributesAsync() - { - var payload = "{\"requests\": [{" + - "\"id\": \"1\"," + - "\"method\": \"GET\"," + - "\"method\": \"POST\"," + - "\"url\": \"http://tempuri.org/Customers\"," + - "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\"}, " + - "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; - - await SetupJsonLightBatchReaderAndRunTestAsync( - payload, - async (jsonLightBatchReader) => - { - try - { - while (await jsonLightBatchReader.ReadAsync()) - { - if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) - { - await jsonLightBatchReader.CreateOperationRequestMessageAsync(); - } - } - - Assert.True(true, "The test was successful, because no ArgumentException was thrown"); - } - catch (ArgumentException ex) - { - Assert.False(true, ex.Message); - } - }, - isResponse: false); - } - [Fact] public async Task ReadBatchRequestWithDuplicateHeadersAsync() { From 0707d6c486d18e8003ad726c36b31c0ca6da18f7 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Tue, 20 Jun 2023 08:14:20 +0200 Subject: [PATCH 08/14] Little optimization at the json property header handling. --- ...ataJsonLightBatchPayloadItemPropertiesCache.cs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index ddc713c302..153c10b21e 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -273,8 +273,7 @@ private void ScanJsonProperties() while (this.jsonReader.NodeType != JsonNodeType.EndObject) { string headerName = this.jsonReader.ReadPropertyName(); - object headerValueObject = this.jsonReader.ReadPrimitiveValue(); - string headerValue = headerValueObject?.ToString(); + string headerValue = this.jsonReader.ReadPrimitiveValue()?.ToString(); // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) @@ -282,14 +281,7 @@ private void ScanJsonProperties() contentTypeHeader = headerValue; } - if (!headers.ContainsKeyOrdinal(headerName)) - { - headers.Add(headerName, headerValue); - } - else - { - headers[propertyName] = headerValue; - } + headers[propertyName] = headerValue; } this.jsonReader.ReadEndObject(); @@ -427,8 +419,7 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() { string headerName = await this.asynchronousJsonReader.ReadPropertyNameAsync() .ConfigureAwait(false); - object headerValueObject = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)); - string headerValue = headerValueObject?.ToString(); + string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false))?.ToString(); // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) From bcffbfc6760b9ed02e4d579bb0222559dc3a86b6 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Wed, 21 Jun 2023 11:36:17 +0200 Subject: [PATCH 09/14] Minor fix, replacing duplicate http headers correctly in the non async method --- .../JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index 153c10b21e..eae05ee791 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -281,7 +281,7 @@ private void ScanJsonProperties() contentTypeHeader = headerValue; } - headers[propertyName] = headerValue; + headers[headerName] = headerValue; } this.jsonReader.ReadEndObject(); From 22cc32a2185ecca4f6ac0907f10febbfb6efbfab Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Fri, 23 Jun 2023 08:37:59 +0200 Subject: [PATCH 10/14] Changed the handling of duplicate json property headers, which will now also throw an exception. --- ...sonLightBatchPayloadItemPropertiesCache.cs | 16 +++++++-- .../Microsoft.OData.Core.cs | 1 + .../Parameterized.Microsoft.OData.Core.cs | 8 +++++ .../ODataJsonLightBatchReaderTests.cs | 34 ------------------- 4 files changed, 23 insertions(+), 36 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index eae05ee791..ab88b956da 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -275,13 +275,19 @@ private void ScanJsonProperties() string headerName = this.jsonReader.ReadPropertyName(); string headerValue = this.jsonReader.ReadPrimitiveValue()?.ToString(); + // Throw an ODataException, if a duplicate header was detected + if (headers.ContainsKeyOrdinal(headerName)) + { + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch(headerName)); + } + // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) { contentTypeHeader = headerValue; } - headers[headerName] = headerValue; + headers.Add(headerName, headerValue); } this.jsonReader.ReadEndObject(); @@ -421,13 +427,19 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() .ConfigureAwait(false); string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false))?.ToString(); + // Throw an ODataException, if a duplicate header was detected + if (headers.ContainsKeyOrdinal(headerName)) + { + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch(headerName)); + } + // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) { contentTypeHeader = headerValue; } - headers[headerName] = headerValue; + headers.Add(headerName, headerValue); } await this.asynchronousJsonReader.ReadEndObjectAsync() diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs index e2eaa291ac..5fdb424c6a 100644 --- a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs @@ -195,6 +195,7 @@ internal sealed class TextRes { internal const string ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached = "ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached"; internal const string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch"; internal const string ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch"; + internal const string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch"; internal const string ODataJsonLightBatchBodyContentReaderStream_UnexpectedNodeType = "ODataJsonLightBatchBodyContentReaderStream_UnexpectedNodeType"; internal const string ODataJsonLightBatchBodyContentReaderStream_UnsupportedContentTypeInHeader = "ODataJsonLightBatchBodyContentReaderStream_UnsupportedContentTypeInHeader"; internal const string ODataAsyncWriter_CannotCreateResponseWhenNotWritingResponse = "ODataAsyncWriter_CannotCreateResponseWhenNotWritingResponse"; diff --git a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs index bcee9f8429..80af159f10 100644 --- a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs @@ -1663,6 +1663,14 @@ internal static string ODataJsonLightBatchPayloadItemPropertiesCache_UnknownProp return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch, p0); } + /// + /// A string like "Duplicate header name '{0}' for message in batch." + /// + internal static string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch(object p0) + { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch, p0); + } + /// /// A string like "Unexpected reader.NodeType: {0}." /// diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs index 7fb8a14a11..d53a3c6b39 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs @@ -363,40 +363,6 @@ await DoReadAsync( isResponse: false); } - [Fact] - public async Task ReadBatchRequestWithDuplicateHeadersAsync() - { - var payload = "{\"requests\": [{" + - "\"id\": \"1\"," + - "\"method\": \"POST\"," + - "\"url\": \"http://tempuri.org/Customers\"," + - "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"duplicate-header\":\"value1\",\"duplicate-header\":\"value2\"}, " + - "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; - - await SetupJsonLightBatchReaderAndRunTestAsync( - payload, - async (jsonLightBatchReader) => - { - try - { - while (await jsonLightBatchReader.ReadAsync()) - { - if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) - { - await jsonLightBatchReader.CreateOperationRequestMessageAsync(); - } - } - - Assert.True(true, "The test was successful, because no ArgumentException was thrown"); - } - catch (ArgumentException ex) - { - Assert.False(true, ex.Message); - } - }, - isResponse: false); - } - [Fact] public async Task ReadBatchRequestWithNullHeadersAsync() { From 3a63962669c097b1ef75a765c02cd25a4e866fcb Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Mon, 26 Jun 2023 10:36:31 +0200 Subject: [PATCH 11/14] Renamed exception messages & added them to the resource file --- ...JsonLightBatchPayloadItemPropertiesCache.cs | 8 ++++---- .../Microsoft.OData.Core.cs | 4 ++-- .../Microsoft.OData.Core.txt | 2 ++ .../Parameterized.Microsoft.OData.Core.cs | 18 +++++++++--------- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index ab88b956da..c526644e6a 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -223,7 +223,7 @@ private void ScanJsonProperties() // Throw an ODataException, if a duplicate json property was detected if (jsonProperties.ContainsKey(propertyName)) { - throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch(propertyName)); + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch(propertyName)); } switch (propertyName) @@ -278,7 +278,7 @@ private void ScanJsonProperties() // Throw an ODataException, if a duplicate header was detected if (headers.ContainsKeyOrdinal(headerName)) { - throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch(headerName)); + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch(headerName)); } // Remember the Content-Type header value. @@ -374,7 +374,7 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() // Throw an ODataException, if a duplicate json property was detected if (jsonProperties.ContainsKey(propertyName)) { - throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch(propertyName)); + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch(propertyName)); } switch (propertyName) @@ -430,7 +430,7 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() // Throw an ODataException, if a duplicate header was detected if (headers.ContainsKeyOrdinal(headerName)) { - throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch(headerName)); + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch(headerName)); } // Remember the Content-Type header value. diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs index 5fdb424c6a..637fd0bc56 100644 --- a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs @@ -193,9 +193,9 @@ internal sealed class TextRes { internal const string ODataBatchReaderStream_MultiByteEncodingsNotSupported = "ODataBatchReaderStream_MultiByteEncodingsNotSupported"; internal const string ODataBatchReaderStream_UnexpectedEndOfInput = "ODataBatchReaderStream_UnexpectedEndOfInput"; internal const string ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached = "ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached"; - internal const string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch"; internal const string ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch"; - internal const string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch"; + internal const string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch"; + internal const string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch"; internal const string ODataJsonLightBatchBodyContentReaderStream_UnexpectedNodeType = "ODataJsonLightBatchBodyContentReaderStream_UnexpectedNodeType"; internal const string ODataJsonLightBatchBodyContentReaderStream_UnsupportedContentTypeInHeader = "ODataJsonLightBatchBodyContentReaderStream_UnsupportedContentTypeInHeader"; internal const string ODataAsyncWriter_CannotCreateResponseWhenNotWritingResponse = "ODataAsyncWriter_CannotCreateResponseWhenNotWritingResponse"; diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.txt b/src/Microsoft.OData.Core/Microsoft.OData.Core.txt index fc88bb09b3..7da6f31a80 100644 --- a/src/Microsoft.OData.Core/Microsoft.OData.Core.txt +++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.txt @@ -207,6 +207,8 @@ ODataBatchReaderStream_UnexpectedEndOfInput=Encountered an unexpected end of inp ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached=Too many white spaces after a boundary delimiter and before the terminating line resource set. For security reasons, the total number of characters for a boundary including white spaces must not exceed {0}. ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch=Unknown property name '{0}' for message in batch. +ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch=Duplicate property name '{0}' for request in batch. +ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch=Duplicate header name '{0}' for request in batch. ODataJsonLightBatchBodyContentReaderStream_UnexpectedNodeType=Unexpected reader.NodeType: {0}. ODataJsonLightBatchBodyContentReaderStream_UnsupportedContentTypeInHeader=Unknown/undefined type, new type that needs to be supported: {0}? diff --git a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs index 80af159f10..3d5b11e4e9 100644 --- a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs @@ -1648,27 +1648,27 @@ internal static string ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitRea } /// - /// A string like "Duplicate property name '{0}' for message in batch." + /// A string like "Unknown property name '{0}' for message in batch." /// - internal static string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch(object p0) + internal static string ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch(object p0) { - return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch, p0); + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch, p0); } /// - /// A string like "Unknown property name '{0}' for message in batch." + /// A string like "Duplicate property name '{0}' for request in batch." /// - internal static string ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch(object p0) + internal static string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch(object p0) { - return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch, p0); + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch, p0); } /// - /// A string like "Duplicate header name '{0}' for message in batch." + /// A string like "Duplicate header name '{0}' for request in batch." /// - internal static string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch(object p0) + internal static string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch(object p0) { - return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch, p0); + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch, p0); } /// From d2aa56a4f0677f642ed478f75d2601c2192a57e4 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Mon, 3 Jul 2023 16:16:00 +0200 Subject: [PATCH 12/14] Improved ODataJsonLightBatchReaderTests for NULL value headers and added test cases for duplicate headers and properties --- .../ODataJsonLightBatchReaderTests.cs | 80 ++++++++++++++++++- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs index d53a3c6b39..5bf136effe 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs @@ -383,11 +383,13 @@ await SetupJsonLightBatchReaderAndRunTestAsync( { if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) { - await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + var operationRequestMessage = await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + // Verify that the Property "null-header" exists and it's value is set to NULL + var nullHeaderProperty = operationRequestMessage.Headers.FirstOrDefault(p => p.Key == "null-header"); + Assert.NotNull(nullHeaderProperty.Key); + Assert.Null(nullHeaderProperty.Value); } } - - Assert.True(true, "The test was successful, because no ArgumentException was thrown"); } catch (ArgumentException ex) { @@ -397,6 +399,78 @@ await SetupJsonLightBatchReaderAndRunTestAsync( isResponse: false); } + [Fact] + public async Task ReadBatchRequestWithDuplicatePropertiesAsync() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"atomicityGroup\": \"g1\"," + + "\"atomicityGroup\": \"g2\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\"}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + await SetupJsonLightBatchReaderAndRunTestAsync( + payload, + async (jsonLightBatchReader) => + { + try + { + while (await jsonLightBatchReader.ReadAsync()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + // The json properties are just iterated through and have no purpose for this test case + await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + } + } + Assert.False(true, "The test failed, because the duplicate header has thrown no ODataException"); + } + catch (ODataException ex) + { + // Verify that the correct duplicate property has raised the ODataException + Assert.Equal(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch("ATOMICITYGROUP"), ex.Message); + } + }, + isResponse: false); + } + + [Fact] + public async Task ReadBatchRequestWithDuplicateHeadersAsync() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"duplicate-header\":\"value1\",\"duplicate-header\":\"value2\"}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + await SetupJsonLightBatchReaderAndRunTestAsync( + payload, + async (jsonLightBatchReader) => + { + try + { + while (await jsonLightBatchReader.ReadAsync()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + // The json properties are just iterated through and have no purpose for this test case + await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + } + } + Assert.False(true, "The test failed, because the duplicate header has thrown no ODataException"); + } + catch (ODataException ex) + { + // Verify that the correct duplicate header has raised the ODataException + Assert.Equal(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch("duplicate-header"), ex.Message); + } + }, + isResponse: false); + } + [Fact] public async Task ReadBatchResponseAsync() { From 23d04e9ae0768b9417dd9b60482d26d2c7ecadb0 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Mon, 10 Jul 2023 12:59:19 +0200 Subject: [PATCH 13/14] Added synchronous unit tests, added AssertThrowsAsync to duplicate json property unit tests --- .../ODataJsonLightBatchReaderTests.cs | 158 +++++++++++++++--- 1 file changed, 133 insertions(+), 25 deletions(-) diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs index 5bf136effe..982a06b056 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs @@ -363,6 +363,42 @@ await DoReadAsync( isResponse: false); } + [Fact] + public void ReadBatchRequestWithNullHeaders() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"null-header\":null}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + SetupJsonLightBatchReaderAndRunTest( + payload, + (jsonLightBatchReader) => + { + try + { + while (jsonLightBatchReader.Read()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + var operationRequestMessage = jsonLightBatchReader.CreateOperationRequestMessage(); + // Verify that the Property "null-header" exists and it's value is set to NULL + var nullHeaderProperty = operationRequestMessage.Headers.FirstOrDefault(p => p.Key == "null-header"); + Assert.NotNull(nullHeaderProperty.Key); + Assert.Null(nullHeaderProperty.Value); + } + } + } + catch (NullReferenceException ex) + { + Assert.False(true, ex.Message); + } + }, + isResponse: false); + } + [Fact] public async Task ReadBatchRequestWithNullHeadersAsync() { @@ -391,7 +427,7 @@ await SetupJsonLightBatchReaderAndRunTestAsync( } } } - catch (ArgumentException ex) + catch (NullReferenceException ex) { Assert.False(true, ex.Message); } @@ -400,7 +436,7 @@ await SetupJsonLightBatchReaderAndRunTestAsync( } [Fact] - public async Task ReadBatchRequestWithDuplicatePropertiesAsync() + public void ReadBatchRequestWithDuplicateProperties() { var payload = "{\"requests\": [{" + "\"id\": \"1\"," + @@ -411,29 +447,89 @@ public async Task ReadBatchRequestWithDuplicatePropertiesAsync() "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\"}, " + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; - await SetupJsonLightBatchReaderAndRunTestAsync( + var exception = Assert.Throws( + () => SetupJsonLightBatchReaderAndRunTest( + payload, + (jsonLightBatchReader) => + { + while (jsonLightBatchReader.Read()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + // The json properties are just iterated through and have no purpose for this test case + jsonLightBatchReader.CreateOperationRequestMessage(); + } + } + Assert.False(true, "The test failed, because the duplicate header has not thrown an ODataException"); + }, + isResponse: false)); + + // Verify that the correct duplicate property has raised the ODataException + Assert.Equal(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch("ATOMICITYGROUP"), exception.Message); + } + + [Fact] + public async Task ReadBatchRequestWithDuplicatePropertiesAsync() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"atomicityGroup\": \"g1\"," + + "\"atomicityGroup\": \"g2\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\"}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + var exception = await Assert.ThrowsAsync( + () => SetupJsonLightBatchReaderAndRunTestAsync( payload, async (jsonLightBatchReader) => { - try + while (await jsonLightBatchReader.ReadAsync()) { - while (await jsonLightBatchReader.ReadAsync()) + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) { - if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) - { - // The json properties are just iterated through and have no purpose for this test case - await jsonLightBatchReader.CreateOperationRequestMessageAsync(); - } + // The json properties are just iterated through and have no purpose for this test case + await jsonLightBatchReader.CreateOperationRequestMessageAsync(); } - Assert.False(true, "The test failed, because the duplicate header has thrown no ODataException"); } - catch (ODataException ex) + Assert.False(true, "The test failed, because the duplicate header has not thrown an ODataException"); + }, + isResponse: false)); + + // Verify that the correct duplicate property has raised the ODataException + Assert.Equal(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch("ATOMICITYGROUP"), exception.Message); + } + + [Fact] + public void ReadBatchRequestWithDuplicateHeaders() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"duplicate-header\":\"value1\",\"duplicate-header\":\"value2\"}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + var exception = Assert.Throws( + () => SetupJsonLightBatchReaderAndRunTest( + payload, + (jsonLightBatchReader) => + { + while (jsonLightBatchReader.Read()) { - // Verify that the correct duplicate property has raised the ODataException - Assert.Equal(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch("ATOMICITYGROUP"), ex.Message); + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + // The json properties are just iterated through and have no purpose for this test case + jsonLightBatchReader.CreateOperationRequestMessage(); + } } + Assert.False(true, "The test failed, because the duplicate header has thrown no ODataException"); }, - isResponse: false); + isResponse: false)); + + // Verify that the correct duplicate header has raised the ODataException + Assert.Equal(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch("duplicate-header"), exception.Message); } [Fact] @@ -446,12 +542,11 @@ public async Task ReadBatchRequestWithDuplicateHeadersAsync() "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"duplicate-header\":\"value1\",\"duplicate-header\":\"value2\"}, " + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; - await SetupJsonLightBatchReaderAndRunTestAsync( + var exception = await Assert.ThrowsAsync( + () => SetupJsonLightBatchReaderAndRunTestAsync( payload, async (jsonLightBatchReader) => { - try - { while (await jsonLightBatchReader.ReadAsync()) { if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) @@ -461,14 +556,11 @@ await SetupJsonLightBatchReaderAndRunTestAsync( } } Assert.False(true, "The test failed, because the duplicate header has thrown no ODataException"); - } - catch (ODataException ex) - { - // Verify that the correct duplicate header has raised the ODataException - Assert.Equal(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch("duplicate-header"), ex.Message); - } }, - isResponse: false); + isResponse: false)); + + // Verify that the correct duplicate header has raised the ODataException + Assert.Equal(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch("duplicate-header"), exception.Message); } [Fact] @@ -1085,6 +1177,22 @@ private async Task DoReadAsync( } } + /// + /// Sets up an ODataJsonLightBatchReader, then runs the given test code synchronously + /// + private void SetupJsonLightBatchReaderAndRunTest( + string payload, + Action func, + bool isResponse = true) + { + using (var jsonLightInputContext = CreateJsonLightInputContext(payload, isAsync: false, isResponse: isResponse)) + { + var jsonLightBatchReader = new ODataJsonLightBatchReader(jsonLightInputContext, synchronous: true); + + func(jsonLightBatchReader); + } + } + /// /// Sets up an ODataJsonLightBatchReader, then runs the given test code asynchronously /// From 6271298fe04251a3ef7c68abbdcc82ceece174d5 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Mon, 17 Jul 2023 11:38:29 +0200 Subject: [PATCH 14/14] reverted ODataJsonLightBatchPayloadItemPropertiesCache null value handling of JSON property and removed corresponding unit tests. --- ...sonLightBatchPayloadItemPropertiesCache.cs | 4 +- .../ODataJsonLightBatchReaderTests.cs | 72 ------------------- 2 files changed, 2 insertions(+), 74 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index c526644e6a..38de20f84c 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -273,7 +273,7 @@ private void ScanJsonProperties() while (this.jsonReader.NodeType != JsonNodeType.EndObject) { string headerName = this.jsonReader.ReadPropertyName(); - string headerValue = this.jsonReader.ReadPrimitiveValue()?.ToString(); + string headerValue = this.jsonReader.ReadPrimitiveValue().ToString(); // Throw an ODataException, if a duplicate header was detected if (headers.ContainsKeyOrdinal(headerName)) @@ -425,7 +425,7 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() { string headerName = await this.asynchronousJsonReader.ReadPropertyNameAsync() .ConfigureAwait(false); - string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false))?.ToString(); + string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)).ToString(); // Throw an ODataException, if a duplicate header was detected if (headers.ContainsKeyOrdinal(headerName)) diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs index 982a06b056..1bf1432bf4 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs @@ -363,78 +363,6 @@ await DoReadAsync( isResponse: false); } - [Fact] - public void ReadBatchRequestWithNullHeaders() - { - var payload = "{\"requests\": [{" + - "\"id\": \"1\"," + - "\"method\": \"POST\"," + - "\"url\": \"http://tempuri.org/Customers\"," + - "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"null-header\":null}, " + - "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; - - SetupJsonLightBatchReaderAndRunTest( - payload, - (jsonLightBatchReader) => - { - try - { - while (jsonLightBatchReader.Read()) - { - if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) - { - var operationRequestMessage = jsonLightBatchReader.CreateOperationRequestMessage(); - // Verify that the Property "null-header" exists and it's value is set to NULL - var nullHeaderProperty = operationRequestMessage.Headers.FirstOrDefault(p => p.Key == "null-header"); - Assert.NotNull(nullHeaderProperty.Key); - Assert.Null(nullHeaderProperty.Value); - } - } - } - catch (NullReferenceException ex) - { - Assert.False(true, ex.Message); - } - }, - isResponse: false); - } - - [Fact] - public async Task ReadBatchRequestWithNullHeadersAsync() - { - var payload = "{\"requests\": [{" + - "\"id\": \"1\"," + - "\"method\": \"POST\"," + - "\"url\": \"http://tempuri.org/Customers\"," + - "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"null-header\":null}, " + - "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; - - await SetupJsonLightBatchReaderAndRunTestAsync( - payload, - async (jsonLightBatchReader) => - { - try - { - while (await jsonLightBatchReader.ReadAsync()) - { - if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) - { - var operationRequestMessage = await jsonLightBatchReader.CreateOperationRequestMessageAsync(); - // Verify that the Property "null-header" exists and it's value is set to NULL - var nullHeaderProperty = operationRequestMessage.Headers.FirstOrDefault(p => p.Key == "null-header"); - Assert.NotNull(nullHeaderProperty.Key); - Assert.Null(nullHeaderProperty.Value); - } - } - } - catch (NullReferenceException ex) - { - Assert.False(true, ex.Message); - } - }, - isResponse: false); - } - [Fact] public void ReadBatchRequestWithDuplicateProperties() {