Skip to content

Commit e91c5e9

Browse files
authored
Added list of test cases (per test plan per test suite) (#147)
Also added a link for each work items per test case
1 parent d5823a8 commit e91c5e9

11 files changed

Lines changed: 221 additions & 19 deletions

File tree

CloneDevOpsTemplate/Controllers/TestController.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,17 @@ public async Task<IActionResult> TestSuites(Guid projectId, int testPlanId)
3535
testSuites = await _testService.GetTestSuitesAsync(projectId, testPlanId) ?? new();
3636
return View(testSuites.Value);
3737
}
38+
39+
public async Task<IActionResult> TestCases(Guid projectId, int testPlanId, int testSuiteId)
40+
{
41+
TestCases testCases = new();
42+
43+
if (!ModelState.IsValid)
44+
{
45+
return View(testCases.Value);
46+
}
47+
48+
testCases = await _testService.GetTestCasesAsync(projectId, testPlanId, testSuiteId) ?? new();
49+
return View(testCases.Value);
50+
}
3851
}

CloneDevOpsTemplate/IServices/ITestService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ public interface ITestService
66
{
77
Task<TestPlans?> GetTestPlansAsync(Guid projectId);
88
Task<TestSuites?> GetTestSuitesAsync(Guid projectId, int testPlanId);
9+
Task<TestCases?> GetTestCasesAsync(Guid projectId, int testPlanId, int testSuiteId);
910
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace CloneDevOpsTemplate.Models;
4+
5+
public class TestCases
6+
{
7+
public int Count { get; set; }
8+
public TestCase[] Value { get; set; } = [];
9+
}
10+
11+
public class TestCase
12+
{
13+
public int Order { get; set; }
14+
public PointAssignment[] PointAssignments { get; set; } = [];
15+
public Project Project { get; set; } = new();
16+
public TestPlanReference TestPlan { get; set; } = new();
17+
public TestSuiteReference TestSuite { get; set; } = new();
18+
public WorkItemDetails WorkItem { get; set; } = new();
19+
}
20+
21+
public class PointAssignment
22+
{
23+
public int Id { get; set; }
24+
public int ConfigurationId { get; set; }
25+
public string ConfigurationName { get; set; } = string.Empty;
26+
public IdentityRef Tester { get; set; } = new();
27+
}
28+
29+
public class WorkItemDetails
30+
{
31+
public int Id { get; set; }
32+
public string Name { get; set; } = string.Empty;
33+
}

CloneDevOpsTemplate/Models/WorkItems.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ public class WorkItem
3333
{
3434
public int Id { get; set; }
3535
public int Rev { get; set; }
36-
public Fields Fields { get; set; } = new();
36+
public WorkItemFields Fields { get; set; } = new();
3737
public Relations[] Relations { get; set; } = [];
3838
public string Url { get; set; } = string.Empty;
3939
}
4040

41-
public class Fields
41+
public class WorkItemFields
4242
{
4343
[JsonPropertyName("System.AreaPath")]
4444
public string SystemAreaPath { get; set; } = string.Empty;
@@ -70,12 +70,14 @@ public class Fields
7070
public IdentityRef SystemAssignedTo { get; set; } = new();
7171
[JsonPropertyName("System.BoardLane")]
7272
public string SystemBoardLane { get; set; } = string.Empty;
73+
[JsonPropertyName("System.Tags")]
74+
public string SystemTags { get; set; } = string.Empty;
75+
[JsonPropertyName("System.Rev")]
76+
public int SystemRev { get; set; }
7377
[JsonPropertyName("Microsoft.VSTS.Scheduling.RemainingWork")]
7478
public float MicrosoftVSTSSchedulingRemainingWork { get; set; }
7579
[JsonPropertyName("Microsoft.VSTS.Common.Priority")]
7680
public float MicrosoftVSTSCommonPriority { get; set; }
77-
[JsonPropertyName("System.Tags")]
78-
public string SystemTags { get; set; } = string.Empty;
7981
[JsonPropertyName("Microsoft.VSTS.TCM.Steps")]
8082
public string MicrosoftVSTSTCMSteps { get; set; } = string.Empty;
8183
[JsonPropertyName("Microsoft.VSTS.TCM.Parameters")]
@@ -86,6 +88,12 @@ public class Fields
8688
public string MicrosoftVSTSTCMAutomationStatus { get; set; } = string.Empty;
8789
[JsonPropertyName("Microsoft.VSTS.Common.AcceptanceCriteria")]
8890
public string MicrosoftVSTSCommonAcceptanceCriteria { get; set; } = string.Empty;
91+
[JsonPropertyName("Microsoft.VSTS.Common.StateChangeDate")]
92+
public DateTime MicrosoftVSTSCommonStateChangeDate { get; set; }
93+
[JsonPropertyName("Microsoft.VSTS.Common.ActivatedBy")]
94+
public IdentityRef MicrosoftVSTSCommonActivatedBy { get; set; } = new();
95+
[JsonPropertyName("Microsoft.VSTS.Common.ActivatedDate")]
96+
public DateTime MicrosoftVSTSCommonActivatedDate { get; set; }
8997
}
9098

9199
public class Relations

CloneDevOpsTemplate/Services/TestService.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,9 @@ public class TestService(IHttpClientFactory httpClientFactory) : ITestService
1616
{
1717
return _client.GetFromJsonAsync<TestSuites>($"{projectId}/_apis/testplan/plans/{testPlanId}/suites");
1818
}
19+
20+
public Task<TestCases?> GetTestCasesAsync(Guid projectId, int testPlanId, int testSuiteId)
21+
{
22+
return _client.GetFromJsonAsync<TestCases>($"{projectId}/_apis/testplan/plans/{testPlanId}/suites/{testSuiteId}/testcase");
23+
}
1924
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
@model TestCase[]
2+
@{
3+
ViewData["Title"] = "Test cases";
4+
}
5+
6+
<h1>Test cases</h1>
7+
8+
@Html.ValidationSummary(false, "", new { @class = "text-danger" })
9+
10+
<table class="table">
11+
<thead>
12+
<tr>
13+
<th>Order</th>
14+
<th>ProjectName</th>
15+
<th>TestPlanName</th>
16+
<th>TestSuiteName</th>
17+
<th>WorkItem</th>
18+
</tr>
19+
</thead>
20+
<tbody>
21+
@foreach (var testCase in Model)
22+
{
23+
<tr>
24+
<td>@testCase.Order</td>
25+
<td>@testCase.Project.Name</td>
26+
<td>@testCase.TestPlan.Name</td>
27+
<td>@testCase.TestSuite.Name</td>
28+
<td>@Html.ActionLink(testCase.WorkItem.Name, "WorkItem", "WorkItems", new { projectId = testCase.Project.Id, workitemId = testCase.WorkItem.Id })</td>
29+
</tr>
30+
}
31+
</tbody>
32+
</table>

CloneDevOpsTemplate/Views/Test/TestSuites.cshtml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<th>Name</th>
1414
<th>Type</th>
1515
<th>ProjectName</th>
16+
<th>TestCases</th>
1617
</tr>
1718
</thead>
1819
<tbody>
@@ -22,6 +23,7 @@
2223
<td>@testSuite.Name</td>
2324
<td>@testSuite.SuiteType</td>
2425
<td>@testSuite.Project.Name</td>
26+
<td>@Html.ActionLink("Test cases", "TestCases", "Test", new { projectId = testSuite.Project.Id, testPlanId = testSuite.Plan.Id, testSuiteId = testSuite.Id })</td>
2527
</tr>
2628
}
2729
</tbody>

