diff --git a/.github/workflows/ci+cd.yml b/.github/workflows/ci+cd.yml index b7a8733c..15a61084 100644 --- a/.github/workflows/ci+cd.yml +++ b/.github/workflows/ci+cd.yml @@ -21,10 +21,14 @@ jobs: build-and-test: runs-on: ${{ matrix.os }} + timeout-minutes: 360 strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] + concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.os }} + cancel-in-progress: true steps: - name: Checkout diff --git a/src/LibChorus/VcsDrivers/Mercurial/HgResumeRestApiServer.cs b/src/LibChorus/VcsDrivers/Mercurial/HgResumeRestApiServer.cs index 1600acf0..5001c6d8 100644 --- a/src/LibChorus/VcsDrivers/Mercurial/HgResumeRestApiServer.cs +++ b/src/LibChorus/VcsDrivers/Mercurial/HgResumeRestApiServer.cs @@ -33,10 +33,15 @@ public HgResumeApiResponse Execute(string method, HgResumeApiParameters request, [Obsolete] public string UserName => null; [Obsolete] public string Password => null; - public HgResumeApiResponse Execute(string method, HgResumeApiParameters parameters, byte[] contentToSend, int secondsBeforeTimeout) + public static string FormatUrl(Uri uri, string method, HgResumeApiParameters parameters) { string queryString = parameters.BuildQueryString(); - Url = string.Format("{0}://{1}/api/v{2}/{3}?{4}", _url.Scheme, _url.Host, ApiVersion, method, queryString); + return $"{uri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped)}/api/v{ApiVersion}/{method}{queryString}"; + } + + public HgResumeApiResponse Execute(string method, HgResumeApiParameters parameters, byte[] contentToSend, int secondsBeforeTimeout) + { + Url = FormatUrl(_url, method, parameters); var req = (HttpWebRequest) WebRequest.Create(Url); req.UserAgent = $"HgResume v{ApiVersion}"; req.PreAuthenticate = true; diff --git a/src/LibChorus/VcsDrivers/Mercurial/IApiServer.cs b/src/LibChorus/VcsDrivers/Mercurial/IApiServer.cs index 93087a9f..974594d5 100644 --- a/src/LibChorus/VcsDrivers/Mercurial/IApiServer.cs +++ b/src/LibChorus/VcsDrivers/Mercurial/IApiServer.cs @@ -43,9 +43,13 @@ public HgResumeApiParameters() // used only by getRevisions API public int Quantity { get; set; } + /// + /// returns a query string, prefixed with a '?' unless there are no parameters + /// + /// public string BuildQueryString() { - string query = ""; + string query = "?"; if (StartOfWindow >= 0) { query += String.Format("offset={0}&", StartOfWindow); @@ -77,6 +81,10 @@ public string BuildQueryString() { query += String.Format("repoId={0}&", RepoId); } + if (query == "?") + { + return ""; + } return query.TrimEnd('&'); } } diff --git a/src/LibChorusTests/VcsDrivers/Mercurial/HgResumeRestApiServerTests.cs b/src/LibChorusTests/VcsDrivers/Mercurial/HgResumeRestApiServerTests.cs index eb07038b..8d8f2904 100644 --- a/src/LibChorusTests/VcsDrivers/Mercurial/HgResumeRestApiServerTests.cs +++ b/src/LibChorusTests/VcsDrivers/Mercurial/HgResumeRestApiServerTests.cs @@ -1,3 +1,4 @@ +using System; using Chorus.VcsDrivers.Mercurial; using NUnit.Framework; @@ -21,5 +22,36 @@ public void Constructor_LanguageForgeUrl_IdentityAndProjectIdSetCorrectly() Assert.That(api.Host, Is.EqualTo("hg.languageforge.org")); Assert.That(api.ProjectId, Is.EqualTo("kyu-dictionary")); } + + [Test] + public void FormatUrl_FormatsCorrectlyWithEmptyParameters() + { + Assert.That(HgResumeRestApiServer.FormatUrl( + new Uri("https://hg.languageforge.org:1234/projects/kyu-dictionary"), + "foo", + new HgResumeApiParameters()), + Is.EqualTo("https://hg.languageforge.org:1234/api/v03/foo")); + } + + [Test] + public void FormatUrl_FormatsCorrectlyWithParameters() + { + //this test is overly specific as the order of the parameters is not important, however this is much simpler to write. + Assert.That(HgResumeRestApiServer.FormatUrl( + new Uri("https://hg.languageforge.org:1234/projects/kyu-dictionary"), + "foo", + new HgResumeApiParameters + { + StartOfWindow = 10, + ChunkSize = 20, + BundleSize = 30, + Quantity = 40, + TransId = "transId", + BaseHashes = new[] { "baseHash1", "baseHash2" }, + RepoId = "repoId" + }), + Is.EqualTo( + "https://hg.languageforge.org:1234/api/v03/foo?offset=10&chunkSize=20&bundleSize=30&quantity=40&transId=transId&baseHashes[]=baseHash1&baseHashes[]=baseHash2&repoId=repoId")); + } } }