Skip to content

Commit a8ece86

Browse files
author
zelodyc
committed
Refactor tests. Add JsonArrayHandler / JsonHandler to simplify tests using ConnectionStub. Added check of requests number for multiple fetches in batches. Correct handling of and paramaters (support any order). Add multiple flavors of agile boards to tests.
1 parent 38d67fe commit a8ece86

File tree

10 files changed

+683
-135
lines changed

10 files changed

+683
-135
lines changed
Lines changed: 10 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Net.Http;
3-
using System.Threading;
43
using System.Threading.Tasks;
54

65
namespace YouTrackSharp.Tests.Infrastructure
@@ -10,18 +9,19 @@ namespace YouTrackSharp.Tests.Infrastructure
109
/// </summary>
1110
public class ConnectionStub : Connection
1211
{
13-
private Func<HttpRequestMessage, HttpResponseMessage> ExecuteRequest { get; }
14-
12+
private readonly HttpClientHandler _handler;
1513
private TimeSpan TimeOut => TimeSpan.FromSeconds(100);
1614

1715
/// <summary>
1816
/// Creates an instance of <see cref="ConnectionStub"/> with give response delegate
1917
/// </summary>
20-
/// <param name="executeRequest">Request delegate</param>
21-
public ConnectionStub(Func<HttpRequestMessage, HttpResponseMessage> executeRequest) : base(
22-
"http://fake.connection.com/")
18+
/// <param name="handler">
19+
/// <see cref="HttpClientHandler"/> to associate to this connection.
20+
/// This can be used to pass a stub handler for testing purposes.
21+
/// </param>
22+
public ConnectionStub(HttpClientHandler handler) : base("http://fake.connection.com/")
2323
{
24-
ExecuteRequest = executeRequest;
24+
_handler = handler;
2525
}
2626

2727
/// <summary>
@@ -31,42 +31,11 @@ public ConnectionStub(Func<HttpRequestMessage, HttpResponseMessage> executeReque
3131
/// <returns><see cref="HttpClient"/> configured to return a predefined message and HTTP status</returns>
3232
public override Task<HttpClient> GetAuthenticatedHttpClient()
3333
{
34-
return Task.FromResult(CreateClient());
35-
}
36-
37-
private HttpClient CreateClient()
38-
{
39-
HttpClient httpClient = new HttpClient(new HttpClientHandlerStub(ExecuteRequest));
34+
HttpClient httpClient = new HttpClient(_handler);
4035
httpClient.BaseAddress = ServerUri;
4136
httpClient.Timeout = TimeOut;
42-
43-
return httpClient;
44-
}
45-
}
46-
47-
/// <summary>
48-
/// <see cref="HttpClientHandler"/> mock, that returns a predefined reply and HTTP status code.
49-
/// </summary>
50-
public class HttpClientHandlerStub : HttpClientHandler
51-
{
52-
private Func<HttpRequestMessage, HttpResponseMessage> ExecuteRequest { get; }
53-
54-
/// <summary>
55-
/// Creates an <see cref="HttpClientHandlerStub"/> instance that delegates HttpRequestMessages
56-
/// </summary>
57-
/// <param name="executeRequest">Request delegate</param>
58-
public HttpClientHandlerStub(Func<HttpRequestMessage, HttpResponseMessage> executeRequest)
59-
{
60-
ExecuteRequest = executeRequest;
61-
}
62-
63-
/// <inheritdoc />
64-
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
65-
CancellationToken cancellationToken)
66-
{
67-
HttpResponseMessage reply = ExecuteRequest?.Invoke(request);
68-
69-
return Task.FromResult(reply);
37+
38+
return Task.FromResult<HttpClient>(httpClient);
7039
}
7140
}
7241
}

tests/YouTrackSharp.Tests/Infrastructure/Connections.cs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,6 @@ public static string ServerUrl
2323

2424
public static Connection Demo3Token =>
2525
new BearerTokenConnection(ServerUrl, "perm:ZGVtbzM=.WW91VHJhY2tTaGFycA==.L04RdcCnjyW2UPCVg1qyb6dQflpzFy", ConfigureTestsHandler);
26-
27-
public static Connection ConnectionStub(string content, HttpStatusCode status = HttpStatusCode.OK)
28-
{
29-
HttpResponseMessage response = new HttpResponseMessage(status);
30-
response.Content = new StringContent(content);
31-
32-
return new ConnectionStub(_ => response);
33-
}
3426

