Skip to content
Closed
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
1 change: 1 addition & 0 deletions MyBlog.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
</Folder>
<Folder Name="/tests/">
<Project Path="tests/Architecture.Tests/Architecture.Tests.csproj" />
<Project Path="tests/E2E.Tests/E2E.Tests.csproj" />
<Project Path="tests/Integration.Tests/Integration.Tests.csproj" />
<Project Path="tests/Unit.Tests/Unit.Tests.csproj" />
</Folder>
Expand Down
38 changes: 38 additions & 0 deletions tests/E2E.Tests/E2E.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<RootNamespace>MyBlog.E2E.Tests</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="13.2.2" />
<PackageReference Include="coverlet.collector" Version="10.0.0" />
<PackageReference Include="FluentAssertions" Version="8.9.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.4.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" />
</ItemGroup>

<ItemGroup>
<Using Include="System.Net" />
<Using Include="Aspire.Hosting.ApplicationModel" />
<Using Include="Aspire.Hosting.Testing" />
<Using Include="Xunit" />
</ItemGroup>

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

<ItemGroup>
<None Update="xunit.runner.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
13 changes: 13 additions & 0 deletions tests/E2E.Tests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//=======================================================
//Copyright (c) 2026. All rights reserved.
//File Name : GlobalUsings.cs
//Company : mpaulosky
//Author : Matthew Paulosky
//Solution Name : MyBlog
//Project Name : E2E.Tests
//=======================================================

global using FluentAssertions;
global using Aspire.Hosting.ApplicationModel;
global using Aspire.Hosting.Testing;
global using Xunit;
44 changes: 44 additions & 0 deletions tests/E2E.Tests/WebAppTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//=======================================================
//Copyright (c) 2026. All rights reserved.
//File Name : WebAppTests.cs
//Company : mpaulosky
//Author : Matthew Paulosky
//Solution Name : MyBlog
//Project Name : E2E.Tests
//=======================================================

namespace MyBlog.E2E.Tests;

public class WebAppTests
{
private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(60);

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

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

Timeout handling is currently duplicated: a 60s CancellationTokenSource governs the entire test, and then each step also adds a separate WaitAsync(DefaultTimeout, cancellationToken). This makes it harder to reason about total time budget and failure modes (cancellation vs timeout exceptions). Consider either (a) using the CTS token only (no WaitAsync(timeout)), or (b) using step-specific timeouts without a global 60s CTS (or a larger global CTS plus smaller per-step timeouts).

Copilot uses AI. Check for mistakes.

[Fact]
public async Task GetWebResourceRootReturnsOkStatusCode()
{
// Arrange
var cancellationToken = new CancellationTokenSource(DefaultTimeout).Token;

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

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

Timeout handling is currently duplicated: a 60s CancellationTokenSource governs the entire test, and then each step also adds a separate WaitAsync(DefaultTimeout, cancellationToken). This makes it harder to reason about total time budget and failure modes (cancellation vs timeout exceptions). Consider either (a) using the CTS token only (no WaitAsync(timeout)), or (b) using step-specific timeouts without a global 60s CTS (or a larger global CTS plus smaller per-step timeouts).

Copilot uses AI. Check for mistakes.

var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.MyBlog_AppHost>(cancellationToken);

Check failure on line 23 in tests/E2E.Tests/WebAppTests.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'MyBlog_AppHost' does not exist in the namespace 'Projects' (are you missing an assembly reference?)

Check failure on line 23 in tests/E2E.Tests/WebAppTests.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'MyBlog_AppHost' does not exist in the namespace 'Projects' (are you missing an assembly reference?)

appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>

Check failure on line 25 in tests/E2E.Tests/WebAppTests.cs

View workflow job for this annotation

GitHub Actions / build-and-test

'IServiceCollection' does not contain a definition for 'ConfigureHttpClientDefaults' and no accessible extension method 'ConfigureHttpClientDefaults' accepting a first argument of type 'IServiceCollection' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 25 in tests/E2E.Tests/WebAppTests.cs

View workflow job for this annotation

GitHub Actions / build-and-test

'IServiceCollection' does not contain a definition for 'ConfigureHttpClientDefaults' and no accessible extension method 'ConfigureHttpClientDefaults' accepting a first argument of type 'IServiceCollection' could be found (are you missing a using directive or an assembly reference?)
{
clientBuilder.AddStandardResilienceHandler();
});

Comment on lines +25 to +29

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

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

AddStandardResilienceHandler() typically enables retries/backoff policies, which can make E2E failures slower and sometimes less deterministic (a failing endpoint may take longer to surface). For a smoke test, consider configuring a minimal/no-retry policy (or a test-specific resilience configuration) so failures are reported quickly and consistently.

Suggested change
appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
{
clientBuilder.AddStandardResilienceHandler();
});

