Browse Source

Reorganizing the structure of the project, adding unit testing, adding PokemonService and ShakespeareService.

master
Eugene ;) 2 years ago
parent
commit
d47641ccb4
  1. 45
      Controllers/PokemonController.cs
  2. 25
      Pokespearean.Models/Generic/WebResult.cs
  3. 8
      Pokespearean.Models/Pokemon/PokeResult.cs
  4. 11
      Pokespearean.Models/Pokespearean.Models.csproj
  5. 18
      Pokespearean.Models/Shakespeare/ShakespearResponse.cs
  6. 52
      Pokespearean.Tests/ControllerTests/PokemonControllerTests.cs
  7. 29
      Pokespearean.Tests/Pokespearean.Tests.csproj
  8. 16
      Pokespearean.Tests/Startup.cs
  9. 26
      Pokespearean.sln
  10. 0
      Pokespearean/.dockerignore
  11. 57
      Pokespearean/Controllers/PokemonController.cs
  12. 1
      Pokespearean/Dockerfile
  13. 0
      Pokespearean/LICENSE
  14. 12
      Pokespearean/Models/Settings.cs
  15. 0
      Pokespearean/Pokespearean.csproj
  16. 0
      Pokespearean/Program.cs
  17. 0
      Pokespearean/Properties/launchSettings.json
  18. 0
      Pokespearean/README.md
  19. 10
      Pokespearean/Services/IPokemonService.cs
  20. 10
      Pokespearean/Services/IShakespeareService.cs
  21. 38
      Pokespearean/Services/PokemonService.cs
  22. 43
      Pokespearean/Services/ShakespeareService.cs
  23. 20
      Pokespearean/Startup.cs
  24. 0
      Pokespearean/appsettings.Development.json
  25. 3
      Pokespearean/appsettings.json
  26. 15
      WeatherForecast.cs

45
Controllers/PokemonController.cs

@ -1,45 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using PokeApiNet;
using Pokespearean.Models.Generic;
using Pokespearean.Models.Pokemon;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Pokespearean.Controllers
{
[ApiController]
[Route("[controller]")]
public class PokemonController : ControllerBase
{
private readonly ILogger<PokemonController> _logger;
public PokemonController(ILogger<PokemonController> logger)
{
_logger = logger;
}
[Route("{pokemon}")]
public async Task<IActionResult> Get([FromRoute] string pokemon = "ho-oh")
{
var result = new WebResult();
try
{
var client = new PokeApiClient();
var pokedex = await client.GetResourceAsync<Pokedex>(pokemon);
return Ok(new PokeResult
{
Name = pokedex.Name,
Description = pokedex.Descriptions.FirstOrDefault(d => d.Language.Name == "en").Description
});
}
catch (Exception ex)
{
return BadRequest(result.Invalidate(ex.Message, ex));
}
}
}
}

25
Pokespearean.Models/Generic/WebResult.cs

@ -0,0 +1,25 @@
using System;
using System.Text.Json.Serialization;
namespace Pokespearean.Models.Generic
{
public class WebResult
{
[JsonIgnore]
public bool IsValid { get; set; } = true;
[JsonIgnore]
public Exception Exception { get; set; }
[JsonIgnore]
public object Data { get; set; }
public string ErrorMessage { get; set; }
public WebResult Invalidate(string errorMessage, Exception exception = default)
{
IsValid = false;
ErrorMessage = errorMessage;
Exception = exception;
return this;
}
}
}

8
Pokespearean.Models/Pokemon/PokeResult.cs

@ -0,0 +1,8 @@
namespace Pokespearean.Models.Pokemon
{
public class PokeResult
{
public string Name { get; set; }
public string Description { get; set; }
}
}

11
Pokespearean.Models/Pokespearean.Models.csproj

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.Json" Version="5.0.0" />
</ItemGroup>
</Project>

18
Pokespearean.Models/Shakespeare/ShakespearResponse.cs

