diff --git a/src/Aspire.Hosting/DistributedApplicationBuilder.cs b/src/Aspire.Hosting/DistributedApplicationBuilder.cs index 5f0a83cf547..cac4a160f38 100644 --- a/src/Aspire.Hosting/DistributedApplicationBuilder.cs +++ b/src/Aspire.Hosting/DistributedApplicationBuilder.cs @@ -67,7 +67,7 @@ public class DistributedApplicationBuilder : IDistributedApplicationBuilder private readonly DistributedApplicationOptions _options; private readonly HostApplicationBuilder _innerBuilder; private readonly IUserSecretsManager _userSecretsManager; - private readonly IFileSystemService _directoryService; + private readonly FileSystemService _directoryService; /// public IHostEnvironment Environment => _innerBuilder.Environment; @@ -303,8 +303,12 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options) // Core things // Create and register the directory service (first, so it can be used by other services) - _directoryService = new FileSystemService(); - _innerBuilder.Services.AddSingleton(_directoryService); + _directoryService = new FileSystemService(_innerBuilder.Configuration); + _innerBuilder.Services.AddSingleton(sp => + { + _directoryService.SetLogger(sp.GetRequiredService>()); + return _directoryService; + }); // Create and register the user secrets manager var userSecretsFactory = new UserSecretsManagerFactory(_directoryService); diff --git a/src/Aspire.Hosting/Utils/FileSystemService.cs b/src/Aspire.Hosting/Utils/FileSystemService.cs index 0d8c3b8e82f..0099c70f066 100644 --- a/src/Aspire.Hosting/Utils/FileSystemService.cs +++ b/src/Aspire.Hosting/Utils/FileSystemService.cs @@ -3,28 +3,146 @@ #pragma warning disable ASPIREFILESYSTEM001 // Type is for evaluation purposes only +using System.Collections.Concurrent; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + namespace Aspire.Hosting; /// /// Default implementation of . /// -internal sealed class FileSystemService : IFileSystemService +internal sealed class FileSystemService : IFileSystemService, IDisposable { - private readonly TempFileSystemService _tempDirectory = new(); + private readonly TempFileSystemService _tempDirectory; + private ILogger? _logger; + private readonly bool _preserveTempFiles; + + // Track allocated temp files and directories as disposable objects using path as key + private readonly ConcurrentDictionary _allocatedItems = new(); + + public FileSystemService(IConfiguration configuration) + { + ArgumentNullException.ThrowIfNull(configuration); + + // Check configuration to preserve temp files for debugging + _preserveTempFiles = configuration["ASPIRE_PRESERVE_TEMP_FILES"] is not null; + + _tempDirectory = new TempFileSystemService(this); + } + + /// + /// Sets the logger for this service. Called after service provider is built. + /// + /// + /// The logger cannot be injected via constructor because the FileSystemService + /// is allocated before logging is fully initialized in the DistributedApplicationBuilder. + /// + internal void SetLogger(ILogger logger) + { + _logger = logger; + } /// public ITempFileSystemService TempDirectory => _tempDirectory; + /// + /// Gets whether temporary files should be preserved for debugging. + /// + internal bool ShouldPreserveTempFiles => _preserveTempFiles; + + /// + /// Gets the logger for this service, if set. + /// + internal ILogger? Logger => _logger; + + private bool _disposed; + private readonly object _disposeLock = new(); + + /// + /// Tracks a temporary item for cleanup on service disposal. + /// + internal void TrackItem(string path, IDisposable item) + { + lock (_disposeLock) + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(FileSystemService), "Cannot allocate temporary files after the service has been disposed."); + } + + _allocatedItems.TryAdd(path, item); + } + } + + /// + /// Removes a temporary item from tracking. + /// + internal void UntrackItem(string path) + { + _allocatedItems.TryRemove(path, out _); + } + + /// + /// Cleans up any remaining temporary files and directories. + /// + public void Dispose() + { + lock (_disposeLock) + { + if (_disposed) + { + return; + } + + _disposed = true; + } + + if (_preserveTempFiles) + { + _logger?.LogInformation("Skipping cleanup of {Count} temporary files/directories due to ASPIRE_PRESERVE_TEMP_FILES configuration", _allocatedItems.Count); + return; + } + + if (_allocatedItems.IsEmpty) + { + return; + } + + _logger?.LogDebug("Cleaning up {Count} remaining temporary files/directories", _allocatedItems.Count); + + foreach (var kvp in _allocatedItems) + { + try + { + kvp.Value.Dispose(); + } + catch (Exception ex) + { + _logger?.LogWarning(ex, "Failed to clean up temporary item"); + } + } + } + /// /// Implementation of . /// private sealed class TempFileSystemService : ITempFileSystemService { + private readonly FileSystemService _parent; + + public TempFileSystemService(FileSystemService parent) + { + _parent = parent; + } + /// public TempDirectory CreateTempSubdirectory(string? prefix = null) { var path = Directory.CreateTempSubdirectory(prefix ?? "aspire").FullName; - return new DefaultTempDirectory(path); + var tempDir = new DefaultTempDirectory(path, _parent); + _parent.TrackItem(path, tempDir); + return tempDir; } /// @@ -33,14 +151,18 @@ public TempFile CreateTempFile(string? fileName = null) if (fileName is null) { var tempFile = Path.GetTempFileName(); - return new DefaultTempFile(tempFile, deleteParentDirectory: false); + var file = new DefaultTempFile(tempFile, deleteParentDirectory: false, _parent); + _parent.TrackItem(tempFile, file); + return file; } // Create a temp subdirectory and place the named file inside it var tempDir = Directory.CreateTempSubdirectory("aspire").FullName; var filePath = Path.Combine(tempDir, fileName); File.Create(filePath).Dispose(); - return new DefaultTempFile(filePath, deleteParentDirectory: true); + var tempFileObj = new DefaultTempFile(filePath, deleteParentDirectory: true, _parent); + _parent.TrackItem(filePath, tempFileObj); + return tempFileObj; } } @@ -50,21 +172,43 @@ public TempFile CreateTempFile(string? fileName = null) private sealed class DefaultTempDirectory : TempDirectory { private readonly string _path; + private readonly FileSystemService _parent; + private bool _disposed; - public DefaultTempDirectory(string path) + public DefaultTempDirectory(string path, FileSystemService parent) { _path = path; + _parent = parent; + + _parent.Logger?.LogDebug("Allocated temporary directory: {Path}", path); } public override string Path => _path; public override void Dispose() { + if (_disposed) + { + return; + } + + _disposed = true; + + // Remove from tracking + _parent.UntrackItem(_path); + + // Skip deletion if preserve flag is set + if (_parent.ShouldPreserveTempFiles) + { + return; + } + try { if (Directory.Exists(_path)) { Directory.Delete(_path, recursive: true); + _parent.Logger?.LogDebug("Cleaned up temporary directory: {Path}", _path); } } catch @@ -81,22 +225,44 @@ private sealed class DefaultTempFile : TempFile { private readonly string _path; private readonly bool _deleteParentDirectory; + private readonly FileSystemService _parent; + private bool _disposed; - public DefaultTempFile(string path, bool deleteParentDirectory) + public DefaultTempFile(string path, bool deleteParentDirectory, FileSystemService parent) { _path = path; _deleteParentDirectory = deleteParentDirectory; + _parent = parent; + + _parent.Logger?.LogDebug("Allocated temporary file: {Path}", path); } public override string Path => _path; public override void Dispose() { + if (_disposed) + { + return; + } + + _disposed = true; + + // Remove from tracking + _parent.UntrackItem(_path); + + // Skip deletion if preserve flag is set + if (_parent.ShouldPreserveTempFiles) + { + return; + } + try { if (File.Exists(_path)) { File.Delete(_path); + _parent.Logger?.LogDebug("Cleaned up temporary file: {Path}", _path); } if (_deleteParentDirectory) diff --git a/tests/Aspire.Hosting.Tests/AspireStoreTests.cs b/tests/Aspire.Hosting.Tests/AspireStoreTests.cs index e1f98a4ea0e..aebdfc2fdc7 100644 --- a/tests/Aspire.Hosting.Tests/AspireStoreTests.cs +++ b/tests/Aspire.Hosting.Tests/AspireStoreTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Hosting.Utils; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace Aspire.Hosting.Tests; @@ -119,11 +120,13 @@ public void GetOrCreateFileWithContent_ShouldNotRecreateFile() [InlineData(null)] [InlineData("")] [InlineData("./folder")] + [InlineData(".\\folder")] [InlineData("folder")] [InlineData("obj/")] + [InlineData("obj\\")] public void AspireStoreConstructor_ShouldThrow_IfNotAbsolutePath(string? basePath) { - var directoryService = new FileSystemService(); + var directoryService = new FileSystemService(new ConfigurationBuilder().Build()); Assert.ThrowsAny(() => new AspireStore(basePath!, directoryService)); } diff --git a/tests/Aspire.Hosting.Tests/Dashboard/DashboardLifecycleHookTests.cs b/tests/Aspire.Hosting.Tests/Dashboard/DashboardLifecycleHookTests.cs index 8dcb0040f27..4dda4322ff4 100644 --- a/tests/Aspire.Hosting.Tests/Dashboard/DashboardLifecycleHookTests.cs +++ b/tests/Aspire.Hosting.Tests/Dashboard/DashboardLifecycleHookTests.cs @@ -523,7 +523,7 @@ private static DashboardEventHandlers CreateHook( new TestHostApplicationLifetime(), new Hosting.Eventing.DistributedApplicationEventing(), rewriter, - new FileSystemService() + new FileSystemService(configuration) ); } diff --git a/tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs b/tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs index afdd17f6ec0..067f58eb723 100644 --- a/tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs +++ b/tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs @@ -2086,7 +2086,7 @@ private static DcpExecutor CreateAppExecutor( new TestDcpDependencyCheckService(), new DcpNameGenerator(configuration, Options.Create(dcpOptions)), events ?? new DcpExecutorEvents(), - new Locations(new FileSystemService()), + new Locations(new FileSystemService(configuration ?? new ConfigurationBuilder().Build())), developerCertificateService); #pragma warning restore ASPIRECERTIFICATES001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. } diff --git a/tests/Aspire.Hosting.Tests/Dcp/DcpHostNotificationTests.cs b/tests/Aspire.Hosting.Tests/Dcp/DcpHostNotificationTests.cs index ea21c987b51..e03ef806cd1 100644 --- a/tests/Aspire.Hosting.Tests/Dcp/DcpHostNotificationTests.cs +++ b/tests/Aspire.Hosting.Tests/Dcp/DcpHostNotificationTests.cs @@ -5,6 +5,7 @@ using Aspire.Hosting.Dcp; using Aspire.Hosting.Resources; using Microsoft.AspNetCore.InternalTesting; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; @@ -18,7 +19,7 @@ public sealed class DcpHostNotificationTests { private static Locations CreateTestLocations() { - var directoryService = new FileSystemService(); + var directoryService = new FileSystemService(new ConfigurationBuilder().Build()); return new Locations(directoryService); } diff --git a/tests/Aspire.Hosting.Tests/FileSystemServiceTests.cs b/tests/Aspire.Hosting.Tests/FileSystemServiceTests.cs index 3f2eba2abfd..f621cda9631 100644 --- a/tests/Aspire.Hosting.Tests/FileSystemServiceTests.cs +++ b/tests/Aspire.Hosting.Tests/FileSystemServiceTests.cs @@ -3,14 +3,29 @@ #pragma warning disable ASPIREFILESYSTEM001 // Type is for evaluation purposes only +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + namespace Aspire.Hosting.Tests; public class FileSystemServiceTests { + private static IConfiguration CreateConfiguration(bool preserveTempFiles = false) + { + var configDict = new Dictionary(); + if (preserveTempFiles) + { + configDict["ASPIRE_PRESERVE_TEMP_FILES"] = "true"; + } + return new ConfigurationBuilder() + .AddInMemoryCollection(configDict) + .Build(); + } + [Fact] public void CreateTempSubdirectory_CreatesDirectory() { - var service = new FileSystemService(); + var service = new FileSystemService(CreateConfiguration()); using var tempDir = service.TempDirectory.CreateTempSubdirectory(); @@ -21,7 +36,7 @@ public void CreateTempSubdirectory_CreatesDirectory() [Fact] public void CreateTempSubdirectory_WithPrefix_CreatesDirectoryWithPrefix() { - var service = new FileSystemService(); + var service = new FileSystemService(CreateConfiguration()); using var tempDir = service.TempDirectory.CreateTempSubdirectory("test-prefix"); @@ -33,7 +48,7 @@ public void CreateTempSubdirectory_WithPrefix_CreatesDirectoryWithPrefix() [Fact] public void CreateTempSubdirectory_Dispose_DeletesDirectory() { - var service = new FileSystemService(); + var service = new FileSystemService(CreateConfiguration()); var tempDir = service.TempDirectory.CreateTempSubdirectory(); var path = tempDir.Path; @@ -47,7 +62,7 @@ public void CreateTempSubdirectory_Dispose_DeletesDirectory() [Fact] public void CreateTempFile_CreatesFile() { - var service = new FileSystemService(); + var service = new FileSystemService(CreateConfiguration()); using var tempFile = service.TempDirectory.CreateTempFile(); @@ -58,7 +73,7 @@ public void CreateTempFile_CreatesFile() [Fact] public void CreateTempFile_WithFileName_CreatesNamedFile() { - var service = new FileSystemService(); + var service = new FileSystemService(CreateConfiguration()); using var tempFile = service.TempDirectory.CreateTempFile("config.json"); @@ -70,7 +85,7 @@ public void CreateTempFile_WithFileName_CreatesNamedFile() [Fact] public void CreateTempFile_Dispose_DeletesFile() { - var service = new FileSystemService(); + var service = new FileSystemService(CreateConfiguration()); var tempFile = service.TempDirectory.CreateTempFile(); var path = tempFile.Path; @@ -84,7 +99,7 @@ public void CreateTempFile_Dispose_DeletesFile() [Fact] public void CreateTempFile_WithFileName_Dispose_DeletesFileAndParentDirectory() { - var service = new FileSystemService(); + var service = new FileSystemService(CreateConfiguration()); var tempFile = service.TempDirectory.CreateTempFile("test-file.txt"); var filePath = tempFile.Path; var parentDir = Path.GetDirectoryName(filePath); @@ -103,7 +118,7 @@ public void PathExtractionPattern_DirectoryPersistsAfterScopeEnds() { // This test verifies the intentional pattern of extracting .Path // without disposing, which is common in the codebase - var service = new FileSystemService(); + var service = new FileSystemService(CreateConfiguration()); string? extractedPath; // Simulate the common pattern: extract path, let object go out of scope @@ -125,7 +140,7 @@ public void PathExtractionPattern_FilePersistsAfterScopeEnds() { // This test verifies the intentional pattern of extracting .Path // without disposing, which is common in the codebase - var service = new FileSystemService(); + var service = new FileSystemService(CreateConfiguration()); string? extractedPath; // Simulate the common pattern: extract path, let object go out of scope @@ -145,7 +160,7 @@ public void PathExtractionPattern_FilePersistsAfterScopeEnds() [Fact] public void TempDirectory_Property_ReturnsSameInstance() { - var service = new FileSystemService(); + var service = new FileSystemService(CreateConfiguration()); var tempDir1 = service.TempDirectory; var tempDir2 = service.TempDirectory; @@ -156,7 +171,7 @@ public void TempDirectory_Property_ReturnsSameInstance() [Fact] public void CreateTempSubdirectory_MultipleCallsCreateDifferentDirectories() { - var service = new FileSystemService(); + var service = new FileSystemService(CreateConfiguration()); using var tempDir1 = service.TempDirectory.CreateTempSubdirectory(); using var tempDir2 = service.TempDirectory.CreateTempSubdirectory(); @@ -169,7 +184,7 @@ public void CreateTempSubdirectory_MultipleCallsCreateDifferentDirectories() [Fact] public void CreateTempFile_MultipleCallsCreateDifferentFiles() { - var service = new FileSystemService(); + var service = new FileSystemService(CreateConfiguration()); using var tempFile1 = service.TempDirectory.CreateTempFile(); using var tempFile2 = service.TempDirectory.CreateTempFile(); @@ -182,7 +197,7 @@ public void CreateTempFile_MultipleCallsCreateDifferentFiles() [Fact] public void Dispose_CalledMultipleTimes_DoesNotThrow() { - var service = new FileSystemService(); + var service = new FileSystemService(CreateConfiguration()); var tempDir = service.TempDirectory.CreateTempSubdirectory(); var tempFile = service.TempDirectory.CreateTempFile(); @@ -194,4 +209,214 @@ public void Dispose_CalledMultipleTimes_DoesNotThrow() tempDir.Dispose(); tempFile.Dispose(); } + + [Fact] + public void ServiceDispose_CleansUpUndisposedFiles() + { + var service = new FileSystemService(CreateConfiguration()); + + // Create temp files/dirs without disposing them + var tempDir = service.TempDirectory.CreateTempSubdirectory(); + var tempFile = service.TempDirectory.CreateTempFile(); + var dirPath = tempDir.Path; + var filePath = tempFile.Path; + + Assert.True(Directory.Exists(dirPath)); + Assert.True(File.Exists(filePath)); + + // Dispose the service - should clean up remaining files + service.Dispose(); + + Assert.False(Directory.Exists(dirPath)); + Assert.False(File.Exists(filePath)); + } + + [Fact] + public void ServiceDispose_WithPreserveConfiguration_SkipsCleanup() + { + var service = new FileSystemService(CreateConfiguration(preserveTempFiles: true)); + + var tempDir = service.TempDirectory.CreateTempSubdirectory(); + var tempFile = service.TempDirectory.CreateTempFile(); + var dirPath = tempDir.Path; + var filePath = tempFile.Path; + + Assert.True(Directory.Exists(dirPath)); + Assert.True(File.Exists(filePath)); + + // Dispose the service - should NOT clean up files + service.Dispose(); + + // Files should still exist + Assert.True(Directory.Exists(dirPath)); + Assert.True(File.Exists(filePath)); + + // Clean up manually + Directory.Delete(dirPath, recursive: true); + File.Delete(filePath); + } + + [Fact] + public void TempDirectory_Dispose_WithPreserveConfiguration_SkipsCleanup() + { + var service = new FileSystemService(CreateConfiguration(preserveTempFiles: true)); + + var tempDir = service.TempDirectory.CreateTempSubdirectory(); + var dirPath = tempDir.Path; + + Assert.True(Directory.Exists(dirPath)); + + // Dispose the temp directory - should NOT clean up + tempDir.Dispose(); + + // Directory should still exist + Assert.True(Directory.Exists(dirPath)); + + // Clean up manually + Directory.Delete(dirPath, recursive: true); + } + + [Fact] + public void TempFile_Dispose_WithPreserveConfiguration_SkipsCleanup() + { + var service = new FileSystemService(CreateConfiguration(preserveTempFiles: true)); + + var tempFile = service.TempDirectory.CreateTempFile(); + var filePath = tempFile.Path; + + Assert.True(File.Exists(filePath)); + + // Dispose the temp file - should NOT clean up + tempFile.Dispose(); + + // File should still exist + Assert.True(File.Exists(filePath)); + + // Clean up manually + File.Delete(filePath); + } + + [Fact] + public void ServiceDispose_WithMixedDisposedAndUndisposedItems_CleansUpOnlyUndisposed() + { + var service = new FileSystemService(CreateConfiguration()); + + // Create multiple temp items + var tempDir1 = service.TempDirectory.CreateTempSubdirectory(); + var tempDir2 = service.TempDirectory.CreateTempSubdirectory(); + var tempFile1 = service.TempDirectory.CreateTempFile(); + var tempFile2 = service.TempDirectory.CreateTempFile(); + + var dir1Path = tempDir1.Path; + var dir2Path = tempDir2.Path; + var file1Path = tempFile1.Path; + var file2Path = tempFile2.Path; + + // Dispose some of them + tempDir1.Dispose(); + tempFile1.Dispose(); + + Assert.False(Directory.Exists(dir1Path)); + Assert.False(File.Exists(file1Path)); + Assert.True(Directory.Exists(dir2Path)); + Assert.True(File.Exists(file2Path)); + + // Dispose the service - should clean up remaining undisposed items + service.Dispose(); + + Assert.False(Directory.Exists(dir2Path)); + Assert.False(File.Exists(file2Path)); + } + + [Fact] + public void ServiceDispose_EmptyTracking_DoesNotThrow() + { + var service = new FileSystemService(CreateConfiguration()); + + // Dispose without creating any temp items + service.Dispose(); + + // Dispose again should also not throw + service.Dispose(); + } + + [Fact] + public void ServiceDispose_CalledMultipleTimes_IsIdempotent() + { + var service = new FileSystemService(CreateConfiguration()); + + // Create temp items + var tempDir = service.TempDirectory.CreateTempSubdirectory(); + var tempFile = service.TempDirectory.CreateTempFile(); + var dirPath = tempDir.Path; + var filePath = tempFile.Path; + + // First dispose - should clean up + service.Dispose(); + + Assert.False(Directory.Exists(dirPath)); + Assert.False(File.Exists(filePath)); + + // Second dispose - should be a no-op and not throw + service.Dispose(); + } + + [Fact] + public void CreateTempFile_WithFileName_TracksOnlyFile() + { + var service = new FileSystemService(CreateConfiguration()); + + var tempFile = service.TempDirectory.CreateTempFile("test.txt"); + var filePath = tempFile.Path; + var parentDir = Path.GetDirectoryName(filePath)!; + + // Both file and parent dir should exist + Assert.True(File.Exists(filePath)); + Assert.True(Directory.Exists(parentDir)); + + // Dispose the temp file - should delete both file and parent directory + tempFile.Dispose(); + + Assert.False(File.Exists(filePath)); + Assert.False(Directory.Exists(parentDir)); + } + + [Fact] + public void CreateTempFile_AfterServiceDisposed_ThrowsObjectDisposedException() + { + var service = new FileSystemService(CreateConfiguration()); + + // Dispose the service + service.Dispose(); + + // Attempting to create a temp file after disposal should throw + Assert.Throws(() => service.TempDirectory.CreateTempFile()); + } + + [Fact] + public void CreateTempDirectory_AfterServiceDisposed_ThrowsObjectDisposedException() + { + var service = new FileSystemService(CreateConfiguration()); + + // Dispose the service + service.Dispose(); + + // Attempting to create a temp directory after disposal should throw + Assert.Throws(() => service.TempDirectory.CreateTempSubdirectory()); + } + + [Fact] + public void SetLogger_CanBeCalledWithoutError() + { + var service = new FileSystemService(CreateConfiguration()); + var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); + var logger = loggerFactory.CreateLogger(); + + // Should not throw + service.SetLogger(logger); + + // Should still work normally + using var tempDir = service.TempDirectory.CreateTempSubdirectory(); + Assert.True(Directory.Exists(tempDir.Path)); + } } diff --git a/tests/Aspire.Hosting.Tests/SecretsStoreTests.cs b/tests/Aspire.Hosting.Tests/SecretsStoreTests.cs index 18a4e967bf5..bf88084a0d9 100644 --- a/tests/Aspire.Hosting.Tests/SecretsStoreTests.cs +++ b/tests/Aspire.Hosting.Tests/SecretsStoreTests.cs @@ -16,7 +16,12 @@ public class SecretsStoreTests { private static readonly ConstructorInfo s_userSecretsIdAttrCtor = typeof(UserSecretsIdAttribute).GetConstructor([typeof(string)])!; - private static UserSecretsManagerFactory CreateFactory() => new UserSecretsManagerFactory(new FileSystemService()); + private static UserSecretsManagerFactory CreateFactory() + { + return new UserSecretsManagerFactory( + new FileSystemService( + new ConfigurationBuilder().Build())); + } [Fact] public void GetOrSetUserSecret_SavesValueToUserSecrets() diff --git a/tests/Aspire.Hosting.Tests/UserSecretsParameterDefaultTests.cs b/tests/Aspire.Hosting.Tests/UserSecretsParameterDefaultTests.cs index 40360a87eda..9e6b01fa173 100644 --- a/tests/Aspire.Hosting.Tests/UserSecretsParameterDefaultTests.cs +++ b/tests/Aspire.Hosting.Tests/UserSecretsParameterDefaultTests.cs @@ -18,7 +18,12 @@ public class UserSecretsParameterDefaultTests { private static readonly ConstructorInfo s_userSecretsIdAttrCtor = typeof(UserSecretsIdAttribute).GetConstructor([typeof(string)])!; - private static UserSecretsManagerFactory CreateFactory() => new UserSecretsManagerFactory(new FileSystemService()); + private static UserSecretsManagerFactory CreateFactory() + { + return new UserSecretsManagerFactory( + new FileSystemService( + new ConfigurationBuilder().Build())); + } [Fact] public void UserSecretsParameterDefault_GetDefaultValue_SavesValueInAppHostUserSecrets()