From 16fe8039bb516cd10cff61fe8572cbe0d79f345c Mon Sep 17 00:00:00 2001 From: StevanFreeborn Date: Fri, 8 Jul 2022 16:32:25 -0500 Subject: [PATCH] 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)