Skip to content
Merged
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Add unit tests for ParameterProcessor functionality
  • Loading branch information
davidfowl committed Jul 3, 2025
commit 42001b5e61c408e3876b3a26b52095137ee501d2
210 changes: 210 additions & 0 deletions tests/Aspire.Hosting.Tests/Orchestrator/ParameterProcessorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
// Licensed to the .NET Foundation under one or more agreements.
Copy link

Copilot AI Jul 3, 2025

Choose a reason for hiding this comment

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

[nitpick] This test class is over 350 lines long, covering multiple scenarios. Consider splitting it into smaller, focused test classes (e.g., initialization tests vs. unresolved-parameter handling tests) to improve readability and maintainability.

Copilot uses AI. Check for mistakes.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Hosting.Orchestrator;
using Aspire.Hosting.Tests.Utils;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;

#pragma warning disable ASPIREINTERACTION001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

namespace Aspire.Hosting.Tests.Orchestrator;

public class ParameterProcessorTests
{
[Fact]
public async Task InitializeParametersAsync_WithValidParameters_SetsActiveState()
{
// Arrange
var parameterProcessor = CreateParameterProcessor();
var parameters = new[]
{
CreateParameterResource("param1", "value1"),
CreateParameterResource("param2", "value2")
};

// Act
await parameterProcessor.InitializeParametersAsync(parameters);

// Assert
foreach (var param in parameters)
{
Assert.NotNull(param.WaitForValueTcs);
Assert.True(param.WaitForValueTcs.Task.IsCompletedSuccessfully);
Assert.Equal(param.Value, await param.WaitForValueTcs.Task);
}
}

[Fact]
public async Task InitializeParametersAsync_WithSecretParameter_MarksAsSecret()
{
// Arrange
var notificationService = ResourceNotificationServiceTestHelpers.Create();
var parameterProcessor = CreateParameterProcessor(notificationService: notificationService);
var secretParam = CreateParameterResource("secret", "secretValue", secret: true);

var updates = new List<(IResource Resource, CustomResourceSnapshot Snapshot)>();
var watchTask = Task.Run(async () =>
{
await foreach (var resourceEvent in notificationService.WatchAsync().ConfigureAwait(false))
{
updates.Add((resourceEvent.Resource, resourceEvent.Snapshot));
break; // Only collect the first update
}
});

// Act
await parameterProcessor.InitializeParametersAsync([secretParam]);

// Wait for the notification
await watchTask.WaitAsync(TimeSpan.FromSeconds(5));

// Assert
Assert.Single(updates);
var (resource, snapshot) = updates[0];
Assert.Equal(secretParam, resource);
Assert.Equal(KnownResourceStates.Active, snapshot.State?.Text);
Assert.Equal(KnownResourceStateStyles.Success, snapshot.State?.Style);
}

[Fact]
public async Task InitializeParametersAsync_WithMissingParameterValue_AddsToUnresolvedWhenInteractionAvailable()
{
// Arrange
var interactionService = CreateInteractionService();
var parameterProcessor = CreateParameterProcessor(interactionService: interactionService);
var parameterWithMissingValue = CreateParameterWithMissingValue("missingParam");

// Act
await parameterProcessor.InitializeParametersAsync([parameterWithMissingValue]);

// Assert
Assert.NotNull(parameterWithMissingValue.WaitForValueTcs);
Assert.False(parameterWithMissingValue.WaitForValueTcs.Task.IsCompleted);
}

[Fact]
public async Task InitializeParametersAsync_WithMissingParameterValue_SetsExceptionWhenInteractionNotAvailable()
{
// Arrange
var interactionService = CreateInteractionService(disableDashboard: true);
var parameterProcessor = CreateParameterProcessor(interactionService: interactionService);
var parameterWithMissingValue = CreateParameterWithMissingValue("missingParam");

// Act
await parameterProcessor.InitializeParametersAsync([parameterWithMissingValue]);

// Assert
Assert.NotNull(parameterWithMissingValue.WaitForValueTcs);
Assert.True(parameterWithMissingValue.WaitForValueTcs.Task.IsCompleted);
Assert.True(parameterWithMissingValue.WaitForValueTcs.Task.IsFaulted);
Assert.IsType<MissingParameterValueException>(parameterWithMissingValue.WaitForValueTcs.Task.Exception?.InnerException);
}

[Fact]
public async Task InitializeParametersAsync_WithNonMissingParameterException_SetsException()
{
// Arrange
var parameterProcessor = CreateParameterProcessor();
var parameterWithError = CreateParameterWithGenericError("errorParam");

// Act
await parameterProcessor.InitializeParametersAsync([parameterWithError]);

// Assert
Assert.NotNull(parameterWithError.WaitForValueTcs);
Assert.True(parameterWithError.WaitForValueTcs.Task.IsCompleted);
Assert.True(parameterWithError.WaitForValueTcs.Task.IsFaulted);
Assert.IsType<InvalidOperationException>(parameterWithError.WaitForValueTcs.Task.Exception?.InnerException);
}

[Fact]
public async Task HandleUnresolvedParametersAsync_WithInteractionService_DoesNotThrow()
{
// Arrange
var interactionService = CreateInteractionService();
var parameterProcessor = CreateParameterProcessor(interactionService: interactionService);
var parameterWithMissingValue = CreateParameterWithMissingValue("missingParam");

// Act - Initialize parameters first to add to unresolved list
await parameterProcessor.InitializeParametersAsync([parameterWithMissingValue]);

// Allow background task to start
await Task.Delay(50);

// Assert - The background task should have started without throwing
Assert.NotNull(parameterWithMissingValue.WaitForValueTcs);
// The parameter should remain unresolved since we don't complete the interaction
Assert.False(parameterWithMissingValue.WaitForValueTcs.Task.IsCompleted);

// Verify there's an active interaction for the parameter
var interactions = interactionService.GetCurrentInteractions();
Assert.NotEmpty(interactions);
}

[Fact]
public async Task HandleUnresolvedParametersAsync_CallDirectly_DoesNotThrow()
{
// Arrange
var interactionService = CreateInteractionService();
var parameterProcessor = CreateParameterProcessor(interactionService: interactionService);

// Act & Assert - Should not throw even when called directly with no unresolved parameters
await parameterProcessor.HandleUnresolvedParametersAsync();
}

[Fact]
public async Task InitializeParametersAsync_WithEmptyParameterList_CompletesSuccessfully()
{
// Arrange
var parameterProcessor = CreateParameterProcessor();

// Act & Assert - Should not throw
await parameterProcessor.InitializeParametersAsync(Array.Empty<ParameterResource>());
}

private static ParameterProcessor CreateParameterProcessor(
ResourceNotificationService? notificationService = null,
ResourceLoggerService? loggerService = null,
IInteractionService? interactionService = null,
ILogger<ParameterProcessor>? logger = null,
bool disableDashboard = true)
{
return new ParameterProcessor(
notificationService ?? ResourceNotificationServiceTestHelpers.Create(),
loggerService ?? new ResourceLoggerService(),
interactionService ?? CreateInteractionService(disableDashboard),
logger ?? new NullLogger<ParameterProcessor>()
);
}

private static InteractionService CreateInteractionService(bool disableDashboard = false)
{
return new InteractionService(
new NullLogger<InteractionService>(),
new DistributedApplicationOptions { DisableDashboard = disableDashboard },
new ServiceCollection().BuildServiceProvider());
}

private static ParameterResource CreateParameterResource(string name, string value, bool secret = false)
{
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?> { [$"Parameters:{name}"] = value })
.Build();

return new ParameterResource(name, _ => configuration[$"Parameters:{name}"] ?? throw new MissingParameterValueException($"Parameter '{name}' is missing"), secret);
}

private static ParameterResource CreateParameterWithMissingValue(string name)
{
return new ParameterResource(name, _ => throw new MissingParameterValueException($"Parameter '{name}' is missing"), secret: false);
}

private static ParameterResource CreateParameterWithGenericError(string name)
{
return new ParameterResource(name, _ => throw new InvalidOperationException($"Generic error for parameter '{name}'"), secret: false);
}
}
Loading