Skip to content

Instantly share code, notes, and snippets.

@jsheridanwells
Created August 7, 2020 18:05
Show Gist options
  • Save jsheridanwells/792f380e030039c5de3ec8afa74986f0 to your computer and use it in GitHub Desktop.
Save jsheridanwells/792f380e030039c5de3ec8afa74986f0 to your computer and use it in GitHub Desktop.
wws # 4 files
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;
}
}
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);
}
}
}
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}";
}
}
}
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);
}
}
}
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);
}
}
}
}
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