CloneDevOpsTemplateTest/Controllers/TestControllerTest.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,60 @@ public async Task TestSuites_ValidModelState_NullTestSuites_ReturnsViewWithEmpty
119119
var viewModel = Assert.IsType<TestSuite[]>(viewResult.Model);
120120
Assert.Empty(viewModel);
121121
}
122+
123+
[Fact]
124+
public async Task TestCases_InvalidModelState_ReturnsViewWithEmptyValue()
125+
{
126+
// Arrange
127+
_controller.ModelState.AddModelError("Error", "Invalid model state");
128+
var projectId = Guid.NewGuid();
129+
var testPlanId = 1;
130+
var testSuiteId = 1;
131+
132+
// Act
133+
var result = await _controller.TestCases(projectId, testPlanId, testSuiteId);
134+
135+
// Assert
136+
var viewResult = Assert.IsType<ViewResult>(result);
137+
var viewModel = Assert.IsType<TestCase[]>(viewResult.Model);
138+
Assert.Empty(viewModel);
139+
}
140+
141+
[Fact]
142+
public async Task TestCases_ValidModelState_ReturnsViewWithTestCasesValue()
143+
{
144+
// Arrange
145+
var projectId = Guid.NewGuid();
146+
var testPlanId = 1;
147+
var testSuiteId = 1;
148+
var testCases = new TestCases { Value = [new TestCase { Order = 1, WorkItem = new WorkItemDetails { Name = "test case" } }] };
149+
_mockTestService.Setup(service => service.GetTestCasesAsync(projectId, testPlanId, testSuiteId))
150+
.ReturnsAsync(testCases);
151+
152+
// Act
153+
var result = await _controller.TestCases(projectId, testPlanId, testSuiteId);
154+
155+
// Assert
156+
var viewResult = Assert.IsType<ViewResult>(result);
157+
Assert.Equal(testCases.Value, viewResult.Model);
158+
}
159+
160+
[Fact]
161+
public async Task TestCases_ValidModelState_NullTestCases_ReturnsViewWithEmptyValue()
162+
{
163+
// Arrange
164+
var projectId = Guid.NewGuid();
165+
var testPlanId = 1;
166+
var testSuiteId = 1;
167+
_mockTestService.Setup(service => service.GetTestCasesAsync(projectId, testPlanId, testSuiteId))
168+
.ReturnsAsync((TestCases)null!);
169+
170+
// Act
171+
var result = await _controller.TestCases(projectId, testPlanId, testSuiteId);
172+
173+
// Assert
174+
var viewResult = Assert.IsType<ViewResult>(result);
175+
var viewModel = Assert.IsType<TestCase[]>(viewResult.Model);
176+
Assert.Empty(viewModel);
177+
}
122178
}

