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
64 changes: 62 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
name: CI/CD

on:
pull_request:
branches: [main]
push:
branches: [main]

Expand Down Expand Up @@ -159,6 +161,64 @@ jobs:
contents: read

steps:
- name: All checks passed
run: echo "All resource checks passed. E2E integration tests will be added here."
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
toolchain: stable
targets: wasm32-unknown-unknown

- name: Install cargo-binstall
uses: cargo-bins/cargo-binstall@0b24824336e2b3800b0f89d9e08b2c08bfa3dcdd # v1.17.9

- name: Install tools
run: cargo binstall -y trunk wasm-bindgen-cli
shell: bash

- name: Cache Rust compilation
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
workspaces: |
client-management -> target
ui -> target
cache-on-failure: true

- name: Setup .NET
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7
with:
dotnet-version: 10.x

- name: Restore Aspire AppHost Dependencies
run: dotnet restore DistHaul.sln
shell: bash
working-directory: DistHaul

- name: Build Aspire AppHost
run: dotnet build DistHaul.sln --configuration Release --no-restore
shell: bash
working-directory: DistHaul

- name: Execute Aspire AppHost Tests
id: test
run: dotnet test --solution DistHaul.sln --configuration Release --no-build --verbosity normal --results-directory ./TestResults --coverage --coverage-output-format cobertura
shell: bash
working-directory: DistHaul

