Skip to content

Commit ec0cdc3

Browse files
committed
ValueConvertion refactored and more unit tests to increase code coverage
1 parent 0cecfe2 commit ec0cdc3

22 files changed

+706
-99
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[*.cs]
2+
3+
# CS8603: Possible null reference return.
4+
dotnet_diagnostic.CS8603.severity = none
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using Microsoft.Xrm.Sdk.Metadata;
2+
3+
namespace Dataverse.ConfigurationMigrationTool.Console.Tests.FakeBuilders;
4+
public class FakeAttributeMetadataBuilder
5+
{
6+
private string LogicalName { get; set; }
7+
private FakeAttributeMetadataBuilder()
8+
{
9+
}
10+
public FakeAttributeMetadataBuilder WithLogicalName(string logicalName)
11+
{
12+
LogicalName = logicalName;
13+
return this;
14+
}
15+
public T Build<T>() where T : AttributeMetadata, new()
16+
{
17+
var attributeMetadata = new T
18+
{
19+
LogicalName = LogicalName
20+
};
21+
return attributeMetadata;
22+
}
23+
public static FakeAttributeMetadataBuilder New()
24+
{
25+
return new FakeAttributeMetadataBuilder();
26+
}
27+
}

src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/Validators/Rules/EntitySchemas/EntitySchemaValidatorTests.cs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,104 @@ namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Import.Val
1212
public class EntitySchemaValidatorTests
1313
{
1414
private readonly IMetadataService metadataService = Substitute.For<IMetadataService>();
15+
[Fact]
16+
public async Task GivenEntityDoesNotExist_WhenValidated_ThenShouldReturnError()
17+
{
18+
// Arrange
19+
metadataService.GetEntity("nonexistent").Returns((EntityMetadata)null);
20+
var validator = CreateValidator(null, null);
21+
22+
var schema = new EntitySchema { Name = "nonexistent" };
23+
24+
// Act
25+
var result = await validator.Validate(schema);
26+
27+
// Assert
28+
result.IsError.ShouldBeTrue();
29+
result.Failures.ShouldContain(f => f.Message == $"Entity {schema.Name} does not exist in target environment");
30+
}
31+
32+
[Fact]
33+
public async Task GivenFieldDoesNotExist_WhenValidated_ThenShouldReturnError()
34+
{
35+
// Arrange
36+
var contactMd = FakeMetadata.Contact;
37+
metadataService.GetEntity("contact").Returns(contactMd);
38+
var missingField = new FieldSchema { Name = "missing_field" };
39+
var schema = new EntitySchema { Name = "contact", Fields = new FieldsSchema { Field = new List<FieldSchema> { missingField } } };
40+
var validator = CreateValidator(null, null);
41+
42+
// Act
43+
var result = await validator.Validate(schema);
44+
45+
// Assert
46+
result.IsError.ShouldBeTrue();
47+
result.Failures.ShouldContain(f => f.Message == $"Attribute '{missingField.Name}' for Entity {contactMd.LogicalName} does not have exist in target environment");
48+
}
49+
50+
[Fact]
51+
public async Task GivenFieldRuleFails_WhenValidated_ThenShouldReturnError()
52+
{
53+
// Arrange
54+
var contactMd = FakeMetadata.Contact;
55+
metadataService.GetEntity("contact").Returns(contactMd);
56+
var fieldSchema = new FieldSchema { Name = contactMd.Attributes.First().LogicalName };
57+
var schema = new EntitySchema { Name = "contact", Fields = new FieldsSchema { Field = new List<FieldSchema> { fieldSchema } } };
58+
var failedRule = Substitute.For<IFieldSchemaValidationRule>();
59+
failedRule.Validate(Arg.Any<FieldSchema>(), Arg.Any<AttributeMetadata>())
60+
.Returns(RuleResult.Failure("Field rule failed"));
61+
var validator = CreateValidator([failedRule], null);
62+
63+
// Act
64+
var result = await validator.Validate(schema);
65+
66+
// Assert
67+
result.IsError.ShouldBeTrue();
68+
result.Failures.ShouldContain(f => f.Message == "Field rule failed");
69+
}
70+
71+
[Fact]
72+
public async Task GivenRelationshipDoesNotExist_WhenValidated_ThenShouldReturnError()
73+
{
74+
// Arrange
75+
var contactMd = FakeMetadata.Contact;
76+
metadataService.GetEntity("contact").Returns(contactMd);
77+
metadataService.GetRelationShipM2M("missing_relationship").Returns((ManyToManyRelationshipMetadata)null);
78+
var relationshipSchema = new RelationshipSchema { Name = "missing_relationship" };
79+
var schema = new EntitySchema { Name = "contact", Relationships = new RelationshipsSchema { Relationship = new List<RelationshipSchema> { relationshipSchema } } };
80+
var validator = CreateValidator(null, null);
81+
82+
// Act
83+
var result = await validator.Validate(schema);
84+
85+
// Assert
86+
result.IsError.ShouldBeTrue();
87+
result.Failures.ShouldContain(f => f.Message == $"ManyToMany Relationship Table {relationshipSchema.Name} does not exist in target environment or it's not a M2M relationship.");
88+
}
89+
90+
[Fact]
91+
public async Task GivenRelationshipRuleFails_WhenValidated_ThenShouldReturnError()
92+
{
93+
// Arrange
94+
var contactMd = FakeMetadata.Contact;
95+
metadataService.GetEntity("contact").Returns(contactMd);
96+
var m2m = contactMd.ManyToManyRelationships.FirstOrDefault();
97+
metadataService.GetRelationShipM2M(m2m.SchemaName).Returns(m2m);
98+
var relationshipSchema = new RelationshipSchema { Name = m2m.SchemaName };
99+
var schema = new EntitySchema { Name = "contact", Relationships = new RelationshipsSchema { Relationship = new List<RelationshipSchema> { relationshipSchema } } };
100+
var failedRule = Substitute.For<IRelationshipSchemaValidationRule>();
101+
failedRule.Validate(Arg.Any<string>(), Arg.Any<RelationshipSchema>(), Arg.Any<ManyToManyRelationshipMetadata>())
102+
.Returns(RuleResult.Failure("Relationship rule failed"));
103+
var validator = CreateValidator(null, new List<IRelationshipSchemaValidationRule> { failedRule });
104+
105+
// Act
106+
var result = await validator.Validate(schema);
107+
108+
// Assert
109+
result.IsError.ShouldBeTrue();
110+
result.Failures.ShouldContain(f => f.Message == "Relationship rule failed");
111+
}
112+
15113
[Fact]
16114
public async Task GivenAValidEntitySchema_WhenItIsValidated_ThenItShouldReturnSuccess()
17115
{
@@ -40,6 +138,7 @@ public async Task GivenAValidEntitySchema_WhenItIsValidated_ThenItShouldReturnSu
40138

41139

42140

141+
43142
private IValidator<EntitySchema> CreateValidator(
44143
IEnumerable<IFieldSchemaValidationRule> fieldSchemaRules,
45144
IEnumerable<IRelationshipSchemaValidationRule> relationshipSchemaRules)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Model;
2+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Validators;
3+
using Dataverse.ConfigurationMigrationTool.Console.Features.Shared;
4+
using NSubstitute;
5+
using Shouldly;
6+
7+
namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Import.Validators.Rules;
8+
public class SchemaValidatorTests
9+
{
10+
private readonly IValidator<EntitySchema> entityValidator = Substitute.For<IValidator<EntitySchema>>();
11+
private readonly IValidator<ImportSchema> validator;
12+
public SchemaValidatorTests()
13+
{
14+
validator = new SchemaValidator(entityValidator);
15+
}
16+
17+
[Fact]
18+
public async Task GivenValidSchema_WhenValidated_ThenShouldReturnSuccess()
19+
{
20+
// Arrange
21+
var schema = new ImportSchema
22+
{
23+
Entity = new List<EntitySchema>
24+
{
25+
FakeSchemas.Account,
26+
FakeSchemas.Opportunity,
27+
FakeSchemas.Contact
28+
}
29+
};
30+
entityValidator.Validate(Arg.Any<EntitySchema>()).Returns(new ValidationResult());
31+
// Act
32+
var result = await validator.Validate(schema);
33+
// Assert
34+
result.IsError.ShouldBeFalse();
35+
result.Failures.ShouldBeEmpty();
36+
}
37+
38+
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.ValueConverters;
2+
using Shouldly;
3+
4+
namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Import.ValueConverters;
5+
public class BooleanValueConverterTests
6+
{
7+
private readonly BooleanValueConverter converter = new BooleanValueConverter();
8+
9+
[Fact]
10+
public void GivenTrueString_WhenConverted_ThenShouldReturnTrue()
11+
{
12+
// Arrange
13+
var input = "true";
14+
// Act
15+
var result = converter.Convert(input) as bool?;
16+
// Assert
17+
result.Value.ShouldBeTrue();
18+
}
19+
[Fact]
20+
public void GivenFalseString_WhenConverted_ThenShouldReturnFalse()
21+
{
22+
// Arrange
23+
var input = "false";
24+
// Act
25+
var result = converter.Convert(input) as bool?;
26+
// Assert
27+
result.Value.ShouldBeFalse();
28+
}
29+
[Fact]
30+
public void GivenNonBooleanString_WhenConverted_ThenShouldReturnNull()
31+
{
32+
// Arrange
33+
var input = "hello";
34+
// Act
35+
var result = converter.Convert(input) as bool?;
36+
// Assert
37+
result.ShouldBeNull();
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Model;
2+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.ValueConverters;
3+
using Dataverse.ConfigurationMigrationTool.Console.Tests.FakeBuilders;
4+
using Microsoft.Xrm.Sdk;
5+
using Microsoft.Xrm.Sdk.Metadata;
6+
using NSubstitute;
7+
using Shouldly;
8+
using System.Web;
9+
10+
namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Import.ValueConverters;
11+
public class DataverseValueConverterTests
12+
{
13+
private readonly IMainConverter mainConverter = Substitute.For<IMainConverter>();
14+
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+
};
32+
public DataverseValueConverterTests()
33+
{
34+
converter = new DataverseValueConverter(mainConverter);
35+
}
36+
37+
[Fact]
38+
public void GivenAStringAttributeAndAField_WhenConverted_ThenItShouldConvertProperly()
39+
{
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));
50+
}
51+
[Fact]
52+
public void GivenAPicklistAttributeAndAField_WhenConverted_ThenItShouldConvertProperly()
53+
{
54+
// Arrange
55+
int expectedValue = 1;
56+
var attributeMD =
57+
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));
63+
// Act
64+
var result = converter.Convert(attributeMD, field);
65+
// Assert
66+
result.ShouldBeOfType<OptionSetValue>();
67+
((OptionSetValue)result).Value.ShouldBe(expectedValue);
68+
mainConverter.Received(1)
69+
.Convert<OptionSetValue>(field.Value);
70+
}
71+
[Fact]
72+
public void GivenAIntegerAttributeAndAField_WhenConverted_ThenItShouldConvertProperly()
73+
{
74+
// Arrange
75+
int expectedValue = 1;
76+
var attributeMD =
77+
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);
83+
// Act
84+
var result = converter.Convert(attributeMD, field);
85+
// Assert
86+
result.ShouldBeOfType<int>();
87+
((int?)result).ShouldBe(expectedValue);
88+
mainConverter.Received(1)
89+
.Convert<int?>(field.Value);
90+
}
91+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.ValueConverters;
2+
using Shouldly;
3+
4+
namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Import.ValueConverters;
5+
public class DatetimeValueConverterTests
6+
{
7+
private readonly DatetimeValueConverter converter = new DatetimeValueConverter();
8+
[Fact]
9+
public void GivenADateTimeString_WhenConverted_ThenItShouldReturnDateTime()
10+
{
11+
// Arrange
12+
var date = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc);
13+
var input = date.ToString("o"); // ISO 8601 format
14+
// Act
15+
var result = converter.Convert(input) as DateTime?;
16+
// Assert
17+
result.ShouldNotBeNull();
18+
result.Value.ShouldBe(date);
19+
}
20+
[Fact]
21+
public void GivenANonDateTimeString_WhenConverted_ThenItShouldReturnNull()
22+
{
23+
// Arrange;
24+
var input = "not a date";
25+
// Act
26+
var result = converter.Convert(input) as DateTime?;
27+
// Assert
28+
result.ShouldBeNull();
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.ValueConverters;
2+
using Shouldly;
3+
4+
namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Import.ValueConverters;
5+
public class DecimalValueConverterTests
6+
{
7+
private readonly DecimalValueConverter converter = new DecimalValueConverter();
8+
[Fact]
9+
public void GivenADecimalString_WhenConverted_ThenItShouldReturnDecimal()
10+
{
11+
// Arrange
12+
var decimalValue = 123.45m;
13+
var input = decimalValue.ToString();
14+
// Act
15+
var result = converter.Convert(input) as decimal?;
16+
// Assert
17+
result.ShouldNotBeNull();
18+
result.Value.ShouldBe(decimalValue);
19+
}
20+
[Fact]
21+
public void GivenANonDecimalString_WhenConverted_ThenItShouldReturnNull()
22+
{
23+
// Arrange
24+
var input = "not a decimal";
25+
// Act
26+
var result = converter.Convert(input) as decimal?;
27+
// Assert
28+
result.ShouldBeNull();
29+
}
30+
}

0 commit comments

Comments
 (0)