Skip to content

Commit 9dde196

Browse files
committed
Add some unit tests on parsing json contents
1 parent 1d6e2e2 commit 9dde196

File tree

5 files changed

+149
-23
lines changed

5 files changed

+149
-23
lines changed

GitExtensions.AzureDevOpsCommitMessage.sln

+14
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionIt
1111
.github\workflows\release_plugin.yml = .github\workflows\release_plugin.yml
1212
EndProjectSection
1313
EndProject
14+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GitExtensions.AzureDevOpsCommitMessageTests", "Gitextensions.AzureDevOpsCommitMessageTests\GitExtensions.AzureDevOpsCommitMessageTests.csproj", "{BAA615EE-CB7C-4A14-99D2-632E2D14ECC1}"
15+
EndProject
1416
Global
1517
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1618
Debug|Any CPU = Debug|Any CPU
@@ -33,6 +35,18 @@ Global
3335
{C5137205-996A-41E9-A3EB-F6DB37F6AE21}.Release|x64.Build.0 = Release|Any CPU
3436
{C5137205-996A-41E9-A3EB-F6DB37F6AE21}.Release|x86.ActiveCfg = Release|Any CPU
3537
{C5137205-996A-41E9-A3EB-F6DB37F6AE21}.Release|x86.Build.0 = Release|Any CPU
38+
{BAA615EE-CB7C-4A14-99D2-632E2D14ECC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39+
{BAA615EE-CB7C-4A14-99D2-632E2D14ECC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
40+
{BAA615EE-CB7C-4A14-99D2-632E2D14ECC1}.Debug|x64.ActiveCfg = Debug|Any CPU
41+
{BAA615EE-CB7C-4A14-99D2-632E2D14ECC1}.Debug|x64.Build.0 = Debug|Any CPU
42+
{BAA615EE-CB7C-4A14-99D2-632E2D14ECC1}.Debug|x86.ActiveCfg = Debug|Any CPU
43+
{BAA615EE-CB7C-4A14-99D2-632E2D14ECC1}.Debug|x86.Build.0 = Debug|Any CPU
44+
{BAA615EE-CB7C-4A14-99D2-632E2D14ECC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
45+
{BAA615EE-CB7C-4A14-99D2-632E2D14ECC1}.Release|Any CPU.Build.0 = Release|Any CPU
46+
{BAA615EE-CB7C-4A14-99D2-632E2D14ECC1}.Release|x64.ActiveCfg = Release|Any CPU
47+
{BAA615EE-CB7C-4A14-99D2-632E2D14ECC1}.Release|x64.Build.0 = Release|Any CPU
48+
{BAA615EE-CB7C-4A14-99D2-632E2D14ECC1}.Release|x86.ActiveCfg = Release|Any CPU
49+
{BAA615EE-CB7C-4A14-99D2-632E2D14ECC1}.Release|x86.Build.0 = Release|Any CPU
3650
EndGlobalSection
3751
GlobalSection(SolutionProperties) = preSolution
3852
HideSolutionNode = FALSE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0-windows</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
8+
<IsPackable>false</IsPackable>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
13+
<PackageReference Include="xunit" Version="2.4.2" />
14+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
15+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
16+
<PrivateAssets>all</PrivateAssets>
17+
</PackageReference>
18+
<PackageReference Include="coverlet.collector" Version="3.1.2">
19+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
20+
<PrivateAssets>all</PrivateAssets>
21+
</PackageReference>
22+
</ItemGroup>
23+
24+
<ItemGroup>
25+
<ProjectReference Include="..\src\GitExtensions.AzureDevOpsCommitMessage\GitExtensions.AzureDevOpsCommitMessage.csproj" />
26+
</ItemGroup>
27+
28+
<ItemGroup>
29+
<Reference Include="ResourceManager">
30+
<HintPath>..\..\gitextensions.shared\ResourceManager.dll</HintPath>
31+
</Reference>
32+
</ItemGroup>
33+
34+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using GitExtensions.AzureDevOpsCommitMessage;
2+
using static GitExtensions.AzureDevOpsCommitMessage.Plugin;
3+
4+
namespace GitExtensions.AzureDevOpsCommitMessageTests
5+
{
6+
public class PluginTests
7+
{
8+
// Taken from https://learn.microsoft.com/fr-fr/rest/api/azure/devops/wit/work-items/get-work-item?view=azure-devops-rest-7.0&tabs=HTTP#get-work-item
9+
private const string WorkItemContentExample =
10+
"{\r\n \"id\": 12,\r\n \"rev\": 3,\r\n \"fields\": {\r\n \"System.AreaPath\": \"MyAgilePrj2\",\r\n \"System.TeamProject\": \"MyAgilePrj2\",\r\n \"System.IterationPath\": \"MyAgilePrj2\\\\Iteration 1\",\r\n \"System.WorkItemType\": \"User Story\",\r\n \"System.State\": \"Active\",\r\n \"System.Reason\": \"Implementation started\",\r\n \"System.AssignedTo\": {\r\n \"displayName\": \"Jamal Hartnett\",\r\n \"url\": \"https://vssps.dev.azure.com/fabrikam/_apis/Identities/d291b0c4-a05c-4ea6-8df1-4b41d5f39eff\",\r\n \"_links\": {\r\n \"avatar\": {\r\n \"href\": \"https://dev.azure.com/mseng/_apis/GraphProfile/MemberAvatars/aad.YTkzODFkODYtNTYxYS03ZDdiLWJjM2QtZDUzMjllMjM5OTAz\"\r\n }\r\n },\r\n \"id\": \"d291b0c4-a05c-4ea6-8df1-4b41d5f39eff\",\r\n \"uniqueName\": \"[email protected]\",\r\n \"imageUrl\": \"https://dev.azure.com/fabrikam/_api/_common/identityImage?id=d291b0c4-a05c-4ea6-8df1-4b41d5f39eff\",\r\n \"descriptor\": \"aad.YTkzODFkODYtNTYxYS03ZDdiLWJjM2QtZDUzMjllMjM5OTAz\"\r\n },\r\n \"System.CreatedDate\": \"2017-09-04T06:11:59.05Z\",\r\n \"System.CreatedBy\": {\r\n \"displayName\": \"Jamal Hartnett\",\r\n \"url\": \"https://vssps.dev.azure.com/fabrikam/_apis/Identities/d291b0c4-a05c-4ea6-8df1-4b41d5f39eff\",\r\n \"_links\": {\r\n \"avatar\": {\r\n \"href\": \"https://dev.azure.com/mseng/_apis/GraphProfile/MemberAvatars/aad.YTkzODFkODYtNTYxYS03ZDdiLWJjM2QtZDUzMjllMjM5OTAz\"\r\n }\r\n },\r\n \"id\": \"d291b0c4-a05c-4ea6-8df1-4b41d5f39eff\",\r\n \"uniqueName\": \"[email protected]\",\r\n \"imageUrl\": \"https://dev.azure.com/fabrikam/_api/_common/identityImage?id=d291b0c4-a05c-4ea6-8df1-4b41d5f39eff\",\r\n \"descriptor\": \"aad.YTkzODFkODYtNTYxYS03ZDdiLWJjM2QtZDUzMjllMjM5OTAz\"\r\n },\r\n \"System.ChangedDate\": \"2017-10-04T23:32:02.18Z\",\r\n \"System.ChangedBy\": {\r\n \"displayName\": \"Jamal Hartnett\",\r\n \"url\": \"https://vssps.dev.azure.com/fabrikam/_apis/Identities/d291b0c4-a05c-4ea6-8df1-4b41d5f39eff\",\r\n \"_links\": {\r\n \"avatar\": {\r\n \"href\": \"https://dev.azure.com/mseng/_apis/GraphProfile/MemberAvatars/aad.YTkzODFkODYtNTYxYS03ZDdiLWJjM2QtZDUzMjllMjM5OTAz\"\r\n }\r\n },\r\n \"id\": \"d291b0c4-a05c-4ea6-8df1-4b41d5f39eff\",\r\n \"uniqueName\": \"[email protected]\",\r\n \"imageUrl\": \"https://dev.azure.com/fabrikam/_api/_common/identityImage?id=d291b0c4-a05c-4ea6-8df1-4b41d5f39eff\",\r\n \"descriptor\": \"aad.YTkzODFkODYtNTYxYS03ZDdiLWJjM2QtZDUzMjllMjM5OTAz\"\r\n },\r\n \"System.Title\": \"Epic 2\",\r\n \"Microsoft.VSTS.Common.StateChangeDate\": \"2017-10-04T23:32:01.6Z\",\r\n \"Microsoft.VSTS.Common.ActivatedDate\": \"2017-10-04T23:32:01.6Z\",\r\n \"Microsoft.VSTS.Common.ActivatedBy\": \"Jamal Hartnett <[email protected]>\",\r\n \"Microsoft.VSTS.Common.Priority\": 2,\r\n \"Microsoft.VSTS.Common.ValueArea\": \"Business\",\r\n \"System.Tags\": \"client; sample; teamservices\"\r\n },\r\n \"_links\": {\r\n \"self\": {\r\n \"href\": \"https://dev.azure.com/fabrikam/_apis/wit/workItems/12\"\r\n },\r\n \"workItemUpdates\": {\r\n \"href\": \"https://dev.azure.com/fabrikam/_apis/wit/workItems/12/updates\"\r\n },\r\n \"workItemRevisions\": {\r\n \"href\": \"https://dev.azure.com/fabrikam/_apis/wit/workItems/12/revisions\"\r\n },\r\n \"workItemHistory\": {\r\n \"href\": \"https://dev.azure.com/fabrikam/_apis/wit/workItems/12/history\"\r\n },\r\n \"html\": {\r\n \"href\": \"https://dev.azure.com/fabrikam/web/wi.aspx?pcguid=20cda608-32f0-4e6e-9b7c-8def7b38d15a&id=12\"\r\n },\r\n \"workItemType\": {\r\n \"href\": \"https://dev.azure.com/fabrikam/54332e84-3d54-4c67-9bd0-0e88a9849330/_apis/wit/workItemTypes/User%20Story\"\r\n },\r\n \"fields\": {\r\n \"href\": \"https://dev.azure.com/fabrikam/_apis/wit/fields\"\r\n }\r\n },\r\n \"url\": \"https://dev.azure.com/fabrikam/_apis/wit/workItems/12\"\r\n}";
11+
12+
private readonly TestAccessor _sut = new Plugin().GetTestAccessor();
13+
14+
[Fact]
15+
public void GetWorkItems()
16+
{
17+
var workItems = _sut.GetWorkItems("{\r\n \"workItems\": [{\r\n \"id\": 12,\r\n \"url\": \"https://dev.azure.com/fabrikam/_apis/wit/workItems/12\"\r\n}, {\r\n \"id\": 25,\r\n \"url\": \"https://dev.azure.com/fabrikam/_apis/wit/workItems/25\"\r\n}]}");
18+
19+
Assert.Equal(new List<(string id, string url)> { (id: "12", url: "https://dev.azure.com/fabrikam/_apis/wit/workItems/12"), (id: "25", url: "https://dev.azure.com/fabrikam/_apis/wit/workItems/25") }, workItems);
20+
}
21+
22+
[Theory]
23+
[InlineData("{id}/{System.Title}", "134/Epic 2")]
24+
[InlineData("{id}/{System.Title}/{System.State}", "134/Epic 2/Active")]
25+
[InlineData("{id}/{System.Title}/{System.CreatedBy|displayName}", "134/Epic 2/Jamal Hartnett")]
26+
[InlineData("{id}/{System.Title}/{System.AssignedTo|_links|avatar|href}", "134/Epic 2/https://dev.azure.com/mseng/_apis/GraphProfile/MemberAvatars/aad.YTkzODFkODYtNTYxYS03ZDdiLWJjM2QtZDUzMjllMjM5OTAz")]
27+
public void GetCommitTemplateFromWorkItemData(string template, string value)
28+
{
29+
var commitTemplate = _sut.GetCommitTemplateFromWorkItemData("134", template, WorkItemContentExample);
30+
31+
Assert.Equal("134: Epic 2", commitTemplate.Title);
32+
Assert.Equal(value, commitTemplate.Text);
33+
}
34+
35+
[Theory]
36+
[InlineData("{id}/{System.Title}/{System.AssignedTo|notExisting}", "134/Epic 2/{System.AssignedTo|notExisting}")]
37+
[InlineData("{id}/{System.Title}/{System.NotExisting}", "134/Epic 2/{System.NotExisting}")]
38+
public void GetCommitTemplateFromWorkItemData_PatternNotFoundIsNotReplaced(string template, string value)
39+
{
40+
var plugin = new Plugin().GetTestAccessor();
41+
var commitTemplate = _sut.GetCommitTemplateFromWorkItemData("134", template, WorkItemContentExample);
42+
43+
Assert.Equal("134: Epic 2", commitTemplate.Title);
44+
Assert.Equal(value, commitTemplate.Text);
45+
}
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
global using Xunit;

src/GitExtensions.AzureDevOpsCommitMessage/Plugin.cs

+53-23
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
using System;
22
using System.Collections.Generic;
33
using System.ComponentModel.Composition;
4-
using System.Diagnostics;
54
using System.Linq;
65
using System.Net.Http;
76
using System.Net.Http.Headers;
7+
using System.Runtime.CompilerServices;
88
using System.Text;
99
using System.Text.RegularExpressions;
1010
using System.Threading.Tasks;
@@ -18,6 +18,8 @@
1818
using Newtonsoft.Json.Linq;
1919
using ResourceManager;
2020

21+
[assembly: InternalsVisibleTo("Gitextensions.AzureDevOpsCommitMessageTests")]
22+
2123
namespace GitExtensions.AzureDevOpsCommitMessage
2224
{
2325
[Export(typeof(IGitPlugin))]
@@ -365,24 +367,17 @@ private async Task<IList<CommitTemplate>> GetCommitTemplatesAsync(HttpClient cli
365367
var response = await client.SendAsync(request).ConfigureAwait(true);
366368
if (!response.IsSuccessStatusCode)
367369
{
368-
return new[] { new CommitTemplate($"Call error", response.ReasonPhrase) };
370+
return new[] { new CommitTemplate("Call error", response.ReasonPhrase) };
369371
}
370372

371373
try
372374
{
373-
var commitTemplates = new List<CommitTemplate>();
374375
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
375-
var workItems = JObject.Parse(responseContent)["workItems"];
376-
if (workItems == null)
377-
{
378-
return commitTemplates;
379-
}
380-
381-
foreach (JToken workitem in workItems)
376+
var commitTemplates = new List<CommitTemplate>();
377+
foreach (var workitem in GetWorkitems(responseContent))
382378
{
383-
commitTemplates.Add(await GetWorkItemAsync(client, workitem["id"].ToString(), workitem["url"].ToString(), stringTemplate));
379+
commitTemplates.Add(await GetWorkItemAsync(client, workitem.id, workitem.url, stringTemplate));
384380
}
385-
386381
return commitTemplates;
387382
}
388383
catch (Exception e)
@@ -397,8 +392,18 @@ private async Task<IList<CommitTemplate>> GetCommitTemplatesAsync(HttpClient cli
397392
}
398393
}
399394

400-
private async Task<CommitTemplate> GetWorkItemAsync(HttpClient client, string id, string url,
401-
string template)
395+
private IEnumerable<(string id, string url)> GetWorkitems(string jsonWorkitems)
396+
{
397+
var workItems = JObject.Parse(jsonWorkitems)["workItems"];
398+
if (workItems == null)
399+
{
400+
return Enumerable.Empty<(string id, string url)>();
401+
}
402+
403+
return workItems.Select(w => (w["id"].ToString(), w["url"].ToString()));
404+
}
405+
406+
private async Task<CommitTemplate> GetWorkItemAsync(HttpClient client, string id, string url, string template)
402407
{
403408
using (var request = new HttpRequestMessage(new HttpMethod("GET"), url))
404409
{
@@ -407,14 +412,8 @@ private async Task<CommitTemplate> GetWorkItemAsync(HttpClient client, string id
407412
{
408413
try
409414
{
410-
var responseContent = await response.Content.ReadAsStringAsync();
411-
var workItemData = JObject.Parse(responseContent)["fields"];
412-
if (_btnPreview != null)
413-
{
414-
_allFieldsAndValues = ExtractAllFields(workItemData);
415-
}
416-
417-
return new CommitTemplate(PopulateTemplate("{id}: {System.Title}", id, workItemData), PopulateTemplate(template, id, workItemData));
415+
var jsonWorkitem = await response.Content.ReadAsStringAsync();
416+
return GetCommitTemplateFromWorkitemData(id, template, jsonWorkitem);
418417
}
419418
catch (Exception e)
420419
{
@@ -426,6 +425,18 @@ private async Task<CommitTemplate> GetWorkItemAsync(HttpClient client, string id
426425
}
427426
}
428427

428+
private CommitTemplate GetCommitTemplateFromWorkitemData(string id, string template, string jsonWorkitem)
429+
{
430+
var workItemData = JObject.Parse(jsonWorkitem)["fields"];
431+
if (_btnPreview != null)
432+
{
433+
_allFieldsAndValues = ExtractAllFields(workItemData);
434+
}
435+
436+
return new CommitTemplate(PopulateTemplate("{id}: {System.Title}", id, workItemData),
437+
PopulateTemplate(template, id, workItemData));
438+
}
439+
429440
private string Elide(string value)
430441
{
431442
if (string.IsNullOrEmpty(value))
@@ -560,7 +571,7 @@ public static string ExtractTextFromHtml(string html)
560571
return s;
561572
}
562573

563-
private class CommitTemplate
574+
public class CommitTemplate
564575
{
565576
public string Title { get; }
566577
public string Text { get; }
@@ -571,5 +582,24 @@ public CommitTemplate(string title, string text)
571582
Text = text;
572583
}
573584
}
585+
586+
587+
internal TestAccessor GetTestAccessor()
588+
=> new(this);
589+
590+
internal readonly struct TestAccessor
591+
{
592+
private readonly Plugin _plugin;
593+
594+
public TestAccessor(Plugin plugin)
595+
{
596+
_plugin = plugin;
597+
}
598+
599+
public CommitTemplate GetCommitTemplateFromWorkItemData(string id, string template, string jsonWorkitem)
600+
=> _plugin.GetCommitTemplateFromWorkitemData(id, template, jsonWorkitem);
601+
public IEnumerable<(string id, string url)> GetWorkItems(string jsonWorkitem)
602+
=> _plugin.GetWorkitems(jsonWorkitem);
603+
}
574604
}
575605
}

0 commit comments

Comments
 (0)