Skip to content

Commit 2e16213

Browse files
authored
Merge pull request #13 from dotnetprog/feature/AddPrincipalMappingInterceptors
Add Entity Interceptors For team,businessunit and systemuser Attributes
2 parents 8ee78fa + bc63cf9 commit 2e16213

33 files changed

+1063
-60
lines changed

src/Dataverse.ConfigurationMigrationTool/.editorconfig

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,90 @@
22

33
# CS8603: Possible null reference return.
44
dotnet_diagnostic.CS8603.severity = none
5+
csharp_indent_labels = one_less_than_current
6+
csharp_using_directive_placement = outside_namespace:silent
7+
csharp_prefer_simple_using_statement = true:suggestion
8+
csharp_prefer_braces = true:silent
9+
csharp_style_namespace_declarations = file_scoped:silent
10+
csharp_style_prefer_method_group_conversion = true:silent
11+
csharp_style_prefer_top_level_statements = true:silent
12+
csharp_style_prefer_primary_constructors = true:suggestion
13+
csharp_prefer_system_threading_lock = true:suggestion
14+
csharp_style_expression_bodied_methods = false:silent
15+
csharp_style_expression_bodied_constructors = false:silent
16+
csharp_style_expression_bodied_operators = false:silent
17+
csharp_style_expression_bodied_properties = true:silent
18+
csharp_style_expression_bodied_indexers = true:silent
19+
csharp_style_expression_bodied_accessors = true:silent
20+
csharp_style_expression_bodied_lambdas = true:silent
21+
csharp_style_expression_bodied_local_functions = false:silent
22+
csharp_style_throw_expression = true:suggestion
23+
csharp_style_prefer_null_check_over_type_check = true:suggestion
24+
25+
[*.{cs,vb}]
26+
#### Naming styles ####
27+
28+
# Naming rules
29+
30+
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
31+
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
32+
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
33+
34+
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
35+
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
36+
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
37+
38+
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
39+
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
40+
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
41+
42+
# Symbol specifications
43+
44+
dotnet_naming_symbols.interface.applicable_kinds = interface
45+
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
46+
dotnet_naming_symbols.interface.required_modifiers =
47+
48+
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
49+
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
50+
dotnet_naming_symbols.types.required_modifiers =
51+
52+
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
53+
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
54+
dotnet_naming_symbols.non_field_members.required_modifiers =
55+
56+
# Naming styles
57+
58+
dotnet_naming_style.begins_with_i.required_prefix = I
59+
dotnet_naming_style.begins_with_i.required_suffix =
60+
dotnet_naming_style.begins_with_i.word_separator =
61+
dotnet_naming_style.begins_with_i.capitalization = pascal_case
62+
63+
dotnet_naming_style.pascal_case.required_prefix =
64+
dotnet_naming_style.pascal_case.required_suffix =
65+
dotnet_naming_style.pascal_case.word_separator =
66+
dotnet_naming_style.pascal_case.capitalization = pascal_case
67+
68+
dotnet_naming_style.pascal_case.required_prefix =
69+
dotnet_naming_style.pascal_case.required_suffix =
70+
dotnet_naming_style.pascal_case.word_separator =
71+
dotnet_naming_style.pascal_case.capitalization = pascal_case
72+
dotnet_style_operator_placement_when_wrapping = beginning_of_line
73+
tab_width = 4
74+
indent_size = 4
75+
end_of_line = crlf
76+
dotnet_style_coalesce_expression = true:suggestion
77+
dotnet_style_null_propagation = true:suggestion
78+
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
79+
dotnet_style_prefer_auto_properties = true:silent
80+
dotnet_style_object_initializer = true:suggestion
81+
dotnet_style_collection_initializer = true:suggestion
82+
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
83+
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
84+
dotnet_style_prefer_conditional_expression_over_return = true:silent
85+
dotnet_style_explicit_tuple_names = true:suggestion
86+
dotnet_style_prefer_inferred_tuple_names = true:suggestion
87+
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
88+
dotnet_style_prefer_compound_assignment = true:suggestion
89+
dotnet_style_prefer_simplified_interpolation = true:suggestion
90+
dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion
91+
dotnet_style_namespace_match_folder = true:suggestion