3527
public static class TestData
3628
{
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Net;
4+
using System.Net.Http;
5+
using System.Text.RegularExpressions;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
9+
namespace YouTrackSharp.Tests.Infrastructure
10+
{
11+
/// <summary>
12+
/// This handler is used to return a json array from a range of give json strings, created from the $top and $skip
13+
/// parameters of the HTTP request.
14+
/// This handler can be used to simulate a server returning json arrays in batches.
15+
/// </summary>
16+
public class JsonArrayHandler : HttpClientHandler
17+
{
18+
private readonly ICollection<string> _jsonObjects;
19+
public int RequestsReceived { get; private set; }
20+
21+
/// <summary>
22+
/// Creates an instance of <see cref="JsonArrayHandler"/>
23+
/// </summary>
24+
/// <param name="jsonObjects">List of json objects that this instance will pick from</param>
25+
public JsonArrayHandler(params string[] jsonObjects)
26+
{
27+
_jsonObjects = jsonObjects;
28+
RequestsReceived = 0;
29+
}
30+
31+
/// <inheritdoc />
32+
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
33+
{
34+
RequestsReceived++;
35+
36+
GetRequestedRange(request, _jsonObjects.Count, out int skip, out int count);
37+
string json = GetJsonArray(_jsonObjects, skip, count);
38+
39+
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
40+
response.Content = new StringContent(json);
41+
42+
return Task.FromResult(response);
43+
}
44+
45+
/// <summary>
46+
/// Creates a JSON array from a range of the given json strings.
47+
/// This allows to simulate returning a total number of elements, in batches.
48+
/// </summary>
49+
/// <param name="jsonObjects">JSON objects from which the JSON array will be created</param>
50+
/// <param name="skip">Number of items to skip</param>
51+
/// <param name="count">Number of items to return</param>
52+
/// <returns>
53+
/// Json array
54+
/// </returns>
55+
private string GetJsonArray(ICollection<string> jsonObjects, int skip, int count)
56+
{
57+
IEnumerable<string> jsonObjectRange = jsonObjects.Skip(skip).Take(count);
58+
string json = $"[{string.Join(",", jsonObjectRange)}]";
59+
60+
return json;
61+
}
62+
63+
/// <summary>
64+
/// Parses the $skip and $top parameters from a Youtrack REST request URI, and computes the requested range
65+
/// of objects to return (capped by <see cref="maxIndex"/>).
66+
/// </summary>
67+
/// <param name="request">HTTP request</param>
68+
/// <param name="maxIndex">Max index (range will not go beyond that index, even if $skip + $top is greater</param>
69+
/// <param name="skip">Number of items to skip</param>
70+
/// <param name="count">Number of items to return</param>
71+
/// <returns>Range computed from request's $skip and $top</returns>
72+
private void GetRequestedRange(HttpRequestMessage request, int maxIndex, out int skip, out int count)
73+
{
74+
string requestUri = request.RequestUri.ToString();
75+
76+
Match match = Regex.Match(requestUri, "&\\$top=(?<top>[0-9]+)(&\\$skip=(?<skip>[0-9]+))?|&\\$skip=(?<skip>[0-9]+)(&\\$top=(?<top>[0-9]+))?");
77+
78+
count = maxIndex;
79+
if (match.Groups.ContainsKey("top") && match.Groups["top"].Success)
80+
{
81+
count = int.Parse(match.Groups["top"].Value);
82+
}
83+
84+
skip = 0;
85+
if (match.Groups.ContainsKey("skip") && match.Groups["skip"].Success)
86+
{
87+
skip = int.Parse(match.Groups["skip"].Value);
88+
}
89+
}
90+
}
91+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System.Net;
2+
using System.Net.Http;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
6+
namespace YouTrackSharp.Tests.Infrastructure
7+
{
8+
/// <summary>
9+
/// This handler returns a predefined json string on every request.
10+
/// </summary>
11+
public class JsonHandler : HttpClientHandler
12+
{
13+
private readonly string _json;
14+
15+
/// <summary>
16+
/// Creates an instance of <see cref="JsonHandler"/>
17+
/// </summary>
18+
/// <param name="json">Json string that will be returned upon each request</param>
19+
public JsonHandler(string json)
20+
{
21+
_json = json;
22+
}
23+
24+
/// <inheritdoc />
25+
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
26+
{
27+
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
28+
response.Content = new StringContent(_json);
29+
30+
return Task.FromResult(response);
31+
}
32+
}
33+
}

tests/YouTrackSharp.Tests/Integration/Agiles/AgileServiceTest.cs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,12 @@ public partial class AgileServiceTest
88
{
99
private static string DemoBoardId => "108-2";
1010
private static string DemoBoardNamePrefix => "Test Board597fb561-ea1f-4095-9636-859ae4439605";
11-
11+
1212
private static string DemoSprintId => "109-2";
1313
private static string DemoSprintName => "First sprint";
1414

15-
private static string SingleAgileJson => GetTextResource("YouTrackSharp.Tests.Resources.CompleteAgile.json");
16-
17-
private static string GetAgileJsonArray(int count)
18-
{
19-
string agileJson = SingleAgileJson;
20-
21-
string agilesJson = string.Join(",", Enumerable.Range(0, count).Select(_ => agileJson));
22-
23-
return $"[{agilesJson}]";
24-
}
15+
private static string FullAgile01 => GetTextResource("YouTrackSharp.Tests.Resources.FullAgile01.json");
16+
private static string FullAgile02 => GetTextResource("YouTrackSharp.Tests.Resources.FullAgile02.json");
2517

2618
private static string GetTextResource(string name)
2719
{

tests/YouTrackSharp.Tests/Integration/Agiles/GetAgiles.cs

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -63,43 +63,67 @@ await Assert.ThrowsAsync<UnauthorizedConnectionException>(
6363
}
6464

6565
[Fact]
66-
public async Task Full_Agile_Json_Gets_Deserialized_Successfully()
66+
public async Task Mock_Connection_Returns_Full_Agile_01()
6767
{
6868
// Arrange
69-
IAgileService agileService = Connections.ConnectionStub(GetAgileJsonArray(1)).CreateAgileService();
69+
string[] strings = { FullAgile01, FullAgile02 };
70+
71+
JsonArrayHandler handler = new JsonArrayHandler(strings);
72+
ConnectionStub connection = new ConnectionStub(handler);
73+
74+
IAgileService agileService = connection.CreateAgileService();
7075

7176
// Act
7277
ICollection<Agile> result = await agileService.GetAgileBoards(true);
7378

7479
// Assert
7580
Assert.NotNull(result);
76-
Assert.NotEmpty(result);
77-
78-
Agile demoBoard = result.FirstOrDefault();
79-
Assert.NotNull(demoBoard);
80-
Assert.Equal(DemoBoardId, demoBoard.Id);
81-
Assert.Equal(DemoBoardNamePrefix, demoBoard.Name);
82-
83-
Assert.NotNull(demoBoard.ColumnSettings);
84-
Assert.NotNull(demoBoard.Projects);
85-
Assert.NotNull(demoBoard.Sprints);
86-
Assert.NotNull(demoBoard.Projects);
87-
Assert.NotNull(demoBoard.Sprints);
88-
Assert.NotNull(demoBoard.Status);
89-
Assert.NotNull(demoBoard.ColumnSettings);
90-
Assert.NotNull(demoBoard.CurrentSprint);
91-
Assert.NotNull(demoBoard.EstimationField);
92-
Assert.NotNull(demoBoard.SprintsSettings);
93-
Assert.NotNull(demoBoard.SwimlaneSettings);
94-
Assert.NotNull(demoBoard.ColorCoding);
95-
Assert.NotNull(demoBoard.UpdateableBy);
96-
Assert.NotNull(demoBoard.VisibleFor);
97-
Assert.NotNull(demoBoard.OriginalEstimationField);
98-
99-
Sprint sprint = demoBoard.Sprints.FirstOrDefault();
100-
Assert.NotNull(sprint);
101-
Assert.Equal(DemoSprintId, sprint.Id);
102-
Assert.Equal(DemoSprintName, sprint.Name);
81+
Assert.Equal(2, result.Count);
82+
83+
foreach (Agile agile in result)
84+
{
85+
Assert.NotNull(agile);
86+
87+
Assert.True("109-1".Equals(agile.Id) || "109-2".Equals(agile.Id));
88+
89+
90+
91+
Assert.NotNull(agile.ColumnSettings);
92+
Assert.NotNull(agile.Projects);
93+
Assert.NotNull(agile.Sprints);
94+
Assert.NotNull(agile.Projects);
95+
Assert.NotNull(agile.Sprints);
96+
Assert.NotNull(agile.Status);
97+
Assert.NotNull(agile.ColumnSettings);
98+
Assert.NotNull(agile.CurrentSprint);
99+
Assert.NotNull(agile.EstimationField);
100+
Assert.NotNull(agile.SprintsSettings);
101+
Assert.NotNull(agile.SwimlaneSettings);
102+
Assert.NotNull(agile.ColorCoding);
103+
Assert.NotNull(agile.UpdateableBy);
104+
Assert.NotNull(agile.VisibleFor);
105+
Assert.NotNull(agile.OriginalEstimationField);
106+
107+
Sprint sprint = agile.Sprints.FirstOrDefault();
108+
Assert.NotNull(sprint);
109+
Assert.Equal(DemoSprintId, sprint.Id);
110+
Assert.Equal(DemoSprintName, sprint.Name);
111+
112+
if ("109-1".Equals(agile.Id))
113+
{
114+
Assert.Equal("Full Board 01", agile.Name);
115+
Assert.IsType<FieldBasedColorCoding>(agile.ColorCoding);
116+
Assert.IsType<IssueBasedSwimlaneSettings>(agile.SwimlaneSettings);
117+
Assert.IsType<CustomFilterField>(((IssueBasedSwimlaneSettings)agile.SwimlaneSettings).Field);
118+
}
119+
else
120+
{
121+
Assert.Equal("Full Board 02", agile.Name);
122+
Assert.IsType<ProjectBasedColorCoding>(agile.ColorCoding);
123+
Assert.IsType<AttributeBasedSwimlaneSettings>(agile.SwimlaneSettings);
124+
Assert.IsType<PredefinedFilterField>(((AttributeBasedSwimlaneSettings)agile.SwimlaneSettings).Field);
125+
}
126+
}
103127
}
104128
}
105129
}

0 commit comments

Comments
 (0)