diff --git a/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/CommandProcessorHostingServiceTests.cs b/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/CommandProcessorHostingServiceTests.cs new file mode 100644 index 0000000..ae042cf --- /dev/null +++ b/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/CommandProcessorHostingServiceTests.cs @@ -0,0 +1,86 @@ +using Dataverse.ConfigurationMigrationTool.Console.Features; +using Dataverse.ConfigurationMigrationTool.Console.Features.Shared; +using Dataverse.ConfigurationMigrationTool.Console.Tests.Extensions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using NSubstitute; +using Shouldly; +using System.Reflection; + +namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features; +public class CommandProcessorHostingServiceTests +{ + private readonly IOptions _options; + private readonly IServiceScopeFactory _serviceProviderFactory; + private readonly IHostApplicationLifetime _lifetime; + private readonly IServiceScope _serviceScope; + private readonly IServiceCollection ServiceCollection = new ServiceCollection(); + private readonly ILogger commandLogger = Substitute.For>(); + private readonly CommandProcessorHostingServiceOptions HostingOptions = new CommandProcessorHostingServiceOptions + { + CommandVerb = "fake" + }; + + public CommandProcessorHostingServiceTests() + { + + ServiceCollection.AddSingleton>(commandLogger); + _serviceScope = Substitute.For(); + _serviceScope.ServiceProvider.Returns(ServiceCollection.BuildServiceProvider()); + _options = Substitute.For>(); + _options.Value.Returns(HostingOptions); + _serviceProviderFactory = Substitute.For(); + _serviceProviderFactory.CreateScope().Returns(_serviceScope); + _lifetime = Substitute.For(); + + } + + [Fact] + public async Task GivenACommandVerb_WhenCommandProcessorRuns_ThenItShouldExecuteTheProperCommand() + { + //Arrange + + var host = new CommandProcessorHostingService(_options, _serviceProviderFactory, _lifetime, Assembly.GetExecutingAssembly()); + //Act + await host.StartAsync(CancellationToken.None); + + //Assert + commandLogger.ShouldHaveLogged(LogLevel.Information, "Fake command executed successfully."); + + } + + [Fact] + public async Task GivenAnNonExistantCommandVerb_WhenCommandProcessorRuns_ThenItShouldExecuteTheProperCommand() + { + //Arrange + var host = new CommandProcessorHostingService(_options, _serviceProviderFactory, _lifetime); + //Act + Func act = () => host.StartAsync(CancellationToken.None); + + //Assert + var ex = await act.ShouldThrowAsync(); + ex.Message.ShouldBe($"No command found for verb '{HostingOptions.CommandVerb}'"); + + } + + + +} +[CommandVerb("fake")] +public class FakeCommand : ICommand +{ + private readonly ILogger _logger; + + public FakeCommand(ILogger logger) + { + _logger = logger; + } + + public Task Execute() + { + _logger.LogInformation("Fake command executed successfully."); + return Task.CompletedTask; + } +} diff --git a/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/CoconaAppExtensionsTests.cs b/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/CoconaAppExtensionsTests.cs deleted file mode 100644 index bac1a59..0000000 --- a/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/CoconaAppExtensionsTests.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Cocona.Builder; -using Dataverse.ConfigurationMigrationTool.Console.Features.Import; -using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Commands; -using NSubstitute; -using System.Reflection; -namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Import; -public class CoconaAppExtensionsTests -{ - [Fact] - public void GivenACoconaCommandBuilder_WhenImportFeatureIsUsed_ThenItShouldAddImportCommand() - { - // Arrange - var commandBuilder = Substitute.For(); - // Act - commandBuilder.UseImportFeature(); - // Assert - commandBuilder.Received(1).Add(Arg.Is(t => typeof(TypeCommandDataSource).GetField("_type", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(t) == typeof(ImportCommands))); - - } -} diff --git a/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/Commands/ImportCommandsTest.cs b/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/Commands/ImportCommandsTest.cs index 567cd87..1df1f05 100644 --- a/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/Commands/ImportCommandsTest.cs +++ b/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/Commands/ImportCommandsTest.cs @@ -68,7 +68,7 @@ public async Task GivenDataToImportWithSchema_WhenTheCommandExecutes_ThenItShoul _importDataProvider.ReadSchemaFromFile(SchemaFilePath).Returns(importSchema); _schemaValidator.Validate(importSchema).Returns(new ValidationResult()); //Act - await _importCommands.Import(SchemaFilePath, DataFilePath); + await _importCommands.Execute(); //Assert Received.InOrder(async () => @@ -79,6 +79,44 @@ public async Task GivenDataToImportWithSchema_WhenTheCommandExecutes_ThenItShoul await _importDataService.Execute(Arg.Is(x => x.RelationshipSchema == FakeSchemas.Contact.Relationships.Relationship.First()), datasets); }); + } + [Fact] + public async Task GivenDataToImportWithSchema_WhenTheCommandExecutesAndFails_ThenItShouldThrowAnError() + { + //Arrange + var importSchema = new ImportSchema + { + Entity = new() + { + FakeSchemas.Account, + FakeSchemas.Contact, + FakeSchemas.Opportunity + + } + }; + var datasets = new Entities + { + Entity = new() + { + FakeDatasets.AccountSets, + FakeDatasets.ContactSets, + FakeDatasets.OpportunitiesSet + } + }; + _importDataService.Execute(Arg.Any(), Arg.Any()) + .Returns(TaskResult.Failed); + _importDataProvider.ReadFromFile(DataFilePath).Returns(datasets); + _importDataProvider.ReadSchemaFromFile(SchemaFilePath).Returns(importSchema); + _schemaValidator.Validate(importSchema).Returns(new ValidationResult()); + //Act + Func act = () => _importCommands.Execute(); + + //Assert + var ex = await act.ShouldThrowAsync(); + ex.Message.ShouldBe("Import process failed."); + + + } [Fact] public async Task GivenAnInvalidSchema_WhenTheCommandExecutes_ThenItShouldFailAndLogIssues() @@ -115,7 +153,7 @@ public async Task GivenAnInvalidSchema_WhenTheCommandExecutes_ThenItShouldFailAn } }); //Act - Func act = () => _importCommands.Import(SchemaFilePath, DataFilePath); + Func act = () => _importCommands.Execute(); //Assert var ex = await act.ShouldThrowAsync(); diff --git a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Dataverse.ConfigurationMigrationTool.Console.csproj b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Dataverse.ConfigurationMigrationTool.Console.csproj index acc11c3..670f6b8 100644 --- a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Dataverse.ConfigurationMigrationTool.Console.csproj +++ b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Dataverse.ConfigurationMigrationTool.Console.csproj @@ -10,11 +10,11 @@ - - + + + - diff --git a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Shared/CommandProcessorHostingService.cs b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/CommandProcessorHostingService.cs similarity index 68% rename from src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Shared/CommandProcessorHostingService.cs rename to src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/CommandProcessorHostingService.cs index 44c6af0..77ea3d0 100644 --- a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Shared/CommandProcessorHostingService.cs +++ b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/CommandProcessorHostingService.cs @@ -1,25 +1,31 @@ -using Microsoft.Extensions.DependencyInjection; +using Dataverse.ConfigurationMigrationTool.Console.Features.Shared; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using System.Reflection; -namespace Dataverse.ConfigurationMigrationTool.Console.Features.Shared; +namespace Dataverse.ConfigurationMigrationTool.Console.Features; public class CommandProcessorHostingService : BackgroundService { private readonly CommandProcessorHostingServiceOptions _options; private readonly IServiceScopeFactory _serviceProviderFactory; private readonly IHostApplicationLifetime _lifetime; + private readonly IEnumerable _commandAssemblies; - public CommandProcessorHostingService(IOptions options, IServiceScopeFactory serviceProviderFactory, IHostApplicationLifetime lifetime) + public CommandProcessorHostingService(IOptions options, IServiceScopeFactory serviceProviderFactory, IHostApplicationLifetime lifetime, params Assembly[] commandAssemblies) { _options = options.Value; _serviceProviderFactory = serviceProviderFactory; _lifetime = lifetime; + _commandAssemblies = commandAssemblies.Length > 0 ? commandAssemblies : new[] { Assembly.GetExecutingAssembly() }; } + public CommandProcessorHostingService(IOptions options, IServiceScopeFactory serviceProviderFactory, IHostApplicationLifetime lifetime) : this(options, serviceProviderFactory, lifetime, []) + { + } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - var types = from type in Assembly.GetExecutingAssembly().GetTypes() + var types = from type in _commandAssemblies.SelectMany(a => a.GetTypes()) where Attribute.IsDefined(type, typeof(CommandVerbAttribute)) && type.IsClass && !type.IsAbstract && diff --git a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Shared/CommandProcessorHostingServiceOptions.cs b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/CommandProcessorHostingServiceOptions.cs similarity index 93% rename from src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Shared/CommandProcessorHostingServiceOptions.cs rename to src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/CommandProcessorHostingServiceOptions.cs index 99e764f..8f705dc 100644 --- a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Shared/CommandProcessorHostingServiceOptions.cs +++ b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/CommandProcessorHostingServiceOptions.cs @@ -1,4 +1,4 @@ -namespace Dataverse.ConfigurationMigrationTool.Console.Features.Shared; +namespace Dataverse.ConfigurationMigrationTool.Console.Features; public class CommandProcessorHostingServiceOptions { public string CommandVerb { get; set; } diff --git a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/CoconaAppExtensions.cs b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/CoconaAppExtensions.cs deleted file mode 100644 index 71b1e7d..0000000 --- a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/CoconaAppExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Cocona; -using Cocona.Builder; -using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Commands; - -namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import; - -public static class CoconaAppExtensions -{ - public static ICoconaCommandsBuilder UseImportFeature(this ICoconaCommandsBuilder app) - { - app.AddCommands(); - return app; - } - -} diff --git a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/Commands/ImportCommands.cs b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/Commands/ImportCommands.cs index 94c8ebc..204f93c 100644 --- a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/Commands/ImportCommands.cs +++ b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/Commands/ImportCommands.cs @@ -1,9 +1,7 @@ -using Cocona; -using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Model; +using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Model; using Dataverse.ConfigurationMigrationTool.Console.Features.Shared; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using ConsoleApp = System.Console; namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import.Commands; [CommandVerb("import")] @@ -31,15 +29,12 @@ public ImportCommands(ILogger logger, public async Task Execute() => await Import(_options.schema, _options.data); - [Command("import")] - public async Task Import([Option("schema")] string schemafilepath, [Option("data")] string datafilepath) + private async Task Import(string schemafilepath, string datafilepath) { var ImportQueue = new Queue(); - ConsoleApp.WriteLine($"{datafilepath} with schema {schemafilepath}"); var schema = await _importDataProvider.ReadSchemaFromFile(schemafilepath); var importdata = await _importDataProvider.ReadFromFile(datafilepath); - ConsoleApp.WriteLine($"Schema Count: {schema.Entity.Count} | Data count {importdata.Entity.Count}"); var schemaValidationResult = await _schemaValidator.Validate(schema); @@ -66,7 +61,7 @@ public async Task Import([Option("schema")] string schemafilepath, [Option("data } } - + var taskResults = new List(); while (ImportQueue.Count > 0) { var importTask = ImportQueue.Dequeue(); @@ -90,11 +85,16 @@ public async Task Import([Option("schema")] string schemafilepath, [Option("data } #endregion - await _importDataService.Execute(importTask, importdata); - + var result = await _importDataService.Execute(importTask, importdata); + taskResults.Add(result); } + if (taskResults.Any(r => r == TaskResult.Failed)) + { + _logger.LogError("Import process failed."); + throw new Exception("Import process failed."); + } diff --git a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Program.cs b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Program.cs index 333b895..a3659b2 100644 --- a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Program.cs +++ b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Program.cs @@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerPlatform.Dataverse.Client; using System.Reflection; -var builder = new HostBuilder(); +var builder = Host.CreateDefaultBuilder(args); builder.ConfigureHostConfiguration((config) => { // Configure the host configuration, such as environment variables, command line arguments, etc. @@ -33,10 +33,6 @@ config.AddUserSecrets(Assembly.GetExecutingAssembly()); } Console.WriteLine($"Using configuration file: appsettings.{context.HostingEnvironment.EnvironmentName}.json"); - foreach (var arg in context.Configuration.AsEnumerable()) - { - Console.WriteLine($"Configuration: {arg.Key} => {arg.Value}"); - } }); builder.ConfigureServices((context, services) => { @@ -56,44 +52,6 @@ .AddImportFeature(context.Configuration); // Configure other services. }); -builder.ConfigureCocona(args, configureApplication: app => -{ - // Configure your app's commands normally as you would with app - app.UseImportFeature(); -}); + var app = builder.Build(); await app.RunAsync(); -//var builder = CoconaApp.CreateBuilder(); -//builder.Configuration -// .AddEnvironmentVariables() -// .AddJsonFile("appsettings.json", false, false) -// .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", false, false); -//Console.WriteLine($"Using configuration file: appsettings.{builder.Environment.EnvironmentName}.json"); -//foreach (var arg in args) -//{ -// Console.WriteLine($"Argument: {arg}"); -//} -//if (!builder.Environment.IsProduction()) -//{ -// //Secrets should never be in clear text in source controlled file such appsettings.json. -// //For Developement, we therefore store them locally into UserSecrets Store, part of dotnet foundation. -// //For Production, secrets can be either written into appsettings.Production.json file by pipeline -// //or you can configure another Configuration Provider to provide the secrets like AzureKeyvault or Hashicorp Vault. -// builder.Configuration.AddUserSecrets(Assembly.GetExecutingAssembly()); -//} -//builder.Services -// .AddLogging(lb => lb.AddConsole()) -// .Configure(builder.Configuration.GetSection("Dataverse")) -// .Configure(builder.Configuration.GetSection("Dataverse")) -// .AddTransient() -// .AddSingleton() -// .AddTransient() -// .AddTransient((sp) => (ServiceClient)sp.GetRequiredService().Create()) -// .AddSingleton() -// .AddDataverseClient() -// .AddImportFeature(); -//Console.WriteLine($"Services are completed"); -//var app = builder.Build(); -//app.UseImportFeature(); -//Console.WriteLine($"Running App"); -//await app.RunAsync(); \ No newline at end of file diff --git a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Services/Dataverse/SdkDataverseServiceFactory.cs b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Services/Dataverse/SdkDataverseServiceFactory.cs index 02ad5f0..50f07f4 100644 --- a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Services/Dataverse/SdkDataverseServiceFactory.cs +++ b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Services/Dataverse/SdkDataverseServiceFactory.cs @@ -18,10 +18,6 @@ public SdkDataverseServiceFactory(IOptions op } public IOrganizationServiceAsync2 Create() { - _logger.LogInformation("ClientId Length", _options.ClientId.ToString().Length); - _logger.LogInformation("ClientSecret Length", _options.ClientSecret.ToString().Length); - _logger.LogInformation("Url Length", _options.Url.ToString().Length); - // throw new InvalidOperationException($"test: {_options.Url} {_options.ClientId} {_options.ClientSecret}"); var serviceClient = new ServiceClient( new Uri(_options.Url), _options.ClientId.ToString(),