diff --git a/MFiles.VAF.Extensions.Tests/MFValueListItemSearchBuilderTests.cs b/MFiles.VAF.Extensions.Tests/MFValueListItemSearchBuilderTests.cs new file mode 100644 index 0000000..6d27064 --- /dev/null +++ b/MFiles.VAF.Extensions.Tests/MFValueListItemSearchBuilderTests.cs @@ -0,0 +1,357 @@ +using MFilesAPI; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MFiles.VAF.Extensions.Tests +{ + [TestClass] + public class MFValueListItemSearchBuilderTests + { + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void NullVaultThrows() + { + new MFValueListItemSearchBuilder(null, 1); + } + + [TestMethod] + public void ValidVaultDoesNotThrow() + { + new MFValueListItemSearchBuilder(Mock.Of(), 1); + } + + [TestMethod] + public void ValueListIdSet() + { + Assert.AreEqual + ( + 123, + new MFValueListItemSearchBuilder(Mock.Of(), 123).ValueListId + ); + } + + [TestMethod] + public void SearchConditionsEmptyByDefault() + { + Assert.AreEqual + ( + 0, + new MFValueListItemSearchBuilder(Mock.Of(), 1).SearchConditions.Count + ); + } + + [TestMethod] + public void Name() + { + var searchBuilder = new MFValueListItemSearchBuilder(Mock.Of(), 1); + var ret = searchBuilder.Name("hello world"); + Assert.AreEqual(searchBuilder, ret); + var condition = searchBuilder.SearchConditions.Cast().FirstOrDefault(); + Assert.IsNotNull(condition); + Assert.AreEqual(MFExpressionType.MFExpressionTypePropertyValue, condition.Expression.Type); + Assert.AreEqual((int)MFValueListItemPropertyDef.MFValueListItemPropertyDefName, condition.Expression.DataPropertyValuePropertyDef); + Assert.AreEqual("hello world", condition.TypedValue.Value); + Assert.AreEqual(MFDataType.MFDatatypeText, condition.TypedValue.DataType); + Assert.AreEqual(MFConditionType.MFConditionTypeEqual, condition.ConditionType); + } + + [TestMethod] + public void Name_CustomConditionType() + { + var searchBuilder = new MFValueListItemSearchBuilder(Mock.Of(), 1); + var ret = searchBuilder.Name("hello", MFConditionType.MFConditionTypeStartsWith); + var condition = searchBuilder.SearchConditions.Cast().FirstOrDefault(); + Assert.IsNotNull(condition); + Assert.AreEqual(MFConditionType.MFConditionTypeStartsWith, condition.ConditionType); + } + + [TestMethod] + public void Deleted() + { + var searchBuilder = new MFValueListItemSearchBuilder(Mock.Of(), 1); + var ret = searchBuilder.Deleted(true); + Assert.AreEqual(searchBuilder, ret); + var condition = searchBuilder.SearchConditions.Cast().FirstOrDefault(); + Assert.IsNotNull(condition); + Assert.AreEqual(MFExpressionType.MFExpressionTypePropertyValue, condition.Expression.Type); + Assert.AreEqual((int)MFValueListItemPropertyDef.MFValueListItemPropertyDefDeleted, condition.Expression.DataPropertyValuePropertyDef); + Assert.AreEqual(true, condition.TypedValue.Value); + Assert.AreEqual(MFDataType.MFDatatypeBoolean, condition.TypedValue.DataType); + Assert.AreEqual(MFConditionType.MFConditionTypeEqual, condition.ConditionType); + } + + [TestMethod] + public void Deleted_CustomConditionType() + { + var searchBuilder = new MFValueListItemSearchBuilder(Mock.Of(), 1); + var ret = searchBuilder.Deleted(true, MFConditionType.MFConditionTypeNotEqual); + var condition = searchBuilder.SearchConditions.Cast().FirstOrDefault(); + Assert.IsNotNull(condition); + Assert.AreEqual(MFConditionType.MFConditionTypeNotEqual, condition.ConditionType); + } + + [TestMethod] + public void Owner() + { + var searchBuilder = new MFValueListItemSearchBuilder(Mock.Of(), 1); + var ret = searchBuilder.Owner(12345); + Assert.AreEqual(searchBuilder, ret); + var condition = searchBuilder.SearchConditions.Cast().FirstOrDefault(); + Assert.IsNotNull(condition); + Assert.AreEqual(MFExpressionType.MFExpressionTypePropertyValue, condition.Expression.Type); + Assert.AreEqual((int)MFValueListItemPropertyDef.MFValueListItemPropertyDefOwner, condition.Expression.DataPropertyValuePropertyDef); + Assert.AreEqual(MFDataType.MFDatatypeLookup, condition.TypedValue.DataType); + Assert.AreEqual(12345, condition.TypedValue.GetLookupID()); + Assert.AreEqual(MFConditionType.MFConditionTypeEqual, condition.ConditionType); + } + + [TestMethod] + public void Owner_CustomConditionType() + { + var searchBuilder = new MFValueListItemSearchBuilder(Mock.Of(), 1); + var ret = searchBuilder.Owner(12345, MFConditionType.MFConditionTypeNotEqual); + var condition = searchBuilder.SearchConditions.Cast().FirstOrDefault(); + Assert.IsNotNull(condition); + Assert.AreEqual(MFConditionType.MFConditionTypeNotEqual, condition.ConditionType); + } + + [TestMethod] + public void ExternalId() + { + var searchBuilder = new MFValueListItemSearchBuilder(Mock.Of(), 1); + var ret = searchBuilder.ExternalId("extid-11"); + Assert.AreEqual(searchBuilder, ret); + var condition = searchBuilder.SearchConditions.Cast().FirstOrDefault(); + Assert.IsNotNull(condition); + Assert.AreEqual(MFExpressionType.MFExpressionTypePropertyValue, condition.Expression.Type); + Assert.AreEqual((int)MFValueListItemPropertyDef.MFValueListItemPropertyDefExtID, condition.Expression.DataPropertyValuePropertyDef); + Assert.AreEqual("extid-11", condition.TypedValue.Value); + Assert.AreEqual(MFDataType.MFDatatypeText, condition.TypedValue.DataType); + Assert.AreEqual(MFConditionType.MFConditionTypeEqual, condition.ConditionType); + } + + [TestMethod] + public void ExternalId_CustomConditionType() + { + var searchBuilder = new MFValueListItemSearchBuilder(Mock.Of(), 1); + var ret = searchBuilder.ExternalId("extid-11", MFConditionType.MFConditionTypeNotEqual); + var condition = searchBuilder.SearchConditions.Cast().FirstOrDefault(); + Assert.IsNotNull(condition); + Assert.AreEqual(MFConditionType.MFConditionTypeNotEqual, condition.ConditionType); + } + + [TestMethod] + public void Find() + { + // Set up our mock objects. + var valueListItemOperationsMock = new Moq.Mock(); + valueListItemOperationsMock + .Setup(m => m.SearchForValueListItemsEx2 + ( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny() + )).Callback((int vl, SearchConditions c, bool u, MFExternalDBRefreshType rt, bool rcu, int pid, int mr) => + { + Assert.AreEqual(12345, vl); + Assert.AreEqual(1, c.Count); + Assert.AreEqual(false, u); + Assert.AreEqual(MFExternalDBRefreshType.MFExternalDBRefreshTypeNone, rt); + Assert.AreEqual(false, rcu); + Assert.AreEqual(-1, pid); + Assert.AreEqual(5000, mr); + }).Returns(Mock.Of()); + var vaultMock = new Moq.Mock(); + vaultMock + .SetupGet(m => m.ValueListItemOperations) + .Returns(valueListItemOperationsMock.Object); + + // Instantiate the builder and set something to check for. + var searchBuilder = new MFValueListItemSearchBuilder(vaultMock.Object, 12345); + searchBuilder.ExternalId("extid-11"); + + // Find. + var results = searchBuilder.Find(); + + // Ensure that we hit the underlying method.. + valueListItemOperationsMock + .Verify + ( + m => m.SearchForValueListItemsEx2 + ( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny() + ), + Times.Once() + ); + } + + public static IEnumerable FindParameters() + { + // First couple are different, rest are defaults. + yield return new object[] + { + 12345, + new SearchConditions(), + false, + MFExternalDBRefreshType.MFExternalDBRefreshTypeNone, + false, + -1, + 5000 + }; + + // Now let's vary some + yield return new object[] + { + 12345, + new SearchConditions(), + true, + MFExternalDBRefreshType.MFExternalDBRefreshTypeNone, + false, + -1, + 5000 + }; + yield return new object[] + { + 12345, + new SearchConditions(), + false, + MFExternalDBRefreshType.MFExternalDBRefreshTypeFull, + false, + -1, + 5000 + }; + yield return new object[] + { + 1, + new SearchConditions(), + false, + MFExternalDBRefreshType.MFExternalDBRefreshTypeNone, + false, + -1, + 5000 + }; + yield return new object[] + { + 12345, + new SearchConditions(), + false, + MFExternalDBRefreshType.MFExternalDBRefreshTypeNone, + true, + -1, + 5000 + }; + yield return new object[] + { + 12345, + new SearchConditions(), + false, + MFExternalDBRefreshType.MFExternalDBRefreshTypeNone, + false, + 123, + 5000 + }; + yield return new object[] + { + 12345, + new SearchConditions(), + false, + MFExternalDBRefreshType.MFExternalDBRefreshTypeNone, + false, + -1, + 1000 + }; + } + + [TestMethod] + [DynamicData(nameof(FindParameters), DynamicDataSourceType.Method)] + public void Find_CustomParameters + ( + int valueListId, + SearchConditions searchConditions, + bool updateFromServer, + MFExternalDBRefreshType refreshType, + bool replaceCurrentUserWithCallersIdentity, + int propertyDefId, + int maximumResults + ) + { + // Set up our mock objects. + var valueListItemOperationsMock = new Moq.Mock(); + valueListItemOperationsMock + .Setup(m => m.SearchForValueListItemsEx2 + ( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny() + )).Callback((int vl, SearchConditions c, bool u, MFExternalDBRefreshType rt, bool rcu, int pid, int mr) => + { + Assert.AreEqual(valueListId, vl); + Assert.AreEqual(searchConditions.Count, c.Count); // TODO: Check the contents. + Assert.AreEqual(updateFromServer, u); + Assert.AreEqual(refreshType, rt); + Assert.AreEqual(replaceCurrentUserWithCallersIdentity, rcu); + Assert.AreEqual(propertyDefId, pid); + Assert.AreEqual(maximumResults, mr); + }).Returns(Mock.Of()); + var vaultMock = new Moq.Mock(); + vaultMock + .SetupGet(m => m.ValueListItemOperations) + .Returns(valueListItemOperationsMock.Object); + + // Instantiate the builder. + var searchBuilder = new MFValueListItemSearchBuilder(vaultMock.Object, valueListId); + if(null != searchConditions) + { + foreach (var sc in searchConditions.Cast()) + searchBuilder.SearchConditions.Add(0, sc); + } + + // Find. + var results = searchBuilder.Find + ( + updateFromServer, + refreshType, + replaceCurrentUserWithCallersIdentity, + propertyDefId, + maximumResults + ); + + // Ensure that we hit the underlying method.. + valueListItemOperationsMock + .Verify + ( + m => m.SearchForValueListItemsEx2 + ( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny() + ), + Times.Once() + ); + } + + } +} diff --git a/MFiles.VAF.Extensions/MFValueListSearchBuilder.cs b/MFiles.VAF.Extensions/MFValueListSearchBuilder.cs new file mode 100644 index 0000000..e673040 --- /dev/null +++ b/MFiles.VAF.Extensions/MFValueListSearchBuilder.cs @@ -0,0 +1,190 @@ +using MFilesAPI; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MFiles.VAF.Extensions +{ + /// + /// Enables searching for value list items, similar to + /// how does for objects. + /// + public class MFValueListItemSearchBuilder + { + /// + /// The vault to use for searching. + /// + public Vault Vault { get; } + + /// + /// The search conditions. + /// + public SearchConditions SearchConditions { get; } + = new SearchConditions(); + + /// + /// The value list ID to search. + /// + public int ValueListId { get; } + + /// + /// Instantiates the search builder. + /// + /// The vault reference to use for searching. + /// The value list to search. + /// If is null. + public MFValueListItemSearchBuilder(Vault vault, int valueListId) + { + this.Vault = vault + ?? throw new ArgumentNullException(nameof(vault)); + this.ValueListId = valueListId; + } + + /// + /// Adds a "name" search condition. + /// + /// The name of the item to search for. + /// The type of comparison to do. + /// The current . + public MFValueListItemSearchBuilder Name(string name, MFConditionType conditionType = MFConditionType.MFConditionTypeEqual) + { + // Create the condition. + var condition = new SearchCondition(); + + // Set the expression. + condition.Expression.SetValueListItemExpression(MFValueListItemPropertyDef.MFValueListItemPropertyDefName, + MFParentChildBehavior.MFParentChildBehaviorNone); + + // Set the condition type. + condition.ConditionType = conditionType; + + // Set the value. + condition.TypedValue.SetValue(MFDataType.MFDatatypeText, name); + + // Add the condition. + this.SearchConditions.Add(0, condition); + + // Return itself for chaining. + return this; + } + + /// + /// Adds a "deleted" search condition. + /// + /// If then includes only deleted items, if then includes only non-deleted items. + /// The type of comparison to do. + /// The current . + public MFValueListItemSearchBuilder Deleted(bool deletedItems, MFConditionType conditionType = MFConditionType.MFConditionTypeEqual) + { + // Create the condition. + var condition = new SearchCondition(); + + // Set the expression. + condition.Expression.SetValueListItemExpression(MFValueListItemPropertyDef.MFValueListItemPropertyDefDeleted, + MFParentChildBehavior.MFParentChildBehaviorNone); + + // Set the condition type. + condition.ConditionType = conditionType; + + // Set the value. + condition.TypedValue.SetValue(MFDataType.MFDatatypeBoolean, deletedItems); + + // Add the condition. + this.SearchConditions.Add(0, condition); + + // Return itself for chaining. + return this; + } + + /// + /// Adds an "owner" search condition. + /// + /// The id of the owner value list item. + /// The type of comparison to do. + /// The current . + public MFValueListItemSearchBuilder Owner(int ownerId, MFConditionType conditionType = MFConditionType.MFConditionTypeEqual) + { + // Create the condition. + var condition = new SearchCondition(); + + // Set the expression. + condition.Expression.SetValueListItemExpression(MFValueListItemPropertyDef.MFValueListItemPropertyDefOwner, + MFParentChildBehavior.MFParentChildBehaviorNone); + + // Set the condition type. + condition.ConditionType = conditionType; + + // Set the value. + condition.TypedValue.SetValue(MFDataType.MFDatatypeLookup, ownerId); + + // Add the condition. + this.SearchConditions.Add(0, condition); + + // Return itself for chaining. + return this; + } + + /// + /// Adds a condition that filters to only items that match a given external ID, + /// + /// The external ID. + /// The type of comparison to do. + /// The current . + public MFValueListItemSearchBuilder ExternalId(string externalId, MFConditionType conditionType = MFConditionType.MFConditionTypeEqual) + { + // Create the condition. + var condition = new SearchCondition(); + + // Set the expression. + condition.Expression.SetValueListItemExpression(MFValueListItemPropertyDef.MFValueListItemPropertyDefExtID, + MFParentChildBehavior.MFParentChildBehaviorNone); + + // Set the condition type. + condition.ConditionType = conditionType; + + // Set the value. + condition.TypedValue.SetValue(MFDataType.MFDatatypeText, externalId); + + // Add the condition. + this.SearchConditions.Add(0, condition); + + // Return itself for chaining. + return this; + } + + /// + /// Executes the search. + /// + /// If this is set to "true", the search operation is always carried out on the server-side. + /// Specifies how, if at all, external value lists are updated for this operation. + /// + /// Specifies the property definition the filter of which should be applied to the list (-1 for none). + /// The maximum number of results to return. By default, no more than 5,000 results are allowed. + /// Any matching value list items. + /// Uses to search. + public ValueListItemSearchResults Find + ( + bool updateFromServer = false, + MFExternalDBRefreshType refreshType = MFExternalDBRefreshType.MFExternalDBRefreshTypeNone, + bool replaceCurrentUserWithCallersIdentity = false, + int propertyDefId = -1, + int maximumResults = 5000 + ) + { + // Delegate. + return this.Vault.ValueListItemOperations.SearchForValueListItemsEx2 + ( + this.ValueListId, + this.SearchConditions, + updateFromServer, + refreshType, + replaceCurrentUserWithCallersIdentity, + propertyDefId, + maximumResults + ); + } + + } +}