CloneDevOpsTemplateTest/Controllers/WorkItemsControllerTest.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public async Task WorkItems_InvalidModelState_ReturnsEmptyView()
2222
{
2323
// Arrange
2424
Guid projectId = Guid.NewGuid();
25-
_controller.ModelState.AddModelError("Error", "Invalid model state");
25+
_controller.ModelState.AddModelError("Error", "Invalid model state");
2626

2727
// Act
2828
var result = await _controller.WorkItems(projectId, "TestProject");
@@ -51,8 +51,8 @@ public async Task WorkItems_ValidModelState_ReturnsWorkItems()
5151
]
5252
};
5353

54-
var workItem1 = new WorkItem { Id = 1, Fields = new Fields { SystemTitle = "WorkItem1" } };
55-
var workItem2 = new WorkItem { Id = 2, Fields = new Fields { SystemTitle = "WorkItem2" } };
54+
var workItem1 = new WorkItem { Id = 1, Fields = new WorkItemFields { SystemTitle = "WorkItem1" } };
55+
var workItem2 = new WorkItem { Id = 2, Fields = new WorkItemFields { SystemTitle = "WorkItem2" } };
5656
var workItems = new WorkItems
5757
{
5858
Value = [workItem1, workItem2]
@@ -124,7 +124,7 @@ public async Task WorkItem_ValidModelState_ReturnsWorkItem()
124124
var expectedWorkItem = new WorkItem
125125
{
126126
Id = workitemId,
127-
Fields = new Fields { SystemTitle = "Test WorkItem" }
127+
Fields = new WorkItemFields { SystemTitle = "Test WorkItem" }
128128
};
129129

130130
_mockWorkItemService

CloneDevOpsTemplateTest/Services/TestServiceTest.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,56 @@ public async Task GetTestSuitesAsync_ThrowsHttpRequestException_WhenApiResponseI
124124
// Act & Assert
125125
await Assert.ThrowsAsync<HttpRequestException>(() => _testService.GetTestSuitesAsync(projectId, testPlanId));
126126
}
127+
128+
[Fact]
129+
public async Task GetTestCasesAsync_ReturnsTestCases_WhenApiResponseIsSuccessful()
130+
{
131+
// Arrange
132+
var projectId = Guid.NewGuid();
133+
var testPlanId = 1;
134+
var testSuiteId = 1;
135+
var expectedTestCases = new TestCases { Count = 1, Value = [new TestCase { Order = 1, WorkItem = new WorkItemDetails { Name = "test case" } }] };
136+
137+
_httpMessageHandlerMock
138+
.Protected()
139+
.Setup<Task<HttpResponseMessage>>(
140+
"SendAsync",
141+
ItExpr.IsAny<HttpRequestMessage>(),
142+
ItExpr.IsAny<CancellationToken>())
143+
.ReturnsAsync(new HttpResponseMessage
144+
{
145+
StatusCode = HttpStatusCode.OK,
146+
Content = JsonContent.Create(expectedTestCases)
147+
});
148+
149+
// Act
150+
var result = await _testService.GetTestCasesAsync(projectId, testPlanId, testSuiteId);
151+
152+
// Assert
153+
Assert.NotNull(result);
154+
Assert.Equivalent(expectedTestCases, result);
155+
}
156+
157+
[Fact]
158+
public async Task GetTestCasesAsync_ThrowsHttpRequestException_WhenApiResponseIsNotFound()
159+
{
160+
// Arrange
161+
var projectId = Guid.NewGuid();
162+
var testPlanId = 1;
163+
var testSuiteId = 1;
164+
165+
_httpMessageHandlerMock
166+
.Protected()
167+
.Setup<Task<HttpResponseMessage>>(
168+
"SendAsync",
169+
ItExpr.IsAny<HttpRequestMessage>(),
170+
ItExpr.IsAny<CancellationToken>())
171+
.ReturnsAsync(new HttpResponseMessage
172+
{
173+
StatusCode = HttpStatusCode.NotFound
174+
});
175+
176+
// Act & Assert
177+
await Assert.ThrowsAsync<HttpRequestException>(() => _testService.GetTestCasesAsync(projectId, testPlanId, testSuiteId));
178+
}
127179
}

0 commit comments

Comments
 (0)