| File: AzureStorage\AzureStoragePathValidationTests.cs | Web Access |
| Project: src\test\Libraries\Microsoft.Extensions.AI.Evaluation.Reporting.Tests\Microsoft.Extensions.AI.Evaluation.Reporting.Tests.csproj (Microsoft.Extensions.AI.Evaluation.Reporting.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Threading.Tasks; using Azure.Storage.Files.DataLake; using Microsoft.Extensions.AI.Evaluation.Reporting.Storage; using Microsoft.Extensions.Caching.Distributed; using Xunit; namespace Microsoft.Extensions.AI.Evaluation.Reporting.Tests; /// <summary> /// Verifies that the Azure Storage backed <see cref="IEvaluationResultStore"/> and /// <see cref="IDistributedCache"/> implementations reject caller-supplied names that are not valid single /// path segments (mirroring the validation performed by the disk based implementations). These tests do not /// require a configured Azure Storage account because validation occurs before any network request is issued. /// </summary> public class AzureStoragePathValidationTests { // A DataLakeDirectoryClient constructed from a bare Uri is anonymous and never contacts the network in // these tests, since validation throws before any request is built. private static DataLakeDirectoryClient CreateDummyClient() => new(new Uri("https://account.dfs.core.windows.net/container/root")); public static IEnumerable<object[]> InvalidSegments() { yield return new object[] { ".." }; yield return new object[] { "." }; yield return new object[] { "../x" }; yield return new object[] { "../../x" }; yield return new object[] { "foo/bar" }; yield return new object[] { " leading" }; yield return new object[] { "trailing " }; } private static ScenarioRunResult CreateResult(string scenarioName, string iterationName, string executionName) => new( scenarioName: scenarioName, iterationName: iterationName, executionName: executionName, creationTime: DateTime.UtcNow, messages: [new ChatMessage(ChatRole.User, "User prompt")], modelResponse: new ChatResponse(new ChatMessage(ChatRole.Assistant, "LLM response")), evaluationResult: new EvaluationResult(new BooleanMetric("boolean", value: true))); [Theory] [MemberData(nameof(InvalidSegments))] public async Task WriteResultsAsync_InvalidExecutionName_Throws(string segment) { var store = new AzureStorageResultStore(CreateDummyClient()); ScenarioRunResult result = CreateResult("scenario", "iteration", segment); await Assert.ThrowsAsync<ArgumentException>( async () => await store.WriteResultsAsync([result])); } [Theory] [MemberData(nameof(InvalidSegments))] public async Task WriteResultsAsync_InvalidScenarioName_Throws(string segment) { var store = new AzureStorageResultStore(CreateDummyClient()); ScenarioRunResult result = CreateResult(segment, "iteration", "execution"); await Assert.ThrowsAsync<ArgumentException>( async () => await store.WriteResultsAsync([result])); } [Theory] [MemberData(nameof(InvalidSegments))] public async Task WriteResultsAsync_InvalidIterationName_Throws(string segment) { var store = new AzureStorageResultStore(CreateDummyClient()); ScenarioRunResult result = CreateResult("scenario", segment, "execution"); await Assert.ThrowsAsync<ArgumentException>( async () => await store.WriteResultsAsync([result])); } [Theory] [MemberData(nameof(InvalidSegments))] public async Task ReadResultsAsync_InvalidExecutionName_Throws(string segment) { var store = new AzureStorageResultStore(CreateDummyClient()); await Assert.ThrowsAsync<ArgumentException>(async () => { await foreach (ScenarioRunResult result in store.ReadResultsAsync(segment)) { _ = result; } }); } [Theory] [MemberData(nameof(InvalidSegments))] public async Task DeleteResultsAsync_InvalidExecutionName_Throws(string segment) { var store = new AzureStorageResultStore(CreateDummyClient()); await Assert.ThrowsAsync<ArgumentException>( async () => await store.DeleteResultsAsync(segment)); } [Theory] [MemberData(nameof(InvalidSegments))] public async Task GetScenarioNamesAsync_InvalidExecutionName_Throws(string segment) { var store = new AzureStorageResultStore(CreateDummyClient()); await Assert.ThrowsAsync<ArgumentException>(async () => { await foreach (string scenarioName in store.GetScenarioNamesAsync(segment)) { _ = scenarioName; } }); } [Theory] [MemberData(nameof(InvalidSegments))] public async Task GetIterationNamesAsync_InvalidScenarioName_Throws(string segment) { var store = new AzureStorageResultStore(CreateDummyClient()); await Assert.ThrowsAsync<ArgumentException>(async () => { await foreach (string iterationName in store.GetIterationNamesAsync("execution", segment)) { _ = iterationName; } }); } [Theory] [MemberData(nameof(InvalidSegments))] public async Task GetCacheAsync_InvalidScenarioName_Throws(string segment) { var provider = new AzureStorageResponseCacheProvider(CreateDummyClient()); await Assert.ThrowsAsync<ArgumentException>( async () => await provider.GetCacheAsync(segment, "iteration")); } [Theory] [MemberData(nameof(InvalidSegments))] public async Task GetCacheAsync_InvalidIterationName_Throws(string segment) { var provider = new AzureStorageResponseCacheProvider(CreateDummyClient()); await Assert.ThrowsAsync<ArgumentException>( async () => await provider.GetCacheAsync("scenario", segment)); } [Theory] [MemberData(nameof(InvalidSegments))] public async Task Cache_InvalidKey_Throws(string segment) { var provider = new AzureStorageResponseCacheProvider(CreateDummyClient()); IDistributedCache cache = await provider.GetCacheAsync("scenario", "iteration"); await Assert.ThrowsAsync<ArgumentException>(async () => await cache.GetAsync(segment)); await Assert.ThrowsAsync<ArgumentException>( async () => await cache.SetAsync(segment, [1, 2, 3], new DistributedCacheEntryOptions())); await Assert.ThrowsAsync<ArgumentException>(async () => await cache.RemoveAsync(segment)); await Assert.ThrowsAsync<ArgumentException>(async () => await cache.RefreshAsync(segment)); Assert.Throws<ArgumentException>(() => cache.Get(segment)); Assert.Throws<ArgumentException>( () => cache.Set(segment, [1, 2, 3], new DistributedCacheEntryOptions())); Assert.Throws<ArgumentException>(() => cache.Remove(segment)); Assert.Throws<ArgumentException>(() => cache.Refresh(segment)); } }