Copilot uses AI. Check for mistakes.
await using var app = await appHost.BuildAsync(cancellationToken).WaitAsync(DefaultTimeout, cancellationToken);
await app.StartAsync(cancellationToken).WaitAsync(DefaultTimeout, cancellationToken);

// Act
using var httpClient = app.CreateHttpClient("webfrontend");
await app.ResourceNotifications
.WaitForResourceHealthyAsync("webfrontend", cancellationToken)
.WaitAsync(DefaultTimeout, cancellationToken);
Comment on lines +30 to +37

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

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

Timeout handling is currently duplicated: a 60s CancellationTokenSource governs the entire test, and then each step also adds a separate WaitAsync(DefaultTimeout, cancellationToken). This makes it harder to reason about total time budget and failure modes (cancellation vs timeout exceptions). Consider either (a) using the CTS token only (no WaitAsync(timeout)), or (b) using step-specific timeouts without a global 60s CTS (or a larger global CTS plus smaller per-step timeouts).

Suggested change
await using var app = await appHost.BuildAsync(cancellationToken).WaitAsync(DefaultTimeout, cancellationToken);
await app.StartAsync(cancellationToken).WaitAsync(DefaultTimeout, cancellationToken);
// Act
using var httpClient = app.CreateHttpClient("webfrontend");
await app.ResourceNotifications
.WaitForResourceHealthyAsync("webfrontend", cancellationToken)
.WaitAsync(DefaultTimeout, cancellationToken);
await using var app = await appHost.BuildAsync(cancellationToken);
await app.StartAsync(cancellationToken);
// Act
using var httpClient = app.CreateHttpClient("webfrontend");
await app.ResourceNotifications
.WaitForResourceHealthyAsync("webfrontend", cancellationToken);

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +37

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

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

Timeout handling is currently duplicated: a 60s CancellationTokenSource governs the entire test, and then each step also adds a separate WaitAsync(DefaultTimeout, cancellationToken). This makes it harder to reason about total time budget and failure modes (cancellation vs timeout exceptions). Consider either (a) using the CTS token only (no WaitAsync(timeout)), or (b) using step-specific timeouts without a global 60s CTS (or a larger global CTS plus smaller per-step timeouts).

Suggested change
await using var app = await appHost.BuildAsync(cancellationToken).WaitAsync(DefaultTimeout, cancellationToken);
await app.StartAsync(cancellationToken).WaitAsync(DefaultTimeout, cancellationToken);
// Act
using var httpClient = app.CreateHttpClient("webfrontend");
await app.ResourceNotifications
.WaitForResourceHealthyAsync("webfrontend", cancellationToken)
.WaitAsync(DefaultTimeout, cancellationToken);
await using var app = await appHost.BuildAsync(cancellationToken);
await app.StartAsync(cancellationToken);
// Act
using var httpClient = app.CreateHttpClient("webfrontend");
await app.ResourceNotifications
.WaitForResourceHealthyAsync("webfrontend", cancellationToken);

Copilot uses AI. Check for mistakes.

using var response = await httpClient.GetAsync("/", cancellationToken);

// Assert
response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK);
}
}
7 changes: 7 additions & 0 deletions tests/E2E.Tests/xunit.runner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
"methodDisplay": "method",
"methodDisplayOptions": "all",
"parallelizeAssembly": false,
"parallelizeTestCollections": true

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

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

parallelizeTestCollections is effectively moot when parallelizeAssembly is false, which can be confusing for future test additions. Consider setting both to false for clarity, or enable assembly parallelization if you truly want collections to run concurrently.

Suggested change
"parallelizeTestCollections": true
"parallelizeTestCollections": false

Copilot uses AI. Check for mistakes.
}
Loading