diff --git a/eng/build.proj b/eng/build.proj
index 2f95df76500..e59868907db 100644
--- a/eng/build.proj
+++ b/eng/build.proj
@@ -1,6 +1,7 @@
<_SnapshotsToExclude Include="$(MSBuildThisFileDirectory)..\test\**\Snapshots\**\*.*proj" />
+ <_GeneratedContentToExclude Include="$(MSBuildThisFileDirectory)..\test\**\TemplateSandbox\**\*.*proj" />
<_ProjectsToBuild Include="$(MSBuildThisFileDirectory)..\src\**\*.csproj" />
@@ -11,6 +12,6 @@
<_ProjectsToBuild Include="$(MSBuildThisFileDirectory)..\src\Packages\Microsoft.Internal.Extensions.DotNetApiDocs.Transport\Microsoft.Internal.Extensions.DotNetApiDocs.Transport.proj" />
-
+
-
\ No newline at end of file
+
diff --git a/eng/pipelines/templates/BuildAndTest.yml b/eng/pipelines/templates/BuildAndTest.yml
index 92717160bf4..bd008ead075 100644
--- a/eng/pipelines/templates/BuildAndTest.yml
+++ b/eng/pipelines/templates/BuildAndTest.yml
@@ -56,7 +56,25 @@ steps:
--settings $(Build.SourcesDirectory)/eng/CodeCoverage.config
--output ${{ parameters.repoTestResultsPath }}/$(Agent.JobName)_CodeCoverageResults/$(Agent.JobName)_cobertura.xml
"${{ parameters.buildScript }} -test -configuration ${{ parameters.buildConfig }} /bl:${{ parameters.repoLogPath }}/tests.binlog $(_OfficialBuildIdArgs)"
- displayName: Run tests
+ displayName: Run unit tests
+
+ - script: ${{ parameters.buildScript }}
+ -pack
+ -configuration ${{ parameters.buildConfig }}
+ -warnAsError 1
+ /bl:${{ parameters.repoLogPath }}/pack.binlog
+ /p:Restore=false /p:Build=false
+ $(_OfficialBuildIdArgs)
+ displayName: Pack
+
+ - ${{ if ne(parameters.skipTests, 'true') }}:
+ - script: ${{ parameters.buildScript }}
+ -integrationTest
+ -configuration ${{ parameters.buildConfig }}
+ -warnAsError 1
+ /bl:${{ parameters.repoLogPath }}/integration_tests.binlog
+ $(_OfficialBuildIdArgs)
+ displayName: Run integration tests
- pwsh: |
$SourcesDirectory = '$(Build.SourcesDirectory)';
@@ -151,12 +169,11 @@ steps:
displayName: Build Azure DevOps plugin
- script: ${{ parameters.buildScript }}
- -pack
-sign $(_SignArgs)
-publish $(_PublishArgs)
-configuration ${{ parameters.buildConfig }}
-warnAsError 1
- /bl:${{ parameters.repoLogPath }}/pack.binlog
+ /bl:${{ parameters.repoLogPath }}/publish.binlog
/p:Restore=false /p:Build=false
$(_OfficialBuildIdArgs)
- displayName: Pack, sign, and publish
+ displayName: Sign and publish
diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs
new file mode 100644
index 00000000000..1b8c4177f40
--- /dev/null
+++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs
@@ -0,0 +1,161 @@
+// 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 Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.Extensions.AI.Templates.Tests;
+
+///
+/// Contains execution tests for the "AI Chat Web" template.
+///
+///
+/// In addition to validating that the templates build and restore correctly,
+/// these tests are also responsible for template component governance reporting.
+/// This is because the generated output is left on disk after tests complete,
+/// most importantly the project.assets.json file that gets created during restore.
+/// Therefore, it's *critical* that these tests remain in a working state,
+/// as disabling them will also disable CG reporting.
+///
+public class AIChatWebExecutionTests : TemplateExecutionTestBase, ITemplateExecutionTestConfigurationProvider
+{
+ public AIChatWebExecutionTests(TemplateExecutionTestFixture fixture, ITestOutputHelper outputHelper)
+ : base(fixture, outputHelper)
+ {
+ }
+
+ public static TemplateExecutionTestConfiguration Configuration { get; } = new()
+ {
+ TemplatePackageName = "Microsoft.Extensions.AI.Templates",
+ TestOutputFolderPrefix = "AIChatWeb"
+ };
+
+ public static IEnumerable