@ -0,0 +1,18 @@
namespace Pokespearean.Models.Shakespeare
{
public class ShakespearResponse
{
public ShakespearResponseSuccess Success { get; set; }
public ShakespearResponseContents Contents { get; set; }
}
public class ShakespearResponseSuccess
{
public int Total { get; set; }
}
public class ShakespearResponseContents
{
public string Translated { get; set; }
public string Text { get; set; }
public string Translation { get; set; }
}
}

52
Pokespearean.Tests/ControllerTests/PokemonControllerTests.cs

@ -0,0 +1,52 @@
using Microsoft.AspNetCore.Mvc;
using Pokespearean.Controllers;
using Pokespearean.Models.Generic;
using Pokespearean.Models.Pokemon;
using Pokespearean.Services;
using System.Threading.Tasks;
using Xunit;
namespace Pokespearean.Tests
{
public class PokemonControllerTests
{
private readonly IPokemonService PokemonService;
private readonly IShakespeareService ShakespeareService;
public PokemonControllerTests(IPokemonService pokemonService, IShakespeareService shakespeareService)
{
PokemonService = pokemonService;
ShakespeareService = shakespeareService;
}
[Theory]
[InlineData("charizard")]
public async Task GetTest(string pokemonName)
{
var controller = new PokemonController(PokemonService, ShakespeareService);
Assert.NotNull(controller);
var result = await controller.Get(pokemonName);
Assert.NotNull(result);
Assert.IsType<OkObjectResult>(result);
var okResult = result as OkObjectResult;
Assert.IsType<PokeResult>(okResult.Value);
var pokemon = okResult.Value as PokeResult;
Assert.Equal("charizard", pokemon.Name);
Assert.Equal("Charizard flies 'round the sky in search of powerful opponents. 't breathes fire of such most wondrous heat yond 't melts aught. However, 't nev'r turns its fiery breath on any opponent weaker than itself.",
pokemon.Description);
}
[Theory]
[InlineData("amIaPokemon?")]
public async Task GetWithWrongPokemonTest(string pokemonName)
{
var controller = new PokemonController(PokemonService, ShakespeareService);
Assert.NotNull(controller);
var result = await controller.Get(pokemonName);
Assert.NotNull(result);
Assert.IsType<BadRequestObjectResult>(result);
var badRequestResult = result as BadRequestObjectResult;
Assert.IsType<WebResult>(badRequestResult.Value);
}
}
}

29
Pokespearean.Tests/Pokespearean.Tests.csproj

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="Xunit.DependencyInjection" Version="7.1.0" />
<PackageReference Include="xunit.extensibility.core" 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="1.3.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Pokespearean\Pokespearean.csproj" />
</ItemGroup>
</Project>

16
Pokespearean.Tests/Startup.cs

@ -0,0 +1,16 @@
using Microsoft.Extensions.DependencyInjection;
using Pokespearean.Models;
using Pokespearean.Services;
namespace Pokespearean.Tests
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IPokemonService, PokemonService>();
services.AddScoped<IShakespeareService, ShakespeareService>();
services.Configure<Settings>(s => s.ShakespeareApi = "https://api.funtranslations.com");
}
}
}

26
Pokespearean.sln

