From 04930c9ae076c4b705b30ead949bb9bf6385b164 Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Mon, 17 Nov 2025 11:09:48 -0800 Subject: [PATCH 1/8] Update settings for .vscode --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index b83cf1320..4b0690d83 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "azureFunctions.suppressProject": true + "azureFunctions.suppressProject": true, + "dotnet.defaultSolution": "Azure.Functions.Cli.sln" } \ No newline at end of file From a4530189c864e487194cb2ef72919cdbc5dc00e6 Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Mon, 17 Nov 2025 11:10:04 -0800 Subject: [PATCH 2/8] Add namespace to cancel handler unit test --- test/Cli/Func.UnitTests/CancelKeyHandlerTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/Cli/Func.UnitTests/CancelKeyHandlerTests.cs b/test/Cli/Func.UnitTests/CancelKeyHandlerTests.cs index a3a803176..4510d563f 100644 --- a/test/Cli/Func.UnitTests/CancelKeyHandlerTests.cs +++ b/test/Cli/Func.UnitTests/CancelKeyHandlerTests.cs @@ -1,10 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -using Azure.Functions.Cli; using Xunit; using static Azure.Functions.Cli.UnitTests.TestUtilities; +namespace Azure.Functions.Cli.UnitTests.Cli; + public class CancelKeyHandlerTests { [Fact] From 2059771254926e8219f10a105513948db40f10f2 Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Mon, 17 Nov 2025 11:31:32 -0800 Subject: [PATCH 3/8] Update namespace --- test/Cli/Func.UnitTests/CancelKeyHandlerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Cli/Func.UnitTests/CancelKeyHandlerTests.cs b/test/Cli/Func.UnitTests/CancelKeyHandlerTests.cs index 4510d563f..cc2d02127 100644 --- a/test/Cli/Func.UnitTests/CancelKeyHandlerTests.cs +++ b/test/Cli/Func.UnitTests/CancelKeyHandlerTests.cs @@ -4,7 +4,7 @@ using Xunit; using static Azure.Functions.Cli.UnitTests.TestUtilities; -namespace Azure.Functions.Cli.UnitTests.Cli; +namespace Azure.Functions.Cli.UnitTests; public class CancelKeyHandlerTests { From 26b358e0aa0053bfca650af7599c080f2115627f Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Mon, 17 Nov 2025 11:40:03 -0800 Subject: [PATCH 4/8] Update readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d9cd9c75..c6913a90b 100644 --- a/README.md +++ b/README.md @@ -77,13 +77,14 @@ func [--version] [--help] [] [--verbose] | v3 | `brew tap azure/functions`
`brew install azure-functions-core-tools@3` | | v2 | `brew tap azure/functions`
`brew install azure-functions-core-tools@2` | +If upgrading to a new version, you may have to run `brew update` to pull the latest formula +before you run the install command. Or, you can run `brew upgrade`. > [!NOTE] > Homebrew allows side-by-side installation of v2 and v3. You can switch versions with: > > `brew link --overwrite azure-functions-core-tools@3` - ### Linux Installation for Linux requires two steps: From 67ab24031e2dd4cf0dc6a30d4535969edda94ed1 Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Mon, 17 Nov 2025 11:40:44 -0800 Subject: [PATCH 5/8] Remove old test project --- test/Azure.Functions.Cli.Tests/.editorconfig | 10 - .../PackAction/PackValidationHelperTests.cs | 63 ---- .../ApplicationInsights.config | 71 ---- .../Azure.Functions.Cli.Tests.csproj | 52 --- .../BaseAzureResourceManager.cs | 96 ------ .../Commons/FunctionApp.cs | 16 - .../Commons/FunctionAppLocation.cs | 19 -- .../Commons/FunctionAppOs.cs | 38 --- .../Commons/FunctionAppRuntime.cs | 19 -- .../Commons/FunctionAppSku.cs | 104 ------ .../Commons/ListKeysResponse.cs | 11 - .../Commons/ServerFarmPropertiesObject.cs | 12 - .../Commons/ServerFarmSkuObject.cs | 8 - .../Commons/StorageAccountKey.cs | 13 - .../Commons/TestArgResponse.cs | 13 - .../FunctionAppManager.cs | 176 ---------- .../MultiOsResourcesManager.cs | 95 ------ .../ServerFarmManager.cs | 107 ------ .../StorageAccountManager.cs | 149 -------- .../E2E/BaseE2ETest.cs | 14 - .../E2E/DeploymentTests.cs | 110 ------ .../E2E/E2ETestConstants.cs | 12 - .../E2E/Helpers/CliTester.cs | 320 ------------------ .../E2E/Helpers/DirectoryResult.cs | 8 - .../E2E/Helpers/DurableHelper.cs | 37 -- .../E2E/Helpers/FileResult.cs | 13 - .../E2E/Helpers/LogWatcher.cs | 43 --- .../E2E/Helpers/ProcessHelper.cs | 168 --------- .../E2E/Helpers/QueueStorageHelper.cs | 88 ----- .../E2E/Helpers/RetryHelper.cs | 31 -- .../E2E/Helpers/RunConfiguration.cs | 27 -- .../E2E/Helpers/TestConditions.cs | 41 --- .../E2E/Helpers/TestTraits.cs | 35 -- .../KeyVaultReferencesManagerTests.cs | 106 ------ .../TestsToMigrate/ListFunctionsTests.cs | 72 ---- .../TestsToMigrate/PublishActionTests.cs | 208 ------------ test/Azure.Functions.Cli.Tests/app.config | 177 ---------- .../xunit.runner.json | 4 - 38 files changed, 2586 deletions(-) delete mode 100644 test/Azure.Functions.Cli.Tests/.editorconfig delete mode 100644 test/Azure.Functions.Cli.Tests/Actions/LocalActions/PackAction/PackValidationHelperTests.cs delete mode 100644 test/Azure.Functions.Cli.Tests/ApplicationInsights.config delete mode 100644 test/Azure.Functions.Cli.Tests/Azure.Functions.Cli.Tests.csproj delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/BaseAzureResourceManager.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/FunctionApp.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/FunctionAppLocation.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/FunctionAppOs.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/FunctionAppRuntime.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/FunctionAppSku.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/ListKeysResponse.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/ServerFarmPropertiesObject.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/ServerFarmSkuObject.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/StorageAccountKey.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/TestArgResponse.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/FunctionAppManager.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/MultiOsResourcesManager.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/ServerFarmManager.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/StorageAccountManager.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/BaseE2ETest.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/DeploymentTests.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/E2ETestConstants.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/Helpers/CliTester.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/Helpers/DirectoryResult.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/Helpers/DurableHelper.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/Helpers/FileResult.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/Helpers/LogWatcher.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/Helpers/ProcessHelper.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/Helpers/QueueStorageHelper.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/Helpers/RetryHelper.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/Helpers/RunConfiguration.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/Helpers/TestConditions.cs delete mode 100644 test/Azure.Functions.Cli.Tests/E2E/Helpers/TestTraits.cs delete mode 100644 test/Azure.Functions.Cli.Tests/TestsToMigrate/KeyVaultReferencesManagerTests.cs delete mode 100644 test/Azure.Functions.Cli.Tests/TestsToMigrate/ListFunctionsTests.cs delete mode 100644 test/Azure.Functions.Cli.Tests/TestsToMigrate/PublishActionTests.cs delete mode 100644 test/Azure.Functions.Cli.Tests/app.config delete mode 100644 test/Azure.Functions.Cli.Tests/xunit.runner.json diff --git a/test/Azure.Functions.Cli.Tests/.editorconfig b/test/Azure.Functions.Cli.Tests/.editorconfig deleted file mode 100644 index 70db2e4b4..000000000 --- a/test/Azure.Functions.Cli.Tests/.editorconfig +++ /dev/null @@ -1,10 +0,0 @@ -############################# -# Core EditorConfig Options # -############################# - -# top-most EditorConfig file -root = true - -# No rule enforcement as this project is getting deprecated -[*] -dotnet_analyzer_diagnostic.severity = none diff --git a/test/Azure.Functions.Cli.Tests/Actions/LocalActions/PackAction/PackValidationHelperTests.cs b/test/Azure.Functions.Cli.Tests/Actions/LocalActions/PackAction/PackValidationHelperTests.cs deleted file mode 100644 index 95a2ff929..000000000 --- a/test/Azure.Functions.Cli.Tests/Actions/LocalActions/PackAction/PackValidationHelperTests.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. - -using Azure.Functions.Cli.Actions.LocalActions.PackAction; -using Azure.Functions.Cli.Common; -using System.IO; -using Xunit; - -namespace Azure.Functions.Cli.Tests.E2E.PackAction -{ - public class PackValidationHelperTests : IDisposable - { - private readonly string _tempDirectory; - - public PackValidationHelperTests() - { - _tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - Directory.CreateDirectory(_tempDirectory); - } - - public void Dispose() - { - if (Directory.Exists(_tempDirectory)) - { - Directory.Delete(_tempDirectory, recursive: true); - } - } - - [Fact] - public void ValidateRequiredFiles_AllFilesExist_ReturnsTrue() - { - // Arrange - var requiredFiles = new[] { "host.json", "package.json" }; - foreach (var file in requiredFiles) - { - File.WriteAllText(Path.Combine(_tempDirectory, file), "{}"); - } - - // Act - var result = PackValidationHelper.ValidateRequiredFiles(_tempDirectory, requiredFiles, out string missingFile); - - // Assert - Assert.True(result); - Assert.Empty(missingFile); - } - - [Fact] - public void ValidateRequiredFiles_MissingFile_ReturnsFalse() - { - // Arrange - var requiredFiles = new[] { "host.json", "package.json" }; - File.WriteAllText(Path.Combine(_tempDirectory, "host.json"), "{}"); - // Don't create package.json - - // Act - var result = PackValidationHelper.ValidateRequiredFiles(_tempDirectory, requiredFiles, out string missingFile); - - // Assert - Assert.False(result); - Assert.Equal("package.json", missingFile); - } - } -} \ No newline at end of file diff --git a/test/Azure.Functions.Cli.Tests/ApplicationInsights.config b/test/Azure.Functions.Cli.Tests/ApplicationInsights.config deleted file mode 100644 index 83971dd8a..000000000 --- a/test/Azure.Functions.Cli.Tests/ApplicationInsights.config +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - core.windows.net - core.chinacloudapi.cn - core.cloudapi.de - core.usgovcloudapi.net - localhost - 127.0.0.1 - - - - - - - - - - - - - - - - - 5 - Event - - - 5 - Event - - - - \ No newline at end of file diff --git a/test/Azure.Functions.Cli.Tests/Azure.Functions.Cli.Tests.csproj b/test/Azure.Functions.Cli.Tests/Azure.Functions.Cli.Tests.csproj deleted file mode 100644 index 75efe8233..000000000 --- a/test/Azure.Functions.Cli.Tests/Azure.Functions.Cli.Tests.csproj +++ /dev/null @@ -1,52 +0,0 @@ - - - net8.0 - false - true - disable - - - - - - - - - - - - - - - - - - - - - - - - - - - - Always - - - - - - - - - - Always - - - - - - - - diff --git a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/BaseAzureResourceManager.cs b/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/BaseAzureResourceManager.cs deleted file mode 100644 index 78ab11a55..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/BaseAzureResourceManager.cs +++ /dev/null @@ -1,96 +0,0 @@ -using Azure.Functions.Cli.Common; -using System; - -namespace Azure.Functions.Cli.Tests.E2E.AzureResourceManagers -{ - public abstract class BaseAzureResourceManager: IDisposable - { - private static string _accessToken; - private static string _subscriptionId; - private static string _windowsResourceGroup; - private static string _linuxResourceGroup; - - public static string ManagementURL => Constants.DefaultManagementURL; - - protected static string AccessToken - { - get - { - if (_accessToken == null) - { - _accessToken = Environment.GetEnvironmentVariable(Constants.AzureManagementAccessToken); - if (string.IsNullOrEmpty(_accessToken)) - { - throw new Exception($"{Constants.AzureManagementAccessToken} is not defined in current environment"); - } - } - return _accessToken; - } - } - - protected static string SubscriptionId - { - get - { - if (_subscriptionId == null) - { - _subscriptionId = Environment.GetEnvironmentVariable(E2ETestConstants.TestSubscriptionId); - if (string.IsNullOrEmpty(_subscriptionId)) - { - throw new Exception($"{E2ETestConstants.TestSubscriptionId} is not defined in current environment"); - } - } - return _subscriptionId; - } - } - - protected static string WindowsResourceGroupName - { - get - { - if (_windowsResourceGroup == null) - { - _windowsResourceGroup = Environment.GetEnvironmentVariable(E2ETestConstants.TestResourceGroupNameWindows); - if (string.IsNullOrEmpty(_windowsResourceGroup)) - { - throw new Exception($"{E2ETestConstants.TestResourceGroupNameWindows} is not defined in current environment"); - } - } - return _windowsResourceGroup; - } - } - - protected static string LinuxResourceGroupName - { - get - { - if (_linuxResourceGroup == null) - { - _linuxResourceGroup = Environment.GetEnvironmentVariable(E2ETestConstants.TestResourceGroupNameLinux); - if (string.IsNullOrEmpty(_linuxResourceGroup)) - { - throw new Exception($"{E2ETestConstants.TestResourceGroupNameLinux} is not defined in current environment"); - } - } - return _linuxResourceGroup; - } - } - - public void Dispose() - { - try - { - CleanUp(); - } - catch - { - // ITestOutputHelper cannot be used by fixture - } - } - - protected virtual void CleanUp() - { - throw new NotImplementedException(); - } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/FunctionApp.cs b/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/FunctionApp.cs deleted file mode 100644 index a79545122..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/FunctionApp.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Dynamitey; -using System; -using System.Collections.Generic; -using System.Text; - -namespace Azure.Functions.Cli.Tests.E2E.AzureResourceManagers.Commons -{ - public class FunctionApp - { - public string Name { get; set; } - public FunctionAppOs Os { get; set; } - public FunctionAppSku Sku { get; set; } - public FunctionAppRuntime Runtime { get; set; } - public FunctionAppLocation Location { get; set; } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/FunctionAppLocation.cs b/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/FunctionAppLocation.cs deleted file mode 100644 index e08b4a19c..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/FunctionAppLocation.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Azure.Functions.Cli.Tests.E2E.AzureResourceManagers.Commons -{ - public enum FunctionAppLocation - { - WestUs, - WestUs2, - CentralUs, - EastUs, - EastAsia, - SoutheastAsia - } - - public static class FunctionAppLocationExtensions - { - public static string ToRegion(this FunctionAppLocation location) { - return location.ToString().ToLower(); - } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/FunctionAppOs.cs b/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/FunctionAppOs.cs deleted file mode 100644 index 949731897..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/FunctionAppOs.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Azure.Functions.Cli.Common; - -namespace Azure.Functions.Cli.Tests.E2E.AzureResourceManagers.Commons -{ - public enum FunctionAppOs - { - Windows, - Linux - } - - public static class FunctionAppOsExtensions - { - public static string GetFunctionAppKindLabel(this FunctionAppOs os) - { - switch (os) - { - case FunctionAppOs.Windows: - return "functionapp"; - case FunctionAppOs.Linux: - return "functionapp,linux"; - default: - return string.Empty; - } - } - public static string GetServerFarmKindLabel(this FunctionAppOs os) - { - switch (os) - { - case FunctionAppOs.Windows: - return "windows"; - case FunctionAppOs.Linux: - return "linux"; - default: - return string.Empty; - } - } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/FunctionAppRuntime.cs b/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/FunctionAppRuntime.cs deleted file mode 100644 index 2d5916e20..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/FunctionAppRuntime.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Azure.Functions.Cli.Tests.E2E.AzureResourceManagers.Commons -{ - public enum FunctionAppRuntime - { - DotNet, - PowerShell, - Java, - Node, - Python - } - - public static class FunctionAppRuntimeExtensions - { - public static string ToFunctionWorkerRuntime(this FunctionAppRuntime runtime) - { - return runtime.ToString().ToLower(); - } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/FunctionAppSku.cs b/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/FunctionAppSku.cs deleted file mode 100644 index 1eae08306..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/FunctionAppSku.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; - -namespace Azure.Functions.Cli.Tests.E2E.AzureResourceManagers.Commons -{ - public enum FunctionAppSku - { - Consumption, - Dedicated, - ElasticPremium - } - - public static class FunctionAppSkuExtensions - { - public static ServerFarmSkuObject GetServerFarmSku(this FunctionAppSku sku, FunctionAppOs os) - { - // Consumption - if (sku == FunctionAppSku.Consumption) - { - return new ServerFarmSkuObject - { - Tier = "Dynamic", - Name = "Y1" - }; - } - - // Dedicated - if (sku == FunctionAppSku.Dedicated && os == FunctionAppOs.Windows) - { - return new ServerFarmSkuObject - { - Tier = "Standard", - Name = "S1" - }; - } - else if (sku == FunctionAppSku.Dedicated && os == FunctionAppOs.Linux) - { - return new ServerFarmSkuObject - { - Tier = "PremiumV2", - Name = "P1v2" - }; - } - - // ElasticPremium - if (sku == FunctionAppSku.ElasticPremium) - { - return new ServerFarmSkuObject - { - Tier = "ElasticPremium", - Name = "EP1" - }; - } - - return null; - } - - public static ServerFarmPropertiesObject GetServerFarmProperties(this FunctionAppSku sku, FunctionAppOs os) - { - // Consumption - if (sku == FunctionAppSku.Consumption) - { - return new ServerFarmPropertiesObject - { - workerSize = 0, - workerSizeId = 0, - numberOfWorkers = 1, - maximumElasticWorkerCount = 0, - hostingEnvironment = string.Empty, - reserved = os == FunctionAppOs.Linux - }; - } - - // Dedicated - if (sku == FunctionAppSku.Dedicated) - { - return new ServerFarmPropertiesObject - { - workerSize = 0, - workerSizeId = 0, - numberOfWorkers = 1, - maximumElasticWorkerCount = 0, - hostingEnvironment = string.Empty, - reserved = os == FunctionAppOs.Linux - }; - } - - // ElasticPremium - if (sku == FunctionAppSku.ElasticPremium) - { - return new ServerFarmPropertiesObject - { - workerSize = 3, - workerSizeId = 3, - numberOfWorkers = 1, - maximumElasticWorkerCount = 20, - hostingEnvironment = string.Empty, - reserved = os == FunctionAppOs.Linux - }; - } - - return null; - } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/ListKeysResponse.cs b/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/ListKeysResponse.cs deleted file mode 100644 index ad9220417..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/ListKeysResponse.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Azure.Functions.Cli.Tests.E2E.AzureResourceManagers.Commons -{ - public class ListKeysResponse - { - public List keys { get; set; } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/ServerFarmPropertiesObject.cs b/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/ServerFarmPropertiesObject.cs deleted file mode 100644 index c42f5a64c..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/ServerFarmPropertiesObject.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Azure.Functions.Cli.Tests.E2E.AzureResourceManagers.Commons -{ - public class ServerFarmPropertiesObject - { - public int workerSize { get; set; } - public int workerSizeId { get; set; } - public int numberOfWorkers { get; set; } - public int maximumElasticWorkerCount { get; set; } - public bool reserved { get; set; } - public string hostingEnvironment { get; set; } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/ServerFarmSkuObject.cs b/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/ServerFarmSkuObject.cs deleted file mode 100644 index 32a4572f6..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/ServerFarmSkuObject.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Azure.Functions.Cli.Tests.E2E.AzureResourceManagers.Commons -{ - public class ServerFarmSkuObject - { - public string Tier { get; set; } - public string Name { get; set; } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/StorageAccountKey.cs b/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/StorageAccountKey.cs deleted file mode 100644 index b02639091..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/StorageAccountKey.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Azure.Functions.Cli.Tests.E2E.AzureResourceManagers.Commons -{ - public class StorageAccountKey - { - public string keyName { get; set; } - public string value { get; set; } - public string permissions { get; set; } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/TestArgResponse.cs b/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/TestArgResponse.cs deleted file mode 100644 index 92d04107b..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/Commons/TestArgResponse.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Newtonsoft.Json; - -namespace Azure.Functions.Cli.Tests.E2E.AzureResourceManagers.Commons -{ - class TestArgResponse - { - [JsonProperty(PropertyName = "count")] - public int Count { get; set; } - - [JsonProperty(PropertyName = "data")] - public object Data { get; set; } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/FunctionAppManager.cs b/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/FunctionAppManager.cs deleted file mode 100644 index a9e48dc4e..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/FunctionAppManager.cs +++ /dev/null @@ -1,176 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using Azure.Functions.Cli.Arm; -using Azure.Functions.Cli.Helpers; -using Azure.Functions.Cli.Tests.E2E.AzureResourceManagers.Commons; -using Newtonsoft.Json; - -namespace Azure.Functions.Cli.Tests.E2E.AzureResourceManagers -{ - public class FunctionAppManager : MultiOsResourcesManager - { - public async Task Get( - string name) - { - FunctionAppOs os = GetOsFromResourceLabel(name); - string resourceGroup = GetResourceGroupName(os); - Uri uri = new Uri($"{ManagementURL}subscriptions/{SubscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/sites/{name}?api-version=2018-11-01"); - return await ArmClient.HttpInvoke("GET", uri, AccessToken); - } - - public async Task Create( - string name, - string storageAccountName, - string storageAccountKey, - string serverFarmName, - FunctionAppLocation location = FunctionAppLocation.WestUs2, - FunctionAppSku sku = FunctionAppSku.Consumption, - FunctionAppOs os = FunctionAppOs.Windows, - FunctionAppRuntime runtime = FunctionAppRuntime.DotNet) - { - string resourceGroup = GetResourceGroupName(os); - Uri uri = new Uri($"{ManagementURL}subscriptions/{SubscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/sites/{name}?api-version=2018-11-01"); - - string storageAccountConnectionString = $"DefaultEndpointsProtocol=https;AccountName={storageAccountName};AccountKey={storageAccountKey};EndpointSuffix=core.windows.net"; - - List> appSettings = new List>(); - appSettings.Add(new Dictionary { { "name", "FUNCTIONS_WORKER_RUNTIME" }, { "value", runtime.ToFunctionWorkerRuntime() } }); - appSettings.Add(new Dictionary { { "name", "FUNCTIONS_EXTENSION_VERSION" }, { "value", "~2" } }); - appSettings.Add(new Dictionary { { "name", "AzureWebJobsStorage" }, { "value", storageAccountConnectionString } }); - - object payload = new - { - kind = os.GetFunctionAppKindLabel(), - location = location.ToRegion(), - properties = new - { - siteConfig = new - { - appSettings = appSettings - }, - serverFarmId = $"/subscriptions/{SubscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/serverfarms/{serverFarmName}", - hostingEnvironment = "", - clientAffinityEnable = false - } - }; - - var response = await ArmClient.HttpInvoke("PUT", uri, AccessToken, payload); - if (response.IsSuccessStatusCode) - { - AddToResources(name, os); - } - else - { - string statusCode = response.StatusCode.ToString(); - string message = await response.Content.ReadAsStringAsync(); - throw new Exception($"Failed to create function app {name}: ({statusCode}) {message}"); - } - } - - public async Task Delete( - string name) - { - FunctionAppOs os = GetOsFromResourceLabel(name); - string resourceGroup = GetResourceGroupName(os); - - Uri uri = new Uri($"{ManagementURL}/subscriptions/{SubscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/sites/{name}?api-version=2018-11-01"); - var response = await ArmClient.HttpInvoke("DELETE", uri, AccessToken); - if (response.IsSuccessStatusCode) - { - RemoveFromResources(name, os); - } - else - { - string statusCode = response.StatusCode.ToString(); - string message = await response.Content.ReadAsStringAsync(); - throw new Exception($"Failed to remove function app {name}: ({statusCode}) {message}"); - } - } - - public async Task CheckIfSiteExistsFromArg( - string name) - { - var url = new Uri($"{ManagementURL}/{ArmUriTemplates.ArgUri}?api-version={ArmUriTemplates.ArgApiVersion}"); - var payload = new - { - subscriptions = new[] { SubscriptionId }, - query = $"where type =~ \'Microsoft.Web/sites\' and name =~ \'{name}\'" - }; - - var response = await ArmClient.HttpInvoke(HttpMethod.Post, url, AccessToken, payload); - response.EnsureSuccessStatusCode(); - - var result = await response.Content.ReadAsStringAsync(); - var argResponse = JsonConvert.DeserializeObject(result); - return argResponse.Count > 0; - } - - public bool Contains( - string name) - { - return ContainsResource(name); - } - - public async Task WaitUntilCreated( - string name, - int numOfRetries = 5, - int retryIntervalSec = 10) - { - await RetryHelper.Retry(async () => - { - HttpResponseMessage response = await Get(name); - response.EnsureSuccessStatusCode(); - }, retryCount: numOfRetries, retryDelay: TimeSpan.FromSeconds(retryIntervalSec)); - } - - public async Task WaitUntilSiteAvailable( - string name, - int numOfRetries = 5, - int retryIntervalSec = 10) - { - await RetryHelper.Retry(async () => - { - bool exist = await CheckIfSiteExistsFromArg(name); - if (!exist) - { - throw new Exception($"Function app {name} does not exist in Azure Graph"); - } - }, retryCount: numOfRetries, retryDelay: TimeSpan.FromSeconds(retryIntervalSec)); - } - - public async Task WaitUntilScmSiteAvailable( - string name, - int numOfRetries = 5, - int retryIntervalSec = 10) - { - Uri uri = new Uri($"https://{name}.scm.azurewebsites.net"); - await RetryHelper.Retry(async () => - { - HttpResponseMessage response = await ArmClient.HttpInvoke("GET", uri, AccessToken); - response.EnsureSuccessStatusCode(); - }, retryCount: numOfRetries, retryDelay: TimeSpan.FromSeconds(retryIntervalSec)); - } - - public async Task WaitUntilDeleted( - string name, - int numOfRetries = 5, - int retryIntervalSec = 10) - { - await RetryHelper.Retry(async () => - { - await Delete(name); - }, retryCount: numOfRetries, retryDelay: TimeSpan.FromSeconds(retryIntervalSec)); - } - - protected override void CleanUp() - { - List deletedTasks = new List(); - deletedTasks.AddRange(WindowsResources.Select(sa => WaitUntilDeleted(sa))); - deletedTasks.AddRange(LinuxResources.Select(sa => WaitUntilDeleted(sa))); - Task.WhenAll(deletedTasks).Wait(); - } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/MultiOsResourcesManager.cs b/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/MultiOsResourcesManager.cs deleted file mode 100644 index e9fba17be..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/MultiOsResourcesManager.cs +++ /dev/null @@ -1,95 +0,0 @@ -using Azure.Functions.Cli.Tests.E2E.AzureResourceManagers.Commons; -using System; -using System.Collections.Generic; -using System.Text; - -namespace Azure.Functions.Cli.Tests.E2E.AzureResourceManagers -{ - public class MultiOsResourcesManager : BaseAzureResourceManager - { - private HashSet _windowsResources; - private HashSet _linuxResources; - - public MultiOsResourcesManager() - { - _windowsResources = new HashSet(); - _linuxResources = new HashSet(); - } - - protected IEnumerable WindowsResources - { - get - { - return _windowsResources; - } - } - - protected IEnumerable LinuxResources - { - get - { - return _linuxResources; - } - } - - - public string GetResourceGroupName(FunctionAppOs os) - { - switch (os) - { - case FunctionAppOs.Windows: - return WindowsResourceGroupName; - case FunctionAppOs.Linux: - return LinuxResourceGroupName; - default: - return string.Empty; - } - } - - protected FunctionAppOs GetOsFromResourceLabel(ResourceLabel resource) - { - if (_windowsResources.Contains(resource)) - { - return FunctionAppOs.Windows; - } - - if (_linuxResources.Contains(resource)) - { - return FunctionAppOs.Linux; - } - - throw new Exception($"Resource {resource.ToString()} does not exist when executing GetOsFromResourceLabel"); - } - - protected void AddToResources(ResourceLabel resource, FunctionAppOs os) - { - if (os == FunctionAppOs.Windows) - { - _windowsResources.Add(resource); - } - - if (os == FunctionAppOs.Linux) - { - _linuxResources.Add(resource); - } - } - - protected bool ContainsResource(ResourceLabel resource) - { - return _windowsResources.Contains(resource) || _linuxResources.Contains(resource); - } - - protected void RemoveFromResources(ResourceLabel resource, FunctionAppOs os) - { - if (os == FunctionAppOs.Windows) - { - _windowsResources.Remove(resource); - } - - if (os == FunctionAppOs.Linux) - { - _linuxResources.Remove(resource); - } - } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/ServerFarmManager.cs b/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/ServerFarmManager.cs deleted file mode 100644 index 1262223ad..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/ServerFarmManager.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using Azure.Functions.Cli.Arm; -using Azure.Functions.Cli.Helpers; -using Azure.Functions.Cli.Tests.E2E.AzureResourceManagers.Commons; - -namespace Azure.Functions.Cli.Tests.E2E.AzureResourceManagers -{ - public class ServerFarmManager : MultiOsResourcesManager - { - public async Task Get( - string name) - { - FunctionAppOs os = GetOsFromResourceLabel(name); - string resourceGroup = GetResourceGroupName(os); - Uri uri = new Uri($"{ManagementURL}subscriptions/{SubscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/serverfarms/{name}?api-version=2019-08-01"); - return await ArmClient.HttpInvoke("GET", uri, AccessToken); - } - - public async Task Create( - string name, - FunctionAppLocation location = FunctionAppLocation.WestUs2, - FunctionAppSku sku = FunctionAppSku.Consumption, - FunctionAppOs os = FunctionAppOs.Windows) - { - string resourceGroup = GetResourceGroupName(os); - Uri uri = new Uri($"{ManagementURL}subscriptions/{SubscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/serverfarms/{name}?api-version=2019-08-01"); - object payload = new - { - kind = os.GetServerFarmKindLabel(), - location = location.ToRegion(), - properties = sku.GetServerFarmProperties(os), - sku = sku.GetServerFarmSku(os) - }; - var response = await ArmClient.HttpInvoke("PUT", uri, AccessToken, payload); - if (response.IsSuccessStatusCode) - { - AddToResources(name, os); - } - else - { - string statusCode = response.StatusCode.ToString(); - string message = await response.Content.ReadAsStringAsync(); - throw new Exception($"Failed to create server farm {name}: ({statusCode}) {message}"); - } - } - - public async Task Delete( - string name) - { - FunctionAppOs os = GetOsFromResourceLabel(name); - string resourceGroup = GetResourceGroupName(os); - Uri uri = new Uri($"{ManagementURL}subscriptions/{SubscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/serverfarms/{name}?api-version=2019-08-01"); - - var response = await ArmClient.HttpInvoke("DELETE", uri, AccessToken); - if (response.IsSuccessStatusCode) - { - RemoveFromResources(name, os); - } - else - { - string statusCode = response.StatusCode.ToString(); - string message = await response.Content.ReadAsStringAsync(); - throw new Exception($"Failed to remove server farm {name}: ({statusCode}) {message}"); - } - } - public bool Contains( - string name) - { - return ContainsResource(name); - } - - public async Task WaitUntilCreated( - string name, - int numOfRetries = 5, - int retryIntervalSec = 10) - { - await RetryHelper.Retry(async () => - { - HttpResponseMessage response = await Get(name); - response.EnsureSuccessStatusCode(); - }, retryCount: numOfRetries, retryDelay: TimeSpan.FromSeconds(retryIntervalSec)); - } - - public async Task WaitUntilDeleted( - string name, - int numOfRetries = 5, - int retryIntervalSec = 10) - { - await RetryHelper.Retry(async () => - { - await Delete(name); - }, retryCount: numOfRetries, retryDelay: TimeSpan.FromSeconds(retryIntervalSec)); - } - - protected override void CleanUp() - { - List deletedTasks = new List(); - deletedTasks.AddRange(WindowsResources.Select(sa => WaitUntilDeleted(sa))); - deletedTasks.AddRange(LinuxResources.Select(sa => WaitUntilDeleted(sa))); - Task.WhenAll(deletedTasks).Wait(); - } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/StorageAccountManager.cs b/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/StorageAccountManager.cs deleted file mode 100644 index eec0ca8d7..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/AzureResourceManagers/StorageAccountManager.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; -using Azure.Functions.Cli.Arm; -using Azure.Functions.Cli.Helpers; -using Azure.Functions.Cli.Tests.E2E.AzureResourceManagers.Commons; -using Newtonsoft.Json; - -namespace Azure.Functions.Cli.Tests.E2E.AzureResourceManagers -{ - public class StorageAccountManager : MultiOsResourcesManager - { - private HashSet _storageAccounts; - - public StorageAccountManager() : base() - { - _storageAccounts = new HashSet(); - } - - public async Task Get( - string name) - { - FunctionAppOs os = GetOsFromResourceLabel(name); - string resourceGroup = GetResourceGroupName(os); - Uri uri = new Uri($"{ManagementURL}subscriptions/{SubscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Storage/storageAccounts/{name}?api-version=2019-06-01"); - return await ArmClient.HttpInvoke("GET", uri, AccessToken); - } - - public async Task ListKeys( - string name) - { - FunctionAppOs os = GetOsFromResourceLabel(name); - string resourceGroup = GetResourceGroupName(os); - Uri uri = new Uri($"{ManagementURL}subscriptions/{SubscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Storage/storageAccounts/{name}/listkeys?api-version=2019-06-01"); - var response = await ArmClient.HttpInvoke("POST", uri, AccessToken); - if (response.IsSuccessStatusCode) - { - string json = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(json); - } - else - { - string statusCode = response.StatusCode.ToString(); - string message = await response.Content.ReadAsStringAsync(); - throw new Exception($"Failed to list keys for storage account {name}: ({statusCode}) {message}"); - } - } - - public async Task Create( - string name, - FunctionAppLocation location = FunctionAppLocation.WestUs2, - FunctionAppOs os = FunctionAppOs.Windows) - { - string resourceGroup = GetResourceGroupName(os); - Uri uri = new Uri($"{ManagementURL}subscriptions/{SubscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Storage/storageAccounts/{name}?api-version=2019-06-01"); - object payload = new - { - kind = "StorageV2", - location = location.ToRegion(), - sku = new - { - name = "Standard_LRS" - } - }; - var response = await ArmClient.HttpInvoke("PUT", uri, AccessToken, payload); - if (response.IsSuccessStatusCode) - { - AddToResources(name, os); - } - else - { - string statusCode = response.StatusCode.ToString(); - string message = await response.Content.ReadAsStringAsync(); - throw new Exception($"Failed to create storage account {name}: ({statusCode}) {message}"); - } - } - - public async Task Delete( - string name) - { - FunctionAppOs os = GetOsFromResourceLabel(name); - string resourceGroup = GetResourceGroupName(os); - Uri uri = new Uri($"{ManagementURL}subscriptions/{SubscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Storage/storageAccounts/{name}?api-version=2019-06-01"); - var response = await ArmClient.HttpInvoke("DELETE", uri, AccessToken); - if (response.IsSuccessStatusCode) - { - RemoveFromResources(name, os); - } - else - { - string statusCode = response.StatusCode.ToString(); - string message = await response.Content.ReadAsStringAsync(); - throw new Exception($"Failed to remove storage account {name}: ({statusCode}) {message}"); - } - } - - public bool Contains( - string name) - { - return ContainsResource(name); - } - - public async Task WaitUntilCreated( - string name, - int numOfRetries = 5, - int retryIntervalSec = 10) - { - await RetryHelper.Retry(async () => - { - HttpResponseMessage response = await Get(name); - response.EnsureSuccessStatusCode(); - }, retryCount: numOfRetries, retryDelay: TimeSpan.FromSeconds(retryIntervalSec)); - } - - public async Task WaitUntilListKeys( - string name, - int numOfRetries = 5, - int retryIntervalSec = 10) - { - ListKeysResponse result = null; - await RetryHelper.Retry(async () => - { - result = await ListKeys(name); - }, retryCount: numOfRetries, retryDelay: TimeSpan.FromSeconds(retryIntervalSec)); - return result; - } - - public async Task WaitUntilDeleted( - string name, - int numOfRetries = 5, - int retryIntervalSec = 10) - { - await RetryHelper.Retry(async () => - { - await Delete(name); - }, retryCount: numOfRetries, retryDelay: TimeSpan.FromSeconds(retryIntervalSec)); - } - - protected override void CleanUp() - { - List deletedTasks = new List(); - deletedTasks.AddRange(WindowsResources.Select(sa => WaitUntilDeleted(sa))); - deletedTasks.AddRange(LinuxResources.Select(sa => WaitUntilDeleted(sa))); - Task.WhenAll(deletedTasks).Wait(); - } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/BaseE2ETest.cs b/test/Azure.Functions.Cli.Tests/E2E/BaseE2ETest.cs deleted file mode 100644 index 906530439..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/BaseE2ETest.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Xunit.Abstractions; - -namespace Azure.Functions.Cli.Tests.E2E -{ - public abstract class BaseE2ETest - { - protected readonly ITestOutputHelper _output; - - public BaseE2ETest(ITestOutputHelper output) - { - _output = output; - } - } -} \ No newline at end of file diff --git a/test/Azure.Functions.Cli.Tests/E2E/DeploymentTests.cs b/test/Azure.Functions.Cli.Tests/E2E/DeploymentTests.cs deleted file mode 100644 index 38c7199f9..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/DeploymentTests.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Azure.Functions.Cli.Helpers; -using Azure.Functions.Cli.Tests.E2E.AzureResourceManagers; -using Azure.Functions.Cli.Tests.E2E.AzureResourceManagers.Commons; -using Azure.Functions.Cli.Tests.E2E.Helpers; -using Xunit; -using Xunit.Abstractions; - -namespace Azure.Functions.Cli.Tests.E2E -{ - - public class DeploymentTests : BaseE2ETest, IClassFixture, IClassFixture, IClassFixture - { - private readonly StorageAccountManager _storageAccountManager; - private readonly ServerFarmManager _serverFarmManager; - private readonly FunctionAppManager _functionAppManager; - - private readonly static string id = DateTime.Now.ToString("HHmmss"); - private readonly static string linuxStorageAccountName = $"lcte2estorage{id}"; - private readonly static string linuxConsumptionServerFarm = $"lcte2ecserverfarm{id}"; - private readonly static string linuxConsumptionPythonFunctionApp = $"lcte2ecpython{id}"; - private bool ResroucesCreated = false; - - public DeploymentTests(ITestOutputHelper output, StorageAccountManager saManager, ServerFarmManager sfManager, FunctionAppManager faManager) : base(output) - { - _storageAccountManager = saManager; - _serverFarmManager = sfManager; - _functionAppManager = faManager; - - if (EnvironmentHelper.GetEnvironmentVariableAsBool(E2ETestConstants.IsPublicBuild) == true) - { - // no need to create the resources for public build. - return; - } - - if (!EnvironmentHelper.GetEnvironmentVariableAsBool(E2ETestConstants.EnableDeploymentTests)) - { - // no need to create the resources if deployment tests are not enabled. - return; - } - - try - { - if ((!_storageAccountManager.Contains(linuxStorageAccountName) || - !_serverFarmManager.Contains(linuxConsumptionServerFarm) || - !_functionAppManager.Contains(linuxConsumptionPythonFunctionApp)) && - !EnvironmentHelper.GetEnvironmentVariableAsBool(E2ETestConstants.CodeQLBuild)) - { - InitializeLinuxResources().Wait(); - } - ResroucesCreated = true; - } - catch (Exception ex) - { - _output.WriteLine($"Failed to initialize resources: {ex}"); - } - - } - - private async Task InitializeLinuxResources() - { - // Create storage account and server farm - await Task.WhenAll( - _storageAccountManager.Create(linuxStorageAccountName, os: FunctionAppOs.Linux), - _serverFarmManager.Create(linuxConsumptionServerFarm, os: FunctionAppOs.Linux) - ); - - // Check if creation has finished - await Task.WhenAll( - _storageAccountManager.WaitUntilCreated(linuxStorageAccountName), - _serverFarmManager.WaitUntilCreated(linuxConsumptionServerFarm)); - - // Acquire storage account key - ListKeysResponse listkeys = await _storageAccountManager.WaitUntilListKeys(linuxStorageAccountName); - string storageAccountKey = listkeys.keys.FirstOrDefault().value; - - // Create function app - await _functionAppManager.Create(linuxConsumptionPythonFunctionApp, linuxStorageAccountName, storageAccountKey, linuxConsumptionServerFarm, - os: FunctionAppOs.Linux, runtime: FunctionAppRuntime.Python); - await _functionAppManager.WaitUntilSiteAvailable(linuxConsumptionPythonFunctionApp); - await _functionAppManager.WaitUntilScmSiteAvailable(linuxConsumptionPythonFunctionApp); - } - - [SkippableFact] - public async void RemoteBuildPythonFunctionApp() - { - Skip.IfNot(ResroucesCreated, "Resources are not created"); - TestConditions.SkipIfPublicBuild(); - TestConditions.SkipIfCodeQLBuildOrEnableDeploymentTestsNotDefined(); - await CliTester.Run(new[] { - new RunConfiguration - { - Commands = new[] - { - "init . --worker-runtime python", - "new -l python -t HttpTrigger -n httptrigger", - $"azure functionapp publish {linuxConsumptionPythonFunctionApp} --build remote" - }, - OutputContains = new string[] - { - "Remote build succeeded!" - }, - CommandTimeout = TimeSpan.FromMinutes(5) - }, - }, _output); - } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/E2ETestConstants.cs b/test/Azure.Functions.Cli.Tests/E2E/E2ETestConstants.cs deleted file mode 100644 index 454cce912..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/E2ETestConstants.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Azure.Functions.Cli.Tests.E2E -{ - public static class E2ETestConstants - { - public const string TestSubscriptionId = "TEST_SUBSCRIPTION_ID"; - public const string TestResourceGroupNameWindows = "TEST_RESOURCE_GROUP_NAME_WINDOWS"; - public const string TestResourceGroupNameLinux = "TEST_RESOURCE_GROUP_NAME_LINUX"; - public const string EnableDeploymentTests = "ENABLE_DEPLOYMENT_TEST"; - public const string CodeQLBuild = "CODEQL_BUILD"; - public const string IsPublicBuild = "IsPublicBuild"; - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/Helpers/CliTester.cs b/test/Azure.Functions.Cli.Tests/E2E/Helpers/CliTester.cs deleted file mode 100644 index 78f1106d3..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/Helpers/CliTester.cs +++ /dev/null @@ -1,320 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; -using Azure.Functions.Cli.Common; -using FluentAssertions; -using Xunit.Abstractions; - -namespace Azure.Functions.Cli.Tests.E2E.Helpers -{ - public static class CliTester - { - private static readonly string _func; - - private const string StartHostCommand = "start --build"; - - static CliTester() - { - _func = Environment.GetEnvironmentVariable("FUNC_PATH"); - - if (_func == null) - { - // Fallback for local testing in Visual Studio, etc. - _func = Path.Combine(Environment.CurrentDirectory, "func"); - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - _func += ".exe"; - } - - if (!File.Exists(_func)) - { - throw new ApplicationException("Could not locate the 'func' executable to use for testing. Make sure the FUNC_PATH environment variable is set to the full path of the func executable."); - } - } - } - - public static Task Run(RunConfiguration runConfiguration, ITestOutputHelper output = null, string workingDir = null, bool startHost = false) => Run(new[] { runConfiguration }, output, workingDir, startHost); - - public static async Task Run(RunConfiguration[] runConfigurations, ITestOutputHelper output = null, string workingDir = null, bool startHost = false) - { - bool wasWorkingDirPassedIn = (workingDir != null); - string workingDirectory = workingDir ?? Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - - bool cleanupDirectory = string.IsNullOrEmpty(workingDir); - if (cleanupDirectory) - { - Directory.CreateDirectory(workingDirectory); - } - - string nugetConfigPath = Path.Combine(workingDirectory, "NuGet.Config"); - File.Copy(Path.Combine(Directory.GetCurrentDirectory(), "NuGet.Config"), nugetConfigPath); - - try - { - await InternalRun(workingDirectory, runConfigurations, output, startHost, wasWorkingDirPassedIn); - } - finally - { - try - { - if (cleanupDirectory) - { - Directory.Delete(workingDirectory, recursive: true); - } - else - { - File.Delete(nugetConfigPath); - } - } - catch { } - } - } - - private static async Task InternalRun(string workingDir, RunConfiguration[] runConfigurations, ITestOutputHelper output, bool startHost, bool wasWorkingDirPassedIn) - { - await using var hostExe = new Executable(_func, StartHostCommand, workingDirectory: workingDir); - var stdout = new StringBuilder(); - var stderr = new StringBuilder(); - - foreach (var runConfiguration in runConfigurations) - { - Task hostTask = startHost ? hostExe.RunAsync(logStd, logErr) : Task.Delay(runConfiguration.CommandTimeout); - stdout.Clear(); - stderr.Clear(); - var exitCode = 0; - var exitError = false; - runConfiguration.PreTest?.Invoke(workingDir); - - for (var i = 0; i < runConfiguration.Commands.Length; i++) - { - var command = runConfiguration.Commands[i]; - - await using var exe = command switch - { - string cmd when cmd.StartsWith("dotnet", StringComparison.OrdinalIgnoreCase) => - new Executable("dotnet", command.Substring(7), workingDirectory: workingDir), - - // default to func - _ => new Executable(_func, command, workingDirectory: workingDir) - }; - - if (startHost && i == runConfiguration.Commands.Length - 1) - { - // Give the host time to handle the first requests before executing the final command - logStd($"[{DateTime.Now}] Pausing to let the Functions host handle previous requests."); - await Task.Delay(TimeSpan.FromSeconds(20)); - logStd($"[{DateTime.Now}] Resuming commands."); - } - - logStd($"Running: > {exe.Command}"); - - if (runConfiguration.ExpectExit || (i + 1) < runConfiguration.Commands.Length) - { - exitCode = await exe.RunAsync(logStd, logErr, timeout: runConfiguration.CommandTimeout); - } - - if (!runConfiguration.ExpectExit && runConfiguration.Test is not null) - { - var exitCodeTask = exe.RunAsync(logStd, logErr); - - if (runConfiguration.WaitForRunningHostState) - { - await ProcessHelper.WaitForFunctionHostToStart(exe.Process, runConfiguration.HostProcessPort); - } - - try - { - await runConfiguration.Test.Invoke(workingDir, exe.Process, stdout); - } - catch (Exception e) - { - logErr($"Error while running test: {e.Message}"); - // Throw an error if working directory was passed in - // For some reason, tests don't fail with the working directory not being the default one so we need this specification - if(wasWorkingDirPassedIn) - { - exitError = true; - } - - } - finally - { - await Task.WhenAny(exitCodeTask, Task.Delay(runConfiguration.CommandTimeout)); - if (!exitCodeTask.IsCompleted) - { - exe.Process.Kill(); - logErr("Expected process to exit after calling Test() and within timeout, but it didn't."); - } - else - { - exitCode = exitCodeTask.Result; - } - } - } - - // -1 means we intentionally killed the process via p.Kill() - this is not an error - exitError |= exitCode != 0 && exitCode != -1; - } - - if (runConfiguration.ExpectExit && runConfiguration.Test != null) - { - await runConfiguration.Test.Invoke(workingDir, null, null); - } - - if (startHost) - { - if (hostExe.Process?.HasExited == false) - { - logStd($"[{DateTime.Now}] Terminating the Functions host."); - hostExe.Process.Kill(); - } - } - - AssertExitError(runConfiguration, exitError); - AssertFiles(runConfiguration, workingDir); - AssertDirectories(runConfiguration, workingDir); - AssertOutputContent(runConfiguration, stdout); - AssertErrorContent(runConfiguration, stderr); - } - - void logStd(string line) - { - try - { - stdout.AppendLine(line); - output?.WriteLine($"stdout: {line}"); - } - catch { } - } - - void logErr(string line) - { - try - { - stderr.AppendLine(line); - output?.WriteLine($"stderr: {line}"); - } - catch { } - } - } - - private static void AssertExitError(RunConfiguration runConfiguration, bool exitError) - { - if (runConfiguration.ExitInError) - { - exitError.Should().BeTrue(because: $"Commands {runConfiguration.CommandsStr} " + - "expected to have an error but they didn't."); - } - else - { - exitError.Should().BeFalse(because: $"Commands {runConfiguration.CommandsStr} " + - "expected to not fail but failed."); - } - } - - private static void AssertHasStandardError(RunConfiguration runConfiguration, StringBuilder stderr) - { - var error = stderr.ToString().Trim(' ', '\n'); - if (runConfiguration.HasStandardError || - runConfiguration.ErrorContains.Any() || - runConfiguration.ErrorDoesntContain.Any()) - { - error.Should().NotBeNullOrEmpty(because: "The run expects stderr"); - } - else - { - error.Should().BeNullOrEmpty(because: "The run doesn't expect stderr"); - } - } - - private static void AssertFiles(RunConfiguration runConfiguration, string path) - { - foreach (var fileResult in runConfiguration.CheckFiles) - { - var filePath = Path.Combine(path, fileResult.Name); - if (!fileResult.Exists) - { - File.Exists(filePath) - .Should().BeFalse(because: $"File {fileResult.Name} should not exist."); - } - else - { - File.Exists(filePath) - .Should().BeTrue(because: $"File {filePath} should exist"); - - var fileContent = File.ReadAllText(filePath); - if (!string.IsNullOrEmpty(fileResult.ContentIs)) - { - fileContent - .Should() - .Be(fileResult.ContentIs, because: $"File ({fileResult.Name}) content should match ContentIs"); - } - - if (fileResult.ContentContains.Any()) - { - fileContent - .Should() - .ContainAll(fileResult.ContentContains, because: $"File ({fileResult.Name}) content should contain all in ContentContains"); - } - - if (fileResult.ContentNotContains.Any()) - { - fileContent - .Should() - .NotContainAny(fileResult.ContentNotContains, because: $"File ({fileResult.Name}) contains text from the noContain list"); - } - } - } - } - - private static void AssertDirectories(RunConfiguration runConfiguration, string path) - { - foreach (var directoryResult in runConfiguration.CheckDirectories) - { - var dirPath = Path.Combine(path, directoryResult.Name); - if (!directoryResult.Exists) - { - Directory.Exists(dirPath) - .Should().BeFalse(because: $"Directory {directoryResult.Name} should not exist."); - } - else - { - Directory.Exists(dirPath) - .Should().BeTrue(because: $"Directory {dirPath} should exist"); - } - } - } - - private static void AssertOutputContent(RunConfiguration runConfiguration, StringBuilder stdout) - { - var output = stdout.ToString().Trim(); - if (runConfiguration.OutputContains.Any()) - { - output.Should().ContainAll(runConfiguration.OutputContains); - } - - if (runConfiguration.OutputDoesntContain.Any()) - { - output.Should().NotContainAny(runConfiguration.OutputDoesntContain); - } - } - - private static void AssertErrorContent(RunConfiguration runConfiguration, StringBuilder stderr) - { - var error = stderr.ToString().Trim(); - if (runConfiguration.ErrorContains.Any()) - { - error.Should().ContainAll(runConfiguration.ErrorContains); - } - - if (runConfiguration.ErrorDoesntContain.Any()) - { - error.Should().NotContainAny(runConfiguration.ErrorDoesntContain); - } - } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/Helpers/DirectoryResult.cs b/test/Azure.Functions.Cli.Tests/E2E/Helpers/DirectoryResult.cs deleted file mode 100644 index 0450af080..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/Helpers/DirectoryResult.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Azure.Functions.Cli.Tests.E2E.Helpers -{ - public class DirectoryResult - { - public string Name { get; internal set; } - public bool Exists { get; set; } = true; - } -} \ No newline at end of file diff --git a/test/Azure.Functions.Cli.Tests/E2E/Helpers/DurableHelper.cs b/test/Azure.Functions.Cli.Tests/E2E/Helpers/DurableHelper.cs deleted file mode 100644 index fd409b877..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/Helpers/DurableHelper.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.IO; -using Microsoft.Azure.WebJobs.Script; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Azure.Functions.Cli.Tests.E2E.Helpers -{ - public static class DurableHelper - { - private static readonly string StorageConnectionString = Environment.GetEnvironmentVariable("DURABLE_STORAGE_CONNECTION"); - - public static void SetTaskHubName(string workingDirectoryPath, string taskHubName) - { - string hostJsonPath = Path.Combine(workingDirectoryPath, ScriptConstants.HostMetadataFileName); - if (File.Exists(hostJsonPath)) - { - // Attempt to retrieve Durable override settings from host.json - dynamic hostSettings = JObject.Parse(File.ReadAllText(hostJsonPath)); - - string version = hostSettings["version"]; - if (version?.Equals("2.0") == true) - { - // If the version is (explicitly) 2.0, prepend path to 'durableTask' with 'extensions' - hostSettings["extensions"]["durableTask"]["hubName"] = taskHubName; - } - else - { - hostSettings["durableTask"]["HubName"] = taskHubName; - } - - string output = JsonConvert.SerializeObject(hostSettings, Formatting.Indented); - File.WriteAllText(hostJsonPath, output); - } - } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/Helpers/FileResult.cs b/test/Azure.Functions.Cli.Tests/E2E/Helpers/FileResult.cs deleted file mode 100644 index 37cb72ee9..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/Helpers/FileResult.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Azure.Functions.Cli.Tests.E2E.Helpers -{ - public class FileResult - { - public string Name { get; set; } - public string[] ContentContains { get; set; } = Array.Empty(); - public string[] ContentNotContains { get; set; } = Array.Empty(); - public string ContentIs { get; set; } - public bool Exists { get; set; } = true; - } -} \ No newline at end of file diff --git a/test/Azure.Functions.Cli.Tests/E2E/Helpers/LogWatcher.cs b/test/Azure.Functions.Cli.Tests/E2E/Helpers/LogWatcher.cs deleted file mode 100644 index 73d6da787..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/Helpers/LogWatcher.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Abstractions; - -namespace Azure.Functions.Cli.Tests.E2E.Helpers -{ - public static class LogWatcher - { - public static async Task WaitForLogOutput(StringBuilder stdout, string expectedOutput, TimeSpan timeout) - { - var tcs = new TaskCompletionSource(); - using var cancellationTokenSource = new CancellationTokenSource(timeout); - - if (stdout.ToString().Contains(expectedOutput)) - { - tcs.SetResult(true); - } - else - { - var timer = new Timer(state => - { - if (stdout.ToString().Contains(expectedOutput)) - { - tcs.TrySetResult(true); - } - }, null, TimeSpan.Zero, TimeSpan.FromSeconds(1)); - - // Cancel the waiting task if the timeout is reached - cancellationTokenSource.Token.Register(() => - { - tcs.TrySetCanceled(); - timer.Dispose(); - }); - } - - await tcs.Task; - } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/Helpers/ProcessHelper.cs b/test/Azure.Functions.Cli.Tests/E2E/Helpers/ProcessHelper.cs deleted file mode 100644 index 50a465842..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/Helpers/ProcessHelper.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System; -using System.Diagnostics; -using System.Net; -using System.Net.Http; -using System.Net.Sockets; -using System.Runtime.InteropServices; -using System.Text.Json; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace Azure.Functions.Cli.Tests.E2E.Helpers -{ - internal class ProcessHelper - { - private const string CommandExe = "cmd"; - private static readonly Regex pidRegex = new Regex(@"LISTENING\s+(\d+)\s*$"); - private static string FunctionsHostUrl = "http://localhost"; - - public static async Task WaitForFunctionHostToStart(Process funcProcess, int port) - { - var url = $"{FunctionsHostUrl}:{port.ToString()}"; - using var httpClient = new HttpClient(); - - await RetryHelper.RetryAsync(async () => - { - try - { - var response = await httpClient.GetAsync($"{url}/admin/host/status"); - var content = await response.Content.ReadAsStringAsync(); - var doc = JsonDocument.Parse(content); - - if (doc.RootElement.TryGetProperty("state", out JsonElement value) && value.GetString() == "Running") - { - return true; - } - - return false; - } - catch - { - return false; - } - }); - } - - public static void TryKillProcessForPort(int port) - { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // Can only use these commands on a windows device - return; - } - - string searchCommand = string.Format("/c netstat -anop tcp|findstr \":{0}.*LISTENING\"", port); - string searchResult = ExecuteCommand(searchCommand).Trim(); - - if (string.IsNullOrEmpty(searchResult)) - { - // No process running on given port - return; - } - - Match match = pidRegex.Match(searchResult); - - if (!match.Success) - { - Console.WriteLine($"Unable to parse the PID for the process running on port '{port}'"); - return; - } - - string pid = match.Groups[1].Value; - string killCommand = $"/c taskkill /PID {pid} /F"; - string killResult = ExecuteCommand(killCommand); - - if (killResult == null) - { - Console.WriteLine($"Unable to kill the process (PID: {pid}) running on port '{port}'"); - } - } - - public static int GetAvailablePort() - { - TcpListener listener = new TcpListener(IPAddress.Loopback, 0); - try - { - listener.Start(); - int port = ((IPEndPoint)listener.LocalEndpoint).Port; - return port; - } - finally - { - listener.Stop(); - listener.Server.Dispose(); - } - } - - public static void RunProcess(string fileName, string arguments, string workingDirectory, Action writeOutput = null, Action writeError = null) - { - TimeSpan procTimeout = TimeSpan.FromMinutes(3); - - ProcessStartInfo startInfo = new() - { - CreateNoWindow = true, - UseShellExecute = false, - WorkingDirectory = workingDirectory, - RedirectStandardOutput = true, - RedirectStandardError = true, - FileName = fileName - }; - - if (!string.IsNullOrEmpty(arguments)) - { - startInfo.Arguments = arguments; - } - - using Process testProcess = new() - { - StartInfo = startInfo, - }; - - testProcess.OutputDataReceived += (sender, e) => - { - if (e.Data != null) - { - writeOutput?.Invoke(e.Data); - } - }; - - testProcess.ErrorDataReceived += (sender, e) => - { - if (e.Data != null) - { - writeError?.Invoke(e.Data); - } - }; - - testProcess.Start(); - testProcess.BeginOutputReadLine(); - testProcess.BeginErrorReadLine(); - - bool completed = false; - try - { - completed = testProcess.WaitForExit((int)procTimeout.TotalMilliseconds); - } - catch (Exception ex) - { - Console.WriteLine($"Process '{fileName} {arguments}' in working directory '{workingDirectory}' threw exception '{ex}'."); - } - - if (!completed) - { - throw new TimeoutException($"Process '{fileName} {arguments}' in working directory '{workingDirectory}' did not complete in {procTimeout}."); - } - - testProcess.WaitForExit(); - } - - private static string ExecuteCommand(string command) - { - string output = string.Empty; - - RunProcess(CommandExe, command, null, writeOutput: o => output += o + Environment.NewLine); - - return output; - } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/Helpers/QueueStorageHelper.cs b/test/Azure.Functions.Cli.Tests/E2E/Helpers/QueueStorageHelper.cs deleted file mode 100644 index aab48df30..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/Helpers/QueueStorageHelper.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using Azure.Storage.Queues; -using Azure.Storage.Queues.Models; - -namespace Azure.Functions.Cli.Tests.E2E.Helpers -{ - internal static class QueueStorageHelper - { - public const string StorageEmulatorConnectionString = "UseDevelopmentStorage=true"; - - private static QueueClient CreateQueueClient(string queueName) - { - var options = new QueueClientOptions - { - MessageEncoding = QueueMessageEncoding.Base64 - }; - - return new QueueClient(StorageEmulatorConnectionString, queueName, options); - } - - public async static Task DeleteQueue(string queueName) - { - var queueClient = CreateQueueClient(queueName); - await queueClient.DeleteIfExistsAsync(); - } - - public async static Task ClearQueue(string queueName) - { - var queueClient = CreateQueueClient(queueName); - if (await queueClient.ExistsAsync()) - { - await queueClient.ClearMessagesAsync(); - } - } - - public async static Task CreateQueue(string queueName) - { - var queueClient = CreateQueueClient(queueName); - await queueClient.CreateIfNotExistsAsync(); - } - - public async static Task InsertIntoQueue(string queueName, string queueMessage) - { - var messageBytes = Encoding.UTF8.GetBytes(queueMessage); - string base64 = Convert.ToBase64String(messageBytes); - - var queueClient = CreateQueueClient(queueName); - await queueClient.CreateIfNotExistsAsync(); - Response response = await queueClient.SendMessageAsync(queueMessage); - return response.Value.MessageId; - } - - public async static Task ReadFromQueue(string queueName) - { - var queueClient = CreateQueueClient(queueName); - QueueMessage retrievedMessage = null; - await RetryHelper.RetryAsync(async () => - { - Response response = await queueClient.ReceiveMessageAsync(); - retrievedMessage = response.Value; - return retrievedMessage != null; - }); - await queueClient.DeleteMessageAsync(retrievedMessage.MessageId, retrievedMessage.PopReceipt); - return retrievedMessage.Body.ToString(); - } - - public async static Task> ReadMessagesFromQueue(string queueName) - { - var queueClient = CreateQueueClient(queueName); - QueueMessage[] retrievedMessages = null; - List messages = new List(); - await RetryHelper.RetryAsync(async () => - { - retrievedMessages = await queueClient.ReceiveMessagesAsync(maxMessages: 3); - return retrievedMessages != null; - }); - foreach (QueueMessage msg in retrievedMessages) - { - messages.Add(msg.Body.ToString()); - await queueClient.DeleteMessageAsync(msg.MessageId, msg.PopReceipt); - } - return messages; - } - } -} \ No newline at end of file diff --git a/test/Azure.Functions.Cli.Tests/E2E/Helpers/RetryHelper.cs b/test/Azure.Functions.Cli.Tests/E2E/Helpers/RetryHelper.cs deleted file mode 100644 index f7ba91e28..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/Helpers/RetryHelper.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Threading.Tasks; -using Microsoft.Extensions.Configuration; - -namespace Azure.Functions.Cli.Tests.E2E.Helpers -{ - public static class RetryHelper - { - public static async Task RetryAsync(Func> condition, int timeout = 60 * 1000, int pollingInterval = 2 * 1000, bool throwWhenDebugging = false, Func userMessageCallback = null) - { - DateTime start = DateTime.Now; - while (!await condition()) - { - await Task.Delay(pollingInterval); - - bool shouldThrow = !Debugger.IsAttached || (Debugger.IsAttached && throwWhenDebugging); - if (shouldThrow && (DateTime.Now - start).TotalMilliseconds > timeout) - { - string error = "Condition not reached within timeout."; - if (userMessageCallback != null) - { - error += " " + userMessageCallback(); - } - throw new ApplicationException(error); - } - } - } - } -} diff --git a/test/Azure.Functions.Cli.Tests/E2E/Helpers/RunConfiguration.cs b/test/Azure.Functions.Cli.Tests/E2E/Helpers/RunConfiguration.cs deleted file mode 100644 index ea8c32b3e..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/Helpers/RunConfiguration.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Diagnostics; -using System.Text; -using System.Threading.Tasks; - -namespace Azure.Functions.Cli.Tests.E2E.Helpers -{ - public class RunConfiguration - { - public string[] Commands { get; set; } = Array.Empty(); - public bool ExpectExit { get; set; } = true; - public bool ExitInError { get; set; } = false; - public bool HasStandardError { get; set; } = false; - public FileResult[] CheckFiles { get; set; } = Array.Empty(); - public DirectoryResult[] CheckDirectories { get; set; } = Array.Empty(); - public string[] OutputContains { get; set; } = Array.Empty(); - public string[] ErrorContains { get; set; } = Array.Empty(); - public string[] OutputDoesntContain { get; set; } = Array.Empty(); - public string[] ErrorDoesntContain { get; set; } = Array.Empty(); - public Action PreTest { get; set; } - public Func Test { get; set; } - public TimeSpan CommandTimeout { get; set; } = TimeSpan.FromSeconds(40); - public string CommandsStr => $"{string.Join(", ", Commands)}"; - public bool WaitForRunningHostState { get; set; } = false; - public int HostProcessPort { get; set; } = 7071; - } -} \ No newline at end of file diff --git a/test/Azure.Functions.Cli.Tests/E2E/Helpers/TestConditions.cs b/test/Azure.Functions.Cli.Tests/E2E/Helpers/TestConditions.cs deleted file mode 100644 index 7182d41e6..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/Helpers/TestConditions.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using Xunit; -using Azure.Functions.Cli.Common; -using Azure.Functions.Cli.Helpers; - -namespace Azure.Functions.Cli.Tests.E2E.Helpers -{ - public static class TestConditions - { - public static void SkipIfAzureServicePrincipalNotDefined() - { - var directoryId = Environment.GetEnvironmentVariable("AZURE_DIRECTORY_ID"); - var appId = Environment.GetEnvironmentVariable("AZURE_SERVICE_PRINCIPAL_ID"); - var key = Environment.GetEnvironmentVariable("AZURE_SERVICE_PRINCIPAL_KEY"); - - Skip.If(string.IsNullOrEmpty(directoryId) || - string.IsNullOrEmpty(appId) || - string.IsNullOrEmpty(key), - reason: "One or more of AZURE_DIRECTORY_ID, AZURE_SERVICE_PRINCIPAL_ID, AZURE_SERVICE_PRINCIPAL_KEY is not defined"); - } - - public static void SkipIfEnableDeploymentTestsNotDefined() - { - Skip.If(!EnvironmentHelper.GetEnvironmentVariableAsBool(E2ETestConstants.EnableDeploymentTests), - reason: $"{E2ETestConstants.EnableDeploymentTests} is not set to true"); - } - - public static void SkipIfCodeQLBuildOrEnableDeploymentTestsNotDefined() - { - Skip.If(!EnvironmentHelper.GetEnvironmentVariableAsBool(E2ETestConstants.CodeQLBuild) || - !EnvironmentHelper.GetEnvironmentVariableAsBool(E2ETestConstants.EnableDeploymentTests), - reason: $"{E2ETestConstants.CodeQLBuild} is set to false and/or {E2ETestConstants.EnableDeploymentTests} is not set to true "); - } - - public static void SkipIfPublicBuild() - { - Skip.If(EnvironmentHelper.GetEnvironmentVariableAsBool(E2ETestConstants.IsPublicBuild) == true, - reason: $"{E2ETestConstants.IsPublicBuild} is set to true"); - } - } -} \ No newline at end of file diff --git a/test/Azure.Functions.Cli.Tests/E2E/Helpers/TestTraits.cs b/test/Azure.Functions.Cli.Tests/E2E/Helpers/TestTraits.cs deleted file mode 100644 index dd52f2c09..000000000 --- a/test/Azure.Functions.Cli.Tests/E2E/Helpers/TestTraits.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Azure.Functions.Cli.Tests.E2E.Helpers -{ - internal static class TestTraits - { - /// - /// Defines a group of tests to be run together. Useful for test isolation. - /// - public const string Group = "Group"; - - // Groups - /// - /// Tests with RequiresNestedInProcArtifacts label will not be run in the default scenario and only in the artifact consolidation pipeline - /// Otherwise tests with this label will fail in the PR/ official core tools pipelines since the nested inproc artifacts are not present. - /// - public const string RequiresNestedInProcArtifacts = "RequiresNestedInProcArtifacts"; - - /// - /// Tests with UseInConsolidatedArtifactGeneration label will be used in the default scenario and in the artifact consolidation pipeline - /// We still want to run these tests in the PR/ official core tools pipelines and in the artifact consolidation pipeline for a sanity check before publishing the artifacts. - /// - public const string UseInConsolidatedArtifactGeneration = "UseInConsolidatedArtifactGeneration"; - - /// - /// Tests with UseInVisualStudioConsolidatedArtifactGeneration label will not be run in the default scenario and only in the artifact consolidation pipeline - /// Otherwise tests with this label will fail in the PR/ official core tools pipelines since the nested inproc artifacts are not present. - /// - public const string UseInVisualStudioConsolidatedArtifactGeneration = "UseInVisualStudioConsolidatedArtifactGeneration"; - } -} diff --git a/test/Azure.Functions.Cli.Tests/TestsToMigrate/KeyVaultReferencesManagerTests.cs b/test/Azure.Functions.Cli.Tests/TestsToMigrate/KeyVaultReferencesManagerTests.cs deleted file mode 100644 index 4701b77bd..000000000 --- a/test/Azure.Functions.Cli.Tests/TestsToMigrate/KeyVaultReferencesManagerTests.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Requires KeyVault resource or emulator - -using System; -using System.Collections.Generic; -using System.Text; -using Colors.Net; -using FluentAssertions; -using NSubstitute; -using Xunit; - -using Azure.Functions.Cli.Common; - -namespace Azure.Functions.Cli.Tests -{ - public class KeyVaultReferencesManagerTests - { - private IDictionary _settings; - - private readonly KeyVaultReferencesManager _keyVaultReferencesManager = new KeyVaultReferencesManager(); - - [Theory] - [InlineData("", null)] - [InlineData("testKey", null)] - [InlineData("testKey", "")] - [InlineData("testKey", "testValue")] - [InlineData("testKey", "testValue: {\"nestedKey\": \"nestedValue\"}")] - public void ResolveKeyVaultReferencesDoesNotThrow(string key, string value) - { - _settings = new Dictionary - { - { Constants.AzureWebJobsStorage, "UseDevelopmentStorage=true" }, - }; - _settings.Add(key, value); - Exception exception = null; - try - { - _keyVaultReferencesManager.ResolveKeyVaultReferences(_settings); - } - catch (Exception e) - { - exception = e; - } - exception.Should().BeNull(); - } - - [Theory] - [InlineData("test", null, false)] - [InlineData("test", "@Microsoft.KeyVault()", true)] - [InlineData("test", "@Microsoft.KeyVault(string)", true)] - [InlineData("test", "@Microsoft.KeyVault(SecretUri=bad uri)", true)] - [InlineData("test", "@Microsoft.KeyVault(VaultName=vault;)", true)] // missing secret name - [InlineData("test", "@Microsoft.KeyVault(SecretName=vault;)", true)] // missing vault name - [InlineData("test", "@Microsoft-KeyVault()", false)] // hyphen instead of dot - // Attempted Key Vault references are seen as those matching the regular expression - // "^@Microsoft.KeyVault(.*)$". - public void ParseSecretEmitsWarningWithUnsuccessullyMatchedKeyVaultReferences(string key, string value, bool attemptedKeyVaultReference) - { - var output = new StringBuilder(); - var console = Substitute.For(); - console.WriteLine(Arg.Do(o => output.AppendLine(o?.ToString()))).Returns(console); - console.Write(Arg.Do(o => output.Append(o.ToString()))).Returns(console); - ColoredConsole.Out = console; - ColoredConsole.Error = console; - - _keyVaultReferencesManager.ParseSecret(key, value); - var outputString = output.ToString(); - if (attemptedKeyVaultReference) - { - outputString.Should().Contain($"Unable to parse the Key Vault reference for setting: {key}"); - outputString.Should().NotContain(value); - } - else - { - outputString.Should().BeEmpty(); - } - } - - // See https://docs.microsoft.com/en-us/azure/app-service/app-service-key-vault-references - // for more detail on supported key vault reference syntax. - [Theory] - [InlineData("SecretUri=https://sampleurl/secrets/mysecret/version", true, "https://sampleurl/", "mysecret", "version")] - [InlineData("SecretUri=https://sampleurl/secrets/mysecret/version;", true, "https://sampleurl/", "mysecret", "version")] // with semicolon at the end - [InlineData("SecretUri=https://sampleurl/secrets/mysecret/", true, "https://sampleurl/", "mysecret", null)] - [InlineData("VaultName=sampleVault;SecretName=mysecret", true, "https://samplevault.vault.azure.net/", "mysecret", null)] - [InlineData("VaultName=sampleVault;SecretName=mysecret;", true, "https://samplevault.vault.azure.net/", "mysecret", null)] // with semicolon at the end - [InlineData("VaultName=sampleVault;SecretName=mysecret;SecretVersion=secretVersion", true, "https://samplevault.vault.azure.net/", "mysecret", "secretVersion")] - [InlineData("SecretName=mysecret;VaultName=sampleVault;SecretVersion=secretVersion", true, "https://samplevault.vault.azure.net/", "mysecret", "secretVersion")] // different order - public void ParseVaultReferenceMatchesFieldsAppropriately( - string vaultReference, - bool shouldMatch, - string expectedVaultUri = null, - string expectedSecretName = null, - string expectedVersion = null) - { - var matchResult = _keyVaultReferencesManager.ParseVaultReference(vaultReference); - - Assert.True(!((matchResult != null) ^ shouldMatch)); - if (shouldMatch) - { - Assert.Equal(matchResult.Uri.ToString(), expectedVaultUri); - Assert.Equal(matchResult.Name, expectedSecretName); - Assert.Equal(matchResult.Version, expectedVersion); - } - } - } -} \ No newline at end of file diff --git a/test/Azure.Functions.Cli.Tests/TestsToMigrate/ListFunctionsTests.cs b/test/Azure.Functions.Cli.Tests/TestsToMigrate/ListFunctionsTests.cs deleted file mode 100644 index e2a4242cb..000000000 --- a/test/Azure.Functions.Cli.Tests/TestsToMigrate/ListFunctionsTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Requires Function App resource - -using System; -using System.IO; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Azure.Functions.Cli.Tests.E2E.Helpers; -using Xunit; -using Xunit.Abstractions; - -namespace Azure.Functions.Cli.Tests.E2E -{ - public class ListFunctionsTests : BaseE2ETest - { - public ListFunctionsTests(ITestOutputHelper output) : base(output) { } - - - [SkippableFact] - public async Task ListFunctionsWorks() - { - TestConditions.SkipIfAzureServicePrincipalNotDefined(); - - Environment.SetEnvironmentVariable("CLI_DEBUG", "0"); - try - { - await CliTester.Run(new[] { - new RunConfiguration - { - Commands = new[] - { - "azure functionapp list-functions core-tools-list-functions", - }, - OutputContains = new string[] - { - "api/httpwithkey", - "api/httpwithoutkey" - }, - OutputDoesntContain = new string[] - { - "api/httpwithkey?code=", - "api/httpwithoutkey?code=" - }, - CommandTimeout = TimeSpan.FromMinutes(2) - }, - new RunConfiguration - { - Commands = new [] - { - "azure functionapp list-functions core-tools-list-functions --show-keys" - }, - OutputContains = new string [] - { - "api/httpwithkey?code=", - "api/httpwithoutkey" - }, - OutputDoesntContain = new string[] - { - "api/httpwithoutkey?code=" - }, - CommandTimeout = TimeSpan.FromMinutes(2) - } - }); - } - finally - { - Environment.SetEnvironmentVariable("CLI_DEBUG", "1"); - } - } - } -} \ No newline at end of file diff --git a/test/Azure.Functions.Cli.Tests/TestsToMigrate/PublishActionTests.cs b/test/Azure.Functions.Cli.Tests/TestsToMigrate/PublishActionTests.cs deleted file mode 100644 index dfdcce2db..000000000 --- a/test/Azure.Functions.Cli.Tests/TestsToMigrate/PublishActionTests.cs +++ /dev/null @@ -1,208 +0,0 @@ -// Requires Function App resource - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Azure.Functions.Cli.Actions.AzureActions; -using Azure.Functions.Cli.Arm.Models; -using Azure.Functions.Cli.Common; -using Azure.Functions.Cli.Helpers; -using Xunit; -using static Azure.Functions.Cli.Actions.AzureActions.PublishFunctionAppAction; - -namespace Azure.Functions.Cli.Tests -{ - public class PublishActionTests - { - private readonly TestAzureHelperService _helperService = new TestAzureHelperService(); - - [Theory] - [InlineData(null, "6.0")] - [InlineData("something", "6.0")] - [InlineData("6.0", "6.0")] - [InlineData("7.0", "7.0")] - [InlineData("9.0", "9.0")] - public async Task NetFrameworkVersion_DotnetIsolated_Linux_Consumption_Updated(string initialLinuxFxVersion, string expectedNetFrameworkVersion) - { - var site = new Site("test") - { - Kind = "linux", - Sku = "dynamic", - LinuxFxVersion = initialLinuxFxVersion - }; - - await PublishFunctionAppAction.UpdateFrameworkVersions(site, WorkerRuntime.DotnetIsolated, expectedNetFrameworkVersion, false, _helperService); - - // update it to empty - var setting = _helperService.UpdatedSettings.Single(); - Assert.Equal(Constants.LinuxFxVersion, setting.Key); - Assert.Equal($"DOTNET-ISOLATED|{expectedNetFrameworkVersion}", setting.Value); - } - - [Theory] - [InlineData("v6.0")] - [InlineData("6.0")] - [InlineData("6.0.1")] - public async Task NetFrameworkVersion_DotnetIsolated_Linux_Dedicated(string specifiedVersion) - { - var site = new Site("test") - { - Kind = "linux" - }; - - await PublishFunctionAppAction.UpdateFrameworkVersions(site, WorkerRuntime.DotnetIsolated, specifiedVersion, false, _helperService); - - var setting = _helperService.UpdatedSettings.Single(); - Assert.Equal(Constants.LinuxFxVersion, setting.Key); - Assert.Equal("DOTNET-ISOLATED|6.0", setting.Value); - } - - [Theory] - [InlineData("v6.0")] - [InlineData("6.0")] - [InlineData("6.0.1")] - public async Task NetFrameworkVersion_DotnetIsolated_Windows(string specifiedVersion) - { - var site = new Site("test"); - - await PublishFunctionAppAction.UpdateFrameworkVersions(site, WorkerRuntime.DotnetIsolated, specifiedVersion, false, _helperService); - - var setting = _helperService.UpdatedSettings.Single(); - Assert.Equal(Constants.DotnetFrameworkVersion, setting.Key); - Assert.Equal("v6.0", setting.Value); - } - - [Fact] - public async Task NetFrameworkVersion_DotnetIsolated_Linux_Null() - { - // If not specified, assume 8.0 - var site = new Site("test") - { - Kind = "linux" - }; - - await PublishFunctionAppAction.UpdateFrameworkVersions(site, WorkerRuntime.DotnetIsolated, null, false, _helperService); - - var setting = _helperService.UpdatedSettings.Single(); - Assert.Equal(Constants.LinuxFxVersion, setting.Key); - Assert.Equal("DOTNET-ISOLATED|8.0", setting.Value); - } - - [Fact] - public async Task NetFrameworkVersion_DotnetIsolated_Windows_Null() - { - // If not specified, assume 8.0 - var site = new Site("test"); - - await PublishFunctionAppAction.UpdateFrameworkVersions(site, WorkerRuntime.DotnetIsolated, null, false, _helperService); - - var setting = _helperService.UpdatedSettings.Single(); - Assert.Equal(Constants.DotnetFrameworkVersion, setting.Key); - Assert.Equal("v8.0", setting.Value); - } - - [Fact] - public async Task NetFrameworkVersion_Dotnet_Windows_Null() - { - var site = new Site("test") - { - NetFrameworkVersion = "v4.0" - }; - - // If not supported specified, assume 8.0 - await PublishFunctionAppAction.UpdateFrameworkVersions(site, WorkerRuntime.Dotnet, null, false, _helperService); - - var setting = _helperService.UpdatedSettings.Single(); - Assert.Equal(Constants.DotnetFrameworkVersion, setting.Key); - Assert.Equal("v8.0", setting.Value); - } - - [Fact] - public async Task NetFrameworkVersion_Dotnet_Windows_NoOp() - { - var site = new Site("test") - { - NetFrameworkVersion = "v8.0" - }; - - await PublishFunctionAppAction.UpdateFrameworkVersions(site, WorkerRuntime.Dotnet, null, false, _helperService); - - // Should be a no-op as site is already v6.0 - Assert.Null(_helperService.UpdatedSettings); - } - - [Theory] - [InlineData("abc")] - [InlineData("6.0.a.b")] - public async Task NetFrameworkVersion_Invalid(string specifiedVersion) - { - var site = new Site("test"); - - var exception = await Assert.ThrowsAsync(() => - PublishFunctionAppAction.UpdateFrameworkVersions(site, WorkerRuntime.DotnetIsolated, specifiedVersion, false, _helperService)); - - Assert.StartsWith($"The dotnet-version value of '{specifiedVersion}' is invalid.", exception.Message); - } - - [Theory] - [InlineData("dotnet-isolated", WorkerRuntime.DotnetIsolated)] - [InlineData("c#-isolated", WorkerRuntime.DotnetIsolated)] - [InlineData("csharp", WorkerRuntime.Dotnet)] - [InlineData("typescript", WorkerRuntime.Node)] - public void NormalizeWorkerRuntime_ReturnsExpectedWorkerRuntime(string input, WorkerRuntime expected) - { - - var result = WorkerRuntimeLanguageHelper.NormalizeWorkerRuntime(input); - - Assert.Equal(expected, result); - } - - [Theory] - [InlineData("invalid-runtime")] - [InlineData("unknown")] - [InlineData("dotnet-isol")] - [InlineData("c-sharp")] - [InlineData("")] - [InlineData(null)] - public void NormalizeWorkerRuntime_InvalidInput(string inputString) - { - if (string.IsNullOrWhiteSpace(inputString)) - { - var exception = Assert.Throws(() => WorkerRuntimeLanguageHelper.NormalizeWorkerRuntime(inputString)); - Assert.StartsWith($"Worker runtime cannot be null or empty.", exception.Message); - Assert.Equal("workerRuntime", exception.ParamName); - } - else - { - var exception = Assert.Throws(() => WorkerRuntimeLanguageHelper.NormalizeWorkerRuntime(inputString)); - Assert.Equal($"Worker runtime '{inputString}' is not a valid option. Options are {WorkerRuntimeLanguageHelper.AvailableWorkersRuntimeString}", exception.Message); - } - } - - [Fact] - public void ValidateFunctionAppPublish_ThrowException_WhenWorkerRuntimeIsNone() - { - GlobalCoreToolsSettings.SetWorkerRuntime(WorkerRuntime.None); - - var ex = Assert.Throws(() => GlobalCoreToolsSettings.CurrentWorkerRuntime); - Assert.Equal($"Worker runtime cannot be '{WorkerRuntime.None}'. Please set a valid runtime.", ex.Message); - } - - private class TestAzureHelperService : AzureHelperService - { - public Dictionary UpdatedSettings { get; private set; } - - public TestAzureHelperService() - : base(null, null) - { - } - - public override Task> UpdateWebSettings(Site functionApp, Dictionary updatedSettings) - { - UpdatedSettings = updatedSettings; - return Task.FromResult(new HttpResult(string.Empty)); - } - } - } -} diff --git a/test/Azure.Functions.Cli.Tests/app.config b/test/Azure.Functions.Cli.Tests/app.config deleted file mode 100644 index 19852ceb2..000000000 --- a/test/Azure.Functions.Cli.Tests/app.config +++ /dev/null @@ -1,177 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/Azure.Functions.Cli.Tests/xunit.runner.json b/test/Azure.Functions.Cli.Tests/xunit.runner.json deleted file mode 100644 index 7d0d2a699..000000000 --- a/test/Azure.Functions.Cli.Tests/xunit.runner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "parallelizeTestCollections": false, - "parallelizeAssembly": false -} \ No newline at end of file From 50e5b13106f587e3f760f288d70a4ffa91c49d8f Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Wed, 19 Nov 2025 11:01:59 -0800 Subject: [PATCH 6/8] Update windows instructions --- docs/debug-with-host.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/debug-with-host.md b/docs/debug-with-host.md index 2d20c2b99..35b265c09 100644 --- a/docs/debug-with-host.md +++ b/docs/debug-with-host.md @@ -58,3 +58,10 @@ export FUNC_CLI= # Start the host inside the test app directory $FUNC_CLI start ``` + +Windows: + +```pwsh +$env:FUNC_CLI = "" +& $env:FUNC_CLI start +``` \ No newline at end of file From 78fa9d3bb0fb6cd1b65ed4c45710d8b0ec5046a0 Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Thu, 20 Nov 2025 10:58:40 -0800 Subject: [PATCH 7/8] Update contributing and fix sln --- Azure.Functions.Cli.sln | 7 - CONTRIBUTING.md | 143 ++++++++++++------ .../Commands/FuncVersion/VersionTests.cs | 7 +- 3 files changed, 102 insertions(+), 55 deletions(-) diff --git a/Azure.Functions.Cli.sln b/Azure.Functions.Cli.sln index c01de39ce..6e85c8e0f 100644 --- a/Azure.Functions.Cli.sln +++ b/Azure.Functions.Cli.sln @@ -9,8 +9,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{6EE1D011-2 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Functions.Cli", "src\Cli\func\Azure.Functions.Cli.csproj", "{6608738C-3BDB-47F5-BC62-66A8BDF9D884}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Functions.Cli.Tests", "test\Azure.Functions.Cli.Tests\Azure.Functions.Cli.Tests.csproj", "{EAEA6EDB-A301-4A50-86D8-91859DABE30E}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZippedExe", "test\ZippedExe\ZippedExe.csproj", "{2CD45039-0ABD-4082-87D0-52BB5D467B50}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Cli", "Cli", "{154FDAF2-0E86-450E-BE57-4E3D410B0FAC}" @@ -41,10 +39,6 @@ Global {6608738C-3BDB-47F5-BC62-66A8BDF9D884}.Debug|Any CPU.Build.0 = Debug|Any CPU {6608738C-3BDB-47F5-BC62-66A8BDF9D884}.Release|Any CPU.ActiveCfg = Release|Any CPU {6608738C-3BDB-47F5-BC62-66A8BDF9D884}.Release|Any CPU.Build.0 = Release|Any CPU - {EAEA6EDB-A301-4A50-86D8-91859DABE30E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EAEA6EDB-A301-4A50-86D8-91859DABE30E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EAEA6EDB-A301-4A50-86D8-91859DABE30E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EAEA6EDB-A301-4A50-86D8-91859DABE30E}.Release|Any CPU.Build.0 = Release|Any CPU {2CD45039-0ABD-4082-87D0-52BB5D467B50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2CD45039-0ABD-4082-87D0-52BB5D467B50}.Debug|Any CPU.Build.0 = Debug|Any CPU {2CD45039-0ABD-4082-87D0-52BB5D467B50}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -83,7 +77,6 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {6608738C-3BDB-47F5-BC62-66A8BDF9D884} = {154FDAF2-0E86-450E-BE57-4E3D410B0FAC} - {EAEA6EDB-A301-4A50-86D8-91859DABE30E} = {6EE1D011-2334-44F2-9D41-608B969DAE6D} {2CD45039-0ABD-4082-87D0-52BB5D467B50} = {6EE1D011-2334-44F2-9D41-608B969DAE6D} {154FDAF2-0E86-450E-BE57-4E3D410B0FAC} = {5F51C958-39C0-4E0C-9165-71D0BCE647BC} {78231B55-D243-46F1-9C7F-7831B40ED2D8} = {5F51C958-39C0-4E0C-9165-71D0BCE647BC} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 88de25323..e2d580b39 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,75 +1,132 @@ -## Running the latest runtime version +# Contributing to the Azure Functions CLI -## Dependencies +## Contributing to this Repository -There is a dependency on the .NET Core tools for the cross platform support. You can [install these here](https://www.microsoft.com/net/core). +### Filing Issues -To install the required dotnet packages navigate into the repository root and run `dotnet restore` +Filing issues is a great way to contribute to the SDK. Here are some guidelines: -## Compiling the CLI Tools +* Include as much detail as you can be about the problem +* Point to a test repository (e.g. hosted on GitHub) that can help reproduce the issue. This works better then trying to describe step by step how to create a repro scenario. +* Github supports markdown, so when filing bugs make sure you check the formatting before clicking submit. -To build the project run `dotnet build` from the root of the repository. This will build the project and all of its dependencies. -The output will be placed in the `out/bin/Azure.Functions.Cli/debug` directory. +### Submitting Pull Requests -`dotnet run --project src/Cli/func ` will run the CLI tool from the source directory. +If you don't know what a pull request is read this https://help.github.com/articles/using-pull-requests. -### Running against a function app +Before we can accept your pull-request you'll need to sign a [Contribution License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement). You can sign ours [here](https://cla2.dotnetfoundation.org). However, you don't have to do this up-front. You can simply clone, fork, and submit your pull-request as usual. -To test this project against a local function app you can run from that function app's directory +When your pull-request is created, we classify it. If the change is trivial, i.e. you just fixed a typo, then the PR is labelled with `cla-not-required`. Otherwise it's classified as `cla-required`. In that case, the system will also also tell you how you can sign the CLA. Once you signed a CLA, the current and all future pull-requests will be labelled as `cla-signed`. Signing the CLA might sound scary but it's actually super simple and can be done in less than a minute. -- `cd myTestFunctionApp` -- `dotnet run --project PATH_TO_FUNCTIONS_CLI/src/Cli/func ` +Before submitting a feature or substantial code contribution please discuss it with the team and ensure it follows the product roadmap. Note that all code submissions will be rigorously reviewed and tested by the Azure Functions Core Tools team, and only those that meet the bar for both quality and design/roadmap appropriateness will be merged into the source. -where PATH_TO_FUNCTIONS_CLI is the absolute or relative path to the root of this repository. +## Running the CLI locally -Or you can add `out/bin/Azure.Functions.Cli/debug/func` to your `PATH` environment variable and run the command from anywhere. +### Dependencies -- `export PATH=$PATH:/path/to/Azure.Functions.Cli/out/bin/Azure.Functions.Cli/debug/func` -- `func ` +Install [.NET SDK](https://www.microsoft.com/net/core) for cross-platform support. -### Running the Test Suite +### Building the CLI -- Build the solution `dotnet build Azure.Functions.Cli.sln` - - As part of this build, the cli is copied into the test project's output directory (`out/bin/Azure.Functions.Cli.Tests/debug`) - this is what will be used by the tests - - If you wish to override this, you can set the `FUNC_PATH` environment variable to the path of the `func`/`func.exe` you wish to test against -- Run the test suite in Visual Studio Test Explorer or by running `dotnet test` from the `test` project root. - - i.e. `cd test/Azure.Functions.Cli.Tests; dotnet test` +Build the project from the repository root: -#### Storage Emulator +```bash +dotnet build +``` -Some tests, namely E2E, require an Azure storage emulator to be running. You can download the storage emulator [here](https://learn.microsoft.com/azure/storage/common/storage-use-azurite?tabs=visual-studio%2Cblob-storage). +The output will be in `out/bin/Azure.Functions.Cli/debug/`. -Run the emulator before your run the tests. +### Running the CLI -> There is a script you can use for this as well, see `tools/start-emulators.ps1` +**Running and debugging:** -#### Templates Missing +- **VS Code** - Press `F5` to run and debug. You'll be prompted for: + 1. The command (e.g., `start`, `new`, `init`) + 2. Optional `--script-root` path to your test function app -If you see an error saying the templates folder is missing, you can download the templates using the `download-templates.ps1` script. +- **Visual Studio** - Press `F5` to run and debug. Configure the launch profile to set: + 1. The command via `command line arguments` + 2. Path to your test function app path via `working directory` -From the root of the repo, run: +**Command line:** -- `./eng/scripts/download-templates.ps1 -OutputPath "./out/bin//debug` - - e.g. "./out/bin/Azure.Functions.Cli.E2ETests/debug" +Run the CLI from source: -The script will download the template packages to a `templates` folder in the specified output directory. +```bash +dotnet run --project src/Cli/func -- +``` -## Contributing to this Repository +**Running against a specific function app:** -### Filing Issues +Option 1 - Run from the function app directory: -Filing issues is a great way to contribute to the SDK. Here are some guidelines: +```bash +cd myTestFunctionApp +dotnet run --project PATH_TO_CORE_TOOLS_REPO/src/Cli/func -- +``` -* Include as much detail as you can be about the problem -* Point to a test repository (e.g. hosted on GitHub) that can help reproduce the issue. This works better then trying to describe step by step how to create a repro scenario. -* Github supports markdown, so when filing bugs make sure you check the formatting before clicking submit. +Option 2 - Use the `--script-root` parameter: -### Submitting Pull Requests +```bash +dotnet run --project src/Cli/func -- --script-root PATH_TO_TEST_APP +``` -If you don't know what a pull request is read this https://help.github.com/articles/using-pull-requests. +Option 3 - Add the built executable to your PATH: -Before we can accept your pull-request you'll need to sign a [Contribution License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement). You can sign ours [here](https://cla2.dotnetfoundation.org). However, you don't have to do this up-front. You can simply clone, fork, and submit your pull-request as usual. +```bash +export PATH=$PATH:/path/to/azure-functions-core-tools/out/bin/Azure.Functions.Cli/debug +func +``` -When your pull-request is created, we classify it. If the change is trivial, i.e. you just fixed a typo, then the PR is labelled with `cla-not-required`. Otherwise it's classified as `cla-required`. In that case, the system will also also tell you how you can sign the CLA. Once you signed a CLA, the current and all future pull-requests will be labelled as `cla-signed`. Signing the CLA might sound scary but it's actually super simple and can be done in less than a minute. +### Running Tests -Before submitting a feature or substantial code contribution please discuss it with the team and ensure it follows the product roadmap. Note that all code submissions will be rigorously reviewed and tested by the Azure Functions Core Tools team, and only those that meet the bar for both quality and design/roadmap appropriateness will be merged into the source. +Tests can be run using: + +- **Visual Studio Test Explorer** - Use Test Explorer in Visual Studio +- **VS Code** - Using the `.NET Core Test Explorer` extension to discover and run tests +- **Command line** - Use `dotnet test` commands below + +#### Unit Tests + +```bash +dotnet test test/Cli/Func.UnitTests/Azure.Functions.Cli.UnitTests.csproj +``` + +#### E2E Tests + +E2E tests require Azure Storage emulator (Azurite). + +**Option 1 - Using the provided script:** + +```bash +./eng/scripts/start-emulators.ps1 +``` + +**Option 2 - Manual setup:** +- Download [Azurite](https://learn.microsoft.com/azure/storage/common/storage-use-azurite) +- Start Azurite before running tests: + ```bash + azurite --silent --skipApiVersionCheck + ``` + +Then run the E2E tests: + +```bash +dotnet test test/Cli/Func.E2ETests/Azure.Functions.Cli.E2ETests.csproj +``` + +**Note:** The build automatically copies `func` to the test output directory (`out/bin/Azure.Functions.Cli.E2ETests/debug/`). To test a different `func` executable, set the `FUNC_PATH` environment variable: + +```bash +export FUNC_PATH=/path/to/custom/func +``` + +#### Missing Templates + +If tests fail due to missing templates, download them: + +```bash +./eng/scripts/download-templates.ps1 -OutputPath "./out/bin/Azure.Functions.Cli.E2ETests/debug" +``` + +The script downloads templates to a `templates/` folder in the specified output directory. diff --git a/test/Cli/Func.E2ETests/Commands/FuncVersion/VersionTests.cs b/test/Cli/Func.E2ETests/Commands/FuncVersion/VersionTests.cs index f40a1af6b..a903aad40 100644 --- a/test/Cli/Func.E2ETests/Commands/FuncVersion/VersionTests.cs +++ b/test/Cli/Func.E2ETests/Commands/FuncVersion/VersionTests.cs @@ -23,12 +23,9 @@ public void Version_DisplaysVersionNumber(string args) .WithWorkingDirectory(WorkingDirectory) .Execute([args]); - // Verify the output contains a version number starting with "4." - result.Should().HaveStdOutContaining("4.0."); + // Verify the output contains the current version number + result.Should().HaveStdOutContaining(Common.Constants.CliVersion); result.Should().ExitWith(0); - - // TODO: use this when build refactor changes go in: - // result.Should().HaveStdOutContaining(Common.Constants.CliVersion); } } } From 6bf8c625c3e6b91fbba396c434d54fefdbfe3b20 Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Thu, 20 Nov 2025 13:24:10 -0800 Subject: [PATCH 8/8] Migrate old unit tests --- .../PackAction/PackValidationHelperTests.cs | 91 +++++++++++++ .../KeyVaultReferencesManagerTests.cs | 125 ++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 test/Cli/Func.UnitTests/ActionsTests/PackAction/PackValidationHelperTests.cs create mode 100644 test/Cli/Func.UnitTests/CommonTests/KeyVaultReferencesManagerTests.cs diff --git a/test/Cli/Func.UnitTests/ActionsTests/PackAction/PackValidationHelperTests.cs b/test/Cli/Func.UnitTests/ActionsTests/PackAction/PackValidationHelperTests.cs new file mode 100644 index 000000000..915f0e0d4 --- /dev/null +++ b/test/Cli/Func.UnitTests/ActionsTests/PackAction/PackValidationHelperTests.cs @@ -0,0 +1,91 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Azure.Functions.Cli.Actions.LocalActions.PackAction; +using FluentAssertions; +using Xunit; + +namespace Azure.Functions.Cli.UnitTests.ActionsTests.PackAction +{ + public class PackValidationHelperTests : IDisposable + { + private readonly string _tempDirectory; + + public PackValidationHelperTests() + { + _tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(_tempDirectory); + } + + public void Dispose() + { + if (Directory.Exists(_tempDirectory)) + { + Directory.Delete(_tempDirectory, recursive: true); + } + } + + [Fact] + public void ValidateRequiredFiles_AllFilesExist_ReturnsTrue() + { + // Arrange + var requiredFiles = new[] { "host.json", "package.json" }; + foreach (var file in requiredFiles) + { + File.WriteAllText(Path.Combine(_tempDirectory, file), "{}"); + } + + // Act + var result = PackValidationHelper.ValidateRequiredFiles(_tempDirectory, requiredFiles, out string missingFile); + + // Assert + result.Should().BeTrue(); + missingFile.Should().BeEmpty(); + } + + [Fact] + public void ValidateRequiredFiles_MissingFile_ReturnsFalse() + { + // Arrange + var requiredFiles = new[] { "host.json", "package.json" }; + File.WriteAllText(Path.Combine(_tempDirectory, "host.json"), "{}"); + + // Don't create package.json + + // Act + var result = PackValidationHelper.ValidateRequiredFiles(_tempDirectory, requiredFiles, out string missingFile); + + // Assert + result.Should().BeFalse(); + missingFile.Should().Be("package.json"); + } + + [Fact] + public void ValidateRequiredFiles_EmptyDirectory_ReturnsFalse() + { + // Arrange + var requiredFiles = new[] { "host.json" }; + + // Act + var result = PackValidationHelper.ValidateRequiredFiles(_tempDirectory, requiredFiles, out string missingFile); + + // Assert + result.Should().BeFalse(); + missingFile.Should().Be("host.json"); + } + + [Fact] + public void ValidateRequiredFiles_NoRequiredFiles_ReturnsTrue() + { + // Arrange + var requiredFiles = Array.Empty(); + + // Act + var result = PackValidationHelper.ValidateRequiredFiles(_tempDirectory, requiredFiles, out string missingFile); + + // Assert + result.Should().BeTrue(); + missingFile.Should().BeEmpty(); + } + } +} diff --git a/test/Cli/Func.UnitTests/CommonTests/KeyVaultReferencesManagerTests.cs b/test/Cli/Func.UnitTests/CommonTests/KeyVaultReferencesManagerTests.cs new file mode 100644 index 000000000..3fadc5560 --- /dev/null +++ b/test/Cli/Func.UnitTests/CommonTests/KeyVaultReferencesManagerTests.cs @@ -0,0 +1,125 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System.Text; +using Azure.Functions.Cli.Common; +using Colors.Net; +using FluentAssertions; +using NSubstitute; +using Xunit; + +namespace Azure.Functions.Cli.UnitTests.CommonTests +{ + public class KeyVaultReferencesManagerTests + { + private readonly KeyVaultReferencesManager _keyVaultReferencesManager = new KeyVaultReferencesManager(); + private IDictionary _settings; + + [Theory] + [InlineData("", null)] + [InlineData("testKey", null)] + [InlineData("testKey", "")] + [InlineData("testKey", "testValue")] + [InlineData("testKey", "testValue: {\"nestedKey\": \"nestedValue\"}")] + public void ResolveKeyVaultReferencesDoesNotThrow(string key, string value) + { + _settings = new Dictionary + { + { Constants.AzureWebJobsStorage, "UseDevelopmentStorage=true" }, + }; + _settings.Add(key, value); + + Exception exception = null; + try + { + _keyVaultReferencesManager.ResolveKeyVaultReferences(_settings); + } + catch (Exception e) + { + exception = e; + } + + exception.Should().BeNull(); + } + + [Theory] + [InlineData("test", null, false)] + [InlineData("test", "@Microsoft.KeyVault()", true)] + [InlineData("test", "@Microsoft.KeyVault(string)", true)] + [InlineData("test", "@Microsoft.KeyVault(SecretUri=bad uri)", true)] + [InlineData("test", "@Microsoft.KeyVault(VaultName=vault;)", true)] // missing secret name + [InlineData("test", "@Microsoft.KeyVault(SecretName=vault;)", true)] // missing vault name + [InlineData("test", "@Microsoft-KeyVault()", false)] // hyphen instead of dot + public void ParseSecretEmitsWarningWithUnsuccessfullyMatchedKeyVaultReferences(string key, string value, bool attemptedKeyVaultReference) + { + // Arrange + var output = new StringBuilder(); + var console = Substitute.For(); + console.WriteLine(Arg.Do(o => output.AppendLine(o?.ToString()))).Returns(console); + console.Write(Arg.Do(o => output.Append(o.ToString()))).Returns(console); + ColoredConsole.Out = console; + ColoredConsole.Error = console; + + // Act + _keyVaultReferencesManager.ParseSecret(key, value); + + // Assert + var outputString = output.ToString(); + if (attemptedKeyVaultReference) + { + outputString.Should().Contain($"Unable to parse the Key Vault reference for setting: {key}"); + outputString.Should().NotContain(value); + } + else + { + outputString.Should().BeEmpty(); + } + } + + // See https://docs.microsoft.com/en-us/azure/app-service/app-service-key-vault-references + // for more detail on supported key vault reference syntax. + [Theory] + [InlineData("SecretUri=https://sampleurl/secrets/mysecret/version", true, "https://sampleurl/", "mysecret", "version")] + [InlineData("SecretUri=https://sampleurl/secrets/mysecret/version;", true, "https://sampleurl/", "mysecret", "version")] // with semicolon at the end + [InlineData("SecretUri=https://sampleurl/secrets/mysecret/", true, "https://sampleurl/", "mysecret", null)] + [InlineData("VaultName=sampleVault;SecretName=mysecret", true, "https://samplevault.vault.azure.net/", "mysecret", null)] + [InlineData("VaultName=sampleVault;SecretName=mysecret;", true, "https://samplevault.vault.azure.net/", "mysecret", null)] // with semicolon at the end + [InlineData("VaultName=sampleVault;SecretName=mysecret;SecretVersion=secretVersion", true, "https://samplevault.vault.azure.net/", "mysecret", "secretVersion")] + [InlineData("SecretName=mysecret;VaultName=sampleVault;SecretVersion=secretVersion", true, "https://samplevault.vault.azure.net/", "mysecret", "secretVersion")] // different order + public void ParseVaultReferenceMatchesFieldsAppropriately( + string vaultReference, + bool shouldMatch, + string expectedVaultUri = null, + string expectedSecretName = null, + string expectedVersion = null) + { + // Act + var matchResult = _keyVaultReferencesManager.ParseVaultReference(vaultReference); + + // Assert + Assert.True(!((matchResult != null) ^ shouldMatch)); + if (shouldMatch) + { + Assert.Equal(expectedVaultUri, matchResult.Uri.ToString()); + Assert.Equal(expectedSecretName, matchResult.Name); + Assert.Equal(expectedVersion, matchResult.Version); + } + } + + [Theory] + [InlineData("SecretUri", "SecretUri=https://example.vault.azure.net/secrets/mysecret", "https://example.vault.azure.net/secrets/mysecret")] + [InlineData("VaultName", "VaultName=myVault;SecretName=mySecret", "myVault")] + [InlineData("SecretName", "VaultName=myVault;SecretName=mySecret", "mySecret")] + [InlineData("SecretVersion", "VaultName=myVault;SecretName=mySecret;SecretVersion=v1", "v1")] + [InlineData("SecretUri", "VaultName=myVault;SecretName=mySecret", null)] + [InlineData("NonExistent", "VaultName=myVault;SecretName=mySecret", null)] + public void GetValueFromVaultReferenceExtractsCorrectValue(string key, string vaultReference, string expectedValue) + { + // Act + var result = _keyVaultReferencesManager.GetValueFromVaultReference(key, vaultReference); + + // Assert + Assert.Equal(expectedValue, result); + } + } +}