Skip to content

Commit

Permalink
Refactor tests. Add JsonArrayHandler / JsonHandler to simplify tests …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
zelodyc committed Mar 15, 2021
1 parent 38d67fe commit a8ece86
Show file tree
Hide file tree
Showing 10 changed files with 683 additions and 135 deletions.
51 changes: 10 additions & 41 deletions tests/YouTrackSharp.Tests/Infrastructure/ConnectionStub.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace YouTrackSharp.Tests.Infrastructure
Expand All @@ -10,18 +9,19 @@ namespace YouTrackSharp.Tests.Infrastructure
/// </summary>
public class ConnectionStub : Connection
{
private Func<HttpRequestMessage, HttpResponseMessage> ExecuteRequest { get; }

private readonly HttpClientHandler _handler;
private TimeSpan TimeOut => TimeSpan.FromSeconds(100);

/// <summary>
/// Creates an instance of <see cref="ConnectionStub"/> with give response delegate
/// </summary>
/// <param name="executeRequest">Request delegate</param>
public ConnectionStub(Func<HttpRequestMessage, HttpResponseMessage> executeRequest) : base(
"http://fake.connection.com/")
/// <param name="handler">
/// <see cref="HttpClientHandler"/> to associate to this connection.
/// This can be used to pass a stub handler for testing purposes.
/// </param>
public ConnectionStub(HttpClientHandler handler) : base("http://fake.connection.com/")
{
ExecuteRequest = executeRequest;
_handler = handler;
}

/// <summary>
Expand All @@ -31,42 +31,11 @@ public ConnectionStub(Func<HttpRequestMessage, HttpResponseMessage> executeReque
/// <returns><see cref="HttpClient"/> configured to return a predefined message and HTTP status</returns>
public override Task<HttpClient> GetAuthenticatedHttpClient()
{
return Task.FromResult(CreateClient());
}

private HttpClient CreateClient()
{
HttpClient httpClient = new HttpClient(new HttpClientHandlerStub(ExecuteRequest));
HttpClient httpClient = new HttpClient(_handler);
httpClient.BaseAddress = ServerUri;
httpClient.Timeout = TimeOut;

return httpClient;
}
}

/// <summary>
/// <see cref="HttpClientHandler"/> mock, that returns a predefined reply and HTTP status code.
/// </summary>
public class HttpClientHandlerStub : HttpClientHandler
{
private Func<HttpRequestMessage, HttpResponseMessage> ExecuteRequest { get; }

/// <summary>
/// Creates an <see cref="HttpClientHandlerStub"/> instance that delegates HttpRequestMessages
/// </summary>
/// <param name="executeRequest">Request delegate</param>
public HttpClientHandlerStub(Func<HttpRequestMessage, HttpResponseMessage> executeRequest)
{
ExecuteRequest = executeRequest;
}

/// <inheritdoc />
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
HttpResponseMessage reply = ExecuteRequest?.Invoke(request);

return Task.FromResult(reply);

return Task.FromResult<HttpClient>(httpClient);
}
}
}
8 changes: 0 additions & 8 deletions tests/YouTrackSharp.Tests/Infrastructure/Connections.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,6 @@ public static string ServerUrl

public static Connection Demo3Token =>
new BearerTokenConnection(ServerUrl, "perm:ZGVtbzM=.WW91VHJhY2tTaGFycA==.L04RdcCnjyW2UPCVg1qyb6dQflpzFy", ConfigureTestsHandler);

public static Connection ConnectionStub(string content, HttpStatusCode status = HttpStatusCode.OK)
{
HttpResponseMessage response = new HttpResponseMessage(status);
response.Content = new StringContent(content);

return new ConnectionStub(_ => response);
}

