Skip to content

Commit 03fe196

Browse files
committed
Add more unit tests for value converters
1 parent ec0cdc3 commit 03fe196

File tree

10 files changed

+215
-73
lines changed

10 files changed

+215
-73
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Cocona.Builder;
2+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import;
3+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Commands;
4+
using NSubstitute;
5+
using System.Reflection;
6+
namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Import;
7+
public class CoconaAppExtensionsTests
8+
{
9+
[Fact]
10+
public void GivenACoconaCommandBuilder_WhenImportFeatureIsUsed_ThenItShouldAddImportCommand()
11+
{
12+
// Arrange
13+
var commandBuilder = Substitute.For<ICoconaCommandsBuilder>();
14+
// Act
15+
commandBuilder.UseImportFeature();
16+
// Assert
17+
commandBuilder.Received(1).Add(Arg.Is<TypeCommandDataSource>(t => typeof(TypeCommandDataSource).GetField("_type", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(t) == typeof(ImportCommands)));
18+
19+
}
20+
}

src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/ValueConverters/DataverseValueConverterTests.cs

Lines changed: 100 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,15 @@
55
using Microsoft.Xrm.Sdk.Metadata;
66
using NSubstitute;
77
using Shouldly;
8+
using System.Linq.Expressions;
89
using System.Web;
910

1011
namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Import.ValueConverters;
1112
public class DataverseValueConverterTests
1213
{
1314
private readonly IMainConverter mainConverter = Substitute.For<IMainConverter>();
1415
private readonly DataverseValueConverter converter;
15-
public static TheoryData<AttributeMetadata, Field, object> TestData => new()
16-
{
17-
{
18-
FakeAttributeMetadataBuilder.New()
19-
.WithLogicalName("firstname")
20-
.Build<StringAttributeMetadata>(),
21-
new() {Name ="firstname",Value = "Root &lt;&quot;&quot;&gt; ''é&amp;&amp;@ \\ /" },
22-
"Root <\"\"> ''é&&@ \\ /" },
23-
{
24-
FakeAttributeMetadataBuilder
25-
.New()
26-
.WithLogicalName("customertypecode")
27-
.Build<PicklistAttributeMetadata>(),
28-
new() { Name = "customertypecode", Value = "1" },
29-
new OptionSetValue(1)
30-
}
31-
};
16+
3217
public DataverseValueConverterTests()
3318
{
3419
converter = new DataverseValueConverter(mainConverter);
@@ -37,55 +22,123 @@ public DataverseValueConverterTests()
3722
[Fact]
3823
public void GivenAStringAttributeAndAField_WhenConverted_ThenItShouldConvertProperly()
3924
{
40-
// Arrange
41-
var attributeMD =
42-
FakeAttributeMetadataBuilder.New()
43-
.WithLogicalName("firstname")
44-
.Build<StringAttributeMetadata>();
45-
var field = new Field() { Name = attributeMD.LogicalName, Value = "Root &lt;&quot;&quot;&gt; ''é&amp;&amp;@ \\ /" };
46-
// Act
47-
var result = converter.Convert(attributeMD, field);
48-
// Assert
49-
result.ShouldBe(HttpUtility.HtmlDecode(field.Value));
25+
var value = "Root \"\" ''é&&@ \\ /";
26+
var encodedValue = HttpUtility.HtmlEncode(value);
27+
RunTest<string, StringAttributeMetadata>(value, encodedValue);
5028
}
5129
[Fact]
5230
public void GivenAPicklistAttributeAndAField_WhenConverted_ThenItShouldConvertProperly()
5331
{
54-
// Arrange
5532
int expectedValue = 1;
33+
RunTest<OptionSetValue, PicklistAttributeMetadata>(new OptionSetValue(expectedValue), expectedValue.ToString());
34+
}
35+
[Fact]
36+
public void GivenAIntegerAttributeAndAField_WhenConverted_ThenItShouldConvertProperly()
37+
{
38+
RunTest<int?, IntegerAttributeMetadata>(1);
39+
}
40+
[Fact]
41+
public void GivenABooleanAttributeAndAField_WhenConverted_ThenItShouldConvertProperly()
42+
{
43+
RunTest<bool?, BooleanAttributeMetadata>(true);
44+
}
45+
[Fact]
46+
public void GivenAMoneyAttributeAndAField_WhenConverted_ThenItShouldConvertProperly()
47+
{
48+
var moneyValue = 123.4m;
49+
RunTest<Money, MoneyAttributeMetadata>(new Money(moneyValue), moneyValue.ToString());
50+
}
51+
[Fact]
52+
public void GivenAGuidAttributeAndAField_WhenConverted_ThenItShouldConvertProperly()
53+
{
54+
var value = Guid.NewGuid();
55+
RunTest<Guid?, UniqueIdentifierAttributeMetadata>(value, value.ToString());
56+
}
57+
[Fact]
58+
public void GivenADatetimeAttributeAndAField_WhenConverted_ThenItShouldConvertProperly()
59+
{
60+
var value = DateTime.UtcNow;
61+
RunTest<DateTime?, DateTimeAttributeMetadata>(value, value.ToString("o"));
62+
}
63+
[Fact]
64+
public void GivenADecimalAttributeAndAField_WhenConverted_ThenItShouldConvertProperly()
65+
{
66+
var value = 123.4m;
67+
RunTest<decimal?, DecimalAttributeMetadata>(value);
68+
}
69+
[Fact]
70+
public void GivenADoubleAttributeAndAField_WhenConverted_ThenItShouldConvertProperly()
71+
{
72+
var value = 123.4;
73+
RunTest<double?, DoubleAttributeMetadata>(value);
74+
}
75+
[Fact]
76+
public void GivenALookupAttributeAndAField_WhenConverted_ThenItShouldConvertProperly()
77+
{
78+
var value = new EntityReference { Id = Guid.NewGuid(), LogicalName = "randomentity" };
79+
RunLookupTest(value);
80+
}
81+
[Fact]
82+
public void GivenAnEmptyValueRegardlessOfAttributeType_WhenConverted_ThenItShouldReturnNull()
83+
{
84+
//Arrange
85+
var field = new Field() { Name = "test", Value = string.Empty };
86+
//Act
87+
var result = converter.Convert(new AttributeMetadata(), field);
88+
//Assert
89+
result.ShouldBeNull();
90+
}
91+
[Fact]
92+
public void GivenAnUnsupportedAttributeMetadata_WhenConverted_ThenItShouldThrowProperException()
93+
{
94+
//Arrange
95+
var field = new Field() { Name = "test", Value = "125462" };
96+
var amd = new MemoAttributeMetadata();
97+
//Act
98+
Action act = () => converter.Convert(amd, field);
99+
//Assert
100+
act.ShouldThrow<NotImplementedException>()
101+
.Message.ShouldBe($"{amd.AttributeType.Value} is not implemented.");
102+
}
103+
private void RunTest<T, TMD>(T expectedValue, string fieldValue = null) where TMD : AttributeMetadata, new()
104+
{
105+
// Arrange
56106
var attributeMD =
57107
FakeAttributeMetadataBuilder.New()
58-
.WithLogicalName("customertypecode")
59-
.Build<PicklistAttributeMetadata>();
60-
var field = new Field() { Name = attributeMD.LogicalName, Value = expectedValue.ToString() };
61-
mainConverter.Convert<OptionSetValue>(field.Value)
62-
.Returns(new OptionSetValue(expectedValue));
108+
.WithLogicalName("randomfield")
109+
.Build<TMD>();
110+
var field = new Field() { Name = attributeMD.LogicalName, Value = fieldValue ?? expectedValue.ToString() };
111+
mainConverter.Convert<T>(field.Value)
112+
.Returns(expectedValue);
63113
// Act
64114
var result = converter.Convert(attributeMD, field);
65115
// Assert
66-
result.ShouldBeOfType<OptionSetValue>();
67-
((OptionSetValue)result).Value.ShouldBe(expectedValue);
116+
result.ShouldBe(expectedValue);
68117
mainConverter.Received(1)
69-
.Convert<OptionSetValue>(field.Value);
118+
.Convert<T>(field.Value);
70119
}
71-
[Fact]
72-
public void GivenAIntegerAttributeAndAField_WhenConverted_ThenItShouldConvertProperly()
120+
private void RunLookupTest(EntityReference reference)
73121
{
74122
// Arrange
75-
int expectedValue = 1;
123+
Expression<Predicate<Dictionary<string, string>>> extraPropertiesPredicate = d =>
124+
d.ContainsKey("lookuptype") &&
125+
d["lookuptype"] == reference.LogicalName;
126+
76127
var attributeMD =
77128
FakeAttributeMetadataBuilder.New()
78-
.WithLogicalName("customertypecode")
79-
.Build<IntegerAttributeMetadata>();
80-
var field = new Field() { Name = attributeMD.LogicalName, Value = expectedValue.ToString() };
81-
mainConverter.Convert<int?>(field.Value)
82-
.Returns(expectedValue);
129+
.WithLogicalName("randomfield")
130+
.Build<LookupAttributeMetadata>();
131+
var field = new Field() { Name = attributeMD.LogicalName, Value = reference.Id.ToString(), Lookupentity = reference.LogicalName };
132+
mainConverter.Convert<EntityReference>(
133+
field.Value,
134+
Arg.Is(extraPropertiesPredicate)
135+
)
136+
.Returns(reference);
83137
// Act
84138
var result = converter.Convert(attributeMD, field);
85139
// Assert
86-
result.ShouldBeOfType<int>();
87-
((int?)result).ShouldBe(expectedValue);
140+
result.ShouldBe(reference);
88141
mainConverter.Received(1)
89-
.Convert<int?>(field.Value);
142+
.Convert<EntityReference>(field.Value, Arg.Is(extraPropertiesPredicate));
90143
}
91144
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.ValueConverters;
2+
using Shouldly;
3+
using System.Web;
4+
5+
namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Import.ValueConverters;
6+
public class EncodingStringValueConverterTests
7+
{
8+
private readonly EncodingStringValueConverter converter = new EncodingStringValueConverter();
9+
[Fact]
10+
public void GivenAnEncodedString_WhenItIsConverted_ThenItShouldDecodeProperly()
11+
{
12+
// Arrange
13+
var expectedValue = "Root \"\" ''é&&@ \\ /";
14+
// Act
15+
var result = converter.Convert(HttpUtility.HtmlEncode(expectedValue), null);
16+
// Assert
17+
result.ShouldBe(expectedValue);
18+
}
19+
[Fact]
20+
public void GivenADecodedString_WhenItIsConverted_ThenItShouldReturnSameString()
21+
{
22+
// Arrange
23+
var expectedValue = "Root \"\" ''é&&@ \\ /";
24+
// Act
25+
var result = converter.Convert(expectedValue, null);
26+
// Assert
27+
result.ShouldBe(expectedValue);
28+
}
29+
}

src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/ValueConverters/FakeValueConverter.cs

Lines changed: 0 additions & 13 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.ValueConverters;
2+
using NSubstitute;
3+
using Shouldly;
4+
using System.Web;
5+
6+
namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Import.ValueConverters;
7+
public class ReflectionMainConverterTests
8+
{
9+
[Fact]
10+
public void GivenAValueConverter_WhenConvertIsCalled_ThenItShouldReturnConvertedValue()
11+
{
12+
// Arrange
13+
var valueConverterTypes = new List<Type> { typeof(EncodingStringValueConverter) };
14+
var types = Substitute.For<IEnumerable<Type>>();
15+
types.GetEnumerator().Returns(valueConverterTypes.GetEnumerator());
16+
var mainConverter = new ReflectionMainConverter(types);
17+
var expectedValue = "Root \"\" ''é&&@ \\ /";
18+
19+
// Act
20+
var result = mainConverter.Convert<string>(HttpUtility.HtmlEncode(expectedValue));
21+
22+
// Assert
23+
result.ShouldBe(expectedValue);
24+
types.Received(1).GetEnumerator();
25+
}
26+
[Fact]
27+
public void GivenAValueConverter_WhenConvertIsCalledTwiceForSameConversion_ThenItShouldCacheThe2ndTimeReturnConvertedValue()
28+
{
29+
// Arrange
30+
var valueConverterTypes = new List<Type> { typeof(EncodingStringValueConverter) };
31+
var types = Substitute.For<IEnumerable<Type>>();
32+
types.GetEnumerator().Returns(valueConverterTypes.GetEnumerator());
33+
var mainConverter = new ReflectionMainConverter(types);
34+
var expectedValue = "Root \"\" ''é&&@ \\ /";
35+
36+
// Act
37+
var result = mainConverter.Convert<string>(HttpUtility.HtmlEncode(expectedValue));
38+
result = mainConverter.Convert<string>(HttpUtility.HtmlEncode(expectedValue));
39+
40+
// Assert
41+
result.ShouldBe(expectedValue);
42+
types.Received(1).GetEnumerator();
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
using Cocona;
2+
using Cocona.Builder;
23
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Commands;
34

4-
namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import
5+
namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import;
6+
7+
public static class CoconaAppExtensions
58
{
6-
public static class CoconaAppExtensions
9+
public static ICoconaCommandsBuilder UseImportFeature(this ICoconaCommandsBuilder app)
710
{
8-
public static CoconaApp UseImportFeature(this CoconaApp app)
9-
{
10-
app.AddCommands<ImportCommands>();
11-
return app;
12-
}
11+
app.AddCommands<ImportCommands>();
12+
return app;
1313
}
1414
}

src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/IServiceCollectionExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ public static IServiceCollection AddImportFeature(this IServiceCollection servic
1717

1818
return services.RegisterFromReflection<IFieldSchemaValidationRule>()
1919
.RegisterFromReflection<IRelationshipSchemaValidationRule>()
20-
.AddSingleton<IMainConverter, MainValueConverter>(_ =>
20+
.AddSingleton<IMainConverter, ReflectionMainConverter>(_ =>
2121
{
2222
var valueConverterTypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => !t.IsAbstract &&
2323
!t.IsInterface && t.BaseType != null && t.BaseType.IsConstructedGenericType && t.BaseType.GetGenericTypeDefinition() == typeof(BaseValueConverter<>)).ToList();
24-
return new MainValueConverter(valueConverterTypes);
24+
return new ReflectionMainConverter(valueConverterTypes);
2525
})
2626
.AddSingleton<IDataverseValueConverter, DataverseValueConverter>()
2727
.AddTransient<IValidator<ImportSchema>, SchemaValidator>()

src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/ValueConverters/DataverseValueConverter.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Model;
22
using Microsoft.Xrm.Sdk;
33
using Microsoft.Xrm.Sdk.Metadata;
4-
using System.Web;
54

65
namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import.ValueConverters;
76

@@ -25,7 +24,7 @@ public object Convert(AttributeMetadata attributeMetadata, Field field)
2524
switch (attributeMetadata.AttributeType.Value)
2625
{
2726
case AttributeTypeCode.String:
28-
return HttpUtility.HtmlDecode(value);
27+
return _mainConverter.Convert<string>(value);
2928
case AttributeTypeCode.Picklist:
3029
case AttributeTypeCode.State:
3130
case AttributeTypeCode.Status:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Web;
2+
3+
namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import.ValueConverters;
4+
public class EncodingStringValueConverter : BaseValueConverter<string>
5+
{
6+
protected override string ConvertValue(string value, Dictionary<string, string> ExtraProperties)
7+
{
8+
return HttpUtility.HtmlDecode(value);
9+
}
10+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11

22

33
namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import.ValueConverters;
4-
public class MainValueConverter : IMainConverter
4+
public class ReflectionMainConverter : IMainConverter
55
{
66
private readonly IEnumerable<Type> valueConvertertypes;
77
private readonly Dictionary<Type, IValueConverter> cachedConverters = new Dictionary<Type, IValueConverter>();
8-
public MainValueConverter(IEnumerable<Type> valueConvertertypes)
8+
public ReflectionMainConverter(IEnumerable<Type> valueConvertertypes)
99
{
1010
this.valueConvertertypes = valueConvertertypes;
1111
}

0 commit comments

Comments
 (0)