From 0a7c522faa207b253c68f9c4307781d60ab43e0e Mon Sep 17 00:00:00 2001 From: StevanFreeborn Date: Wed, 6 Jul 2022 11:01:46 -0500 Subject: [PATCH 01/16] finished writing tests for getcharacterbyidasync --- .../CharactersControllerIntegrationTests.cs | 79 ++++++++++++++++++- .../unitTests/CharacterControllerUnitTests.cs | 52 +++++++++++- server/Controllers/v1/CharactersController.cs | 2 +- 3 files changed, 128 insertions(+), 5 deletions(-) diff --git a/server.tests/integrationTests/CharactersControllerIntegrationTests.cs b/server.tests/integrationTests/CharactersControllerIntegrationTests.cs index 9afc85a..9e1acd9 100644 --- a/server.tests/integrationTests/CharactersControllerIntegrationTests.cs +++ b/server.tests/integrationTests/CharactersControllerIntegrationTests.cs @@ -1,6 +1,7 @@ using FluentAssertions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Testing; +using MongoDB.Bson; using server.Models; using System.Collections; using System.Collections.Generic; @@ -38,7 +39,7 @@ namespace server.tests.integrationTests var data = await response.Content.ReadAsStreamAsync(); - var characters = JsonSerializer.Deserialize>(data); + var characters = JsonSerializer.Deserialize>(data, _serializerOptions); response.StatusCode.Should().Be(HttpStatusCode.OK); response.Headers.Should().ContainKey("X-Rate-Limit-Limit"); @@ -155,5 +156,81 @@ namespace server.tests.integrationTests character.ActorFullName.ToLower().Should().Contain(actorNameValue); } } + + [Fact] + public async Task GetCharacterByIdAsync_ValidCharacterId_Returns200StatusCodeWithCharacter() + { + var response = await _client.GetAsync(_endpoint); + + var data = await response.Content.ReadAsStreamAsync(); + + var characters = JsonSerializer.Deserialize>(data, _serializerOptions); + + var characterId = characters[0].Id; + + var url = $"{_endpoint}/{characterId}"; + + response = await _client.GetAsync(url); + + data = await response.Content.ReadAsStreamAsync(); + + var character = JsonSerializer.Deserialize(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + response.Headers.Should().ContainKey("X-Rate-Limit-Limit"); + response.Headers.Should().ContainKey("X-Rate-Limit-Remaining"); + response.Headers.Should().ContainKey("X-Rate-Limit-Reset"); + + character.Should().NotBeNull(); + character.Should().BeOfType(); + character.Id.Should().Be(characterId); + } + + [Fact] + public async Task GetCharacterByIdAsync_InvalidCharacterId_Returns400StatusCodeWithValidationProblemDetails() + { + var characterId = "1"; + + var url = $"{_endpoint}/{characterId}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var details = JsonSerializer.Deserialize(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + response.Headers.Should().ContainKey("X-Rate-Limit-Limit"); + response.Headers.Should().ContainKey("X-Rate-Limit-Remaining"); + response.Headers.Should().ContainKey("X-Rate-Limit-Reset"); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + details.Detail.Should().NotBeNull(); + details.Errors.Should().NotBeNull(); + } + + [Fact] + public async Task GetCharacterByIdAsync_ValidCharacterIdForNonExistentCharacter_Returns404StatusCodeWithProblemDetails() + { + var characterId = ObjectId.GenerateNewId(); + + var url = $"{_endpoint}/{characterId}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var details = JsonSerializer.Deserialize(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + response.Headers.Should().ContainKey("X-Rate-Limit-Limit"); + response.Headers.Should().ContainKey("X-Rate-Limit-Remaining"); + response.Headers.Should().ContainKey("X-Rate-Limit-Reset"); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + details.Detail.Should().NotBeNull(); + } } } diff --git a/server.tests/unitTests/CharacterControllerUnitTests.cs b/server.tests/unitTests/CharacterControllerUnitTests.cs index 929857c..e59cbaf 100644 --- a/server.tests/unitTests/CharacterControllerUnitTests.cs +++ b/server.tests/unitTests/CharacterControllerUnitTests.cs @@ -24,7 +24,7 @@ namespace server.tests.unitTests { var filter = new CharacterFilter(); - var characters = new List {new Character(), new Character()}; + var characters = new List { new Character(), new Character() }; _mockRepo .Setup(repo => repo.GetCharactersAsync(filter)) @@ -32,9 +32,9 @@ namespace server.tests.unitTests var response = await _controller.GetCharactersAsync(filter) as ObjectResult; var data = response.Value as List; - + _mockRepo.Verify(repo => repo.GetCharactersAsync(It.IsAny()), Times.Once()); - + response.Should().NotBeNull(); response.Should().BeOfType(); response.StatusCode.Should().Be((int)HttpStatusCode.OK); @@ -66,5 +66,51 @@ namespace server.tests.unitTests details.Should().NotBeNull(); details.Should().BeOfType(); } + + [Fact] + public async Task GetCharacterByIdAsync_RepoThrowsException_Returns500StatusCodeWithProblemDetails() + { + var characterId = "62b7d5506c1b407771829926"; + + _mockRepo + .Setup(repo => repo.GetCharacterByIdAsync(characterId)) + .Throws(new Exception()); + + var response = await _controller.GetCharacterByIdAsync(characterId) as ObjectResult; + + var details = response.Value; + + _mockRepo.Verify(repo => repo.GetCharacterByIdAsync(It.IsAny()), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + } + + [Fact] + public async Task GetCharacterByIdAsync_ValidCharacterId_Returns200StatusCodeWithCharacter() + { + var characterId = "62b7d5506c1b407771829926"; + + _mockRepo + .Setup(repo => repo.GetCharacterByIdAsync(characterId)) + .ReturnsAsync(new Character()); + + var response = await _controller.GetCharacterByIdAsync(characterId) as ObjectResult; + + var character = response.Value; + + _mockRepo.Verify(repo => repo.GetCharacterByIdAsync(It.IsAny()), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.OK); + + character.Should().NotBeNull(); + character.Should().BeOfType(); + } } } diff --git a/server/Controllers/v1/CharactersController.cs b/server/Controllers/v1/CharactersController.cs index ddc3fdf..0cf465a 100644 --- a/server/Controllers/v1/CharactersController.cs +++ b/server/Controllers/v1/CharactersController.cs @@ -66,7 +66,7 @@ namespace server.Controllers.v1 if (!ObjectId.TryParse(id, out _)) { ModelState.AddModelError(nameof(id), $"{id} is not a valid id"); - return ValidationProblem(); + return ValidationProblem(detail: "Invalid request"); } try From 845b1620ef51e72f664c23dcc8bc9905ec218238 Mon Sep 17 00:00:00 2001 From: StevanFreeborn Date: Wed, 6 Jul 2022 11:13:57 -0500 Subject: [PATCH 02/16] added episodes controller unit test and integration test classes --- .../CharactersControllerIntegrationTests.cs | 2 -- .../EpisodesControllerIntegrationTests.cs | 28 +++++++++++++++++++ .../unitTests/EpisodesControllerUnitTests.cs | 18 ++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 server.tests/integrationTests/EpisodesControllerIntegrationTests.cs create mode 100644 server.tests/unitTests/EpisodesControllerUnitTests.cs diff --git a/server.tests/integrationTests/CharactersControllerIntegrationTests.cs b/server.tests/integrationTests/CharactersControllerIntegrationTests.cs index 9e1acd9..54280d0 100644 --- a/server.tests/integrationTests/CharactersControllerIntegrationTests.cs +++ b/server.tests/integrationTests/CharactersControllerIntegrationTests.cs @@ -3,8 +3,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Testing; using MongoDB.Bson; using server.Models; -using System.Collections; -using System.Collections.Generic; using System.Net; using System.Text.Json; diff --git a/server.tests/integrationTests/EpisodesControllerIntegrationTests.cs b/server.tests/integrationTests/EpisodesControllerIntegrationTests.cs new file mode 100644 index 0000000..3a2d5e7 --- /dev/null +++ b/server.tests/integrationTests/EpisodesControllerIntegrationTests.cs @@ -0,0 +1,28 @@ +using Microsoft.AspNetCore.Mvc.Testing; +using System.Text.Json; + +namespace server.tests.integrationTests +{ + public class EpisodesControllerIntegrationTests + { + private readonly HttpClient _client; + private readonly JsonSerializerOptions _serializerOptions; + private readonly string _endpoint; + + public EpisodesControllerIntegrationTests() + { + var webAppFactory = new WebApplicationFactory(); + + _client = webAppFactory.CreateDefaultClient(); + + _client.DefaultRequestHeaders.Add("x-api-version", "1"); + + _serializerOptions = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + + _endpoint = "/api/episodes"; + } + } +} diff --git a/server.tests/unitTests/EpisodesControllerUnitTests.cs b/server.tests/unitTests/EpisodesControllerUnitTests.cs new file mode 100644 index 0000000..097e104 --- /dev/null +++ b/server.tests/unitTests/EpisodesControllerUnitTests.cs @@ -0,0 +1,18 @@ +using Moq; +using server.Controllers.v1; +using server.Persistence.Repositories; + +namespace server.tests.unitTests +{ + public class EpisodesControllerUnitTests + { + private readonly Mock _mockRepo; + private readonly EpisodesController _controller; + + public EpisodesControllerUnitTests() + { + _mockRepo = new Mock(); + _controller = new EpisodesController(_mockRepo.Object); + } + } +} From ae782e8c56f6d87f5778c3afcdffb01d213c7c7d Mon Sep 17 00:00:00 2001 From: StevanFreeborn Date: Wed, 6 Jul 2022 13:02:57 -0500 Subject: [PATCH 03/16] begin writing tests for episodes controller --- .../EpisodesControllerIntegrationTests.cs | 24 +++++++- .../unitTests/EpisodesControllerUnitTests.cs | 55 ++++++++++++++++++- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/server.tests/integrationTests/EpisodesControllerIntegrationTests.cs b/server.tests/integrationTests/EpisodesControllerIntegrationTests.cs index 3a2d5e7..71794d4 100644 --- a/server.tests/integrationTests/EpisodesControllerIntegrationTests.cs +++ b/server.tests/integrationTests/EpisodesControllerIntegrationTests.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Mvc.Testing; +using FluentAssertions; +using Microsoft.AspNetCore.Mvc.Testing; +using server.Models; +using System.Net; using System.Text.Json; namespace server.tests.integrationTests @@ -24,5 +27,24 @@ namespace server.tests.integrationTests _endpoint = "/api/episodes"; } + + [Fact] + public async Task GetEpisodesAsync_AllEpisodes_Returns200StatusCodeWithEpisodes() + { + var response = await _client.GetAsync(_endpoint); + + var data = await response.Content.ReadAsStreamAsync(); + + var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + response.Headers.Should().ContainKey("X-Rate-Limit-Limit"); + response.Headers.Should().ContainKey("X-Rate-Limit-Remaining"); + response.Headers.Should().ContainKey("X-Rate-Limit-Reset"); + + episodes.Should().NotBeNull(); + episodes.Should().BeOfType>(); + episodes.Should().HaveCountGreaterThan(0); + } } } diff --git a/server.tests/unitTests/EpisodesControllerUnitTests.cs b/server.tests/unitTests/EpisodesControllerUnitTests.cs index 097e104..1a8341d 100644 --- a/server.tests/unitTests/EpisodesControllerUnitTests.cs +++ b/server.tests/unitTests/EpisodesControllerUnitTests.cs @@ -1,6 +1,10 @@ -using Moq; +using FluentAssertions; +using Microsoft.AspNetCore.Mvc; +using Moq; using server.Controllers.v1; +using server.Models; using server.Persistence.Repositories; +using System.Net; namespace server.tests.unitTests { @@ -14,5 +18,54 @@ namespace server.tests.unitTests _mockRepo = new Mock(); _controller = new EpisodesController(_mockRepo.Object); } + + [Fact] + public async Task GetEpisodesAsync_AllEpisodes_Returns200StatusCodeWithEpisodesCollection() + { + var filter = new EpisodeFilter(); + + var episodes = new List { new Episode(), new Episode() }; + + _mockRepo + .Setup(repo => repo.GetEpisodesAsync(filter)) + .ReturnsAsync(episodes); + + var response = await _controller.GetEpisodesAsync(filter) as ObjectResult; + + var data = response.Value as List; + + _mockRepo.Verify(repo => repo.GetEpisodesAsync(It.IsAny()), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.OK); + + data.Should().NotBeNull(); + data.Should().BeOfType>(); + data.Should().HaveCount(2); + } + + [Fact] + public async Task GetEpisodesAsync_RepoThrowsException_Returns500StatusCodeWithProblemDetails() + { + var filter = new EpisodeFilter(); + + _mockRepo + .Setup(repo => repo.GetEpisodesAsync(filter)) + .Throws(new Exception()); + + var response = await _controller.GetEpisodesAsync(filter) as ObjectResult; + + var details = response.Value; + + _mockRepo.Verify(repo => repo.GetEpisodesAsync(It.IsAny()), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + } } } From 1e404b04675d8a8960379f81c78e24742e3fe6cf Mon Sep 17 00:00:00 2001 From: StevanFreeborn Date: Wed, 6 Jul 2022 19:46:19 -0500 Subject: [PATCH 04/16] continue writing tests for episode controller. add assert helper. --- server.tests/Helpers/AssertHelper.cs | 15 +++++ .../CharactersControllerIntegrationTests.cs | 36 ++++-------- .../EpisodesControllerIntegrationTests.cs | 55 +++++++++++++++++-- .../unitTests/CharacterControllerUnitTests.cs | 2 +- .../unitTests/EpisodesControllerUnitTests.cs | 2 +- 5 files changed, 79 insertions(+), 31 deletions(-) create mode 100644 server.tests/Helpers/AssertHelper.cs diff --git a/server.tests/Helpers/AssertHelper.cs b/server.tests/Helpers/AssertHelper.cs new file mode 100644 index 0000000..bfa085a --- /dev/null +++ b/server.tests/Helpers/AssertHelper.cs @@ -0,0 +1,15 @@ +using FluentAssertions; +using System.Net.Http.Headers; + +namespace server.tests.Helpers +{ + internal static class AssertHelper + { + public static void CheckForRateLimitingHeaders(HttpResponseHeaders headers) + { + headers.Should().ContainKey("X-Rate-Limit-Limit"); + headers.Should().ContainKey("X-Rate-Limit-Remaining"); + headers.Should().ContainKey("X-Rate-Limit-Reset"); + } + } +} diff --git a/server.tests/integrationTests/CharactersControllerIntegrationTests.cs b/server.tests/integrationTests/CharactersControllerIntegrationTests.cs index 54280d0..d9e5be4 100644 --- a/server.tests/integrationTests/CharactersControllerIntegrationTests.cs +++ b/server.tests/integrationTests/CharactersControllerIntegrationTests.cs @@ -5,8 +5,9 @@ using MongoDB.Bson; using server.Models; using System.Net; using System.Text.Json; +using server.tests.Helpers; -namespace server.tests.integrationTests +namespace server.tests.IntegrationTests { public class CharactersControllerIntegrationTests { @@ -40,9 +41,7 @@ namespace server.tests.integrationTests var characters = JsonSerializer.Deserialize>(data, _serializerOptions); response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Headers.Should().ContainKey("X-Rate-Limit-Limit"); - response.Headers.Should().ContainKey("X-Rate-Limit-Remaining"); - response.Headers.Should().ContainKey("X-Rate-Limit-Reset"); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); characters.Should().NotBeNull(); characters.Should().BeOfType>(); @@ -63,9 +62,7 @@ namespace server.tests.integrationTests var characters = JsonSerializer.Deserialize>(data, _serializerOptions); response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Headers.Should().ContainKey("X-Rate-Limit-Limit"); - response.Headers.Should().ContainKey("X-Rate-Limit-Remaining"); - response.Headers.Should().ContainKey("X-Rate-Limit-Reset"); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); characters.Should().NotBeNull(); characters.Should().BeOfType>(); @@ -91,12 +88,11 @@ namespace server.tests.integrationTests var details = JsonSerializer.Deserialize(data, _serializerOptions); response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - response.Headers.Should().ContainKey("X-Rate-Limit-Limit"); - response.Headers.Should().ContainKey("X-Rate-Limit-Remaining"); - response.Headers.Should().ContainKey("X-Rate-Limit-Reset"); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); details.Should().NotBeNull(); details.Should().BeOfType(); + details.Errors.Should().NotBeNull(); } [Fact] @@ -113,9 +109,7 @@ namespace server.tests.integrationTests var characters = JsonSerializer.Deserialize>(data, _serializerOptions); response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Headers.Should().ContainKey("X-Rate-Limit-Limit"); - response.Headers.Should().ContainKey("X-Rate-Limit-Remaining"); - response.Headers.Should().ContainKey("X-Rate-Limit-Reset"); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); characters.Should().NotBeNull(); characters.Should().BeOfType>(); @@ -141,9 +135,7 @@ namespace server.tests.integrationTests var characters = JsonSerializer.Deserialize>(data, _serializerOptions); response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Headers.Should().ContainKey("X-Rate-Limit-Limit"); - response.Headers.Should().ContainKey("X-Rate-Limit-Remaining"); - response.Headers.Should().ContainKey("X-Rate-Limit-Reset"); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); characters.Should().NotBeNull(); characters.Should().BeOfType>(); @@ -175,9 +167,7 @@ namespace server.tests.integrationTests var character = JsonSerializer.Deserialize(data, _serializerOptions); response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Headers.Should().ContainKey("X-Rate-Limit-Limit"); - response.Headers.Should().ContainKey("X-Rate-Limit-Remaining"); - response.Headers.Should().ContainKey("X-Rate-Limit-Reset"); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); character.Should().NotBeNull(); character.Should().BeOfType(); @@ -198,9 +188,7 @@ namespace server.tests.integrationTests var details = JsonSerializer.Deserialize(data, _serializerOptions); response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - response.Headers.Should().ContainKey("X-Rate-Limit-Limit"); - response.Headers.Should().ContainKey("X-Rate-Limit-Remaining"); - response.Headers.Should().ContainKey("X-Rate-Limit-Reset"); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); details.Should().NotBeNull(); details.Should().BeOfType(); @@ -222,9 +210,7 @@ namespace server.tests.integrationTests var details = JsonSerializer.Deserialize(data, _serializerOptions); response.StatusCode.Should().Be(HttpStatusCode.NotFound); - response.Headers.Should().ContainKey("X-Rate-Limit-Limit"); - response.Headers.Should().ContainKey("X-Rate-Limit-Remaining"); - response.Headers.Should().ContainKey("X-Rate-Limit-Reset"); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); details.Should().NotBeNull(); details.Should().BeOfType(); diff --git a/server.tests/integrationTests/EpisodesControllerIntegrationTests.cs b/server.tests/integrationTests/EpisodesControllerIntegrationTests.cs index 71794d4..211ccc9 100644 --- a/server.tests/integrationTests/EpisodesControllerIntegrationTests.cs +++ b/server.tests/integrationTests/EpisodesControllerIntegrationTests.cs @@ -1,10 +1,12 @@ using FluentAssertions; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Testing; using server.Models; using System.Net; using System.Text.Json; +using server.tests.Helpers; -namespace server.tests.integrationTests +namespace server.tests.IntegrationTests { public class EpisodesControllerIntegrationTests { @@ -38,13 +40,58 @@ namespace server.tests.integrationTests var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Headers.Should().ContainKey("X-Rate-Limit-Limit"); - response.Headers.Should().ContainKey("X-Rate-Limit-Remaining"); - response.Headers.Should().ContainKey("X-Rate-Limit-Reset"); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); episodes.Should().NotBeNull(); episodes.Should().BeOfType>(); episodes.Should().HaveCountGreaterThan(0); } + + [Fact] + public async Task GetEpisodesAsync_SeasonOneEpisodes_Returns200StatusCodeWithEpisodes() + { + var seasonValue = 1; + + var url = $"{_endpoint}?season={seasonValue}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + episodes.Should().NotBeNull(); + episodes.Should().BeOfType>(); + episodes.Should().HaveCountGreaterThan(0); + + foreach (var episode in episodes) + { + episode.Season.Should().Be(seasonValue); + } + } + + [Fact] + public async Task GetEpisodesAsync_InvalidSeasonQueryParameter_Returns400StatusCodeWithValidationProblemDetails() + { + var seasonValue = "test"; + + var url = $"{_endpoint}?season={seasonValue}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var details = JsonSerializer.Deserialize(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + details.Errors.Should().NotBeNull(); + } } } diff --git a/server.tests/unitTests/CharacterControllerUnitTests.cs b/server.tests/unitTests/CharacterControllerUnitTests.cs index e59cbaf..2e35a68 100644 --- a/server.tests/unitTests/CharacterControllerUnitTests.cs +++ b/server.tests/unitTests/CharacterControllerUnitTests.cs @@ -6,7 +6,7 @@ using server.Models; using server.Persistence.Repositories; using System.Net; -namespace server.tests.unitTests +namespace server.tests.UnitTests { public class CharacterControllerUnitTests { diff --git a/server.tests/unitTests/EpisodesControllerUnitTests.cs b/server.tests/unitTests/EpisodesControllerUnitTests.cs index 1a8341d..4e8e165 100644 --- a/server.tests/unitTests/EpisodesControllerUnitTests.cs +++ b/server.tests/unitTests/EpisodesControllerUnitTests.cs @@ -6,7 +6,7 @@ using server.Models; using server.Persistence.Repositories; using System.Net; -namespace server.tests.unitTests +namespace server.tests.UnitTests { public class EpisodesControllerUnitTests { From 5985d5d9fc657aa1fe93dd099373dbf6543e1809 Mon Sep 17 00:00:00 2001 From: StevanFreeborn Date: Fri, 8 Jul 2022 12:51:01 -0500 Subject: [PATCH 05/16] continue working on episodes tests --- .../EpisodesControllerIntegrationTests.cs | 122 +++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/server.tests/integrationTests/EpisodesControllerIntegrationTests.cs b/server.tests/integrationTests/EpisodesControllerIntegrationTests.cs index 211ccc9..0e644ee 100644 --- a/server.tests/integrationTests/EpisodesControllerIntegrationTests.cs +++ b/server.tests/integrationTests/EpisodesControllerIntegrationTests.cs @@ -51,7 +51,7 @@ namespace server.tests.IntegrationTests public async Task GetEpisodesAsync_SeasonOneEpisodes_Returns200StatusCodeWithEpisodes() { var seasonValue = 1; - + var url = $"{_endpoint}?season={seasonValue}"; var response = await _client.GetAsync(url); @@ -93,5 +93,125 @@ namespace server.tests.IntegrationTests details.Should().BeOfType(); details.Errors.Should().NotBeNull(); } + + [Fact] + public async Task GetEpisodesAsync_EpisodesAfterJanuary2020_Returns200StatusCodeWithEpisodes() + { + var startDateValue = new DateTime(2020, 1, 1); + + var url = $"{_endpoint}?startdate={startDateValue}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + episodes.Should().NotBeNull(); + episodes.Should().BeOfType>(); + episodes.Should().HaveCountGreaterThan(0); + + foreach (var episode in episodes) + { + episode.AirDate.Should().BeOnOrAfter(startDateValue); + } + } + + [Fact] + public async Task GetEpisodesAsync_InvalidStartDateQueryParam_Returns400StatusCodeWithValidationProblemDetails() + { + var startDateValue = "test"; + + var url = $"{_endpoint}?startdate={startDateValue}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var details = JsonSerializer.Deserialize(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + details.Errors.Should().NotBeNull(); + } + + [Fact] + public async Task GetEpisodesAsync_EpisodesBeforeJanuary2020_Returns200StatusCodeWithEpisodes() + { + var endDateValue = new DateTime(2020, 1, 1); + + var url = $"{_endpoint}?enddate={endDateValue}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + episodes.Should().NotBeNull(); + episodes.Should().BeOfType>(); + episodes.Should().HaveCountGreaterThan(0); + + foreach (var episode in episodes) + { + episode.AirDate.Should().BeOnOrBefore(endDateValue); + } + } + + [Fact] + public async Task GetEpisodesAsync_InvalidEndDateQueryParam_Returns400StatusCodeWithValidationProblemDetails() + { + var startDateValue = "test"; + + var url = $"{_endpoint}?startdate={startDateValue}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var details = JsonSerializer.Deserialize(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + details.Errors.Should().NotBeNull(); + } + + [Fact] + public async Task GetEpisodesAsync_EpisodeTitlesThatContainThe_Returns200StatusCodeWithEpisodes() + { + var titleValue = "the"; + + var url = $"{_endpoint}?title={titleValue}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + episodes.Should().NotBeNull(); + episodes.Should().BeOfType>(); + episodes.Should().HaveCountGreaterThan(0); + + foreach (var episode in episodes) + { + episode.Title.ToLower().Should().Contain(titleValue); + } + } } } From 16fe8039bb516cd10cff61fe8572cbe0d79f345c Mon Sep 17 00:00:00 2001 From: StevanFreeborn Date: Fri, 8 Jul 2022 16:32:25 -0500 Subject: [PATCH 06/16] finished writing tests for episodes controller. started writing tests for seasons controller. add http client factory class for integration tests --- .editorconfig | 3 + server.tests/Http/HttpClientFactory.cs | 20 + .../EpisodesControllerIntegrationTests.cs | 217 --------- .../unitTests/EpisodesControllerUnitTests.cs | 71 --- .../CharactersControllerIntegrationTests.cs | 31 +- .../EpisodesControllerIntegrationTests.cs | 439 ++++++++++++++++++ .../SeasonsControllerIntegrationTests.cs | 49 ++ .../CharactersControllerUnitTests.cs} | 52 +-- .../unitTests/EpisodesControllerUnitTests.cs | 140 ++++++ .../unitTests/SeasonsControllerUnitTests.cs | 49 ++ server/Controllers/v1/EpisodesController.cs | 2 - 11 files changed, 740 insertions(+), 333 deletions(-) create mode 100644 server.tests/Http/HttpClientFactory.cs delete mode 100644 server.tests/integrationTests/EpisodesControllerIntegrationTests.cs delete mode 100644 server.tests/unitTests/EpisodesControllerUnitTests.cs rename server.tests/{ => v1}/integrationTests/CharactersControllerIntegrationTests.cs (90%) create mode 100644 server.tests/v1/integrationTests/EpisodesControllerIntegrationTests.cs create mode 100644 server.tests/v1/integrationTests/SeasonsControllerIntegrationTests.cs rename server.tests/{unitTests/CharacterControllerUnitTests.cs => v1/unitTests/CharactersControllerUnitTests.cs} (96%) create mode 100644 server.tests/v1/unitTests/EpisodesControllerUnitTests.cs create mode 100644 server.tests/v1/unitTests/SeasonsControllerUnitTests.cs diff --git a/.editorconfig b/.editorconfig index 732666a..fc1783b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,3 +14,6 @@ dotnet_diagnostic.CS8629.severity = silent # CS8602: Dereference of a possibly null reference. dotnet_diagnostic.CS8602.severity = silent + +# CS8620: Argument cannot be used for parameter due to differences in the nullability of reference types. +dotnet_diagnostic.CS8620.severity = silent diff --git a/server.tests/Http/HttpClientFactory.cs b/server.tests/Http/HttpClientFactory.cs new file mode 100644 index 0000000..8495807 --- /dev/null +++ b/server.tests/Http/HttpClientFactory.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Mvc.Testing; + +namespace server.tests.Http +{ + internal static class HttpClientFactory + { + + + public static HttpClient GetHttpClient(int version) + { + var webAppFactory = new WebApplicationFactory(); + + var client = webAppFactory.CreateDefaultClient(); + + client.DefaultRequestHeaders.Add("x-api-version", version.ToString()); + + return client; + } + } +} diff --git a/server.tests/integrationTests/EpisodesControllerIntegrationTests.cs b/server.tests/integrationTests/EpisodesControllerIntegrationTests.cs deleted file mode 100644 index 0e644ee..0000000 --- a/server.tests/integrationTests/EpisodesControllerIntegrationTests.cs +++ /dev/null @@ -1,217 +0,0 @@ -using FluentAssertions; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Testing; -using server.Models; -using System.Net; -using System.Text.Json; -using server.tests.Helpers; - -namespace server.tests.IntegrationTests -{ - public class EpisodesControllerIntegrationTests - { - private readonly HttpClient _client; - private readonly JsonSerializerOptions _serializerOptions; - private readonly string _endpoint; - - public EpisodesControllerIntegrationTests() - { - var webAppFactory = new WebApplicationFactory(); - - _client = webAppFactory.CreateDefaultClient(); - - _client.DefaultRequestHeaders.Add("x-api-version", "1"); - - _serializerOptions = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }; - - _endpoint = "/api/episodes"; - } - - [Fact] - public async Task GetEpisodesAsync_AllEpisodes_Returns200StatusCodeWithEpisodes() - { - var response = await _client.GetAsync(_endpoint); - - var data = await response.Content.ReadAsStreamAsync(); - - var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); - - response.StatusCode.Should().Be(HttpStatusCode.OK); - AssertHelper.CheckForRateLimitingHeaders(response.Headers); - - episodes.Should().NotBeNull(); - episodes.Should().BeOfType>(); - episodes.Should().HaveCountGreaterThan(0); - } - - [Fact] - public async Task GetEpisodesAsync_SeasonOneEpisodes_Returns200StatusCodeWithEpisodes() - { - var seasonValue = 1; - - var url = $"{_endpoint}?season={seasonValue}"; - - var response = await _client.GetAsync(url); - - var data = await response.Content.ReadAsStreamAsync(); - - var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); - - response.StatusCode.Should().Be(HttpStatusCode.OK); - AssertHelper.CheckForRateLimitingHeaders(response.Headers); - - episodes.Should().NotBeNull(); - episodes.Should().BeOfType>(); - episodes.Should().HaveCountGreaterThan(0); - - foreach (var episode in episodes) - { - episode.Season.Should().Be(seasonValue); - } - } - - [Fact] - public async Task GetEpisodesAsync_InvalidSeasonQueryParameter_Returns400StatusCodeWithValidationProblemDetails() - { - var seasonValue = "test"; - - var url = $"{_endpoint}?season={seasonValue}"; - - var response = await _client.GetAsync(url); - - var data = await response.Content.ReadAsStreamAsync(); - - var details = JsonSerializer.Deserialize(data, _serializerOptions); - - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - AssertHelper.CheckForRateLimitingHeaders(response.Headers); - - details.Should().NotBeNull(); - details.Should().BeOfType(); - details.Errors.Should().NotBeNull(); - } - - [Fact] - public async Task GetEpisodesAsync_EpisodesAfterJanuary2020_Returns200StatusCodeWithEpisodes() - { - var startDateValue = new DateTime(2020, 1, 1); - - var url = $"{_endpoint}?startdate={startDateValue}"; - - var response = await _client.GetAsync(url); - - var data = await response.Content.ReadAsStreamAsync(); - - var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); - - response.StatusCode.Should().Be(HttpStatusCode.OK); - AssertHelper.CheckForRateLimitingHeaders(response.Headers); - - episodes.Should().NotBeNull(); - episodes.Should().BeOfType>(); - episodes.Should().HaveCountGreaterThan(0); - - foreach (var episode in episodes) - { - episode.AirDate.Should().BeOnOrAfter(startDateValue); - } - } - - [Fact] - public async Task GetEpisodesAsync_InvalidStartDateQueryParam_Returns400StatusCodeWithValidationProblemDetails() - { - var startDateValue = "test"; - - var url = $"{_endpoint}?startdate={startDateValue}"; - - var response = await _client.GetAsync(url); - - var data = await response.Content.ReadAsStreamAsync(); - - var details = JsonSerializer.Deserialize(data, _serializerOptions); - - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - AssertHelper.CheckForRateLimitingHeaders(response.Headers); - - details.Should().NotBeNull(); - details.Should().BeOfType(); - details.Errors.Should().NotBeNull(); - } - - [Fact] - public async Task GetEpisodesAsync_EpisodesBeforeJanuary2020_Returns200StatusCodeWithEpisodes() - { - var endDateValue = new DateTime(2020, 1, 1); - - var url = $"{_endpoint}?enddate={endDateValue}"; - - var response = await _client.GetAsync(url); - - var data = await response.Content.ReadAsStreamAsync(); - - var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); - - response.StatusCode.Should().Be(HttpStatusCode.OK); - AssertHelper.CheckForRateLimitingHeaders(response.Headers); - - episodes.Should().NotBeNull(); - episodes.Should().BeOfType>(); - episodes.Should().HaveCountGreaterThan(0); - - foreach (var episode in episodes) - { - episode.AirDate.Should().BeOnOrBefore(endDateValue); - } - } - - [Fact] - public async Task GetEpisodesAsync_InvalidEndDateQueryParam_Returns400StatusCodeWithValidationProblemDetails() - { - var startDateValue = "test"; - - var url = $"{_endpoint}?startdate={startDateValue}"; - - var response = await _client.GetAsync(url); - - var data = await response.Content.ReadAsStreamAsync(); - - var details = JsonSerializer.Deserialize(data, _serializerOptions); - - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - AssertHelper.CheckForRateLimitingHeaders(response.Headers); - - details.Should().NotBeNull(); - details.Should().BeOfType(); - details.Errors.Should().NotBeNull(); - } - - [Fact] - public async Task GetEpisodesAsync_EpisodeTitlesThatContainThe_Returns200StatusCodeWithEpisodes() - { - var titleValue = "the"; - - var url = $"{_endpoint}?title={titleValue}"; - - var response = await _client.GetAsync(url); - - var data = await response.Content.ReadAsStreamAsync(); - - var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); - - response.StatusCode.Should().Be(HttpStatusCode.OK); - AssertHelper.CheckForRateLimitingHeaders(response.Headers); - - episodes.Should().NotBeNull(); - episodes.Should().BeOfType>(); - episodes.Should().HaveCountGreaterThan(0); - - foreach (var episode in episodes) - { - episode.Title.ToLower().Should().Contain(titleValue); - } - } - } -} diff --git a/server.tests/unitTests/EpisodesControllerUnitTests.cs b/server.tests/unitTests/EpisodesControllerUnitTests.cs deleted file mode 100644 index 4e8e165..0000000 --- a/server.tests/unitTests/EpisodesControllerUnitTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -using FluentAssertions; -using Microsoft.AspNetCore.Mvc; -using Moq; -using server.Controllers.v1; -using server.Models; -using server.Persistence.Repositories; -using System.Net; - -namespace server.tests.UnitTests -{ - public class EpisodesControllerUnitTests - { - private readonly Mock _mockRepo; - private readonly EpisodesController _controller; - - public EpisodesControllerUnitTests() - { - _mockRepo = new Mock(); - _controller = new EpisodesController(_mockRepo.Object); - } - - [Fact] - public async Task GetEpisodesAsync_AllEpisodes_Returns200StatusCodeWithEpisodesCollection() - { - var filter = new EpisodeFilter(); - - var episodes = new List { new Episode(), new Episode() }; - - _mockRepo - .Setup(repo => repo.GetEpisodesAsync(filter)) - .ReturnsAsync(episodes); - - var response = await _controller.GetEpisodesAsync(filter) as ObjectResult; - - var data = response.Value as List; - - _mockRepo.Verify(repo => repo.GetEpisodesAsync(It.IsAny()), Times.Once()); - - response.Should().NotBeNull(); - response.Should().BeOfType(); - response.StatusCode.Should().Be((int)HttpStatusCode.OK); - - data.Should().NotBeNull(); - data.Should().BeOfType>(); - data.Should().HaveCount(2); - } - - [Fact] - public async Task GetEpisodesAsync_RepoThrowsException_Returns500StatusCodeWithProblemDetails() - { - var filter = new EpisodeFilter(); - - _mockRepo - .Setup(repo => repo.GetEpisodesAsync(filter)) - .Throws(new Exception()); - - var response = await _controller.GetEpisodesAsync(filter) as ObjectResult; - - var details = response.Value; - - _mockRepo.Verify(repo => repo.GetEpisodesAsync(It.IsAny()), Times.Once()); - - response.Should().NotBeNull(); - response.Should().BeOfType(); - response.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError); - - details.Should().NotBeNull(); - details.Should().BeOfType(); - } - } -} diff --git a/server.tests/integrationTests/CharactersControllerIntegrationTests.cs b/server.tests/v1/integrationTests/CharactersControllerIntegrationTests.cs similarity index 90% rename from server.tests/integrationTests/CharactersControllerIntegrationTests.cs rename to server.tests/v1/integrationTests/CharactersControllerIntegrationTests.cs index d9e5be4..fbcf7f8 100644 --- a/server.tests/integrationTests/CharactersControllerIntegrationTests.cs +++ b/server.tests/v1/integrationTests/CharactersControllerIntegrationTests.cs @@ -6,8 +6,9 @@ using server.Models; using System.Net; using System.Text.Json; using server.tests.Helpers; +using server.tests.Http; -namespace server.tests.IntegrationTests +namespace server.tests.v1.IntegrationTests { public class CharactersControllerIntegrationTests { @@ -17,11 +18,7 @@ namespace server.tests.IntegrationTests public CharactersControllerIntegrationTests() { - var webAppFactory = new WebApplicationFactory(); - - _client = webAppFactory.CreateDefaultClient(); - - _client.DefaultRequestHeaders.Add("x-api-version", "1"); + _client = HttpClientFactory.GetHttpClient(1); _serializerOptions = new JsonSerializerOptions { @@ -51,9 +48,9 @@ namespace server.tests.IntegrationTests [Fact] public async Task GetCharactersAsync_SeasonOneCharacters_Returns200StatusCodeWithCharacters() { - var seasonValue = 1; + var season = 1; - var url = $"{_endpoint}?season={seasonValue}"; + var url = $"{_endpoint}?{nameof(season)}={season}"; var response = await _client.GetAsync(url); @@ -70,16 +67,16 @@ namespace server.tests.IntegrationTests foreach (var character in characters) { - character.Seasons.Should().Contain(seasonValue); + character.Seasons.Should().Contain(season); } } [Fact] public async Task GetCharactersAsync_InvalidSeasonQueryParameter_Returns400StatusCodeWithValidationProblemDetails() { - var seasonValue = "test"; + var season = "test"; - var url = $"{_endpoint}?season={seasonValue}"; + var url = $"{_endpoint}?{nameof(season)}={season}"; var response = await _client.GetAsync(url); @@ -98,9 +95,9 @@ namespace server.tests.IntegrationTests [Fact] public async Task GetCharactersAsync_NameContainsJason_Returns200StatusCodeWithCharacters() { - var nameValue = "jason"; + var name = "jason"; - var url = $"{_endpoint}?name={nameValue}"; + var url = $"{_endpoint}?{nameof(name)}={name}"; var response = await _client.GetAsync(url); @@ -117,16 +114,16 @@ namespace server.tests.IntegrationTests foreach (var character in characters) { - character.FullName.ToLower().Should().Contain(nameValue); + character.FullName.ToLower().Should().Contain(name); } } [Fact] public async Task GetCharactersAsync_ActorNameContainsMandy_Returns200StatusCodeWithCharacters() { - var actorNameValue = "mandy"; + var actorName = "mandy"; - var url = $"{_endpoint}?actorname={actorNameValue}"; + var url = $"{_endpoint}?{nameof(actorName)}={actorName}"; var response = await _client.GetAsync(url); @@ -143,7 +140,7 @@ namespace server.tests.IntegrationTests foreach (var character in characters) { - character.ActorFullName.ToLower().Should().Contain(actorNameValue); + character.ActorFullName.ToLower().Should().Contain(actorName); } } diff --git a/server.tests/v1/integrationTests/EpisodesControllerIntegrationTests.cs b/server.tests/v1/integrationTests/EpisodesControllerIntegrationTests.cs new file mode 100644 index 0000000..7841eb7 --- /dev/null +++ b/server.tests/v1/integrationTests/EpisodesControllerIntegrationTests.cs @@ -0,0 +1,439 @@ +using FluentAssertions; +using Microsoft.AspNetCore.Mvc; +using server.Models; +using server.tests.Helpers; +using server.tests.Http; +using System.Net; +using System.Text.Json; + +namespace server.tests.v1.IntegrationTests +{ + public class EpisodesControllerIntegrationTests + { + private readonly HttpClient _client; + private readonly JsonSerializerOptions _serializerOptions; + private readonly string _endpoint; + + public EpisodesControllerIntegrationTests() + { + _client = HttpClientFactory.GetHttpClient(1); + + _serializerOptions = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + + _endpoint = "/api/episodes"; + } + + [Fact] + public async Task GetEpisodesAsync_AllEpisodes_Returns200StatusCodeWithEpisodes() + { + var response = await _client.GetAsync(_endpoint); + + var data = await response.Content.ReadAsStreamAsync(); + + var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + episodes.Should().NotBeNull(); + episodes.Should().BeOfType>(); + episodes.Should().HaveCountGreaterThan(0); + } + + [Fact] + public async Task GetEpisodesAsync_SeasonOneEpisodes_Returns200StatusCodeWithEpisodes() + { + var season = 1; + + var url = $"{_endpoint}?{nameof(season)}={season}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + episodes.Should().NotBeNull(); + episodes.Should().BeOfType>(); + episodes.Should().HaveCountGreaterThan(0); + + foreach (var episode in episodes) + { + episode.Season.Should().Be(season); + } + } + + [Fact] + public async Task GetEpisodesAsync_InvalidSeasonQueryParameter_Returns400StatusCodeWithValidationProblemDetails() + { + var season = "test"; + + var url = $"{_endpoint}?{nameof(season)}={season}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var details = JsonSerializer.Deserialize(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + details.Errors.Should().NotBeNull(); + } + + [Fact] + public async Task GetEpisodesAsync_EpisodesAfterJanuary2020_Returns200StatusCodeWithEpisodes() + { + var startDate = new DateTime(2020, 1, 1); + + var url = $"{_endpoint}?{nameof(startDate)}={startDate}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + episodes.Should().NotBeNull(); + episodes.Should().BeOfType>(); + episodes.Should().HaveCountGreaterThan(0); + + foreach (var episode in episodes) + { + episode.AirDate.Should().BeOnOrAfter(startDate); + } + } + + [Fact] + public async Task GetEpisodesAsync_InvalidStartDateQueryParam_Returns400StatusCodeWithValidationProblemDetails() + { + var startDate = "test"; + + var url = $"{_endpoint}?{nameof(startDate)}={startDate}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var details = JsonSerializer.Deserialize(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + details.Errors.Should().NotBeNull(); + } + + [Fact] + public async Task GetEpisodesAsync_EpisodesBeforeJanuary2020_Returns200StatusCodeWithEpisodes() + { + var endDate = new DateTime(2020, 1, 1); + + var url = $"{_endpoint}?{nameof(endDate)}={endDate}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + episodes.Should().NotBeNull(); + episodes.Should().BeOfType>(); + episodes.Should().HaveCountGreaterThan(0); + + foreach (var episode in episodes) + { + episode.AirDate.Should().BeOnOrBefore(endDate); + } + } + + [Fact] + public async Task GetEpisodesAsync_InvalidEndDateQueryParam_Returns400StatusCodeWithValidationProblemDetails() + { + var endDate = "test"; + + var url = $"{_endpoint}?{nameof(endDate)}={endDate}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var details = JsonSerializer.Deserialize(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + details.Errors.Should().NotBeNull(); + } + + [Fact] + public async Task GetEpisodesAsync_EpisodeTitlesThatContainThe_Returns200StatusCodeWithEpisodes() + { + var title = "the"; + + var url = $"{_endpoint}?{nameof(title)}={title}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + episodes.Should().NotBeNull(); + episodes.Should().BeOfType>(); + episodes.Should().HaveCountGreaterThan(0); + + foreach (var episode in episodes) + { + episode.Title.ToLower().Should().Contain(title); + } + } + + [Fact] + public async Task GetEpisodesAsync_EpisodeSummariesThatContainFoyet_Returns200StatusCodeWithEpisodes() + { + var summaryKeyword = "foyet"; + + var url = $"{_endpoint}?{nameof(summaryKeyword)}={summaryKeyword}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + episodes.Should().NotBeNull(); + episodes.Should().BeOfType>(); + episodes.Should().HaveCountGreaterThan(0); + + foreach (var episode in episodes) + { + episode.Summary.ToLower().Should().Contain(summaryKeyword); + } + } + + [Fact] + public async Task GetEpisodesAsync_EpisodesDirectedByCharles_Returns200StatusCodeWithEpisodes() + { + var directedBy = "charles"; + + var url = $"{_endpoint}?{nameof(directedBy)}={directedBy}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + episodes.Should().NotBeNull(); + episodes.Should().BeOfType>(); + episodes.Should().HaveCountGreaterThan(0); + + foreach (var episode in episodes) + { + episode.DirectedBy.ToLower().Should().Contain(directedBy); + } + } + + [Fact] + public async Task GetEpisodesAsync_EpisodesWrittenByBreen_Returns200StatusCodeWithEpisodes() + { + var writtenBy = "breen"; + + var url = $"{_endpoint}?{nameof(writtenBy)}={writtenBy}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + episodes.Should().NotBeNull(); + episodes.Should().BeOfType>(); + episodes.Should().HaveCountGreaterThan(0); + + foreach (var episode in episodes) + { + episode.WrittenBy.Any(writer => writer.ToLower().Contains(writtenBy)).Should().BeTrue(); + } + } + + [Fact] + public async Task GetEpisodesAsync_EpisodesWithGreaterThan12MillionUsViewers_Returns200StatusCodeWithEpisodes() + { + var viewersRangeStart = 12.0; + + var url = $"{_endpoint}?{nameof(viewersRangeStart)}={viewersRangeStart}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + episodes.Should().NotBeNull(); + episodes.Should().BeOfType>(); + episodes.Should().HaveCountGreaterThan(0); + + foreach (var episode in episodes) + { + episode.UsViewersInMillions.Should().BeGreaterThanOrEqualTo(viewersRangeStart); + } + } + + [Fact] + public async Task GetEpisodesAsync_InvalidViewersRangeStartQueryParam_Returns400StatusCodeWithValidationProblemDetails() + { + var viewersRangeStart = "test"; + + var url = $"{_endpoint}?{nameof(viewersRangeStart)}={viewersRangeStart}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var details = JsonSerializer.Deserialize(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + details.Errors.Should().NotBeNull(); + } + + [Fact] + public async Task GetEpisodesAsync_EpisodesWithLessThan12MillionUsViewers_Returns200StatusCodeWithEpisodes() + { + var viewersRangeEnd = 12.0; + + var url = $"{_endpoint}?{nameof(viewersRangeEnd)}={viewersRangeEnd}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var episodes = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + episodes.Should().NotBeNull(); + episodes.Should().BeOfType>(); + episodes.Should().HaveCountGreaterThan(0); + + foreach (var episode in episodes) + { + episode.UsViewersInMillions.Should().BeLessThanOrEqualTo(viewersRangeEnd); + } + } + + [Fact] + public async Task GetEpisodesAsync_InvalidViewersRangeEndQueryParam_Returns400StatusCodeWithValidationProblemDetails() + { + var viewersRangeEnd = "test"; + + var url = $"{_endpoint}?{nameof(viewersRangeEnd)}={viewersRangeEnd}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var details = JsonSerializer.Deserialize(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + details.Errors.Should().NotBeNull(); + } + + [Fact] + public async Task GetEpisodeByNumberAsync_ValidEpisodeNumber_Returns200StatusCodeWithEpisode() + { + var episodeNumber = 1; + + var url = $"{_endpoint}/{episodeNumber}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var episode = JsonSerializer.Deserialize(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + episode.Should().NotBeNull(); + episode.Should().BeOfType(); + episode.NumberInSeries.Should().Be(episodeNumber); + } + + [Fact] + public async Task GetEpisodeByNumberAsync_InvalidEpisodeNumber_Returns404StatusCode() + { + var episodeNumber = "test"; + + var url = $"{_endpoint}/{episodeNumber}"; + + var response = await _client.GetAsync(url); + + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + } + + [Fact] + public async Task GetEpisodeByNumberAsync_ValidEpisodeNumberForNonExistentEpisode_Returns404StatusCodeWithProblemDetails() + { + var episodeNumber = 10000; + + var url = $"{_endpoint}/{episodeNumber}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var details = JsonSerializer.Deserialize(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + } + } +} diff --git a/server.tests/v1/integrationTests/SeasonsControllerIntegrationTests.cs b/server.tests/v1/integrationTests/SeasonsControllerIntegrationTests.cs new file mode 100644 index 0000000..beb4bc1 --- /dev/null +++ b/server.tests/v1/integrationTests/SeasonsControllerIntegrationTests.cs @@ -0,0 +1,49 @@ +using server.tests.Http; +using System.Text.Json; + +namespace server.tests.v1.integrationTests +{ + public class SeasonsControllerIntegrationTests + { + private readonly HttpClient _client; + private readonly JsonSerializerOptions _serializerOptions; + private readonly string _endpoint; + + public SeasonsControllerIntegrationTests() + { + _client = HttpClientFactory.GetHttpClient(1); + + _serializerOptions = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + + _endpoint = "/api/seasons"; + } + + [Fact] + public async Task GetSeasonsAsync_AllSeasons_Returns200StatusCodeWithSeasons() + { + throw new NotImplementedException(); + } + + [Fact] + public async Task GetSeasonByNumberAsync_SeasonOne_Returns200StatusCodeWithSeason() + { + throw new NotImplementedException(); + } + + [Fact] + public async Task GetSeasonByNumberAsync_InvalidSeason_Returns404StatusCode() + { + throw new NotImplementedException(); + } + + [Fact] + public async Task GetSeasonByNumberAsync_ValidSeasonNumberForNonExistentSeason_Returns404StatusCode() + { + throw new NotImplementedException(); + } + + } +} diff --git a/server.tests/unitTests/CharacterControllerUnitTests.cs b/server.tests/v1/unitTests/CharactersControllerUnitTests.cs similarity index 96% rename from server.tests/unitTests/CharacterControllerUnitTests.cs rename to server.tests/v1/unitTests/CharactersControllerUnitTests.cs index 2e35a68..6f3d0cd 100644 --- a/server.tests/unitTests/CharacterControllerUnitTests.cs +++ b/server.tests/v1/unitTests/CharactersControllerUnitTests.cs @@ -6,14 +6,14 @@ using server.Models; using server.Persistence.Repositories; using System.Net; -namespace server.tests.UnitTests +namespace server.tests.v1.UnitTests { - public class CharacterControllerUnitTests + public class CharactersControllerUnitTests { private readonly Mock _mockRepo; private readonly CharactersController _controller; - public CharacterControllerUnitTests() + public CharactersControllerUnitTests() { _mockRepo = new Mock(); _controller = new CharactersController(_mockRepo.Object); @@ -67,29 +67,6 @@ namespace server.tests.UnitTests details.Should().BeOfType(); } - [Fact] - public async Task GetCharacterByIdAsync_RepoThrowsException_Returns500StatusCodeWithProblemDetails() - { - var characterId = "62b7d5506c1b407771829926"; - - _mockRepo - .Setup(repo => repo.GetCharacterByIdAsync(characterId)) - .Throws(new Exception()); - - var response = await _controller.GetCharacterByIdAsync(characterId) as ObjectResult; - - var details = response.Value; - - _mockRepo.Verify(repo => repo.GetCharacterByIdAsync(It.IsAny()), Times.Once()); - - response.Should().NotBeNull(); - response.Should().BeOfType(); - response.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError); - - details.Should().NotBeNull(); - details.Should().BeOfType(); - } - [Fact] public async Task GetCharacterByIdAsync_ValidCharacterId_Returns200StatusCodeWithCharacter() { @@ -112,5 +89,28 @@ namespace server.tests.UnitTests character.Should().NotBeNull(); character.Should().BeOfType(); } + + [Fact] + public async Task GetCharacterByIdAsync_RepoThrowsException_Returns500StatusCodeWithProblemDetails() + { + var characterId = "62b7d5506c1b407771829926"; + + _mockRepo + .Setup(repo => repo.GetCharacterByIdAsync(characterId)) + .Throws(new Exception()); + + var response = await _controller.GetCharacterByIdAsync(characterId) as ObjectResult; + + var details = response.Value; + + _mockRepo.Verify(repo => repo.GetCharacterByIdAsync(It.IsAny()), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + } } } diff --git a/server.tests/v1/unitTests/EpisodesControllerUnitTests.cs b/server.tests/v1/unitTests/EpisodesControllerUnitTests.cs new file mode 100644 index 0000000..669c393 --- /dev/null +++ b/server.tests/v1/unitTests/EpisodesControllerUnitTests.cs @@ -0,0 +1,140 @@ +using FluentAssertions; +using Microsoft.AspNetCore.Mvc; +using Moq; +using server.Controllers.v1; +using server.Models; +using server.Persistence.Repositories; +using System.Net; + +namespace server.tests.v1.UnitTests +{ + public class EpisodesControllerUnitTests + { + private readonly Mock _mockRepo; + private readonly EpisodesController _controller; + + public EpisodesControllerUnitTests() + { + _mockRepo = new Mock(); + _controller = new EpisodesController(_mockRepo.Object); + } + + [Fact] + public async Task GetEpisodesAsync_AllEpisodes_Returns200StatusCodeWithEpisodesCollection() + { + var filter = new EpisodeFilter(); + + var episodes = new List { new Episode(), new Episode() }; + + _mockRepo + .Setup(repo => repo.GetEpisodesAsync(filter)) + .ReturnsAsync(episodes); + + var response = await _controller.GetEpisodesAsync(filter) as ObjectResult; + + var data = response.Value as List; + + _mockRepo.Verify(repo => repo.GetEpisodesAsync(It.IsAny()), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.OK); + + data.Should().NotBeNull(); + data.Should().BeOfType>(); + data.Should().HaveCount(2); + } + + [Fact] + public async Task GetEpisodesAsync_RepoThrowsException_Returns500StatusCodeWithProblemDetails() + { + var filter = new EpisodeFilter(); + + _mockRepo + .Setup(repo => repo.GetEpisodesAsync(filter)) + .Throws(new Exception()); + + var response = await _controller.GetEpisodesAsync(filter) as ObjectResult; + + var details = response.Value; + + _mockRepo.Verify(repo => repo.GetEpisodesAsync(It.IsAny()), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + } + + [Fact] + public async Task GetEpisodeByNumberAsync_ValidEpisodeNumber_Returns200StatusWithEpisode() + { + var episodeNumber = 1; + + _mockRepo + .Setup(repo => repo.GetEpisodeByNumberAsync(episodeNumber)) + .ReturnsAsync(new Episode()); + + var response = await _controller.GetEpisodeByNumberAsync(episodeNumber) as ObjectResult; + + var episode = response.Value; + + _mockRepo.Verify(repo => repo.GetEpisodeByNumberAsync(It.IsAny()), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.OK); + + episode.Should().NotBeNull(); + episode.Should().BeOfType(); + } + + [Fact] + public async Task GetEpisodeByNumberAsync_ValidEpisodeNumberForNonExistentEpisode_Returns404StatusWithProblemDetails() + { + var episodeNumber = 3000; + + _mockRepo + .Setup(repo => repo.GetEpisodeByNumberAsync(episodeNumber)) + .ReturnsAsync(null as Episode); + + var response = await _controller.GetEpisodeByNumberAsync(episodeNumber) as ObjectResult; + + var details = response.Value; + + _mockRepo.Verify(repo => repo.GetEpisodeByNumberAsync(It.IsAny()), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.NotFound); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + } + + [Fact] + public async Task GetEpisodeByNumberAsync_RepoThrowsException_Returns500StatusCodeWithProblemDetails() + { + var episodeNumber = 1; + + _mockRepo + .Setup(repo => repo.GetEpisodeByNumberAsync(episodeNumber)) + .Throws(new Exception()); + + var response = await _controller.GetEpisodeByNumberAsync(episodeNumber) as ObjectResult; + + var details = response.Value; + + _mockRepo.Verify(repo => repo.GetEpisodeByNumberAsync(It.IsAny()), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + } + } +} diff --git a/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs b/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs new file mode 100644 index 0000000..1cc72cb --- /dev/null +++ b/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs @@ -0,0 +1,49 @@ +using Moq; +using server.Controllers.v1; +using server.Persistence.Repositories; +using System.Runtime.CompilerServices; + +namespace server.tests.v1.unitTests +{ + public class SeasonsControllerUnitTests + { + private readonly Mock _mockRepo; + private readonly SeasonsController _controller; + + public SeasonsControllerUnitTests() + { + _mockRepo = new Mock(); + _controller = new SeasonsController(_mockRepo.Object); + } + + [Fact] + public async Task GetSeasonsAsync_AllSeasons_Returns200StatusCodeWithSeasonsCollection() + { + throw new NotImplementedException(); + } + + [Fact] + public async Task GetSeasonsAsync_RepoThrowsException_Returns500StatusCodeWithProblemDetails() + { + throw new NotImplementedException(); + } + + [Fact] + public async Task GetSeasonByNumberAsync_ValidSeasonNumber_Returns200StatusCodeWithSeason() + { + throw new NotImplementedException(); + } + + [Fact] + public async Task GetSeasonByNumberAsync_ValidSeasonNumberForNonExistentSeason_Returns404StatusCodeWithProblemDetails() + { + throw new NotImplementedException(); + } + + [Fact] + public async Task GetSeasonByNumberAsync_RepoThrowsException_Returns500StatusCodeWithProblemDetails() + { + throw new NotImplementedException(); + } + } +} diff --git a/server/Controllers/v1/EpisodesController.cs b/server/Controllers/v1/EpisodesController.cs index 7ee0864..988ab5b 100644 --- a/server/Controllers/v1/EpisodesController.cs +++ b/server/Controllers/v1/EpisodesController.cs @@ -49,14 +49,12 @@ namespace server.Controllers.v1 /// /// The number of the episode in the series. /// Returns the episode requested. - /// Not a valid request. /// Unable to find an episode with the provided number. /// Failed to get episode. /// Returns the episode requested. [MapToApiVersion("1.0")] [HttpGet("{number:int}")] [ProducesResponseType(typeof(Episode), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)] public async Task GetEpisodeByNumberAsync(int number) From bd78272a305b174fecf1ff559243591b56d72cc0 Mon Sep 17 00:00:00 2001 From: StevanFreeborn Date: Fri, 8 Jul 2022 16:43:32 -0500 Subject: [PATCH 07/16] added options factory --- server.tests/Http/HttpClientFactory.cs | 2 -- server.tests/Options/OptionsFactory.cs | 15 +++++++++++++++ .../CharactersControllerIntegrationTests.cs | 6 ++---- .../EpisodesControllerIntegrationTests.cs | 6 ++---- .../SeasonsControllerIntegrationTests.cs | 6 ++---- .../v1/unitTests/SeasonsControllerUnitTests.cs | 1 - 6 files changed, 21 insertions(+), 15 deletions(-) create mode 100644 server.tests/Options/OptionsFactory.cs diff --git a/server.tests/Http/HttpClientFactory.cs b/server.tests/Http/HttpClientFactory.cs index 8495807..0b6f658 100644 --- a/server.tests/Http/HttpClientFactory.cs +++ b/server.tests/Http/HttpClientFactory.cs @@ -4,8 +4,6 @@ namespace server.tests.Http { internal static class HttpClientFactory { - - public static HttpClient GetHttpClient(int version) { var webAppFactory = new WebApplicationFactory(); diff --git a/server.tests/Options/OptionsFactory.cs b/server.tests/Options/OptionsFactory.cs new file mode 100644 index 0000000..bf62a4c --- /dev/null +++ b/server.tests/Options/OptionsFactory.cs @@ -0,0 +1,15 @@ +using System.Text.Json; + +namespace server.tests.Options +{ + internal static class OptionsFactory + { + public static JsonSerializerOptions GetJsonSerializerOptions() + { + return new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + } + } +} diff --git a/server.tests/v1/integrationTests/CharactersControllerIntegrationTests.cs b/server.tests/v1/integrationTests/CharactersControllerIntegrationTests.cs index fbcf7f8..eed7250 100644 --- a/server.tests/v1/integrationTests/CharactersControllerIntegrationTests.cs +++ b/server.tests/v1/integrationTests/CharactersControllerIntegrationTests.cs @@ -7,6 +7,7 @@ using System.Net; using System.Text.Json; using server.tests.Helpers; using server.tests.Http; +using server.tests.Options; namespace server.tests.v1.IntegrationTests { @@ -20,10 +21,7 @@ namespace server.tests.v1.IntegrationTests { _client = HttpClientFactory.GetHttpClient(1); - _serializerOptions = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }; + _serializerOptions = OptionsFactory.GetJsonSerializerOptions(); _endpoint = "/api/characters"; } diff --git a/server.tests/v1/integrationTests/EpisodesControllerIntegrationTests.cs b/server.tests/v1/integrationTests/EpisodesControllerIntegrationTests.cs index 7841eb7..c9c8c26 100644 --- a/server.tests/v1/integrationTests/EpisodesControllerIntegrationTests.cs +++ b/server.tests/v1/integrationTests/EpisodesControllerIntegrationTests.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using server.Models; using server.tests.Helpers; using server.tests.Http; +using server.tests.Options; using System.Net; using System.Text.Json; @@ -18,10 +19,7 @@ namespace server.tests.v1.IntegrationTests { _client = HttpClientFactory.GetHttpClient(1); - _serializerOptions = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }; + _serializerOptions = OptionsFactory.GetJsonSerializerOptions(); _endpoint = "/api/episodes"; } diff --git a/server.tests/v1/integrationTests/SeasonsControllerIntegrationTests.cs b/server.tests/v1/integrationTests/SeasonsControllerIntegrationTests.cs index beb4bc1..eae7ed7 100644 --- a/server.tests/v1/integrationTests/SeasonsControllerIntegrationTests.cs +++ b/server.tests/v1/integrationTests/SeasonsControllerIntegrationTests.cs @@ -1,4 +1,5 @@ using server.tests.Http; +using server.tests.Options; using System.Text.Json; namespace server.tests.v1.integrationTests @@ -13,10 +14,7 @@ namespace server.tests.v1.integrationTests { _client = HttpClientFactory.GetHttpClient(1); - _serializerOptions = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }; + _serializerOptions = OptionsFactory.GetJsonSerializerOptions(); _endpoint = "/api/seasons"; } diff --git a/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs b/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs index 1cc72cb..b8ec7bb 100644 --- a/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs +++ b/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs @@ -1,7 +1,6 @@ using Moq; using server.Controllers.v1; using server.Persistence.Repositories; -using System.Runtime.CompilerServices; namespace server.tests.v1.unitTests { From 05a042f552a5d3af780df317c28c7d7ea669f894 Mon Sep 17 00:00:00 2001 From: StevanFreeborn Date: Mon, 11 Jul 2022 09:23:06 -0500 Subject: [PATCH 08/16] added unit and integration test classes for quotes controller --- .../QuotesControllerIntegrationTests.cs | 22 +++++++++++++++++++ .../v1/unitTests/QuotesControllerUnitTests.cs | 18 +++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 server.tests/v1/integrationTests/QuotesControllerIntegrationTests.cs create mode 100644 server.tests/v1/unitTests/QuotesControllerUnitTests.cs diff --git a/server.tests/v1/integrationTests/QuotesControllerIntegrationTests.cs b/server.tests/v1/integrationTests/QuotesControllerIntegrationTests.cs new file mode 100644 index 0000000..bdaa319 --- /dev/null +++ b/server.tests/v1/integrationTests/QuotesControllerIntegrationTests.cs @@ -0,0 +1,22 @@ +using server.tests.Http; +using server.tests.Options; +using System.Text.Json; + +namespace server.tests.v1.integrationTests +{ + public class QuotesControllerIntegrationTests + { + private readonly HttpClient _client; + private readonly JsonSerializerOptions _serializerOptions; + private readonly string _endpoint; + + public QuotesControllerIntegrationTests() + { + _client = HttpClientFactory.GetHttpClient(1); + + _serializerOptions = OptionsFactory.GetJsonSerializerOptions(); + + _endpoint = "/api/seasons"; + } + } +} diff --git a/server.tests/v1/unitTests/QuotesControllerUnitTests.cs b/server.tests/v1/unitTests/QuotesControllerUnitTests.cs new file mode 100644 index 0000000..8d02a3f --- /dev/null +++ b/server.tests/v1/unitTests/QuotesControllerUnitTests.cs @@ -0,0 +1,18 @@ +using Moq; +using server.Controllers.v1; +using server.Persistence.Repositories; + +namespace server.tests.v1.unitTests +{ + public class QuotesControllerUnitTests + { + private readonly Mock _mockRepo; + private readonly QuotesController _controller; + + public QuotesControllerUnitTests() + { + _mockRepo = new Mock(); + _controller = new QuotesController(_mockRepo.Object); + } + } +} From 92d18aa4848fb9150d3a35434414b375f21f0b88 Mon Sep 17 00:00:00 2001 From: StevanFreeborn Date: Mon, 11 Jul 2022 09:36:39 -0500 Subject: [PATCH 09/16] continuing to write tests --- server.tests/Http/HttpClientFactory.cs | 2 +- .../CharactersControllerIntegrationTests.cs | 2 +- .../EpisodesControllerIntegrationTests.cs | 2 +- .../QuotesControllerIntegrationTests.cs | 2 +- .../SeasonsControllerIntegrationTests.cs | 2 +- .../unitTests/SeasonsControllerUnitTests.cs | 62 +++++++++++++++++-- 6 files changed, 63 insertions(+), 9 deletions(-) diff --git a/server.tests/Http/HttpClientFactory.cs b/server.tests/Http/HttpClientFactory.cs index 0b6f658..fbfb464 100644 --- a/server.tests/Http/HttpClientFactory.cs +++ b/server.tests/Http/HttpClientFactory.cs @@ -4,7 +4,7 @@ namespace server.tests.Http { internal static class HttpClientFactory { - public static HttpClient GetHttpClient(int version) + public static HttpClient GetClient(int version) { var webAppFactory = new WebApplicationFactory(); diff --git a/server.tests/v1/integrationTests/CharactersControllerIntegrationTests.cs b/server.tests/v1/integrationTests/CharactersControllerIntegrationTests.cs index eed7250..708b2b0 100644 --- a/server.tests/v1/integrationTests/CharactersControllerIntegrationTests.cs +++ b/server.tests/v1/integrationTests/CharactersControllerIntegrationTests.cs @@ -19,7 +19,7 @@ namespace server.tests.v1.IntegrationTests public CharactersControllerIntegrationTests() { - _client = HttpClientFactory.GetHttpClient(1); + _client = HttpClientFactory.GetClient(1); _serializerOptions = OptionsFactory.GetJsonSerializerOptions(); diff --git a/server.tests/v1/integrationTests/EpisodesControllerIntegrationTests.cs b/server.tests/v1/integrationTests/EpisodesControllerIntegrationTests.cs index c9c8c26..0abd42b 100644 --- a/server.tests/v1/integrationTests/EpisodesControllerIntegrationTests.cs +++ b/server.tests/v1/integrationTests/EpisodesControllerIntegrationTests.cs @@ -17,7 +17,7 @@ namespace server.tests.v1.IntegrationTests public EpisodesControllerIntegrationTests() { - _client = HttpClientFactory.GetHttpClient(1); + _client = HttpClientFactory.GetClient(1); _serializerOptions = OptionsFactory.GetJsonSerializerOptions(); diff --git a/server.tests/v1/integrationTests/QuotesControllerIntegrationTests.cs b/server.tests/v1/integrationTests/QuotesControllerIntegrationTests.cs index bdaa319..d078d07 100644 --- a/server.tests/v1/integrationTests/QuotesControllerIntegrationTests.cs +++ b/server.tests/v1/integrationTests/QuotesControllerIntegrationTests.cs @@ -12,7 +12,7 @@ namespace server.tests.v1.integrationTests public QuotesControllerIntegrationTests() { - _client = HttpClientFactory.GetHttpClient(1); + _client = HttpClientFactory.GetClient(1); _serializerOptions = OptionsFactory.GetJsonSerializerOptions(); diff --git a/server.tests/v1/integrationTests/SeasonsControllerIntegrationTests.cs b/server.tests/v1/integrationTests/SeasonsControllerIntegrationTests.cs index eae7ed7..39b7194 100644 --- a/server.tests/v1/integrationTests/SeasonsControllerIntegrationTests.cs +++ b/server.tests/v1/integrationTests/SeasonsControllerIntegrationTests.cs @@ -12,7 +12,7 @@ namespace server.tests.v1.integrationTests public SeasonsControllerIntegrationTests() { - _client = HttpClientFactory.GetHttpClient(1); + _client = HttpClientFactory.GetClient(1); _serializerOptions = OptionsFactory.GetJsonSerializerOptions(); diff --git a/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs b/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs index b8ec7bb..70c6d74 100644 --- a/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs +++ b/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs @@ -1,6 +1,10 @@ -using Moq; +using FluentAssertions; +using Microsoft.AspNetCore.Mvc; +using Moq; using server.Controllers.v1; +using server.Models; using server.Persistence.Repositories; +using System.Net; namespace server.tests.v1.unitTests { @@ -18,13 +22,46 @@ namespace server.tests.v1.unitTests [Fact] public async Task GetSeasonsAsync_AllSeasons_Returns200StatusCodeWithSeasonsCollection() { - throw new NotImplementedException(); + var seasons = new List { new Season(), new Season() }; + + _mockRepo + .Setup(repo => repo.GetSeasonsAsync()) + .ReturnsAsync(seasons); + + var response = await _controller.GetSeasonsAsync() as ObjectResult; + + var data = response.Value as List; + + _mockRepo.Verify(repo => repo.GetSeasonsAsync(), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.OK); + + data.Should().NotBeNull(); + data.Should().BeOfType>(); + data.Should().HaveCount(2); } [Fact] public async Task GetSeasonsAsync_RepoThrowsException_Returns500StatusCodeWithProblemDetails() { - throw new NotImplementedException(); + _mockRepo + .Setup(repo => repo.GetSeasonsAsync()) + .Throws(new Exception()); + + var response = await _controller.GetSeasonsAsync() as ObjectResult; + + var details = response.Value; + + _mockRepo.Verify(repo => repo.GetSeasonsAsync(), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError); + + details.Should().NotBeNull(); + details.Should().BeOfType(); } [Fact] @@ -42,7 +79,24 @@ namespace server.tests.v1.unitTests [Fact] public async Task GetSeasonByNumberAsync_RepoThrowsException_Returns500StatusCodeWithProblemDetails() { - throw new NotImplementedException(); + var seasonNumber = 1; + + _mockRepo + .Setup(repo => repo.GetSeasonByNumberAsync(seasonNumber)) + .Throws(new Exception()); + + var response = await _controller.GetSeasonByNumberAsync(seasonNumber) as ObjectResult; + + var details = response.Value; + + _mockRepo.Verify(repo => repo.GetSeasonByNumberAsync(It.IsAny()), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError); + + details.Should().NotBeNull(); + details.Should().BeOfType(); } } } From baf61fcbaef9830a1f3f948065c2bbe82e5924d4 Mon Sep 17 00:00:00 2001 From: StevanFreeborn Date: Mon, 11 Jul 2022 11:10:52 -0500 Subject: [PATCH 10/16] finished unit tests for seasons controller --- .../unitTests/SeasonsControllerUnitTests.cs | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs b/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs index 70c6d74..327372d 100644 --- a/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs +++ b/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs @@ -67,13 +67,47 @@ namespace server.tests.v1.unitTests [Fact] public async Task GetSeasonByNumberAsync_ValidSeasonNumber_Returns200StatusCodeWithSeason() { - throw new NotImplementedException(); + var seasonNumber = 1; + + _mockRepo + .Setup(repo => repo.GetSeasonByNumberAsync(seasonNumber)) + .ReturnsAsync(new Season()); + + var response = await _controller.GetSeasonByNumberAsync(seasonNumber) as ObjectResult; + + var season = response.Value; + + _mockRepo.Verify(repo => repo.GetSeasonByNumberAsync(It.IsAny()), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.OK); + + season.Should().NotBeNull(); + season.Should().BeOfType(); } [Fact] public async Task GetSeasonByNumberAsync_ValidSeasonNumberForNonExistentSeason_Returns404StatusCodeWithProblemDetails() { - throw new NotImplementedException(); + var seasonNumber = 20; + + _mockRepo + .Setup(repo => repo.GetSeasonByNumberAsync(seasonNumber)) + .ReturnsAsync(null as Season); + + var response = await _controller.GetSeasonByNumberAsync(seasonNumber) as ObjectResult; + + var details = response.Value; + + _mockRepo.Verify(repo => repo.GetSeasonByNumberAsync(It.IsAny()), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.NotFound); + + details.Should().NotBeNull(); + details.Should().BeOfType(); } [Fact] From dbed2a0e3685da4e94d5ec9916d695271ae7d18f Mon Sep 17 00:00:00 2001 From: StevanFreeborn Date: Mon, 11 Jul 2022 11:31:53 -0500 Subject: [PATCH 11/16] finished writing integration tests for seasons controller --- .../SeasonsControllerIntegrationTests.cs | 64 +++++++++++++++++-- .../unitTests/SeasonsControllerUnitTests.cs | 2 +- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/server.tests/v1/integrationTests/SeasonsControllerIntegrationTests.cs b/server.tests/v1/integrationTests/SeasonsControllerIntegrationTests.cs index 39b7194..fe5f403 100644 --- a/server.tests/v1/integrationTests/SeasonsControllerIntegrationTests.cs +++ b/server.tests/v1/integrationTests/SeasonsControllerIntegrationTests.cs @@ -1,5 +1,10 @@ -using server.tests.Http; +using FluentAssertions; +using Microsoft.AspNetCore.Mvc; +using server.Models; +using server.tests.Helpers; +using server.tests.Http; using server.tests.Options; +using System.Net; using System.Text.Json; namespace server.tests.v1.integrationTests @@ -22,25 +27,72 @@ namespace server.tests.v1.integrationTests [Fact] public async Task GetSeasonsAsync_AllSeasons_Returns200StatusCodeWithSeasons() { - throw new NotImplementedException(); + var response = await _client.GetAsync(_endpoint); + + var data = await response.Content.ReadAsStreamAsync(); + + var seasons = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + seasons.Should().NotBeNull(); + seasons.Should().BeOfType>(); + seasons.Should().HaveCountGreaterThan(0); } [Fact] public async Task GetSeasonByNumberAsync_SeasonOne_Returns200StatusCodeWithSeason() { - throw new NotImplementedException(); + var seasonNumber = 1; + + var url = $"{_endpoint}/{seasonNumber}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var season = JsonSerializer.Deserialize(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + season.Should().NotBeNull(); + season.Should().BeOfType(); + season.SeasonNumber.Should().Be(seasonNumber); } [Fact] public async Task GetSeasonByNumberAsync_InvalidSeason_Returns404StatusCode() - { - throw new NotImplementedException(); + { + var seasonNumber = "test"; + + var url = $"{_endpoint}/{seasonNumber}"; + + var response = await _client.GetAsync(url); + + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); } [Fact] public async Task GetSeasonByNumberAsync_ValidSeasonNumberForNonExistentSeason_Returns404StatusCode() { - throw new NotImplementedException(); + var seasonNumber = 20; + + var url = $"{_endpoint}/{seasonNumber}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var details = JsonSerializer.Deserialize(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + details.Should().NotBeNull(); + details.Should().BeOfType(); } } diff --git a/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs b/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs index 327372d..aa288f5 100644 --- a/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs +++ b/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs @@ -84,7 +84,7 @@ namespace server.tests.v1.unitTests response.StatusCode.Should().Be((int)HttpStatusCode.OK); season.Should().NotBeNull(); - season.Should().BeOfType(); + season.Should().BeOfType(); } [Fact] From c492f57980a2301fa2e466b11b7774689b90f4be Mon Sep 17 00:00:00 2001 From: StevanFreeborn Date: Mon, 11 Jul 2022 12:46:12 -0500 Subject: [PATCH 12/16] finished writing unit tests for Quote Controller added unit test for character controller when no character is found for a given valid object id --- .../SeasonsControllerIntegrationTests.cs | 2 +- .../CharactersControllerUnitTests.cs | 24 ++++ .../v1/unitTests/QuotesControllerUnitTests.cs | 126 +++++++++++++++++- .../unitTests/SeasonsControllerUnitTests.cs | 2 +- 4 files changed, 150 insertions(+), 4 deletions(-) diff --git a/server.tests/v1/integrationTests/SeasonsControllerIntegrationTests.cs b/server.tests/v1/integrationTests/SeasonsControllerIntegrationTests.cs index fe5f403..d4917c6 100644 --- a/server.tests/v1/integrationTests/SeasonsControllerIntegrationTests.cs +++ b/server.tests/v1/integrationTests/SeasonsControllerIntegrationTests.cs @@ -7,7 +7,7 @@ using server.tests.Options; using System.Net; using System.Text.Json; -namespace server.tests.v1.integrationTests +namespace server.tests.v1.IntegrationTests { public class SeasonsControllerIntegrationTests { diff --git a/server.tests/v1/unitTests/CharactersControllerUnitTests.cs b/server.tests/v1/unitTests/CharactersControllerUnitTests.cs index 6f3d0cd..43dcab7 100644 --- a/server.tests/v1/unitTests/CharactersControllerUnitTests.cs +++ b/server.tests/v1/unitTests/CharactersControllerUnitTests.cs @@ -90,6 +90,30 @@ namespace server.tests.v1.UnitTests character.Should().BeOfType(); } + [Fact] + public async Task GetCharacterByIdAsync_ValidCharacterIdForNonExistentCharacter_Returns404StatusCodeWithProblemDetails() + { + var characterId = "62b7d5506c1b407771829926"; + + _mockRepo + .Setup(repo => repo.GetCharacterByIdAsync(characterId)) + .ReturnsAsync(null as Character); + + var response = await _controller.GetCharacterByIdAsync(characterId) as ObjectResult; + + var details = response.Value; + + _mockRepo.Verify(repo => repo.GetCharacterByIdAsync(It.IsAny()), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.NotFound); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + } + + [Fact] public async Task GetCharacterByIdAsync_RepoThrowsException_Returns500StatusCodeWithProblemDetails() { diff --git a/server.tests/v1/unitTests/QuotesControllerUnitTests.cs b/server.tests/v1/unitTests/QuotesControllerUnitTests.cs index 8d02a3f..d38ef20 100644 --- a/server.tests/v1/unitTests/QuotesControllerUnitTests.cs +++ b/server.tests/v1/unitTests/QuotesControllerUnitTests.cs @@ -1,8 +1,12 @@ -using Moq; +using FluentAssertions; +using Microsoft.AspNetCore.Mvc; +using Moq; using server.Controllers.v1; +using server.Models; using server.Persistence.Repositories; +using System.Net; -namespace server.tests.v1.unitTests +namespace server.tests.v1.UnitTests { public class QuotesControllerUnitTests { @@ -14,5 +18,123 @@ namespace server.tests.v1.unitTests _mockRepo = new Mock(); _controller = new QuotesController(_mockRepo.Object); } + + [Fact] + public async Task GetQuotesAsync_AllQuotes_Returns200StatusCodeWithQuotesCollection() + { + var filter = new QuoteFilter(); + + var quotes = new List { new Quote(), new Quote() }; + + _mockRepo + .Setup(repo => repo.GetQuotesAsync(filter)) + .ReturnsAsync(quotes); + + var response = await _controller.GetQuotesAsync(filter) as ObjectResult; + + var data = response.Value as List; + + _mockRepo.Verify(repo => repo.GetQuotesAsync(It.IsAny()), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.OK); + + data.Should().NotBeNull(); + data.Should().BeOfType>(); + data.Should().HaveCount(2); + } + + [Fact] + public async Task GetQuotesAsync_RepoThrowsException_Returns500StatusCodeWithProblemDetails() + { + var filter = new QuoteFilter(); + + _mockRepo + .Setup(repo => repo.GetQuotesAsync(filter)) + .Throws(new Exception()); + + var response = await _controller.GetQuotesAsync(filter) as ObjectResult; + + var details = response.Value; + + _mockRepo.Verify(repo => repo.GetQuotesAsync(It.IsAny()), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + } + + + [Fact] + public async Task GetQuoteByIdAsync_ValidId_Returns200StatusCodeWithQuote() + { + var quoteId = "62b7d5506c1b407771829926"; + + _mockRepo + .Setup(repo => repo.GetQuoteByIdAsync(quoteId)) + .ReturnsAsync(new Quote()); + + var response = await _controller.GetQuoteByIdAsync(quoteId) as ObjectResult; + + var character = response.Value; + + _mockRepo.Verify(repo => repo.GetQuoteByIdAsync(It.IsAny()), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.OK); + + character.Should().NotBeNull(); + character.Should().BeOfType(); + } + + public async Task GetQuoteByIdAsync_ValidIdForNonExistentQuote_Returns404StatusCodeWithProblemDetails() + { + var quoteId = "62b7d5506c1b407771829926"; + + _mockRepo + .Setup(repo => repo.GetQuoteByIdAsync(quoteId)) + .ReturnsAsync(null as Quote); + + var response = await _controller.GetQuoteByIdAsync(quoteId) as ObjectResult; + + var quote = response.Value; + + _mockRepo.Verify(repo => repo.GetQuoteByIdAsync(It.IsAny()), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.NotFound); + + quote.Should().NotBeNull(); + quote.Should().BeOfType(); + } + + [Fact] + public async Task GetQuoteByIdAsync_RepoThrowsException_Returns500StatusCodeWithProblemDetails() + { + var quoteId = "62b7d5506c1b407771829926"; + + _mockRepo + .Setup(repo => repo.GetQuoteByIdAsync(quoteId)) + .Throws(new Exception()); + + var response = await _controller.GetQuoteByIdAsync(quoteId) as ObjectResult; + + var details = response.Value; + + _mockRepo.Verify(repo => repo.GetQuoteByIdAsync(It.IsAny()), Times.Once()); + + response.Should().NotBeNull(); + response.Should().BeOfType(); + response.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + } } } diff --git a/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs b/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs index aa288f5..e5373a2 100644 --- a/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs +++ b/server.tests/v1/unitTests/SeasonsControllerUnitTests.cs @@ -6,7 +6,7 @@ using server.Models; using server.Persistence.Repositories; using System.Net; -namespace server.tests.v1.unitTests +namespace server.tests.v1.UnitTests { public class SeasonsControllerUnitTests { From e94d2135e19eb69eaa916e4f87a0079625d54f4f Mon Sep 17 00:00:00 2001 From: StevanFreeborn Date: Mon, 11 Jul 2022 17:37:25 -0500 Subject: [PATCH 13/16] begin writing integration tests for quotes controller --- .../CharactersControllerIntegrationTests.cs | 5 +- .../QuotesControllerIntegrationTests.cs | 200 +++++++++++++++++- 2 files changed, 199 insertions(+), 6 deletions(-) diff --git a/server.tests/v1/integrationTests/CharactersControllerIntegrationTests.cs b/server.tests/v1/integrationTests/CharactersControllerIntegrationTests.cs index 708b2b0..7d81348 100644 --- a/server.tests/v1/integrationTests/CharactersControllerIntegrationTests.cs +++ b/server.tests/v1/integrationTests/CharactersControllerIntegrationTests.cs @@ -1,13 +1,12 @@ using FluentAssertions; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Testing; using MongoDB.Bson; using server.Models; -using System.Net; -using System.Text.Json; using server.tests.Helpers; using server.tests.Http; using server.tests.Options; +using System.Net; +using System.Text.Json; namespace server.tests.v1.IntegrationTests { diff --git a/server.tests/v1/integrationTests/QuotesControllerIntegrationTests.cs b/server.tests/v1/integrationTests/QuotesControllerIntegrationTests.cs index d078d07..dfd13d1 100644 --- a/server.tests/v1/integrationTests/QuotesControllerIntegrationTests.cs +++ b/server.tests/v1/integrationTests/QuotesControllerIntegrationTests.cs @@ -1,8 +1,13 @@ -using server.tests.Http; +using FluentAssertions; +using Microsoft.AspNetCore.Mvc; +using server.Models; +using server.tests.Helpers; +using server.tests.Http; using server.tests.Options; +using System.Net; using System.Text.Json; -namespace server.tests.v1.integrationTests +namespace server.tests.v1.IntegrationTests { public class QuotesControllerIntegrationTests { @@ -16,7 +21,196 @@ namespace server.tests.v1.integrationTests _serializerOptions = OptionsFactory.GetJsonSerializerOptions(); - _endpoint = "/api/seasons"; + _endpoint = "/api/quotes"; + } + + [Fact] + public async Task GetQuotesAsync_AllQuotes_Returns200StatusCodeWithQuotes() + { + var response = await _client.GetAsync(_endpoint); + + var data = await response.Content.ReadAsStreamAsync(); + + var characters = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + characters.Should().NotBeNull(); + characters.Should().BeOfType>(); + characters.Should().HaveCountGreaterThan(0); + } + + [Fact] + public async Task GetQuotesAsync_SeasonOneQuotes_Returns200StatusCodeWithQoutes() + { + var season = 1; + + var url = $"{_endpoint}?{nameof(season)}={season}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var quotes = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + quotes.Should().NotBeNull(); + quotes.Should().BeOfType>(); + quotes.Should().HaveCountGreaterThan(0); + + foreach (var quote in quotes) + { + quote.Season.Should().Be(season); + } + } + + [Fact] + public async Task GetQuotesAsync_InvalidSeasonQueryParameter_Returns400StatusCodeWithValidationProblemDetails() + { + var season = "test"; + + var url = $"{_endpoint}?{nameof(season)}={season}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var details = JsonSerializer.Deserialize(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + details.Errors.Should().NotBeNull(); + } + + [Fact] + public async Task GetQuotesAsync_EpisodeOneQuotes_Returns200StatusCodeWithQuotes() + { + var episode = 1; + + var url = $"{_endpoint}?{nameof(episode)}={episode}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var quotes = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + quotes.Should().NotBeNull(); + quotes.Should().BeOfType>(); + quotes.Should().HaveCountGreaterThan(0); + + foreach (var quote in quotes) + { + quote.Episode.Should().Be(episode); + } + } + + [Fact] + public async Task GetQuotesAsync_InvalidEpisodeQueryParameter_Returns400StatusCodeWithValidationProblemDetails() + { + var episode = "test"; + + var url = $"{_endpoint}?{nameof(episode)}={episode}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var details = JsonSerializer.Deserialize(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + details.Errors.Should().NotBeNull(); + } + + [Fact] + public async Task GetQuotesAsync_QuotesThatContainEvil_Returns200StatusCodeWithQuotes() + { + var textKeyword = "evil"; + + var url = $"{_endpoint}?{nameof(textKeyword)}={textKeyword}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var quotes = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + quotes.Should().NotBeNull(); + quotes.Should().BeOfType>(); + quotes.Should().HaveCountGreaterThan(0); + + foreach (var quote in quotes) + { + quote.Text.ToLower().Should().Contain(textKeyword); + } + } + + [Fact] + public async Task GetQuotesAsync_QuotesByYoda_Returns200StatusCodeWithQuotes() + { + var source = "yoda"; + + var url = $"{_endpoint}?{nameof(source)}={source}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var quotes = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + quotes.Should().NotBeNull(); + quotes.Should().BeOfType>(); + quotes.Should().HaveCountGreaterThan(0); + + foreach (var quote in quotes) + { + quote.Source.ToLower().Should().Contain(source); + } + } + + [Fact] + public async Task GetQuotesAsync_QuotesNarratedByJason_Returns200StatusCodeWithQuotes() + { + var narrator = "jason"; + + var url = $"{_endpoint}?{nameof(narrator)}={narrator}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var quotes = JsonSerializer.Deserialize>(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + quotes.Should().NotBeNull(); + quotes.Should().BeOfType>(); + quotes.Should().HaveCountGreaterThan(0); + + foreach (var quote in quotes) + { + quote.Narrator.ToLower().Should().Contain(narrator); + } } } } From ec98526ff78c9c702de007113ca1fd0fff4f3324 Mon Sep 17 00:00:00 2001 From: StevanFreeborn Date: Mon, 11 Jul 2022 18:07:42 -0500 Subject: [PATCH 14/16] finished integration tests for quotes controller --- .../QuotesControllerIntegrationTests.cs | 71 +++++++++++++++++++ server/Controllers/v1/QuotesController.cs | 2 +- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/server.tests/v1/integrationTests/QuotesControllerIntegrationTests.cs b/server.tests/v1/integrationTests/QuotesControllerIntegrationTests.cs index dfd13d1..c3c029b 100644 --- a/server.tests/v1/integrationTests/QuotesControllerIntegrationTests.cs +++ b/server.tests/v1/integrationTests/QuotesControllerIntegrationTests.cs @@ -1,5 +1,6 @@ using FluentAssertions; using Microsoft.AspNetCore.Mvc; +using MongoDB.Bson; using server.Models; using server.tests.Helpers; using server.tests.Http; @@ -212,5 +213,75 @@ namespace server.tests.v1.IntegrationTests quote.Narrator.ToLower().Should().Contain(narrator); } } + + [Fact] + public async Task GetQuotesById_ValidQuoteId_Returns200StatusCodeWithQuote() + { + var response = await _client.GetAsync(_endpoint); + + var data = await response.Content.ReadAsStreamAsync(); + + var quotes = JsonSerializer.Deserialize>(data, _serializerOptions); + + var quoteId = quotes[0].Id; + + var url = $"{_endpoint}/{quoteId}"; + + response = await _client.GetAsync(url); + + data = await response.Content.ReadAsStreamAsync(); + + var quote = JsonSerializer.Deserialize(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + quote.Should().NotBeNull(); + quote.Should().BeOfType(); + quote.Id.Should().Be(quoteId); + } + + [Fact] + public async Task GetQuotesById_InvalidQuoteId_Returns400StatusCodeWithValidationProblemDetails() + { + var quoteId = "1"; + + var url = $"{_endpoint}/{quoteId}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var details = JsonSerializer.Deserialize(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + details.Detail.Should().NotBeNull(); + details.Errors.Should().NotBeNull(); + } + + [Fact] + public async Task GetQuoteByIdAsync_ValidQuoteIdForNonExistentQuote_Returns404StatusCodeWithProblemDetails() + { + var quoteId = ObjectId.GenerateNewId(); + + var url = $"{_endpoint}/{quoteId}"; + + var response = await _client.GetAsync(url); + + var data = await response.Content.ReadAsStreamAsync(); + + var details = JsonSerializer.Deserialize(data, _serializerOptions); + + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + AssertHelper.CheckForRateLimitingHeaders(response.Headers); + + details.Should().NotBeNull(); + details.Should().BeOfType(); + details.Detail.Should().NotBeNull(); + } } } diff --git a/server/Controllers/v1/QuotesController.cs b/server/Controllers/v1/QuotesController.cs index a5a18d6..a9ed1ba 100644 --- a/server/Controllers/v1/QuotesController.cs +++ b/server/Controllers/v1/QuotesController.cs @@ -67,7 +67,7 @@ namespace server.Controllers.v1 if (!ObjectId.TryParse(id, out _)) { ModelState.AddModelError(nameof(id), $"{id} is not a valid id"); - return ValidationProblem(); + return ValidationProblem(detail: "Invalid request"); } try From f9f3a3adc488d3e5b2bd9e870e488324e885b06d Mon Sep 17 00:00:00 2001 From: StevanFreeborn Date: Tue, 19 Jul 2022 09:05:56 -0500 Subject: [PATCH 15/16] added dev script to client package.json file --- client/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/client/package.json b/client/package.json index 441fb34..be6b6c2 100644 --- a/client/package.json +++ b/client/package.json @@ -26,6 +26,7 @@ }, "scripts": { "start": "set HTTPS=true&&react-scripts start", + "dev": "start npm start && cd ../server && start dotnet run", "build": "CI=false && react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", From b90d7518fb5dabbc53e622f38b2d5e067b7d312c Mon Sep 17 00:00:00 2001 From: StevanFreeborn Date: Thu, 28 Jul 2022 14:36:02 -0500 Subject: [PATCH 16/16] address terser dependabot alert --- client/package-lock.json | 16 +++++++++------- client/package.json | 6 ++++-- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 6fe2545..4665093 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -23,11 +23,13 @@ "react-router": "^6.3.0", "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", + "terser": ">=5.14.2", "web-vitals": "^2.1.4" }, "devDependencies": { "eslint-config-react-app": "^7.0.1", - "jest-editor-support": "^30.1.0" + "jest-editor-support": "^30.1.0", + "terser": ">=5.14.2" } }, "node_modules/@ampproject/remapping": { @@ -15455,9 +15457,9 @@ } }, "node_modules/terser": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.1.tgz", - "integrity": "sha512-+ahUAE+iheqBTDxXhTisdA8hgvbEG1hHOQ9xmNjeUJSoi6DU/gMrKNcfZjHkyY6Alnuyc+ikYJaxxfHkT3+WuQ==", + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", "dependencies": { "@jridgewell/source-map": "^0.3.2", "acorn": "^8.5.0", @@ -27758,9 +27760,9 @@ } }, "terser": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.1.tgz", - "integrity": "sha512-+ahUAE+iheqBTDxXhTisdA8hgvbEG1hHOQ9xmNjeUJSoi6DU/gMrKNcfZjHkyY6Alnuyc+ikYJaxxfHkT3+WuQ==", + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", "requires": { "@jridgewell/source-map": "^0.3.2", "acorn": "^8.5.0", diff --git a/client/package.json b/client/package.json index be6b6c2..8887f33 100644 --- a/client/package.json +++ b/client/package.json @@ -22,7 +22,8 @@ "react-router": "^6.3.0", "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "terser": ">=5.14.2" }, "scripts": { "start": "set HTTPS=true&&react-scripts start", @@ -52,6 +53,7 @@ }, "devDependencies": { "eslint-config-react-app": "^7.0.1", - "jest-editor-support": "^30.1.0" + "jest-editor-support": "^30.1.0", + "terser": ">=5.14.2" } }