@ -3,9 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30711.63
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pokespearean", "Pokespearean.csproj", "{B9920ADA-7244-4E6A-8935-4EE1D66273D7}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pokespearean", "Pokespearean\Pokespearean.csproj", "{9EE4EE5B-670B-44C4-B327-FED8A35AABA8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pokespearean.Models", "..\Pokespearean.Models\Pokespearean.Models.csproj", "{B62A4320-ED4F-4B29-9CE7-56EC1EBA4A26}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pokespearean.Models", "Pokespearean.Models\Pokespearean.Models.csproj", "{AA12865F-0EC1-4057-AC55-A1CC9B0A66AF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pokespearean.Tests", "Pokespearean.Tests\Pokespearean.Tests.csproj", "{2B740E78-E3E7-4285-BE87-27B188BE0B01}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -13,14 +15,18 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B9920ADA-7244-4E6A-8935-4EE1D66273D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B9920ADA-7244-4E6A-8935-4EE1D66273D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B9920ADA-7244-4E6A-8935-4EE1D66273D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9920ADA-7244-4E6A-8935-4EE1D66273D7}.Release|Any CPU.Build.0 = Release|Any CPU
{B62A4320-ED4F-4B29-9CE7-56EC1EBA4A26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B62A4320-ED4F-4B29-9CE7-56EC1EBA4A26}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B62A4320-ED4F-4B29-9CE7-56EC1EBA4A26}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B62A4320-ED4F-4B29-9CE7-56EC1EBA4A26}.Release|Any CPU.Build.0 = Release|Any CPU
{9EE4EE5B-670B-44C4-B327-FED8A35AABA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9EE4EE5B-670B-44C4-B327-FED8A35AABA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9EE4EE5B-670B-44C4-B327-FED8A35AABA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9EE4EE5B-670B-44C4-B327-FED8A35AABA8}.Release|Any CPU.Build.0 = Release|Any CPU
{AA12865F-0EC1-4057-AC55-A1CC9B0A66AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AA12865F-0EC1-4057-AC55-A1CC9B0A66AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AA12865F-0EC1-4057-AC55-A1CC9B0A66AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AA12865F-0EC1-4057-AC55-A1CC9B0A66AF}.Release|Any CPU.Build.0 = Release|Any CPU
{2B740E78-E3E7-4285-BE87-27B188BE0B01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2B740E78-E3E7-4285-BE87-27B188BE0B01}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B740E78-E3E7-4285-BE87-27B188BE0B01}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B740E78-E3E7-4285-BE87-27B188BE0B01}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

0
.dockerignore → Pokespearean/.dockerignore

57
Pokespearean/Controllers/PokemonController.cs

@ -0,0 +1,57 @@
using Microsoft.AspNetCore.Mvc;
using Pokespearean.Models.Generic;
using Pokespearean.Models.Pokemon;
using Pokespearean.Services;
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
namespace Pokespearean.Controllers
{
[ApiController]
[Route("[controller]")]
public class PokemonController : ControllerBase
{
private readonly IPokemonService PokemonService;
private readonly IShakespeareService ShakespeareService;
public PokemonController(IPokemonService pokemonService,
IShakespeareService shakespeareService)
{
PokemonService = pokemonService;
ShakespeareService = shakespeareService;
}
[Route("{pokemonName}")]
public async Task<IActionResult> Get([FromRoute, Required] string pokemonName)
{
var result = new WebResult();
try
{
result = await PokemonService.GetPokemonDescription(pokemonName);
if (!result.IsValid)
return BadRequest(result);
var pokemonDescription = result.Data as string;
result = await ShakespeareService.ToShakespearean(pokemonDescription);
if (!result.IsValid)
return BadRequest(result);
var shakespeareTranslation = result.Data as string;
var pokeResult = new PokeResult
{
Name = pokemonName,
Description = shakespeareTranslation
};
return Ok(pokeResult);
}
catch (Exception ex)
{
return BadRequest(result.Invalidate(ex.Message, ex));
}
}
}
}

1
Dockerfile → Pokespearean/Dockerfile

@ -1,7 +1,6 @@
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base
USER 1001
WORKDIR /app
EXPOSE 80

0
LICENSE → Pokespearean/LICENSE

12
Pokespearean/Models/Settings.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Pokespearean.Models
{
public class Settings
{
public string ShakespeareApi { get; set; }
}
}

0
Pokespearean.csproj → Pokespearean/Pokespearean.csproj

0
Program.cs → Pokespearean/Program.cs

0
Properties/launchSettings.json → Pokespearean/Properties/launchSettings.json

0
README.md → Pokespearean/README.md

10
Pokespearean/Services/IPokemonService.cs

@ -0,0 +1,10 @@
using Pokespearean.Models.Generic;
using System.Threading.Tasks;
namespace Pokespearean.Services
{
public interface IPokemonService
{
Task<WebResult> GetPokemonDescription(string pokemonName);
}
}

