Merge pull request #17 from StevanFreeborn/master
Deploy master to production
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<Project Sdk="Microsoft.VisualStudio.JavaScript.Sdk/0.5.26-alpha">
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>8fc072ca-af79-4930-b9b1-139230dc3426</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupCommand>set BROWSER=none&&npm start</StartupCommand>
|
||||
<JavaScriptTestRoot>src\</JavaScriptTestRoot>
|
||||
<JavaScriptTestFramework>Jest</JavaScriptTestFramework>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>8fc072ca-af79-4930-b9b1-139230dc3426</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupCommand>set BROWSER=none&&npm start</StartupCommand>
|
||||
<JavaScriptTestRoot>src\</JavaScriptTestRoot>
|
||||
<JavaScriptTestFramework>Jest</JavaScriptTestFramework>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -55,6 +55,8 @@ export default function Home() {
|
||||
|
||||
const handleTryAgain = async () => {
|
||||
|
||||
setErrorMessage(null);
|
||||
|
||||
if (errorMessage === 'Failed to load characters.') return await getCharacters();
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 _))
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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 _))
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
+21
-16
@@ -1,22 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AspNetCoreRateLimit" Version="4.0.2" />
|
||||
<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="Swashbuckle.AspNetCore" Version="6.3.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AspNetCoreRateLimit" Version="4.0.2" />
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user