Created
August 7, 2020 18:05
-
-
Save jsheridanwells/792f380e030039c5de3ec8afa74986f0 to your computer and use it in GitHub Desktop.
wws # 4 files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Net; | |
namespace WeatherWalkingSkeleton.Infrastructure | |
{ | |
public class OpenWeatherException : Exception | |
{ | |
public HttpStatusCode StatusCode { get; } | |
public OpenWeatherException() { } | |
public OpenWeatherException(HttpStatusCode statusCode) | |
=> StatusCode = statusCode; | |
public OpenWeatherException(HttpStatusCode statusCode, string message) : base(message) | |
=> StatusCode = statusCode; | |
public OpenWeatherException(HttpStatusCode statusCode, string message, Exception inner) : base(message, inner) | |
=> StatusCode = statusCode; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using System.Net.Http; | |
using System.Text.Json; | |
using WeatherWalkingSkeleton.Models; | |
namespace WeatherWalkingSkeleton.Tests.Infrastructure | |
{ | |
public static class OpenWeatherResponses | |
{ | |
public static StringContent OkResponse => BuildOkResponse(); | |
public static StringContent UnauthorizedResponse => BuildUnauthorizedResponse(); | |
public static StringContent NotFoundResponse => BuildNotFoundResponse(); | |
public static StringContent InternalErrorResponse => BuildInternalErrorResponse(); | |
private static StringContent BuildOkResponse() | |
{ | |
var response = new OpenWeatherResponse | |
{ | |
Forecasts = new List<Forecast> | |
{ | |
new Forecast{ Dt = 1594155600, Temps = new Temps { Temp = (decimal)32.93 } } | |
} | |
}; | |
var json = JsonSerializer.Serialize(response); | |
return new StringContent(json); | |
} | |
private static StringContent BuildUnauthorizedResponse() | |
{ | |
var json = JsonSerializer.Serialize(new { Cod = 401, Message = "Invalid Api key." }); | |
return new StringContent(json); | |
} | |
private static StringContent BuildNotFoundResponse() | |
{ | |
var json = JsonSerializer.Serialize(new { Cod = 404, Message = "city not found" }); | |
return new StringContent(json); | |
} | |
private static StringContent BuildInternalErrorResponse() | |
{ | |
var json = JsonSerializer.Serialize(new {Cod = 500, Message = "Internal Error."}); | |
return new StringContent(json); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Net.Http; | |
using System.Text.Json; | |
using System.Text.Json.Serialization; | |
using System.Threading.Tasks; | |
using Microsoft.Extensions.Options; | |
using WeatherWalkingSkeleton.Config; | |
using WeatherWalkingSkeleton.Infrastructure; | |
using WeatherWalkingSkeleton.Models; | |
namespace WeatherWalkingSkeleton.Services | |
{ | |
public enum Unit | |
{ | |
Metric, | |
Imperial, | |
Kelvin | |
} | |
public interface IOpenWeatherService | |
{ | |
Task<List<WeatherForecast>> GetFiveDayForecastAsync(string location, Unit unit = Unit.Metric); | |
} | |
public class OpenWeatherService : IOpenWeatherService | |
{ | |
private readonly OpenWeather _openWeatherConfig; | |
private readonly IHttpClientFactory _httpFactory; | |
public OpenWeatherService(IOptions<OpenWeather> opts, IHttpClientFactory httpFactory) | |
{ | |
_openWeatherConfig = opts.Value; | |
_httpFactory = httpFactory; | |
} | |
public async Task<List<WeatherForecast>> GetFiveDayForecastAsync(string location, Unit unit = Unit.Metric) | |
{ | |
string url = BuildOpenWeatherUrl("forecast", location, unit); | |
var forecasts = new List<WeatherForecast>(); | |
var client = _httpFactory.CreateClient("OpenWeatherClient"); | |
var response = await client.GetAsync(url); | |
if (response.IsSuccessStatusCode) | |
{ | |
var json = await response.Content.ReadAsStringAsync(); | |
var openWeatherResponse = JsonSerializer.Deserialize<OpenWeatherResponse>(json); | |
foreach (var forecast in openWeatherResponse.Forecasts) | |
{ | |
forecasts.Add(new WeatherForecast | |
{ | |
Date = new DateTime(forecast.Dt), | |
Temp = forecast.Temps.Temp, | |
FeelsLike = forecast.Temps.FeelsLike, | |
TempMin = forecast.Temps.TempMin, | |
TempMax = forecast.Temps.TempMax, | |
}); | |
} | |
return forecasts; | |
} | |
else | |
{ | |
throw new OpenWeatherException(response.StatusCode, "Error response from OpenWeatherApi: " + response.ReasonPhrase); | |
} | |
} | |
private string BuildOpenWeatherUrl(string resource, string location, Unit unit) | |
{ | |
return $"https://api.openweathermap.org/data/2.5/{resource}" + | |
$"?appid={_openWeatherConfig.ApiKey}" + | |
$"&q={location}" + | |
$"&units={unit}"; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Net; | |
using System.Threading.Tasks; | |
using WeatherWalkingSkeleton.Infrastructure; | |
using WeatherWalkingSkeleton.Models; | |
using WeatherWalkingSkeleton.Tests.Infrastructure; | |
using Xunit; | |
namespace WeatherWalkingSkeleton.Services | |
{ | |
public class OpenWeatherService_Tests | |
{ | |
[Fact] | |
public async Task Returns_A_WeatherForecast() | |
{ | |
var opts = OptionsBuilder.OpenWeatherConfig(); | |
var clientFactory = ClientBuilder.OpenWeatherClientFactory(OpenWeatherResponses.OkResponse); | |
var sut = new OpenWeatherService(opts, clientFactory); | |
var result = await sut.GetFiveDayForecastAsync("Chicago"); | |
Assert.IsType<List<WeatherForecast>>(result); | |
} | |
[Fact] | |
public async Task Returns_Expected_Values_From_the_Api() | |
{ | |
var opts = OptionsBuilder.OpenWeatherConfig(); | |
var clientFactory = ClientBuilder.OpenWeatherClientFactory(OpenWeatherResponses.OkResponse); | |
var sut = new OpenWeatherService(opts, clientFactory); | |
var result = await sut.GetFiveDayForecastAsync("Chicago"); | |
Assert.Equal(new DateTime(1594155600), result[0].Date); | |
Assert.Equal((decimal)32.93, result[0].Temp); | |
} | |
[Fact] | |
public async Task Returns_OpenWeatherException_When_Unauthorized() | |
{ | |
var opts = OptionsBuilder.OpenWeatherConfig(); | |
var clientFactory = ClientBuilder.OpenWeatherClientFactory(OpenWeatherResponses.UnauthorizedResponse, | |
HttpStatusCode.Unauthorized); | |
var sut = new OpenWeatherService(opts, clientFactory); | |
var result = await Assert.ThrowsAsync<OpenWeatherException>(() => sut.GetFiveDayForecastAsync("Chicago")); | |
Assert.Equal(401, (int)result.StatusCode); | |
} | |
[Fact] | |
public async Task Returns_OpenWeatherException_When_Called_With_Bad_Argument() | |
{ | |
var opts = OptionsBuilder.OpenWeatherConfig(); | |
var clientFactory = ClientBuilder.OpenWeatherClientFactory(OpenWeatherResponses.NotFoundResponse, | |
HttpStatusCode.NotFound); | |
var sut = new OpenWeatherService(opts, clientFactory); | |
var result = await Assert.ThrowsAsync<OpenWeatherException>(() => sut.GetFiveDayForecastAsync("Westeros")); | |
Assert.Equal(404, (int)result.StatusCode); | |
} | |
[Fact] | |
public async Task Returns_OpenWeatherException_On_OpenWeatherInternalError() | |
{ | |
var opts = OptionsBuilder.OpenWeatherConfig(); | |
var clientFactory = ClientBuilder.OpenWeatherClientFactory(OpenWeatherResponses.InternalErrorResponse, | |
HttpStatusCode.InternalServerError); | |
var sut = new OpenWeatherService(opts, clientFactory); | |
var result = await Assert.ThrowsAsync<OpenWeatherException>(() => sut.GetFiveDayForecastAsync("New York")); | |
Assert.Equal(500, (int)result.StatusCode); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Net; | |
using System.Threading.Tasks; | |
using Microsoft.AspNetCore.Mvc; | |
using Microsoft.Extensions.Logging; | |
using WeatherWalkingSkeleton.Infrastructure; | |
using WeatherWalkingSkeleton.Services; | |
namespace WeatherWalkingSkeleton.Controllers | |
{ | |
[ApiController] | |
[Route("[controller]")] | |
public class WeatherForecastController : ControllerBase | |
{ | |
private readonly ILogger<WeatherForecastController> _logger; | |
private readonly IOpenWeatherService _weatherService; | |
public WeatherForecastController(ILogger<WeatherForecastController> logger, IOpenWeatherService weatherService) | |
{ | |
_logger = logger; | |
_weatherService = weatherService; | |
} | |
[HttpGet] | |
public async Task<IActionResult> Get(string location, Unit unit = Unit.Metric) | |
{ | |
try | |
{ | |
var forecast = await _weatherService.GetFiveDayForecastAsync(location, unit); | |
return Ok(forecast); | |
} | |
catch (OpenWeatherException e) | |
{ | |
if (e.StatusCode == HttpStatusCode.NotFound) | |
return BadRequest($"Location: \"{ location }\" not found."); | |
else | |
return StatusCode(500, e.Message); | |
} | |
catch (Exception e) | |
{ | |
return StatusCode(500, e.Message); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using System.Net; | |
using System.Threading.Tasks; | |
using Microsoft.AspNetCore.Mvc; | |
using Microsoft.Extensions.Logging.Abstractions; | |
using WeatherWalkingSkeleton.Controllers; | |
using WeatherWalkingSkeleton.Models; | |
using WeatherWalkingSkeleton.Services; | |
using WeatherWalkingSkeleton.Tests.Infrastructure; | |
using Xunit; | |
namespace WeatherWalkingSkeleton.Tests.Controllers_Tests | |
{ | |
public class WeatherForecastController_Tests | |
{ | |
[Fact] | |
public async Task Returns_OkResult_With_WeatherForecast() | |
{ | |
var opts = OptionsBuilder.OpenWeatherConfig(); | |
var clientFactory = ClientBuilder.OpenWeatherClientFactory(OpenWeatherResponses.OkResponse); | |
var service = new OpenWeatherService(opts, clientFactory); | |
var sut = new WeatherForecastController(new NullLogger<WeatherForecastController>(), service); | |
var result = await sut.Get("Chicago") as OkObjectResult; | |
Assert.IsType<List<WeatherForecast>>(result.Value); | |
Assert.Equal(200, result.StatusCode); | |
} | |
[Fact] | |
public async Task Returns_500_When_Api_Returns_Error() | |
{ | |
var opts = OptionsBuilder.OpenWeatherConfig(); | |
var clientFactory = ClientBuilder.OpenWeatherClientFactory(OpenWeatherResponses.UnauthorizedResponse, | |
HttpStatusCode.Unauthorized); | |
var service = new OpenWeatherService(opts, clientFactory); | |
var sut = new WeatherForecastController(new NullLogger<WeatherForecastController>(), service); | |
var result = await sut.Get("Rio de Janeiro") as ObjectResult; | |
Assert.Contains("Error response from OpenWeatherApi: Unauthorized", result.Value.ToString()); | |
Assert.Equal(500, result.StatusCode); | |
} | |
[Fact] | |
public async Task Returns_BadRequestResult_When_Location_Not_Found() | |
{ | |
var opts = OptionsBuilder.OpenWeatherConfig(); | |
var clientFactory = ClientBuilder.OpenWeatherClientFactory(OpenWeatherResponses.NotFoundResponse, | |
HttpStatusCode.NotFound); | |
var service = new OpenWeatherService(opts, clientFactory); | |
var sut = new WeatherForecastController(new NullLogger<WeatherForecastController>(), service); | |
var result = await sut.Get("Westworld") as ObjectResult; | |
Assert.Contains("not found", result.Value.ToString()); | |
Assert.Equal(400, result.StatusCode); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment