diff --git a/src/ApplicationCore/Exceptions/CatalogImageMissingException.cs b/src/ApplicationCore/Exceptions/CatalogImageMissingException.cs new file mode 100644 index 0000000..ff07258 --- /dev/null +++ b/src/ApplicationCore/Exceptions/CatalogImageMissingException.cs @@ -0,0 +1,18 @@ +using System; + +namespace ApplicationCore.Exceptions +{ + public class CatalogImageMissingException : Exception + { + public CatalogImageMissingException(string message, + Exception innerException = null) + : base(message, innerException: innerException) + { + } + public CatalogImageMissingException(Exception innerException) + : base("No catalog image found for the provided id.", + innerException: innerException) + { + } + } +} diff --git a/src/ApplicationCore/Interfaces/IAppLogger.cs b/src/ApplicationCore/Interfaces/IAppLogger.cs new file mode 100644 index 0000000..1ae198d --- /dev/null +++ b/src/ApplicationCore/Interfaces/IAppLogger.cs @@ -0,0 +1,7 @@ +namespace ApplicationCore.Interfaces +{ + public interface IAppLogger + { + void LogWarning(string message, params object[] args); + } +} diff --git a/src/ApplicationCore/Interfaces/IBasketService.cs b/src/ApplicationCore/Interfaces/IBasketService.cs index 1b868a1..a5f25b9 100644 --- a/src/ApplicationCore/Interfaces/IBasketService.cs +++ b/src/ApplicationCore/Interfaces/IBasketService.cs @@ -14,5 +14,4 @@ namespace ApplicationCore.Interfaces { T Parse(IPrincipal principal); } - } diff --git a/src/ApplicationCore/Interfaces/IImageService.cs b/src/ApplicationCore/Interfaces/IImageService.cs new file mode 100644 index 0000000..d2d01c7 --- /dev/null +++ b/src/ApplicationCore/Interfaces/IImageService.cs @@ -0,0 +1,7 @@ +namespace ApplicationCore.Interfaces +{ + public interface IImageService + { + byte[] GetImageBytesById(int id); + } +} diff --git a/src/Infrastructure/FileSystem/LocalFileImageService.cs b/src/Infrastructure/FileSystem/LocalFileImageService.cs new file mode 100644 index 0000000..82db8b2 --- /dev/null +++ b/src/Infrastructure/FileSystem/LocalFileImageService.cs @@ -0,0 +1,30 @@ +using ApplicationCore.Exceptions; +using ApplicationCore.Interfaces; +using Microsoft.AspNetCore.Hosting; +using System.IO; + +namespace Infrastructure.FileSystem +{ + public class LocalFileImageService : IImageService + { + private readonly IHostingEnvironment _env; + + public LocalFileImageService(IHostingEnvironment env) + { + _env = env; + } + public byte[] GetImageBytesById(int id) + { + try + { + var contentRoot = _env.ContentRootPath + "//Pics"; + var path = Path.Combine(contentRoot, id + ".png"); + return File.ReadAllBytes(path); + } + catch (FileNotFoundException ex) + { + throw new CatalogImageMissingException(ex); + } + } + } +} diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index b377567..c2c8f95 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -14,5 +14,8 @@ + + + \ No newline at end of file diff --git a/src/Infrastructure/Logging/LoggerAdapter.cs b/src/Infrastructure/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..b79e843 --- /dev/null +++ b/src/Infrastructure/Logging/LoggerAdapter.cs @@ -0,0 +1,18 @@ +using ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + } +} diff --git a/src/Web/Controllers/CatalogController.cs b/src/Web/Controllers/CatalogController.cs index 25d50ae..0874cd3 100644 --- a/src/Web/Controllers/CatalogController.cs +++ b/src/Web/Controllers/CatalogController.cs @@ -3,34 +3,42 @@ using Microsoft.eShopWeb.ViewModels; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using System; -using System.IO; using System.Threading.Tasks; +using ApplicationCore.Interfaces; +using ApplicationCore.Exceptions; +using Microsoft.Extensions.Logging; namespace Microsoft.eShopWeb.Controllers { public class CatalogController : Controller { private readonly IHostingEnvironment _env; - private readonly ICatalogService _catalogSvc; + private readonly ICatalogService _catalogService; + private readonly IImageService _imageService; + private readonly IAppLogger _logger; - public CatalogController(IHostingEnvironment env, ICatalogService catalogSvc) + public CatalogController(IHostingEnvironment env, + ICatalogService catalogService, + IImageService imageService, + IAppLogger logger) { _env = env; - _catalogSvc = catalogSvc; - } - + _catalogService = catalogService; + _imageService = imageService; + _logger = logger; + } // GET: // public async Task Index(int? BrandFilterApplied, int? TypesFilterApplied, int? page) { var itemsPage = 10; - var catalog = await _catalogSvc.GetCatalogItems(page ?? 0, itemsPage, BrandFilterApplied, TypesFilterApplied); + var catalog = await _catalogService.GetCatalogItems(page ?? 0, itemsPage, BrandFilterApplied, TypesFilterApplied); var vm = new CatalogIndex() { CatalogItems = catalog.Data, - Brands = await _catalogSvc.GetBrands(), - Types = await _catalogSvc.GetTypes(), + Brands = await _catalogService.GetBrands(), + Types = await _catalogService.GetTypes(), BrandFilterApplied = BrandFilterApplied ?? 0, TypesFilterApplied = TypesFilterApplied ?? 0, PaginationInfo = new PaginationInfo() @@ -48,18 +56,23 @@ namespace Microsoft.eShopWeb.Controllers return View(vm); } - [HttpGet("{id}")] - [Route("[controller]/pic/{id}")] - // GET: //pic/{id} + [HttpGet("[controller]/pic/{id}")] public IActionResult GetImage(int id) { - var contentRoot = _env.ContentRootPath + "//Pics"; - var path = Path.Combine(contentRoot, id + ".png"); - Byte[] b = System.IO.File.ReadAllBytes(path); - return File(b, "image/png"); - + byte[] imageBytes; + try + { + imageBytes = _imageService.GetImageBytesById(id); + } + catch (CatalogImageMissingException ex) + { + _logger.LogWarning($"No image found for id: {id}"); + return NotFound(); + } + return File(imageBytes, "image/png"); } - + + public IActionResult Error() { return View(); diff --git a/src/Web/Startup.cs b/src/Web/Startup.cs index 0bf788f..7c2f2ed 100644 --- a/src/Web/Startup.cs +++ b/src/Web/Startup.cs @@ -11,6 +11,9 @@ using Infrastructure.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using System.Text; using Microsoft.AspNetCore.Http; +using ApplicationCore.Interfaces; +using Infrastructure.FileSystem; +using Infrastructure.Logging; namespace Microsoft.eShopWeb { @@ -65,6 +68,8 @@ namespace Microsoft.eShopWeb services.AddScoped(); services.AddScoped(); services.Configure(Configuration); + services.AddSingleton(); + services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>)); services.AddMvc(); _services = services; diff --git a/tests/IntegrationTests/IntegrationTests.csproj b/tests/IntegrationTests/IntegrationTests.csproj index f032596..c3a4881 100644 --- a/tests/IntegrationTests/IntegrationTests.csproj +++ b/tests/IntegrationTests/IntegrationTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp1.1 @@ -10,4 +10,12 @@ + + + + + + + + diff --git a/tests/IntegrationTests/Web/Controllers/CatalogControllerGetImage.cs b/tests/IntegrationTests/Web/Controllers/CatalogControllerGetImage.cs new file mode 100644 index 0000000..0217ad1 --- /dev/null +++ b/tests/IntegrationTests/Web/Controllers/CatalogControllerGetImage.cs @@ -0,0 +1,17 @@ +using Microsoft.eShopWeb.Controllers; +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace IntegrationTests.Web.Controllers +{ + public class CatalogControllerGetImage + { + [Fact] + public void ReturnsFileContentResultGivenValidId() + { + //var controller = new CatalogController() + } + } +} diff --git a/tests/UnitTests/UnitTests.csproj b/tests/UnitTests/UnitTests.csproj index f032596..18ba44b 100644 --- a/tests/UnitTests/UnitTests.csproj +++ b/tests/UnitTests/UnitTests.csproj @@ -1,13 +1,25 @@ - + netcoreapp1.1 + + + + + + + + + + + + diff --git a/tests/UnitTests/Web/Controllers/CatalogControllerGetImage.cs b/tests/UnitTests/Web/Controllers/CatalogControllerGetImage.cs new file mode 100644 index 0000000..2c65931 --- /dev/null +++ b/tests/UnitTests/Web/Controllers/CatalogControllerGetImage.cs @@ -0,0 +1,83 @@ +using ApplicationCore.Exceptions; +using ApplicationCore.Interfaces; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.eShopWeb.Controllers; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +namespace UnitTests +{ + public class CatalogControllerGetImage + { + private Mock _mockImageService = new Mock(); + private Mock> _mockLogger = new Mock>(); + private CatalogController _controller; + private int _testImageId = 123; + private byte[] _testBytes = { 0x01, 0x02, 0x03 }; + + public CatalogControllerGetImage() + { + _controller = new CatalogController(null, null, _mockImageService.Object, + _mockLogger.Object); + } + + [Fact] + public void CallsImageServiceWithId() + { + SetupImageWithTestBytes(); + + _controller.GetImage(_testImageId); + _mockImageService.Verify(); + } + + [Fact] + public void ReturnsFileResultWithBytesGivenSuccess() + { + SetupImageWithTestBytes(); + + var result = _controller.GetImage(_testImageId); + + var fileResult = Assert.IsType(result); + var bytes = Assert.IsType(fileResult.FileContents); + } + + [Fact] + public void ReturnsNotFoundResultGivenImageMissingException() + { + SetupMissingImage(); + + var result = _controller.GetImage(_testImageId); + + var actionResult = Assert.IsType(result); + } + + [Fact] + public void LogsWarningGivenImageMissingException() + { + SetupMissingImage(); + _mockLogger.Setup(l => l.LogWarning(It.IsAny())) + .Verifiable(); + + _controller.GetImage(_testImageId); + + _mockLogger.Verify(); + } + + private void SetupMissingImage() + { + _mockImageService + .Setup(i => i.GetImageBytesById(_testImageId)) + .Throws(new CatalogImageMissingException("missing image")); + } + + private void SetupImageWithTestBytes() + { + _mockImageService + .Setup(i => i.GetImageBytesById(_testImageId)) + .Returns(_testBytes) + .Verifiable(); + } + } +}