From 846a0bd81677ef9791c9222714b9547ac3749ed1 Mon Sep 17 00:00:00 2001 From: Qualizorg Date: Thu, 15 Aug 2024 13:24:47 +0200 Subject: [PATCH 01/16] git test --- src/Microsoft.Azure.CosmosRepository/test.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/Microsoft.Azure.CosmosRepository/test.cs diff --git a/src/Microsoft.Azure.CosmosRepository/test.cs b/src/Microsoft.Azure.CosmosRepository/test.cs new file mode 100644 index 000000000..b9f477e1b --- /dev/null +++ b/src/Microsoft.Azure.CosmosRepository/test.cs @@ -0,0 +1,15 @@ +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Azure.CosmosRepository +{ + internal class test + { + } +} From 5a366dd44f2bd66ac8ef10d6b6dffd901d470453 Mon Sep 17 00:00:00 2001 From: Qualizorg Date: Thu, 15 Aug 2024 14:07:44 +0200 Subject: [PATCH 02/16] checkpoint --- .../Builders/IPatchOperationBuilder.cs | 96 +++++ .../Builders/PatchOperationBuilder.cs | 132 +++++-- src/Microsoft.Azure.CosmosRepository/test.cs | 15 - .../Builders/PatchOperationBuilderTests.cs | 369 +++++++++++++----- .../InMemory/InMemoryChangeFeedTests.cs | 18 +- .../Stubs/TestItem.cs | 2 + 6 files changed, 491 insertions(+), 141 deletions(-) delete mode 100644 src/Microsoft.Azure.CosmosRepository/test.cs diff --git a/src/Microsoft.Azure.CosmosRepository/Builders/IPatchOperationBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Builders/IPatchOperationBuilder.cs index 2f46e08c2..f99dd7a2f 100644 --- a/src/Microsoft.Azure.CosmosRepository/Builders/IPatchOperationBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Builders/IPatchOperationBuilder.cs @@ -23,4 +23,100 @@ public interface IPatchOperationBuilder where TItem : IItem /// This currently only supports operations on properties on the root level of a JSON document, /// replacing properties on a nested object for example are currently not supported. IPatchOperationBuilder Replace(Expression> expression, TValue? value); + + /// + /// Allows a property of an to be replaced with the value provided + /// + /// The path to replace + /// The value to replace the property defined with. + /// The type of the property that is been replaced. + /// The same instance of + /// This currently only supports operations on properties on the root level of a JSON document, + /// replacing properties on a nested object for example are currently not supported. + IPatchOperationBuilder Replace(string path, TValue value); + + /// + /// Allows a property of an to be set with the value provided + /// + /// The type of the property that is being set. + /// The expression to define which property to operate on. + /// The value to set the property defined with. + /// The same instance of + IPatchOperationBuilder Set(Expression> expression, TValue? value); + + /// + /// Allows a property of an to be set with the value provided + /// + /// The type of value to set. + /// The json document path + /// The value to set. + /// The same instance of + IPatchOperationBuilder Set(string path, TValue? value); + + /// + /// Allows a property of an to be added with the value provided + /// + /// The type of the property that is being added. + /// The expression to define which property to operate on. + /// The value to add to the document + /// The same instance of + IPatchOperationBuilder Add(Expression> expression, TValue? value); + + /// + /// Allows a property of an to be added with the value provided + /// + /// The type of the property that is being added. + /// Target location reference. + /// The value to add to the document + /// The same instance of + IPatchOperationBuilder Add(string path, TValue? value); + + /// + /// Allows a property of an to be removed with the value provided + /// + /// The type of the property that is being removed. + /// The expression to define which property to operate on. + /// The same instance of + IPatchOperationBuilder Remove(Expression> expression); + + /// + /// Allows a property of an to be added with the value provided + /// + /// Target location reference. + /// The same instance of + IPatchOperationBuilder Remove(string path); + + /// + /// Allows a property of an to be incremented with the value provided. + /// + /// Target location reference. + /// The value to add to the document. + /// The same instance of + IPatchOperationBuilder Increment(string path, long value); + + /// + /// Allows a property of an to be added with the value provided + /// + /// The type of the property that is being added. + /// The expression to define which property to operate on. + /// The value to add to the document + /// The same instance of + IPatchOperationBuilder Increment(Expression> expression, double value); + + /// + /// Allows a property of an to be incremented with the value provided. + /// + /// Target location reference. + /// The value to increment. + /// The same instance of + IPatchOperationBuilder Increment(string path, double value); + + /// + /// Allows a property of an to be added with the value provided + /// + /// The type of the property that is being added. + /// The expression to define which property to operate on. + /// The value to increment + /// The same instance of + IPatchOperationBuilder Increment(Expression> expression, long value); } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs index c9a773bce..5b570b869 100644 --- a/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs @@ -1,41 +1,115 @@ -// Copyright (c) David Pine. All rights reserved. +// Copyright (c) IEvangelist. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Azure.CosmosRepository.Builders; - -internal class PatchOperationBuilder : IPatchOperationBuilder where TItem : IItem +[assembly: InternalsVisibleTo("Microsoft.Azure.CosmosRepositoryTests")] +namespace Microsoft.Azure.CosmosRepository.Builders { - private readonly List _patchOperations = new(); - private readonly NamingStrategy _namingStrategy; - internal readonly List _rawPatchOperations = new(); + internal class PatchOperationBuilder : IPatchOperationBuilder where TItem : IItem + { + private readonly List _patchOperations = new(); + private readonly NamingStrategy _namingStrategy; - public IReadOnlyList PatchOperations => _patchOperations; + internal readonly List _rawPatchOperations = new(); - public PatchOperationBuilder() => - _namingStrategy = new CamelCaseNamingStrategy(); + public IReadOnlyList PatchOperations => _patchOperations; - public PatchOperationBuilder(CosmosPropertyNamingPolicy? cosmosPropertyNamingPolicy) => - _namingStrategy = cosmosPropertyNamingPolicy == CosmosPropertyNamingPolicy.Default - ? new DefaultNamingStrategy() - : new CamelCaseNamingStrategy(); + public PatchOperationBuilder() => + _namingStrategy = new CamelCaseNamingStrategy(); - public IPatchOperationBuilder Replace(Expression> expression, TValue? value) - { - PropertyInfo property = expression.GetPropertyInfo(); - var propertyToReplace = GetPropertyToReplace(property); - _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Replace)); - _patchOperations.Add(PatchOperation.Replace($"/{propertyToReplace}", value)); - return this; - } + public PatchOperationBuilder(CosmosPropertyNamingPolicy? cosmosPropertyNamingPolicy) => + _namingStrategy = cosmosPropertyNamingPolicy == CosmosPropertyNamingPolicy.Default + ? new DefaultNamingStrategy() + : new CamelCaseNamingStrategy(); - private string GetPropertyToReplace(MemberInfo propertyInfo) - { - JsonPropertyAttribute[] attributes = - propertyInfo.GetCustomAttributes(true).ToArray(); + public IPatchOperationBuilder Replace(Expression> expression, TValue? value) + { + string propertyToReplace = GetPropertyToReplace(expression); + return Replace(propertyToReplace, value); + } + public IPatchOperationBuilder Replace(string path, TValue value) + { + _patchOperations.Add(PatchOperation.Replace($"/{path}", value)); + return this; + } + + public IPatchOperationBuilder Set(Expression> expression, TValue? value) + { + string propertyToReplace = GetPropertyToReplace(expression); + return Set(propertyToReplace, value); + } + + public IPatchOperationBuilder Set(string path, TValue? value) + { + _patchOperations.Add(PatchOperation.Set($"/{path}", value)); + return this; + } + + public IPatchOperationBuilder Add(Expression> expression, TValue? value) + { + string propertyToReplace = GetPropertyToReplace(expression); + return Add(propertyToReplace, value); + } + + public IPatchOperationBuilder Add(string path, TValue? value) + { + _patchOperations.Add(PatchOperation.Add($"/{path}", value)); + return this; + } + + public IPatchOperationBuilder Remove(Expression> expression) + { + string propertyToReplace = GetPropertyToReplace(expression); + return Remove(propertyToReplace); + } + + public IPatchOperationBuilder Remove(string path) + { + _patchOperations.Add(PatchOperation.Remove($"/{path}")); + return this; + } + + public IPatchOperationBuilder Increment(string path, long value) + { + _patchOperations.Add(PatchOperation.Increment($"/{path}", value)); + return this; + } + + public IPatchOperationBuilder Increment(string path, double value) + { + _patchOperations.Add(PatchOperation.Increment($"/{path}", value)); + return this; + } + + public IPatchOperationBuilder Increment(Expression> expression, double value) + { + var propertyToReplace = GetPropertyToReplace(expression); + return Increment(propertyToReplace, value); + } + + public IPatchOperationBuilder Increment(Expression> expression, long value) + { + var propertyToReplace = GetPropertyToReplace(expression); + return Increment(propertyToReplace, value); + } + + /// + /// Get the property name to replace. This only works for a single level of nesting. + /// + /// If you're looking for nesting call the respective method with a given path instead. + /// The property to get the path of. + /// Returns the path of the property name. + internal string GetPropertyToReplace(Expression> expression) + { + PropertyInfo property = expression.GetPropertyInfo(); + + JsonPropertyAttribute[] attributes = + property.GetCustomAttributes(true).ToArray(); + + return attributes.Length is 0 + ? _namingStrategy.GetPropertyName(property.Name, false) + : attributes[0].PropertyName; + } - return attributes.Length is 0 - ? _namingStrategy.GetPropertyName(propertyInfo.Name, false) - : attributes[0].PropertyName; } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/test.cs b/src/Microsoft.Azure.CosmosRepository/test.cs deleted file mode 100644 index b9f477e1b..000000000 --- a/src/Microsoft.Azure.CosmosRepository/test.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) David Pine. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.Azure.CosmosRepository -{ - internal class test - { - } -} diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs index b2295e2be..de5f03eac 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs @@ -1,124 +1,301 @@ -// Copyright (c) David Pine. All rights reserved. +// Copyright (c) IEvangelist. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Azure.CosmosRepositoryTests.Builders; - -public class Item1 : Item +namespace Microsoft.Azure.CosmosRepositoryTests.Builders { - [JsonProperty("thisIsTheName")] - public string TestProperty { get; set; } = null!; + public class Item1 : Item + { + [JsonProperty("thisIsTheName")] + public string TestProperty { get; set; } = null!; - public int TestIntProperty { get; set; } -} + public int TestIntProperty { get; set; } + } -public class RequiredItem : Item -{ - [Required] - public string TestProperty { get; set; } = null!; -} + public class RequiredItem : Item + { + [Required] + public string TestProperty { get; set; } = null!; + } -public class RequiredAndJsonItem : Item -{ - [Required] - [JsonProperty("testProperty")] - public string TestProperty { get; set; } = null!; -} + public class RequiredAndJsonItem : Item + { + [Required] + [JsonProperty("testProperty")] + public string TestProperty { get; set; } = null!; + } -public class PatchOperationBuilderTests -{ - [Fact] - public void ReplaceGivenPropertyValueWithJsonAttributeSetsCorrectReplaceValue() + public class PatchOperationBuilderTests { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); + [Fact] + public void GetPropertyToReplaceGetsCorrectValueWithJsonAttribute() + { + //Arrange + PatchOperationBuilder builder = new PatchOperationBuilder(); + Expression> expression = item => item.TestProperty; - //Act - builder.Replace(x => x.TestProperty, "100"); + //Act + string path = builder.GetPropertyToReplace(expression); - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal("/thisIsTheName", operation.Path); - } + //Assert + Assert.Equal("thisIsTheName", path); + } - [Fact] - public void ReplaceGivenPropertyWithNoAttributesSetsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); + [Fact] + public void GetPropertyToReplaceGetsCorrectValueWithNoAttributes() + { + //Arrange + PatchOperationBuilder builder = new PatchOperationBuilder(); + Expression> expression = item => item.TestIntProperty; - //Act - builder.Replace(x => x.TestIntProperty, 50); + //Act + string path = builder.GetPropertyToReplace(expression); - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal("/testIntProperty", operation.Path); - } + //Assert + Assert.Equal("testIntProperty", path); + } - [Fact] - public void ReplaceGivenPropertyWithRequiredAttributeSetsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); + [Fact] + public void GetPropertyToReplaceGetsCorrectValueWithRequiredAttribute() + { + //Arrange + PatchOperationBuilder builder = new PatchOperationBuilder(); + Expression> expression = item => item.TestProperty; - //Act - builder.Replace(x => x.TestProperty, "Test Value"); + //Act + string path = builder.GetPropertyToReplace(expression); - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal("/testProperty", operation.Path); - } + //Assert + Assert.Equal("testProperty", path); + } - [Fact] - public void ReplaceGivenPropertyWithRequiredAndJsonAttributesSetsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); + [Fact] + public void GetPropertyToReplaceGetsCorrectValueWithRequiredAndJsonAttribute() + { + //Arrange + PatchOperationBuilder builder = new PatchOperationBuilder(); + Expression> expression = item => item.TestProperty; - //Act - builder.Replace(x => x.TestProperty, "Test Value"); + //Act + string path = builder.GetPropertyToReplace(expression); - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal("/testProperty", operation.Path); - } + //Assert + Assert.Equal("testProperty", path); + } - [Theory] - [MemberData(nameof(GetTestCases))] - public void AcknowledgeRepositorySerializationSettingForRetrievingPatchOperation(CosmosPropertyNamingPolicy? propertyNamingPolicy, - string expectedPropertyName) - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(propertyNamingPolicy); + [Fact] + public void ReplaceGivenExpressionSetsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); - //Act - builder.Replace(x => x.TestIntProperty, 1234); + //Act + builder.Replace(x => x.TestProperty, "100"); - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal($"/{expectedPropertyName}", operation.Path); - } + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/thisIsTheName", operation.Path); + } - public static IEnumerable GetTestCases() - { - yield return new object?[] + [Fact] + public void ReplaceGivenPathSetsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Replace("thisIsTheName", "100"); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/thisIsTheName", operation.Path); + } + + [Fact] + public void SetGivenExpressionSetsCorrectPatchOperation() { - CosmosPropertyNamingPolicy.CamelCase, - new CamelCaseNamingStrategy().GetPropertyName(nameof(Item1.TestIntProperty), false) - }; - yield return new object?[] + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Set(x => x.TestProperty, "100"); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal("/thisIsTheName", operation.Path); + } + + [Fact] + public void SetGivenPathSetsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Set("thisIsTheName", "100"); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal("/thisIsTheName", operation.Path); + } + + [Fact] + public void AddGivenExpressionSetsCorrectPatchOperation() { - CosmosPropertyNamingPolicy.Default, - new DefaultNamingStrategy().GetPropertyName(nameof(Item1.TestIntProperty), false) - }; - yield return new object?[] + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Add(x => x.TestProperty, "100"); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal("/thisIsTheName", operation.Path); + } + + [Fact] + public void AddGivenPathSetsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Add("thisIsTheName", "100"); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal("/thisIsTheName", operation.Path); + } + + [Fact] + public void RemoveGivenExpressionSetsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Remove(x => x.TestProperty); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/thisIsTheName", operation.Path); + } + + [Fact] + public void RemoveGivenPathSetsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Remove("thisIsTheName"); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/thisIsTheName", operation.Path); + } + + [Fact] + public void IncrementDoubleGivenExpressionSetsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Increment(x => x.TestProperty, 100.123); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal("/thisIsTheName", operation.Path); + } + + [Fact] + public void IncrementDoubleGivenPathSetsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Increment("thisIsTheName", 100.123); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal("/thisIsTheName", operation.Path); + } + + [Fact] + public void IncrementLongGivenExpressionSetsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Increment(x => x.TestProperty, 123456789); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal("/thisIsTheName", operation.Path); + } + + [Fact] + public void IncrementLongGivenPathSetsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Increment("thisIsTheName", 123456789); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal("/thisIsTheName", operation.Path); + } + + [Theory] + [MemberData(nameof(GetTestCases))] + public void AcknowledgeRepositorySerializationSettingForRetrievingPatchOperation(CosmosPropertyNamingPolicy? propertyNamingPolicy, + string expectedPropertyName) + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(propertyNamingPolicy); + + //Act + builder.Replace(x => x.TestIntProperty, 1234); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal($"/{expectedPropertyName}", operation.Path); + } + + public static IEnumerable GetTestCases() { - null, - new CamelCaseNamingStrategy().GetPropertyName(nameof(Item1.TestIntProperty), false) - }; + yield return new object?[] + { + CosmosPropertyNamingPolicy.CamelCase, + new CamelCaseNamingStrategy().GetPropertyName(nameof(Item1.TestIntProperty), false) + }; + yield return new object?[] + { + CosmosPropertyNamingPolicy.Default, + new DefaultNamingStrategy().GetPropertyName(nameof(Item1.TestIntProperty), false) + }; + yield return new object?[] + { + null, + new CamelCaseNamingStrategy().GetPropertyName(nameof(Item1.TestIntProperty), false) + }; + } } } \ No newline at end of file diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/ChangeFeed/InMemory/InMemoryChangeFeedTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/ChangeFeed/InMemory/InMemoryChangeFeedTests.cs index c0c3a3048..25a0258f6 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/ChangeFeed/InMemory/InMemoryChangeFeedTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/ChangeFeed/InMemory/InMemoryChangeFeedTests.cs @@ -123,7 +123,7 @@ public async Task UpdateAsync_CollectionOfItemWhereSomeExist_InvokesChangeFeedPr } [Fact] - public async Task UpdateAsync_PatchUpdate_InvokesChangeFeedProcessor() + public async Task UpdateAsync_PatchUpdate_Replace_InvokesChangeFeedProcessor() { //Arrange TestItem item = new(); @@ -138,4 +138,20 @@ public async Task UpdateAsync_PatchUpdate_InvokesChangeFeedProcessor() Assert.Contains(_testItemChangeFeedProcessor.ChangedItems, x => x.Property == "propertyValue"); } + [Fact] + public async Task UpdateAsync_PatchUpdate_Add_InvokesChangeFeedProcessor() + { + //Arrange + TestItem item = new(); + + item = await _testItemRepository.CreateAsync(item); + + //Act + await _testItemRepository.UpdateAsync(item.Id, builder => builder.Add(x => x.Items, ["Test123"])); + + //Assert + Assert.Equal(2, _testItemChangeFeedProcessor.InvocationCount); + Assert.Contains(_testItemChangeFeedProcessor.ChangedItems, x => x.Property == "propertyValue"); + } + } \ No newline at end of file diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Stubs/TestItem.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Stubs/TestItem.cs index ab8753fe4..735518df2 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/Stubs/TestItem.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Stubs/TestItem.cs @@ -13,4 +13,6 @@ public TestItem(string etag) : base(etag) { } [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public int Number { get; set; } + + public IEnumerable Items { get; set; } = default!; } \ No newline at end of file From 80aa7015721f843544d52c6f0889634b6ce6d173 Mon Sep 17 00:00:00 2001 From: Qualizorg Date: Thu, 15 Aug 2024 21:08:57 +0200 Subject: [PATCH 03/16] checkpoint --- .../Builders/PatchOperationBuilder.cs | 170 +++++++++++++++--- .../Builders/PatchOperationBuilderTests.cs | 113 ++++++------ 2 files changed, 208 insertions(+), 75 deletions(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs index 5b570b869..f3ca8eba9 100644 --- a/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs @@ -1,6 +1,9 @@ // Copyright (c) IEvangelist. All rights reserved. // Licensed under the MIT License. +using System.Linq.Expressions; +using System.Reflection; + [assembly: InternalsVisibleTo("Microsoft.Azure.CosmosRepositoryTests")] namespace Microsoft.Azure.CosmosRepository.Builders { @@ -24,7 +27,10 @@ public PatchOperationBuilder(CosmosPropertyNamingPolicy? cosmosPropertyNamingPol public IPatchOperationBuilder Replace(Expression> expression, TValue? value) { - string propertyToReplace = GetPropertyToReplace(expression); + var propertyInfo = expression.GetPropertyInfo(); + var propertyToReplace = GetPropertyToReplace(propertyInfo); + + _rawPatchOperations.Add(new InternalPatchOperation(propertyInfo, value, PatchOperationType.Replace)); return Replace(propertyToReplace, value); } public IPatchOperationBuilder Replace(string path, TValue value) @@ -35,81 +41,205 @@ public IPatchOperationBuilder Replace(string path, TValue value) public IPatchOperationBuilder Set(Expression> expression, TValue? value) { - string propertyToReplace = GetPropertyToReplace(expression); - return Set(propertyToReplace, value); + var propertyInfo = expression.GetPropertyInfo(); + var propertyToReplace = GetPropertyToReplace(propertyInfo); + return InternalSet(propertyToReplace, propertyInfo, value); } public IPatchOperationBuilder Set(string path, TValue? value) { + var propertyInfo = GetPropertyInfoToReplace(path); + _rawPatchOperations.Add(new InternalPatchOperation(propertyInfo, value, PatchOperationType.Set)); _patchOperations.Add(PatchOperation.Set($"/{path}", value)); return this; } - public IPatchOperationBuilder Add(Expression> expression, TValue? value) + private IPatchOperationBuilder InternalSet(string path, PropertyInfo property, TValue? value) { - string propertyToReplace = GetPropertyToReplace(expression); - return Add(propertyToReplace, value); + _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Set)); + _patchOperations.Add(PatchOperation.Set($"/{path}", value)); + return this; } + public IPatchOperationBuilder Add(Expression> expression, TValue? value) + { + var propertyInfo = expression.GetPropertyInfo(); + var propertyToReplace = GetPropertyToReplace(propertyInfo); + return InternalAdd(propertyToReplace, propertyInfo, value); + } public IPatchOperationBuilder Add(string path, TValue? value) { + var propertyInfo = GetPropertyInfoToReplace(path); + var propertyToReplace = GetPropertyToReplace(propertyInfo); + return InternalAdd(propertyToReplace, propertyInfo, value); + } + + public IPatchOperationBuilder InternalAdd(string path, PropertyInfo property, TValue? value) + { + _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Add)); _patchOperations.Add(PatchOperation.Add($"/{path}", value)); return this; } public IPatchOperationBuilder Remove(Expression> expression) { - string propertyToReplace = GetPropertyToReplace(expression); - return Remove(propertyToReplace); + var propertyInfo = expression.GetPropertyInfo(); + var propertyToReplace = GetPropertyToReplace(propertyInfo); + return InternalRemove(propertyToReplace, propertyInfo); } public IPatchOperationBuilder Remove(string path) { + var propertyInfo = GetPropertyInfoToReplace(path); + var propertyToReplace = GetPropertyToReplace(propertyInfo); + return InternalRemove(propertyToReplace, propertyInfo); + } + + public IPatchOperationBuilder InternalRemove(string path, PropertyInfo property) + { + _rawPatchOperations.Add(new InternalPatchOperation(property, null, PatchOperationType.Remove)); _patchOperations.Add(PatchOperation.Remove($"/{path}")); return this; } - public IPatchOperationBuilder Increment(string path, long value) + public IPatchOperationBuilder InternalIncrement(string path, PropertyInfo property, long value) { + _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Increment)); _patchOperations.Add(PatchOperation.Increment($"/{path}", value)); return this; } - public IPatchOperationBuilder Increment(string path, double value) + public IPatchOperationBuilder InternalIncrement(string path, PropertyInfo property, double value) { + _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Increment)); _patchOperations.Add(PatchOperation.Increment($"/{path}", value)); return this; } public IPatchOperationBuilder Increment(Expression> expression, double value) { - var propertyToReplace = GetPropertyToReplace(expression); - return Increment(propertyToReplace, value); + var propertyInfo = expression.GetPropertyInfo(); + var propertyToReplace = GetPropertyToReplace(propertyInfo); + return InternalIncrement(propertyToReplace, propertyInfo, value); } public IPatchOperationBuilder Increment(Expression> expression, long value) { - var propertyToReplace = GetPropertyToReplace(expression); - return Increment(propertyToReplace, value); + var propertyInfo = expression.GetPropertyInfo(); + var propertyToReplace = GetPropertyToReplace(propertyInfo); + return InternalIncrement(propertyToReplace, propertyInfo, value); + } + + public IPatchOperationBuilder Increment(string path, long value) + { + var propertyInfo = GetPropertyInfoToReplace(path); + var propertyToReplace = GetPropertyToReplace(propertyInfo); + return InternalIncrement(propertyToReplace, propertyInfo, value); + } + + public IPatchOperationBuilder Increment(string path, double value) + { + var propertyInfo = GetPropertyInfoToReplace(path); + var propertyToReplace = GetPropertyToReplace(propertyInfo); + return InternalIncrement(propertyToReplace, propertyInfo, value); } /// /// Get the property name to replace. This only works for a single level of nesting. /// /// If you're looking for nesting call the respective method with a given path instead. - /// The property to get the path of. + /// The property info of the property to be replaced. /// Returns the path of the property name. - internal string GetPropertyToReplace(Expression> expression) + internal string GetPropertyToReplace(MemberInfo propertyInfo) { - PropertyInfo property = expression.GetPropertyInfo(); - JsonPropertyAttribute[] attributes = - property.GetCustomAttributes(true).ToArray(); + propertyInfo.GetCustomAttributes(true).ToArray(); return attributes.Length is 0 - ? _namingStrategy.GetPropertyName(property.Name, false) + ? _namingStrategy.GetPropertyName(propertyInfo.Name, false) : attributes[0].PropertyName; } + /// + /// Get the property name to replace. This only works for a single level of nesting. + /// + /// If you're looking for nesting call the respective method with a given path instead. + /// The path to get the property. + /// Returns the path of the property name. + internal PropertyInfo GetPropertyInfoToReplace(string path) + { + Type itemType = typeof(TItem); + + PropertyInfo property = GetNestedPropertyInfo(itemType, path) ?? throw new InvalidOperationException($"The property {path} does not exist on {itemType.Name}"); + + return property; + } + + public static PropertyInfo? GetPropertyInfoByJsonPropertyName(Type type, string jsonPropertyName) + { + // Iterate through all properties of the type + foreach (PropertyInfo property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + // Check if the property has a JsonPropertyAttribute + var jsonPropertyAttribute = property.GetCustomAttribute(); + + // If the attribute is found and the PropertyName matches the provided jsonPropertyName + if (jsonPropertyAttribute != null && jsonPropertyAttribute.PropertyName == jsonPropertyName) + { + // Return the PropertyInfo of the matching property + return property; + } + } + + // Return null if no matching property is found + return null; + } + + public static PropertyInfo? GetNestedPropertyInfo(Type type, string path) + { + // Split the path by '/' to get the individual property names + string[] properties = path.Split('/'); + + Type currentType = type; + PropertyInfo? propertyInfo = null; + + // Iterate through each property in the path + foreach (string propertyNameOrJsonProperty in properties) + { + // First, attempt to get the property by its direct name + propertyInfo = currentType.GetProperty(propertyNameOrJsonProperty, BindingFlags.Public | BindingFlags.Instance); + + // If the property is found by name, continue to the next level + if (propertyInfo != null) + { + currentType = propertyInfo.PropertyType; + continue; + } + + // If the property is not found by name, search by JsonPropertyAttribute + PropertyInfo[] currentProperties = currentType.GetProperties(BindingFlags.Public | BindingFlags.Instance); + + foreach (var property in currentProperties) + { + var jsonPropertyAttribute = property.GetCustomAttribute(); + if (jsonPropertyAttribute != null && jsonPropertyAttribute.PropertyName == propertyNameOrJsonProperty) + { + propertyInfo = property; + currentType = propertyInfo.PropertyType; + break; + } + } + + // If no matching property is found at all, return null + if (propertyInfo == null) + { + return null; + } + } + + // Return the final PropertyInfo + return propertyInfo; + } + } } \ No newline at end of file diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs index de5f03eac..d96e0e5fb 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs @@ -26,63 +26,66 @@ public class RequiredAndJsonItem : Item public class PatchOperationBuilderTests { - [Fact] - public void GetPropertyToReplaceGetsCorrectValueWithJsonAttribute() - { - //Arrange - PatchOperationBuilder builder = new PatchOperationBuilder(); - Expression> expression = item => item.TestProperty; - - //Act - string path = builder.GetPropertyToReplace(expression); - - //Assert - Assert.Equal("thisIsTheName", path); - } - - [Fact] - public void GetPropertyToReplaceGetsCorrectValueWithNoAttributes() - { - //Arrange - PatchOperationBuilder builder = new PatchOperationBuilder(); - Expression> expression = item => item.TestIntProperty; - - //Act - string path = builder.GetPropertyToReplace(expression); - - //Assert - Assert.Equal("testIntProperty", path); - } - - [Fact] - public void GetPropertyToReplaceGetsCorrectValueWithRequiredAttribute() - { - //Arrange - PatchOperationBuilder builder = new PatchOperationBuilder(); - Expression> expression = item => item.TestProperty; - - //Act - string path = builder.GetPropertyToReplace(expression); - - //Assert - Assert.Equal("testProperty", path); - } - - [Fact] - public void GetPropertyToReplaceGetsCorrectValueWithRequiredAndJsonAttribute() - { - //Arrange - PatchOperationBuilder builder = new PatchOperationBuilder(); - Expression> expression = item => item.TestProperty; - - //Act - string path = builder.GetPropertyToReplace(expression); - - //Assert - Assert.Equal("testProperty", path); - } + //[Fact] + //public void GetPropertyToReplaceGetsCorrectValueWithJsonAttribute() + +#pragma warning disable S125 // Sections of code should not be commented out + //{ + // //Arrange + // PatchOperationBuilder builder = new PatchOperationBuilder(); + // Expression> expression = item => item.TestProperty; + + // //Act + // string path = builder.GetPropertyInfoToReplace(expression); + + // //Assert + // Assert.Equal("thisIsTheName", path); + //} + + //[Fact] + //public void GetPropertyToReplaceGetsCorrectValueWithNoAttributes() + //{ + // //Arrange + // PatchOperationBuilder builder = new PatchOperationBuilder(); + // Expression> expression = item => item.TestIntProperty; + + // //Act + // string path = builder.GetPropertyInfoToReplace(expression); + + // //Assert + // Assert.Equal("testIntProperty", path); + //} + + //[Fact] + //public void GetPropertyToReplaceGetsCorrectValueWithRequiredAttribute() + //{ + // //Arrange + // PatchOperationBuilder builder = new PatchOperationBuilder(); + // Expression> expression = item => item.TestProperty; + + // //Act + // string path = builder.GetPropertyInfoToReplace(expression); + + // //Assert + // Assert.Equal("testProperty", path); + //} + + //[Fact] + //public void GetPropertyToReplaceGetsCorrectValueWithRequiredAndJsonAttribute() + //{ + // //Arrange + // PatchOperationBuilder builder = new PatchOperationBuilder(); + // Expression> expression = item => item.TestProperty; + + // //Act + // string path = builder.GetPropertyInfoToReplace(expression); + + // //Assert + // Assert.Equal("testProperty", path); + //} [Fact] +#pragma warning restore S125 // Sections of code should not be commented out public void ReplaceGivenExpressionSetsCorrectPatchOperation() { //Arrange From c2f6f950a04ef6305dd895fb2b43f39becc697af Mon Sep 17 00:00:00 2001 From: Qualizorg Date: Thu, 15 Aug 2024 22:52:53 +0200 Subject: [PATCH 04/16] stabilized tests --- .../Builders/PatchOperationBuilder.cs | 94 ++--- .../Builders/PatchOperationBuilderTests.cs | 372 +++++------------- .../InMemory/InMemoryChangeFeedTests.cs | 18 +- 3 files changed, 146 insertions(+), 338 deletions(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs index f3ca8eba9..1be7faa0a 100644 --- a/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs @@ -1,6 +1,7 @@ // Copyright (c) IEvangelist. All rights reserved. // Licensed under the MIT License. +using System.IO; using System.Linq.Expressions; using System.Reflection; @@ -29,43 +30,32 @@ public IPatchOperationBuilder Replace(Expression Replace(string path, TValue value) { - _patchOperations.Add(PatchOperation.Replace($"/{path}", value)); - return this; + var propertyInfo = GetPropertyInfoToReplace(path); + return InternalReplace(path, propertyInfo, value); } public IPatchOperationBuilder Set(Expression> expression, TValue? value) { var propertyInfo = expression.GetPropertyInfo(); var propertyToReplace = GetPropertyToReplace(propertyInfo); - return InternalSet(propertyToReplace, propertyInfo, value); + return InternalSet($"/{propertyToReplace}", propertyInfo, value); } public IPatchOperationBuilder Set(string path, TValue? value) { var propertyInfo = GetPropertyInfoToReplace(path); - _rawPatchOperations.Add(new InternalPatchOperation(propertyInfo, value, PatchOperationType.Set)); - _patchOperations.Add(PatchOperation.Set($"/{path}", value)); - return this; - } - - private IPatchOperationBuilder InternalSet(string path, PropertyInfo property, TValue? value) - { - _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Set)); - _patchOperations.Add(PatchOperation.Set($"/{path}", value)); - return this; + return InternalSet(path, propertyInfo, value); } public IPatchOperationBuilder Add(Expression> expression, TValue? value) { var propertyInfo = expression.GetPropertyInfo(); var propertyToReplace = GetPropertyToReplace(propertyInfo); - return InternalAdd(propertyToReplace, propertyInfo, value); + return InternalAdd($"/{propertyToReplace}", propertyInfo, value); } public IPatchOperationBuilder Add(string path, TValue? value) { @@ -74,13 +64,6 @@ public IPatchOperationBuilder Add(string path, TValue? value) return InternalAdd(propertyToReplace, propertyInfo, value); } - public IPatchOperationBuilder InternalAdd(string path, PropertyInfo property, TValue? value) - { - _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Add)); - _patchOperations.Add(PatchOperation.Add($"/{path}", value)); - return this; - } - public IPatchOperationBuilder Remove(Expression> expression) { var propertyInfo = expression.GetPropertyInfo(); @@ -95,27 +78,6 @@ public IPatchOperationBuilder Remove(string path) return InternalRemove(propertyToReplace, propertyInfo); } - public IPatchOperationBuilder InternalRemove(string path, PropertyInfo property) - { - _rawPatchOperations.Add(new InternalPatchOperation(property, null, PatchOperationType.Remove)); - _patchOperations.Add(PatchOperation.Remove($"/{path}")); - return this; - } - - public IPatchOperationBuilder InternalIncrement(string path, PropertyInfo property, long value) - { - _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Increment)); - _patchOperations.Add(PatchOperation.Increment($"/{path}", value)); - return this; - } - - public IPatchOperationBuilder InternalIncrement(string path, PropertyInfo property, double value) - { - _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Increment)); - _patchOperations.Add(PatchOperation.Increment($"/{path}", value)); - return this; - } - public IPatchOperationBuilder Increment(Expression> expression, double value) { var propertyInfo = expression.GetPropertyInfo(); @@ -144,6 +106,48 @@ public IPatchOperationBuilder Increment(string path, double value) return InternalIncrement(propertyToReplace, propertyInfo, value); } + public IPatchOperationBuilder InternalReplace(string path, PropertyInfo property, TValue? value) + { + _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Replace)); + _patchOperations.Add(PatchOperation.Replace($"{path}", value)); + return this; + } + + private IPatchOperationBuilder InternalSet(string path, PropertyInfo property, TValue? value) + { + _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Set)); + _patchOperations.Add(PatchOperation.Set(path, value)); + return this; + } + + public IPatchOperationBuilder InternalIncrement(string path, PropertyInfo property, long value) + { + _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Increment)); + _patchOperations.Add(PatchOperation.Increment(path, value)); + return this; + } + + public IPatchOperationBuilder InternalIncrement(string path, PropertyInfo property, double value) + { + _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Increment)); + _patchOperations.Add(PatchOperation.Increment(path, value)); + return this; + } + + public IPatchOperationBuilder InternalAdd(string path, PropertyInfo property, TValue? value) + { + _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Add)); + _patchOperations.Add(PatchOperation.Add(path, value)); + return this; + } + + public IPatchOperationBuilder InternalRemove(string path, PropertyInfo property) + { + _rawPatchOperations.Add(new InternalPatchOperation(property, null, PatchOperationType.Remove)); + _patchOperations.Add(PatchOperation.Remove(path)); + return this; + } + /// /// Get the property name to replace. This only works for a single level of nesting. /// diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs index d96e0e5fb..b2295e2be 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs @@ -1,304 +1,124 @@ -// Copyright (c) IEvangelist. All rights reserved. +// Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Azure.CosmosRepositoryTests.Builders +namespace Microsoft.Azure.CosmosRepositoryTests.Builders; + +public class Item1 : Item { - public class Item1 : Item - { - [JsonProperty("thisIsTheName")] - public string TestProperty { get; set; } = null!; + [JsonProperty("thisIsTheName")] + public string TestProperty { get; set; } = null!; - public int TestIntProperty { get; set; } - } + public int TestIntProperty { get; set; } +} - public class RequiredItem : Item - { - [Required] - public string TestProperty { get; set; } = null!; - } +public class RequiredItem : Item +{ + [Required] + public string TestProperty { get; set; } = null!; +} - public class RequiredAndJsonItem : Item - { - [Required] - [JsonProperty("testProperty")] - public string TestProperty { get; set; } = null!; - } +public class RequiredAndJsonItem : Item +{ + [Required] + [JsonProperty("testProperty")] + public string TestProperty { get; set; } = null!; +} - public class PatchOperationBuilderTests +public class PatchOperationBuilderTests +{ + [Fact] + public void ReplaceGivenPropertyValueWithJsonAttributeSetsCorrectReplaceValue() { - //[Fact] - //public void GetPropertyToReplaceGetsCorrectValueWithJsonAttribute() - -#pragma warning disable S125 // Sections of code should not be commented out - //{ - // //Arrange - // PatchOperationBuilder builder = new PatchOperationBuilder(); - // Expression> expression = item => item.TestProperty; - - // //Act - // string path = builder.GetPropertyInfoToReplace(expression); - - // //Assert - // Assert.Equal("thisIsTheName", path); - //} - - //[Fact] - //public void GetPropertyToReplaceGetsCorrectValueWithNoAttributes() - //{ - // //Arrange - // PatchOperationBuilder builder = new PatchOperationBuilder(); - // Expression> expression = item => item.TestIntProperty; - - // //Act - // string path = builder.GetPropertyInfoToReplace(expression); - - // //Assert - // Assert.Equal("testIntProperty", path); - //} - - //[Fact] - //public void GetPropertyToReplaceGetsCorrectValueWithRequiredAttribute() - //{ - // //Arrange - // PatchOperationBuilder builder = new PatchOperationBuilder(); - // Expression> expression = item => item.TestProperty; - - // //Act - // string path = builder.GetPropertyInfoToReplace(expression); - - // //Assert - // Assert.Equal("testProperty", path); - //} - - //[Fact] - //public void GetPropertyToReplaceGetsCorrectValueWithRequiredAndJsonAttribute() - //{ - // //Arrange - // PatchOperationBuilder builder = new PatchOperationBuilder(); - // Expression> expression = item => item.TestProperty; - - // //Act - // string path = builder.GetPropertyInfoToReplace(expression); - - // //Assert - // Assert.Equal("testProperty", path); - //} - - [Fact] -#pragma warning restore S125 // Sections of code should not be commented out - public void ReplaceGivenExpressionSetsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - //Act - builder.Replace(x => x.TestProperty, "100"); - - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal("/thisIsTheName", operation.Path); - } - - [Fact] - public void ReplaceGivenPathSetsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - //Act - builder.Replace("thisIsTheName", "100"); - - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal("/thisIsTheName", operation.Path); - } - - [Fact] - public void SetGivenExpressionSetsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - //Act - builder.Set(x => x.TestProperty, "100"); - - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Set, operation.OperationType); - Assert.Equal("/thisIsTheName", operation.Path); - } + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); - [Fact] - public void SetGivenPathSetsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - //Act - builder.Set("thisIsTheName", "100"); - - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Set, operation.OperationType); - Assert.Equal("/thisIsTheName", operation.Path); - } - - [Fact] - public void AddGivenExpressionSetsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - //Act - builder.Add(x => x.TestProperty, "100"); - - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Add, operation.OperationType); - Assert.Equal("/thisIsTheName", operation.Path); - } - - [Fact] - public void AddGivenPathSetsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); + //Act + builder.Replace(x => x.TestProperty, "100"); - //Act - builder.Add("thisIsTheName", "100"); - - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Add, operation.OperationType); - Assert.Equal("/thisIsTheName", operation.Path); - } - - [Fact] - public void RemoveGivenExpressionSetsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - //Act - builder.Remove(x => x.TestProperty); - - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Remove, operation.OperationType); - Assert.Equal("/thisIsTheName", operation.Path); - } + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/thisIsTheName", operation.Path); + } - [Fact] - public void RemoveGivenPathSetsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); + [Fact] + public void ReplaceGivenPropertyWithNoAttributesSetsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); - //Act - builder.Remove("thisIsTheName"); + //Act + builder.Replace(x => x.TestIntProperty, 50); - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Remove, operation.OperationType); - Assert.Equal("/thisIsTheName", operation.Path); - } + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/testIntProperty", operation.Path); + } - [Fact] - public void IncrementDoubleGivenExpressionSetsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); + [Fact] + public void ReplaceGivenPropertyWithRequiredAttributeSetsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); - //Act - builder.Increment(x => x.TestProperty, 100.123); + //Act + builder.Replace(x => x.TestProperty, "Test Value"); - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Increment, operation.OperationType); - Assert.Equal("/thisIsTheName", operation.Path); - } + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/testProperty", operation.Path); + } - [Fact] - public void IncrementDoubleGivenPathSetsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); + [Fact] + public void ReplaceGivenPropertyWithRequiredAndJsonAttributesSetsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); - //Act - builder.Increment("thisIsTheName", 100.123); + //Act + builder.Replace(x => x.TestProperty, "Test Value"); - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Increment, operation.OperationType); - Assert.Equal("/thisIsTheName", operation.Path); - } + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/testProperty", operation.Path); + } - [Fact] - public void IncrementLongGivenExpressionSetsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); + [Theory] + [MemberData(nameof(GetTestCases))] + public void AcknowledgeRepositorySerializationSettingForRetrievingPatchOperation(CosmosPropertyNamingPolicy? propertyNamingPolicy, + string expectedPropertyName) + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(propertyNamingPolicy); - //Act - builder.Increment(x => x.TestProperty, 123456789); + //Act + builder.Replace(x => x.TestIntProperty, 1234); - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Increment, operation.OperationType); - Assert.Equal("/thisIsTheName", operation.Path); - } + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal($"/{expectedPropertyName}", operation.Path); + } - [Fact] - public void IncrementLongGivenPathSetsCorrectPatchOperation() + public static IEnumerable GetTestCases() + { + yield return new object?[] { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - //Act - builder.Increment("thisIsTheName", 123456789); - - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Increment, operation.OperationType); - Assert.Equal("/thisIsTheName", operation.Path); - } - - [Theory] - [MemberData(nameof(GetTestCases))] - public void AcknowledgeRepositorySerializationSettingForRetrievingPatchOperation(CosmosPropertyNamingPolicy? propertyNamingPolicy, - string expectedPropertyName) + CosmosPropertyNamingPolicy.CamelCase, + new CamelCaseNamingStrategy().GetPropertyName(nameof(Item1.TestIntProperty), false) + }; + yield return new object?[] { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(propertyNamingPolicy); - - //Act - builder.Replace(x => x.TestIntProperty, 1234); - - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal($"/{expectedPropertyName}", operation.Path); - } - - public static IEnumerable GetTestCases() + CosmosPropertyNamingPolicy.Default, + new DefaultNamingStrategy().GetPropertyName(nameof(Item1.TestIntProperty), false) + }; + yield return new object?[] { - yield return new object?[] - { - CosmosPropertyNamingPolicy.CamelCase, - new CamelCaseNamingStrategy().GetPropertyName(nameof(Item1.TestIntProperty), false) - }; - yield return new object?[] - { - CosmosPropertyNamingPolicy.Default, - new DefaultNamingStrategy().GetPropertyName(nameof(Item1.TestIntProperty), false) - }; - yield return new object?[] - { - null, - new CamelCaseNamingStrategy().GetPropertyName(nameof(Item1.TestIntProperty), false) - }; - } + null, + new CamelCaseNamingStrategy().GetPropertyName(nameof(Item1.TestIntProperty), false) + }; } } \ No newline at end of file diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/ChangeFeed/InMemory/InMemoryChangeFeedTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/ChangeFeed/InMemory/InMemoryChangeFeedTests.cs index 25a0258f6..c0c3a3048 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/ChangeFeed/InMemory/InMemoryChangeFeedTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/ChangeFeed/InMemory/InMemoryChangeFeedTests.cs @@ -123,7 +123,7 @@ public async Task UpdateAsync_CollectionOfItemWhereSomeExist_InvokesChangeFeedPr } [Fact] - public async Task UpdateAsync_PatchUpdate_Replace_InvokesChangeFeedProcessor() + public async Task UpdateAsync_PatchUpdate_InvokesChangeFeedProcessor() { //Arrange TestItem item = new(); @@ -138,20 +138,4 @@ public async Task UpdateAsync_PatchUpdate_Replace_InvokesChangeFeedProcessor() Assert.Contains(_testItemChangeFeedProcessor.ChangedItems, x => x.Property == "propertyValue"); } - [Fact] - public async Task UpdateAsync_PatchUpdate_Add_InvokesChangeFeedProcessor() - { - //Arrange - TestItem item = new(); - - item = await _testItemRepository.CreateAsync(item); - - //Act - await _testItemRepository.UpdateAsync(item.Id, builder => builder.Add(x => x.Items, ["Test123"])); - - //Assert - Assert.Equal(2, _testItemChangeFeedProcessor.InvocationCount); - Assert.Contains(_testItemChangeFeedProcessor.ChangedItems, x => x.Property == "propertyValue"); - } - } \ No newline at end of file From 788be75d24ba29d26dea4607c50b91328536afec Mon Sep 17 00:00:00 2001 From: Qualizorg Date: Thu, 15 Aug 2024 23:14:02 +0200 Subject: [PATCH 05/16] checkpoint serializer --- .../Builders/PatchOperationBuilder.cs | 6 +++--- .../Builders/PatchOperationBuilderTests.cs | 13 +++++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs index 1be7faa0a..e4a0ef308 100644 --- a/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs @@ -68,7 +68,7 @@ public IPatchOperationBuilder Remove(Expression Remove(string path) @@ -82,14 +82,14 @@ public IPatchOperationBuilder Increment(Expression Increment(Expression> expression, long value) { var propertyInfo = expression.GetPropertyInfo(); var propertyToReplace = GetPropertyToReplace(propertyInfo); - return InternalIncrement(propertyToReplace, propertyInfo, value); + return InternalIncrement($"/{propertyToReplace}", propertyInfo, value); } public IPatchOperationBuilder Increment(string path, long value) diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs index b2295e2be..4230fc3dd 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs @@ -95,12 +95,17 @@ public void AcknowledgeRepositorySerializationSettingForRetrievingPatchOperation IPatchOperationBuilder builder = new PatchOperationBuilder(propertyNamingPolicy); //Act - builder.Replace(x => x.TestIntProperty, 1234); + builder.Add(x => x.TestIntProperty, 1); + builder.Set(x => x.TestIntProperty, 2); + builder.Replace(x => x.TestIntProperty, 3); + builder.Increment(x => x.TestIntProperty, 1); + builder.Remove(x => x.TestIntProperty); //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal($"/{expectedPropertyName}", operation.Path); + var paths = builder.PatchOperations.Select(x => x.Path); + + // Check that all paths are equal to the expected value + Assert.All(paths, path => Assert.Equal($"/{expectedPropertyName}", path)); } public static IEnumerable GetTestCases() From 1f69ca5461c9a568049ec014b2871030794498f3 Mon Sep 17 00:00:00 2001 From: Qualizorg Date: Thu, 15 Aug 2024 23:47:59 +0200 Subject: [PATCH 06/16] Bumped coverage PatchOperationBuilder to 80+ --- .../Builders/PatchOperationBuilder.cs | 44 +++-- .../Builders/PatchOperationBuilderTests.cs | 166 ++++++++++++++++++ 2 files changed, 192 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs index e4a0ef308..5791b9237 100644 --- a/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs @@ -30,7 +30,7 @@ public IPatchOperationBuilder Replace(Expression Replace(string path, TValue value) { @@ -42,7 +42,7 @@ public IPatchOperationBuilder Set(Expression> { var propertyInfo = expression.GetPropertyInfo(); var propertyToReplace = GetPropertyToReplace(propertyInfo); - return InternalSet($"/{propertyToReplace}", propertyInfo, value); + return InternalSet(propertyToReplace, propertyInfo, value); } public IPatchOperationBuilder Set(string path, TValue? value) @@ -55,7 +55,7 @@ public IPatchOperationBuilder Add(Expression> { var propertyInfo = expression.GetPropertyInfo(); var propertyToReplace = GetPropertyToReplace(propertyInfo); - return InternalAdd($"/{propertyToReplace}", propertyInfo, value); + return InternalAdd(propertyToReplace, propertyInfo, value); } public IPatchOperationBuilder Add(string path, TValue? value) { @@ -68,7 +68,7 @@ public IPatchOperationBuilder Remove(Expression Remove(string path) @@ -82,14 +82,14 @@ public IPatchOperationBuilder Increment(Expression Increment(Expression> expression, long value) { var propertyInfo = expression.GetPropertyInfo(); var propertyToReplace = GetPropertyToReplace(propertyInfo); - return InternalIncrement($"/{propertyToReplace}", propertyInfo, value); + return InternalIncrement(propertyToReplace, propertyInfo, value); } public IPatchOperationBuilder Increment(string path, long value) @@ -108,46 +108,54 @@ public IPatchOperationBuilder Increment(string path, double value) public IPatchOperationBuilder InternalReplace(string path, PropertyInfo property, TValue? value) { + path = NormalizePath(path); _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Replace)); - _patchOperations.Add(PatchOperation.Replace($"{path}", value)); + _patchOperations.Add(PatchOperation.Replace($"/{path}", value)); return this; } private IPatchOperationBuilder InternalSet(string path, PropertyInfo property, TValue? value) { + path = NormalizePath(path); _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Set)); - _patchOperations.Add(PatchOperation.Set(path, value)); + _patchOperations.Add(PatchOperation.Set($"/{path}", value)); return this; } public IPatchOperationBuilder InternalIncrement(string path, PropertyInfo property, long value) { + path = NormalizePath(path); _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Increment)); - _patchOperations.Add(PatchOperation.Increment(path, value)); + _patchOperations.Add(PatchOperation.Increment($"/{path}", value)); return this; } public IPatchOperationBuilder InternalIncrement(string path, PropertyInfo property, double value) { + path = NormalizePath(path); _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Increment)); - _patchOperations.Add(PatchOperation.Increment(path, value)); + _patchOperations.Add(PatchOperation.Increment($"/{path}", value)); return this; } public IPatchOperationBuilder InternalAdd(string path, PropertyInfo property, TValue? value) { + path = NormalizePath(path); _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Add)); - _patchOperations.Add(PatchOperation.Add(path, value)); + _patchOperations.Add(PatchOperation.Add($"/{path}", value)); return this; } public IPatchOperationBuilder InternalRemove(string path, PropertyInfo property) { + path = NormalizePath(path); _rawPatchOperations.Add(new InternalPatchOperation(property, null, PatchOperationType.Remove)); - _patchOperations.Add(PatchOperation.Remove(path)); + _patchOperations.Add(PatchOperation.Remove($"/{path}")); return this; } + private string NormalizePath(string path) => path.StartsWith("/", StringComparison.CurrentCultureIgnoreCase) ? path.Substring(1) : path; + /// /// Get the property name to replace. This only works for a single level of nesting. /// @@ -199,10 +207,10 @@ internal PropertyInfo GetPropertyInfoToReplace(string path) return null; } - public static PropertyInfo? GetNestedPropertyInfo(Type type, string path) + public PropertyInfo? GetNestedPropertyInfo(Type type, string path) { // Split the path by '/' to get the individual property names - string[] properties = path.Split('/'); + string[] properties = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); Type currentType = type; PropertyInfo? propertyInfo = null; @@ -210,8 +218,11 @@ internal PropertyInfo GetPropertyInfoToReplace(string path) // Iterate through each property in the path foreach (string propertyNameOrJsonProperty in properties) { + // If the property is not found by name, search by JsonPropertyAttribute + IEnumerable currentProperties = currentType.GetProperties(BindingFlags.Public | BindingFlags.Instance); + // First, attempt to get the property by its direct name - propertyInfo = currentType.GetProperty(propertyNameOrJsonProperty, BindingFlags.Public | BindingFlags.Instance); + propertyInfo = currentProperties.FirstOrDefault(p => string.Equals(p.Name, propertyNameOrJsonProperty, StringComparison.OrdinalIgnoreCase)); // If the property is found by name, continue to the next level if (propertyInfo != null) @@ -220,9 +231,6 @@ internal PropertyInfo GetPropertyInfoToReplace(string path) continue; } - // If the property is not found by name, search by JsonPropertyAttribute - PropertyInfo[] currentProperties = currentType.GetProperties(BindingFlags.Public | BindingFlags.Instance); - foreach (var property in currentProperties) { var jsonPropertyAttribute = property.GetCustomAttribute(); diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs index 4230fc3dd..847fbd37e 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs @@ -86,6 +86,172 @@ public void ReplaceGivenPropertyWithRequiredAndJsonAttributesSetsCorrectPatchOpe Assert.Equal("/testProperty", operation.Path); } + [Fact] + public void SetGivenPropertySetsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Set(x => x.TestIntProperty, 100); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal("/testIntProperty", operation.Path); + } + + [Fact] + public void AddGivenPropertyAddsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Add(x => x.TestIntProperty, 200); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal("/testIntProperty", operation.Path); + } + + [Fact] + public void RemoveGivenPropertyRemovesCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Remove(x => x.TestIntProperty); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/testIntProperty", operation.Path); + } + + [Fact] + public void IncrementGivenPropertyWithDoubleIncrementsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Increment(x => x.TestIntProperty, 1.5); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal("/testIntProperty", operation.Path); + } + + [Fact] + public void IncrementGivenPropertyWithLongIncrementsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Increment(x => x.TestIntProperty, 10); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal("/testIntProperty", operation.Path); + } + + [Fact] + public void ReplaceGivenPathSetsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Replace("/testIntProperty", 50); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/testIntProperty", operation.Path); + } + + [Fact] + public void SetGivenPathSetsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Set("/testIntProperty", 100); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal("/testIntProperty", operation.Path); + } + + [Fact] + public void AddGivenPathSetsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Add("/testIntProperty", 200); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal("/testIntProperty", operation.Path); + } + + [Fact] + public void RemoveGivenPathSetsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Remove("/testIntProperty"); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/testIntProperty", operation.Path); + } + + [Fact] + public void IncrementGivenPathWithDoubleIncrementsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Increment("/testIntProperty", 1.5); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal("/testIntProperty", operation.Path); + } + + [Fact] + public void IncrementGivenPathWithLongIncrementsCorrectPatchOperation() + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + //Act + builder.Increment("/testIntProperty", 10); + + //Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal("/testIntProperty", operation.Path); + } + + [Theory] [MemberData(nameof(GetTestCases))] public void AcknowledgeRepositorySerializationSettingForRetrievingPatchOperation(CosmosPropertyNamingPolicy? propertyNamingPolicy, From 79fcfade931812c725530a0eabde01bd06860758 Mon Sep 17 00:00:00 2001 From: Qualizorg Date: Fri, 16 Aug 2024 11:27:09 +0200 Subject: [PATCH 07/16] checkpoint prior to handle position memebers. --- .../Builders/PatchOperationBuilder.cs | 166 +++++----- .../Extensions/ExpressionExtensions.cs | 7 - .../Builders/PatchOperationBuilderTests.cs | 306 ++++++++++++++++++ 3 files changed, 399 insertions(+), 80 deletions(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs index 5791b9237..12aac781a 100644 --- a/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs @@ -29,20 +29,21 @@ public PatchOperationBuilder(CosmosPropertyNamingPolicy? cosmosPropertyNamingPol public IPatchOperationBuilder Replace(Expression> expression, TValue? value) { var propertyInfo = expression.GetPropertyInfo(); - var propertyToReplace = GetPropertyToReplace(propertyInfo); - return InternalReplace(propertyToReplace, propertyInfo, value); + var path = GetPath(expression); + return InternalReplace(path, propertyInfo, value); } public IPatchOperationBuilder Replace(string path, TValue value) { var propertyInfo = GetPropertyInfoToReplace(path); + return InternalReplace(path, propertyInfo, value); } public IPatchOperationBuilder Set(Expression> expression, TValue? value) { var propertyInfo = expression.GetPropertyInfo(); - var propertyToReplace = GetPropertyToReplace(propertyInfo); - return InternalSet(propertyToReplace, propertyInfo, value); + var path = GetPath(expression); + return InternalSet(path, propertyInfo, value); } public IPatchOperationBuilder Set(string path, TValue? value) @@ -54,55 +55,52 @@ public IPatchOperationBuilder Set(string path, TValue? value) public IPatchOperationBuilder Add(Expression> expression, TValue? value) { var propertyInfo = expression.GetPropertyInfo(); - var propertyToReplace = GetPropertyToReplace(propertyInfo); - return InternalAdd(propertyToReplace, propertyInfo, value); + var path = GetPath(expression); + return InternalAdd(path, propertyInfo, value); } public IPatchOperationBuilder Add(string path, TValue? value) { var propertyInfo = GetPropertyInfoToReplace(path); - var propertyToReplace = GetPropertyToReplace(propertyInfo); - return InternalAdd(propertyToReplace, propertyInfo, value); + return InternalAdd(path, propertyInfo, value); } public IPatchOperationBuilder Remove(Expression> expression) { var propertyInfo = expression.GetPropertyInfo(); - var propertyToReplace = GetPropertyToReplace(propertyInfo); - return InternalRemove(propertyToReplace, propertyInfo); + var path = GetPath(expression); + return InternalRemove(path, propertyInfo); } public IPatchOperationBuilder Remove(string path) { var propertyInfo = GetPropertyInfoToReplace(path); - var propertyToReplace = GetPropertyToReplace(propertyInfo); - return InternalRemove(propertyToReplace, propertyInfo); + return InternalRemove(path, propertyInfo); } public IPatchOperationBuilder Increment(Expression> expression, double value) { var propertyInfo = expression.GetPropertyInfo(); - var propertyToReplace = GetPropertyToReplace(propertyInfo); - return InternalIncrement(propertyToReplace, propertyInfo, value); + var path = GetPath(expression); + return InternalIncrement(path, propertyInfo, value); } public IPatchOperationBuilder Increment(Expression> expression, long value) { var propertyInfo = expression.GetPropertyInfo(); - var propertyToReplace = GetPropertyToReplace(propertyInfo); - return InternalIncrement(propertyToReplace, propertyInfo, value); + var path = GetPath(expression); + return InternalIncrement(path, propertyInfo, value); } public IPatchOperationBuilder Increment(string path, long value) { var propertyInfo = GetPropertyInfoToReplace(path); - var propertyToReplace = GetPropertyToReplace(propertyInfo); - return InternalIncrement(propertyToReplace, propertyInfo, value); + return InternalIncrement(path, propertyInfo, value); } public IPatchOperationBuilder Increment(string path, double value) { var propertyInfo = GetPropertyInfoToReplace(path); - var propertyToReplace = GetPropertyToReplace(propertyInfo); + var propertyToReplace = GetPropertyName(propertyInfo); return InternalIncrement(propertyToReplace, propertyInfo, value); } @@ -110,7 +108,7 @@ public IPatchOperationBuilder InternalReplace(string path, Proper { path = NormalizePath(path); _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Replace)); - _patchOperations.Add(PatchOperation.Replace($"/{path}", value)); + _patchOperations.Add(PatchOperation.Replace(path, value)); return this; } @@ -118,7 +116,7 @@ private IPatchOperationBuilder InternalSet(string path, PropertyI { path = NormalizePath(path); _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Set)); - _patchOperations.Add(PatchOperation.Set($"/{path}", value)); + _patchOperations.Add(PatchOperation.Set(path, value)); return this; } @@ -126,7 +124,7 @@ public IPatchOperationBuilder InternalIncrement(string path, PropertyInfo { path = NormalizePath(path); _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Increment)); - _patchOperations.Add(PatchOperation.Increment($"/{path}", value)); + _patchOperations.Add(PatchOperation.Increment(path, value)); return this; } @@ -134,7 +132,7 @@ public IPatchOperationBuilder InternalIncrement(string path, PropertyInfo { path = NormalizePath(path); _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Increment)); - _patchOperations.Add(PatchOperation.Increment($"/{path}", value)); + _patchOperations.Add(PatchOperation.Increment(path, value)); return this; } @@ -142,7 +140,7 @@ public IPatchOperationBuilder InternalAdd(string path, PropertyIn { path = NormalizePath(path); _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Add)); - _patchOperations.Add(PatchOperation.Add($"/{path}", value)); + _patchOperations.Add(PatchOperation.Add(path, value)); return this; } @@ -150,19 +148,19 @@ public IPatchOperationBuilder InternalRemove(string path, PropertyInfo pr { path = NormalizePath(path); _rawPatchOperations.Add(new InternalPatchOperation(property, null, PatchOperationType.Remove)); - _patchOperations.Add(PatchOperation.Remove($"/{path}")); + _patchOperations.Add(PatchOperation.Remove(path)); return this; } - private string NormalizePath(string path) => path.StartsWith("/", StringComparison.CurrentCultureIgnoreCase) ? path.Substring(1) : path; + private string NormalizePath(string path) => "/" + path.TrimStart('/'); /// - /// Get the property name to replace. This only works for a single level of nesting. + /// Get the property name. This only works for a single level of nesting. /// /// If you're looking for nesting call the respective method with a given path instead. /// The property info of the property to be replaced. /// Returns the path of the property name. - internal string GetPropertyToReplace(MemberInfo propertyInfo) + internal string GetPropertyName(MemberInfo propertyInfo) { JsonPropertyAttribute[] attributes = propertyInfo.GetCustomAttributes(true).ToArray(); @@ -172,6 +170,44 @@ internal string GetPropertyToReplace(MemberInfo propertyInfo) : attributes[0].PropertyName; } + public string GetPath(Expression> expr) + { + var stack = new Stack(); + + MemberExpression? me; + switch (expr.Body.NodeType) + { + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + me = ((expr.Body is UnaryExpression ue) ? ue.Operand : null) as MemberExpression; + break; + default: + me = expr.Body as MemberExpression; + break; + } + + while (me != null) + { + var memberInfo = me.Member as PropertyInfo; + + if (memberInfo != null) + { + var jsonPropertyAttribute = memberInfo.GetCustomAttribute(true); + + var propertyName = jsonPropertyAttribute != null + ? jsonPropertyAttribute.PropertyName + : _namingStrategy.GetPropertyName(memberInfo.Name, false); + + stack.Push(propertyName); + } + + me = me.Expression as MemberExpression; + } + + var path = string.Join("/", stack); + return NormalizePath(path); // Using the arrow function here + } + /// /// Get the property name to replace. This only works for a single level of nesting. /// @@ -187,27 +223,7 @@ internal PropertyInfo GetPropertyInfoToReplace(string path) return property; } - public static PropertyInfo? GetPropertyInfoByJsonPropertyName(Type type, string jsonPropertyName) - { - // Iterate through all properties of the type - foreach (PropertyInfo property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - // Check if the property has a JsonPropertyAttribute - var jsonPropertyAttribute = property.GetCustomAttribute(); - - // If the attribute is found and the PropertyName matches the provided jsonPropertyName - if (jsonPropertyAttribute != null && jsonPropertyAttribute.PropertyName == jsonPropertyName) - { - // Return the PropertyInfo of the matching property - return property; - } - } - - // Return null if no matching property is found - return null; - } - - public PropertyInfo? GetNestedPropertyInfo(Type type, string path) + private PropertyInfo? GetNestedPropertyInfo(Type type, string path) { // Split the path by '/' to get the individual property names string[] properties = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); @@ -218,40 +234,44 @@ internal PropertyInfo GetPropertyInfoToReplace(string path) // Iterate through each property in the path foreach (string propertyNameOrJsonProperty in properties) { - // If the property is not found by name, search by JsonPropertyAttribute - IEnumerable currentProperties = currentType.GetProperties(BindingFlags.Public | BindingFlags.Instance); - - // First, attempt to get the property by its direct name - propertyInfo = currentProperties.FirstOrDefault(p => string.Equals(p.Name, propertyNameOrJsonProperty, StringComparison.OrdinalIgnoreCase)); - - // If the property is found by name, continue to the next level - if (propertyInfo != null) - { - currentType = propertyInfo.PropertyType; - continue; - } - - foreach (var property in currentProperties) - { - var jsonPropertyAttribute = property.GetCustomAttribute(); - if (jsonPropertyAttribute != null && jsonPropertyAttribute.PropertyName == propertyNameOrJsonProperty) - { - propertyInfo = property; - currentType = propertyInfo.PropertyType; - break; - } - } + // Search for the property prioritizing JsonPropertyAttribute + propertyInfo = FindProperty(currentType, propertyNameOrJsonProperty); - // If no matching property is found at all, return null + // If no matching property is found, return null if (propertyInfo == null) { return null; } + + // Move to the next level of nesting + currentType = propertyInfo.PropertyType; } // Return the final PropertyInfo return propertyInfo; } + private PropertyInfo? FindProperty(Type type, string propertyNameOrJsonProperty) + { + // Get all public instance properties of the current type once + PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + + // Standardize the provided property name + var standardizedPropertyName = _namingStrategy.GetPropertyName(propertyNameOrJsonProperty, false); + + // Search for the property prioritizing JsonPropertyAttribute, fallback to direct name matching + return properties.FirstOrDefault(p => + { + var jsonPropertyAttribute = p.GetCustomAttribute(); + if (jsonPropertyAttribute != null) + { + return string.Equals(jsonPropertyAttribute.PropertyName, standardizedPropertyName, StringComparison.OrdinalIgnoreCase); + } + + // Fallback to direct property name comparison (standardized for consistency) + return string.Equals(_namingStrategy.GetPropertyName(p.Name, false), standardizedPropertyName, StringComparison.OrdinalIgnoreCase); + }); + } + } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Extensions/ExpressionExtensions.cs b/src/Microsoft.Azure.CosmosRepository/Extensions/ExpressionExtensions.cs index a51d85fda..54b68ea6e 100644 --- a/src/Microsoft.Azure.CosmosRepository/Extensions/ExpressionExtensions.cs +++ b/src/Microsoft.Azure.CosmosRepository/Extensions/ExpressionExtensions.cs @@ -47,13 +47,6 @@ internal static PropertyInfo GetPropertyInfo(this Expression if (member.Member is not PropertyInfo propInfo) throw new ArgumentException($"Expression '{propertyLambda}' refers to a field, not a property."); -#pragma warning disable IDE0046 // Convert to conditional expression - if (propInfo.ReflectedType != null && - type != propInfo.ReflectedType && - !type.IsSubclassOf(propInfo.ReflectedType)) - throw new ArgumentException($"Expression '{propertyLambda}' refers to a property that is not from type {type}."); -#pragma warning restore IDE0046 // Convert to conditional expression - return propInfo; } } \ No newline at end of file diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs index 847fbd37e..0f814a1d9 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs @@ -24,6 +24,28 @@ public class RequiredAndJsonItem : Item public string TestProperty { get; set; } = null!; } +public class Address +{ + public string Street { get; set; } = null!; + public int ZipCode { get; set; } + + public CountryInfo Country { get; set; } = new CountryInfo(); + + public string[] Tags { get; set; } = default!; +} + +public class CountryInfo +{ + public string Name { get; set; } = string.Empty; + public string CountryCode { get; set; } = string.Empty; +} + +public class Person : Item +{ + public string Name { get; set; } = null!; + public Address Address { get; set; } = null!; +} + public class PatchOperationBuilderTests { [Fact] @@ -86,6 +108,36 @@ public void ReplaceGivenPropertyWithRequiredAndJsonAttributesSetsCorrectPatchOpe Assert.Equal("/testProperty", operation.Path); } + [Fact] + public void ReplaceDeeplyNestedPropertyUsingExpressionReplacesCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Replace(x => x.Address.Country.CountryCode, "UK"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/address/country/countryCode", operation.Path); + } + + [Fact] + public void ReplaceDeeplyNestedPropertyUsingPathReplacesCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Replace("/address/country/countryCode", "UK"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/address/country/countryCode", operation.Path); + } + [Fact] public void SetGivenPropertySetsCorrectPatchOperation() { @@ -101,6 +153,36 @@ public void SetGivenPropertySetsCorrectPatchOperation() Assert.Equal("/testIntProperty", operation.Path); } + [Fact] + public void SetDeeplyNestedPropertyUsingExpressionSetsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Set(x => x.Address.Country.Name, "United Kingdom"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal("/address/country/name", operation.Path); + } + + [Fact] + public void SetDeeplyNestedPropertyUsingPathSetsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Set("/address/country/name", "United Kingdom"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal("/address/country/name", operation.Path); + } + [Fact] public void AddGivenPropertyAddsCorrectPatchOperation() { @@ -116,6 +198,36 @@ public void AddGivenPropertyAddsCorrectPatchOperation() Assert.Equal("/testIntProperty", operation.Path); } + [Fact] + public void AddDeeplyNestedPropertyUsingExpressionAddsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Add(x => x.Address.Tags, ["NewTag"]); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal("/address/tags/0", operation.Path); + } + + [Fact] + public void AddDeeplyNestedPropertyUsingPathAddsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Add("/address/tags/-", "NewTag"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal("/address/tags/0", operation.Path); + } + [Fact] public void RemoveGivenPropertyRemovesCorrectPatchOperation() { @@ -221,6 +333,36 @@ public void RemoveGivenPathSetsCorrectPatchOperation() Assert.Equal("/testIntProperty", operation.Path); } + [Fact] + public void RemoveDeeplyNestedPropertyUsingExpressionRemovesCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Remove(x => x.Address.Tags[0]); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/address/tags/0", operation.Path); + } + + [Fact] + public void RemoveDeeplyNestedPropertyUsingPathRemovesCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Remove("/address/tags"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/address/tags", operation.Path); + } + [Fact] public void IncrementGivenPathWithDoubleIncrementsCorrectPatchOperation() { @@ -251,6 +393,170 @@ public void IncrementGivenPathWithLongIncrementsCorrectPatchOperation() Assert.Equal("/testIntProperty", operation.Path); } + [Fact] + public void ReplaceNestedPropertyUsingExpressionSetsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Replace(x => x.Address.Street, "123 Main St"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/address/street", operation.Path); + } + [Fact] + public void ReplaceDeeplyNestedPropertyUsingExpressionSetsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Replace(x => x.Address.Country.Name, "United Kingdom"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/address/country/name", operation.Path); + } + + [Fact] + public void ReplaceNestedPropertyUsingPathSetsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Replace("/address/street", "456 Elm St"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/address/street", operation.Path); + } + + [Fact] + public void SetNestedPropertyUsingExpressionSetsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Set(x => x.Address.ZipCode, 90210); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal("/address/zipCode", operation.Path); + } + + [Fact] + public void SetNestedPropertyUsingPathSetsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Set("/address/zipCode", 12345); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal("/address/zipCode", operation.Path); + } + + [Fact] + public void AddNestedPropertyUsingExpressionAddsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Add(x => x.Address.ZipCode, 11111); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal("/address/zipCode", operation.Path); + } + + [Fact] + public void AddNestedPropertyUsingPathAddsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Add("/address/zipCode", 22222); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal("/address/zipCode", operation.Path); + } + + [Fact] + public void RemoveNestedPropertyUsingExpressionRemovesCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Remove(x => x.Address.ZipCode); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/address/zipCode", operation.Path); + } + + [Fact] + public void RemoveNestedPropertyUsingPathRemovesCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Remove("/address/zipCode"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/address/zipCode", operation.Path); // Assuming camelCase naming strategy + } + + [Fact] + public void IncrementNestedPropertyUsingExpressionIncrementsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Increment(x => x.Address.ZipCode, 10); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal("/address/zipCode", operation.Path); + } + + [Fact] + public void IncrementNestedPropertyUsingPathIncrementsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Increment("/address/zipCode", 5); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal("/address/zipCode", operation.Path); + } + [Theory] [MemberData(nameof(GetTestCases))] From afc8512a5c15b54dd865df53ece719d07c15bd1c Mon Sep 17 00:00:00 2001 From: Qualizorg Date: Fri, 16 Aug 2024 13:31:57 +0200 Subject: [PATCH 08/16] Finished POC, Missing todos --- .../Builders/PatchOperationBuilder.cs | 153 +++++--- .../Extensions/ExpressionExtensions.cs | 38 +- .../Internals/InternalPatchOperation.cs | 4 +- .../Repositories/InMemoryRepository.Update.cs | 2 + .../Builders/PatchOperationBuilderTests.cs | 342 +++++------------- .../InMemory/InMemoryChangeFeedTests.cs | 2 + .../DefaultRepositoryTests.cs | 2 + .../InMemoryRepositoryTests.cs | 2 + 8 files changed, 245 insertions(+), 300 deletions(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs index 12aac781a..c6107c44c 100644 --- a/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs @@ -104,55 +104,91 @@ public IPatchOperationBuilder Increment(string path, double value) return InternalIncrement(propertyToReplace, propertyInfo, value); } - public IPatchOperationBuilder InternalReplace(string path, PropertyInfo property, TValue? value) + internal IPatchOperationBuilder InternalReplace(string path, PropertyInfo property, TValue? value) { - path = NormalizePath(path); - _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Replace)); + path = NormalizePathPrefix(path); + var index = ExtractIndexFromPath(path); + _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Replace, index)); _patchOperations.Add(PatchOperation.Replace(path, value)); return this; } - private IPatchOperationBuilder InternalSet(string path, PropertyInfo property, TValue? value) + internal IPatchOperationBuilder InternalSet(string path, PropertyInfo property, TValue? value) { - path = NormalizePath(path); - _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Set)); + path = NormalizePathPrefix(path); + var index = ExtractIndexFromPath(path); + _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Set, index)); _patchOperations.Add(PatchOperation.Set(path, value)); return this; } - public IPatchOperationBuilder InternalIncrement(string path, PropertyInfo property, long value) + internal IPatchOperationBuilder InternalIncrement(string path, PropertyInfo property, long value) { - path = NormalizePath(path); - _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Increment)); + path = NormalizePathPrefix(path); + var index = ExtractIndexFromPath(path); + _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Increment, index)); _patchOperations.Add(PatchOperation.Increment(path, value)); return this; } - public IPatchOperationBuilder InternalIncrement(string path, PropertyInfo property, double value) + internal IPatchOperationBuilder InternalIncrement(string path, PropertyInfo property, double value) { - path = NormalizePath(path); - _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Increment)); + path = NormalizePathPrefix(path); + var index = ExtractIndexFromPath(path); + _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Increment, index)); _patchOperations.Add(PatchOperation.Increment(path, value)); return this; } - public IPatchOperationBuilder InternalAdd(string path, PropertyInfo property, TValue? value) + internal IPatchOperationBuilder InternalAdd(string path, PropertyInfo property, TValue? value) { - path = NormalizePath(path); - _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Add)); + path = NormalizePathPrefix(path); + path = NormalizePathSuffix(path, "/-"); + var index = ExtractIndexFromPath(path); + _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Add, index)); _patchOperations.Add(PatchOperation.Add(path, value)); return this; } - public IPatchOperationBuilder InternalRemove(string path, PropertyInfo property) + internal IPatchOperationBuilder InternalRemove(string path, PropertyInfo property) { - path = NormalizePath(path); - _rawPatchOperations.Add(new InternalPatchOperation(property, null, PatchOperationType.Remove)); + path = NormalizePathPrefix(path); + var index = ExtractIndexFromPath(path); + _rawPatchOperations.Add(new InternalPatchOperation(property, null, PatchOperationType.Remove, index)); _patchOperations.Add(PatchOperation.Remove(path)); return this; } - private string NormalizePath(string path) => "/" + path.TrimStart('/'); + private string NormalizePathPrefix(string path) => "/" + path.TrimStart('/').TrimEnd('/'); + + private string NormalizePathSuffix(string path, string expectedSuffix) => path.EndsWith(expectedSuffix) ? path : path + expectedSuffix; + + private int? ExtractIndexFromPath(string path) + { + if (string.IsNullOrEmpty(path)) + { + return null; + } + + // Split the path by '/' to get the segments + var segments = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + + // Get the last segment + var lastSegment = segments.LastOrDefault(); + + if (string.IsNullOrEmpty(lastSegment)) + { + return null; + } + + // Check if the last segment is a number + if (int.TryParse(lastSegment, out int position)) + { + return position; + } + + return null; + } /// /// Get the property name. This only works for a single level of nesting. @@ -173,39 +209,68 @@ internal string GetPropertyName(MemberInfo propertyInfo) public string GetPath(Expression> expr) { var stack = new Stack(); + Expression? expression = expr.Body; - MemberExpression? me; - switch (expr.Body.NodeType) + // Handle both MemberExpression and IndexExpression in the loop + while (expression != null) { - case ExpressionType.Convert: - case ExpressionType.ConvertChecked: - me = ((expr.Body is UnaryExpression ue) ? ue.Operand : null) as MemberExpression; - break; - default: - me = expr.Body as MemberExpression; - break; - } + if (expression is MemberExpression memberExpression) + { + var memberInfo = memberExpression.Member as PropertyInfo; - while (me != null) - { - var memberInfo = me.Member as PropertyInfo; + if (memberInfo != null) + { + var jsonPropertyAttribute = memberInfo.GetCustomAttribute(true); - if (memberInfo != null) - { - var jsonPropertyAttribute = memberInfo.GetCustomAttribute(true); + var propertyName = jsonPropertyAttribute != null + ? jsonPropertyAttribute.PropertyName + : _namingStrategy.GetPropertyName(memberInfo.Name, false); - var propertyName = jsonPropertyAttribute != null - ? jsonPropertyAttribute.PropertyName - : _namingStrategy.GetPropertyName(memberInfo.Name, false); + stack.Push(propertyName); + } - stack.Push(propertyName); + expression = memberExpression.Expression; } + else if (expression.NodeType == ExpressionType.ArrayIndex) + { + // Handle array indexing, e.g., x.Tags[0] where Tags is an array + var binaryExpression = (BinaryExpression)expression; + var indexValue = Expression.Lambda(binaryExpression.Right).Compile().DynamicInvoke(); + stack.Push($"{indexValue}"); - me = me.Expression as MemberExpression; + // Move to the object being indexed (e.g., Tags) + expression = binaryExpression.Left; + } + else if (expression is MethodCallExpression methodCallExpression) + { + // Handle list indexing like x.Tags[0], which results in a MethodCallExpression + if (methodCallExpression.Method.Name == "get_Item" && methodCallExpression.Arguments.Count == 1) + { + // Extract the index value + var indexValue = Expression.Lambda(methodCallExpression.Arguments[0]).Compile().DynamicInvoke(); + stack.Push($"[{indexValue}]"); + + // Move to the object being indexed (e.g., Tags) + expression = methodCallExpression.Object; + } + else + { + throw new ArgumentException($"Expression '{expr}' refers to an unsupported method."); + } + } + else if (expression is UnaryExpression unaryExpression) + { + // Handle conversions (e.g., Convert expressions) + expression = unaryExpression.Operand; + } + else + { + break; + } } var path = string.Join("/", stack); - return NormalizePath(path); // Using the arrow function here + return NormalizePathPrefix(path); } /// @@ -234,6 +299,12 @@ internal PropertyInfo GetPropertyInfoToReplace(string path) // Iterate through each property in the path foreach (string propertyNameOrJsonProperty in properties) { + + //Checking if is a position in an array + if (int.TryParse(propertyNameOrJsonProperty, out int pos) || propertyNameOrJsonProperty.EndsWith("-")) { + continue; + } + // Search for the property prioritizing JsonPropertyAttribute propertyInfo = FindProperty(currentType, propertyNameOrJsonProperty); diff --git a/src/Microsoft.Azure.CosmosRepository/Extensions/ExpressionExtensions.cs b/src/Microsoft.Azure.CosmosRepository/Extensions/ExpressionExtensions.cs index 54b68ea6e..ecd45d0ae 100644 --- a/src/Microsoft.Azure.CosmosRepository/Extensions/ExpressionExtensions.cs +++ b/src/Microsoft.Azure.CosmosRepository/Extensions/ExpressionExtensions.cs @@ -1,6 +1,8 @@ // Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. +using System.Linq.Expressions; + namespace Microsoft.Azure.CosmosRepository.Extensions; /// @@ -37,16 +39,36 @@ internal static Expression> Or( this Expression> first, Expression> second) => first.Compose(second, Expression.Or); + internal static PropertyInfo GetPropertyInfo(this Expression> propertyLambda) { - Type type = typeof(TSource); - - if (propertyLambda.Body is not MemberExpression member) - throw new ArgumentException($"Expression '{propertyLambda}' refers to a method, not a property."); - - if (member.Member is not PropertyInfo propInfo) - throw new ArgumentException($"Expression '{propertyLambda}' refers to a field, not a property."); + // Check if the body is a MemberExpression + if (propertyLambda.Body is MemberExpression memberExpression) + { + // Check if the MemberExpression refers to a Property + if (memberExpression.Member is PropertyInfo propInfo) + { + return propInfo; + } + else + { + throw new ArgumentException($"Expression '{propertyLambda}' refers to a field, not a property."); + } + } - return propInfo; + if (propertyLambda.Body.NodeType == ExpressionType.ArrayIndex && propertyLambda.Body is BinaryExpression binaryExpression && binaryExpression.Left is MemberExpression binaryMemberExpression) + { + // Check if the MemberExpression refers to a Property + if (binaryMemberExpression.Member is PropertyInfo propInfo) + { + return propInfo; + } + else + { + throw new ArgumentException($"Expression '{propertyLambda}' refers to a field, not a property."); + } + } + // Handle unexpected cases + throw new ArgumentException($"Expression '{propertyLambda}' is not a valid property expression."); } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Internals/InternalPatchOperation.cs b/src/Microsoft.Azure.CosmosRepository/Internals/InternalPatchOperation.cs index a4a53606b..4a692dabb 100644 --- a/src/Microsoft.Azure.CosmosRepository/Internals/InternalPatchOperation.cs +++ b/src/Microsoft.Azure.CosmosRepository/Internals/InternalPatchOperation.cs @@ -3,10 +3,10 @@ namespace Microsoft.Azure.CosmosRepository.Internals; -internal class InternalPatchOperation(PropertyInfo propertyInfo, object? newValue, PatchOperationType type) +internal class InternalPatchOperation(PropertyInfo propertyInfo, object? newValue, PatchOperationType type, int? index = null) { public PatchOperationType Type { get; } = type; public PropertyInfo PropertyInfo { get; } = propertyInfo; - + public int? Index { get; } = index; public object? NewValue { get; } = newValue; } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.Update.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.Update.cs index 4fd45f65d..7c68acfc7 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.Update.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.Update.cs @@ -104,6 +104,8 @@ public async ValueTask UpdateAsync(string id, builder(patchOperationBuilder); + + //TODO: Rely only on patchOperationBuilder.PatchOperations to translate to InfoProperties and add support for Add, Set, Remove, and Increment operations foreach (InternalPatchOperation internalPatchOperation in patchOperationBuilder._rawPatchOperations.Where(ipo => ipo.Type is PatchOperationType.Replace)) { diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs index 0f814a1d9..c784648ec 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs @@ -48,16 +48,17 @@ public class Person : Item public class PatchOperationBuilderTests { + // Replace Tests [Fact] public void ReplaceGivenPropertyValueWithJsonAttributeSetsCorrectReplaceValue() { - //Arrange + // Arrange IPatchOperationBuilder builder = new PatchOperationBuilder(); - //Act + // Act builder.Replace(x => x.TestProperty, "100"); - //Assert + // Assert PatchOperation operation = builder.PatchOperations[0]; Assert.Equal(PatchOperationType.Replace, operation.OperationType); Assert.Equal("/thisIsTheName", operation.Path); @@ -66,13 +67,13 @@ public void ReplaceGivenPropertyValueWithJsonAttributeSetsCorrectReplaceValue() [Fact] public void ReplaceGivenPropertyWithNoAttributesSetsCorrectPatchOperation() { - //Arrange + // Arrange IPatchOperationBuilder builder = new PatchOperationBuilder(); - //Act + // Act builder.Replace(x => x.TestIntProperty, 50); - //Assert + // Assert PatchOperation operation = builder.PatchOperations[0]; Assert.Equal(PatchOperationType.Replace, operation.OperationType); Assert.Equal("/testIntProperty", operation.Path); @@ -81,13 +82,13 @@ public void ReplaceGivenPropertyWithNoAttributesSetsCorrectPatchOperation() [Fact] public void ReplaceGivenPropertyWithRequiredAttributeSetsCorrectPatchOperation() { - //Arrange + // Arrange IPatchOperationBuilder builder = new PatchOperationBuilder(); - //Act + // Act builder.Replace(x => x.TestProperty, "Test Value"); - //Assert + // Assert PatchOperation operation = builder.PatchOperations[0]; Assert.Equal(PatchOperationType.Replace, operation.OperationType); Assert.Equal("/testProperty", operation.Path); @@ -96,13 +97,13 @@ public void ReplaceGivenPropertyWithRequiredAttributeSetsCorrectPatchOperation() [Fact] public void ReplaceGivenPropertyWithRequiredAndJsonAttributesSetsCorrectPatchOperation() { - //Arrange + // Arrange IPatchOperationBuilder builder = new PatchOperationBuilder(); - //Act + // Act builder.Replace(x => x.TestProperty, "Test Value"); - //Assert + // Assert PatchOperation operation = builder.PatchOperations[0]; Assert.Equal(PatchOperationType.Replace, operation.OperationType); Assert.Equal("/testProperty", operation.Path); @@ -139,63 +140,64 @@ public void ReplaceDeeplyNestedPropertyUsingPathReplacesCorrectValue() } [Fact] - public void SetGivenPropertySetsCorrectPatchOperation() + public void ReplaceNestedPropertyUsingExpressionSetsCorrectValue() { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); - //Act - builder.Set(x => x.TestIntProperty, 100); + // Act + builder.Replace(x => x.Address.Street, "123 Main St"); - //Assert + // Assert PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Set, operation.OperationType); - Assert.Equal("/testIntProperty", operation.Path); + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/address/street", operation.Path); } [Fact] - public void SetDeeplyNestedPropertyUsingExpressionSetsCorrectValue() + public void ReplaceDeeplyNestedPropertyUsingExpressionSetsCorrectValue() { // Arrange IPatchOperationBuilder builder = new PatchOperationBuilder(); // Act - builder.Set(x => x.Address.Country.Name, "United Kingdom"); + builder.Replace(x => x.Address.Country.Name, "United Kingdom"); // Assert PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal(PatchOperationType.Replace, operation.OperationType); Assert.Equal("/address/country/name", operation.Path); } [Fact] - public void SetDeeplyNestedPropertyUsingPathSetsCorrectValue() + public void ReplaceNestedPropertyUsingPathSetsCorrectValue() { // Arrange IPatchOperationBuilder builder = new PatchOperationBuilder(); // Act - builder.Set("/address/country/name", "United Kingdom"); + builder.Replace("/address/street", "456 Elm St"); // Assert PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Set, operation.OperationType); - Assert.Equal("/address/country/name", operation.Path); + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/address/street", operation.Path); } + // Add Tests [Fact] public void AddGivenPropertyAddsCorrectPatchOperation() { - //Arrange + // Arrange IPatchOperationBuilder builder = new PatchOperationBuilder(); - //Act + // Act builder.Add(x => x.TestIntProperty, 200); - //Assert + // Assert PatchOperation operation = builder.PatchOperations[0]; Assert.Equal(PatchOperationType.Add, operation.OperationType); - Assert.Equal("/testIntProperty", operation.Path); + Assert.Equal("/testIntProperty/-", operation.Path); } [Fact] @@ -210,7 +212,7 @@ public void AddDeeplyNestedPropertyUsingExpressionAddsCorrectValue() // Assert PatchOperation operation = builder.PatchOperations[0]; Assert.Equal(PatchOperationType.Add, operation.OperationType); - Assert.Equal("/address/tags/0", operation.Path); + Assert.Equal("/address/tags/-", operation.Path); } [Fact] @@ -225,316 +227,200 @@ public void AddDeeplyNestedPropertyUsingPathAddsCorrectValue() // Assert PatchOperation operation = builder.PatchOperations[0]; Assert.Equal(PatchOperationType.Add, operation.OperationType); - Assert.Equal("/address/tags/0", operation.Path); - } - - [Fact] - public void RemoveGivenPropertyRemovesCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - //Act - builder.Remove(x => x.TestIntProperty); - - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Remove, operation.OperationType); - Assert.Equal("/testIntProperty", operation.Path); - } - - [Fact] - public void IncrementGivenPropertyWithDoubleIncrementsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - //Act - builder.Increment(x => x.TestIntProperty, 1.5); - - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Increment, operation.OperationType); - Assert.Equal("/testIntProperty", operation.Path); - } - - [Fact] - public void IncrementGivenPropertyWithLongIncrementsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - //Act - builder.Increment(x => x.TestIntProperty, 10); - - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Increment, operation.OperationType); - Assert.Equal("/testIntProperty", operation.Path); - } - - [Fact] - public void ReplaceGivenPathSetsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - //Act - builder.Replace("/testIntProperty", 50); - - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal("/testIntProperty", operation.Path); - } - - [Fact] - public void SetGivenPathSetsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - //Act - builder.Set("/testIntProperty", 100); - - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Set, operation.OperationType); - Assert.Equal("/testIntProperty", operation.Path); - } - - [Fact] - public void AddGivenPathSetsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - //Act - builder.Add("/testIntProperty", 200); - - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Add, operation.OperationType); - Assert.Equal("/testIntProperty", operation.Path); + Assert.Equal("/address/tags/-", operation.Path); } [Fact] - public void RemoveGivenPathSetsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - //Act - builder.Remove("/testIntProperty"); - - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Remove, operation.OperationType); - Assert.Equal("/testIntProperty", operation.Path); - } - - [Fact] - public void RemoveDeeplyNestedPropertyUsingExpressionRemovesCorrectValue() + public void AddNestedPropertyUsingExpressionAddsCorrectValue() { // Arrange IPatchOperationBuilder builder = new PatchOperationBuilder(); // Act - builder.Remove(x => x.Address.Tags[0]); + builder.Add(x => x.Address.ZipCode, 11111); // Assert PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Remove, operation.OperationType); - Assert.Equal("/address/tags/0", operation.Path); + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal("/address/zipCode/-", operation.Path); } [Fact] - public void RemoveDeeplyNestedPropertyUsingPathRemovesCorrectValue() + public void AddNestedPropertyUsingPathAddsCorrectValue() { // Arrange IPatchOperationBuilder builder = new PatchOperationBuilder(); // Act - builder.Remove("/address/tags"); + builder.Add("/address/zipCode/-", 22222); // Assert PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Remove, operation.OperationType); - Assert.Equal("/address/tags", operation.Path); - } - - [Fact] - public void IncrementGivenPathWithDoubleIncrementsCorrectPatchOperation() - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - //Act - builder.Increment("/testIntProperty", 1.5); - - //Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Increment, operation.OperationType); - Assert.Equal("/testIntProperty", operation.Path); + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal("/address/zipCode/-", operation.Path); } + // Remove Tests [Fact] - public void IncrementGivenPathWithLongIncrementsCorrectPatchOperation() + public void RemoveGivenPropertyRemovesCorrectPatchOperation() { - //Arrange + // Arrange IPatchOperationBuilder builder = new PatchOperationBuilder(); - //Act - builder.Increment("/testIntProperty", 10); + // Act + builder.Remove(x => x.TestIntProperty); - //Assert + // Assert PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal(PatchOperationType.Remove, operation.OperationType); Assert.Equal("/testIntProperty", operation.Path); } [Fact] - public void ReplaceNestedPropertyUsingExpressionSetsCorrectValue() + public void RemoveDeeplyNestedPropertyListItemUsingExpressionRemovesCorrectValue() { // Arrange IPatchOperationBuilder builder = new PatchOperationBuilder(); // Act - builder.Replace(x => x.Address.Street, "123 Main St"); + builder.Remove(x => x.Address.Tags[0]); // Assert PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal("/address/street", operation.Path); + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/address/tags/0", operation.Path); } + [Fact] - public void ReplaceDeeplyNestedPropertyUsingExpressionSetsCorrectValue() + public void RemoveDeeplyNestedPropertyListItemUsingPathRemovesCorrectValue() { // Arrange IPatchOperationBuilder builder = new PatchOperationBuilder(); // Act - builder.Replace(x => x.Address.Country.Name, "United Kingdom"); + builder.Remove("/address/tags/0"); // Assert PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal("/address/country/name", operation.Path); + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/address/tags/0", operation.Path); } [Fact] - public void ReplaceNestedPropertyUsingPathSetsCorrectValue() + public void RemoveDeeplyNestedPropertyUsingPathRemovesCorrectValue() { // Arrange IPatchOperationBuilder builder = new PatchOperationBuilder(); // Act - builder.Replace("/address/street", "456 Elm St"); + builder.Remove("/address/tags"); // Assert PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal("/address/street", operation.Path); + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/address/tags", operation.Path); } [Fact] - public void SetNestedPropertyUsingExpressionSetsCorrectValue() + public void RemoveNestedPropertyUsingExpressionRemovesCorrectValue() { // Arrange IPatchOperationBuilder builder = new PatchOperationBuilder(); // Act - builder.Set(x => x.Address.ZipCode, 90210); + builder.Remove(x => x.Address.ZipCode); // Assert PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal(PatchOperationType.Remove, operation.OperationType); Assert.Equal("/address/zipCode", operation.Path); } [Fact] - public void SetNestedPropertyUsingPathSetsCorrectValue() + public void RemoveNestedPropertyUsingPathRemovesCorrectValue() { // Arrange IPatchOperationBuilder builder = new PatchOperationBuilder(); // Act - builder.Set("/address/zipCode", 12345); + builder.Remove("/address/zipCode"); // Assert PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal(PatchOperationType.Remove, operation.OperationType); Assert.Equal("/address/zipCode", operation.Path); } + // Set Tests [Fact] - public void AddNestedPropertyUsingExpressionAddsCorrectValue() + public void SetGivenPropertySetsCorrectPatchOperation() { // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); + IPatchOperationBuilder builder = new PatchOperationBuilder(); // Act - builder.Add(x => x.Address.ZipCode, 11111); + builder.Set(x => x.TestIntProperty, 1); // Assert PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Add, operation.OperationType); - Assert.Equal("/address/zipCode", operation.Path); + Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal("/testIntProperty", operation.Path); } [Fact] - public void AddNestedPropertyUsingPathAddsCorrectValue() + public void SetNestedPropertyUsingExpressionSetsCorrectValue() { // Arrange IPatchOperationBuilder builder = new PatchOperationBuilder(); // Act - builder.Add("/address/zipCode", 22222); + builder.Set(x => x.Address.Street, "789 Oak St"); // Assert PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Add, operation.OperationType); - Assert.Equal("/address/zipCode", operation.Path); + Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal("/address/street", operation.Path); } [Fact] - public void RemoveNestedPropertyUsingExpressionRemovesCorrectValue() + public void SetNestedPropertyUsingPathSetsCorrectValue() { // Arrange IPatchOperationBuilder builder = new PatchOperationBuilder(); // Act - builder.Remove(x => x.Address.ZipCode); + builder.Set("/address/street", "789 Oak St"); // Assert PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Remove, operation.OperationType); - Assert.Equal("/address/zipCode", operation.Path); + Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal("/address/street", operation.Path); } + // Increment Tests [Fact] - public void RemoveNestedPropertyUsingPathRemovesCorrectValue() + public void IncrementGivenPropertyIncreasesValueCorrectly() { // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); + IPatchOperationBuilder builder = new PatchOperationBuilder(); // Act - builder.Remove("/address/zipCode"); + builder.Increment(x => x.TestIntProperty, 10); // Assert PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Remove, operation.OperationType); - Assert.Equal("/address/zipCode", operation.Path); // Assuming camelCase naming strategy + Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal("/testIntProperty", operation.Path); } [Fact] - public void IncrementNestedPropertyUsingExpressionIncrementsCorrectValue() + public void IncrementDeeplyNestedPropertyUsingExpressionIncreasesValueCorrectly() { // Arrange IPatchOperationBuilder builder = new PatchOperationBuilder(); // Act - builder.Increment(x => x.Address.ZipCode, 10); + builder.Increment(x => x.Address.ZipCode, 5); // Assert PatchOperation operation = builder.PatchOperations[0]; @@ -543,7 +429,7 @@ public void IncrementNestedPropertyUsingExpressionIncrementsCorrectValue() } [Fact] - public void IncrementNestedPropertyUsingPathIncrementsCorrectValue() + public void IncrementDeeplyNestedPropertyUsingPathIncreasesValueCorrectly() { // Arrange IPatchOperationBuilder builder = new PatchOperationBuilder(); @@ -556,46 +442,4 @@ public void IncrementNestedPropertyUsingPathIncrementsCorrectValue() Assert.Equal(PatchOperationType.Increment, operation.OperationType); Assert.Equal("/address/zipCode", operation.Path); } - - - [Theory] - [MemberData(nameof(GetTestCases))] - public void AcknowledgeRepositorySerializationSettingForRetrievingPatchOperation(CosmosPropertyNamingPolicy? propertyNamingPolicy, - string expectedPropertyName) - { - //Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(propertyNamingPolicy); - - //Act - builder.Add(x => x.TestIntProperty, 1); - builder.Set(x => x.TestIntProperty, 2); - builder.Replace(x => x.TestIntProperty, 3); - builder.Increment(x => x.TestIntProperty, 1); - builder.Remove(x => x.TestIntProperty); - - //Assert - var paths = builder.PatchOperations.Select(x => x.Path); - - // Check that all paths are equal to the expected value - Assert.All(paths, path => Assert.Equal($"/{expectedPropertyName}", path)); - } - - public static IEnumerable GetTestCases() - { - yield return new object?[] - { - CosmosPropertyNamingPolicy.CamelCase, - new CamelCaseNamingStrategy().GetPropertyName(nameof(Item1.TestIntProperty), false) - }; - yield return new object?[] - { - CosmosPropertyNamingPolicy.Default, - new DefaultNamingStrategy().GetPropertyName(nameof(Item1.TestIntProperty), false) - }; - yield return new object?[] - { - null, - new CamelCaseNamingStrategy().GetPropertyName(nameof(Item1.TestIntProperty), false) - }; - } -} \ No newline at end of file +} diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/ChangeFeed/InMemory/InMemoryChangeFeedTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/ChangeFeed/InMemory/InMemoryChangeFeedTests.cs index c0c3a3048..fd8a33009 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/ChangeFeed/InMemory/InMemoryChangeFeedTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/ChangeFeed/InMemory/InMemoryChangeFeedTests.cs @@ -138,4 +138,6 @@ public async Task UpdateAsync_PatchUpdate_InvokesChangeFeedProcessor() Assert.Contains(_testItemChangeFeedProcessor.ChangedItems, x => x.Property == "propertyValue"); } + //TODO: Add more tests for other operations Set, Add, Remove, Increment, + } \ No newline at end of file diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/DefaultRepositoryTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/DefaultRepositoryTests.cs index 6b5a5bae7..580e15016 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/DefaultRepositoryTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/DefaultRepositoryTests.cs @@ -460,6 +460,8 @@ public async Task UpdateAsync_Patch_WhenEtagIsSet_UseSetEtagValueInPatchOptions( It.Is(options => options.IfMatchEtag == etag), It.IsAny()), Times.Once); } + + //TODO: Add PatchBuilder tests for Set, Add, Remove, Increment, etc. } class MockExpressionProvider : IRepositoryExpressionProvider diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/InMemoryRepositoryTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/InMemoryRepositoryTests.cs index f64a22bc3..920ddee2e 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/InMemoryRepositoryTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/InMemoryRepositoryTests.cs @@ -989,6 +989,8 @@ await _rootObjectRepository.UpdateAsync(root.Id, builder => Assert.Equal(2, deserialisedItem.NestedObject.Property2); } + //TODO: Add more PatchBuilder tests for Set, Add, Remove, Increment, etc. + [Fact] public async Task PageAsync_PredicateThatDoesNotMatch_ReturnsEmptyList() { From 74799169d934f9bd8ebd8ba104ea1a3e97adcdf3 Mon Sep 17 00:00:00 2001 From: Qualizorg Date: Fri, 16 Aug 2024 14:40:07 +0200 Subject: [PATCH 09/16] Checkpoint merge --- .../Builders/PatchOperationBuilder.cs | 91 ++++++++++--------- .../Extensions/ExpressionExtensions.cs | 8 +- 2 files changed, 50 insertions(+), 49 deletions(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs index f652319a4..4a0663be6 100644 --- a/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs @@ -8,44 +8,44 @@ [assembly: InternalsVisibleTo("Microsoft.Azure.CosmosRepositoryTests")] namespace Microsoft.Azure.CosmosRepository.Builders { - private readonly List _patchOperations = []; - private readonly NamingStrategy _namingStrategy; - internal readonly List _rawPatchOperations = []; - + internal class PatchOperationBuilder : IPatchOperationBuilder where TItem : IItem { - private readonly List _patchOperations = new(); + private readonly List _patchOperations = []; private readonly NamingStrategy _namingStrategy; - internal readonly List _rawPatchOperations = new(); + internal readonly List _rawPatchOperations = []; public IReadOnlyList PatchOperations => _patchOperations; + public PatchOperationBuilder() => _namingStrategy = new CamelCaseNamingStrategy(); - - public IPatchOperationBuilder Replace(Expression> expression, TValue? value) - { - IReadOnlyList propertyInfos = expression.GetPropertyInfos(); - var propertyToReplace = GetPropertyToReplace(propertyInfos); - - _rawPatchOperations.Add(new InternalPatchOperation(propertyInfos, value, PatchOperationType.Replace)); - _patchOperations.Add(PatchOperation.Replace($"/{propertyToReplace}", value)); - - return this; - } - public PatchOperationBuilder(CosmosPropertyNamingPolicy? cosmosPropertyNamingPolicy) => + public PatchOperationBuilder(CosmosPropertyNamingPolicy? cosmosPropertyNamingPolicy) => _namingStrategy = cosmosPropertyNamingPolicy == CosmosPropertyNamingPolicy.Default ? new DefaultNamingStrategy() : new CamelCaseNamingStrategy(); + //public IPatchOperationBuilder Replace(Expression> expression, TValue? value) + //{ + // var propertyInfo = expression.GetPropertyInfo(); + // var propertyInfos = expression.GetPropertyInfos(); + // var path = GetPath(expression); + // return InternalReplace(path, propertyInfo, value); + //} + public IPatchOperationBuilder Replace(Expression> expression, TValue? value) { - var propertyInfo = expression.GetPropertyInfo(); - var path = GetPath(expression); - return InternalReplace(path, propertyInfo, value); + IReadOnlyList propertyInfos = expression.GetPropertyInfos(); + var propertyToReplace = GetPropertyToReplace(propertyInfos); + + _rawPatchOperations.Add(new InternalPatchOperation(propertyInfos, value, PatchOperationType.Replace)); + _patchOperations.Add(PatchOperation.Replace($"/{propertyToReplace}", value)); + + return this; } + public IPatchOperationBuilder Replace(string path, TValue value) { var propertyInfo = GetPropertyInfoToReplace(path); @@ -122,7 +122,7 @@ internal IPatchOperationBuilder InternalReplace(string path, Prop { path = NormalizePathPrefix(path); var index = ExtractIndexFromPath(path); - _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Replace, index)); + _rawPatchOperations.Add(new InternalPatchOperation([property], value, PatchOperationType.Replace, index)); _patchOperations.Add(PatchOperation.Replace(path, value)); return this; } @@ -131,7 +131,7 @@ internal IPatchOperationBuilder InternalSet(string path, Property { path = NormalizePathPrefix(path); var index = ExtractIndexFromPath(path); - _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Set, index)); + _rawPatchOperations.Add(new InternalPatchOperation([property], value, PatchOperationType.Set, index)); _patchOperations.Add(PatchOperation.Set(path, value)); return this; } @@ -140,7 +140,7 @@ internal IPatchOperationBuilder InternalIncrement(string path, PropertyIn { path = NormalizePathPrefix(path); var index = ExtractIndexFromPath(path); - _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Increment, index)); + _rawPatchOperations.Add(new InternalPatchOperation([property], value, PatchOperationType.Increment, index)); _patchOperations.Add(PatchOperation.Increment(path, value)); return this; } @@ -149,7 +149,7 @@ internal IPatchOperationBuilder InternalIncrement(string path, PropertyIn { path = NormalizePathPrefix(path); var index = ExtractIndexFromPath(path); - _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Increment, index)); + _rawPatchOperations.Add(new InternalPatchOperation([property], value, PatchOperationType.Increment, index)); _patchOperations.Add(PatchOperation.Increment(path, value)); return this; } @@ -159,7 +159,7 @@ internal IPatchOperationBuilder InternalAdd(string path, Property path = NormalizePathPrefix(path); path = NormalizePathSuffix(path, "/-"); var index = ExtractIndexFromPath(path); - _rawPatchOperations.Add(new InternalPatchOperation(property, value, PatchOperationType.Add, index)); + _rawPatchOperations.Add(new InternalPatchOperation([property], value, PatchOperationType.Add, index)); _patchOperations.Add(PatchOperation.Add(path, value)); return this; } @@ -168,7 +168,7 @@ internal IPatchOperationBuilder InternalRemove(string path, PropertyInfo { path = NormalizePathPrefix(path); var index = ExtractIndexFromPath(path); - _rawPatchOperations.Add(new InternalPatchOperation(property, null, PatchOperationType.Remove, index)); + _rawPatchOperations.Add(new InternalPatchOperation([property], null, PatchOperationType.Remove, index)); _patchOperations.Add(PatchOperation.Remove(path)); return this; } @@ -219,28 +219,28 @@ internal string GetPropertyName(MemberInfo propertyInfo) ? _namingStrategy.GetPropertyName(propertyInfo.Name, false) : attributes[0].PropertyName; } - - private string GetPropertyToReplace(IEnumerable propertyInfos) - { - List propertiesNames = []; - foreach (PropertyInfo propertyInfo in propertyInfos) + private string GetPropertyToReplace(IEnumerable propertyInfos) { - JsonPropertyAttribute[] attributes = - propertyInfo.GetCustomAttributes(true).ToArray(); + List propertiesNames = []; - var propertyName = attributes.Length is 0 - ? _namingStrategy.GetPropertyName(propertyInfo.Name, false) - : attributes[0].PropertyName; + foreach (PropertyInfo propertyInfo in propertyInfos) + { + JsonPropertyAttribute[] attributes = + propertyInfo.GetCustomAttributes(true).ToArray(); - propertiesNames.Add(propertyName); - } + var propertyName = attributes.Length is 0 + ? _namingStrategy.GetPropertyName(propertyInfo.Name, false) + : attributes[0].PropertyName; - return string.Join("/", propertiesNames); - } + propertiesNames.Add(propertyName); + } - private string GetPropertyToReplace(MemberInfo propertyInfo) => - GetPropertyToReplace([propertyInfo]); + return string.Join("/", propertiesNames); + } + + private string GetPropertyToReplace(MemberInfo propertyInfo) => + GetPropertyToReplace([propertyInfo]); public string GetPath(Expression> expr) @@ -336,9 +336,10 @@ internal PropertyInfo GetPropertyInfoToReplace(string path) // Iterate through each property in the path foreach (string propertyNameOrJsonProperty in properties) { - + //Checking if is a position in an array - if (int.TryParse(propertyNameOrJsonProperty, out int pos) || propertyNameOrJsonProperty.EndsWith("-")) { + if (int.TryParse(propertyNameOrJsonProperty, out int pos) || propertyNameOrJsonProperty.EndsWith("-")) + { continue; } diff --git a/src/Microsoft.Azure.CosmosRepository/Extensions/ExpressionExtensions.cs b/src/Microsoft.Azure.CosmosRepository/Extensions/ExpressionExtensions.cs index 60b177dc9..3e3cdeaff 100644 --- a/src/Microsoft.Azure.CosmosRepository/Extensions/ExpressionExtensions.cs +++ b/src/Microsoft.Azure.CosmosRepository/Extensions/ExpressionExtensions.cs @@ -42,11 +42,11 @@ internal static Expression> Or( internal static PropertyInfo GetPropertyInfo(this Expression> propertyLambda) { - var propertyInfos = GetPropertyInfosInternal(propertyLambda); + //var propertyInfos = GetPropertyInfosInternal(propertyLambda); - PropertyInfo propInfo = propertyInfos[propertyInfos.Count - 1]; + //PropertyInfo propInfo = propertyInfos[propertyInfos.Count - 1]; - ThrowArgumentExceptionIfPropertyIsNotFromSourceType(propertyLambda, propInfo); + //ThrowArgumentExceptionIfPropertyIsNotFromSourceType(propertyLambda, propInfo); //Intermediate Step Our Code // Check if the body is a MemberExpression @@ -78,7 +78,7 @@ internal static PropertyInfo GetPropertyInfo(this Expression // Handle unexpected cases throw new ArgumentException($"Expression '{propertyLambda}' is not a valid property expression."); - return propInfo; + // return propInfo; } internal static IReadOnlyList GetPropertyInfos(this Expression> propertyLambda) From 6ef0ec3bb661c8f4c7afc7984272a685997d27f3 Mon Sep 17 00:00:00 2001 From: Qualizorg Date: Fri, 16 Aug 2024 23:22:48 +0200 Subject: [PATCH 10/16] Added unit tests --- .../Builders/PatchOperationBuilder.cs | 191 ++++---- .../Extensions/ExpressionExtensions.cs | 74 +-- .../PatchOperationBuilderTests.Add.cs | 181 +++++++ .../PatchOperationBuilderTests.Increment.cs | 144 ++++++ .../PatchOperationBuilderTests.Remove.cs | 161 +++++++ .../PatchOperationBuilderTests.Replace.cs | 196 ++++++++ .../PatchOperationBuilderTests.Set.cs | 136 ++++++ .../PatchOperationBuilderTests.cs | 94 ++++ .../Builders/PatchOperationBuilderTests.cs | 445 ------------------ 9 files changed, 1050 insertions(+), 572 deletions(-) create mode 100644 tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.Add.cs create mode 100644 tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.Increment.cs create mode 100644 tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.Remove.cs create mode 100644 tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.Replace.cs create mode 100644 tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.Set.cs create mode 100644 tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.cs delete mode 100644 tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs diff --git a/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs index 4a0663be6..1c768a132 100644 --- a/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Builders/PatchOperationBuilder.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq.Expressions; using System.Reflection; +using Newtonsoft.Json.Linq; [assembly: InternalsVisibleTo("Microsoft.Azure.CosmosRepositoryTests")] namespace Microsoft.Azure.CosmosRepository.Builders @@ -27,148 +28,171 @@ public PatchOperationBuilder(CosmosPropertyNamingPolicy? cosmosPropertyNamingPol ? new DefaultNamingStrategy() : new CamelCaseNamingStrategy(); - //public IPatchOperationBuilder Replace(Expression> expression, TValue? value) - //{ - // var propertyInfo = expression.GetPropertyInfo(); - // var propertyInfos = expression.GetPropertyInfos(); - // var path = GetPath(expression); - // return InternalReplace(path, propertyInfo, value); - //} - public IPatchOperationBuilder Replace(Expression> expression, TValue? value) { - IReadOnlyList propertyInfos = expression.GetPropertyInfos(); - var propertyToReplace = GetPropertyToReplace(propertyInfos); - - _rawPatchOperations.Add(new InternalPatchOperation(propertyInfos, value, PatchOperationType.Replace)); - _patchOperations.Add(PatchOperation.Replace($"/{propertyToReplace}", value)); - - return this; + return InternalReplace(expression, value); } public IPatchOperationBuilder Replace(string path, TValue value) { - var propertyInfo = GetPropertyInfoToReplace(path); - - return InternalReplace(path, propertyInfo, value); + var propertyInfos = GetPropertyInfos(path); + return InternalReplace(propertyInfos, value, path); } public IPatchOperationBuilder Set(Expression> expression, TValue? value) { - var propertyInfo = expression.GetPropertyInfo(); - var path = GetPath(expression); - return InternalSet(path, propertyInfo, value); + return InternalSet(expression, value); } public IPatchOperationBuilder Set(string path, TValue? value) { - var propertyInfo = GetPropertyInfoToReplace(path); - return InternalSet(path, propertyInfo, value); + var propertyInfos = GetPropertyInfos(path); + return InternalSet(propertyInfos, value, path); } public IPatchOperationBuilder Add(Expression> expression, TValue? value) { - var propertyInfo = expression.GetPropertyInfo(); - var path = GetPath(expression); - return InternalAdd(path, propertyInfo, value); + return InternalAdd(expression, value); } public IPatchOperationBuilder Add(string path, TValue? value) { - var propertyInfo = GetPropertyInfoToReplace(path); - return InternalAdd(path, propertyInfo, value); + var propertyInfos = GetPropertyInfos(path); + return InternalAdd(propertyInfos, value, path); } public IPatchOperationBuilder Remove(Expression> expression) { - var propertyInfo = expression.GetPropertyInfo(); - var path = GetPath(expression); - return InternalRemove(path, propertyInfo); + return InternalRemove(expression); } public IPatchOperationBuilder Remove(string path) { - var propertyInfo = GetPropertyInfoToReplace(path); - return InternalRemove(path, propertyInfo); + var propertyInfos = GetPropertyInfos(path); + return InternalRemove(propertyInfos, path); } public IPatchOperationBuilder Increment(Expression> expression, double value) { - var propertyInfo = expression.GetPropertyInfo(); - var path = GetPath(expression); - return InternalIncrement(path, propertyInfo, value); + return InternalIncrement(expression, value); } public IPatchOperationBuilder Increment(Expression> expression, long value) { - var propertyInfo = expression.GetPropertyInfo(); - var path = GetPath(expression); - return InternalIncrement(path, propertyInfo, value); + return InternalIncrement(expression, value); } public IPatchOperationBuilder Increment(string path, long value) { - var propertyInfo = GetPropertyInfoToReplace(path); - return InternalIncrement(path, propertyInfo, value); + var propertyInfos = GetPropertyInfos(path); + return InternalIncrement(propertyInfos, value, path); } public IPatchOperationBuilder Increment(string path, double value) { - var propertyInfo = GetPropertyInfoToReplace(path); - var propertyToReplace = GetPropertyName(propertyInfo); - return InternalIncrement(propertyToReplace, propertyInfo, value); + var propertyInfos = GetPropertyInfos(path); + return InternalIncrement(propertyInfos, value, path); } - internal IPatchOperationBuilder InternalReplace(string path, PropertyInfo property, TValue? value) + internal IPatchOperationBuilder InternalReplace(Expression> expression, TValue? value) { - path = NormalizePathPrefix(path); + IReadOnlyList propertyInfos = expression.GetPropertyInfos(); + return InternalReplace(propertyInfos, value); + } + + internal IPatchOperationBuilder InternalReplace(IReadOnlyList propertyInfos, TValue? value, string? path = null) + { + path = path ?? GetPropertyPath(propertyInfos); var index = ExtractIndexFromPath(path); - _rawPatchOperations.Add(new InternalPatchOperation([property], value, PatchOperationType.Replace, index)); + + path = NormalizePathPrefix(path); + _rawPatchOperations.Add(new InternalPatchOperation(propertyInfos, value, PatchOperationType.Replace, index)); _patchOperations.Add(PatchOperation.Replace(path, value)); return this; } - internal IPatchOperationBuilder InternalSet(string path, PropertyInfo property, TValue? value) + internal IPatchOperationBuilder InternalSet(Expression> expression, TValue? value, string? path = null) { - path = NormalizePathPrefix(path); + IReadOnlyList propertyInfos = expression.GetPropertyInfos(); + return InternalSet(propertyInfos, value, path); + } + + internal IPatchOperationBuilder InternalSet(IReadOnlyList propertyInfos, TValue? value, string? path = null) + { + path = path ?? GetPropertyPath(propertyInfos); var index = ExtractIndexFromPath(path); - _rawPatchOperations.Add(new InternalPatchOperation([property], value, PatchOperationType.Set, index)); + + path = NormalizePathPrefix(path); + _rawPatchOperations.Add(new InternalPatchOperation(propertyInfos, value, PatchOperationType.Set, index)); _patchOperations.Add(PatchOperation.Set(path, value)); return this; } - internal IPatchOperationBuilder InternalIncrement(string path, PropertyInfo property, long value) + internal IPatchOperationBuilder InternalIncrement(Expression> expression, long value, string? path = null) { - path = NormalizePathPrefix(path); + IReadOnlyList propertyInfos = expression.GetPropertyInfos(); + return InternalIncrement(propertyInfos, value, path); + } + + internal IPatchOperationBuilder InternalIncrement(IReadOnlyList propertyInfos, long value, string? path = null) + { + path = path ?? GetPropertyPath(propertyInfos); var index = ExtractIndexFromPath(path); - _rawPatchOperations.Add(new InternalPatchOperation([property], value, PatchOperationType.Increment, index)); + + path = NormalizePathPrefix(path); + _rawPatchOperations.Add(new InternalPatchOperation(propertyInfos, value, PatchOperationType.Increment, index)); _patchOperations.Add(PatchOperation.Increment(path, value)); return this; } - internal IPatchOperationBuilder InternalIncrement(string path, PropertyInfo property, double value) + internal IPatchOperationBuilder InternalIncrement(Expression> expression, double value, string? path = null) { - path = NormalizePathPrefix(path); + IReadOnlyList propertyInfos = expression.GetPropertyInfos(); + return InternalIncrement(propertyInfos, value, path); + } + + internal IPatchOperationBuilder InternalIncrement(IReadOnlyList propertyInfos, double value, string? path = null) + { + path = path ?? GetPropertyPath(propertyInfos); var index = ExtractIndexFromPath(path); - _rawPatchOperations.Add(new InternalPatchOperation([property], value, PatchOperationType.Increment, index)); + + path = NormalizePathPrefix(path); + _rawPatchOperations.Add(new InternalPatchOperation(propertyInfos, value, PatchOperationType.Increment, index)); _patchOperations.Add(PatchOperation.Increment(path, value)); return this; } - internal IPatchOperationBuilder InternalAdd(string path, PropertyInfo property, TValue? value) + internal IPatchOperationBuilder InternalAdd(Expression> expression, TValue? value, string? path = null) + { + IReadOnlyList propertyInfos = expression.GetPropertyInfos(); + return InternalAdd(propertyInfos, value, path); + } + + internal IPatchOperationBuilder InternalAdd(IReadOnlyList propertyInfos, TValue? value, string? path = null) { + path = path ?? GetPropertyPath(propertyInfos); + var index = ExtractIndexFromPath(path); + path = NormalizePathPrefix(path); path = NormalizePathSuffix(path, "/-"); - var index = ExtractIndexFromPath(path); - _rawPatchOperations.Add(new InternalPatchOperation([property], value, PatchOperationType.Add, index)); + + _rawPatchOperations.Add(new InternalPatchOperation(propertyInfos, value, PatchOperationType.Add, index)); _patchOperations.Add(PatchOperation.Add(path, value)); return this; } - internal IPatchOperationBuilder InternalRemove(string path, PropertyInfo property) + internal IPatchOperationBuilder InternalRemove(Expression> expression, string? path = null) { + IReadOnlyList propertyInfos = expression.GetPropertyInfos(); + return InternalRemove(propertyInfos, path); + } + + internal IPatchOperationBuilder InternalRemove(IReadOnlyList propertyInfos, string? path = null) + { + path = path ?? GetPropertyPath(propertyInfos); + path = NormalizePathPrefix(path); var index = ExtractIndexFromPath(path); - _rawPatchOperations.Add(new InternalPatchOperation([property], null, PatchOperationType.Remove, index)); + _rawPatchOperations.Add(new InternalPatchOperation(propertyInfos, null, PatchOperationType.Remove, index)); _patchOperations.Add(PatchOperation.Remove(path)); return this; } @@ -220,7 +244,7 @@ internal string GetPropertyName(MemberInfo propertyInfo) : attributes[0].PropertyName; } - private string GetPropertyToReplace(IEnumerable propertyInfos) + private string GetPropertyPath(IEnumerable propertyInfos) { List propertiesNames = []; @@ -239,9 +263,8 @@ private string GetPropertyToReplace(IEnumerable propertyInfos) return string.Join("/", propertiesNames); } - private string GetPropertyToReplace(MemberInfo propertyInfo) => - GetPropertyToReplace([propertyInfo]); - + private string GetPropertyPath(MemberInfo propertyInfo) => + GetPropertyPath([propertyInfo]); public string GetPath(Expression> expr) { @@ -253,19 +276,6 @@ public string GetPath(Expression> expr) { if (expression is MemberExpression memberExpression) { - var memberInfo = memberExpression.Member as PropertyInfo; - - if (memberInfo != null) - { - var jsonPropertyAttribute = memberInfo.GetCustomAttribute(true); - - var propertyName = jsonPropertyAttribute != null - ? jsonPropertyAttribute.PropertyName - : _namingStrategy.GetPropertyName(memberInfo.Name, false); - - stack.Push(propertyName); - } - expression = memberExpression.Expression; } else if (expression.NodeType == ExpressionType.ArrayIndex) @@ -311,27 +321,26 @@ public string GetPath(Expression> expr) } /// - /// Get the property name to replace. This only works for a single level of nesting. + /// Get the property name to replace. /// /// If you're looking for nesting call the respective method with a given path instead. /// The path to get the property. /// Returns the path of the property name. - internal PropertyInfo GetPropertyInfoToReplace(string path) + internal IReadOnlyList GetPropertyInfos(string path) { Type itemType = typeof(TItem); - PropertyInfo property = GetNestedPropertyInfo(itemType, path) ?? throw new InvalidOperationException($"The property {path} does not exist on {itemType.Name}"); - - return property; + return GetNestedPropertyInfos(itemType, path); } - private PropertyInfo? GetNestedPropertyInfo(Type type, string path) + private IReadOnlyList GetNestedPropertyInfos(Type type, string path) { // Split the path by '/' to get the individual property names string[] properties = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); Type currentType = type; - PropertyInfo? propertyInfo = null; + List propertyInfos = []; + PropertyInfo? currentPropertyInfo; // Iterate through each property in the path foreach (string propertyNameOrJsonProperty in properties) @@ -344,20 +353,22 @@ internal PropertyInfo GetPropertyInfoToReplace(string path) } // Search for the property prioritizing JsonPropertyAttribute - propertyInfo = FindProperty(currentType, propertyNameOrJsonProperty); + currentPropertyInfo = FindProperty(currentType, propertyNameOrJsonProperty); // If no matching property is found, return null - if (propertyInfo == null) + if (currentPropertyInfo == null) { - return null; + throw new InvalidOperationException($"The property {path} does not exist on {type.Name}"); } // Move to the next level of nesting - currentType = propertyInfo.PropertyType; + currentType = currentPropertyInfo.PropertyType; + propertyInfos.Add(currentPropertyInfo); + } // Return the final PropertyInfo - return propertyInfo; + return propertyInfos; } private PropertyInfo? FindProperty(Type type, string propertyNameOrJsonProperty) diff --git a/src/Microsoft.Azure.CosmosRepository/Extensions/ExpressionExtensions.cs b/src/Microsoft.Azure.CosmosRepository/Extensions/ExpressionExtensions.cs index 3e3cdeaff..f8f92035f 100644 --- a/src/Microsoft.Azure.CosmosRepository/Extensions/ExpressionExtensions.cs +++ b/src/Microsoft.Azure.CosmosRepository/Extensions/ExpressionExtensions.cs @@ -40,46 +40,46 @@ internal static Expression> Or( Expression> second) => first.Compose(second, Expression.Or); - internal static PropertyInfo GetPropertyInfo(this Expression> propertyLambda) - { - //var propertyInfos = GetPropertyInfosInternal(propertyLambda); + //internal static PropertyInfo GetPropertyInfo(this Expression> propertyLambda) + //{ + // //var propertyInfos = GetPropertyInfosInternal(propertyLambda); - //PropertyInfo propInfo = propertyInfos[propertyInfos.Count - 1]; + // //PropertyInfo propInfo = propertyInfos[propertyInfos.Count - 1]; - //ThrowArgumentExceptionIfPropertyIsNotFromSourceType(propertyLambda, propInfo); + // //ThrowArgumentExceptionIfPropertyIsNotFromSourceType(propertyLambda, propInfo); - //Intermediate Step Our Code - // Check if the body is a MemberExpression - if (propertyLambda.Body is MemberExpression memberExpression) - { - // Check if the MemberExpression refers to a Property - if (memberExpression.Member is PropertyInfo propInfo) - { - return propInfo; - } - else - { - throw new ArgumentException($"Expression '{propertyLambda}' refers to a field, not a property."); - } - } - - if (propertyLambda.Body.NodeType == ExpressionType.ArrayIndex && propertyLambda.Body is BinaryExpression binaryExpression && binaryExpression.Left is MemberExpression binaryMemberExpression) - { - // Check if the MemberExpression refers to a Property - if (binaryMemberExpression.Member is PropertyInfo propInfo) - { - return propInfo; - } - else - { - throw new ArgumentException($"Expression '{propertyLambda}' refers to a field, not a property."); - } - } - // Handle unexpected cases - throw new ArgumentException($"Expression '{propertyLambda}' is not a valid property expression."); - - // return propInfo; - } + // //Intermediate Step Our Code + // // Check if the body is a MemberExpression + // if (propertyLambda.Body is MemberExpression memberExpression) + // { + // // Check if the MemberExpression refers to a Property + // if (memberExpression.Member is PropertyInfo propInfo) + // { + // return propInfo; + // } + // else + // { + // throw new ArgumentException($"Expression '{propertyLambda}' refers to a field, not a property."); + // } + // } + + // if (propertyLambda.Body.NodeType == ExpressionType.ArrayIndex && propertyLambda.Body is BinaryExpression binaryExpression && binaryExpression.Left is MemberExpression binaryMemberExpression) + // { + // // Check if the MemberExpression refers to a Property + // if (binaryMemberExpression.Member is PropertyInfo propInfo) + // { + // return propInfo; + // } + // else + // { + // throw new ArgumentException($"Expression '{propertyLambda}' refers to a field, not a property."); + // } + // } + // // Handle unexpected cases + // throw new ArgumentException($"Expression '{propertyLambda}' is not a valid property expression."); + + // // return propInfo; + //} internal static IReadOnlyList GetPropertyInfos(this Expression> propertyLambda) { diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.Add.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.Add.cs new file mode 100644 index 000000000..c5f233c8c --- /dev/null +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.Add.cs @@ -0,0 +1,181 @@ +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Azure.CosmosRepositoryTests.Builders +{ + public partial class PatchOperationBuilderTests + { + + [Theory] + [MemberData(nameof(GetTestCases))] + public void AcknowledgeRepositorySerializationSettingForRetrievingAddPatchOperation(CosmosPropertyNamingPolicy? propertyNamingPolicy, + string expectedPropertyName) + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(propertyNamingPolicy); + + //Act + builder.Add(x => x.TestIntProperty, 1); + + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal($"/{expectedPropertyName}/-", operation.Path); + + } + + // Add Tests + [Fact] + public void AddGivenPropertyAddsCorrectPatchOperation() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Add(x => x.TestIntProperty, 200); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal("/testIntProperty/-", operation.Path); + } + + [Fact] + public void AddDeeplyNestedPropertyUsingExpressionAddsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Add(x => x.Address.Tags, new[] { "NewTag" }); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal("/address/tags/-", operation.Path); + } + + [Fact] + public void AddDeeplyNestedPropertyUsingPathAddsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Add("/address/tags/-", "NewTag"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal("/address/tags/-", operation.Path); + } + + [Fact] + public void AddNestedPropertyUsingExpressionAddsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Add(x => x.Address.ZipCode, 11111); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal("/address/zipCode/-", operation.Path); + } + + [Fact] + public void AddNestedPropertyUsingPathAddsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Add("/address/zipCode/-", 22222); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal("/address/zipCode/-", operation.Path); + } + + [Fact] + public void AddGivenNullValueViaExpressionAddsCorrectPatchOperation() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Add(x => x.TestProperty, null); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal("/thisIsTheName/-", operation.Path); + } + + [Fact] + public void AddEmptyArrayViaExpressionAddsCorrectPatchOperation() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Add(x => x.Address.Tags, []); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal("/address/tags/-", operation.Path); + } + + [Fact] + public void AddEmptyArrayViaPathAddsCorrectPatchOperation() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Add("/address/tags/-", []); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal("/address/tags/-", operation.Path); + } + + [Fact] + public void AddComplexObjectViaExpressionAddsCorrectPatchOperation() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + var newAddress = new Address { Street = "New Street", ZipCode = 12345 }; + + // Act + builder.Add(x => x.Address, newAddress); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal("/address/-", operation.Path); + } + + [Fact] + public void AddComplexObjectViaPathAddsCorrectPatchOperation() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + var newAddress = new Address { Street = "New Street", ZipCode = 12345 }; + + // Act + builder.Add("/address/-", newAddress); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Add, operation.OperationType); + Assert.Equal("/address/-", operation.Path); + } + } +} diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.Increment.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.Increment.cs new file mode 100644 index 000000000..0aeba0ad3 --- /dev/null +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.Increment.cs @@ -0,0 +1,144 @@ +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Azure.CosmosRepositoryTests.Builders +{ + public partial class PatchOperationBuilderTests + { + // Increment Tests + [Fact] + public void IncrementGivenPropertyIncreasesValueCorrectly() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Increment(x => x.TestIntProperty, 10); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal("/testIntProperty", operation.Path); + } + + [Fact] + public void IncrementDeeplyNestedPropertyUsingExpressionIncreasesValueCorrectly() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Increment(x => x.Address.ZipCode, 5); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal("/address/zipCode", operation.Path); + } + + [Fact] + public void IncrementDeeplyNestedPropertyUsingPathIncreasesValueCorrectly() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Increment("/address/zipCode", 5); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal("/address/zipCode", operation.Path); + } + + [Fact] + public void IncrementGivenPropertyWithNegativeValueDecreasesValueCorrectly() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Increment(x => x.TestIntProperty, -5); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal("/testIntProperty", operation.Path); + } + + [Fact] + public void IncrementGivenPropertyPathWithNegativeValueDecreasesValueCorrectly() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Increment("/address/zipCode", -10); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal("/address/zipCode", operation.Path); + } + + [Fact] + public void IncrementGivenPropertyWithDoubleValueIncreasesValueCorrectly() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Increment(x => x.TestIntProperty, 5.5); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal("/testIntProperty", operation.Path); + } + + [Fact] + public void IncrementGivenPropertyPathWithDoubleValueIncreasesValueCorrectly() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Increment("/address/zipCode", 3.5); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal("/address/zipCode", operation.Path); + } + + [Fact] + public void IncrementDeeplyNestedPropertyWithDoubleValueUsingExpressionIncreasesValueCorrectly() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Increment(x => x.Address.ZipCode, 2.5); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal("/address/zipCode", operation.Path); + } + + [Fact] + public void IncrementDeeplyNestedPropertyWithDoubleValueUsingPathIncreasesValueCorrectly() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Increment("/address/zipCode", 2.5); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Increment, operation.OperationType); + Assert.Equal("/address/zipCode", operation.Path); + } + } +} diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.Remove.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.Remove.cs new file mode 100644 index 000000000..98567339f --- /dev/null +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.Remove.cs @@ -0,0 +1,161 @@ +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Azure.CosmosRepositoryTests.Builders +{ + public partial class PatchOperationBuilderTests + { + // Remove Tests + [Fact] + public void RemoveGivenPropertyRemovesCorrectPatchOperation() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Remove(x => x.TestIntProperty); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/testIntProperty", operation.Path); + } + + [Fact] + public void RemoveDeeplyNestedPropertyListItemUsingPathRemovesCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Remove("/address/tags/0"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/address/tags/0", operation.Path); + } + + [Fact] + public void RemoveDeeplyNestedPropertyUsingPathRemovesCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Remove("/address/tags"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/address/tags", operation.Path); + } + + [Fact] + public void RemoveNestedPropertyUsingExpressionRemovesCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Remove(x => x.Address.ZipCode); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/address/zipCode", operation.Path); + } + + [Fact] + public void RemoveNestedPropertyUsingPathRemovesCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Remove("/address/zipCode"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/address/zipCode", operation.Path); + } + + [Fact] + public void RemoveNonNestedPropertyUsingPathRemovesCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Remove("/thisIsTheName"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/thisIsTheName", operation.Path); + } + + [Fact] + public void RemoveDeeplyNestedPropertyWithJsonPropertyUsingExpressionRemovesCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Remove(x => x.Address.Country.CountryCode); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/address/country/countryCode", operation.Path); + } + + [Fact] + public void RemovePropertyWithJsonPropertyUsingPathRemovesCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Remove("/testProperty"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/testProperty", operation.Path); + } + + [Fact] + public void RemoveComplexNestedPropertyUsingExpressionRemovesCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Remove(x => x.Address.Country); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/address/country", operation.Path); + } + + [Fact] + public void RemoveComplexNestedPropertyUsingPathRemovesCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Remove("/address/country"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Remove, operation.OperationType); + Assert.Equal("/address/country", operation.Path); + } + } +} + + diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.Replace.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.Replace.cs new file mode 100644 index 000000000..51587e27b --- /dev/null +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.Replace.cs @@ -0,0 +1,196 @@ +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Azure.CosmosRepositoryTests.Builders +{ + public partial class PatchOperationBuilderTests + { + + // Replace Tests + [Fact] + public void ReplaceGivenPropertyValueWithJsonAttributeSetsCorrectReplaceValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Replace(x => x.TestProperty, "100"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/thisIsTheName", operation.Path); + } + + [Fact] + public void ReplaceGivenPropertyWithNoAttributesSetsCorrectPatchOperation() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Replace(x => x.TestIntProperty, 50); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/testIntProperty", operation.Path); + } + + [Fact] + public void ReplaceGivenPropertyWithRequiredAttributeSetsCorrectPatchOperation() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Replace(x => x.TestProperty, "Test Value"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/testProperty", operation.Path); + } + + [Fact] + public void ReplaceGivenPropertyWithRequiredAndJsonAttributesSetsCorrectPatchOperation() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Replace(x => x.TestProperty, "Test Value"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/testProperty", operation.Path); + } + + [Fact] + public void ReplaceDeeplyNestedPropertyUsingExpressionReplacesCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Replace(x => x.Address.Country.CountryCode, "UK"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/address/country/countryCode", operation.Path); + } + + [Fact] + public void ReplaceDeeplyNestedPropertyUsingPathReplacesCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Replace("/address/country/countryCode", "UK"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/address/country/countryCode", operation.Path); + } + + [Fact] + public void ReplaceNestedPropertyUsingExpressionSetsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Replace(x => x.Address.Street, "123 Main St"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/address/street", operation.Path); + } + + [Fact] + public void ReplaceDeeplyNestedPropertyUsingExpressionSetsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Replace(x => x.Address.Country.Name, "United Kingdom"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/address/country/name", operation.Path); + } + + [Fact] + public void ReplaceDeeplyNestedPropertyListUsingExpressionSetsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Replace("/address/tags/0", "United Kingdom"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/address/tags/0", operation.Path); + } + + [Fact] + public void ReplaceNestedPropertyUsingPathSetsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Replace("/address/street", "456 Elm St"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/address/street", operation.Path); + } + + [Fact] + public void ReplaceComplexNestedObjectUsingExpressionSetsCorrectPatchOperation() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + var newCountry = new CountryInfo { Name = "Canada", CountryCode = "CA" }; + + // Act + builder.Replace(x => x.Address.Country, newCountry); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/address/country", operation.Path); + } + + + [Fact] + public void ReplaceComplexNestedObjectUsingPathSetsCorrectPatchOperation() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + var newCountry = new CountryInfo { Name = "Canada", CountryCode = "CA" }; + + // Act + builder.Replace("/address/country", newCountry); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Replace, operation.OperationType); + Assert.Equal("/address/country", operation.Path); + } + + } +} diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.Set.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.Set.cs new file mode 100644 index 000000000..b21c68d38 --- /dev/null +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.Set.cs @@ -0,0 +1,136 @@ +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Azure.CosmosRepositoryTests.Builders +{ + public partial class PatchOperationBuilderTests + { + // Set Tests + [Fact] + public void SetGivenPropertySetsCorrectPatchOperation() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Set(x => x.TestIntProperty, 1); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal("/testIntProperty", operation.Path); + } + + [Fact] + public void SetNestedPropertyUsingExpressionSetsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Set(x => x.Address.Street, "789 Oak St"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal("/address/street", operation.Path); + } + + [Fact] + public void SetNestedPropertyUsingPathSetsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Set("/address/street", "789 Oak St"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal("/address/street", operation.Path); + } + + [Fact] + public void SetDeeplyNestedPropertyUsingExpressionSetsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Set(x => x.Address.Country.Name, "Canada"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal("/address/country/name", operation.Path); + } + + [Fact] + public void SetDeeplyNestedPropertyUsingPathSetsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Set("/address/country/name", "Canada"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal("/address/country/name", operation.Path); + } + + [Fact] + public void SetListItemUsingPathSetsCorrectValue() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + // Act + builder.Set("/address/tags/1", "Updated Tag"); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal("/address/tags/1", operation.Path); + } + + [Fact] + public void SetComplexNestedObjectUsingExpressionSetsCorrectPatchOperation() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + var newCountry = new CountryInfo { Name = "Mexico", CountryCode = "MX" }; + + // Act + builder.Set(x => x.Address.Country, newCountry); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal("/address/country", operation.Path); + } + + [Fact] + public void SetComplexNestedObjectUsingPathSetsCorrectPatchOperation() + { + // Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(); + + var newCountry = new CountryInfo { Name = "Mexico", CountryCode = "MX" }; + + // Act + builder.Set("/address/country", newCountry); + + // Assert + PatchOperation operation = builder.PatchOperations[0]; + Assert.Equal(PatchOperationType.Set, operation.OperationType); + Assert.Equal("/address/country", operation.Path); + } + + + + } +} \ No newline at end of file diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.cs new file mode 100644 index 000000000..e6493463e --- /dev/null +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilder/PatchOperationBuilderTests.cs @@ -0,0 +1,94 @@ +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Azure.CosmosRepositoryTests.Builders; + +public class Item1 : Item +{ + [JsonProperty("thisIsTheName")] + public string TestProperty { get; set; } = null!; + + public int TestIntProperty { get; set; } +} + +public class RequiredItem : Item +{ + [Required] + public string TestProperty { get; set; } = null!; +} + +public class RequiredAndJsonItem : Item +{ + [Required] + [JsonProperty("testProperty")] + public string TestProperty { get; set; } = null!; +} + +public class Address +{ + public string Street { get; set; } = null!; + public int ZipCode { get; set; } + + public CountryInfo Country { get; set; } = new CountryInfo(); + + public string[] Tags { get; set; } = default!; + + public CountryInfo[] RelatedCountries { get; set; } = default!; +} + +public class CountryInfo +{ + public string Name { get; set; } = string.Empty; + public string CountryCode { get; set; } = string.Empty; +} + +public class Person : Item +{ + public string Name { get; set; } = null!; + public Address Address { get; set; } = null!; +} + +public partial class PatchOperationBuilderTests +{ + + [Theory] + [MemberData(nameof(GetTestCases))] + public void AcknowledgeRepositorySerializationSettingForRetrievingPatchOperation(CosmosPropertyNamingPolicy? propertyNamingPolicy, + string expectedPropertyName) + { + //Arrange + IPatchOperationBuilder builder = new PatchOperationBuilder(propertyNamingPolicy); + + //Act + builder.Set(x => x.TestIntProperty, 2); + builder.Replace(x => x.TestIntProperty, 3); + builder.Increment(x => x.TestIntProperty, 1); + builder.Remove(x => x.TestIntProperty); + + //Assert + var paths = builder.PatchOperations.Select(x => x.Path); + + // Check that all paths are equal to the expected value + Assert.All(paths, path => Assert.Equal($"/{expectedPropertyName}", path)); + } + + public static IEnumerable GetTestCases() + { + yield return new object?[] + { + CosmosPropertyNamingPolicy.CamelCase, + new CamelCaseNamingStrategy().GetPropertyName(nameof(Item1.TestIntProperty), false) + }; + yield return new object?[] + { + CosmosPropertyNamingPolicy.Default, + new DefaultNamingStrategy().GetPropertyName(nameof(Item1.TestIntProperty), false) + }; + yield return new object?[] + { + null, + new CamelCaseNamingStrategy().GetPropertyName(nameof(Item1.TestIntProperty), false) + }; + } + +} diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs deleted file mode 100644 index c784648ec..000000000 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/Builders/PatchOperationBuilderTests.cs +++ /dev/null @@ -1,445 +0,0 @@ -// Copyright (c) David Pine. All rights reserved. -// Licensed under the MIT License. - -namespace Microsoft.Azure.CosmosRepositoryTests.Builders; - -public class Item1 : Item -{ - [JsonProperty("thisIsTheName")] - public string TestProperty { get; set; } = null!; - - public int TestIntProperty { get; set; } -} - -public class RequiredItem : Item -{ - [Required] - public string TestProperty { get; set; } = null!; -} - -public class RequiredAndJsonItem : Item -{ - [Required] - [JsonProperty("testProperty")] - public string TestProperty { get; set; } = null!; -} - -public class Address -{ - public string Street { get; set; } = null!; - public int ZipCode { get; set; } - - public CountryInfo Country { get; set; } = new CountryInfo(); - - public string[] Tags { get; set; } = default!; -} - -public class CountryInfo -{ - public string Name { get; set; } = string.Empty; - public string CountryCode { get; set; } = string.Empty; -} - -public class Person : Item -{ - public string Name { get; set; } = null!; - public Address Address { get; set; } = null!; -} - -public class PatchOperationBuilderTests -{ - // Replace Tests - [Fact] - public void ReplaceGivenPropertyValueWithJsonAttributeSetsCorrectReplaceValue() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Replace(x => x.TestProperty, "100"); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal("/thisIsTheName", operation.Path); - } - - [Fact] - public void ReplaceGivenPropertyWithNoAttributesSetsCorrectPatchOperation() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Replace(x => x.TestIntProperty, 50); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal("/testIntProperty", operation.Path); - } - - [Fact] - public void ReplaceGivenPropertyWithRequiredAttributeSetsCorrectPatchOperation() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Replace(x => x.TestProperty, "Test Value"); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal("/testProperty", operation.Path); - } - - [Fact] - public void ReplaceGivenPropertyWithRequiredAndJsonAttributesSetsCorrectPatchOperation() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Replace(x => x.TestProperty, "Test Value"); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal("/testProperty", operation.Path); - } - - [Fact] - public void ReplaceDeeplyNestedPropertyUsingExpressionReplacesCorrectValue() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Replace(x => x.Address.Country.CountryCode, "UK"); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal("/address/country/countryCode", operation.Path); - } - - [Fact] - public void ReplaceDeeplyNestedPropertyUsingPathReplacesCorrectValue() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Replace("/address/country/countryCode", "UK"); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal("/address/country/countryCode", operation.Path); - } - - [Fact] - public void ReplaceNestedPropertyUsingExpressionSetsCorrectValue() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Replace(x => x.Address.Street, "123 Main St"); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal("/address/street", operation.Path); - } - - [Fact] - public void ReplaceDeeplyNestedPropertyUsingExpressionSetsCorrectValue() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Replace(x => x.Address.Country.Name, "United Kingdom"); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal("/address/country/name", operation.Path); - } - - [Fact] - public void ReplaceNestedPropertyUsingPathSetsCorrectValue() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Replace("/address/street", "456 Elm St"); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Replace, operation.OperationType); - Assert.Equal("/address/street", operation.Path); - } - - // Add Tests - [Fact] - public void AddGivenPropertyAddsCorrectPatchOperation() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Add(x => x.TestIntProperty, 200); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Add, operation.OperationType); - Assert.Equal("/testIntProperty/-", operation.Path); - } - - [Fact] - public void AddDeeplyNestedPropertyUsingExpressionAddsCorrectValue() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Add(x => x.Address.Tags, ["NewTag"]); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Add, operation.OperationType); - Assert.Equal("/address/tags/-", operation.Path); - } - - [Fact] - public void AddDeeplyNestedPropertyUsingPathAddsCorrectValue() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Add("/address/tags/-", "NewTag"); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Add, operation.OperationType); - Assert.Equal("/address/tags/-", operation.Path); - } - - [Fact] - public void AddNestedPropertyUsingExpressionAddsCorrectValue() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Add(x => x.Address.ZipCode, 11111); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Add, operation.OperationType); - Assert.Equal("/address/zipCode/-", operation.Path); - } - - [Fact] - public void AddNestedPropertyUsingPathAddsCorrectValue() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Add("/address/zipCode/-", 22222); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Add, operation.OperationType); - Assert.Equal("/address/zipCode/-", operation.Path); - } - - // Remove Tests - [Fact] - public void RemoveGivenPropertyRemovesCorrectPatchOperation() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Remove(x => x.TestIntProperty); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Remove, operation.OperationType); - Assert.Equal("/testIntProperty", operation.Path); - } - - [Fact] - public void RemoveDeeplyNestedPropertyListItemUsingExpressionRemovesCorrectValue() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Remove(x => x.Address.Tags[0]); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Remove, operation.OperationType); - Assert.Equal("/address/tags/0", operation.Path); - } - - [Fact] - public void RemoveDeeplyNestedPropertyListItemUsingPathRemovesCorrectValue() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Remove("/address/tags/0"); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Remove, operation.OperationType); - Assert.Equal("/address/tags/0", operation.Path); - } - - [Fact] - public void RemoveDeeplyNestedPropertyUsingPathRemovesCorrectValue() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Remove("/address/tags"); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Remove, operation.OperationType); - Assert.Equal("/address/tags", operation.Path); - } - - [Fact] - public void RemoveNestedPropertyUsingExpressionRemovesCorrectValue() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Remove(x => x.Address.ZipCode); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Remove, operation.OperationType); - Assert.Equal("/address/zipCode", operation.Path); - } - - [Fact] - public void RemoveNestedPropertyUsingPathRemovesCorrectValue() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Remove("/address/zipCode"); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Remove, operation.OperationType); - Assert.Equal("/address/zipCode", operation.Path); - } - - // Set Tests - [Fact] - public void SetGivenPropertySetsCorrectPatchOperation() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Set(x => x.TestIntProperty, 1); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Set, operation.OperationType); - Assert.Equal("/testIntProperty", operation.Path); - } - - [Fact] - public void SetNestedPropertyUsingExpressionSetsCorrectValue() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Set(x => x.Address.Street, "789 Oak St"); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Set, operation.OperationType); - Assert.Equal("/address/street", operation.Path); - } - - [Fact] - public void SetNestedPropertyUsingPathSetsCorrectValue() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Set("/address/street", "789 Oak St"); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Set, operation.OperationType); - Assert.Equal("/address/street", operation.Path); - } - - // Increment Tests - [Fact] - public void IncrementGivenPropertyIncreasesValueCorrectly() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Increment(x => x.TestIntProperty, 10); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Increment, operation.OperationType); - Assert.Equal("/testIntProperty", operation.Path); - } - - [Fact] - public void IncrementDeeplyNestedPropertyUsingExpressionIncreasesValueCorrectly() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Increment(x => x.Address.ZipCode, 5); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Increment, operation.OperationType); - Assert.Equal("/address/zipCode", operation.Path); - } - - [Fact] - public void IncrementDeeplyNestedPropertyUsingPathIncreasesValueCorrectly() - { - // Arrange - IPatchOperationBuilder builder = new PatchOperationBuilder(); - - // Act - builder.Increment("/address/zipCode", 5); - - // Assert - PatchOperation operation = builder.PatchOperations[0]; - Assert.Equal(PatchOperationType.Increment, operation.OperationType); - Assert.Equal("/address/zipCode", operation.Path); - } -} From f4677f9269e5aa06a0a4ab26c6bb60bf8e812bea Mon Sep 17 00:00:00 2001 From: Qualizorg Date: Fri, 16 Aug 2024 23:41:26 +0200 Subject: [PATCH 11/16] updated interface method summaries --- .../Builders/IPatchOperationBuilder.cs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Builders/IPatchOperationBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Builders/IPatchOperationBuilder.cs index f99dd7a2f..ea0aa87a8 100644 --- a/src/Microsoft.Azure.CosmosRepository/Builders/IPatchOperationBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Builders/IPatchOperationBuilder.cs @@ -20,8 +20,6 @@ public interface IPatchOperationBuilder where TItem : IItem /// The value to replace the property defined with. /// The type of the property that is been replaced. /// The same instance of - /// This currently only supports operations on properties on the root level of a JSON document, - /// replacing properties on a nested object for example are currently not supported. IPatchOperationBuilder Replace(Expression> expression, TValue? value); /// @@ -31,8 +29,6 @@ public interface IPatchOperationBuilder where TItem : IItem /// The value to replace the property defined with. /// The type of the property that is been replaced. /// The same instance of - /// This currently only supports operations on properties on the root level of a JSON document, - /// replacing properties on a nested object for example are currently not supported. IPatchOperationBuilder Replace(string path, TValue value); /// @@ -72,7 +68,7 @@ public interface IPatchOperationBuilder where TItem : IItem IPatchOperationBuilder Add(string path, TValue? value); /// - /// Allows a property of an to be removed with the value provided + /// Allows a property of an to be removed /// /// The type of the property that is being removed. /// The expression to define which property to operate on. @@ -80,7 +76,7 @@ public interface IPatchOperationBuilder where TItem : IItem IPatchOperationBuilder Remove(Expression> expression); /// - /// Allows a property of an to be added with the value provided + /// Allows a property of an to be removed /// /// Target location reference. /// The same instance of @@ -95,16 +91,16 @@ public interface IPatchOperationBuilder where TItem : IItem IPatchOperationBuilder Increment(string path, long value); /// - /// Allows a property of an to be added with the value provided + /// Allows a property of an to be incremented with the value provided /// - /// The type of the property that is being added. + /// The type of the property that is meant to be incremented. /// The expression to define which property to operate on. /// The value to add to the document /// The same instance of IPatchOperationBuilder Increment(Expression> expression, double value); /// - /// Allows a property of an to be incremented with the value provided. + /// Allows a property of an to be incremented with the value provided /// /// Target location reference. /// The value to increment. @@ -112,9 +108,9 @@ public interface IPatchOperationBuilder where TItem : IItem IPatchOperationBuilder Increment(string path, double value); /// - /// Allows a property of an to be added with the value provided + /// Allows a property of an to be incremented with the value provided /// - /// The type of the property that is being added. + /// The type of the property that is meant to be incremented. /// The expression to define which property to operate on. /// The value to increment /// The same instance of From 6ec020e234837108551e7cd2363afcd3c5764f06 Mon Sep 17 00:00:00 2001 From: Qualizorg Date: Mon, 19 Aug 2024 16:23:50 +0200 Subject: [PATCH 12/16] Added ETag return at update patch operation --- .../Repositories/DefaultRepository.Update.cs | 8 +++++--- .../Repositories/IWriteOnlyRepository.cs | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs index 4de7db98f..c19d2d273 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs @@ -49,7 +49,7 @@ public async ValueTask> UpdateAsync( return updateTasks.Select(x => x.Result); } - public async ValueTask UpdateAsync(string id, + public async ValueTask UpdateAsync(string id, Action> builder, string? partitionKeyValue = null, string? etag = default, @@ -71,7 +71,9 @@ public async ValueTask UpdateAsync(string id, patchItemRequestOptions.IfMatchEtag = etag; } - await container.PatchItemAsync(id, new PartitionKey(partitionKeyValue), - patchOperationBuilder.PatchOperations, patchItemRequestOptions, cancellationToken); + var response = await container.PatchItemAsync(id, new PartitionKey(partitionKeyValue), + patchOperationBuilder.PatchOperations, patchItemRequestOptions, cancellationToken); + + return response.ETag; } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs index 3c0aa18ad..efca9a126 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs @@ -77,8 +77,8 @@ ValueTask> UpdateAsync( /// The that will define the update operations to perform. /// The cancellation token to use when making asynchronous operations. /// Indicate to set IfMatchEtag in the ItemRequestOptions in the underlying Cosmos call. This requires TItem to implement the IItemWithEtag interface. - /// A representing the asynchronous operation. - ValueTask UpdateAsync( + /// A representing the asynchronous operation with the etag as value. + ValueTask UpdateAsync( string id, Action> builder, string? partitionKeyValue = null, From 9658473cdfaba26e8bcb05193d3683c8dd90d1f3 Mon Sep 17 00:00:00 2001 From: Qualizorg Date: Tue, 20 Aug 2024 11:59:32 +0200 Subject: [PATCH 13/16] Updated with latest changes --- nuget-push-command.txt | 4 + ...t.Azure.CosmosRepository.AspNetCore.csproj | 9 +- .../Microsoft.Azure.CosmosRepository.csproj | 15 ++-- .../Repositories/IWriteOnlyRepository.cs | 48 ++++++++++- .../Repositories/InMemoryRepository.Update.cs | 8 +- .../Repositories/InMemoryRepository.cs | 82 +++++++++++++++++++ 6 files changed, 149 insertions(+), 17 deletions(-) create mode 100644 nuget-push-command.txt diff --git a/nuget-push-command.txt b/nuget-push-command.txt new file mode 100644 index 000000000..85c69a205 --- /dev/null +++ b/nuget-push-command.txt @@ -0,0 +1,4 @@ +dotnet nuget push .\src\Microsoft.Azure.CosmosRepository.AspNetCore\bin\Release\QV.IEvangelist.Azure.CosmosRepository.AspNetCore.0.0.5-sandboxed.nupkg --source "QualiviewPush" --api-key y3sjfi7g2d237wg3lh342pzezytayg4bsdoh5gza6fwxu3yvljza + + +dotnet nuget push .\src\Microsoft.Azure.CosmosRepository\bin\Release\QV.IEvangelist.Azure.CosmosRepository.0.0.5-sandboxed.nupkg --source "QualiviewPush" --api-key y3sjfi7g2d237wg3lh342pzezytayg4bsdoh5gza6fwxu3yvljza \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository.AspNetCore/Microsoft.Azure.CosmosRepository.AspNetCore.csproj b/src/Microsoft.Azure.CosmosRepository.AspNetCore/Microsoft.Azure.CosmosRepository.AspNetCore.csproj index 69def86b0..a488ef611 100644 --- a/src/Microsoft.Azure.CosmosRepository.AspNetCore/Microsoft.Azure.CosmosRepository.AspNetCore.csproj +++ b/src/Microsoft.Azure.CosmosRepository.AspNetCore/Microsoft.Azure.CosmosRepository.AspNetCore.csproj @@ -7,13 +7,8 @@ Copyright © IEvangelist. All rights reserved. Licensed under the MIT License. en-US $([System.DateTime]::Now.ToString(yyyyMMdd)) - $(ClientOfficialVersion) - $(ClientPreviewVersion) - nightly-$(CurrentDate) - preview - $(ClientVersion) - $(ClientVersion)-$(VersionSuffix) - $(ClientVersion) + + 0.0.5.0-sandboxed IEvangelist,Mumby0168 true IEvangelist Azure Cosmos DB Repository Client library diff --git a/src/Microsoft.Azure.CosmosRepository/Microsoft.Azure.CosmosRepository.csproj b/src/Microsoft.Azure.CosmosRepository/Microsoft.Azure.CosmosRepository.csproj index cff3f7544..8b34468da 100644 --- a/src/Microsoft.Azure.CosmosRepository/Microsoft.Azure.CosmosRepository.csproj +++ b/src/Microsoft.Azure.CosmosRepository/Microsoft.Azure.CosmosRepository.csproj @@ -9,13 +9,14 @@ Copyright © IEvangelist. All rights reserved. Licensed under the MIT License. en-US $([System.DateTime]::Now.ToString(yyyyMMdd)) - $(ClientOfficialVersion) - $(ClientPreviewVersion) - nightly-$(CurrentDate) - preview - $(ClientVersion) - $(ClientVersion)-$(VersionSuffix) - $(ClientVersion) + + + + + + + + 0.0.5.0-sandboxed IEvangelist true Microsoft.Azure.CosmosRepository diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs index efca9a126..62857b227 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs @@ -81,7 +81,53 @@ ValueTask> UpdateAsync( ValueTask UpdateAsync( string id, Action> builder, - string? partitionKeyValue = null, + string partitionKeyValue, + string? etag = default, + CancellationToken cancellationToken = default); + + /// + /// Updates the given cosmos item using the provided and supported patch operations. + /// + /// The string identifier. + /// The that will define the update operations to perform. + /// The cancellation token to use when making asynchronous operations. + /// Indicate to set IfMatchEtag in the ItemRequestOptions in the underlying Cosmos call. This requires TItem to implement the IItemWithEtag interface. + /// A representing the asynchronous operation with the etag as value. + ValueTask UpdateAsync( + string id, + Action> builder, + string? etag = default, + CancellationToken cancellationToken = default); + + /// + /// Updates the given cosmos item using the provided and supported patch operations. + /// + /// The string identifier. + /// The partition key values if different than the . + /// The that will define the update operations to perform. + /// The cancellation token to use when making asynchronous operations. + /// Indicate to set IfMatchEtag in the ItemRequestOptions in the underlying Cosmos call. This requires TItem to implement the IItemWithEtag interface. + /// A representing the asynchronous operation with the etag as value. + ValueTask UpdateAsync( + string id, + Action> builder, + IEnumerable partitionKeyValues, + string? etag = default, + CancellationToken cancellationToken = default); + + /// + /// Updates the given cosmos item using the provided and supported patch operations. + /// + /// The string identifier. + /// The partition key if different than the . + /// The that will define the update operations to perform. + /// The cancellation token to use when making asynchronous operations. + /// Indicate to set IfMatchEtag in the ItemRequestOptions in the underlying Cosmos call. This requires TItem to implement the IItemWithEtag interface. + /// A representing the asynchronous operation with the etag as value. + ValueTask UpdateAsync( + string id, + Action> builder, + PartitionKey partitionKey, string? etag = default, CancellationToken cancellationToken = default); diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.Update.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.Update.cs index 26e35ecf7..c9af63811 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.Update.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.Update.cs @@ -68,7 +68,7 @@ public async ValueTask> UpdateAsync(IEnumerable values } /// - public async ValueTask UpdateAsync(string id, + public async ValueTask UpdateAsync(string id, Action> builder, string? partitionKeyValue = null, string? etag = default, @@ -131,9 +131,13 @@ public async ValueTask UpdateAsync(string id, ConcurrentDictionary items = InMemoryStorage.GetDictionary(); - items[id] = SerializeItem(item!, Guid.NewGuid().ToString(), CurrentTs); + var newETag = Guid.NewGuid().ToString(); + + items[id] = SerializeItem(item!, newETag, CurrentTs); Changes?.Invoke(new ChangeFeedItemArgs(DeserializeItem(items[id]))); + + return newETag; } private void MismatchedEtags() => diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.cs index 8d9d2366f..1ab42dadf 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.cs @@ -22,4 +22,86 @@ public InMemoryRepository(ISpecificationEvaluator specificationEvaluator) => private void NotFound() => throw new CosmosException(string.Empty, HttpStatusCode.NotFound, 0, string.Empty, 0); private void Conflict() => throw new CosmosException(string.Empty, HttpStatusCode.Conflict, 0, string.Empty, 0); + + public ValueTask TryGetAsync(string id, IEnumerable partitionKeyValues, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask TryGetAsync(string id, PartitionKey partitionKey, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask GetAsync(string id, IEnumerable partitionKeyValues, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask> GetAsync(PartitionKey partitionKey, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask> GetAsync(PartitionKey partitionKey, Expression> predicate, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask ExistsAsync(string id, IEnumerable partitionKeyValues, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask ExistsAsync(Expression> predicate, PartitionKey partitionKey, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask CountAsync(Expression> predicate, IEnumerable partitionKeyValues, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask CountAsync(Expression> predicate, PartitionKey partitionKey, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask CountAsync(PartitionKey partitionKey, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask> PageAsync(PartitionKey partitionKey, Expression>? predicate = null, int pageNumber = 1, int pageSize = 25, bool returnTotal = false, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask> PageAsync(PartitionKey partitionKey, Expression>? predicate = null, int pageSize = 25, string? continuationToken = null, bool returnTotal = false, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask UpdateAsync(string id, Action> builder, string? etag = null, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask UpdateAsync(string id, Action> builder, IEnumerable partitionKeyValues, string? etag = null, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask UpdateAsync(string id, Action> builder, PartitionKey partitionKey, string? etag = null, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask DeleteAsync(string id, IEnumerable partitionKeyValues, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + } \ No newline at end of file From 201e01334a9640670766ee4a03e56135eada1276 Mon Sep 17 00:00:00 2001 From: Qualizorg Date: Tue, 20 Aug 2024 12:08:06 +0200 Subject: [PATCH 14/16] deleted api key --- nuget-push-command.txt | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 nuget-push-command.txt diff --git a/nuget-push-command.txt b/nuget-push-command.txt deleted file mode 100644 index 85c69a205..000000000 --- a/nuget-push-command.txt +++ /dev/null @@ -1,4 +0,0 @@ -dotnet nuget push .\src\Microsoft.Azure.CosmosRepository.AspNetCore\bin\Release\QV.IEvangelist.Azure.CosmosRepository.AspNetCore.0.0.5-sandboxed.nupkg --source "QualiviewPush" --api-key y3sjfi7g2d237wg3lh342pzezytayg4bsdoh5gza6fwxu3yvljza - - -dotnet nuget push .\src\Microsoft.Azure.CosmosRepository\bin\Release\QV.IEvangelist.Azure.CosmosRepository.0.0.5-sandboxed.nupkg --source "QualiviewPush" --api-key y3sjfi7g2d237wg3lh342pzezytayg4bsdoh5gza6fwxu3yvljza \ No newline at end of file From 8b004a197476cc98a6214fd3215b0001d97c1b6f Mon Sep 17 00:00:00 2001 From: Qualizorg Date: Tue, 20 Aug 2024 12:19:52 +0200 Subject: [PATCH 15/16] restored --- nuget-push-command.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 nuget-push-command.txt diff --git a/nuget-push-command.txt b/nuget-push-command.txt new file mode 100644 index 000000000..85c69a205 --- /dev/null +++ b/nuget-push-command.txt @@ -0,0 +1,4 @@ +dotnet nuget push .\src\Microsoft.Azure.CosmosRepository.AspNetCore\bin\Release\QV.IEvangelist.Azure.CosmosRepository.AspNetCore.0.0.5-sandboxed.nupkg --source "QualiviewPush" --api-key y3sjfi7g2d237wg3lh342pzezytayg4bsdoh5gza6fwxu3yvljza + + +dotnet nuget push .\src\Microsoft.Azure.CosmosRepository\bin\Release\QV.IEvangelist.Azure.CosmosRepository.0.0.5-sandboxed.nupkg --source "QualiviewPush" --api-key y3sjfi7g2d237wg3lh342pzezytayg4bsdoh5gza6fwxu3yvljza \ No newline at end of file From 9f2573dfc263bc070a100c2402ea802238946e13 Mon Sep 17 00:00:00 2001 From: Qualizorg <118535493+Qualizorg@users.noreply.github.com> Date: Tue, 20 Aug 2024 12:27:12 +0200 Subject: [PATCH 16/16] Delete nuget-push-command.txt --- nuget-push-command.txt | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 nuget-push-command.txt diff --git a/nuget-push-command.txt b/nuget-push-command.txt deleted file mode 100644 index 85c69a205..000000000 --- a/nuget-push-command.txt +++ /dev/null @@ -1,4 +0,0 @@ -dotnet nuget push .\src\Microsoft.Azure.CosmosRepository.AspNetCore\bin\Release\QV.IEvangelist.Azure.CosmosRepository.AspNetCore.0.0.5-sandboxed.nupkg --source "QualiviewPush" --api-key y3sjfi7g2d237wg3lh342pzezytayg4bsdoh5gza6fwxu3yvljza - - -dotnet nuget push .\src\Microsoft.Azure.CosmosRepository\bin\Release\QV.IEvangelist.Azure.CosmosRepository.0.0.5-sandboxed.nupkg --source "QualiviewPush" --api-key y3sjfi7g2d237wg3lh342pzezytayg4bsdoh5gza6fwxu3yvljza \ No newline at end of file