Merge pull request #18 from StevanFreeborn/master

Deploy to production
This commit is contained in:
StevanFreeborn
2022-07-28 14:38:22 -05:00
committed by GitHub
19 changed files with 1660 additions and 242 deletions
+3
View File
@@ -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
+9 -7
View File
@@ -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",
+5 -2
View File
@@ -22,10 +22,12 @@
"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",
"dev": "start npm start && cd ../server && start dotnet run",
"build": "CI=false && react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
@@ -51,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"
}
}
+15
View File
@@ -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");
}
}
}
+18
View File
@@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Mvc.Testing;
namespace server.tests.Http
{
internal static class HttpClientFactory
{
public static HttpClient GetClient(int version)
{
var webAppFactory = new WebApplicationFactory<Program>();
var client = webAppFactory.CreateDefaultClient();
client.DefaultRequestHeaders.Add("x-api-version", version.ToString());
return client;
}
}
}
+15
View File
@@ -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
};
}
}
}
@@ -1,159 +0,0 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Testing;
using server.Models;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Text.Json;
namespace server.tests.integrationTests
{
public class CharactersControllerIntegrationTests
{
private readonly HttpClient _client;
private readonly JsonSerializerOptions _serializerOptions;
private readonly string _endpoint;
public CharactersControllerIntegrationTests()
{
var webAppFactory = new WebApplicationFactory<Program>();
_client = webAppFactory.CreateDefaultClient();
_client.DefaultRequestHeaders.Add("x-api-version", "1");
_serializerOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
_endpoint = "/api/characters";
}
[Fact]
public async Task GetCharactersAsync_AllCharacters_Returns200StatusCodeWithCharacters()
{
var response = await _client.GetAsync(_endpoint);
var data = await response.Content.ReadAsStreamAsync();
var characters = JsonSerializer.Deserialize<List<Character>>(data);
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");
characters.Should().NotBeNull();
characters.Should().BeOfType<List<Character>>();
characters.Should().HaveCountGreaterThan(0);
}
[Fact]
public async Task GetCharactersAsync_SeasonOneCharacters_Returns200StatusCodeWithCharacters()
{
var seasonValue = 1;
var url = $"{_endpoint}?season={seasonValue}";
var response = await _client.GetAsync(url);
var data = await response.Content.ReadAsStreamAsync();
var characters = JsonSerializer.Deserialize<List<Character>>(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");
characters.Should().NotBeNull();
characters.Should().BeOfType<List<Character>>();
characters.Should().HaveCountGreaterThan(0);
foreach (var character in characters)
{
character.Seasons.Should().Contain(seasonValue);
}
}
[Fact]
public async Task GetCharactersAsync_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<ValidationProblemDetails>(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<ValidationProblemDetails>();
}
[Fact]
public async Task GetCharactersAsync_NameContainsJason_Returns200StatusCodeWithCharacters()
{
var nameValue = "jason";
var url = $"{_endpoint}?name={nameValue}";
var response = await _client.GetAsync(url);
var data = await response.Content.ReadAsStreamAsync();
var characters = JsonSerializer.Deserialize<List<Character>>(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");
characters.Should().NotBeNull();
characters.Should().BeOfType<List<Character>>();
characters.Should().HaveCountGreaterThan(0);
foreach (var character in characters)
{
character.FullName.ToLower().Should().Contain(nameValue);
}
}
[Fact]
public async Task GetCharactersAsync_ActorNameContainsMandy_Returns200StatusCodeWithCharacters()
{
var actorNameValue = "mandy";
var url = $"{_endpoint}?actorname={actorNameValue}";
var response = await _client.GetAsync(url);
var data = await response.Content.ReadAsStreamAsync();
var characters = JsonSerializer.Deserialize<List<Character>>(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");
characters.Should().NotBeNull();
characters.Should().BeOfType<List<Character>>();
characters.Should().HaveCountGreaterThan(0);
foreach (var character in characters)
{
character.ActorFullName.ToLower().Should().Contain(actorNameValue);
}
}
}
}
@@ -1,70 +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 CharacterControllerUnitTests
{
private readonly Mock<ICharacterRepository> _mockRepo;
private readonly CharactersController _controller;
public CharacterControllerUnitTests()
{
_mockRepo = new Mock<ICharacterRepository>();
_controller = new CharactersController(_mockRepo.Object);
}
[Fact]
public async Task GetCharactersAsync_AllCharacters_Returns200StatusCodeWithCharactersCollection()
{
var filter = new CharacterFilter();
var characters = new List<Character> {new Character(), new Character()};
_mockRepo
.Setup(repo => repo.GetCharactersAsync(filter))
.ReturnsAsync(characters);
var response = await _controller.GetCharactersAsync(filter) as ObjectResult;
var data = response.Value as List<Character>;
_mockRepo.Verify(repo => repo.GetCharactersAsync(It.IsAny<CharacterFilter>()), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<OkObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.OK);
data.Should().NotBeNull();
data.Should().BeOfType<List<Character>>();
data.Should().HaveCount(2);
}
[Fact]
public async Task GetCharactersAsync_RepoThrowsException_Returns500StatusCodeWithProblemDetails()
{
var filter = new CharacterFilter();
_mockRepo
.Setup(repo => repo.GetCharactersAsync(filter))
.Throws(new Exception());
var response = await _controller.GetCharactersAsync(filter) as ObjectResult;
var details = response.Value;
_mockRepo.Verify(repo => repo.GetCharactersAsync(It.IsAny<CharacterFilter>()), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<ObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError);
details.Should().NotBeNull();
details.Should().BeOfType<ProblemDetails>();
}
}
}
@@ -0,0 +1,214 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Bson;
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
{
public class CharactersControllerIntegrationTests
{
private readonly HttpClient _client;
private readonly JsonSerializerOptions _serializerOptions;
private readonly string _endpoint;
public CharactersControllerIntegrationTests()
{
_client = HttpClientFactory.GetClient(1);
_serializerOptions = OptionsFactory.GetJsonSerializerOptions();
_endpoint = "/api/characters";
}
[Fact]
public async Task GetCharactersAsync_AllCharacters_Returns200StatusCodeWithCharacters()
{
var response = await _client.GetAsync(_endpoint);
var data = await response.Content.ReadAsStreamAsync();
var characters = JsonSerializer.Deserialize<List<Character>>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
characters.Should().NotBeNull();
characters.Should().BeOfType<List<Character>>();
characters.Should().HaveCountGreaterThan(0);
}
[Fact]
public async Task GetCharactersAsync_SeasonOneCharacters_Returns200StatusCodeWithCharacters()
{
var season = 1;
var url = $"{_endpoint}?{nameof(season)}={season}";
var response = await _client.GetAsync(url);
var data = await response.Content.ReadAsStreamAsync();
var characters = JsonSerializer.Deserialize<List<Character>>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
characters.Should().NotBeNull();
characters.Should().BeOfType<List<Character>>();
characters.Should().HaveCountGreaterThan(0);
foreach (var character in characters)
{
character.Seasons.Should().Contain(season);
}
}
[Fact]
public async Task GetCharactersAsync_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<ValidationProblemDetails>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
details.Should().NotBeNull();
details.Should().BeOfType<ValidationProblemDetails>();
details.Errors.Should().NotBeNull();
}
[Fact]
public async Task GetCharactersAsync_NameContainsJason_Returns200StatusCodeWithCharacters()
{
var name = "jason";
var url = $"{_endpoint}?{nameof(name)}={name}";
var response = await _client.GetAsync(url);
var data = await response.Content.ReadAsStreamAsync();
var characters = JsonSerializer.Deserialize<List<Character>>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
characters.Should().NotBeNull();
characters.Should().BeOfType<List<Character>>();
characters.Should().HaveCountGreaterThan(0);
foreach (var character in characters)
{
character.FullName.ToLower().Should().Contain(name);
}
}
[Fact]
public async Task GetCharactersAsync_ActorNameContainsMandy_Returns200StatusCodeWithCharacters()
{
var actorName = "mandy";
var url = $"{_endpoint}?{nameof(actorName)}={actorName}";
var response = await _client.GetAsync(url);
var data = await response.Content.ReadAsStreamAsync();
var characters = JsonSerializer.Deserialize<List<Character>>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
characters.Should().NotBeNull();
characters.Should().BeOfType<List<Character>>();
characters.Should().HaveCountGreaterThan(0);
foreach (var character in characters)
{
character.ActorFullName.ToLower().Should().Contain(actorName);
}
}
[Fact]
public async Task GetCharacterByIdAsync_ValidCharacterId_Returns200StatusCodeWithCharacter()
{
var response = await _client.GetAsync(_endpoint);
var data = await response.Content.ReadAsStreamAsync();
var characters = JsonSerializer.Deserialize<List<Character>>(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<Character>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
character.Should().NotBeNull();
character.Should().BeOfType<Character>();
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<ValidationProblemDetails>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
details.Should().NotBeNull();
details.Should().BeOfType<ValidationProblemDetails>();
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<ProblemDetails>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
details.Should().NotBeNull();
details.Should().BeOfType<ProblemDetails>();
details.Detail.Should().NotBeNull();
}
}
}
@@ -0,0 +1,437 @@
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
{
public class EpisodesControllerIntegrationTests
{
private readonly HttpClient _client;
private readonly JsonSerializerOptions _serializerOptions;
private readonly string _endpoint;
public EpisodesControllerIntegrationTests()
{
_client = HttpClientFactory.GetClient(1);
_serializerOptions = OptionsFactory.GetJsonSerializerOptions();
_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<List<Episode>>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
episodes.Should().NotBeNull();
episodes.Should().BeOfType<List<Episode>>();
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<List<Episode>>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
episodes.Should().NotBeNull();
episodes.Should().BeOfType<List<Episode>>();
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<ValidationProblemDetails>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
details.Should().NotBeNull();
details.Should().BeOfType<ValidationProblemDetails>();
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<List<Episode>>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
episodes.Should().NotBeNull();
episodes.Should().BeOfType<List<Episode>>();
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<ValidationProblemDetails>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
details.Should().NotBeNull();
details.Should().BeOfType<ValidationProblemDetails>();
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<List<Episode>>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
episodes.Should().NotBeNull();
episodes.Should().BeOfType<List<Episode>>();
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<ValidationProblemDetails>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
details.Should().NotBeNull();
details.Should().BeOfType<ValidationProblemDetails>();
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<List<Episode>>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
episodes.Should().NotBeNull();
episodes.Should().BeOfType<List<Episode>>();
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<List<Episode>>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
episodes.Should().NotBeNull();
episodes.Should().BeOfType<List<Episode>>();
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<List<Episode>>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
episodes.Should().NotBeNull();
episodes.Should().BeOfType<List<Episode>>();
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<List<Episode>>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
episodes.Should().NotBeNull();
episodes.Should().BeOfType<List<Episode>>();
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<List<Episode>>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
episodes.Should().NotBeNull();
episodes.Should().BeOfType<List<Episode>>();
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<ValidationProblemDetails>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
details.Should().NotBeNull();
details.Should().BeOfType<ValidationProblemDetails>();
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<List<Episode>>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
episodes.Should().NotBeNull();
episodes.Should().BeOfType<List<Episode>>();
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<ValidationProblemDetails>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
details.Should().NotBeNull();
details.Should().BeOfType<ValidationProblemDetails>();
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<Episode>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
episode.Should().NotBeNull();
episode.Should().BeOfType<Episode>();
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<ProblemDetails>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
details.Should().NotBeNull();
details.Should().BeOfType<ProblemDetails>();
}
}
}
@@ -0,0 +1,287 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Bson;
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
{
public class QuotesControllerIntegrationTests
{
private readonly HttpClient _client;
private readonly JsonSerializerOptions _serializerOptions;
private readonly string _endpoint;
public QuotesControllerIntegrationTests()
{
_client = HttpClientFactory.GetClient(1);
_serializerOptions = OptionsFactory.GetJsonSerializerOptions();
_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<List<Quote>>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
characters.Should().NotBeNull();
characters.Should().BeOfType<List<Quote>>();
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<List<Quote>>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
quotes.Should().NotBeNull();
quotes.Should().BeOfType<List<Quote>>();
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<ValidationProblemDetails>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
details.Should().NotBeNull();
details.Should().BeOfType<ValidationProblemDetails>();
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<List<Quote>>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
quotes.Should().NotBeNull();
quotes.Should().BeOfType<List<Quote>>();
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<ValidationProblemDetails>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
details.Should().NotBeNull();
details.Should().BeOfType<ValidationProblemDetails>();
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<List<Quote>>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
quotes.Should().NotBeNull();
quotes.Should().BeOfType<List<Quote>>();
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<List<Quote>>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
quotes.Should().NotBeNull();
quotes.Should().BeOfType<List<Quote>>();
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<List<Quote>>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
quotes.Should().NotBeNull();
quotes.Should().BeOfType<List<Quote>>();
quotes.Should().HaveCountGreaterThan(0);
foreach (var quote in quotes)
{
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<List<Quote>>(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<Quote>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
quote.Should().NotBeNull();
quote.Should().BeOfType<Quote>();
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<ValidationProblemDetails>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
details.Should().NotBeNull();
details.Should().BeOfType<ValidationProblemDetails>();
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<ProblemDetails>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
details.Should().NotBeNull();
details.Should().BeOfType<ProblemDetails>();
details.Detail.Should().NotBeNull();
}
}
}
@@ -0,0 +1,99 @@
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
{
public class SeasonsControllerIntegrationTests
{
private readonly HttpClient _client;
private readonly JsonSerializerOptions _serializerOptions;
private readonly string _endpoint;
public SeasonsControllerIntegrationTests()
{
_client = HttpClientFactory.GetClient(1);
_serializerOptions = OptionsFactory.GetJsonSerializerOptions();
_endpoint = "/api/seasons";
}
[Fact]
public async Task GetSeasonsAsync_AllSeasons_Returns200StatusCodeWithSeasons()
{
var response = await _client.GetAsync(_endpoint);
var data = await response.Content.ReadAsStreamAsync();
var seasons = JsonSerializer.Deserialize<List<Season>>(data, _serializerOptions);
response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
seasons.Should().NotBeNull();
seasons.Should().BeOfType<List<Season>>();
seasons.Should().HaveCountGreaterThan(0);
}
[Fact]
public async Task GetSeasonByNumberAsync_SeasonOne_Returns200StatusCodeWithSeason()
{
var seasonNumber = 1;
var url = $"{_endpoint}/{seasonNumber}";
var response = await _client.GetAsync(url);
var data = await response.Content.ReadAsStreamAsync();
var season = JsonSerializer.Deserialize<Season>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.OK);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
season.Should().NotBeNull();
season.Should().BeOfType<Season>();
season.SeasonNumber.Should().Be(seasonNumber);
}
[Fact]
public async Task GetSeasonByNumberAsync_InvalidSeason_Returns404StatusCode()
{
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()
{
var seasonNumber = 20;
var url = $"{_endpoint}/{seasonNumber}";
var response = await _client.GetAsync(url);
var data = await response.Content.ReadAsStreamAsync();
var details = JsonSerializer.Deserialize<ProblemDetails>(data, _serializerOptions);
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
AssertHelper.CheckForRateLimitingHeaders(response.Headers);
details.Should().NotBeNull();
details.Should().BeOfType<ProblemDetails>();
}
}
}
@@ -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 CharactersControllerUnitTests
{
private readonly Mock<ICharacterRepository> _mockRepo;
private readonly CharactersController _controller;
public CharactersControllerUnitTests()
{
_mockRepo = new Mock<ICharacterRepository>();
_controller = new CharactersController(_mockRepo.Object);
}
[Fact]
public async Task GetCharactersAsync_AllCharacters_Returns200StatusCodeWithCharactersCollection()
{
var filter = new CharacterFilter();
var characters = new List<Character> { new Character(), new Character() };
_mockRepo
.Setup(repo => repo.GetCharactersAsync(filter))
.ReturnsAsync(characters);
var response = await _controller.GetCharactersAsync(filter) as ObjectResult;
var data = response.Value as List<Character>;
_mockRepo.Verify(repo => repo.GetCharactersAsync(It.IsAny<CharacterFilter>()), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<OkObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.OK);
data.Should().NotBeNull();
data.Should().BeOfType<List<Character>>();
data.Should().HaveCount(2);
}
[Fact]
public async Task GetCharactersAsync_RepoThrowsException_Returns500StatusCodeWithProblemDetails()
{
var filter = new CharacterFilter();
_mockRepo
.Setup(repo => repo.GetCharactersAsync(filter))
.Throws(new Exception());
var response = await _controller.GetCharactersAsync(filter) as ObjectResult;
var details = response.Value;
_mockRepo.Verify(repo => repo.GetCharactersAsync(It.IsAny<CharacterFilter>()), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<ObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError);
details.Should().NotBeNull();
details.Should().BeOfType<ProblemDetails>();
}
[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<string>()), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<OkObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.OK);
character.Should().NotBeNull();
character.Should().BeOfType<Character>();
}
[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<string>()), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<ObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.NotFound);
details.Should().NotBeNull();
details.Should().BeOfType<ProblemDetails>();
}
[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<string>()), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<ObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError);
details.Should().NotBeNull();
details.Should().BeOfType<ProblemDetails>();
}
}
}
@@ -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<IEpisodeRepository> _mockRepo;
private readonly EpisodesController _controller;
public EpisodesControllerUnitTests()
{
_mockRepo = new Mock<IEpisodeRepository>();
_controller = new EpisodesController(_mockRepo.Object);
}
[Fact]
public async Task GetEpisodesAsync_AllEpisodes_Returns200StatusCodeWithEpisodesCollection()
{
var filter = new EpisodeFilter();
var episodes = new List<Episode> { 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<Episode>;
_mockRepo.Verify(repo => repo.GetEpisodesAsync(It.IsAny<EpisodeFilter>()), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<OkObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.OK);
data.Should().NotBeNull();
data.Should().BeOfType<List<Episode>>();
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<EpisodeFilter>()), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<ObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError);
details.Should().NotBeNull();
details.Should().BeOfType<ProblemDetails>();
}
[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<int>()), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<OkObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.OK);
episode.Should().NotBeNull();
episode.Should().BeOfType<Episode>();
}
[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<int>()), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<ObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.NotFound);
details.Should().NotBeNull();
details.Should().BeOfType<ProblemDetails>();
}
[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<int>()), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<ObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError);
details.Should().NotBeNull();
details.Should().BeOfType<ProblemDetails>();
}
}
}
@@ -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 QuotesControllerUnitTests
{
private readonly Mock<IQuoteRepository> _mockRepo;
private readonly QuotesController _controller;
public QuotesControllerUnitTests()
{
_mockRepo = new Mock<IQuoteRepository>();
_controller = new QuotesController(_mockRepo.Object);
}
[Fact]
public async Task GetQuotesAsync_AllQuotes_Returns200StatusCodeWithQuotesCollection()
{
var filter = new QuoteFilter();
var quotes = new List<Quote> { 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<Quote>;
_mockRepo.Verify(repo => repo.GetQuotesAsync(It.IsAny<QuoteFilter>()), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<OkObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.OK);
data.Should().NotBeNull();
data.Should().BeOfType<List<Quote>>();
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<QuoteFilter>()), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<ObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError);
details.Should().NotBeNull();
details.Should().BeOfType<ProblemDetails>();
}
[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<string>()), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<OkObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.OK);
character.Should().NotBeNull();
character.Should().BeOfType<Quote>();
}
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<String>()), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<OkObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.NotFound);
quote.Should().NotBeNull();
quote.Should().BeOfType<Quote>();
}
[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<string>()), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<ObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError);
details.Should().NotBeNull();
details.Should().BeOfType<ProblemDetails>();
}
}
}
@@ -0,0 +1,136 @@
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 SeasonsControllerUnitTests
{
private readonly Mock<ISeasonRepository> _mockRepo;
private readonly SeasonsController _controller;
public SeasonsControllerUnitTests()
{
_mockRepo = new Mock<ISeasonRepository>();
_controller = new SeasonsController(_mockRepo.Object);
}
[Fact]
public async Task GetSeasonsAsync_AllSeasons_Returns200StatusCodeWithSeasonsCollection()
{
var seasons = new List<Season> { new Season(), new Season() };
_mockRepo
.Setup(repo => repo.GetSeasonsAsync())
.ReturnsAsync(seasons);
var response = await _controller.GetSeasonsAsync() as ObjectResult;
var data = response.Value as List<Season>;
_mockRepo.Verify(repo => repo.GetSeasonsAsync(), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<OkObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.OK);
data.Should().NotBeNull();
data.Should().BeOfType<List<Season>>();
data.Should().HaveCount(2);
}
[Fact]
public async Task GetSeasonsAsync_RepoThrowsException_Returns500StatusCodeWithProblemDetails()
{
_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<ObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError);
details.Should().NotBeNull();
details.Should().BeOfType<ProblemDetails>();
}
[Fact]
public async Task GetSeasonByNumberAsync_ValidSeasonNumber_Returns200StatusCodeWithSeason()
{
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<int>()), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<OkObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.OK);
season.Should().NotBeNull();
season.Should().BeOfType<Season>();
}
[Fact]
public async Task GetSeasonByNumberAsync_ValidSeasonNumberForNonExistentSeason_Returns404StatusCodeWithProblemDetails()
{
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<int>()), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<ObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.NotFound);
details.Should().NotBeNull();
details.Should().BeOfType<ProblemDetails>();
}
[Fact]
public async Task GetSeasonByNumberAsync_RepoThrowsException_Returns500StatusCodeWithProblemDetails()
{
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<int>()), Times.Once());
response.Should().NotBeNull();
response.Should().BeOfType<ObjectResult>();
response.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError);
details.Should().NotBeNull();
details.Should().BeOfType<ProblemDetails>();
}
}
}
@@ -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
@@ -49,14 +49,12 @@ namespace server.Controllers.v1
/// </summary>
/// <param name="number">The number of the episode in the series.</param>
/// <response code="200">Returns the episode requested.</response>
/// <response code="400">Not a valid request.</response>
/// <response code="404">Unable to find an episode with the provided number.</response>
/// <response code="500">Failed to get episode.</response>
/// <returns>Returns the episode requested.</returns>
[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<IActionResult> GetEpisodeByNumberAsync(int number)
+1 -1
View File
@@ -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