Skip to content

Commit b763eb3

Browse files
authored
Merge pull request #14 from dotnetprog/feature/AddExportCommand
Feature/add export command
2 parents 2e16213 + c0e062d commit b763eb3

File tree

61 files changed

+1041
-219
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+1041
-219
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<Configuration>
77
<Format>cobertura,opencover</Format>
88
<Exclude>[*]Dataverse.ConfigurationMigrationTool.Console.Services.Dataverse.Connection.*,[*]Dataverse.ConfigurationMigrationTool.Console.Services.Dataverse.Configuration.*</Exclude>
9-
<ExcludeByFile>**/Program.cs,</ExcludeByFile>
9+
<ExcludeByFile>**/Program.cs,**/IServiceCollectionExtensions.cs</ExcludeByFile>
1010
<SkipAutoProps>true</SkipAutoProps>
1111
</Configuration>
1212
</DataCollector>

src/Dataverse.ConfigurationMigrationTool/Console.Tests/FakeBuilders/FakeEntityMetadataBuilder.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public FakeEntityMetadataBuilder AddRelationship(string SchemaName, string Targe
3535
{
3636
Entity1LogicalName = EntityName,
3737
Entity2LogicalName = TargetEntity,
38+
Entity1IntersectAttribute = $"{EntityName}id",
39+
Entity2IntersectAttribute = $"{TargetEntity}id",
3840
IntersectEntityName = SchemaName,
3941
SchemaName = SchemaName
4042
};

src/Dataverse.ConfigurationMigrationTool/Console.Tests/FakeDatasets.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Model;
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Shared.Domain;
22

33
namespace Dataverse.ConfigurationMigrationTool.Console.Tests;
44
internal static class FakeDatasets

src/Dataverse.ConfigurationMigrationTool/Console.Tests/FakeSchemas.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Model;
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Shared.Domain;
22

33
namespace Dataverse.ConfigurationMigrationTool.Console.Tests;
44
internal static class FakeSchemas
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Export;
2+
using Dataverse.ConfigurationMigrationTool.Console.Features.Export.Commands;
3+
using Dataverse.ConfigurationMigrationTool.Console.Features.Shared;
4+
using Dataverse.ConfigurationMigrationTool.Console.Features.Shared.Domain;
5+
using Microsoft.Extensions.Logging;
6+
using Microsoft.Extensions.Options;
7+
using NSubstitute;
8+
using Shouldly;
9+
10+
namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Export.Commands;
11+
public class ExportCommandTests
12+
{
13+
private readonly ILogger<ExportCommand> _logger;
14+
private readonly ExportCommandOption _options = new ExportCommandOption();
15+
private readonly IOptions<ExportCommandOption> _optionsWrapper;
16+
private readonly IValidator<DataSchema> _schemaValidator;
17+
private readonly IFileDataService _fileDataService;
18+
private readonly IDataExportService _dataExportService;
19+
private readonly ExportCommand _exportCommand;
20+
public ExportCommandTests()
21+
{
22+
_logger = Substitute.For<ILogger<ExportCommand>>();
23+
_optionsWrapper = Substitute.For<IOptions<ExportCommandOption>>();
24+
_optionsWrapper.Value.Returns(_options);
25+
_schemaValidator = Substitute.For<IValidator<DataSchema>>();
26+
_fileDataService = Substitute.For<IFileDataService>();
27+
_dataExportService = Substitute.For<IDataExportService>(); ;
28+
_exportCommand = new ExportCommand(_logger, _optionsWrapper, _schemaValidator, _fileDataService, _dataExportService);
29+
}
30+
31+
[Fact]
32+
public async Task GivenACommand_WhenItExecutesWithASchema_ThenItShouldExportData()
33+
{
34+
// Arrange
35+
_options.schema = "schema.json";
36+
_options.output = "output.json";
37+
38+
var schema = new DataSchema();
39+
_fileDataService.ReadAsync<DataSchema>(_options.schema).Returns(Task.FromResult(schema));
40+
var validationResult = new ValidationResult();
41+
_schemaValidator.Validate(schema).Returns(Task.FromResult(validationResult));
42+
var entities = new List<EntityImport>
43+
{
44+
new EntityImport { Name = "TestEntity", Displayname = "Test Entity" }
45+
};
46+
_dataExportService.ExportEntitiesFromSchema(schema).Returns(Task.FromResult(entities.AsEnumerable()));
47+
// Act
48+
await _exportCommand.Execute();
49+
// Assert
50+
await _fileDataService.Received(1).WriteAsync(Arg.Is<Entities>(e => e.Entity.Count == 1), _options.output);
51+
}
52+
[Fact]
53+
public async Task GivenACommand_WhenItExecutesWithAnInvalidSchema_ThenItShouldThrowError()
54+
{
55+
// Arrange
56+
_options.schema = "schema.json";
57+
_options.output = "output.json";
58+
59+
var schema = new DataSchema();
60+
_fileDataService.ReadAsync<DataSchema>(_options.schema).Returns(Task.FromResult(schema));
61+
var validationResult = new ValidationResult()
62+
{
63+
Failures = [new("Test", "property failure")]
64+
};
65+
_schemaValidator.Validate(schema).Returns(Task.FromResult(validationResult));
66+
var entities = new List<EntityImport>
67+
{
68+
new EntityImport { Name = "TestEntity", Displayname = "Test Entity" }
69+
};
70+
_dataExportService.ExportEntitiesFromSchema(schema).Returns(Task.FromResult(entities.AsEnumerable()));
71+
// Act
72+
var ex = await _exportCommand.Execute().ShouldThrowAsync<Exception>();
73+
// Assert
74+
ex.Message.ShouldBe("Provided Schema was not valid.");
75+
}
76+
77+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Export;
2+
using Dataverse.ConfigurationMigrationTool.Console.Features.Shared;
3+
using Dataverse.ConfigurationMigrationTool.Console.Features.Shared.Domain;
4+
using Microsoft.Extensions.Logging;
5+
using NSubstitute;
6+
using Shouldly;
7+
using Record = Dataverse.ConfigurationMigrationTool.Console.Features.Shared.Domain.Record;
8+
9+
namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Export;
10+
public class DataExportServiceTests
11+
{
12+
private readonly ILogger<DataExportService> _logger;
13+
private readonly IMetadataService _metadataService;
14+
private readonly IDomainService _domainService;
15+
private readonly DataExportService _dataExportService;
16+
17+
public DataExportServiceTests()
18+
{
19+
_logger = Substitute.For<ILogger<DataExportService>>();
20+
_metadataService = Substitute.For<IMetadataService>();
21+
_domainService = Substitute.For<IDomainService>();
22+
_dataExportService = new DataExportService(_logger, _metadataService, _domainService);
23+
}
24+
[Fact]
25+
public async Task GivenADataExportService_WhenItExportsDataFromSchema_ThenItShouldUseDomainServiceProperly()
26+
{
27+
//Arrange
28+
29+
var schema = FakeSchemas.Contact;
30+
var metadata = FakeMetadata.Contact;
31+
_metadataService.GetEntity(metadata.LogicalName).Returns(metadata);
32+
33+
var records = new List<Record>
34+
{
35+
new()
36+
{
37+
Id = Guid.NewGuid(),
38+
Field = [new(){
39+
Name = "firstname",
40+
Value = "John"
41+
},
42+
new(){
43+
Name = "lastname",
44+
Value = "Doe"
45+
}],
46+
},
47+
new()
48+
{
49+
Id = Guid.NewGuid(),
50+
Field = [new(){
51+
Name = "firstname",
52+
Value = "Jane"
53+
},
54+
new(){
55+
Name = "lastname",
56+
Value = "Dane"
57+
}],
58+
}
59+
};
60+
var rels = new List<M2mrelationship>
61+
{
62+
new()
63+
{
64+
M2mrelationshipname = "contact_opportunities",
65+
Sourceid = Guid.NewGuid(),
66+
Targetentityname = "opportunity",
67+
Targetentitynameidfield = "opportunityid",
68+
Targetids = new Targetids
69+
{
70+
Targetid = [Guid.NewGuid(), Guid.NewGuid()]
71+
}
72+
}
73+
};
74+
75+
_domainService.GetRecords(schema).Returns(records);
76+
_domainService.GetM2mRelationships(metadata.ManyToManyRelationships.First())
77+
.Returns(rels);
78+
79+
//Act
80+
var result = await _dataExportService.ExportEntitiesFromSchema(new DataSchema
81+
{
82+
Entity = [schema]
83+
});
84+
//Assert
85+
var entityImport = result.Single();
86+
entityImport.Name.ShouldBe(schema.Name);
87+
entityImport.Displayname.ShouldBe(schema.Displayname);
88+
entityImport.Records.Record.ShouldBe(records);
89+
entityImport.M2mrelationships.M2mrelationship.ShouldBe(rels, true);
90+
}
91+
92+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Export.Mappers;
2+
using Microsoft.Xrm.Sdk;
3+
using Shouldly;
4+
5+
namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Export.Mappers;
6+
public class DataverseRecordToRecordMapperTests
7+
{
8+
private readonly DataverseRecordToRecordMapper _mapper = new DataverseRecordToRecordMapper();
9+
[Fact]
10+
public void GivenAnEntity_WhenItIsMappedToARecord_ThenItTheRecordShouldBeProperplyCreated()
11+
{
12+
//Arrange
13+
var Schema = FakeSchemas.Account;
14+
var Entity = new Entity("account")
15+
{
16+
Id = Guid.NewGuid(),
17+
["name"] = "Test Account",
18+
["primarycontactid"] = new EntityReference("contact", Guid.NewGuid())
19+
};
20+
//Act
21+
var record = _mapper.Map((Schema, Entity));
22+
//Assert
23+
record.Id.ShouldBe(Entity.Id);
24+
record.Field.ForEach(field =>
25+
{
26+
field.ShouldNotBeNull();
27+
Entity.Attributes.Keys.ShouldContain(field.Name);
28+
});
29+
record.Field.First(f => f.Name == "name").Value.ShouldBe("Test Account");
30+
var lookupField = record.Field.First(f => f.Name == "primarycontactid");
31+
lookupField.Value.ShouldBe(Entity.GetAttributeValue<EntityReference>("primarycontactid").Id.ToString());
32+
lookupField.Lookupentity.ShouldBe("contact");
33+
lookupField.Lookupentityname.ShouldBeNull();
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Export.Mappers;
2+
using Dataverse.ConfigurationMigrationTool.Console.Features.Shared.Domain;
3+
using Microsoft.Xrm.Sdk;
4+
using Shouldly;
5+
using System.Web;
6+
7+
namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Export.Mappers;
8+
public class EntityFieldValueToFieldMapperTests
9+
{
10+
private readonly EntityFieldValueToFieldMapper _mapper = new EntityFieldValueToFieldMapper();
11+
private static readonly DateTime CurrentDate = DateTime.UtcNow;
12+
private static readonly Guid RandomGuid = Guid.NewGuid();
13+
public static TheoryData<FieldSchema, object, Field> TestData => new TheoryData<FieldSchema, object, Field>
14+
{
15+
{
16+
new FieldSchema { Name = "testField" },
17+
null,
18+
null
19+
},
20+
{
21+
new FieldSchema { Name = "testLookupField" },
22+
new EntityReference("testEntity", RandomGuid),
23+
new Field { Name = "testLookupField", Lookupentity = "testEntity", Value = RandomGuid.ToString() }
24+
},
25+
{
26+
new FieldSchema { Name = "testOptionSetField" },
27+
new OptionSetValue(1),
28+
new Field { Name = "testOptionSetField", Value = "1" }
29+
},
30+
{
31+
new FieldSchema { Name = "testBooleanField" },
32+
true,
33+
new Field { Name = "testBooleanField", Value = "True" }
34+
},
35+
{
36+
new FieldSchema { Name = "testDateTimeField" },
37+
CurrentDate,
38+
new Field { Name = "testDateTimeField", Value = CurrentDate.ToString("o") }
39+
},
40+
{
41+
new FieldSchema { Name = "testGuidField" },
42+
RandomGuid,
43+
new Field { Name = "testGuidField", Value = RandomGuid.ToString() }
44+
},
45+
{
46+
new FieldSchema { Name = "testMoneyField" },
47+
new Money(100.50m),
48+
new Field { Name = "testMoneyField", Value = "100.50" }
49+
},
50+
{
51+
new FieldSchema { Name = "testDecimalField" },
52+
123.45m,
53+
new Field { Name = "testDecimalField", Value = "123.45" }
54+
},
55+
{
56+
new FieldSchema { Name = "testDoubleField" },
57+
123.456789,
58+
new Field { Name = "testDoubleField", Value = "123.456789" }
59+
},
60+
{
61+
new FieldSchema { Name = "testStringField" },
62+
"<>Root<>''/",
63+
new Field { Name = "testStringField", Value = HttpUtility.HtmlEncode("<>Root<>''/") }
64+
}
65+
};
66+
67+
[Theory]
68+
[MemberData(nameof(TestData))]
69+
public void GivenAnAttributeValue_WhenItIsMappedToAField_ThenFieldShouldBeCreatedProperly(FieldSchema schema, object value, Field expected)
70+
{
71+
// Act
72+
var result = _mapper.Map((schema, value));
73+
// Assert
74+
if (expected == null)
75+
{
76+
result.ShouldBeNull();
77+
}
78+
else
79+
{
80+
result.ShouldBeEquivalentTo(expected);
81+
}
82+
}
83+
}

src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/Commands/ImportCommandsTest.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Commands;
33
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Model;
44
using Dataverse.ConfigurationMigrationTool.Console.Features.Shared;
5+
using Dataverse.ConfigurationMigrationTool.Console.Features.Shared.Domain;
56
using Dataverse.ConfigurationMigrationTool.Console.Tests.Extensions;
67
using Microsoft.Extensions.Logging;
78
using Microsoft.Extensions.Options;
@@ -13,7 +14,7 @@ public class ImportCommandsTest
1314
{
1415
private readonly ILogger<ImportCommands> _logger;
1516
private readonly IImportDataProvider _importDataProvider;
16-
private readonly IValidator<ImportSchema> _schemaValidator;
17+
private readonly IValidator<DataSchema> _schemaValidator;
1718
private readonly IImportTaskProcessorService _importDataService;
1819
private readonly ImportCommands _importCommands;
1920
const string DataFilePath = "data.json";
@@ -28,7 +29,7 @@ public ImportCommandsTest()
2829
{
2930
_logger = Substitute.For<ILogger<ImportCommands>>();
3031
_importDataProvider = Substitute.For<IImportDataProvider>();
31-
_schemaValidator = Substitute.For<IValidator<ImportSchema>>();
32+
_schemaValidator = Substitute.For<IValidator<DataSchema>>();
3233
_importDataService = Substitute.For<IImportTaskProcessorService>();
3334
var options = Substitute.For<IOptions<ImportCommandOptions>>();
3435
options.Value.Returns(_options);
@@ -43,7 +44,7 @@ public ImportCommandsTest()
4344
public async Task GivenDataToImportWithSchema_WhenTheCommandExecutes_ThenItShouldProcessImportsAccordingly()
4445
{
4546
//Arrange
46-
var importSchema = new ImportSchema
47+
var importSchema = new DataSchema
4748
{
4849
Entity = new()
4950
{
@@ -84,7 +85,7 @@ public async Task GivenDataToImportWithSchema_WhenTheCommandExecutes_ThenItShoul
8485
public async Task GivenDataToImportWithSchema_WhenTheCommandExecutesAndFails_ThenItShouldThrowAnError()
8586
{
8687
//Arrange
87-
var importSchema = new ImportSchema
88+
var importSchema = new DataSchema
8889
{
8990
Entity = new()
9091
{
@@ -122,7 +123,7 @@ public async Task GivenDataToImportWithSchema_WhenTheCommandExecutesAndFails_The
122123
public async Task GivenAnInvalidSchema_WhenTheCommandExecutes_ThenItShouldFailAndLogIssues()
123124
{
124125
//Arrange
125-
var importSchema = new ImportSchema
126+
var importSchema = new DataSchema
126127
{
127128
Entity = new()
128129
{

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Model;
44
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.ValueConverters;
55
using Dataverse.ConfigurationMigrationTool.Console.Features.Shared;
6+
using Dataverse.ConfigurationMigrationTool.Console.Features.Shared.Domain;
67
using Dataverse.ConfigurationMigrationTool.Console.Services.Dataverse.Connection;
78
using Dataverse.ConfigurationMigrationTool.Console.Tests.Extensions;
89
using Dataverse.ConfigurationMigrationTool.Console.Tests.FakeBuilders;

0 commit comments

Comments
 (0)