src/Dataverse.ConfigurationMigrationTool/Console.Tests/CodeCoverage.runsettings

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<DataCollector friendlyName="XPlat code coverage">
66
<Configuration>
77
<Format>cobertura,opencover</Format>
8-
<Exclude>[*]Dataverse.ConfigurationMigrationTool.Console.Services.Dataverse*</Exclude>
8+
<Exclude>[*]Dataverse.ConfigurationMigrationTool.Console.Services.Dataverse.Connection.*,[*]Dataverse.ConfigurationMigrationTool.Console.Services.Dataverse.Configuration.*</Exclude>
99
<ExcludeByFile>**/Program.cs,</ExcludeByFile>
1010
<SkipAutoProps>true</SkipAutoProps>
1111
</Configuration>

src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/ImportTaskProcessorServiceTests.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
using Dataverse.ConfigurationMigrationTool.Console.Features.Import;
2+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Interceptors;
23
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Model;
34
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.ValueConverters;
45
using Dataverse.ConfigurationMigrationTool.Console.Features.Shared;
5-
using Dataverse.ConfigurationMigrationTool.Console.Services.Dataverse;
6+
using Dataverse.ConfigurationMigrationTool.Console.Services.Dataverse.Connection;
67
using Dataverse.ConfigurationMigrationTool.Console.Tests.Extensions;
78
using Dataverse.ConfigurationMigrationTool.Console.Tests.FakeBuilders;
89
using Microsoft.Extensions.Logging;
@@ -20,14 +21,17 @@ public class ImportTaskProcessorServiceTests
2021
private readonly IDataverseValueConverter _dataverseValueConverter;
2122
private readonly IBulkOrganizationService bulkOrganizationService;
2223
private readonly ImportTaskProcessorService importService;
24+
private readonly IEntityInterceptor _entityInterceptor = Substitute.For<IEntityInterceptor>();
2325

