diff --git a/.gitignore b/.gitignore index 54da99a7b6..2d7a978af9 100644 --- a/.gitignore +++ b/.gitignore @@ -388,6 +388,9 @@ experimental/generator-dotnet-yeoman/node_modules # Allow the packages directory under tests !/tests/unit/[Pp]ackages/* +# Allow the packages directory under tests +!/tests/unit/[Pp]ackages/* + # Ignore correct yarn files (no zero install support) .yarn/* !.yarn/patches diff --git a/build/templates/template-function-dotnet-bot-resources.json b/build/templates/template-function-dotnet-bot-resources.json new file mode 100644 index 0000000000..4d8ee5aeaf --- /dev/null +++ b/build/templates/template-function-dotnet-bot-resources.json @@ -0,0 +1,115 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botName": { + "type": "string" + }, + "botLocation": { + "type": "string" + }, + "appInsightsName": { + "type": "string", + "defaultValue": "" + }, + "appServicePlanName": { + "type": "string" + }, + "appServicePlanResourceGroup": { + "type": "string" + }, + "botSku": { + "type": "string", + "defaultValue": "F0", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + } + }, + "variables": { + "siteHost": "[concat(parameters('botName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('botName'))]" + }, + "resources": [ + { + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[parameters('botLocation')]", + "kind": "functionapp", + "name": "[parameters('botName')]", + "properties": { + "name": "[parameters('botName')]", + "kind": "functionapp", + "httpsOnly": true, + "alwaysOn": true, + "webSocketsEnabled": true + }, + "resources": [ + { + "name": "appsettings", + "type": "config", + "apiVersion": "2015-08-01", + "dependsOn": [ + "[concat('Microsoft.Web/Sites/', parameters('botName'))]" + ], + "properties": { + "FUNCTIONS_EXTENSION_VERSION": "~3", + "FUNCTIONS_WORKER_RUNTIME": "dotnet", + "APPINSIGHTS_INSTRUMENTATIONKEY": "[if(empty(parameters('appInsightsName')), '', reference(resourceId(parameters('appServicePlanResourceGroup'),'Microsoft.Insights/components', parameters('appInsightsName')), '2015-05-01', 'Full').properties.InstrumentationKey)]", + "MicrosoftAppId": "[parameters('appId')]", + "MicrosoftAppPassword": "[parameters('appSecret')]" + } + } + ] + }, + { + "type": "Microsoft.Web/sites/hostNameBindings", + "apiVersion": "2020-09-01", + "name": "[concat(parameters('botName'), '/', parameters('botName'), '.azurewebsites.net')]", + "location": "[parameters('botLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', parameters('botName'))]" + ], + "properties": { + "siteName": "[parameters('botName')]", + "hostNameType": "Verified" + } + }, + { + "type": "Microsoft.BotService/botServices", + "apiVersion": "2020-06-02", + "name": "[parameters('botName')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', parameters('botName'))]" + ], + "properties": { + "name": "[parameters('botName')]", + "displayName": "[parameters('botName')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": "", + "developerAppInsightKey": "", + "publishingCredentials": null, + "storageResourceId": null + } + } + ] +} \ No newline at end of file diff --git a/build/templates/template-function-js-bot-resources.json b/build/templates/template-function-js-bot-resources.json new file mode 100644 index 0000000000..03c4145641 --- /dev/null +++ b/build/templates/template-function-js-bot-resources.json @@ -0,0 +1,116 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botName": { + "type": "string" + }, + "botLocation": { + "type": "string" + }, + "appInsightsName": { + "type": "string", + "defaultValue": "" + }, + "appServicePlanName": { + "type": "string" + }, + "appServicePlanResourceGroup": { + "type": "string" + }, + "botSku": { + "type": "string", + "defaultValue": "F0", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + } + }, + "variables": { + "siteHost": "[concat(parameters('botName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('botName'))]" + }, + "resources": [ + { + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[parameters('botLocation')]", + "kind": "functionapp", + "name": "[parameters('botName')]", + "properties": { + "name": "[parameters('botName')]", + "kind": "functionapp", + "httpsOnly": true, + "alwaysOn": true, + "webSocketsEnabled": true + }, + "resources": [ + { + "name": "appsettings", + "type": "config", + "apiVersion": "2015-08-01", + "dependsOn": [ + "[concat('Microsoft.Web/Sites/', parameters('botName'))]" + ], + "properties": { + "FUNCTIONS_EXTENSION_VERSION": "~3", + "FUNCTIONS_WORKER_RUNTIME": "node", + "WEBSITE_NODE_DEFAULT_VERSION": "~14", + "APPINSIGHTS_INSTRUMENTATIONKEY": "[if(empty(parameters('appInsightsName')), '', reference(resourceId(parameters('appServicePlanResourceGroup'),'Microsoft.Insights/components', parameters('appInsightsName')), '2015-05-01', 'Full').properties.InstrumentationKey)]", + "MicrosoftAppId": "[parameters('appId')]", + "MicrosoftAppPassword": "[parameters('appSecret')]" + } + } + ] + }, + { + "type": "Microsoft.Web/sites/hostNameBindings", + "apiVersion": "2020-09-01", + "name": "[concat(parameters('botName'), '/', parameters('botName'), '.azurewebsites.net')]", + "location": "[parameters('botLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', parameters('botName'))]" + ], + "properties": { + "siteName": "[parameters('botName')]", + "hostNameType": "Verified" + } + }, + { + "type": "Microsoft.BotService/botServices", + "apiVersion": "2020-06-02", + "name": "[parameters('botName')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', parameters('botName'))]" + ], + "properties": { + "name": "[parameters('botName')]", + "displayName": "[parameters('botName')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": "", + "developerAppInsightKey": "", + "publishingCredentials": null, + "storageResourceId": null + } + } + ] +} \ No newline at end of file diff --git a/tests/functional/Tests/ComponentsFunctionalTests/Common/HostBot.cs b/tests/functional/Tests/ComponentsFunctionalTests/Common/HostBot.cs index 9d18a41068..8412fa936f 100644 --- a/tests/functional/Tests/ComponentsFunctionalTests/Common/HostBot.cs +++ b/tests/functional/Tests/ComponentsFunctionalTests/Common/HostBot.cs @@ -10,9 +10,19 @@ public enum HostBot /// EmptyBotDotNetWebApp, + /// + /// Empty bot implemented using DotNet and Functions. + /// + EmptyBotDotNetFunctions, + /// /// Empty bot implemented using JS and Web App. /// - EmptyBotJSWebApp + EmptyBotJSWebApp, + + /// + /// Empty bot implemented using JS and Functions. + /// + EmptyBotJSFunctions } } diff --git a/tests/functional/Tests/ComponentsFunctionalTests/EmptyBot/EmptyBotTests.cs b/tests/functional/Tests/ComponentsFunctionalTests/EmptyBot/EmptyBotTests.cs index 5eaeae0a2a..883a4dbdc3 100644 --- a/tests/functional/Tests/ComponentsFunctionalTests/EmptyBot/EmptyBotTests.cs +++ b/tests/functional/Tests/ComponentsFunctionalTests/EmptyBot/EmptyBotTests.cs @@ -37,7 +37,9 @@ public static IEnumerable TestCases() var hostBots = new List { HostBot.EmptyBotDotNetWebApp, - HostBot.EmptyBotJSWebApp + HostBot.EmptyBotDotNetFunctions, + HostBot.EmptyBotJSWebApp, + HostBot.EmptyBotJSFunctions }; var scripts = new List { "EmptyBot.json" }; diff --git a/tests/functional/Tests/ComponentsFunctionalTests/appsettings.json b/tests/functional/Tests/ComponentsFunctionalTests/appsettings.json index af1a042508..ff4b218749 100644 --- a/tests/functional/Tests/ComponentsFunctionalTests/appsettings.json +++ b/tests/functional/Tests/ComponentsFunctionalTests/appsettings.json @@ -13,9 +13,17 @@ "BotId": "bfcfnemptybotdotnetwebapp", "DirectLineSecret": "" }, + "EmptyBotDotNetFunctions": { + "BotId": "bfcfnemptybotdotnetfunctions", + "DirectLineSecret": "" + }, "EmptyBotJSWebApp": { "BotId": "bfcfnemptybotjswebapp", "DirectLineSecret": "" + }, + "EmptyBotJSFunctions": { + "BotId": "bfcfnemptybotjsfunctions", + "DirectLineSecret": "" } } } diff --git a/tests/functional/build/yaml/deployBotResources/dotnet/deploy.yml b/tests/functional/build/yaml/deployBotResources/dotnet/deploy.yml index d7084aa0ad..8f03969b23 100644 --- a/tests/functional/build/yaml/deployBotResources/dotnet/deploy.yml +++ b/tests/functional/build/yaml/deployBotResources/dotnet/deploy.yml @@ -154,30 +154,57 @@ stages: pathToPublish: "$(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ parameters.buildFolder }}/${{ bot.name }}/${{ bot.name }}.zip" artifactName: dotnet-$(BUILD.BUILDID) + - ${{ if eq(bot.project.integration, 'webapp') }}: # Create App Service and Bot Channel Registration - - template: ../common/createAppService.yml - parameters: - appId: $(APPID) - appInsight: "${{ parameters.appInsight }}" - appSecret: $(APPSECRET) - appServicePlan: "${{ parameters.appServicePlan }}" - appServicePlanRG: "${{ parameters.appServicePlanRG }}" - azureSubscription: "${{ parameters.azureSubscription }}" - botGroup: "${{ parameters.resourceGroup }}" - botName: "${{ bot.name }}" - botPricingTier: "${{ parameters.botPricingTier }}" - resourceSuffix: "${{ parameters.resourceSuffix }}" - templateFile: "build/templates/template-bot-resources.json" + - template: ../common/createAppService.yml + parameters: + appId: $(APPID) + appInsight: "${{ parameters.appInsight }}" + appSecret: $(APPSECRET) + appServicePlan: "${{ parameters.appServicePlan }}" + appServicePlanRG: "${{ parameters.appServicePlanRG }}" + azureSubscription: "${{ parameters.azureSubscription }}" + botGroup: "${{ parameters.resourceGroup }}" + botName: "${{ bot.name }}" + botPricingTier: "${{ parameters.botPricingTier }}" + resourceSuffix: "${{ parameters.resourceSuffix }}" + templateFile: "build/templates/template-bot-resources.json" - # Deploy bot - - task: AzureWebApp@1 - displayName: 'Deploy Azure Web App : ${{ bot.name }}-$(BUILD.BUILDID)' - inputs: - azureSubscription: "${{ parameters.azureSubscription }}" - appName: '${{ bot.name }}${{ parameters.resourceSuffix }}-$(BUILD.BUILDID)' - resourceGroupName: '${{ parameters.resourceGroup }}' - package: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ parameters.buildFolder }}/${{ bot.name }}/${{ bot.name }}.zip' - deploymentMethod: runFromPackage + # Deploy bot to Azure Web App + - task: AzureWebApp@1 + displayName: 'Deploy Azure Web App : ${{ bot.name }}-$(BUILD.BUILDID)' + inputs: + azureSubscription: "${{ parameters.azureSubscription }}" + appName: '${{ bot.name }}${{ parameters.resourceSuffix }}-$(BUILD.BUILDID)' + resourceGroupName: '${{ parameters.resourceGroup }}' + package: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ parameters.buildFolder }}/${{ bot.name }}/${{ bot.name }}.zip' + deploymentMethod: runFromPackage + + - ${{ if eq(bot.project.integration, 'functions') }}: + # Create Functions App and Bot Channel Registration + - template: ../common/createAppService.yml + parameters: + appId: $(APPID) + appInsight: "${{ parameters.appInsight }}" + appSecret: $(APPSECRET) + appServicePlan: "${{ parameters.appServicePlan }}" + appServicePlanRG: "${{ parameters.appServicePlanRG }}" + azureSubscription: "${{ parameters.azureSubscription }}" + botGroup: "${{ parameters.resourceGroup }}" + botName: "${{ bot.name }}" + botPricingTier: "${{ parameters.botPricingTier }}" + resourceSuffix: "${{ parameters.resourceSuffix }}" + templateFile: "build/templates/template-function-dotnet-bot-resources.json" + + # Deploy bot to Azure Functions + - task: AzureFunctionApp@1 + displayName: 'Deploy Functions App : ${{ bot.name }}-$(BUILD.BUILDID)' + inputs: + azureSubscription: "${{ parameters.azureSubscription }}" + appType: 'functionApp' + appName: '${{ bot.name }}${{ parameters.resourceSuffix }}-$(BUILD.BUILDID)' + package: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ parameters.buildFolder }}/${{ bot.name }}/${{ bot.name }}.zip' + deploymentMethod: runFromPackage # Configure OAuth - ${{ if eq(bot.type, 'Skill') }}: diff --git a/tests/functional/build/yaml/deployBotResources/js/deploy.yml b/tests/functional/build/yaml/deployBotResources/js/deploy.yml index a8994dc8b8..ff5ea602a7 100644 --- a/tests/functional/build/yaml/deployBotResources/js/deploy.yml +++ b/tests/functional/build/yaml/deployBotResources/js/deploy.yml @@ -172,30 +172,57 @@ stages: pathToPublish: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/build/${{ bot.name }}.zip' artifactName: javascript-$(BUILD.BUILDID) + - ${{ if eq(bot.project.integration, 'webapp') }}: # Create App Service and Bot Channel Registration - - template: ../common/createAppService.yml - parameters: - appId: $(APPID) - appInsight: "${{ parameters.appInsight }}" - appSecret: $(APPSECRET) - appServicePlan: "${{ parameters.appServicePlan }}" - appServicePlanRG: "${{ parameters.appServicePlanRG }}" - azureSubscription: "${{ parameters.azureSubscription }}" - botGroup: "${{ parameters.resourceGroup }}" - botName: "${{ bot.name }}" - botPricingTier: "${{ parameters.botPricingTier }}" - resourceSuffix: "${{ parameters.resourceSuffix }}" - templateFile: "build/templates/template-bot-resources.json" + - template: ../common/createAppService.yml + parameters: + appId: $(APPID) + appInsight: "${{ parameters.appInsight }}" + appSecret: $(APPSECRET) + appServicePlan: "${{ parameters.appServicePlan }}" + appServicePlanRG: "${{ parameters.appServicePlanRG }}" + azureSubscription: "${{ parameters.azureSubscription }}" + botGroup: "${{ parameters.resourceGroup }}" + botName: "${{ bot.name }}" + botPricingTier: "${{ parameters.botPricingTier }}" + resourceSuffix: "${{ parameters.resourceSuffix }}" + templateFile: "build/templates/template-bot-resources.json" - # Deploy bot - - task: AzureWebApp@1 - displayName: 'Deploy Azure Web App : ${{ bot.name }}-$(BUILD.BUILDID)' - inputs: - azureSubscription: "${{ parameters.azureSubscription }}" - appName: '${{ bot.name }}${{ parameters.resourceSuffix }}-$(BUILD.BUILDID)' - resourceGroupName: '${{ parameters.resourceGroup }}' - package: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/build/${{ bot.name }}.zip' - deploymentMethod: runFromPackage + # Deploy bot to Azure Web App + - task: AzureWebApp@1 + displayName: 'Deploy Azure Web App : ${{ bot.name }}-$(BUILD.BUILDID)' + inputs: + azureSubscription: "${{ parameters.azureSubscription }}" + appName: '${{ bot.name }}${{ parameters.resourceSuffix }}-$(BUILD.BUILDID)' + resourceGroupName: '${{ parameters.resourceGroup }}' + package: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/build/${{ bot.name }}.zip' + deploymentMethod: runFromPackage + + - ${{ if eq(bot.project.integration, 'functions') }}: + # Create Functions App and Bot Channel Registration + - template: ../common/createAppService.yml + parameters: + appId: $(APPID) + appInsight: "${{ parameters.appInsight }}" + appSecret: $(APPSECRET) + appServicePlan: "${{ parameters.appServicePlan }}" + appServicePlanRG: "${{ parameters.appServicePlanRG }}" + azureSubscription: "${{ parameters.azureSubscription }}" + botGroup: "${{ parameters.resourceGroup }}" + botName: "${{ bot.name }}" + botPricingTier: "${{ parameters.botPricingTier }}" + resourceSuffix: "${{ parameters.resourceSuffix }}" + templateFile: "build/templates/template-function-js-bot-resources.json" + + # Deploy bot to Azure Functions + - task: AzureFunctionApp@1 + displayName: 'Deploy Functions App : ${{ bot.name }}-$(BUILD.BUILDID)' + inputs: + azureSubscription: "${{ parameters.azureSubscription }}" + appType: 'functionApp' + appName: '${{ bot.name }}${{ parameters.resourceSuffix }}-$(BUILD.BUILDID)' + package: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/build/${{ bot.name }}.zip' + deploymentMethod: runFromPackage # Configure OAuth - ${{ if eq(bot.type, 'Skill') }}: diff --git a/tests/functional/build/yaml/testScenarios/configureConsumers.yml b/tests/functional/build/yaml/testScenarios/configureConsumers.yml index c6100be4fe..df53957890 100644 --- a/tests/functional/build/yaml/testScenarios/configureConsumers.yml +++ b/tests/functional/build/yaml/testScenarios/configureConsumers.yml @@ -4,7 +4,9 @@ parameters: type: object default: EmptyBotDotNetWebApp: "" + EmptyBotDotNetFunctions: "" EmptyBotJSWebApp: "" + EmptyBotJSFunctions: "" - name: azureSubscription displayName: Azure Service Connection @@ -401,12 +403,24 @@ steps: resourceGroup = $groups.DotNet configType = $types.Appsettings } + @{ + key = "EmptyBotDotNetFunctions" + botName = "bfcfnemptybotdotnetfunctions" + resourceGroup = $groups.DotNet + configType = $types.Appsettings + } @{ key = "EmptyBotJSWebApp" botName = "bfcfnemptybotjswebapp" resourceGroup = $groups.JS configType = $types.Appsettings } + @{ + key = "EmptyBotJSFunctions" + botName = "bfcfnemptybotjsfunctions" + resourceGroup = $groups.JS + configType = $types.Appsettings + } ) # Bots Test Scenarios @@ -415,7 +429,9 @@ steps: name = "EmptyBot"; consumers = @( "EmptyBotDotNetWebApp", - "EmptyBotJSWebApp" + "EmptyBotDotNetFunctions", + "EmptyBotJSWebApp", + "EmptyBotJSFunctions" ); } ) diff --git a/tests/functional/build/yaml/testScenarios/runTestScenarios.yml b/tests/functional/build/yaml/testScenarios/runTestScenarios.yml index 9f947008bd..c3fe9839e2 100644 --- a/tests/functional/build/yaml/testScenarios/runTestScenarios.yml +++ b/tests/functional/build/yaml/testScenarios/runTestScenarios.yml @@ -17,7 +17,9 @@ variables: ## Bots Configuration (Define these variables in Azure) # BfcfnEmptyBotDotNetWebAppId: (optional) App Id for BfcfnEmptyBotDotNetWebApp bot. - # BfcfnEmptyBotJSWebAppAppId: (optional) App Id for BfcfnEmptyBotJSWebApp bot. + # BfcfnEmptyBotDotNetFunctionsId: (optional) App Id for BfcfnEmptyBotDotNetFunctions bot. + # BfcfnEmptyBotJSWebAppId: (optional) App Id for BfcfnEmptyBotJSWebApp bot. + # BfcfnEmptyBotJSFunctionsId: (optional) App Id for BfcfnEmptyBotJSFunctions bot. # DeployBotResourcesGuid: (optional) Deploy Bot Resources pipeline GUID. @@ -64,7 +66,9 @@ stages: parameters: appIds: EmptyBotDotNetWebApp: "$(BFCFNEMPTYBOTDOTNETWEBAPPID)" + EmptyBotDotNetFunctions: "$(BFCFNEMPTYBOTDOTNETFUNCTIONSID)" EmptyBotJSWebApp: "$(BFCFNEMPTYBOTJSWEBAPPID)" + EmptyBotJSFunctions: "$(BFCFNEMPTYBOTJSFUNCTIONSID)" azureSubscription: "$(AZURESUBSCRIPTION)" buildConfiguration: "$(BUILDCONFIGURATION)" buildIdSuffix: $[stageDependencies.Download_Variables.Download_Variables.outputs['Set_Variables.DeploymentBuildSuffix']]