public static class TestData
{
Expand Down
91 changes: 91 additions & 0 deletions tests/YouTrackSharp.Tests/Infrastructure/JsonArrayHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

namespace YouTrackSharp.Tests.Infrastructure
{
/// <summary>
/// This handler is used to return a json array from a range of give json strings, created from the $top and $skip
/// parameters of the HTTP request.
/// This handler can be used to simulate a server returning json arrays in batches.
/// </summary>
public class JsonArrayHandler : HttpClientHandler
{
private readonly ICollection<string> _jsonObjects;
public int RequestsReceived { get; private set; }

/// <summary>
/// Creates an instance of <see cref="JsonArrayHandler"/>
/// </summary>
/// <param name="jsonObjects">List of json objects that this instance will pick from</param>
public JsonArrayHandler(params string[] jsonObjects)
{
_jsonObjects = jsonObjects;
RequestsReceived = 0;
}

/// <inheritdoc />
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
RequestsReceived++;

GetRequestedRange(request, _jsonObjects.Count, out int skip, out int count);
string json = GetJsonArray(_jsonObjects, skip, count);

HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StringContent(json);

return Task.FromResult(response);
}

/// <summary>
/// Creates a JSON array from a range of the given json strings.
/// This allows to simulate returning a total number of elements, in batches.
/// </summary>
/// <param name="jsonObjects">JSON objects from which the JSON array will be created</param>
/// <param name="skip">Number of items to skip</param>
/// <param name="count">Number of items to return</param>
/// <returns>
/// Json array
/// </returns>
private string GetJsonArray(ICollection<string> jsonObjects, int skip, int count)
{
IEnumerable<string> jsonObjectRange = jsonObjects.Skip(skip).Take(count);
string json = $"[{string.Join(",", jsonObjectRange)}]";

return json;
}

/// <summary>
/// Parses the $skip and $top parameters from a Youtrack REST request URI, and computes the requested range
/// of objects to return (capped by <see cref="maxIndex"/>).
/// </summary>
/// <param name="request">HTTP request</param>
/// <param name="maxIndex">Max index (range will not go beyond that index, even if $skip + $top is greater</param>
/// <param name="skip">Number of items to skip</param>
/// <param name="count">Number of items to return</param>
/// <returns>Range computed from request's $skip and $top</returns>
private void GetRequestedRange(HttpRequestMessage request, int maxIndex, out int skip, out int count)
{
string requestUri = request.RequestUri.ToString();

Match match = Regex.Match(requestUri, "&\\$top=(?<top>[0-9]+)(&\\$skip=(?<skip>[0-9]+))?|&\\$skip=(?<skip>[0-9]+)(&\\$top=(?<top>[0-9]+))?");

count = maxIndex;
if (match.Groups.ContainsKey("top") && match.Groups["top"].Success)
{
count = int.Parse(match.Groups["top"].Value);
}

skip = 0;
if (match.Groups.ContainsKey("skip") && match.Groups["skip"].Success)
{
skip = int.Parse(match.Groups["skip"].Value);
}
}
}
}
33 changes: 33 additions & 0 deletions tests/YouTrackSharp.Tests/Infrastructure/JsonHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace YouTrackSharp.Tests.Infrastructure
{
/// <summary>
/// This handler returns a predefined json string on every request.
/// </summary>
public class JsonHandler : HttpClientHandler
{
private readonly string _json;

/// <summary>
/// Creates an instance of <see cref="JsonHandler"/>
/// </summary>
/// <param name="json">Json string that will be returned upon each request</param>
public JsonHandler(string json)
{
_json = json;
}

/// <inheritdoc />
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StringContent(_json);

return Task.FromResult(response);
}
}
}
14 changes: 3 additions & 11 deletions tests/YouTrackSharp.Tests/Integration/Agiles/AgileServiceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,12 @@ public partial class AgileServiceTest
{
private static string DemoBoardId => "108-2";
private static string DemoBoardNamePrefix => "Test Board597fb561-ea1f-4095-9636-859ae4439605";

private static string DemoSprintId => "109-2";
private static string DemoSprintName => "First sprint";

private static string SingleAgileJson => GetTextResource("YouTrackSharp.Tests.Resources.CompleteAgile.json");

private static string GetAgileJsonArray(int count)
{
string agileJson = SingleAgileJson;

string agilesJson = string.Join(",", Enumerable.Range(0, count).Select(_ => agileJson));

return $"[{agilesJson}]";
}
private static string FullAgile01 => GetTextResource("YouTrackSharp.Tests.Resources.FullAgile01.json");
private static string FullAgile02 => GetTextResource("YouTrackSharp.Tests.Resources.FullAgile02.json");

private static string GetTextResource(string name)
{
Expand Down
82 changes: 53 additions & 29 deletions tests/YouTrackSharp.Tests/Integration/Agiles/GetAgiles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,43 +63,67 @@ await Assert.ThrowsAsync<UnauthorizedConnectionException>(
}

[Fact]
public async Task Full_Agile_Json_Gets_Deserialized_Successfully()
public async Task Mock_Connection_Returns_Full_Agile_01()
{
// Arrange
IAgileService agileService = Connections.ConnectionStub(GetAgileJsonArray(1)).CreateAgileService();
string[] strings = { FullAgile01, FullAgile02 };

JsonArrayHandler handler = new JsonArrayHandler(strings);
ConnectionStub connection = new ConnectionStub(handler);

IAgileService agileService = connection.CreateAgileService();

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

// Assert
Assert.NotNull(result);
Assert.NotEmpty(result);

Agile demoBoard = result.FirstOrDefault();
Assert.NotNull(demoBoard);
Assert.Equal(DemoBoardId, demoBoard.Id);
Assert.Equal(DemoBoardNamePrefix, demoBoard.Name);

Assert.NotNull(demoBoard.ColumnSettings);
Assert.NotNull(demoBoard.Projects);
Assert.NotNull(demoBoard.Sprints);
Assert.NotNull(demoBoard.Projects);
Assert.NotNull(demoBoard.Sprints);
Assert.NotNull(demoBoard.Status);
Assert.NotNull(demoBoard.ColumnSettings);
Assert.NotNull(demoBoard.CurrentSprint);
Assert.NotNull(demoBoard.EstimationField);
Assert.NotNull(demoBoard.SprintsSettings);
Assert.NotNull(demoBoard.SwimlaneSettings);
Assert.NotNull(demoBoard.ColorCoding);
Assert.NotNull(demoBoard.UpdateableBy);
Assert.NotNull(demoBoard.VisibleFor);
Assert.NotNull(demoBoard.OriginalEstimationField);

Sprint sprint = demoBoard.Sprints.FirstOrDefault();
Assert.NotNull(sprint);
Assert.Equal(DemoSprintId, sprint.Id);
Assert.Equal(DemoSprintName, sprint.Name);
Assert.Equal(2, result.Count);

foreach (Agile agile in result)
{
Assert.NotNull(agile);

Assert.True("109-1".Equals(agile.Id) || "109-2".Equals(agile.Id));



Assert.NotNull(agile.ColumnSettings);
Assert.NotNull(agile.Projects);
Assert.NotNull(agile.Sprints);
Assert.NotNull(agile.Projects);
Assert.NotNull(agile.Sprints);
Assert.NotNull(agile.Status);
Assert.NotNull(agile.ColumnSettings);
Assert.NotNull(agile.CurrentSprint);
Assert.NotNull(agile.EstimationField);
Assert.NotNull(agile.SprintsSettings);
Assert.NotNull(agile.SwimlaneSettings);
Assert.NotNull(agile.ColorCoding);
Assert.NotNull(agile.UpdateableBy);
Assert.NotNull(agile.VisibleFor);
Assert.NotNull(agile.OriginalEstimationField);

Sprint sprint = agile.Sprints.FirstOrDefault();
Assert.NotNull(sprint);
Assert.Equal(DemoSprintId, sprint.Id);
Assert.Equal(DemoSprintName, sprint.Name);

if ("109-1".Equals(agile.Id))
{
Assert.Equal("Full Board 01", agile.Name);
Assert.IsType<FieldBasedColorCoding>(agile.ColorCoding);
Assert.IsType<IssueBasedSwimlaneSettings>(agile.SwimlaneSettings);
Assert.IsType<CustomFilterField>(((IssueBasedSwimlaneSettings)agile.SwimlaneSettings).Field);
}
else
{
Assert.Equal("Full Board 02", agile.Name);
Assert.IsType<ProjectBasedColorCoding>(agile.ColorCoding);
Assert.IsType<AttributeBasedSwimlaneSettings>(agile.SwimlaneSettings);
Assert.IsType<PredefinedFilterField>(((AttributeBasedSwimlaneSettings)agile.SwimlaneSettings).Field);
}
}
}
}
}
Expand Down
Loading

0 comments on commit a8ece86

Please sign in to comment.