2426
public ImportTaskProcessorServiceTests()
2527
{
2628
this.metadataService = Substitute.For<IMetadataService>();
2729
this.logger = Substitute.For<ILogger<ImportTaskProcessorService>>();
2830
_dataverseValueConverter = Substitute.For<IDataverseValueConverter>();
2931
this.bulkOrganizationService = Substitute.For<IBulkOrganizationService>();
30-
this.importService = new ImportTaskProcessorService(metadataService, logger, _dataverseValueConverter, bulkOrganizationService);
32+
this.importService = new ImportTaskProcessorService(metadataService, logger, _dataverseValueConverter, bulkOrganizationService, _entityInterceptor);
33+
_entityInterceptor.InterceptAsync(Arg.Any<Entity>())
34+
.Returns(x => x.Arg<Entity>());
3135
}
3236
[Fact]
3337
public async Task GivenAnImportTaskWhereEntitySchemaIsNotFound_WhenExecuted_ThenItShouldReturnCompleted()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Interceptors;
2+
using Microsoft.Xrm.Sdk;
3+
using NSubstitute;
4+
using Shouldly;
5+
6+
namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Import.Interceptors;
7+
public abstract class BaseEntityInterceptorTests<T>
8+
where T : IEntityInterceptor
9+
{
10+
private IEntityInterceptor Interceptor { get; set; }
11+
protected readonly IEntityInterceptor Successor = Substitute.For<IEntityInterceptor>();
12+
protected BaseEntityInterceptorTests()
13+
{
14+
Interceptor = CreateInterceptor();
15+
Interceptor.SetSuccessor(Successor);
16+
Successor.InterceptAsync(Arg.Any<Entity>())
17+
.Returns(x => x.Arg<Entity>());
18+
}
19+
protected abstract T CreateInterceptor();
20+
protected async Task<Entity> InterceptAsync(Entity entity, bool ShouldSuccessorBeCalled = true)
21+
{
22+
var result = await Interceptor.InterceptAsync(entity);
23+
if (ShouldSuccessorBeCalled)
24+
{
25+
await Successor.Received(1).InterceptAsync(entity);
26+
}
27+
else
28+
{
29+
await Successor.DidNotReceive().InterceptAsync(entity);
30+
}
31+
return result;
32+
}
33+
[Fact]
34+
public async Task GivenAnEntityInterceptorWith_NoSuccessor_ThenItShouldReturnEntity()
35+
{
36+
//Arrange
37+
var interceptor = CreateInterceptor();
38+
39+
var entity = new Entity();
40+
//Act
41+
var result = await interceptor.InterceptAsync(entity);
42+
//Assert
43+
result.ShouldBe(entity);
44+
await Successor.DidNotReceive().InterceptAsync(entity);
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Interceptors;
2+
using Dataverse.ConfigurationMigrationTool.Console.Features.Shared;
3+
using Microsoft.Xrm.Sdk;
4+
using NSubstitute;
5+
using Shouldly;
6+
7+
namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Import.Interceptors;
8+
public class BusinessUnitInterceptorTests : BaseEntityInterceptorTests<BusinessUnitInterceptor>
9+
{
10+
private readonly IBusinessUnitRepository _businessUnitRepository = Substitute.For<IBusinessUnitRepository>();
11+
12+
13+
14+
15+
[Fact]
16+
public async Task GivenAnEntityWithBusinessUnitField_WhenItsIntercepted_ThenItShouldResolveBusinessUnitByNameWithRepository()
17+
{
18+
//Arrange
19+
var buName = "Test Business Unit";
20+
var expectedBu = new Entity("businessunit")
21+
{
22+
Id = Guid.NewGuid(),
23+
["name"] = buName
24+
};
25+
_businessUnitRepository.GetByNameAsync(buName).Returns(expectedBu);
26+
27+
var entity = new Entity("account")
28+
{
29+
Id = Guid.NewGuid(),
30+
["name"] = "Test Account",
31+
["businessunitid"] = new EntityReference("businessunit", Guid.NewGuid()) { Name = buName }
32+
};
33+
34+
//Act
35+
var result = await InterceptAsync(entity);
36+
//Assert
37+
result.GetAttributeValue<EntityReference>("businessunitid").Id.ShouldBe(expectedBu.Id);
38+
}
39+
[Fact]
40+
public async Task GivenAnEntityWithBusinessUnitField_WhenItsIntercepted_ThenItShouldResolveBusinessUnitByIdWithRepository()
41+
{
42+
//Arrange
43+
var buName = "Test Business Unit";
44+
var expectedBu = new Entity("businessunit")
45+
{
46+
Id = Guid.NewGuid(),
47+
["name"] = buName
48+
};
49+
_businessUnitRepository.GetByIdAsync(expectedBu.Id).Returns(expectedBu);
50+
51+
var entity = new Entity("account")
52+
{
53+
Id = Guid.NewGuid(),
54+
["name"] = "Test Account",
55+
["businessunitid"] = expectedBu.ToEntityReference()
56+
};
57+
58+
//Act
59+
var result = await InterceptAsync(entity);
60+
//Assert
61+
result.GetAttributeValue<EntityReference>("businessunitid").Id.ShouldBe(expectedBu.Id);
62+
}
63+
[Fact]
64+
public async Task GivenAnEntityWithBusinessUnitField_WhenItsInterceptedAndCantBeResolved_ThenItShouldRemoveFieldFromEntity()
65+
{
66+
//Arrange
67+
var buName = "Test Business Unit";
68+
var expectedBu = new Entity("businessunit")
69+
{
70+
Id = Guid.NewGuid(),
71+
["name"] = buName
72+
};
73+
74+
var entity = new Entity("account")
75+
{
76+
Id = Guid.NewGuid(),
77+
["name"] = "Test Account",
78+
["businessunitid"] = expectedBu.ToEntityReference()
79+
};
80+
81+
//Act
82+
var result = await InterceptAsync(entity);
83+
//Assert
84+
result.Attributes.FirstOrDefault(kv => kv.Key == "businessunitid").ShouldBe(default);
85+
}
86+
87+
protected override BusinessUnitInterceptor CreateInterceptor() => new BusinessUnitInterceptor(_businessUnitRepository);
88+
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Interceptors;
2+
using Dataverse.ConfigurationMigrationTool.Console.Features.Shared;
3+
using Microsoft.Xrm.Sdk;
4+
using NSubstitute;
5+
using Shouldly;
6+
7+
namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Import.Interceptors;
8+
public class TargetTeamInterceptorTests : BaseEntityInterceptorTests<TargetTeamInterceptor>
9+
{
10+
private readonly ITeamRepository _teamRepository = Substitute.For<ITeamRepository>();
11+
protected override TargetTeamInterceptor CreateInterceptor() => new TargetTeamInterceptor(_teamRepository);
12+
[Fact]
13+
public async Task GivenAnEntityWithTeamField_WhenItsIntercepted_ThenItShouldResolveTeamByNameWithRepository()
14+
{
15+
//Arrange
16+
var team = "Test Team";
17+
var expectedTeam = new Entity("team")
18+
{
19+
Id = Guid.NewGuid(),
20+
["name"] = team
21+
};
22+
_teamRepository.GetByNameAsync(team).Returns(expectedTeam);
23+
24+
var entity = new Entity("account")
25+
{
26+
Id = Guid.NewGuid(),
27+
["name"] = "Test Account",
28+
["ownerid"] = new EntityReference("team", Guid.NewGuid()) { Name = team }
29+
};
30+
31+
//Act
32+
var result = await InterceptAsync(entity);
33+
//Assert
34+
result.GetAttributeValue<EntityReference>("ownerid").Id.ShouldBe(expectedTeam.Id);
35+
}
36+
[Fact]
37+
public async Task GivenAnEntityWithTeamField_WhenItsIntercepted_ThenItShouldResolveTeamByIdWithRepository()
38+
{
39+
//Arrange
40+
var teamName = "Test team";
41+
var expectedTeam = new Entity("team")
42+
{
43+
Id = Guid.NewGuid(),
44+
["name"] = teamName
45+
};
46+
_teamRepository.GetByIdAsync(expectedTeam.Id).Returns(expectedTeam);
47+
48+
var entity = new Entity("account")
49+
{
50+
Id = Guid.NewGuid(),
51+
["name"] = "Test Account",
52+
["ownerid"] = expectedTeam.ToEntityReference()
53+
};
54+
55+
//Act
56+
var result = await InterceptAsync(entity);
57+
//Assert
58+
result.GetAttributeValue<EntityReference>("ownerid").Id.ShouldBe(expectedTeam.Id);
59+
}
60+
[Fact]
61+
public async Task GivenAnEntityWithTeamField_WhenItsInterceptedAndCantBeResolved_ThenItShouldRemoveFieldFromEntity()
62+
{
63+
//Arrange
64+
var teamName = "Test team";
65+
var expectedTeam = new Entity("team")
66+
{
67+
Id = Guid.NewGuid(),
68+
["name"] = teamName
69+
};
70+
71+
var entity = new Entity("account")
72+
{
73+
Id = Guid.NewGuid(),
74+
["name"] = "Test Account",
75+
["ownerid"] = expectedTeam.ToEntityReference()
76+
};
77+
78+
//Act
79+
var result = await InterceptAsync(entity);
80+
//Assert
81+
result.Attributes.FirstOrDefault(kv => kv.Key == "ownerid").ShouldBe(default);
82+
}
83+
}

0 commit comments

Comments
 (0)