10
Pokespearean/Services/IShakespeareService.cs

@ -0,0 +1,10 @@
using Pokespearean.Models.Generic;
using System.Threading.Tasks;
namespace Pokespearean.Services
{
public interface IShakespeareService
{
Task<WebResult> ToShakespearean(string text);
}
}

38
Pokespearean/Services/PokemonService.cs

@ -0,0 +1,38 @@
using PokeApiNet;
using Pokespearean.Models.Generic;
using System;
using System.Threading.Tasks;
using System.Linq;
namespace Pokespearean.Services
{
public class PokemonService : IPokemonService
{
private readonly PokeApiClient pokeApiClient;
public PokemonService()
{
pokeApiClient = new PokeApiClient();
}
public async Task<WebResult> GetPokemonDescription(string pokemonName)
{
var result = new WebResult();
try
{
var pokemonSpecies = await pokeApiClient.GetResourceAsync<PokemonSpecies>(pokemonName);
var hasRubyVersion = pokemonSpecies.FlavorTextEntries.Where(fte => fte.Language.Name == "en" && fte.Version.Name == "ruby").Any();
result.Data = pokemonSpecies.FlavorTextEntries
.FirstOrDefault(fte => (hasRubyVersion ? fte.Version.Name == "ruby" : true) && fte.Language.Name == "en")?
.FlavorText?.Replace("\n", " ")?.Replace("\f", " ");
return result;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return result.Invalidate(ex.Message, ex);
}
}
}
}

43
Pokespearean/Services/ShakespeareService.cs

@ -0,0 +1,43 @@
using Pokespearean.Models.Generic;
using System;
using System.Threading.Tasks;
using Pokespearean.Models;
using Pokespearean.Models.Shakespeare;
using Flurl;
using Flurl.Http;
using Microsoft.Extensions.Options;
namespace Pokespearean.Services
{
public class ShakespeareService : IShakespeareService
{
private string shakespeareApi;
public ShakespeareService(IOptions<Settings> settingsOption)
{
shakespeareApi = settingsOption.Value.ShakespeareApi;
}
public async Task<WebResult> ToShakespearean(string text)
{
var result = new WebResult();
try
{
var response = await shakespeareApi
.AppendPathSegment("translate")
.AppendPathSegment("shakespeare.json")
.SetQueryParam(nameof(text), text, isEncoded: true)
.GetJsonAsync<ShakespearResponse>();
result.Data = response.Contents.Translated;
return result;
}
catch (System.Exception ex)
{
Console.WriteLine(ex.Message);
return result.Invalidate(ex.Message, ex);
}
}
}
}

20
Startup.cs → Pokespearean/Startup.cs

@ -1,15 +1,10 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Pokespearean.Models;
using Pokespearean.Services;
namespace Pokespearean
{
@ -22,21 +17,19 @@ namespace Pokespearean
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IPokemonService, PokemonService>();
services.AddScoped<IShakespeareService, ShakespeareService>();
services.Configure<Settings>(Configuration.GetSection(nameof(Settings)));
services.AddControllers().AddJsonOptions(options =>
{
//options.JsonSerializerOptions.MaxDepth = 0;
//options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
//options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.IgnoreReadOnlyFields = true;
options.JsonSerializerOptions.IgnoreReadOnlyProperties = true;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
@ -45,9 +38,6 @@ namespace Pokespearean
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();

0
appsettings.Development.json → Pokespearean/appsettings.Development.json

3
appsettings.json → Pokespearean/appsettings.json

@ -1,4 +1,7 @@
{
"Settings": {
"ShakespeareApi": "https://api.funtranslations.com"
},
"Logging": {
"LogLevel": {
"Default": "Information",

15
WeatherForecast.cs

@ -1,15 +0,0 @@
using System;
namespace Pokespearean
{
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string Summary { get; set; }
}
}
Loading…
Cancel
Save