- name: Code Coverage Report
run: |
dotnet tool install --global dotnet-reportgenerator-globaltool --version 5.5.4
reportgenerator -reports:TestResults/**.cobertura.xml -targetdir:coveragereport -reporttypes:Html "-filefilters:-*.g.cs" -verbosity:Warning
cd coveragereport
zip -r ../code-coverage-report.zip .
shell: bash
working-directory: DistHaul

- name: Archive Test Report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # 7.0.1
if: ${{ steps.test.conclusion == 'success' && always()}}
with:
name: aspire-code-coverage-report
path: ./DistHaul/coveragereport/
retention-days: 7
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>$(NoWarn);ASPIRECERTIFICATES001</NoWarn>
</PropertyGroup>

Expand All @@ -14,6 +15,7 @@
<PackageReference Include="Aspire.Hosting.Keycloak" Version="13.2.2-preview.1.26207.2" />
<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="13.2.2" />
<PackageReference Include="Aspire.Hosting.Redis" Version="13.2.2" />
<PackageReference Include="Aspire.Hosting.Testing" Version="13.2.2" />
</ItemGroup>

</Project>
File renamed without changes.
35 changes: 35 additions & 0 deletions DistHaul/DistHaul.Tests/DistHaul.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Exe</OutputType>
<RootNamespace>DistHaul.Tests</RootNamespace>
<TargetFramework>net10.0</TargetFramework>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
<PackageReference Include="Microsoft.Testing.Extensions.CodeCoverage" Version="18.6.2" />
<PackageReference Include="xunit.v3.mtp-v2" Version="3.2.2"/>
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

<ItemGroup>
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\DistHaul.AppHost\DistHaul.AppHost.csproj" />
</ItemGroup>

</Project>
10 changes: 10 additions & 0 deletions DistHaul/DistHaul.Tests/UnitTest1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace DistHaul.Tests;

public class UnitTest1
{
[Fact]
public void Test1()
{
Assert.True(true);
}
Comment on lines +1 to +9

Copilot AI Apr 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert.True(true) is a no-op test and won’t catch regressions. Replace this with a meaningful test that exercises the Aspire AppHost (e.g., using Aspire.Hosting.Testing to start the distributed app and assert expected resources/endpoints are healthy).

Suggested change
namespace DistHaul.Tests;
public class UnitTest1
{
[Fact]
public void Test1()
{
Assert.True(true);
}
using System.Reflection;
using Aspire.Hosting.Testing;
namespace DistHaul.Tests;
public class UnitTest1
{
[Fact]
public async Task Test1()
{
var appHostType = FindAppHostType();
Assert.NotNull(appHostType);
var builder = await CreateTestingBuilderAsync(appHostType!);
Assert.NotNull(builder);
var buildAsyncMethod = builder.GetType().GetMethod("BuildAsync", Type.EmptyTypes);
Assert.NotNull(buildAsyncMethod);
var buildTask = (Task)buildAsyncMethod!.Invoke(builder, null)!;
await buildTask;
var app = buildTask.GetType().GetProperty("Result")!.GetValue(buildTask);
Assert.NotNull(app);
try
{
var startAsyncMethod = app!.GetType().GetMethod("StartAsync", Type.EmptyTypes);
Assert.NotNull(startAsyncMethod);
var startTask = (Task)startAsyncMethod!.Invoke(app, null)!;
await startTask;
}
finally
{
if (app is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}
else if (app is IDisposable disposable)
{
disposable.Dispose();
}
}
}
private static Type? FindAppHostType()
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
Type? appHostType;
try
{
appHostType = assembly
.GetTypes()
.FirstOrDefault(t =>
t is { IsClass: true, IsAbstract: false } &&
t.Namespace == "Projects" &&
t.Name.EndsWith("_AppHost", StringComparison.Ordinal));
}
catch (ReflectionTypeLoadException ex)
{
appHostType = ex.Types.FirstOrDefault(t =>
t is { IsClass: true, IsAbstract: false } &&
t.Namespace == "Projects" &&
t.Name.EndsWith("_AppHost", StringComparison.Ordinal));
}
if (appHostType is not null)
{
return appHostType;
}
}
return null;
}
private static async Task<object> CreateTestingBuilderAsync(Type appHostType)
{
var createAsyncMethod = typeof(DistributedApplicationTestingBuilder)
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.First(m => m.Name == "CreateAsync" && m.IsGenericMethodDefinition && m.GetParameters().Length == 0)
.MakeGenericMethod(appHostType);
var createTask = (Task)createAsyncMethod.Invoke(null, null)!;
await createTask;
return createTask.GetType().GetProperty("Result")!.GetValue(createTask)!;
}

Copilot uses AI. Check for mistakes.
}
3 changes: 3 additions & 0 deletions DistHaul/DistHaul.Tests/xunit.runner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json"
}
16 changes: 15 additions & 1 deletion Aspire/DistHaul.sln → DistHaul/DistHaul.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppHost", "AppHost\AppHost.csproj", "{5DA93E71-D203-44FF-9924-9FAE6497E935}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DistHaul.AppHost", "DistHaul.AppHost\DistHaul.AppHost.csproj", "{5DA93E71-D203-44FF-9924-9FAE6497E935}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DistHaul.Tests", "DistHaul.Tests\DistHaul.Tests.csproj", "{9B2CE424-D97E-4716-BE57-870DFC1B92B3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -27,6 +29,18 @@ Global
{5DA93E71-D203-44FF-9924-9FAE6497E935}.Release|x64.Build.0 = Release|Any CPU
{5DA93E71-D203-44FF-9924-9FAE6497E935}.Release|x86.ActiveCfg = Release|Any CPU
{5DA93E71-D203-44FF-9924-9FAE6497E935}.Release|x86.Build.0 = Release|Any CPU
{9B2CE424-D97E-4716-BE57-870DFC1B92B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9B2CE424-D97E-4716-BE57-870DFC1B92B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9B2CE424-D97E-4716-BE57-870DFC1B92B3}.Debug|x64.ActiveCfg = Debug|Any CPU
{9B2CE424-D97E-4716-BE57-870DFC1B92B3}.Debug|x64.Build.0 = Debug|Any CPU
{9B2CE424-D97E-4716-BE57-870DFC1B92B3}.Debug|x86.ActiveCfg = Debug|Any CPU
{9B2CE424-D97E-4716-BE57-870DFC1B92B3}.Debug|x86.Build.0 = Debug|Any CPU
{9B2CE424-D97E-4716-BE57-870DFC1B92B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9B2CE424-D97E-4716-BE57-870DFC1B92B3}.Release|Any CPU.Build.0 = Release|Any CPU
{9B2CE424-D97E-4716-BE57-870DFC1B92B3}.Release|x64.ActiveCfg = Release|Any CPU
{9B2CE424-D97E-4716-BE57-870DFC1B92B3}.Release|x64.Build.0 = Release|Any CPU
{9B2CE424-D97E-4716-BE57-870DFC1B92B3}.Release|x86.ActiveCfg = Release|Any CPU
{9B2CE424-D97E-4716-BE57-870DFC1B92B3}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
2 changes: 1 addition & 1 deletion aspire.config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"appHost": {
"path": "Aspire/AppHost/AppHost.csproj"
"path": "DistHaul/DistHaul.AppHost/DistHaul.AppHost.csproj"
}
}
4 changes: 2 additions & 2 deletions client-management/api/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ async fn main() -> Result<()> {

let settings = Settings::load().context("Failed to load configuration")?;

let telemetry_guard = telemetry::init_telemetry(&settings.tls)
.context("Failed to initialise telemetry")?;
let telemetry_guard =
telemetry::init_telemetry(&settings.tls).context("Failed to initialise telemetry")?;

let app = Router::new()
.route("/health", get(routes::health))
Expand Down
8 changes: 6 additions & 2 deletions client-management/api/src/telemetry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,12 @@ pub fn init_telemetry(tls_settings: &TlsSettings) -> Result<TelemetryGuard> {
/// trusted CA root allows tonic/rustls to complete the TLS handshake with the
/// Aspire dashboard's gRPC OTLP endpoint.
fn build_tls_config(tls_settings: &TlsSettings) -> Result<ClientTlsConfig> {
let pem = std::fs::read(Path::new(&tls_settings.certificate_path))
.with_context(|| format!("Failed to read TLS certificate from {}", tls_settings.certificate_path))?;
let pem = std::fs::read(Path::new(&tls_settings.certificate_path)).with_context(|| {
format!(
"Failed to read TLS certificate from {}",
tls_settings.certificate_path
)
})?;

let tls_config = ClientTlsConfig::new().ca_certificate(Certificate::from_pem(pem));

Expand Down
5 changes: 5 additions & 0 deletions global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"test": {
"runner": "Microsoft.Testing.Platform"
}
}