Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update VersionHelper to utilize GH release tag for version check #4224

Merged
merged 6 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 61 additions & 25 deletions src/Azure.Functions.Cli/Helpers/VersionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,6 +18,14 @@ namespace Azure.Functions.Cli.Helpers
{
internal class VersionHelper
{
internal static string _cliVersion = Constants.CliVersion;

// This method is created only for testing
internal static void SetCliVersion(string version)
{
_cliVersion = version;
}

public static async Task<string> RunAsync(Task<bool> isRunningOlderVersion = null)
{
isRunningOlderVersion ??= IsRunningAnOlderVersion();
Expand All @@ -36,35 +45,35 @@ public static async Task<string> RunAsync(Task<bool> 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<bool> IsRunningAnOlderVersion()
public static async Task<bool> IsRunningAnOlderVersion(HttpClient client = null)
VineethReyya marked this conversation as resolved.
Show resolved Hide resolved
{
try
{
var client = new System.Net.Http.HttpClient
using (client ??= new System.Net.Http.HttpClient{Timeout = TimeSpan.FromSeconds(1)})
{
Timeout = TimeSpan.FromSeconds(1)
};
var response = await client.GetAsync(Constants.CoreToolsVersionsFeedUrl);
var content = await response.Content.ReadAsStringAsync();
var data = JsonConvert.DeserializeObject<CliFeed>(content);
IEnumerable releases = ((IEnumerable) data.Releases);
var releaseList = new List<ReleaseSummary>();
foreach (var item in releases)
{
var jProperty = (Newtonsoft.Json.Linq.JProperty)item;
var releaseDetail = JsonConvert.DeserializeObject<ReleaseDetail>(jProperty.Value.ToString());
releaseList.Add(new ReleaseSummary() { Release = jProperty.Name, ReleaseDetail = releaseDetail.ReleaseList.FirstOrDefault() });
}
var response = await client.GetAsync(Constants.CoreToolsVersionsFeedUrl);
var content = await response.Content.ReadAsStringAsync();
var data = JsonConvert.DeserializeObject<CliFeed>(content);
IEnumerable releases = ((IEnumerable)data.Releases);
var releaseList = new List<ReleaseSummary>();
foreach (var item in releases)
{
var jProperty = (Newtonsoft.Json.Linq.JProperty)item;
var releaseDetail = JsonConvert.DeserializeObject<ReleaseDetail>(jProperty.Value.ToString());
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)
{
return true;
}
if (!string.IsNullOrEmpty(latestCoreToolsAssemblyZipFile) &&
!latestCoreToolsAssemblyZipFile.Contains($"{_cliVersion}.zip"))
{
return true;
}

return false;
return false;
}

}
catch (Exception)
{
Expand Down Expand Up @@ -159,8 +168,26 @@ 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
Expand All @@ -183,16 +210,25 @@ public string CoreToolsReleaseNumber
return uri.Segments[2].Replace("/", string.Empty);
}
}

public CoreToolsRelease ReleaseDetail { get; set; }

public string CoreToolsAssemblyZipFile
{
get
{
return _coreToolsAssemblyZipFile;
}
}
}

private class ReleaseDetail
internal class ReleaseDetail
{
[JsonProperty("coreTools")]
public IList<CoreToolsRelease> ReleaseList { get; set; }
}

private class CoreToolsRelease
internal class CoreToolsRelease
{
[JsonProperty("OS")]
public string Os { get; set; }
Expand Down
98 changes: 98 additions & 0 deletions test/Azure.Functions.Cli.Tests/E2E/VersionTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
using System;
using System.Net.Http;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Azure.Functions.Cli.Common;
using Azure.Functions.Cli.Helpers;
using Azure.Functions.Cli.Tests.E2E.Helpers;
using Moq.Protected;
using Moq;
using Xunit;
using Xunit.Abstractions;
using static Azure.Functions.Cli.Helpers.VersionHelper;
using FluentAssertions;

namespace Azure.Functions.Cli.Tests.E2E
{
Expand All @@ -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<HttpMessageHandler>();

// Mock the SendAsync method to return a mocked response
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(mockJsonResponse)
});

// Return HttpClient with mocked handler
return new HttpClient(mockHandler.Object) { Timeout = TimeSpan.FromSeconds(1) };
}
}
}
Loading