Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/AdaptiveRemote.App/AppHostBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using AdaptiveRemote.Configuration;
using AdaptiveRemote.Services.Lifecycle;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

Expand All @@ -17,10 +18,13 @@ public static IHostBuilder ConfigureApp(this IHostBuilder hostBuilder)
.AddSystemWrapperServices()
.OptionallyAddTestHookEndpoint();

public static IHostBuilder ConfigureAppSettings(this IHostBuilder hostBuilder, string[] args)
public static IHostBuilder ConfigureAppSettings(this IHostBuilder hostBuilder, AcceleratedServices acceleratedServices)
=> hostBuilder
.ConfigureAppConfiguration(config =>
{
// Add startup configuration sources to maintain consistency between startup and host
config.AddConfiguration(acceleratedServices.StartupConfig);

config.AddInMemoryCollection(new Dictionary<string, string?>
{
// This makes the default behavior to publish telemetry when the full application
Expand All @@ -31,7 +35,6 @@ public static IHostBuilder ConfigureAppSettings(this IHostBuilder hostBuilder, s
// ["telemetry:Publish"] = "True"
});
config.AddUserSecrets<UserSecretsKey>();
config.AddCommandLine(args);
});

// This class is used to locate the user secrets assembly for this project.
Expand Down
2 changes: 1 addition & 1 deletion src/AdaptiveRemote.App/AppHostRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public async Task RunAsync(CancellationToken cancellationToken)
{
IHostBuilder hostBuilder = ConfigureHostBuilder()
.ConfigureServices(acceleratedServices.AddPrecreatedServices)
.ConfigureAppSettings(CommandLineArguments)
.ConfigureAppSettings(acceleratedServices)
.ConfigureApp();

// Allow tests to inject services before the host is built
Expand Down
71 changes: 41 additions & 30 deletions src/AdaptiveRemote.App/Services/Lifecycle/AcceleratedServices.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using AdaptiveRemote.Models;
using AdaptiveRemote.Configuration;
using AdaptiveRemote.Logging;
using AdaptiveRemote.Models;
using AdaptiveRemote.Services.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -14,10 +16,15 @@ public class AcceleratedServices
internal ITestEndpointHooks TestEndpoint { get; }

/// <summary>
/// Command line configuration parsed from arguments.
/// Available for any startup services that need command line settings.
/// Raw command-line arguments passed to the application.
/// </summary>
public IConfigurationRoot CommandLineConfig { get; }
internal string[] Args { get; }

/// <summary>
/// Startup configuration built from appsettings.json, environment variables, and command-line args.
/// Available for any startup services that need settings before the host is configured.
/// </summary>
public IConfigurationRoot StartupConfig { get; }

/// <summary>
/// Logger factory for startup processes.
Expand All @@ -27,31 +34,46 @@ public class AcceleratedServices

public AcceleratedServices(string[] args)
{
// Parse command line configuration early
Args = args;

// Build startup configuration from appsettings.json, environment variables, and command line args
string environmentName = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production";
string basePath = AppContext.BaseDirectory;

ConfigurationBuilder configBuilder = new();
CommandLineConfig = configBuilder
.AddCommandLine(args)
.Build();
configBuilder.SetBasePath(basePath);
configBuilder.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false);
configBuilder.AddJsonFile($"appsettings.{environmentName}.json", optional: true, reloadOnChange: false);
configBuilder.AddEnvironmentVariables();
configBuilder.AddCommandLine(args);
StartupConfig = configBuilder.Build();

// Create logger factory for startup processes, configured from StartupConfig
LoggerFactory = Microsoft.Extensions.Logging.LoggerFactory.Create(builder =>
{
builder.AddConsole();
builder.AddConfiguration(StartupConfig.GetSection("Logging"));

// Create logger factory for startup processes
LoggerFactory = Microsoft.Extensions.Logging.LoggerFactory.Create(builder => builder.AddConsole());
// Add file logging if configured
LoggingSettings loggingSettings = StartupConfig.GetSection(SettingsKeys.Logging).Get<LoggingSettings>()
?? new LoggingSettings();
if (loggingSettings.FilePath is not null)
{
builder.AddProvider(new FileLoggerProvider(loggingSettings.FilePath));
}
});

ViewModel = new();
Controller = new LifecycleViewController(ViewModel);
DiagnosticAdapter = new(Controller);

// Check if test control port is configured
int? controlPort = ParseControlPort();
if (controlPort.HasValue)
TestingSettings testingSettings = StartupConfig.GetSection(SettingsKeys.Testing).Get<TestingSettings>()
?? new TestingSettings();
if (testingSettings.ControlPort.HasValue)
{
// Create TestingSettings from command line config
TestingSettings testSettings = new()
{
ControlPort = controlPort
};

// Create and start the test endpoint service
TestEndpointService testEndpointService = new(testSettings, LoggerFactory);
TestEndpointService testEndpointService = new(testingSettings, LoggerFactory);
testEndpointService.StartListening();
TestEndpoint = testEndpointService;
}
Expand All @@ -69,15 +91,4 @@ public virtual void AddPrecreatedServices(IServiceCollection services)
.AddSingleton(Controller)
.AddSingleton(ViewModel);
}

private int? ParseControlPort()
{
string? portString = CommandLineConfig["test:ControlPort"];
if (int.TryParse(portString, out int port))
{
return port;
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ public WpfAcceleratedServices(string[] args)
{
MainWindow = new(ViewModel);

TestingSettings? settings = new ConfigurationBuilder()
.AddCommandLine(args)
.Build()
TestingSettings? settings = StartupConfig
.GetSection("test")
.Get<TestingSettings>();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
using AdaptiveRemote;
using FluentAssertions;
using Microsoft.Extensions.Configuration;

namespace AdaptiveRemote.Services.Lifecycle;

[TestClass]
public class AcceleratedServicesTests
{
[TestMethod]
public void AcceleratedServices_Constructor_InitializesArgs()
{
// Arrange
string[] args = ["--foo=bar"];

// Act
AcceleratedServices sut = new(args);

// Assert
sut.Args.Should().BeSameAs(args);
}

[TestMethod]
public void AcceleratedServices_Constructor_BuildsStartupConfigFromCommandLineArgs()
{
// Arrange
string[] args = ["--custom=value"];

// Act
AcceleratedServices sut = new(args);

// Assert
sut.StartupConfig["custom"].Should().Be("value");
}

[TestMethod]
public void AcceleratedServices_StartupConfig_CommandLineArgsOverrideEnvironmentVariables()
{
// Arrange
string envVarName = "ACCELERATED_SERVICES_TEST_VAR";
Environment.SetEnvironmentVariable(envVarName, "env-value");
try
{
string[] args = [$"--{envVarName}=cli-value"];

// Act
AcceleratedServices sut = new(args);

// Assert
sut.StartupConfig[envVarName].Should().Be("cli-value");
}
finally
{
Environment.SetEnvironmentVariable(envVarName, null);
}
}

[TestMethod]
public void AcceleratedServices_StartupConfig_IncludesEnvironmentVariables()
{
// Arrange
string envVarName = "ACCELERATED_SERVICES_TEST_ENV";
Environment.SetEnvironmentVariable(envVarName, "env-value");
try
{
string[] args = [];

// Act
AcceleratedServices sut = new(args);

// Assert
sut.StartupConfig[envVarName].Should().Be("env-value");
}
finally
{
Environment.SetEnvironmentVariable(envVarName, null);
}
}

[TestMethod]
public void AcceleratedServices_LoggerFactory_IsConfigured()
{
// Arrange
string[] args = [];

// Act
AcceleratedServices sut = new(args);

// Assert
sut.LoggerFactory.Should().NotBeNull();
}

[TestMethod]
public void AcceleratedServices_ViewModel_IsInitialized()
{
// Arrange
string[] args = [];

// Act
AcceleratedServices sut = new(args);

// Assert
sut.ViewModel.Should().NotBeNull();
sut.ViewModel.CurrentPhase.Should().Be(LifecyclePhase.Waiting);
}

[TestMethod]
public void AcceleratedServices_Controller_IsInitialized()
{
// Arrange
string[] args = [];

// Act
AcceleratedServices sut = new(args);

// Assert
sut.Controller.Should().NotBeNull();
}

[TestMethod]
public void AcceleratedServices_DiagnosticAdapter_IsInitialized()
{
// Arrange
string[] args = [];

// Act
AcceleratedServices sut = new(args);

// Assert
sut.DiagnosticAdapter.Should().NotBeNull();
}

[TestMethod]
public void AcceleratedServices_TestEndpoint_StartsWhenControlPortConfigured()
{
// Arrange
int port = Random.Shared.Next(10000, 60000);
string[] args = [$"--test:ControlPort={port}"];

// Act
AcceleratedServices sut = new(args);

// Assert
sut.TestEndpoint.Should().NotBeNull();
sut.TestEndpoint.Should().BeOfType<Testing.TestEndpointService>();
}

[TestMethod]
public void AcceleratedServices_TestEndpoint_DisabledWhenControlPortNotConfigured()
{
// Arrange
string[] args = [];

// Act
AcceleratedServices sut = new(args);

// Assert
sut.TestEndpoint.Should().NotBeNull();
sut.TestEndpoint.Should().BeOfType<Testing.DisabledTestEndpointHooks>();
}
}
Loading