From 38be62db0c520f89f9851f4df4f50aa21d98a3dd Mon Sep 17 00:00:00 2001 From: gbaudrit Date: Mon, 31 Aug 2026 22:18:02 +0200 Subject: [PATCH 1/5] test: clean isolated web host data --- .../TestingDataDirectoryCleanupService.cs | 33 ++++++++++++++ src/Agentstration.Web/Program.cs | 44 +++++++++++-------- .../DeclarativeBootstrapTests.cs | 8 +++- .../LegacyVerticalRemovalTests.cs | 2 +- .../QuartzHostLifecycleTests.cs | 28 +++++++++++- .../WorkplaceApiTests.cs | 10 ++--- 6 files changed, 98 insertions(+), 27 deletions(-) create mode 100644 src/Agentstration.Web/Hosting/TestingDataDirectoryCleanupService.cs diff --git a/src/Agentstration.Web/Hosting/TestingDataDirectoryCleanupService.cs b/src/Agentstration.Web/Hosting/TestingDataDirectoryCleanupService.cs new file mode 100644 index 00000000..7c3d3cac --- /dev/null +++ b/src/Agentstration.Web/Hosting/TestingDataDirectoryCleanupService.cs @@ -0,0 +1,33 @@ +using Microsoft.Data.Sqlite; + +namespace Agentstration.Web.Hosting; + +internal sealed class TestingDataDirectoryCleanupService(string directory, bool deleteOnShutdown) : IHostedService +{ + internal string DirectoryPath { get; } = directory; + + public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + public async Task StopAsync(CancellationToken cancellationToken) + { + SqliteConnection.ClearAllPools(); + if (!deleteOnShutdown) return; + + for (var attempt = 0; ; attempt++) + { + try + { + if (Directory.Exists(DirectoryPath)) Directory.Delete(DirectoryPath, recursive: true); + return; + } + catch (IOException) when (attempt < 4) + { + await Task.Delay(TimeSpan.FromMilliseconds(50), cancellationToken); + } + catch (UnauthorizedAccessException) when (attempt < 4) + { + await Task.Delay(TimeSpan.FromMilliseconds(50), cancellationToken); + } + } + } +} diff --git a/src/Agentstration.Web/Program.cs b/src/Agentstration.Web/Program.cs index 80b4df1f..520ce215 100644 --- a/src/Agentstration.Web/Program.cs +++ b/src/Agentstration.Web/Program.cs @@ -39,18 +39,19 @@ var toolExecutionCapture = builder.Configuration.GetSection("Agentstration:ToolExecution").Get() ?? new(); toolExecutionCapture.Validate(); builder.Services.AddSingleton(toolExecutionCapture); -var dataDirectory = builder.Configuration["Data:Directory"] ?? Path.Combine(builder.Environment.ContentRootPath, ".agentstration"); +var isTesting = builder.Environment.IsEnvironment("Testing"); +var configuredTestingDataDirectory = builder.Configuration["Data:TestingDirectory"]; +var ownsTestingDataDirectory = isTesting && string.IsNullOrWhiteSpace(configuredTestingDataDirectory); +var dataDirectory = isTesting + ? configuredTestingDataDirectory ?? Path.Combine(Path.GetTempPath(), $"agentstration-web-tests-{Guid.NewGuid():N}") + : builder.Configuration["Data:Directory"] ?? Path.Combine(builder.Environment.ContentRootPath, ".agentstration"); Directory.CreateDirectory(dataDirectory); var identityConnectionString = builder.Configuration.GetConnectionString("Identity") - ?? (builder.Environment.IsEnvironment("Testing") - ? $"Data Source={Path.Combine(Path.GetTempPath(), $"agentstration-identity-tests-{Guid.NewGuid():N}.db")}" - : $"Data Source={Path.Combine(dataDirectory, "identity.db")}"); + ?? $"Data Source={Path.Combine(dataDirectory, "identity.db")}"; var dataProtectionKeysPath = configuredAuthentication.DataProtectionKeysPath; if (string.IsNullOrWhiteSpace(dataProtectionKeysPath)) { - dataProtectionKeysPath = builder.Environment.IsEnvironment("Testing") - ? Path.Combine(Path.GetTempPath(), $"agentstration-data-protection-tests-{Guid.NewGuid():N}") - : Path.Combine(dataDirectory, "data-protection-keys"); + dataProtectionKeysPath = Path.Combine(dataDirectory, "data-protection-keys"); } var aiProvider = builder.Configuration["AI:Provider"] ?? "Managed"; var useManagedProfileResolver = string.Equals(aiProvider, "Managed", StringComparison.OrdinalIgnoreCase); @@ -58,26 +59,31 @@ var aiEndpoint = builder.Configuration["AI:Endpoint"] ?? defaultAiEndpoint; if (!Uri.TryCreate(aiEndpoint.EndsWith('/') ? aiEndpoint : aiEndpoint + '/', UriKind.Absolute, out var parsedAiEndpoint)) throw new InvalidOperationException("AI:Endpoint must be an absolute URL."); var aiOptions = new AiProviderOptions(aiProvider, parsedAiEndpoint, builder.Configuration["AI:Model"] ?? "phi4-mini", builder.Configuration["AI:ApiKey"]); -var controlPlanePath = builder.Environment.IsEnvironment("Testing") - ? Path.Combine(Path.GetTempPath(), $"agentstration-tests-{Guid.NewGuid():N}.db") - : builder.Configuration["Data:ControlPlanePath"] ?? Path.Combine(builder.Environment.ContentRootPath, ".agentstration", "control-plane.db"); +var controlPlanePath = isTesting + ? Path.Combine(dataDirectory, "control-plane.db") + : builder.Configuration["Data:ControlPlanePath"] ?? Path.Combine(dataDirectory, "control-plane.db"); var controlPlaneDirectory = Path.GetDirectoryName(controlPlanePath); if (!string.IsNullOrWhiteSpace(controlPlaneDirectory)) Directory.CreateDirectory(controlPlaneDirectory); -var workPlanePath = builder.Environment.IsEnvironment("Testing") - ? Path.Combine(Path.GetTempPath(), $"agentstration-work-tests-{Guid.NewGuid():N}.db") - : builder.Configuration["Data:WorkPlanePath"] ?? Path.Combine(builder.Environment.ContentRootPath, ".agentstration", "work-plane.db"); +var workPlanePath = isTesting + ? Path.Combine(dataDirectory, "work-plane.db") + : builder.Configuration["Data:WorkPlanePath"] ?? Path.Combine(dataDirectory, "work-plane.db"); var workPlaneDirectory = Path.GetDirectoryName(workPlanePath); if (!string.IsNullOrWhiteSpace(workPlaneDirectory)) Directory.CreateDirectory(workPlaneDirectory); -var flowPath = builder.Environment.IsEnvironment("Testing") - ? Path.Combine(Path.GetTempPath(), $"agentstration-flow-tests-{Guid.NewGuid():N}.db") - : builder.Configuration["Data:FlowPath"] ?? Path.Combine(builder.Environment.ContentRootPath, ".agentstration", "flow-plane.db"); +var flowPath = isTesting + ? Path.Combine(dataDirectory, "flow-plane.db") + : builder.Configuration["Data:FlowPath"] ?? Path.Combine(dataDirectory, "flow-plane.db"); var flowDirectory = Path.GetDirectoryName(flowPath); if (!string.IsNullOrWhiteSpace(flowDirectory)) Directory.CreateDirectory(flowDirectory); -var runtimePath = builder.Environment.IsEnvironment("Testing") - ? Path.Combine(Path.GetTempPath(), $"agentstration-runtime-tests-{Guid.NewGuid():N}.db") - : builder.Configuration["Data:RuntimePath"] ?? Path.Combine(builder.Environment.ContentRootPath, ".agentstration", "runtime-plane.db"); +var runtimePath = isTesting + ? Path.Combine(dataDirectory, "runtime-plane.db") + : builder.Configuration["Data:RuntimePath"] ?? Path.Combine(dataDirectory, "runtime-plane.db"); var runtimeDirectory = Path.GetDirectoryName(runtimePath); if (!string.IsNullOrWhiteSpace(runtimeDirectory)) Directory.CreateDirectory(runtimeDirectory); +if (isTesting) +{ + builder.Services.AddSingleton(new TestingDataDirectoryCleanupService(dataDirectory, ownsTestingDataDirectory)); + builder.Services.AddSingleton(provider => provider.GetRequiredService()); +} builder.Services.AddAgentstration(dataDirectory, aiOptions, $"Data Source={controlPlanePath}", $"Data Source={workPlanePath}", $"Data Source={flowPath}", $"Data Source={runtimePath}"); builder.Services.AddAgentstrationModelProviders( builder.Configuration, diff --git a/tests/Agentstration.Management.Tests/DeclarativeBootstrapTests.cs b/tests/Agentstration.Management.Tests/DeclarativeBootstrapTests.cs index 51c04974..faa3cea4 100644 --- a/tests/Agentstration.Management.Tests/DeclarativeBootstrapTests.cs +++ b/tests/Agentstration.Management.Tests/DeclarativeBootstrapTests.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -342,6 +343,7 @@ private static WebApplicationFactory Factory(string path, string? passw new WebApplicationFactory().WithWebHostBuilder(builder => { builder.UseEnvironment("Testing"); + builder.UseSetting("Data:TestingDirectory", Path.Combine(path, ".test-data")); builder.UseSetting("Agentstration:Authentication:Mode", "Local"); builder.UseSetting("Agentstration:Bootstrap:Path", Directory.GetParent(path)!.FullName); builder.UseSetting("Agentstration:Bootstrap:InitialBootstrapEnabled", "true"); @@ -433,6 +435,10 @@ public TemporaryDirectory() } public string Path { get; } - public void Dispose() => Directory.Delete(Path, recursive: true); + public void Dispose() + { + SqliteConnection.ClearAllPools(); + Directory.Delete(Path, recursive: true); + } } } diff --git a/tests/Agentstration.Web.Tests/LegacyVerticalRemovalTests.cs b/tests/Agentstration.Web.Tests/LegacyVerticalRemovalTests.cs index e186fda9..b8c84c0f 100644 --- a/tests/Agentstration.Web.Tests/LegacyVerticalRemovalTests.cs +++ b/tests/Agentstration.Web.Tests/LegacyVerticalRemovalTests.cs @@ -90,7 +90,7 @@ public async Task StartupDoesNotCreateLegacyDataJson() new WebApplicationFactory().WithWebHostBuilder(builder => { builder.UseEnvironment("Testing"); - builder.UseSetting("Data:Directory", dataDirectory ?? Path.Combine(Path.GetTempPath(), $"agentstration-web-tests-{Guid.NewGuid():N}")); + if (dataDirectory is not null) builder.UseSetting("Data:TestingDirectory", dataDirectory); builder.UseSetting("Logging:LogLevel:Default", "Warning"); }); } diff --git a/tests/Agentstration.Web.Tests/QuartzHostLifecycleTests.cs b/tests/Agentstration.Web.Tests/QuartzHostLifecycleTests.cs index 84e0a6b7..dfc3d08c 100644 --- a/tests/Agentstration.Web.Tests/QuartzHostLifecycleTests.cs +++ b/tests/Agentstration.Web.Tests/QuartzHostLifecycleTests.cs @@ -1,12 +1,38 @@ using System.Net; +using Agentstration.Web.Hosting; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.DependencyInjection; namespace Agentstration.Web.Tests; [TestClass] public sealed class QuartzHostLifecycleTests { + [TestMethod] + public async Task UnconfiguredTestingHostDeletesItsOwnedDataDirectoryAfterShutdown() + { + string directory; + await using (var factory = new WebApplicationFactory().WithWebHostBuilder(builder => + { + builder.UseEnvironment("Testing"); + builder.UseSetting("Logging:LogLevel:Default", "Warning"); + })) + { + using var client = factory.CreateClient(); + Assert.AreEqual(HttpStatusCode.OK, (await client.GetAsync("/health")).StatusCode); + directory = factory.Services.GetRequiredService().DirectoryPath; + Assert.IsTrue(Directory.Exists(directory)); + Assert.IsTrue(File.Exists(Path.Combine(directory, "control-plane.db"))); + Assert.IsTrue(File.Exists(Path.Combine(directory, "work-plane.db"))); + Assert.IsTrue(File.Exists(Path.Combine(directory, "flow-plane.db"))); + Assert.IsTrue(File.Exists(Path.Combine(directory, "runtime-plane.db"))); + Assert.IsTrue(File.Exists(Path.Combine(directory, "identity.db"))); + } + + Assert.IsFalse(Directory.Exists(directory)); + } + [TestMethod] public async Task SchedulerDatabaseIsReleasedAfterEveryHostShutdown() { @@ -36,7 +62,7 @@ public async Task SchedulerDatabaseIsReleasedAfterEveryHostShutdown() new WebApplicationFactory().WithWebHostBuilder(builder => { builder.UseEnvironment("Testing"); - builder.UseSetting("Data:Directory", dataDirectory); + builder.UseSetting("Data:TestingDirectory", dataDirectory); builder.UseSetting("Logging:LogLevel:Default", "Warning"); }); } diff --git a/tests/Agentstration.Work.Api.Tests/WorkplaceApiTests.cs b/tests/Agentstration.Work.Api.Tests/WorkplaceApiTests.cs index 3e61a99c..844c8fe5 100644 --- a/tests/Agentstration.Work.Api.Tests/WorkplaceApiTests.cs +++ b/tests/Agentstration.Work.Api.Tests/WorkplaceApiTests.cs @@ -36,7 +36,7 @@ public async Task AgentBindingPublishesPinnedDirectAgentFlowAndDraftDoesNotAffec await using var factory = new WebApplicationFactory().WithWebHostBuilder(builder => { builder.UseEnvironment("Testing"); - builder.UseSetting("Data:Directory", dataDirectory); + builder.UseSetting("Data:TestingDirectory", dataDirectory); }); using var client = factory.CreateClient(); @@ -206,7 +206,7 @@ public async Task TaskFlowRunEndpointEnforcesTenantWorkspaceAndPrincipalScope() await using var factory = new WebApplicationFactory().WithWebHostBuilder(builder => { builder.UseEnvironment("Testing"); - builder.UseSetting("Data:Directory", dataDirectory); + builder.UseSetting("Data:TestingDirectory", dataDirectory); }); using var client = factory.CreateClient(); var current = await factory.Services.GetRequiredService().EnsureInitializedAsync(default); @@ -301,7 +301,7 @@ public async Task ImmediateResponseConversationCanContinueWithoutCreatingATask() await using var factory = new WebApplicationFactory().WithWebHostBuilder(builder => { builder.UseEnvironment("Testing"); - builder.UseSetting("Data:Directory", dataDirectory); + builder.UseSetting("Data:TestingDirectory", dataDirectory); }); using var client = factory.CreateClient(); using var submittedResponse = await client.PostAsJsonAsync( @@ -342,7 +342,7 @@ public async Task CompletedTaskCanContinueWithANewCorrelatedFlowRunAndVersionedO await using var factory = new WebApplicationFactory().WithWebHostBuilder(builder => { builder.UseEnvironment("Testing"); - builder.UseSetting("Data:Directory", dataDirectory); + builder.UseSetting("Data:TestingDirectory", dataDirectory); }); using var client = factory.CreateClient(); using var submittedResponse = await client.PostAsJsonAsync( @@ -440,7 +440,7 @@ public async Task PendingActionUsesSingleUseTokenAndDoesNotInventDeliverables() await using var factory = new WebApplicationFactory().WithWebHostBuilder(builder => { builder.UseEnvironment("Testing"); - builder.UseSetting("Data:Directory", dataDirectory); + builder.UseSetting("Data:TestingDirectory", dataDirectory); }); using var client = factory.CreateClient(); var taskCreated = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); From 862b73c4d43bf85c8636d53ebd3efa3924464693 Mon Sep 17 00:00:00 2001 From: gbaudrit Date: Mon, 31 Aug 2026 22:18:21 +0200 Subject: [PATCH 2/5] test: parallelize management test classes --- tests/Agentstration.Management.Tests/AssemblyInfo.cs | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 tests/Agentstration.Management.Tests/AssemblyInfo.cs diff --git a/tests/Agentstration.Management.Tests/AssemblyInfo.cs b/tests/Agentstration.Management.Tests/AssemblyInfo.cs new file mode 100644 index 00000000..903504ed --- /dev/null +++ b/tests/Agentstration.Management.Tests/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.ClassLevel)] From 65278ca736d9a261c9f3df49da6a8ddec3cdca13 Mon Sep 17 00:00:00 2001 From: gbaudrit Date: Mon, 31 Aug 2026 22:18:32 +0200 Subject: [PATCH 3/5] test: normalize MCP catalog test line endings --- .../McpToolCatalogTests.cs | 306 +++++++++--------- 1 file changed, 153 insertions(+), 153 deletions(-) diff --git a/tests/Agentstration.Web.Tests/McpToolCatalogTests.cs b/tests/Agentstration.Web.Tests/McpToolCatalogTests.cs index f4321990..b57befcf 100644 --- a/tests/Agentstration.Web.Tests/McpToolCatalogTests.cs +++ b/tests/Agentstration.Web.Tests/McpToolCatalogTests.cs @@ -1,10 +1,10 @@ -extern alias UtilitiesExtension; - -using System.Net.Http; -using System.Text.Json; -using Agentstration.Management.Abstractions; -using Agentstration.Runtime.Abstractions; -using Agentstration.Runtime.Core; +extern alias UtilitiesExtension; + +using System.Net.Http; +using System.Text.Json; +using Agentstration.Management.Abstractions; +using Agentstration.Runtime.Abstractions; +using Agentstration.Runtime.Core; using Agentstration.Tools.Mcp; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; @@ -13,13 +13,13 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; - -namespace Agentstration.Web.Tests; - -[TestClass] -public sealed class McpToolCatalogTests -{ - [TestMethod] + +namespace Agentstration.Web.Tests; + +[TestClass] +public sealed class McpToolCatalogTests +{ + [TestMethod] public void ServerMcpEndpointRemainsAvailableWithoutLegacyPlatformTools() { using var host = new WebApplicationFactory().WithWebHostBuilder(builder => builder.UseEnvironment("Testing")); @@ -47,40 +47,40 @@ public void ServerMcpEndpointRemainsAvailableWithoutLegacyPlatformTools() Assert.IsFalse(publishedToolNames.Any(name => legacyTools.Contains(name, StringComparer.OrdinalIgnoreCase))); Assert.IsNull(typeof(global::Program).Assembly.GetType("Agentstration.Web.PlatformMcpTools")); } - - [TestMethod] - public async Task GovernanceBlocksDisabledProviderToolAndUnavailableTool() - { - await using var host = new WebApplicationFactory().WithWebHostBuilder(builder => builder.UseEnvironment("Testing")); - var adapter = Adapter(host); - var provider = Provider(); - var tool = Tool(provider.Metadata.Name); - - await AssertCodeAsync("tool_provider_disabled", provider with { Definition = provider.Definition with { Enabled = false } }, tool); - await AssertCodeAsync("tool_disabled", provider, tool with { Definition = tool.Definition with { Enabled = false } }); - await AssertCodeAsync("tool_unavailable", provider, tool with { Definition = tool.Definition with { Discovery = tool.Definition.Discovery! with { Available = false } } }); - - async Task AssertCodeAsync(string code, ToolProviderResource currentProvider, ToolResource currentTool) - { - var store = new FakeStore(currentProvider, currentTool); - var catalog = new McpToolCatalog(store, adapter); - var error = await Assert.ThrowsAsync(async () => await catalog.ResolveAsync([currentTool.Metadata.Name])); - Assert.AreEqual(code, error.Code); - var invocationError = await Assert.ThrowsAsync(async () => - await new ToolExecutionPipeline(new McpToolInvoker(store, adapter)).ExecuteAsync(new ToolExecutionContext - { - ToolCallId = "governance-call", - InvocationId = "governance-invocation", - ToolId = currentTool.Metadata.Name, - ToolName = currentTool.Definition.ExternalId ?? currentTool.Metadata.Name, - ToolProviderId = currentProvider.Metadata.Name, - ExternalToolId = currentTool.Definition.ExternalId - })); - Assert.AreEqual(code, invocationError.Code); - } - } - - [TestMethod] + + [TestMethod] + public async Task GovernanceBlocksDisabledProviderToolAndUnavailableTool() + { + await using var host = new WebApplicationFactory().WithWebHostBuilder(builder => builder.UseEnvironment("Testing")); + var adapter = Adapter(host); + var provider = Provider(); + var tool = Tool(provider.Metadata.Name); + + await AssertCodeAsync("tool_provider_disabled", provider with { Definition = provider.Definition with { Enabled = false } }, tool); + await AssertCodeAsync("tool_disabled", provider, tool with { Definition = tool.Definition with { Enabled = false } }); + await AssertCodeAsync("tool_unavailable", provider, tool with { Definition = tool.Definition with { Discovery = tool.Definition.Discovery! with { Available = false } } }); + + async Task AssertCodeAsync(string code, ToolProviderResource currentProvider, ToolResource currentTool) + { + var store = new FakeStore(currentProvider, currentTool); + var catalog = new McpToolCatalog(store, adapter); + var error = await Assert.ThrowsAsync(async () => await catalog.ResolveAsync([currentTool.Metadata.Name])); + Assert.AreEqual(code, error.Code); + var invocationError = await Assert.ThrowsAsync(async () => + await new ToolExecutionPipeline(new McpToolInvoker(store, adapter)).ExecuteAsync(new ToolExecutionContext + { + ToolCallId = "governance-call", + InvocationId = "governance-invocation", + ToolId = currentTool.Metadata.Name, + ToolName = currentTool.Definition.ExternalId ?? currentTool.Metadata.Name, + ToolProviderId = currentProvider.Metadata.Name, + ExternalToolId = currentTool.Definition.ExternalId + })); + Assert.AreEqual(code, invocationError.Code); + } + } + + [TestMethod] public async Task ApprovalGovernanceIsRetainedAsProviderNeutralMetadata() { await using var host = new WebApplicationFactory(); @@ -98,110 +98,110 @@ public async Task ApprovalGovernanceIsRetainedAsProviderNeutralMetadata() new TestHttpClientFactory(host.Server.CreateHandler()), NullLoggerFactory.Instance); var catalog = new McpToolCatalog(new FakeStore(provider, tool), adapter); - - var runtime = (await catalog.ResolveAsync([tool.Metadata.Name])).Single(); - - Assert.IsTrue(runtime.RequiresApproval); - Assert.IsFalse(runtime is AITool); - } - - [TestMethod] - public async Task AepContributionInvokesMcpThroughTheSameExecutionPipeline() - { - await using var host = new WebApplicationFactory(); - var configuration = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary - { - ["Agentstration:Extensions:Agentstration.Extensions.Utilities:Endpoint"] = "http://extension/" - }).Build(); - var adapter = new ToolProviderAdapter( - new ConfigurationAepExtensionEndpointResolver(configuration), - new ConfigurationToolProviderEnvironmentResolver(configuration), - new TestHttpClientFactory(host.Server.CreateHandler()), - NullLoggerFactory.Instance); - var provider = Provider() with - { - Metadata = new ResourceMetadata { Name = "utilities" }, - Definition = new ToolProviderProperties - { - DisplayName = "Utilities AEP", - ProviderType = ToolProviderType.Aep, - Aep = new AepToolProviderConfiguration { ExtensionId = "Agentstration.Extensions.Utilities" } - } - }; - var tool = Tool(provider.Metadata.Name) with - { - Metadata = new ResourceMetadata { Name = "utilities.hash.compute" }, - Definition = Tool(provider.Metadata.Name).Definition with { ExternalId = "hash.compute" } - }; - var store = new FakeStore(provider, tool); - var descriptor = (await new McpToolCatalog(store, adapter).ResolveAsync([tool.Metadata.Name])).Single(); - var pipeline = new ToolExecutionPipeline(new McpToolInvoker(store, adapter)); - var context = Context(descriptor) with - { - Arguments = JsonSerializer.SerializeToElement(new { text = "agentstration" }) - }; - - var result = await pipeline.ExecuteAsync(context); - - Assert.AreEqual("utilities", descriptor.ProviderId); - Assert.AreEqual("hash.compute", descriptor.ExternalId); - Assert.IsNotNull(result); - StringAssert.Contains(result.Value.GetRawText(), UtilitiesExtension::Agentstration.Extensions.Utilities.UtilityTools.ComputeHash("agentstration")); - } - - private static ToolExecutionContext Context(IAgentTool tool) => new() - { - ToolCallId = "call-1", - InvocationId = "invocation-1", - ToolId = tool.Id, - ToolName = tool.Name, - ToolProviderId = tool.ProviderId, - ExternalToolId = tool.ExternalId - }; - - private static ToolProviderAdapter Adapter(WebApplicationFactory host) - { - var configuration = new ConfigurationBuilder().Build(); - return new ToolProviderAdapter(new ConfigurationAepExtensionEndpointResolver(configuration), new ConfigurationToolProviderEnvironmentResolver(configuration), new TestHttpClientFactory(host.Server.CreateHandler()), NullLoggerFactory.Instance); - } - - private static ToolProviderResource Provider() => new() - { - ApiVersion = ManagementApiVersions.CoreV1, - Kind = ResourceKinds.ToolProvider, - Metadata = new ResourceMetadata { Name = "local" }, - Definition = new ToolProviderProperties { DisplayName = "Local MCP", ProviderType = ToolProviderType.Mcp, Mcp = new McpToolProviderConfiguration { Transport = McpToolProviderTransport.StreamableHttp, Endpoint = new Uri("http://localhost/mcp") } } - }; - - private static ToolResource Tool(string providerId) => new() - { - ApiVersion = ManagementApiVersions.CoreV1, - Kind = ResourceKinds.Tool, + + var runtime = (await catalog.ResolveAsync([tool.Metadata.Name])).Single(); + + Assert.IsTrue(runtime.RequiresApproval); + Assert.IsFalse(runtime is AITool); + } + + [TestMethod] + public async Task AepContributionInvokesMcpThroughTheSameExecutionPipeline() + { + await using var host = new WebApplicationFactory(); + var configuration = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary + { + ["Agentstration:Extensions:Agentstration.Extensions.Utilities:Endpoint"] = "http://extension/" + }).Build(); + var adapter = new ToolProviderAdapter( + new ConfigurationAepExtensionEndpointResolver(configuration), + new ConfigurationToolProviderEnvironmentResolver(configuration), + new TestHttpClientFactory(host.Server.CreateHandler()), + NullLoggerFactory.Instance); + var provider = Provider() with + { + Metadata = new ResourceMetadata { Name = "utilities" }, + Definition = new ToolProviderProperties + { + DisplayName = "Utilities AEP", + ProviderType = ToolProviderType.Aep, + Aep = new AepToolProviderConfiguration { ExtensionId = "Agentstration.Extensions.Utilities" } + } + }; + var tool = Tool(provider.Metadata.Name) with + { + Metadata = new ResourceMetadata { Name = "utilities.hash.compute" }, + Definition = Tool(provider.Metadata.Name).Definition with { ExternalId = "hash.compute" } + }; + var store = new FakeStore(provider, tool); + var descriptor = (await new McpToolCatalog(store, adapter).ResolveAsync([tool.Metadata.Name])).Single(); + var pipeline = new ToolExecutionPipeline(new McpToolInvoker(store, adapter)); + var context = Context(descriptor) with + { + Arguments = JsonSerializer.SerializeToElement(new { text = "agentstration" }) + }; + + var result = await pipeline.ExecuteAsync(context); + + Assert.AreEqual("utilities", descriptor.ProviderId); + Assert.AreEqual("hash.compute", descriptor.ExternalId); + Assert.IsNotNull(result); + StringAssert.Contains(result.Value.GetRawText(), UtilitiesExtension::Agentstration.Extensions.Utilities.UtilityTools.ComputeHash("agentstration")); + } + + private static ToolExecutionContext Context(IAgentTool tool) => new() + { + ToolCallId = "call-1", + InvocationId = "invocation-1", + ToolId = tool.Id, + ToolName = tool.Name, + ToolProviderId = tool.ProviderId, + ExternalToolId = tool.ExternalId + }; + + private static ToolProviderAdapter Adapter(WebApplicationFactory host) + { + var configuration = new ConfigurationBuilder().Build(); + return new ToolProviderAdapter(new ConfigurationAepExtensionEndpointResolver(configuration), new ConfigurationToolProviderEnvironmentResolver(configuration), new TestHttpClientFactory(host.Server.CreateHandler()), NullLoggerFactory.Instance); + } + + private static ToolProviderResource Provider() => new() + { + ApiVersion = ManagementApiVersions.CoreV1, + Kind = ResourceKinds.ToolProvider, + Metadata = new ResourceMetadata { Name = "local" }, + Definition = new ToolProviderProperties { DisplayName = "Local MCP", ProviderType = ToolProviderType.Mcp, Mcp = new McpToolProviderConfiguration { Transport = McpToolProviderTransport.StreamableHttp, Endpoint = new Uri("http://localhost/mcp") } } + }; + + private static ToolResource Tool(string providerId) => new() + { + ApiVersion = ManagementApiVersions.CoreV1, + Kind = ResourceKinds.Tool, Metadata = new ResourceMetadata { Name = "local.sample_tool" }, - Definition = new ToolResourceProperties - { - DisplayName = "List workspaces", - Provider = new ResourceReference(providerId), + Definition = new ToolResourceProperties + { + DisplayName = "List workspaces", + Provider = new ResourceReference(providerId), ExternalId = "sample_tool", - Enabled = true, - Discovery = new ToolDiscoveryState { Available = true, FirstSeenAt = DateTimeOffset.UnixEpoch, LastSeenAt = DateTimeOffset.UnixEpoch }, - Schema = new ToolSchema { Input = JsonSerializer.SerializeToElement(new { type = "object" }) } - } - }; - - private sealed class TestHttpClientFactory(HttpMessageHandler handler) : IHttpClientFactory - { - public HttpClient CreateClient(string name) => new(handler, false) { BaseAddress = new Uri("http://localhost/") }; - } - - private sealed class FakeStore(params Resource[] resources) : IControlPlaneStore - { - private readonly Dictionary values = resources.ToDictionary(value => new ResourceKey(value.Kind, value.Metadata.Name)); - public Task InitializeAsync(CancellationToken cancellationToken) => Task.CompletedTask; - public Task?> GetAsync(ResourceKey key, CancellationToken cancellationToken) where T : Resource => Task.FromResult(values.TryGetValue(key, out var value) && value is T typed ? new StoredResource(typed, "test", DateTimeOffset.UnixEpoch) : null); - public Task>> ListAsync(string kind, int skip, int take, CancellationToken cancellationToken) where T : Resource => throw new NotSupportedException(); - public Task> PutAsync(T resource, string? ifMatch, bool ifNoneMatch, CancellationToken cancellationToken) where T : Resource => throw new NotSupportedException(); - public Task> CreateImmutableAsync(T resource, CancellationToken cancellationToken) where T : Resource => throw new NotSupportedException(); - public Task DeleteAsync(ResourceKey key, string? ifMatch, CancellationToken cancellationToken) => throw new NotSupportedException(); - } -} + Enabled = true, + Discovery = new ToolDiscoveryState { Available = true, FirstSeenAt = DateTimeOffset.UnixEpoch, LastSeenAt = DateTimeOffset.UnixEpoch }, + Schema = new ToolSchema { Input = JsonSerializer.SerializeToElement(new { type = "object" }) } + } + }; + + private sealed class TestHttpClientFactory(HttpMessageHandler handler) : IHttpClientFactory + { + public HttpClient CreateClient(string name) => new(handler, false) { BaseAddress = new Uri("http://localhost/") }; + } + + private sealed class FakeStore(params Resource[] resources) : IControlPlaneStore + { + private readonly Dictionary values = resources.ToDictionary(value => new ResourceKey(value.Kind, value.Metadata.Name)); + public Task InitializeAsync(CancellationToken cancellationToken) => Task.CompletedTask; + public Task?> GetAsync(ResourceKey key, CancellationToken cancellationToken) where T : Resource => Task.FromResult(values.TryGetValue(key, out var value) && value is T typed ? new StoredResource(typed, "test", DateTimeOffset.UnixEpoch) : null); + public Task>> ListAsync(string kind, int skip, int take, CancellationToken cancellationToken) where T : Resource => throw new NotSupportedException(); + public Task> PutAsync(T resource, string? ifMatch, bool ifNoneMatch, CancellationToken cancellationToken) where T : Resource => throw new NotSupportedException(); + public Task> CreateImmutableAsync(T resource, CancellationToken cancellationToken) where T : Resource => throw new NotSupportedException(); + public Task DeleteAsync(ResourceKey key, string? ifMatch, CancellationToken cancellationToken) => throw new NotSupportedException(); + } +} From 0c689bcfd941159c62b88e6e96cb85e96c39f122 Mon Sep 17 00:00:00 2001 From: gbaudrit Date: Mon, 31 Aug 2026 22:18:45 +0200 Subject: [PATCH 4/5] ci: remove superseded build workflow --- .github/workflows/build.yml | 57 ------------------------------------- 1 file changed, 57 deletions(-) delete mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 62efc40b..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Build and test - -on: - pull_request: - push: - branches: [main] - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: build-${{ github.ref }} - cancel-in-progress: true - -jobs: - platform: - name: Platform - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-dotnet@v4 - with: - global-json-file: global.json - - name: Restore - run: dotnet restore Agentstration.slnx -p:NuGetAudit=true -p:NuGetAuditMode=all - - name: Build - run: dotnet build Agentstration.slnx --configuration Release --no-restore - - name: Test - run: dotnet test Agentstration.slnx --configuration Release --no-build - - aep: - name: AEP SDK - runs-on: ubuntu-latest - defaults: - run: - working-directory: aep - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-dotnet@v4 - with: - global-json-file: global.json - - name: Restore - run: dotnet restore Aep.slnx -p:NuGetAudit=true -p:NuGetAuditMode=all - - name: Build - run: dotnet build Aep.slnx --configuration Release --no-restore - - name: Test - run: dotnet test Aep.slnx --configuration Release --no-build - - container: - name: Container - runs-on: ubuntu-latest - needs: platform - steps: - - uses: actions/checkout@v4 - - name: Build image - run: docker build --file Dockerfile --tag agentstration:ci . From 3f33030fab574af2ee510661c3cd421f47c6ab70 Mon Sep 17 00:00:00 2001 From: gbaudrit Date: Tue, 1 Sep 2026 07:24:18 +0200 Subject: [PATCH 5/5] fix(test): preserve storage paths and isolate SQLite pools --- .../TestingDataDirectoryCleanupService.cs | 13 ++++++++++- src/Agentstration.Web/Program.cs | 12 +++++----- .../ControlPlaneStoreHardeningTests.cs | 3 +-- .../DeclarativeBootstrapTests.cs | 3 +-- .../IdentityFoundationTests.cs | 2 +- .../LocalIdentityPersistenceTests.cs | 3 +-- .../PackCompositionTests.cs | 3 +-- .../PackTests.cs | 3 +-- .../SecurityAuditPersistenceTests.cs | 3 +-- .../SqliteTestCleanup.cs | 22 +++++++++++++++++++ .../TriggerTests.cs | 5 ++--- .../QuartzHostLifecycleTests.cs | 6 ++++- 12 files changed, 55 insertions(+), 23 deletions(-) create mode 100644 tests/Agentstration.Management.Tests/SqliteTestCleanup.cs diff --git a/src/Agentstration.Web/Hosting/TestingDataDirectoryCleanupService.cs b/src/Agentstration.Web/Hosting/TestingDataDirectoryCleanupService.cs index 7c3d3cac..ed5773af 100644 --- a/src/Agentstration.Web/Hosting/TestingDataDirectoryCleanupService.cs +++ b/src/Agentstration.Web/Hosting/TestingDataDirectoryCleanupService.cs @@ -10,7 +10,7 @@ internal sealed class TestingDataDirectoryCleanupService(string directory, bool public async Task StopAsync(CancellationToken cancellationToken) { - SqliteConnection.ClearAllPools(); + ClearPoolsInDirectory(DirectoryPath); if (!deleteOnShutdown) return; for (var attempt = 0; ; attempt++) @@ -30,4 +30,15 @@ public async Task StopAsync(CancellationToken cancellationToken) } } } + + private static void ClearPoolsInDirectory(string directory) + { + if (!Directory.Exists(directory)) return; + + foreach (var databasePath in Directory.EnumerateFiles(directory, "*.db", SearchOption.AllDirectories)) + { + using var connection = new SqliteConnection($"Data Source={databasePath}"); + SqliteConnection.ClearPool(connection); + } + } } diff --git a/src/Agentstration.Web/Program.cs b/src/Agentstration.Web/Program.cs index 520ce215..1acb9004 100644 --- a/src/Agentstration.Web/Program.cs +++ b/src/Agentstration.Web/Program.cs @@ -43,7 +43,9 @@ var configuredTestingDataDirectory = builder.Configuration["Data:TestingDirectory"]; var ownsTestingDataDirectory = isTesting && string.IsNullOrWhiteSpace(configuredTestingDataDirectory); var dataDirectory = isTesting - ? configuredTestingDataDirectory ?? Path.Combine(Path.GetTempPath(), $"agentstration-web-tests-{Guid.NewGuid():N}") + ? string.IsNullOrWhiteSpace(configuredTestingDataDirectory) + ? Path.Combine(Path.GetTempPath(), $"agentstration-web-tests-{Guid.NewGuid():N}") + : configuredTestingDataDirectory : builder.Configuration["Data:Directory"] ?? Path.Combine(builder.Environment.ContentRootPath, ".agentstration"); Directory.CreateDirectory(dataDirectory); var identityConnectionString = builder.Configuration.GetConnectionString("Identity") @@ -61,22 +63,22 @@ var aiOptions = new AiProviderOptions(aiProvider, parsedAiEndpoint, builder.Configuration["AI:Model"] ?? "phi4-mini", builder.Configuration["AI:ApiKey"]); var controlPlanePath = isTesting ? Path.Combine(dataDirectory, "control-plane.db") - : builder.Configuration["Data:ControlPlanePath"] ?? Path.Combine(dataDirectory, "control-plane.db"); + : builder.Configuration["Data:ControlPlanePath"] ?? Path.Combine(builder.Environment.ContentRootPath, ".agentstration", "control-plane.db"); var controlPlaneDirectory = Path.GetDirectoryName(controlPlanePath); if (!string.IsNullOrWhiteSpace(controlPlaneDirectory)) Directory.CreateDirectory(controlPlaneDirectory); var workPlanePath = isTesting ? Path.Combine(dataDirectory, "work-plane.db") - : builder.Configuration["Data:WorkPlanePath"] ?? Path.Combine(dataDirectory, "work-plane.db"); + : builder.Configuration["Data:WorkPlanePath"] ?? Path.Combine(builder.Environment.ContentRootPath, ".agentstration", "work-plane.db"); var workPlaneDirectory = Path.GetDirectoryName(workPlanePath); if (!string.IsNullOrWhiteSpace(workPlaneDirectory)) Directory.CreateDirectory(workPlaneDirectory); var flowPath = isTesting ? Path.Combine(dataDirectory, "flow-plane.db") - : builder.Configuration["Data:FlowPath"] ?? Path.Combine(dataDirectory, "flow-plane.db"); + : builder.Configuration["Data:FlowPath"] ?? Path.Combine(builder.Environment.ContentRootPath, ".agentstration", "flow-plane.db"); var flowDirectory = Path.GetDirectoryName(flowPath); if (!string.IsNullOrWhiteSpace(flowDirectory)) Directory.CreateDirectory(flowDirectory); var runtimePath = isTesting ? Path.Combine(dataDirectory, "runtime-plane.db") - : builder.Configuration["Data:RuntimePath"] ?? Path.Combine(dataDirectory, "runtime-plane.db"); + : builder.Configuration["Data:RuntimePath"] ?? Path.Combine(builder.Environment.ContentRootPath, ".agentstration", "runtime-plane.db"); var runtimeDirectory = Path.GetDirectoryName(runtimePath); if (!string.IsNullOrWhiteSpace(runtimeDirectory)) Directory.CreateDirectory(runtimeDirectory); if (isTesting) diff --git a/tests/Agentstration.Management.Tests/ControlPlaneStoreHardeningTests.cs b/tests/Agentstration.Management.Tests/ControlPlaneStoreHardeningTests.cs index 7b811998..d556b7c7 100644 --- a/tests/Agentstration.Management.Tests/ControlPlaneStoreHardeningTests.cs +++ b/tests/Agentstration.Management.Tests/ControlPlaneStoreHardeningTests.cs @@ -3,7 +3,6 @@ using Agentstration.Management.Storage.Sqlite; using Agentstration.Resources; using Agentstration.Runtime.Abstractions; -using Microsoft.Data.Sqlite; using Microsoft.Extensions.DependencyInjection; namespace Agentstration.Management.Tests; @@ -261,7 +260,7 @@ public static async Task CreateAsync(ICurrentRequestContext? conte public async ValueTask DisposeAsync() { await Provider.DisposeAsync(); - SqliteConnection.ClearAllPools(); + SqliteTestCleanup.ClearPoolsInDirectory(directory); Directory.Delete(directory, true); } diff --git a/tests/Agentstration.Management.Tests/DeclarativeBootstrapTests.cs b/tests/Agentstration.Management.Tests/DeclarativeBootstrapTests.cs index faa3cea4..6ead3a23 100644 --- a/tests/Agentstration.Management.Tests/DeclarativeBootstrapTests.cs +++ b/tests/Agentstration.Management.Tests/DeclarativeBootstrapTests.cs @@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc.Testing; -using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -437,7 +436,7 @@ public TemporaryDirectory() public string Path { get; } public void Dispose() { - SqliteConnection.ClearAllPools(); + SqliteTestCleanup.ClearPoolsInDirectory(Path); Directory.Delete(Path, recursive: true); } } diff --git a/tests/Agentstration.Management.Tests/IdentityFoundationTests.cs b/tests/Agentstration.Management.Tests/IdentityFoundationTests.cs index 8754d8fc..aa8f6d4e 100644 --- a/tests/Agentstration.Management.Tests/IdentityFoundationTests.cs +++ b/tests/Agentstration.Management.Tests/IdentityFoundationTests.cs @@ -403,7 +403,7 @@ public async Task ExecuteSqlAsync(string sql) public async ValueTask DisposeAsync() { await Services.DisposeAsync(); - SqliteConnection.ClearAllPools(); + SqliteTestCleanup.ClearPoolsInDirectory(directory); Directory.Delete(directory, recursive: true); } } diff --git a/tests/Agentstration.Management.Tests/LocalIdentityPersistenceTests.cs b/tests/Agentstration.Management.Tests/LocalIdentityPersistenceTests.cs index 395160c2..1b54c5d2 100644 --- a/tests/Agentstration.Management.Tests/LocalIdentityPersistenceTests.cs +++ b/tests/Agentstration.Management.Tests/LocalIdentityPersistenceTests.cs @@ -1,7 +1,6 @@ using Agentstration.Security.AspNetCoreIdentity; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Identity; -using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -145,7 +144,7 @@ private static string TemporaryDirectory() private static void DeleteTemporaryDirectory(string path) { - SqliteConnection.ClearAllPools(); + SqliteTestCleanup.ClearPoolsInDirectory(path); if (Directory.Exists(path)) Directory.Delete(path, recursive: true); } } diff --git a/tests/Agentstration.Management.Tests/PackCompositionTests.cs b/tests/Agentstration.Management.Tests/PackCompositionTests.cs index b6d05db3..ec3217e1 100644 --- a/tests/Agentstration.Management.Tests/PackCompositionTests.cs +++ b/tests/Agentstration.Management.Tests/PackCompositionTests.cs @@ -5,7 +5,6 @@ using Agentstration.Management.Core; using Agentstration.Management.Storage.Sqlite; using Agentstration.Resources; -using Microsoft.Data.Sqlite; using Microsoft.Extensions.DependencyInjection; namespace Agentstration.Management.Tests; @@ -71,7 +70,7 @@ public async Task CreateProjectStoresImmutableWorkspaceSnapshot() } finally { - SqliteConnection.ClearAllPools(); + SqliteTestCleanup.ClearPoolsInDirectory(directory); if (Directory.Exists(directory)) Directory.Delete(directory, true); } } diff --git a/tests/Agentstration.Management.Tests/PackTests.cs b/tests/Agentstration.Management.Tests/PackTests.cs index 9b24f047..c47216d3 100644 --- a/tests/Agentstration.Management.Tests/PackTests.cs +++ b/tests/Agentstration.Management.Tests/PackTests.cs @@ -15,7 +15,6 @@ using Agentstration.Runtime.Contracts; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; -using Microsoft.Data.Sqlite; using Microsoft.Extensions.DependencyInjection; namespace Agentstration.Management.Tests; @@ -880,7 +879,7 @@ public static async Task CreateAsync() } public async ValueTask DisposeAsync() { - await provider.DisposeAsync(); SqliteConnection.ClearAllPools(); if (Directory.Exists(directory)) Directory.Delete(directory, true); + await provider.DisposeAsync(); SqliteTestCleanup.ClearPoolsInDirectory(directory); if (Directory.Exists(directory)) Directory.Delete(directory, true); } } } diff --git a/tests/Agentstration.Management.Tests/SecurityAuditPersistenceTests.cs b/tests/Agentstration.Management.Tests/SecurityAuditPersistenceTests.cs index 25ac7ee7..dbe691d3 100644 --- a/tests/Agentstration.Management.Tests/SecurityAuditPersistenceTests.cs +++ b/tests/Agentstration.Management.Tests/SecurityAuditPersistenceTests.cs @@ -1,6 +1,5 @@ using Agentstration.Management.Abstractions; using Agentstration.Management.Storage.Sqlite; -using Microsoft.Data.Sqlite; using Microsoft.Extensions.DependencyInjection; namespace Agentstration.Management.Tests; @@ -45,7 +44,7 @@ public async Task AuditEventsSurviveControlPlaneProviderRestart() } finally { - SqliteConnection.ClearAllPools(); + SqliteTestCleanup.ClearPoolsInDirectory(directory); if (Directory.Exists(directory)) Directory.Delete(directory, recursive: true); } } diff --git a/tests/Agentstration.Management.Tests/SqliteTestCleanup.cs b/tests/Agentstration.Management.Tests/SqliteTestCleanup.cs new file mode 100644 index 00000000..28503c80 --- /dev/null +++ b/tests/Agentstration.Management.Tests/SqliteTestCleanup.cs @@ -0,0 +1,22 @@ +using Microsoft.Data.Sqlite; + +namespace Agentstration.Management.Tests; + +internal static class SqliteTestCleanup +{ + public static void ClearPoolsInDirectory(string directory) + { + if (!Directory.Exists(directory)) return; + + foreach (var databasePath in Directory.EnumerateFiles(directory, "*.db", SearchOption.AllDirectories)) + { + ClearPool(databasePath); + } + } + + public static void ClearPool(string databasePath) + { + using var connection = new SqliteConnection($"Data Source={databasePath}"); + SqliteConnection.ClearPool(connection); + } +} diff --git a/tests/Agentstration.Management.Tests/TriggerTests.cs b/tests/Agentstration.Management.Tests/TriggerTests.cs index 9f2f46f5..7a87d223 100644 --- a/tests/Agentstration.Management.Tests/TriggerTests.cs +++ b/tests/Agentstration.Management.Tests/TriggerTests.cs @@ -9,7 +9,6 @@ using Agentstration.Management.Core; using Agentstration.Management.Storage.Sqlite; using Agentstration.Resources; -using Microsoft.Data.Sqlite; using Microsoft.Extensions.DependencyInjection; namespace Agentstration.Management.Tests; @@ -114,7 +113,7 @@ public async Task OccurrenceStoreIsIdempotentWorkspaceScopedAndSurvivesRestartAs Assert.IsEmpty(await restarted.Store.ListAsync(Guid.NewGuid(), trigger, 10, CancellationToken.None)); } } - finally { SqliteConnection.ClearAllPools(); File.Delete(database); } + finally { SqliteTestCleanup.ClearPool(database); File.Delete(database); } } [TestMethod] @@ -299,7 +298,7 @@ public static async Task CreateAsync() public async ValueTask DisposeAsync() { await Services.DisposeAsync(); - SqliteConnection.ClearAllPools(); + SqliteTestCleanup.ClearPool(Database); File.Delete(Database); } } diff --git a/tests/Agentstration.Web.Tests/QuartzHostLifecycleTests.cs b/tests/Agentstration.Web.Tests/QuartzHostLifecycleTests.cs index dfc3d08c..0bb00a28 100644 --- a/tests/Agentstration.Web.Tests/QuartzHostLifecycleTests.cs +++ b/tests/Agentstration.Web.Tests/QuartzHostLifecycleTests.cs @@ -10,12 +10,16 @@ namespace Agentstration.Web.Tests; public sealed class QuartzHostLifecycleTests { [TestMethod] - public async Task UnconfiguredTestingHostDeletesItsOwnedDataDirectoryAfterShutdown() + [DataRow(null)] + [DataRow("")] + [DataRow(" ")] + public async Task MissingOrWhitespaceTestingDirectoryUsesAndDeletesOwnedDirectory(string? configuredDirectory) { string directory; await using (var factory = new WebApplicationFactory().WithWebHostBuilder(builder => { builder.UseEnvironment("Testing"); + if (configuredDirectory is not null) builder.UseSetting("Data:TestingDirectory", configuredDirectory); builder.UseSetting("Logging:LogLevel:Default", "Warning"); })) {