diff --git a/src/Robware.Api.Blog.Tests/BlogControllerTests.cs b/src/Robware.Api.Blog.Tests/BlogControllerTests.cs new file mode 100644 index 0000000..37eb192 --- /dev/null +++ b/src/Robware.Api.Blog.Tests/BlogControllerTests.cs @@ -0,0 +1,52 @@ +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using NSubstitute; +using NSubstitute.ExceptionExtensions; +using Robware.Api.Blog.Controllers; +using Robware.Blog; +using Robware.Data; +using Xunit; + +namespace Robware.Api.Blog.Tests { + public class BlogControllerTests { + [Fact] + public async Task Get_WithUrl_ReturnsBlogPost() { + var logger = Substitute.For>(); + var repo = Substitute.For(); + + repo.GetPostByUrlAsync("url").Returns(new BlogPost()); + + var expectation = new BlogPost(); + + var controller = new BlogController(logger, repo); + (await controller.Get("url")).Value.Should().BeEquivalentTo(expectation); + } + + [Fact] + public async Task Get_WithId_ReturnsBlogPost() { + var logger = Substitute.For>(); + var repo = Substitute.For(); + + repo.GetPostByIdAsync(1).Returns(new BlogPost()); + + var expectation = new BlogPost(); + + var controller = new BlogController(logger, repo); + (await controller.Get("1")).Value.Should().BeEquivalentTo(expectation); + } + + [Fact] + public async Task Get_WithInvalidUrl_Returns404() { + var logger = Substitute.For>(); + var repo = Substitute.For(); + + repo.GetPostByUrlAsync("url").Throws(new ItemNotFoundException("", null)); + + var controller = new BlogController(logger, repo); + (await controller.Get("url")).Result.Should().BeOfType(); + + } + } +} diff --git a/src/Robware.Api.Blog.Tests/Robware.Api.Blog.Tests.csproj b/src/Robware.Api.Blog.Tests/Robware.Api.Blog.Tests.csproj new file mode 100644 index 0000000..04a0fda --- /dev/null +++ b/src/Robware.Api.Blog.Tests/Robware.Api.Blog.Tests.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + + + diff --git a/src/Robware.Api.Blog.sln b/src/Robware.Api.Blog.sln index 5e5a5e3..bae9257 100644 --- a/src/Robware.Api.Blog.sln +++ b/src/Robware.Api.Blog.sln @@ -16,6 +16,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\.editorconfig = ..\.editorconfig EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robware.Api.Blog.Tests", "Robware.Api.Blog.Tests\Robware.Api.Blog.Tests.csproj", "{54DEC274-5F28-42FB-9B60-1E4BAF3D7BF9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -38,6 +40,10 @@ Global {EEB41C17-B61C-451A-9C72-2B405DC42743}.Debug|Any CPU.Build.0 = Debug|Any CPU {EEB41C17-B61C-451A-9C72-2B405DC42743}.Release|Any CPU.ActiveCfg = Release|Any CPU {EEB41C17-B61C-451A-9C72-2B405DC42743}.Release|Any CPU.Build.0 = Release|Any CPU + {54DEC274-5F28-42FB-9B60-1E4BAF3D7BF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {54DEC274-5F28-42FB-9B60-1E4BAF3D7BF9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {54DEC274-5F28-42FB-9B60-1E4BAF3D7BF9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {54DEC274-5F28-42FB-9B60-1E4BAF3D7BF9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Robware.Api.Blog/Controllers/BlogController.cs b/src/Robware.Api.Blog/Controllers/BlogController.cs index f83ea5b..6a2f1ca 100644 --- a/src/Robware.Api.Blog/Controllers/BlogController.cs +++ b/src/Robware.Api.Blog/Controllers/BlogController.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Robware.Blog; +using Robware.Data; namespace Robware.Api.Blog.Controllers { [ApiController] @@ -16,14 +17,20 @@ namespace Robware.Api.Blog.Controllers { } [HttpGet(nameof(Get) + "/{url}")] - public async Task Get(string url) { + public async Task> Get(string url) { _logger.Log(LogLevel.Information, $"{nameof(Get)}: {nameof(url)}={url}"); - if (int.TryParse(url, out int id)) { - return await _blogRepository.GetPostByIdAsync(id); - } + try { + if (int.TryParse(url, out int id)) { + return await _blogRepository.GetPostByIdAsync(id); + } - return await _blogRepository.GetPostByUrlAsync(url); + return await _blogRepository.GetPostByUrlAsync(url); + } + catch (ItemNotFoundException e) { + _logger.Log(LogLevel.Error, e.Message); + return NotFound("Could not find blog post"); + } } //[HttpGet] diff --git a/src/Robware.Data/BlogRepository.cs b/src/Robware.Data/BlogRepository.cs index 116ee30..fbe8abf 100644 --- a/src/Robware.Data/BlogRepository.cs +++ b/src/Robware.Data/BlogRepository.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Data; using System.Linq; using System.Threading.Tasks; using Dapper; @@ -23,11 +24,20 @@ namespace Robware.Data UserId = state.User_Id }; + private async Task> DoQuery(IDbConnection connection, string query, object param = null) { + var result = await connection.QueryAsync(query, param); + + if (!result.Any()) + throw new ItemNotFoundException(query, param); + + return result; + } + public async Task GetPostByUrlAsync(string url) { const string query = "SELECT * FROM blog_posts WHERE post_url=@url AND post_deleted=0"; using (var connection = _dbProvider.NewConnection()) { connection.Open(); - var result = await connection.QueryAsync(query, new{url}); + var result = await DoQuery(connection, query, new{url}); return MapBlogPost(result.First()); } } @@ -36,7 +46,7 @@ namespace Robware.Data const string query = "SELECT * FROM blog_posts WHERE post_content<>'' AND post_deleted=0 ORDER BY post_timestamp DESC LIMIT @offset,@limit"; using (var connection = _dbProvider.NewConnection()) { connection.Open(); - var results = await connection.QueryAsync(query, new{limit, offset}); + var results = await DoQuery(connection, query, new{limit, offset}); return results.Select(MapBlogPost); } } @@ -48,7 +58,7 @@ namespace Robware.Data var query="SELECT COUNT(*) FROM blog_posts WHERE post_content<>'' AND post_deleted=0"; using(var connection = _dbProvider.NewConnection()) { connection.Open(); - var result = await connection.QueryAsync(query); + var result = await DoQuery(connection, query); return result.First(); } } @@ -57,7 +67,7 @@ namespace Robware.Data const string query = "SELECT * FROM blog_posts WHERE post_id=@id AND post_deleted=0"; using (var connection = _dbProvider.NewConnection()) { connection.Open(); - var result = await connection.QueryAsync(query, new {id}); + var result = await DoQuery(connection, query, new {id}); return MapBlogPost(result.First()); } } @@ -70,7 +80,7 @@ namespace Robware.Data using (var connection = _dbProvider.NewConnection()) { connection.Open(); - var result = await connection.QueryAsync(newPost ? newPostQuery : updatePostQuery, post); + var result = await DoQuery(connection, newPost ? newPostQuery : updatePostQuery, post); return newPost ? await GetPostByIdAsync(result.Single()) : post; } } @@ -80,7 +90,7 @@ namespace Robware.Data using (var connection = _dbProvider.NewConnection()) { connection.Open(); - var result = await connection.QueryAsync(query); + var result = await DoQuery(connection, query); return result.Select(MapBlogPost); } } diff --git a/src/Robware.Data/ItemNotFoundException.cs b/src/Robware.Data/ItemNotFoundException.cs new file mode 100644 index 0000000..c097c95 --- /dev/null +++ b/src/Robware.Data/ItemNotFoundException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Robware.Data { + public class ItemNotFoundException:Exception { + public object Parameters { get; } + + public ItemNotFoundException(string query, object parameters):base($"Could not find an item for the query: {query}") { + Parameters = parameters; + } + } +} \ No newline at end of file