From b560bb37294955937ae09c404d58042bc30ed08c Mon Sep 17 00:00:00 2001 From: Yas Date: Thu, 28 May 2020 06:00:57 +1200 Subject: [PATCH 1/3] rename type names to have version in them rather than in namespace. --- .../ExampleV1Test.cs} | 16 ++++++------- .../ExampleV2Test.cs} | 20 ++++++++-------- ...leController.cs => ExampleV1Controller.cs} | 23 ++++++++++--------- ...leController.cs => ExampleV2Controller.cs} | 21 +++++++++-------- ...eatherForecast.cs => WeatherForecastV1.cs} | 6 +++-- ...eatherForecast.cs => WeatherForecastV2.cs} | 6 +++-- 6 files changed, 48 insertions(+), 44 deletions(-) rename RestApi.Test/{V1/ExampleTest.cs => ApiTests/ExampleV1Test.cs} (89%) rename RestApi.Test/{V2/ExampleTest.cs => ApiTests/ExampleV2Test.cs} (94%) rename RestApi/Controllers/{V1/ExampleController.cs => ExampleV1Controller.cs} (70%) rename RestApi/Controllers/{V2/ExampleController.cs => ExampleV2Controller.cs} (81%) rename RestApi/Models/{V1/WeatherForecast.cs => WeatherForecastV1.cs} (74%) rename RestApi/Models/{V2/WeatherForecast.cs => WeatherForecastV2.cs} (77%) diff --git a/RestApi.Test/V1/ExampleTest.cs b/RestApi.Test/ApiTests/ExampleV1Test.cs similarity index 89% rename from RestApi.Test/V1/ExampleTest.cs rename to RestApi.Test/ApiTests/ExampleV1Test.cs index 3f3036c..ab126bc 100644 --- a/RestApi.Test/V1/ExampleTest.cs +++ b/RestApi.Test/ApiTests/ExampleV1Test.cs @@ -1,17 +1,15 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Net; using System.Text.Json; using System.Threading.Tasks; using NUnit.Framework; -using RestApi.Models.V1; +using RestApi.Models; using RestApi.Test.Models; -namespace RestApi.Test.V1 +namespace RestApi.Test.ApiTests { [TestFixture] - public class ExampleTest : TestBase + public class ExampleV1Test : TestBase { [TestCase("")] [TestCase("/")] @@ -23,7 +21,7 @@ await CatchWebException(async () => using (var stream = await client.OpenReadTaskAsync( new Uri(SetUp.UrlToV1Example + route))) { - var data = await JsonSerializer.DeserializeAsync(stream); + var data = await JsonSerializer.DeserializeAsync(stream); Assert.That(data.Length, Is.EqualTo(5)); } @@ -35,12 +33,12 @@ public async Task TestExampleWithDate(string date, string[] dates) { await CatchWebException(async () => { - WeatherForecast[] data; + WeatherForecastV1[] data; using (var client = new WebClient()) using (var stream = await client.OpenReadTaskAsync( new Uri($"{SetUp.UrlToV1Example}/{date}"))) { - data = await JsonSerializer.DeserializeAsync(stream); + data = await JsonSerializer.DeserializeAsync(stream); Assert.That(data.Length, Is.EqualTo(5)); Assert.That(data[0].Date, Is.EqualTo(dates[0])); @@ -63,7 +61,7 @@ public async Task TestExampleWithInvalidDate(string date) using (var stream = await client.OpenReadTaskAsync( new Uri($"{SetUp.UrlToV1Example}/{date}"))) { - var data = await JsonSerializer.DeserializeAsync(stream); + var data = await JsonSerializer.DeserializeAsync(stream); } Assert.Fail("This test should have ended up with an error response."); diff --git a/RestApi.Test/V2/ExampleTest.cs b/RestApi.Test/ApiTests/ExampleV2Test.cs similarity index 94% rename from RestApi.Test/V2/ExampleTest.cs rename to RestApi.Test/ApiTests/ExampleV2Test.cs index e97b97e..c9b178b 100644 --- a/RestApi.Test/V2/ExampleTest.cs +++ b/RestApi.Test/ApiTests/ExampleV2Test.cs @@ -4,13 +4,13 @@ using System.Text.Json; using System.Threading.Tasks; using NUnit.Framework; -using RestApi.Models.V2; +using RestApi.Models; using RestApi.Test.Models; -namespace RestApi.Test.V2 +namespace RestApi.Test.ApiTests { [TestFixture] - public class ExampleTest : TestBase + public class ExampleV2Test : TestBase { [TestCase("")] [TestCase("/20200303")] @@ -22,7 +22,7 @@ public async Task TestExample(string route) using (var stream = await client.OpenReadTaskAsync( new Uri(SetUp.UrlToV2Example + route))) { - var data = await JsonSerializer.DeserializeAsync(stream); + var data = await JsonSerializer.DeserializeAsync(stream); } Assert.Fail("This test should have ended up with an error response."); @@ -48,7 +48,7 @@ await CatchWebException(async () => using (var stream = await client.OpenReadTaskAsync( new Uri(SetUp.UrlToV2ExampleWF + route))) { - var data = await JsonSerializer.DeserializeAsync(stream); + var data = await JsonSerializer.DeserializeAsync(stream); Assert.That(data.Length, Is.EqualTo(5 * 3)); } @@ -66,7 +66,7 @@ await CatchWebException(async () => using (var stream = await client.OpenReadTaskAsync( new Uri($"{SetUp.UrlToV2ExampleWF}?from={date}"))) { - var data = await JsonSerializer.DeserializeAsync(stream); + var data = await JsonSerializer.DeserializeAsync(stream); Assert.That(data.Length, Is.EqualTo(5 * 3)); Assert.That(data.Min(d => d.Date), @@ -86,7 +86,7 @@ public async Task TestExampleWFWithInvalidDate(string date) using (var stream = await client.OpenReadTaskAsync( new Uri($"{SetUp.UrlToV2ExampleWF}?from={date}"))) { - var data = await JsonSerializer.DeserializeAsync(stream); + var data = await JsonSerializer.DeserializeAsync(stream); } Assert.Fail("This test should have ended up with an error response."); @@ -115,7 +115,7 @@ await CatchWebException(async () => using (var stream = await client.OpenReadTaskAsync( new Uri($"{SetUp.UrlToV2ExampleWF}/{area}?from={date}{(days == null ? "" : $"&days={days}")}"))) { - var data = await JsonSerializer.DeserializeAsync(stream); + var data = await JsonSerializer.DeserializeAsync(stream); Assert.That(data.Length, Is.EqualTo(days ?? 5)); Assert.That(data.Min(d => d.Date), @@ -133,7 +133,7 @@ public async Task TestExampleWFWithInvalidAreaAndValidDate(string area, string d using (var stream = await client.OpenReadTaskAsync( new Uri($"{SetUp.UrlToV2ExampleWF}/{area}?from={date}"))) { - var data = await JsonSerializer.DeserializeAsync(stream); + var data = await JsonSerializer.DeserializeAsync(stream); } Assert.Fail("This test should have ended up with an error response."); } @@ -159,7 +159,7 @@ public async Task TestExampleWFWithValidAreaAndInvalidDate(string area, string d using (var stream = await client.OpenReadTaskAsync( new Uri($"{SetUp.UrlToV2ExampleWF}/{area}?from={date}"))) { - var data = await JsonSerializer.DeserializeAsync(stream); + var data = await JsonSerializer.DeserializeAsync(stream); } Assert.Fail("This test should have ended up with an error response."); diff --git a/RestApi/Controllers/V1/ExampleController.cs b/RestApi/Controllers/ExampleV1Controller.cs similarity index 70% rename from RestApi/Controllers/V1/ExampleController.cs rename to RestApi/Controllers/ExampleV1Controller.cs index 945b490..bae2037 100644 --- a/RestApi/Controllers/V1/ExampleController.cs +++ b/RestApi/Controllers/ExampleV1Controller.cs @@ -4,14 +4,16 @@ using System.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using RestApi.Models.V1; +using NSwag.Annotations; +using RestApi.Models; -namespace RestApi.Controllers.V1 +namespace RestApi.Controllers { [ApiController] [ApiVersion("1")] - [Route("api/v{version:apiVersion}/[controller]")] - public partial class ExampleController : ControllerBase + [Route("api/v{version:apiVersion}/Example")] + [OpenApiTag("API Example: Weather Forecast")] + public partial class ExampleV1Controller : ControllerBase { private static readonly string[] Summaries = new[] { @@ -20,13 +22,13 @@ public partial class ExampleController : ControllerBase private readonly ILogger _logger; - public ExampleController(ILogger logger) + public ExampleV1Controller(ILogger logger) { _logger = logger; } [HttpGet("{date}")] - public virtual IEnumerable Get([FromRoute] string date) + public IEnumerable Get([FromRoute] string date) { DateTime baseDate = DateTime.UtcNow; if (date != null @@ -42,17 +44,16 @@ public virtual IEnumerable Get([FromRoute] string date) var random = new Random(); return Enumerable.Range(0, 5) - .Select(index => new WeatherForecast + .Select(index => new WeatherForecastV1 { Date = baseDate.AddDays(index).ToString("d MMM, yyyy"), TemperatureC = random.Next(-20, 55), Summary = Summaries[random.Next(Summaries.Length)], - }) - .ToArray(); + }); } - [HttpGet()] - public virtual IEnumerable Get() + [HttpGet] + public IEnumerable Get() => Get(null); } } diff --git a/RestApi/Controllers/V2/ExampleController.cs b/RestApi/Controllers/ExampleV2Controller.cs similarity index 81% rename from RestApi/Controllers/V2/ExampleController.cs rename to RestApi/Controllers/ExampleV2Controller.cs index 86da566..9b2cafc 100644 --- a/RestApi/Controllers/V2/ExampleController.cs +++ b/RestApi/Controllers/ExampleV2Controller.cs @@ -4,14 +4,16 @@ using System.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using RestApi.Models.V2; +using NSwag.Annotations; +using RestApi.Models; -namespace RestApi.Controllers.V2 +namespace RestApi.Controllers { [ApiController] [ApiVersion("2")] - [Route("api/v{version:apiVersion}/[controller]")] - public partial class ExampleController : ControllerBase + [Route("api/v{version:apiVersion}/Example")] + [OpenApiTag("API Example: Weather Forecast")] + public partial class ExampleV2Controller : ControllerBase { private static readonly string[] Areas = new[] { @@ -25,13 +27,13 @@ public partial class ExampleController : ControllerBase private readonly ILogger _logger; - public ExampleController(ILogger logger) + public ExampleV2Controller(ILogger logger) { _logger = logger; } [HttpGet("WeatherForecast/{area}")] - public virtual IEnumerable GetWeatherForecast( + public IEnumerable GetWeatherForecast( [FromRoute] string area, [FromQuery] string from, [FromQuery] int? days = null) @@ -62,19 +64,18 @@ public virtual IEnumerable GetWeatherForecast( var random = new Random(); return Enumerable.Range(0, days.Value) - .Select(index => areas.Select(a => new WeatherForecast + .Select(index => areas.Select(a => new WeatherForecastV2 { Area = a, Date = baseDate.AddDays(index), TemperatureC = random.Next(-20, 55), Summary = Summaries[random.Next(Summaries.Length)], })) - .SelectMany(w => w) - .ToArray(); + .SelectMany(w => w); } [HttpGet("WeatherForecast")] - public virtual IEnumerable GetWeatherForecast( + public IEnumerable GetWeatherForecast( [FromQuery] string from, [FromQuery] int? days) => GetWeatherForecast(null, from, days); diff --git a/RestApi/Models/V1/WeatherForecast.cs b/RestApi/Models/WeatherForecastV1.cs similarity index 74% rename from RestApi/Models/V1/WeatherForecast.cs rename to RestApi/Models/WeatherForecastV1.cs index 37f1acc..6255a54 100644 --- a/RestApi/Models/V1/WeatherForecast.cs +++ b/RestApi/Models/WeatherForecastV1.cs @@ -1,9 +1,11 @@ using System; using System.Text.Json.Serialization; +using NJsonSchema.Annotations; -namespace RestApi.Models.V1 +namespace RestApi.Models { - public class WeatherForecast + [JsonSchema(name: "WeatherForecast")] + public class WeatherForecastV1 { [JsonPropertyName("date")] public string Date { get; set; } diff --git a/RestApi/Models/V2/WeatherForecast.cs b/RestApi/Models/WeatherForecastV2.cs similarity index 77% rename from RestApi/Models/V2/WeatherForecast.cs rename to RestApi/Models/WeatherForecastV2.cs index 7ba527f..f871c35 100644 --- a/RestApi/Models/V2/WeatherForecast.cs +++ b/RestApi/Models/WeatherForecastV2.cs @@ -1,9 +1,11 @@ using System; using System.Text.Json.Serialization; +using NJsonSchema.Annotations; -namespace RestApi.Models.V2 +namespace RestApi.Models { - public class WeatherForecast + [JsonSchema(name: "WeatherForecast")] + public class WeatherForecastV2 { [JsonPropertyName("date")] public DateTime Date { get; set; } From 2201accf0f4c1edb02d4323aff04906bc1b11cfa Mon Sep 17 00:00:00 2001 From: Yas Date: Thu, 28 May 2020 06:04:11 +1200 Subject: [PATCH 2/3] refactor the logic to wait for service wake-up in test code, using HTTP Status in thrown exception. --- RestApi.Test/SetUp.cs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/RestApi.Test/SetUp.cs b/RestApi.Test/SetUp.cs index ab36666..a033313 100644 --- a/RestApi.Test/SetUp.cs +++ b/RestApi.Test/SetUp.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Net; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -59,14 +60,14 @@ public async Task SetUpBeforeAll() DateTime start = DateTime.UtcNow; DateTime timeout = DateTime.UtcNow + TimeSpan.FromSeconds(ApiServiceStartUpTimeoutSeconds); Exception exception = null; - while (DateTime.UtcNow < timeout) + while (true) { try { await Task.Delay(500); _logger.LogDebug("Checking service startup..."); - var client = new System.Net.WebClient(); - using (stream = await client.OpenReadTaskAsync(new Uri(UrlToSwagger))) { } + var client = new WebClient(); + using (stream = await client.OpenReadTaskAsync(new Uri(ApiServiceBaseUrl))) { } var wakeup = DateTime.UtcNow - start; _logger.LogInformation("API service started successfully."); break; @@ -74,14 +75,18 @@ public async Task SetUpBeforeAll() catch (Exception e) { exception = e; - } - } + if (e is WebException && ((WebException)e).Status != WebExceptionStatus.UnknownError) + { + break; + } - if (stream == null) - { - var error = $"Test target API service did not start up within {ApiServiceStartUpTimeoutSeconds} seconds"; - _logger.LogError(error); - throw new Exception(error, exception); + if (DateTime.UtcNow > timeout) + { + var error = $"Test target API service did not start up within {ApiServiceStartUpTimeoutSeconds} seconds"; + _logger.LogError(error); + throw new Exception(error, exception); + } + } } } From 5a522ed74cb2e6bd6df087b1cb54f2099b6fa9d9 Mon Sep 17 00:00:00 2001 From: Yas Date: Fri, 5 Jun 2020 05:31:45 +1200 Subject: [PATCH 3/3] use ApiVersionNeutral for ErrorController --- RestApi/Controllers/ErrorController.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/RestApi/Controllers/ErrorController.cs b/RestApi/Controllers/ErrorController.cs index 695a323..4fffdbf 100644 --- a/RestApi/Controllers/ErrorController.cs +++ b/RestApi/Controllers/ErrorController.cs @@ -7,8 +7,7 @@ namespace RestApi.Controllers { [ApiController] - [ApiVersion("1")] - [ApiVersion("2")] + [ApiVersionNeutral] [Route("api/[controller]")] [OpenApiIgnore] public class ErrorController : ControllerBase