diff --git a/publish-scripts/chocolatey/installps_template b/publish-scripts/chocolatey/installps_template index 198f76c79..0f349d879 100644 --- a/publish-scripts/chocolatey/installps_template +++ b/publish-scripts/chocolatey/installps_template @@ -40,11 +40,12 @@ $packageArgs = @{ Install-ChocolateyZipPackage @packageArgs # only symlink for func.exe -$files = Get-ChildItem $toolsDir -include *.exe -recurse +$files = Get-ChildItem $toolsDir -filter *.exe -Recurse -File foreach ($file in $files) { - if (!$file.Name.Equals("func.exe")) { + if (!$file.Name.Equals("func.exe") -or (!($file.DirectoryName -eq $toolsDir) -and $file.Name.Equals("func.exe"))) { #generate an ignore file - New-Item "$file.ignore" -type file -force | Out-Null + $ignoreFilePath = Join-Path -Path $file.DirectoryName -ChildPath "$($file.Name).ignore" + New-Item -Path $ignoreFilePath -Type File -Force | Out-Null } } diff --git a/src/Azure.Functions.Cli/Helpers/VersionHelper.cs b/src/Azure.Functions.Cli/Helpers/VersionHelper.cs index 7f13da888..a39d15773 100644 --- a/src/Azure.Functions.Cli/Helpers/VersionHelper.cs +++ b/src/Azure.Functions.Cli/Helpers/VersionHelper.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net.Http; using System.Reflection; using System.Runtime.InteropServices; using System.Text; @@ -17,6 +18,14 @@ namespace Azure.Functions.Cli.Helpers { internal class VersionHelper { + private static string _cliVersion = Constants.CliVersion; + + // This method is created only for testing + public static void SetCliVersion(string version) + { + _cliVersion = version; + } + public static async Task RunAsync(Task isRunningOlderVersion = null) { isRunningOlderVersion ??= IsRunningAnOlderVersion(); @@ -36,11 +45,11 @@ public static async Task RunAsync(Task isRunningOlderVersion = nul // Check that current core tools is the latest version. // To ensure that it doesn't block other tasks. The HTTP Request timeout is only 1 second. // We simply ingnore the exception if for any reason the check fails. - public static async Task IsRunningAnOlderVersion() + public static async Task IsRunningAnOlderVersion(HttpClient client = null) { try { - var client = new System.Net.Http.HttpClient + client ??= new System.Net.Http.HttpClient { Timeout = TimeSpan.FromSeconds(1) }; @@ -53,13 +62,13 @@ public static async Task IsRunningAnOlderVersion() { var jProperty = (Newtonsoft.Json.Linq.JProperty)item; var releaseDetail = JsonConvert.DeserializeObject(jProperty.Value.ToString()); - releaseList.Add(new ReleaseSummary() { Release = jProperty.Name, ReleaseDetail = releaseDetail.ReleaseList.FirstOrDefault() }); + releaseList.Add(new ReleaseSummary(jProperty.Name, releaseDetail.ReleaseList.FirstOrDefault())); } - var latestCoreToolsReleaseVersion = releaseList.FirstOrDefault(x => x.Release == data.Tags.V4Release.ReleaseVersion)?.CoreToolsReleaseNumber; + var latestCoreToolsAssemblyZipFile = releaseList.FirstOrDefault(x => x.Release == data.Tags.V4Release.ReleaseVersion)?.CoreToolsAssemblyZipFile; - if (!string.IsNullOrEmpty(latestCoreToolsReleaseVersion) && - Constants.CliVersion != latestCoreToolsReleaseVersion) + if (!string.IsNullOrEmpty(latestCoreToolsAssemblyZipFile) && + !latestCoreToolsAssemblyZipFile.Contains($"{_cliVersion}.zip")) { return true; } @@ -159,8 +168,24 @@ private class Release public bool Hidden { get; set; } } - private class ReleaseSummary + internal class ReleaseSummary { + private readonly string _coreToolsAssemblyZipFile; + public ReleaseSummary(string release, CoreToolsRelease releaseDetail) + { + Release = release; + ReleaseDetail = releaseDetail; + + if (string.IsNullOrEmpty(ReleaseDetail?.DownloadLink)) + { + _coreToolsAssemblyZipFile = string.Empty; + } + else + { + Uri uri = new UriBuilder(ReleaseDetail?.DownloadLink).Uri; + _coreToolsAssemblyZipFile = uri.Segments.FirstOrDefault(segment => segment.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)); + } + } public string Release { get; set; } public string CoreToolsReleaseNumber @@ -184,15 +209,23 @@ public string CoreToolsReleaseNumber } } public CoreToolsRelease ReleaseDetail { get; set; } + public string CoreToolsAssemblyZipFile + { + get + { + return _coreToolsAssemblyZipFile; + } + } + } - private class ReleaseDetail + internal class ReleaseDetail { [JsonProperty("coreTools")] public IList ReleaseList { get; set; } } - private class CoreToolsRelease + internal class CoreToolsRelease { [JsonProperty("OS")] public string Os { get; set; } diff --git a/test/Azure.Functions.Cli.Tests/E2E/VersionTests.cs b/test/Azure.Functions.Cli.Tests/E2E/VersionTests.cs index 82ce73320..5552a4a12 100644 --- a/test/Azure.Functions.Cli.Tests/E2E/VersionTests.cs +++ b/test/Azure.Functions.Cli.Tests/E2E/VersionTests.cs @@ -2,8 +2,16 @@ using System.Threading.Tasks; using Azure.Functions.Cli.Common; using Azure.Functions.Cli.Tests.E2E.Helpers; +using FluentAssertions; using Xunit; using Xunit.Abstractions; +using static Azure.Functions.Cli.Helpers.VersionHelper; +using Azure.Functions.Cli.Helpers; +using Moq.Protected; +using Moq; +using System.Net.Http; +using System.Net; +using System.Threading; namespace Azure.Functions.Cli.Tests.E2E { @@ -24,5 +32,95 @@ await CliTester.Run(new RunConfiguration CommandTimeout = TimeSpan.FromSeconds(30) }, _output); } + + [Fact] + public void CoreToolsAssemblyZipFile_ShouldParseCorrectSegment_WhenValidDownloadLinkIsProvided() + { + var fakeDownloadLink = "https://example.com/public/coretoolnumber/V4/assemblyfile.zip"; + var releaseDetail = new CoreToolsRelease { DownloadLink = fakeDownloadLink }; + var releaseSummary = new ReleaseSummary ("V4",releaseDetail); + + var result = releaseSummary.CoreToolsAssemblyZipFile; + + result.Should().Be("assemblyfile.zip"); // We expect the segment "assemblyfile.zip" based on the provided URL + } + + [Fact] + public void CoreToolsAssemblyZipFile_ShouldReturnEmpty_WhenDownloadLinkIsNull() + { + var releaseDetail = new CoreToolsRelease { DownloadLink = null }; + var releaseSummary = new ReleaseSummary("V4", releaseDetail); + + var result = releaseSummary.CoreToolsAssemblyZipFile; + + result.Should().Be(string.Empty); // The result should be empty when there is no link + } + + [Fact] + public async Task IsRunningAnOlderVersion_ShouldReturnTrue_WhenVersionIsOlder() + { + // Create the mocked HttpClient with the mock response + var mockHttpClient = GetMockHttpClientWithResponse(); + + SetCliVersion("4.0.1"); + var result = await VersionHelper.IsRunningAnOlderVersion(mockHttpClient); + + result.Should().Be(true); + } + + [Fact] + public async Task IsRunningAnOlderVersion_ShouldReturnFalse_WhenVersionIsUpToDate() + { + // Create the mocked HttpClient with the mock response + var mockHttpClient = GetMockHttpClientWithResponse(); + + SetCliVersion("4.0.6610"); + var result = await VersionHelper.IsRunningAnOlderVersion(mockHttpClient); + + result.Should().Be(false); + } + + // Method to return a mocked HttpClient + private HttpClient GetMockHttpClientWithResponse() + { + var mockJsonResponse = @"{ + 'tags': { + 'v4': { + 'release': '4.0', + 'releaseQuality': 'GA', + 'hidden': false + }, + }, + 'releases': { + '4.0': { + 'coreTools': [ + { + 'OS': 'Windows', + 'Architecture': 'x86', + 'downloadLink': 'https://example.com/public/0.0.1/Azure.Functions.Latest.4.0.6610.zip', + 'sha2': 'BB4978D83CFBABAE67D4D720FEC1F1171BE0406B2147EF3FECA476C19ADD9920', + 'size': 'full', + 'default': 'true' + } + ] + } + } + }"; + var mockHandler = new Mock(); + + // Mock the SendAsync method to return a mocked response + mockHandler.Protected() + .Setup>("SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(mockJsonResponse) + }); + + // Return HttpClient with mocked handler + return new HttpClient(mockHandler.Object); + } } -} \ No newline at end of file +}