From c826374ab2710797c5d4157a70846eebe6c8e36c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viviana=20Due=C3=B1as=20Chavez?= Date: Wed, 17 Sep 2025 12:03:46 -0700 Subject: [PATCH 1/6] Add project name normalization to match aspire's code generator for --aspire projects. Add execution tests for the fix. --- .../.template.config/template.json | 26 +++++++++++++++++++ .../AIChatWebExecutionTests.cs | 23 ++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/.template.config/template.json b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/.template.config/template.json index caeceae1dde..7c1ec674d17 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/.template.config/template.json +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/.template.config/template.json @@ -499,6 +499,12 @@ "valueTransform": "vectorStoreIndexNameTransform", "replaces": "data-ChatWithCustomData-CSharp.Web-" }, + "projectNameReplacer": { + "type": "derived", + "valueSource": "name", + "valueTransform": "projectNameTransform_Formatting", + "replaces": "ChatWithCustomData_CSharp_Web" + }, "webProjectNamespaceAdjuster": { "type": "generated", "generator": "switch", @@ -524,6 +530,26 @@ } }, "forms": { + "projectNameTransform_Normalize": { + "identifier": "replace", + "pattern": "[^a-zA-Z0-9_]", + "replacement": "_", + "description": "Replace non-word chars, start before digit, after period with underscore" + }, + "projectNameTransform_AppendWeb": { + "identifier": "replace", + "pattern": "^(.*)$", + "replacement": "$1_Web", + "description": "Append _Web to the project name" + }, + "projectNameTransform_Formatting": { + "identifier": "chain", + "steps": [ + "projectNameTransform_Normalize", + "projectNameTransform_AppendWeb" + ], + "description": "Normalize and add _Web suffix" + }, "vectorStoreIndexNameTransform": { "identifier": "chain", "steps": [ diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs index 1b8c4177f40..d3458e8a1d0 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs @@ -71,6 +71,29 @@ public async Task CreateRestoreAndBuild_AspireTemplate(params string[] args) await Fixture.BuildProjectAsync(project); } + /// + /// Tests build for a project name containing a dot ('.'), which triggers the class name normalization bug described in issue #6811. + /// This runs for all option combinations with --aspire true. + /// + [Theory] + [MemberData(nameof(GetAspireTemplateOptions))] + public async Task CreateRestoreAndBuild_ProjectNameWithDot(params string[] args) + { + // Use a project name with a dot to trigger the bug. + const string ProjectNameWithDot = "dot.name"; + var project = await Fixture.CreateProjectAsync( + templateName: "aichatweb", + projectName: ProjectNameWithDot, + args); + + project.StartupProjectRelativePath = $"{ProjectNameWithDot}.AppHost"; + + await Fixture.RestoreProjectAsync(project); + + // Just attempt to build; if the bug is present, this will fail with CS0234. + await Fixture.BuildProjectAsync(project); + } + private static readonly (string name, string[] values)[] _templateOptions = [ ("--provider", ["azureopenai", "githubmodels", "ollama", "openai"]), ("--vector-store", ["azureaisearch", "local", "qdrant"]), From 8d333a8e3f15fb8b533f1c826926264b3a61df92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viviana=20Due=C3=B1as=20Chavez?= Date: Thu, 18 Sep 2025 17:13:50 -0700 Subject: [PATCH 2/6] Address PR feedback: Update regex logic and execution test. --- .../.template.config/template.json | 27 ++++++----- .../Program.cs | 2 +- .../AIChatWebExecutionTests.cs | 45 ++++++++++++------- 3 files changed, 48 insertions(+), 26 deletions(-) diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/.template.config/template.json b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/.template.config/template.json index 7c1ec674d17..101fead3e72 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/.template.config/template.json +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/.template.config/template.json @@ -499,11 +499,11 @@ "valueTransform": "vectorStoreIndexNameTransform", "replaces": "data-ChatWithCustomData-CSharp.Web-" }, - "projectNameReplacer": { + "aspireClassNameReplacer": { "type": "derived", "valueSource": "name", - "valueTransform": "projectNameTransform_Formatting", - "replaces": "ChatWithCustomData_CSharp_Web" + "valueTransform": "aspireClassName_Formatting", + "replaces": "ChatWithCustomData_CSharp_Web_AspireClassName" }, "webProjectNamespaceAdjuster": { "type": "generated", @@ -530,23 +530,30 @@ } }, "forms": { - "projectNameTransform_Normalize": { + "aspireClassName_ReplaceInvalidChars_DigitDots": { "identifier": "replace", - "pattern": "[^a-zA-Z0-9_]", + "pattern": "((?<=\\.)|^)(?=\\d)", "replacement": "_", - "description": "Replace non-word chars, start before digit, after period with underscore" + "description": "Insert underscore before digits at start or after a dot" }, - "projectNameTransform_AppendWeb": { + "aspireClassName_ReplaceInvalidChars_NonWords": { + "identifier": "replace", + "pattern": "\\W", + "replacement": "_", + "description": "Replace non-word characters with underscore" + }, + "aspireClassName_AppendWeb": { "identifier": "replace", "pattern": "^(.*)$", "replacement": "$1_Web", "description": "Append _Web to the project name" }, - "projectNameTransform_Formatting": { + "aspireClassName_Formatting": { "identifier": "chain", "steps": [ - "projectNameTransform_Normalize", - "projectNameTransform_AppendWeb" + "aspireClassName_ReplaceInvalidChars_DigitDots", + "aspireClassName_ReplaceInvalidChars_NonWords", + "aspireClassName_AppendWeb" ], "description": "Normalize and add _Web suffix" }, diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.AppHost/Program.cs b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.AppHost/Program.cs index c3045240cda..fd50e8a8fd5 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.AppHost/Program.cs +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.AppHost/Program.cs @@ -51,7 +51,7 @@ #else // UseLocalVectorStore #endif -var webApp = builder.AddProject("aichatweb-app"); +var webApp = builder.AddProject("aichatweb-app"); #if (IsOllama) // AI SERVICE PROVIDER REFERENCES webApp .WithReference(chat) diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs index d3458e8a1d0..7d9b206774c 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs @@ -72,26 +72,41 @@ public async Task CreateRestoreAndBuild_AspireTemplate(params string[] args) } /// - /// Tests build for a project name containing a dot ('.'), which triggers the class name normalization bug described in issue #6811. - /// This runs for all option combinations with --aspire true. + /// Tests build for various project name formats, including dots and other + /// separators, to trigger the class name normalization bug described + /// in https://github.com/dotnet/extensions/issues/6811 + /// This runs for all provider combinations with --aspire true and different + /// project names to ensure the bug is caught in all scenarios. /// [Theory] - [MemberData(nameof(GetAspireTemplateOptions))] - public async Task CreateRestoreAndBuild_ProjectNameWithDot(params string[] args) + [InlineData("Ollama")] + [InlineData("OpenAI")] + [InlineData("AzureOpenAI")] + [InlineData("GitHubModels")] + public async Task CreateRestoreAndBuild_ProjectNameVariants(string provider) { - // Use a project name with a dot to trigger the bug. - const string ProjectNameWithDot = "dot.name"; - var project = await Fixture.CreateProjectAsync( - templateName: "aichatweb", - projectName: ProjectNameWithDot, - args); - - project.StartupProjectRelativePath = $"{ProjectNameWithDot}.AppHost"; + string[] projectNames = new[] + { + "dot.name", + "space name", + "mix.ed-dash_name 123", + ".1My.Projec-", + "1Project123", + "project.123" + }; + + foreach (var projectName in projectNames) + { + var project = await Fixture.CreateProjectAsync( + templateName: "aichatweb", + projectName: projectName, + args: new[] { "--aspire", $"--provider={provider}" }); - await Fixture.RestoreProjectAsync(project); + project.StartupProjectRelativePath = $"{projectName}.AppHost"; - // Just attempt to build; if the bug is present, this will fail with CS0234. - await Fixture.BuildProjectAsync(project); + await Fixture.RestoreProjectAsync(project); + await Fixture.BuildProjectAsync(project); + } } private static readonly (string name, string[] values)[] _templateOptions = [ From 9f68b2fca46a567e906a0195331ab00a57340ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viviana=20Due=C3=B1as=20Chavez?= Date: Sun, 21 Sep 2025 23:20:46 -0700 Subject: [PATCH 3/6] Address PR feedback: Test combine all available providers with aspire flag. More test cases coverage. --- .../AIChatWebExecutionTests.cs | 54 ++++++++++--------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs index 7d9b206774c..3ad5e48a43e 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs @@ -79,34 +79,18 @@ public async Task CreateRestoreAndBuild_AspireTemplate(params string[] args) /// project names to ensure the bug is caught in all scenarios. /// [Theory] - [InlineData("Ollama")] - [InlineData("OpenAI")] - [InlineData("AzureOpenAI")] - [InlineData("GitHubModels")] - public async Task CreateRestoreAndBuild_ProjectNameVariants(string provider) + [MemberData(nameof(GetProjectNameVariants))] + public async Task CreateRestoreAndBuild_ProjectNameVariants(string provider, string projectName) { - string[] projectNames = new[] - { - "dot.name", - "space name", - "mix.ed-dash_name 123", - ".1My.Projec-", - "1Project123", - "project.123" - }; - - foreach (var projectName in projectNames) - { - var project = await Fixture.CreateProjectAsync( - templateName: "aichatweb", - projectName: projectName, - args: new[] { "--aspire", $"--provider={provider}" }); + var project = await Fixture.CreateProjectAsync( + templateName: "aichatweb", + projectName: projectName, + args: new[] { "--aspire", $"--provider={provider}" }); - project.StartupProjectRelativePath = $"{projectName}.AppHost"; + project.StartupProjectRelativePath = $"{projectName}.AppHost"; - await Fixture.RestoreProjectAsync(project); - await Fixture.BuildProjectAsync(project); - } + await Fixture.RestoreProjectAsync(project); + await Fixture.BuildProjectAsync(project); } private static readonly (string name, string[] values)[] _templateOptions = [ @@ -196,4 +180,24 @@ private static IEnumerable GetAllPossibleOptions(ReadOnlyMemory<(strin } } } + + public static IEnumerable GetProjectNameVariants() + { + foreach (string provider in new[] { "ollama", "openai", "azureopenai", "githubmodels" }) + { + foreach (string projectName in new[] + { + "dot.name", + "space name", + "mix.ed-dash_name 123", + ".1My.Projec-", + "1Project123", + "project.123", + "only#" + }) + { + yield return new object[] { provider, projectName }; + } + } + } } From d9529c806346b50b1935bc973cacd6aa604df9c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viviana=20Due=C3=B1as=20Chavez?= Date: Mon, 22 Sep 2025 13:44:49 -0700 Subject: [PATCH 4/6] PR suggestions: Add more test cases --- .../AIChatWebExecutionTests.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs index 3ad5e48a43e..44976955975 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs @@ -188,12 +188,14 @@ public static IEnumerable GetProjectNameVariants() foreach (string projectName in new[] { "dot.name", + "project.123", "space name", "mix.ed-dash_name 123", ".1My.Projec-", "1Project123", - "project.123", - "only#" + "11double", + "1", + "nomatch" }) { yield return new object[] { provider, projectName }; From 724290b51135000112b759d4b2af882c42e6fc67 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Mon, 22 Sep 2025 17:53:29 -0700 Subject: [PATCH 5/6] Add an [EnvironmentVariableSkipCondition] attribute for conditional tests. Make the Aspire project name theory conditional. --- .../AIChatWebExecutionTests.cs | 45 +++++++--- ...osoft.Extensions.AI.Templates.Tests.csproj | 4 + ...vironmentVariableSkipConditionAttribute.cs | 86 +++++++++++++++++++ 3 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 test/TestUtilities/XUnit/EnvironmentVariableSkipConditionAttribute.cs diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs index 44976955975..84c83b0024c 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.TestUtilities; using Xunit; using Xunit.Abstractions; @@ -71,6 +72,16 @@ public async Task CreateRestoreAndBuild_AspireTemplate(params string[] args) await Fixture.BuildProjectAsync(project); } + /// + /// Runs a single test with --aspire true and a project name that will trigger the class + /// name normalization bug reported in https://github.com/dotnet/extensions/issues/6811. + /// + [Fact] + public async Task CreateRestoreAndBuild_AspireProjectName() + { + await CreateRestoreAndBuild_AspireProjectName_Variants("azureopenai", "mix.ed-dash_name 123"); + } + /// /// Tests build for various project name formats, including dots and other /// separators, to trigger the class name normalization bug described @@ -78,9 +89,15 @@ public async Task CreateRestoreAndBuild_AspireTemplate(params string[] args) /// This runs for all provider combinations with --aspire true and different /// project names to ensure the bug is caught in all scenarios. /// - [Theory] - [MemberData(nameof(GetProjectNameVariants))] - public async Task CreateRestoreAndBuild_ProjectNameVariants(string provider, string projectName) + /// + /// Because this test takes a long time to run, it is skipped by default. Set the + /// environment variable AI_TEMPLATES_TEST_PROJECT_NAMES to "true" or "1" + /// to enable it. + /// + [ConditionalTheory] + [EnvironmentVariableSkipCondition("AI_TEMPLATES_TEST_PROJECT_NAMES", "true", "1")] + [MemberData(nameof(GetAspireProjectNameVariants))] + public async Task CreateRestoreAndBuild_AspireProjectName_Variants(string provider, string projectName) { var project = await Fixture.CreateProjectAsync( templateName: "aichatweb", @@ -181,22 +198,22 @@ private static IEnumerable GetAllPossibleOptions(ReadOnlyMemory<(strin } } - public static IEnumerable GetProjectNameVariants() + public static IEnumerable GetAspireProjectNameVariants() { foreach (string provider in new[] { "ollama", "openai", "azureopenai", "githubmodels" }) { foreach (string projectName in new[] { - "dot.name", - "project.123", - "space name", - "mix.ed-dash_name 123", - ".1My.Projec-", - "1Project123", - "11double", - "1", - "nomatch" - }) + "mix.ed-dash_name 123", + "dot.name", + "project.123", + "space name", + ".1My.Projec-", + "1Project123", + "11double", + "1", + "nomatch" + }) { yield return new object[] { provider, projectName }; } diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj index d2fc26ea0ab..e4c52714b79 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj @@ -15,6 +15,10 @@ + + + + diff --git a/test/TestUtilities/XUnit/EnvironmentVariableSkipConditionAttribute.cs b/test/TestUtilities/XUnit/EnvironmentVariableSkipConditionAttribute.cs new file mode 100644 index 00000000000..bd495546641 --- /dev/null +++ b/test/TestUtilities/XUnit/EnvironmentVariableSkipConditionAttribute.cs @@ -0,0 +1,86 @@ +// 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.Linq; + +namespace Microsoft.TestUtilities; + +/// +/// Skips a test based on the value of an environment variable. +/// +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] +public class EnvironmentVariableSkipConditionAttribute : Attribute, ITestCondition +{ + private string? _currentValue; + + /// + /// Initializes a new instance of the class. + /// + /// Name of the environment variable. + /// Value(s) of the environment variable to match for the condition. + /// + /// By default, the test will be run if the value of the variable matches any of the supplied values. + /// Set to False to run the test only if the value does not match. + /// + public EnvironmentVariableSkipConditionAttribute(string variableName, params string[] values) + { + if (string.IsNullOrEmpty(variableName)) + { + throw new ArgumentException("Value cannot be null or empty.", nameof(variableName)); + } + + if (values == null || values.Length == 0) + { + throw new ArgumentException("You must supply at least one value to match.", nameof(values)); + } + + VariableName = variableName; + Values = values; + } + + /// + /// Gets or sets a value indicating whether the test should run if the value of the variable matches any + /// of the supplied values. If False, the test runs only if the value does not match any of the + /// supplied values. Default is True. + /// + public bool RunOnMatch { get; set; } = true; + + /// + /// Gets the name of the environment variable. + /// + public string VariableName { get; } + + /// + /// Gets the value(s) of the environment variable to match for the condition. + /// + public string[] Values { get; } + + /// + /// Gets a value indicating whether the condition is met for the configured environment variable and values. + /// + public bool IsMet + { + get + { + _currentValue ??= Environment.GetEnvironmentVariable(VariableName); + var hasMatched = Values.Any(value => string.Equals(value, _currentValue, StringComparison.OrdinalIgnoreCase)); + + return RunOnMatch ? hasMatched : !hasMatched; + } + } + + /// + /// Gets a value indicating the reason the test was skipped. + /// + public string SkipReason + { + get + { + var value = _currentValue ?? "(null)"; + + return $"Test skipped on environment variable with name '{VariableName}' and value '{value}' " + + $"for the '{nameof(RunOnMatch)}' value of '{RunOnMatch}'."; + } + } +} From d1864e722f3b8322b61d64894a0dff5ee5f0ec75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viviana=20Due=C3=B1as=20Chavez?= Date: Tue, 23 Sep 2025 16:50:51 -0700 Subject: [PATCH 6/6] Regex logic and _Web append into one step. Rename test skip for clarity. --- .../.template.config/template.json | 29 +++---------------- .../Program.cs | 2 +- .../AIChatWebExecutionTests.cs | 2 +- ... EnvironmentVariableConditionAttribute.cs} | 6 ++-- 4 files changed, 9 insertions(+), 30 deletions(-) rename test/TestUtilities/XUnit/{EnvironmentVariableSkipConditionAttribute.cs => EnvironmentVariableConditionAttribute.cs} (92%) diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/.template.config/template.json b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/.template.config/template.json index 101fead3e72..d95f7c41c13 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/.template.config/template.json +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/.template.config/template.json @@ -502,7 +502,7 @@ "aspireClassNameReplacer": { "type": "derived", "valueSource": "name", - "valueTransform": "aspireClassName_Formatting", + "valueTransform": "aspireClassName_ReplaceInvalidChars", "replaces": "ChatWithCustomData_CSharp_Web_AspireClassName" }, "webProjectNamespaceAdjuster": { @@ -530,32 +530,11 @@ } }, "forms": { - "aspireClassName_ReplaceInvalidChars_DigitDots": { + "aspireClassName_ReplaceInvalidChars": { "identifier": "replace", - "pattern": "((?<=\\.)|^)(?=\\d)", + "pattern": "(((?<=\\.)|^)(?=\\d)|\\W)", "replacement": "_", - "description": "Insert underscore before digits at start or after a dot" - }, - "aspireClassName_ReplaceInvalidChars_NonWords": { - "identifier": "replace", - "pattern": "\\W", - "replacement": "_", - "description": "Replace non-word characters with underscore" - }, - "aspireClassName_AppendWeb": { - "identifier": "replace", - "pattern": "^(.*)$", - "replacement": "$1_Web", - "description": "Append _Web to the project name" - }, - "aspireClassName_Formatting": { - "identifier": "chain", - "steps": [ - "aspireClassName_ReplaceInvalidChars_DigitDots", - "aspireClassName_ReplaceInvalidChars_NonWords", - "aspireClassName_AppendWeb" - ], - "description": "Normalize and add _Web suffix" + "description": "Insert underscore before digits at start, or after a dot, or to replace non-word characters" }, "vectorStoreIndexNameTransform": { "identifier": "chain", diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.AppHost/Program.cs b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.AppHost/Program.cs index fd50e8a8fd5..df2f6765d9e 100644 --- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.AppHost/Program.cs +++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData-CSharp.AppHost/Program.cs @@ -51,7 +51,7 @@ #else // UseLocalVectorStore #endif -var webApp = builder.AddProject("aichatweb-app"); +var webApp = builder.AddProject("aichatweb-app"); #if (IsOllama) // AI SERVICE PROVIDER REFERENCES webApp .WithReference(chat) diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs index 84c83b0024c..f5d2bc52e3a 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs @@ -95,7 +95,7 @@ public async Task CreateRestoreAndBuild_AspireProjectName() /// to enable it. /// [ConditionalTheory] - [EnvironmentVariableSkipCondition("AI_TEMPLATES_TEST_PROJECT_NAMES", "true", "1")] + [EnvironmentVariableCondition("AI_TEMPLATES_TEST_PROJECT_NAMES", "true", "1")] [MemberData(nameof(GetAspireProjectNameVariants))] public async Task CreateRestoreAndBuild_AspireProjectName_Variants(string provider, string projectName) { diff --git a/test/TestUtilities/XUnit/EnvironmentVariableSkipConditionAttribute.cs b/test/TestUtilities/XUnit/EnvironmentVariableConditionAttribute.cs similarity index 92% rename from test/TestUtilities/XUnit/EnvironmentVariableSkipConditionAttribute.cs rename to test/TestUtilities/XUnit/EnvironmentVariableConditionAttribute.cs index bd495546641..45a54409047 100644 --- a/test/TestUtilities/XUnit/EnvironmentVariableSkipConditionAttribute.cs +++ b/test/TestUtilities/XUnit/EnvironmentVariableConditionAttribute.cs @@ -10,12 +10,12 @@ namespace Microsoft.TestUtilities; /// Skips a test based on the value of an environment variable. /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] -public class EnvironmentVariableSkipConditionAttribute : Attribute, ITestCondition +public class EnvironmentVariableConditionAttribute : Attribute, ITestCondition { private string? _currentValue; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Name of the environment variable. /// Value(s) of the environment variable to match for the condition. @@ -23,7 +23,7 @@ public class EnvironmentVariableSkipConditionAttribute : Attribute, ITestConditi /// By default, the test will be run if the value of the variable matches any of the supplied values. /// Set to False to run the test only if the value does not match. /// - public EnvironmentVariableSkipConditionAttribute(string variableName, params string[] values) + public EnvironmentVariableConditionAttribute(string variableName, params string[] values) { if (string.IsNullOrEmpty(variableName)) {