From fd92d38cdd8d51e235cc794c10657c4efcf88d83 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 8 Sep 2024 13:17:54 -0500 Subject: [PATCH] Added first controller unit test as well as several mock services --- ComiServ/Controllers/ComicController.cs | 2 + .../ContollerTests/ComicControllerTests.cs | 87 +++++++++++++++++++ ComiServUnitTest/DatabaseHelper.cs | 25 ++++++ ComiServUnitTest/Mocks/MockComicAnalyzer.cs | 75 ++++++++++++++++ ComiServUnitTest/Mocks/MockConfig.cs | 20 +++++ .../Mocks/MockPictureConverter.cs | 33 +++++++ 6 files changed, 242 insertions(+) create mode 100644 ComiServUnitTest/ContollerTests/ComicControllerTests.cs create mode 100644 ComiServUnitTest/DatabaseHelper.cs create mode 100644 ComiServUnitTest/Mocks/MockComicAnalyzer.cs create mode 100644 ComiServUnitTest/Mocks/MockConfig.cs create mode 100644 ComiServUnitTest/Mocks/MockPictureConverter.cs diff --git a/ComiServ/Controllers/ComicController.cs b/ComiServ/Controllers/ComicController.cs index d262e64..729c18e 100644 --- a/ComiServ/Controllers/ComicController.cs +++ b/ComiServ/Controllers/ComicController.cs @@ -313,6 +313,8 @@ public class ComicController(ComicsContext context, ILogger log var comic = await _context.Comics.SingleOrDefaultAsync(c => c.Handle == handle); if (comic is null) return NotFound(RequestError.ComicNotFound); + //can't mock this easily, move to a service? + //maybe IComicAnalyzer should be used even just to read a file var data = await System.IO.File.ReadAllBytesAsync(Path.Join(_config.LibraryRoot, comic.Filepath)); return File(data, "application/octet-stream", new FileInfo(comic.Filepath).Name); } diff --git a/ComiServUnitTest/ContollerTests/ComicControllerTests.cs b/ComiServUnitTest/ContollerTests/ComicControllerTests.cs new file mode 100644 index 0000000..f41b04f --- /dev/null +++ b/ComiServUnitTest/ContollerTests/ComicControllerTests.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ComiServ; +using ComiServ.Entities; +using ComiServ.Services; +using ComiServ.Controllers; +using Microsoft.CodeAnalysis.Elfie.Diagnostics; +using Microsoft.Extensions.Logging; +using ComiServUnitTest.Mocks; +using ComiServ.Background; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; + +namespace ComiServUnitTest.ContollerTests; + +[TestClass] +public class ComicControllerTests +{ + private ILoggerFactory? _factory; + public ILoggerFactory factory + { get + { + _factory = _factory ?? LoggerFactory.Create(builder => builder.AddConsole()); + return _factory; + } + } + + [TestMethod] + public async Task TestGetPage() + { + const int PAGE_NUMBER = 7; + const string PAGE_FILEPATH = "Page File.jpg"; + const string PAGE_MIME = "image/jpeg"; + var context = DatabaseHelper.CreateDatabase(); + Comic comic = new() + { + Title = "Test Comic", + Filepath = Path.Join("test", "filepath"), + Handle = string.Join("", Enumerable.Repeat("A", ComicsContext.HANDLE_LENGTH)), + Description = "" + }; + context.Comics.Add(comic); + User user = new() + { + Username = "user", + UserTypeId = UserTypeEnum.User, + Salt = User.MakeSalt(), + HashedPassword = User.Hash([], []), + }; + context.Users.Add(user); + context.SaveChanges(); + Configuration config = new() + { + AutoScanPeriodHours = 24, + DatabaseFile = "", + LibraryRoot = "root", + }; + ComicPage comicPage = new(PAGE_FILEPATH, PAGE_MIME, [1, 2, 3, 4, 5]); + MockComicAnalyzer analyzer = new(); + analyzer.ComicPages.Add((Path.Join(config.LibraryRoot, comic.Filepath), PAGE_NUMBER), comicPage); + IPictureConverter converter = new MockPictureConverter(); + AuthenticationService auth = new(); + auth.Authenticate(user); + var controller = new ComicController( + context, + factory.CreateLogger(), + new MockConfig(config), + analyzer, + converter, + auth + ); + //get actual page + var result = await controller.GetComicPage(comic.Handle, PAGE_NUMBER, null, null, null); + Assert.IsInstanceOfType(result); + var contents = ((FileContentResult)result).FileContents; + Assert.IsTrue(comicPage.Data.SequenceEqual(contents)); + //invalid handle (too short) + var result2 = await controller.GetComicFile(string.Join("", Enumerable.Repeat("A", ComicsContext.HANDLE_LENGTH - 1))); + Assert.IsInstanceOfType(result2); + //valid handle but doesn't exist + var result3 = await controller.GetComicFile(string.Join("", Enumerable.Repeat("B", ComicsContext.HANDLE_LENGTH))); + Assert.IsInstanceOfType(result3); + } +} diff --git a/ComiServUnitTest/DatabaseHelper.cs b/ComiServUnitTest/DatabaseHelper.cs new file mode 100644 index 0000000..253daf0 --- /dev/null +++ b/ComiServUnitTest/DatabaseHelper.cs @@ -0,0 +1,25 @@ +using ComiServ; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ComiServUnitTest; + +internal static class DatabaseHelper +{ + public static ComicsContext CreateDatabase() + { + var connection = new SqliteConnection("Filename=:memory:"); + connection.Open(); + var contextOptions = new DbContextOptionsBuilder() + .UseSqlite(connection) + .Options; + var context = new ComicsContext(contextOptions); + context.Database.EnsureCreated(); + return context; + } +} diff --git a/ComiServUnitTest/Mocks/MockComicAnalyzer.cs b/ComiServUnitTest/Mocks/MockComicAnalyzer.cs new file mode 100644 index 0000000..4cc0d02 --- /dev/null +++ b/ComiServUnitTest/Mocks/MockComicAnalyzer.cs @@ -0,0 +1,75 @@ +using ComiServ.Background; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ComiServUnitTest.Mocks +{ + internal class MockComicAnalyzer : IComicAnalyzer + { + //preseed these + public readonly Dictionary AnalysisResults = []; + public readonly HashSet ComicsThatExist = []; + public readonly Dictionary<(string, int), ComicPage> ComicPages = []; + + //check these afterwards + public readonly List Analyzed = []; + public readonly List CheckedForExistance = []; + public readonly List Deleted = []; + public readonly List<(string, int)> RetreivedPages = []; + public MockComicAnalyzer() + { + + } + public void Clear() + { + Analyzed.Clear(); + CheckedForExistance.Clear(); + Deleted.Clear(); + RetreivedPages.Clear(); + } + public ComicAnalysis? AnalyzeComic(string filename) + { + Analyzed.Add(filename); + if (AnalysisResults.TryGetValue(filename, out var analysis)) + return analysis; + return null; + } + +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + public async Task AnalyzeComicAsync(string filename) +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + { + return AnalyzeComic(filename); + } + + public bool ComicFileExists(string filename) + { + CheckedForExistance.Add(filename); + return ComicsThatExist.Contains(filename); + } + + public void DeleteComicFile(string filename) + { + Deleted.Add(filename); + } + + public ComicPage? GetComicPage(string filepath, int page) + { + var key = (filepath, page); + RetreivedPages.Add(key); + if (ComicPages.TryGetValue(key, out var comicPage)) + return comicPage; + return null; + } + +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + public async Task GetComicPageAsync(string filepath, int page) +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + { + return GetComicPage(filepath, page); + } + } +} diff --git a/ComiServUnitTest/Mocks/MockConfig.cs b/ComiServUnitTest/Mocks/MockConfig.cs new file mode 100644 index 0000000..7122eb3 --- /dev/null +++ b/ComiServUnitTest/Mocks/MockConfig.cs @@ -0,0 +1,20 @@ +using ComiServ.Services; +using Microsoft.Build.Framework; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ComiServUnitTest.Mocks; + +internal class MockConfig : IConfigService +{ + private readonly Configuration _Config; + public Configuration Config => _Config.Copy(); + public MockConfig(Configuration config) + { + _Config = config; + } +} diff --git a/ComiServUnitTest/Mocks/MockPictureConverter.cs b/ComiServUnitTest/Mocks/MockPictureConverter.cs new file mode 100644 index 0000000..5d12de4 --- /dev/null +++ b/ComiServUnitTest/Mocks/MockPictureConverter.cs @@ -0,0 +1,33 @@ +using ComiServ.Background; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ComiServUnitTest.Mocks +{ + internal class MockPictureConverter : IPictureConverter + { + public MockPictureConverter() + { + + } + + public Task MakeThumbnail(Stream image) + { + throw new NotImplementedException(); + } + + public Task Resize(Stream image, Size newSize, PictureFormats? newFormat = null) + { + throw new NotImplementedException(); + } + + public Task ResizeIfBigger(Stream image, Size maxSize, PictureFormats? newFormat = null) + { + throw new NotImplementedException(); + } + } +}