Merge pull request #17 from StevanFreeborn/master

Deploy master to production
This commit is contained in:
StevanFreeborn
2022-07-06 09:46:18 -05:00
committed by GitHub
13 changed files with 311 additions and 32 deletions
+6
View File
@@ -8,3 +8,9 @@ dotnet_diagnostic.CS8604.severity = none
# CS1591: Missing XML comment for publicly visible type or member
dotnet_diagnostic.CS1591.severity = none
# CS8629: Nullable value type may be null.
dotnet_diagnostic.CS8629.severity = silent
# CS8602: Dereference of a possibly null reference.
dotnet_diagnostic.CS8602.severity = silent
+2
View File
@@ -55,6 +55,8 @@ export default function Home() {
const handleTryAgain = async () => {
setErrorMessage(null);
if (errorMessage === 'Failed to load characters.') return await getCharacters();
}
+6
View File
@@ -15,6 +15,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "server.tests", "server.tests\server.tests.csproj", "{8B86105B-F6F4-49A9-B384-C2823F693F38}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -31,6 +33,10 @@ Global
{1EF6EB3C-FCA0-4FF6-8A99-373AFAC6E4EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1EF6EB3C-FCA0-4FF6-8A99-373AFAC6E4EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1EF6EB3C-FCA0-4FF6-8A99-373AFAC6E4EC}.Release|Any CPU.Build.0 = Release|Any CPU
{8B86105B-F6F4-49A9-B384-C2823F693F38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8B86105B-F6F4-49A9-B384-C2823F693F38}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8B86105B-F6F4-49A9-B384-C2823F693F38}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8B86105B-F6F4-49A9-B384-C2823F693F38}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
+1
View File
@@ -0,0 +1 @@
global using Xunit;
@@ -0,0 +1,159 @@
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);
}
}
}
}
+30
View File
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.7.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.6" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\server\server.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,70 @@
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>();
}
}
}
@@ -31,7 +31,7 @@ namespace server.Controllers.v1
[ProducesResponseType(typeof(List<Character>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<List<Character>>> GetCharactersAsync([FromQuery] CharacterFilter? filter)
public async Task<IActionResult> GetCharactersAsync([FromQuery] CharacterFilter? filter)
{
try
{
@@ -60,7 +60,7 @@ namespace server.Controllers.v1
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<Character>> GetCharacterByIdAsync(string id)
public async Task<IActionResult> GetCharacterByIdAsync(string id)
{
if (!ObjectId.TryParse(id, out _))
+2 -2
View File
@@ -30,7 +30,7 @@ namespace server.Controllers.v1
[ProducesResponseType(typeof(List<Episode>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<List<Episode>>> GetEpisodesAsync([FromQuery] EpisodeFilter? filter)
public async Task<IActionResult> GetEpisodesAsync([FromQuery] EpisodeFilter? filter)
{
try
{
@@ -59,7 +59,7 @@ namespace server.Controllers.v1
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<Episode>> GetEpisodeByNumberAsync(int number)
public async Task<IActionResult> GetEpisodeByNumberAsync(int number)
{
try
{
+2 -2
View File
@@ -32,7 +32,7 @@ namespace server.Controllers.v1
[ProducesResponseType(typeof(List<Quote>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<List<Quote>>> GetQuotesAsync([FromQuery] QuoteFilter? filter)
public async Task<IActionResult> GetQuotesAsync([FromQuery] QuoteFilter? filter)
{
try
{
@@ -61,7 +61,7 @@ namespace server.Controllers.v1
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<Quote>> GetQuoteByIdAsync(string id)
public async Task<IActionResult> GetQuoteByIdAsync(string id)
{
if (!ObjectId.TryParse(id, out _))
+2 -2
View File
@@ -27,7 +27,7 @@ namespace server.Controllers.v1
[HttpGet]
[ProducesResponseType(typeof(List<Season>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<List<Season>>> GetSeasonsAsync()
public async Task<IActionResult> GetSeasonsAsync()
{
try
{
@@ -53,7 +53,7 @@ namespace server.Controllers.v1
[ProducesResponseType(typeof(Season), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<Season>> GetSeasonByNumberAsync(int number)
public async Task<IActionResult> GetSeasonByNumberAsync(int number)
{
try
{
+5
View File
@@ -16,7 +16,12 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.0.0" />
<PackageReference Include="MongoDB.Driver" Version="2.16.0" />
<PackageReference Include="Moq" Version="4.18.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.1" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="server.tests" />
</ItemGroup>
</Project>