diff --git a/.github/workflows/pipelines.yml b/.github/workflows/pipelines.yml
index 2f4dfa2..608db0c 100644
--- a/.github/workflows/pipelines.yml
+++ b/.github/workflows/pipelines.yml
@@ -21,7 +21,7 @@ on:
jobs:
build:
name: 🛠️ Build
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
strategy:
matrix:
configuration: [Debug, Release]
@@ -62,7 +62,7 @@ jobs:
pack:
name: 📦 Pack
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
strategy:
matrix:
configuration: [Debug, Release]
@@ -80,10 +80,16 @@ jobs:
uploadPackedArtifact: true
version: ${{ needs.build.outputs.version }}
- sonarcloud:
- name: 🔬 Code Quality Analysis
+ test:
+ name: 🧪 Test
needs: [build]
- runs-on: ubuntu-22.04
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-24.04, windows-2022]
+ configuration: [Debug, Release]
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 15
steps:
- name: Checkout
uses: codebeltnet/git-checkout@v1
@@ -93,64 +99,43 @@ jobs:
with:
includePreview: true
- - name: Install .NET Tool - Sonar Scanner
- uses: codebeltnet/dotnet-tool-install-sonarscanner@v1
-
- - name: Restore Dependencies
- uses: codebeltnet/dotnet-restore@v2
-
- - name: Run SonarCloud Analysis
- uses: codebeltnet/sonarcloud-scan@v1
- with:
- token: ${{ secrets.SONAR_TOKEN }}
- organization: geekle
- projectKey: bootstrapper
- version: ${{ needs.build.outputs.version }}
+ - name: Install .NET Tool - Report Generator
+ uses: codebeltnet/dotnet-tool-install-reportgenerator@v1
- - name: Build
- uses: codebeltnet/dotnet-build@v2
+ - name: Test with ${{ matrix.configuration }} build
+ uses: codebeltnet/dotnet-test@v3
with:
+ configuration: ${{ matrix.configuration }}
buildSwitches: -p:SkipSignAssembly=true
- uploadBuildArtifact: false
- - name: Finalize SonarCloud Analysis
- uses: codebeltnet/sonarcloud-scan-finalize@v1
- with:
- token: ${{ secrets.SONAR_TOKEN }}
+ sonarcloud:
+ name: call-sonarcloud
+ needs: [build,test]
+ uses: codebeltnet/jobs-sonarcloud/.github/workflows/default.yml@v1
+ with:
+ organization: geekle
+ projectKey: bootstrapper
+ version: ${{ needs.build.outputs.version }}
+ secrets: inherit
+
+ codecov:
+ name: call-codecov
+ needs: [build,test]
+ uses: codebeltnet/jobs-codecov/.github/workflows/default.yml@v1
+ with:
+ repository: codebeltnet/bootstrapper
+ secrets: inherit
codeql:
- name: 🛡️ Security Analysis
- needs: [build]
- runs-on: ubuntu-22.04
- steps:
- - name: Checkout
- uses: codebeltnet/git-checkout@v1
-
- - name: Install .NET
- uses: codebeltnet/install-dotnet@v1
- with:
- includePreview: true
-
- - name: Restore Dependencies
- uses: codebeltnet/dotnet-restore@v2
-
- - name: Prepare CodeQL SAST Analysis
- uses: codebeltnet/codeql-scan@v1
-
- - name: Build
- uses: codebeltnet/dotnet-build@v2
- with:
- buildSwitches: -p:SkipSignAssembly=true
- uploadBuildArtifact: false
-
- - name: Finalize CodeQL SAST Analysis
- uses: codebeltnet/codeql-scan-finalize@v1
+ name: call-codeql
+ needs: [build,test]
+ uses: codebeltnet/jobs-codeql/.github/workflows/default.yml@v1
deploy:
if: github.event_name != 'pull_request'
name: 🚀 Deploy v${{ needs.build.outputs.version }}
- runs-on: ubuntu-22.04
- needs: [build,pack,sonarcloud,codeql]
+ runs-on: ubuntu-24.04
+ needs: [build, pack, test, sonarcloud, codecov, codeql]
environment: Production
steps:
- uses: codebeltnet/nuget-push@v1
diff --git a/Codebelt.Bootstrapper.sln b/Codebelt.Bootstrapper.sln
index 9922c94..e4684f1 100644
--- a/Codebelt.Bootstrapper.sln
+++ b/Codebelt.Bootstrapper.sln
@@ -39,6 +39,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codebelt.Bootstrapper.Minim
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codebelt.Bootstrapper.MinimalWorker.App", "app\Codebelt.Bootstrapper.MinimalWorker.App\Codebelt.Bootstrapper.MinimalWorker.App.csproj", "{E87D127B-4CD1-4009-962E-EEF70A522C13}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codebelt.Bootstrapper.Tests", "test\Codebelt.Bootstrapper.Tests\Codebelt.Bootstrapper.Tests.csproj", "{CC656409-7479-4553-8E9B-0854801E1590}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codebelt.Bootstrapper.FunctionalTests", "test\Codebelt.Bootstrapper.FunctionalTests\Codebelt.Bootstrapper.FunctionalTests.csproj", "{8970491F-E0BD-489D-AA7E-617A67C2E39F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -109,6 +115,14 @@ Global
{E87D127B-4CD1-4009-962E-EEF70A522C13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E87D127B-4CD1-4009-962E-EEF70A522C13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E87D127B-4CD1-4009-962E-EEF70A522C13}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CC656409-7479-4553-8E9B-0854801E1590}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CC656409-7479-4553-8E9B-0854801E1590}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CC656409-7479-4553-8E9B-0854801E1590}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CC656409-7479-4553-8E9B-0854801E1590}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8970491F-E0BD-489D-AA7E-617A67C2E39F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8970491F-E0BD-489D-AA7E-617A67C2E39F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8970491F-E0BD-489D-AA7E-617A67C2E39F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8970491F-E0BD-489D-AA7E-617A67C2E39F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -130,6 +144,8 @@ Global
{3C43A01F-51E7-4166-91D5-E2088E68F4E5} = {28409021-5670-4008-9061-DBAAA0FC4DA6}
{497AD6CD-1E7E-4B4D-ACE5-CDD005A14F96} = {28409021-5670-4008-9061-DBAAA0FC4DA6}
{E87D127B-4CD1-4009-962E-EEF70A522C13} = {28409021-5670-4008-9061-DBAAA0FC4DA6}
+ {CC656409-7479-4553-8E9B-0854801E1590} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+ {8970491F-E0BD-489D-AA7E-617A67C2E39F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {72FB037E-3629-4CDB-812E-D577A3D4FD26}
diff --git a/Directory.Build.props b/Directory.Build.props
index c107324..6e78383 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -73,6 +73,6 @@
-
+
diff --git a/Directory.Packages.props b/Directory.Packages.props
index c60caa3..a26b3ba 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -4,26 +4,26 @@
-
-
-
-
+
+
+
+
-
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestBackgroundService.cs b/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestBackgroundService.cs
new file mode 100644
index 0000000..ceb5176
--- /dev/null
+++ b/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestBackgroundService.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace Codebelt.Bootstrapper.Assets
+{
+ public class TestBackgroundService : BackgroundService
+ {
+ private readonly ILogger _logger;
+
+ public TestBackgroundService(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public TimeSpan Elapsed { get; private set; } = TimeSpan.Zero;
+
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ var sw = Stopwatch.StartNew();
+ await this.WaitForApplicationStartedAnnouncementAsync(stoppingToken).ConfigureAwait(false);
+ sw.Stop();
+ Elapsed = sw.Elapsed;
+ _logger.LogInformation("TestBackgroundService started after {Elapsed}", Elapsed);
+ }
+ }
+}
diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestFixture.cs b/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestFixture.cs
new file mode 100644
index 0000000..52f0c27
--- /dev/null
+++ b/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestFixture.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+using Codebelt.Extensions.Xunit.Hosting;
+using Codebelt.Extensions.Xunit;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+
+namespace Codebelt.Bootstrapper.Assets
+{
+ public class TestHostFixture : HostFixture
+ {
+ public override void ConfigureHost(Test hostTest)
+ {
+ var hb = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder()
+ .ConfigureAppConfiguration((context, config) =>
+ {
+ ConfigureCallback(config.Build(), context.HostingEnvironment);
+ })
+ .ConfigureServices((context, services) =>
+ {
+ Configuration = context.Configuration;
+ HostingEnvironment = context.HostingEnvironment;
+ ConfigureServicesCallback(services);
+ })
+ .ConfigureHostConfiguration(builder =>
+ {
+ builder.AddInMemoryCollection(new Dictionary
+ {
+ { HostDefaults.ApplicationKey, hostTest.CallerType.Assembly.GetName().Name }
+ });
+ });
+ ConfigureHostCallback(hb);
+ Host = hb.Build();
+ }
+ }
+}
diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestStartup.cs b/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestStartup.cs
new file mode 100644
index 0000000..d8461ab
--- /dev/null
+++ b/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestStartup.cs
@@ -0,0 +1,17 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace Codebelt.Bootstrapper.Assets
+{
+ public class TestStartup : StartupRoot
+ {
+ public TestStartup(IConfiguration configuration, IHostEnvironment environment) : base(configuration, environment)
+ {
+ }
+
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ }
+ }
+}
diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs b/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs
new file mode 100644
index 0000000..79d35fb
--- /dev/null
+++ b/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Threading;
+using Codebelt.Bootstrapper.Assets;
+using Codebelt.Extensions.Xunit;
+using Codebelt.Extensions.Xunit.Hosting;
+using Microsoft.Extensions.Hosting;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Codebelt.Bootstrapper
+{
+ public class BootstrapperLifetimeTest : Test
+ {
+ private bool _started = false;
+ private bool _stopping = false;
+ private bool _stopped = false;
+
+ public BootstrapperLifetimeTest(ITestOutputHelper output) : base(output)
+ {
+ BootstrapperLifetime.OnApplicationStartedCallback = () => { _started = true; };
+ BootstrapperLifetime.OnApplicationStoppingCallback = () => { _stopping = true; };
+ BootstrapperLifetime.OnApplicationStoppedCallback = () => { _stopped = true; };
+ }
+
+ [Fact]
+ public void OnApplicationStartedCallback_ShouldBeInvokedWhenStartingHost()
+ {
+ using var test = GenericHostTestFactory.Create(services =>
+ {
+ services.AddXunitTestLoggingOutputHelperAccessor();
+ services.AddXunitTestLogging(TestOutput);
+ }, hb =>
+ {
+ hb.UseBootstrapperLifetime();
+ hb.UseBootstrapperStartup();
+ }, new TestHostFixture());
+
+ test.Host.Start();
+
+ Assert.True(_started);
+ }
+
+ [Fact]
+ public void OnApplicationStoppingCallback_OnApplicationStoppedCallback_ShouldBeInvokedWhenStoppingHost()
+ {
+ using var test = GenericHostTestFactory.Create(services =>
+ {
+ services.AddXunitTestLoggingOutputHelperAccessor();
+ services.AddXunitTestLogging(TestOutput);
+ }, hb =>
+ {
+ hb.UseBootstrapperLifetime();
+ hb.UseBootstrapperStartup();
+ }, new TestHostFixture());
+
+ test.Host.Start();
+ test.Host.StopAsync().GetAwaiter().GetResult();
+
+ Assert.True(_stopping && _stopped);
+ }
+ }
+}
diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/Codebelt.Bootstrapper.FunctionalTests.csproj b/test/Codebelt.Bootstrapper.FunctionalTests/Codebelt.Bootstrapper.FunctionalTests.csproj
new file mode 100644
index 0000000..5e9152a
--- /dev/null
+++ b/test/Codebelt.Bootstrapper.FunctionalTests/Codebelt.Bootstrapper.FunctionalTests.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Codebelt.Bootstrapper
+
+
+
+
+
+ <_Parameter1>DisableTestParallelization = true
+ <_Parameter1_IsLiteral>true
+
+
+
+
+
+
+
+
diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/HostApplicationBuilderExtensionsTest.cs b/test/Codebelt.Bootstrapper.FunctionalTests/HostApplicationBuilderExtensionsTest.cs
new file mode 100644
index 0000000..0b7c598
--- /dev/null
+++ b/test/Codebelt.Bootstrapper.FunctionalTests/HostApplicationBuilderExtensionsTest.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Codebelt.Bootstrapper.Assets;
+using Codebelt.Extensions.Xunit;
+using Codebelt.Extensions.Xunit.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Xunit;
+using Xunit.Abstractions;
+using static System.Net.Mime.MediaTypeNames;
+
+namespace Codebelt.Bootstrapper
+{
+ public class HostApplicationBuilderExtensionsTest : Test
+ {
+ public HostApplicationBuilderExtensionsTest(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public void UseBootstrapperLifetime_ShouldRegisterBootstrapperLifetime()
+ {
+ var host = Host.CreateApplicationBuilder()
+ .UseBootstrapperLifetime()
+ .Build();
+
+ var bootstrapperLifetime = host.Services.GetService();
+
+ Assert.NotNull(bootstrapperLifetime);
+ Assert.IsType(bootstrapperLifetime);
+ }
+ }
+}
diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/HostBuilderExtensionsTest.cs b/test/Codebelt.Bootstrapper.FunctionalTests/HostBuilderExtensionsTest.cs
new file mode 100644
index 0000000..defe9b4
--- /dev/null
+++ b/test/Codebelt.Bootstrapper.FunctionalTests/HostBuilderExtensionsTest.cs
@@ -0,0 +1,49 @@
+using Codebelt.Bootstrapper.Assets;
+using Codebelt.Extensions.Xunit;
+using Codebelt.Extensions.Xunit.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Codebelt.Bootstrapper
+{
+ public class HostBuilderExtensionsTest : Test
+ {
+ public HostBuilderExtensionsTest(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public void UseBootstrapperLifetime_ShouldRegisterBootstrapperLifetime()
+ {
+ using var test = GenericHostTestFactory.Create(services =>
+ {
+ }, hb =>
+ {
+ hb.UseBootstrapperLifetime();
+ }, new TestHostFixture());
+
+ var bootstrapperLifetime = test.ServiceProvider.GetService();
+
+ Assert.NotNull(bootstrapperLifetime);
+ Assert.IsType(bootstrapperLifetime);
+ }
+
+ [Fact]
+ public void UseBootstrapperStartup_ShouldRegisterStartupFactory()
+ {
+ using var test = GenericHostTestFactory.Create(services =>
+ {
+ }, hb =>
+ {
+ hb.UseBootstrapperStartup();
+ }, new TestHostFixture());
+
+ var startupFactory = test.ServiceProvider.GetService>();
+
+ Assert.NotNull(startupFactory);
+ Assert.IsType>(startupFactory);
+ }
+ }
+}
diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/HostedServiceExtensionsTest.cs b/test/Codebelt.Bootstrapper.FunctionalTests/HostedServiceExtensionsTest.cs
new file mode 100644
index 0000000..a830336
--- /dev/null
+++ b/test/Codebelt.Bootstrapper.FunctionalTests/HostedServiceExtensionsTest.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Codebelt.Bootstrapper.Assets;
+using Codebelt.Extensions.Xunit;
+using Codebelt.Extensions.Xunit.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Codebelt.Bootstrapper
+{
+ public class HostedServiceExtensionsTest : Test
+ {
+ public HostedServiceExtensionsTest(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public async Task WaitForApplicationStartedAnnouncementAsync_MustWaitForApplicationStarted()
+ {
+ var timeToWait = TimeSpan.FromMilliseconds(50);
+ var started = false;
+ BootstrapperLifetime.OnApplicationStartedCallback = () =>
+ {
+ Thread.Sleep(timeToWait);
+ };
+
+ using var test = GenericHostTestFactory.Create(services =>
+ {
+ services.AddXunitTestLoggingOutputHelperAccessor();
+ services.AddXunitTestLogging(TestOutput);
+ services.AddHostedService();
+ }, hb =>
+ {
+ hb.UseBootstrapperLifetime();
+ }, new TestHostFixture());
+
+ var bgs = test.ServiceProvider.GetRequiredService() as TestBackgroundService;
+
+ await test.Host.StartAsync().ConfigureAwait(false);
+
+ await Task.Delay(timeToWait).ConfigureAwait(false);
+
+ Assert.True(bgs.Elapsed >= timeToWait, $"{bgs.Elapsed} >= {timeToWait}");
+ }
+ }
+}
diff --git a/test/Codebelt.Bootstrapper.Tests/Assets/StartupRootUnsafeAccessor.cs b/test/Codebelt.Bootstrapper.Tests/Assets/StartupRootUnsafeAccessor.cs
new file mode 100644
index 0000000..fca1272
--- /dev/null
+++ b/test/Codebelt.Bootstrapper.Tests/Assets/StartupRootUnsafeAccessor.cs
@@ -0,0 +1,15 @@
+using System.Runtime.CompilerServices;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+
+namespace Codebelt.Bootstrapper.Assets
+{
+ public static class StartupRootUnsafeAccessor
+ {
+ [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "get_Configuration")]
+ public static extern IConfiguration GetConfiguration(StartupRoot startup);
+
+ [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "get_Environment")]
+ public static extern IHostEnvironment GetEnvironment(StartupRoot startup);
+ }
+}
diff --git a/test/Codebelt.Bootstrapper.Tests/Assets/TestStartupRoot.cs b/test/Codebelt.Bootstrapper.Tests/Assets/TestStartupRoot.cs
new file mode 100644
index 0000000..34df4c4
--- /dev/null
+++ b/test/Codebelt.Bootstrapper.Tests/Assets/TestStartupRoot.cs
@@ -0,0 +1,20 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace Codebelt.Bootstrapper.Assets
+{
+ public class TestStartupRoot : StartupRoot
+ {
+ public TestStartupRoot(IConfiguration configuration, IHostEnvironment environment)
+ : base(configuration, environment)
+ {
+ }
+
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ // Add test services here
+ services.AddSingleton("TestService");
+ }
+ }
+}
diff --git a/test/Codebelt.Bootstrapper.Tests/Codebelt.Bootstrapper.Tests.csproj b/test/Codebelt.Bootstrapper.Tests/Codebelt.Bootstrapper.Tests.csproj
new file mode 100644
index 0000000..dc95b98
--- /dev/null
+++ b/test/Codebelt.Bootstrapper.Tests/Codebelt.Bootstrapper.Tests.csproj
@@ -0,0 +1,11 @@
+
+
+
+ Codebelt.Bootstrapper
+
+
+
+
+
+
+
diff --git a/test/Codebelt.Bootstrapper.Tests/StartupRootTest.cs b/test/Codebelt.Bootstrapper.Tests/StartupRootTest.cs
new file mode 100644
index 0000000..65d5471
--- /dev/null
+++ b/test/Codebelt.Bootstrapper.Tests/StartupRootTest.cs
@@ -0,0 +1,81 @@
+using System.Collections.Generic;
+using System.IO;
+using Codebelt.Bootstrapper.Assets;
+using Codebelt.Extensions.Xunit;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Hosting.Internal;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Codebelt.Bootstrapper
+{
+ public class StartupRootTest : Test
+ {
+ private readonly IConfiguration _configuration;
+ private readonly IHostEnvironment _environment;
+
+ public StartupRootTest(ITestOutputHelper output) : base(output)
+ {
+ var inMemorySettings = new Dictionary {
+ {"TestKey", "TestValue"}
+ };
+
+ _configuration = new ConfigurationBuilder()
+ .AddInMemoryCollection(inMemorySettings)
+ .Build();
+
+ _environment = new HostingEnvironment()
+ {
+ EnvironmentName = Environments.Development,
+ ApplicationName = "TestApp",
+ ContentRootPath = Directory.GetCurrentDirectory()
+ };
+ }
+
+ [Fact]
+ public void ConfigurationProperty_ShouldReturnInjectedConfiguration()
+ {
+ // Arrange
+ var startup = new TestStartupRoot(_configuration, _environment);
+
+ // Act
+ var configuration = StartupRootUnsafeAccessor.GetConfiguration(startup);
+
+ // Assert
+ Assert.Equal(_configuration, configuration);
+ }
+
+
+
+ [Fact]
+ public void EnvironmentProperty_ShouldReturnInjectedEnvironment()
+ {
+ // Arrange
+ var startup = new TestStartupRoot(_configuration, _environment);
+
+ // Act
+ var environment = StartupRootUnsafeAccessor.GetEnvironment(startup);
+
+ // Assert
+ Assert.Equal(_environment, environment);
+ }
+
+ [Fact]
+ public void ConfigureServices_ShouldAddServicesToServiceCollection()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var startup = new TestStartupRoot(_configuration, _environment);
+
+ // Act
+ startup.ConfigureServices(services);
+ var serviceProvider = services.BuildServiceProvider();
+ var testService = serviceProvider.GetService();
+
+ // Assert
+ Assert.Equal("TestService", testService);
+ }
+ }
+}
diff --git a/testenvironments.json b/testenvironments.json
new file mode 100644
index 0000000..328e82b
--- /dev/null
+++ b/testenvironments.json
@@ -0,0 +1,15 @@
+{
+ "version": "1",
+ "environments": [
+ {
+ "name": "WSL-Ubuntu",
+ "type": "wsl",
+ "wslDistribution": "Ubuntu-24.04"
+ },
+ {
+ "name": "Docker-Ubuntu",
+ "type": "docker",
+ "dockerImage": "gimlichael/ubuntu-testrunner:net8.0.407-9.0.202"
+ }
+ ]
+}