
* Cleaned up some old code in download bookmark that could create pointless temp folders. * Fixed a bad http call on reading list remove read and cleaned up the messaging * Undid an optimization in finding cover image due to it perfoming depth first rather than breadth. * Updated CleanComicInfo to have Translators and CoverArtists, which were previously missing. * Renamed Refresh Metadata to Refresh Covers on the UI, given Metadata refresh is done in Scan. * Library detail will now retain the search query in the UI. Reduced the amount of api calls to the backend on load. * Reverted allowing the filter to reside in the UI (even though it does work). * Updated the Age Rating to match the v2.1 spec. * Fixed a bug where progress wasn't being saved * Fixed line height not having any effect due to not applying to children elements in the reader * Fixed some wording for Refresh Covers confirmation * Delete Series will now send an event to the UI informing that series was deleted. * Change Progress widget to show Refreshing Covers for * When we exit early due to potential missing folders/drives in a scan, tell the UI that scan is 100% done. * Fixed manage library not supressing scan loader when a complete came in * Fixed a spelling difference for Publication Status between filter and series detail * Fixed a bug where collection detail page would flash on first load due to duplicate load events * Added bookmarks to backups * Fixed issues where fullscreen mode would break infinite scroller contiunous reader
320 lines
17 KiB
C#
320 lines
17 KiB
C#
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.IO.Abstractions;
|
|
using System.IO.Abstractions.TestingHelpers;
|
|
using System.IO.Compression;
|
|
using System.Linq;
|
|
using API.Archive;
|
|
using API.Data.Metadata;
|
|
using API.Services;
|
|
using Microsoft.Extensions.Logging;
|
|
using NetVips;
|
|
using NSubstitute;
|
|
using NSubstitute.Extensions;
|
|
using Xunit;
|
|
using Xunit.Abstractions;
|
|
|
|
namespace API.Tests.Services
|
|
{
|
|
public class ArchiveServiceTests
|
|
{
|
|
private readonly ITestOutputHelper _testOutputHelper;
|
|
private readonly ArchiveService _archiveService;
|
|
private readonly ILogger<ArchiveService> _logger = Substitute.For<ILogger<ArchiveService>>();
|
|
private readonly ILogger<DirectoryService> _directoryServiceLogger = Substitute.For<ILogger<DirectoryService>>();
|
|
private readonly IDirectoryService _directoryService = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), new FileSystem());
|
|
|
|
public ArchiveServiceTests(ITestOutputHelper testOutputHelper)
|
|
{
|
|
_testOutputHelper = testOutputHelper;
|
|
_archiveService = new ArchiveService(_logger, _directoryService, new ImageService(Substitute.For<ILogger<ImageService>>(), _directoryService));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("flat file.zip", false)]
|
|
[InlineData("file in folder in folder.zip", true)]
|
|
[InlineData("file in folder.zip", true)]
|
|
[InlineData("file in folder_alt.zip", true)]
|
|
public void ArchiveNeedsFlatteningTest(string archivePath, bool expected)
|
|
{
|
|
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/Archives");
|
|
var file = Path.Join(testDirectory, archivePath);
|
|
using ZipArchive archive = ZipFile.OpenRead(file);
|
|
Assert.Equal(expected, _archiveService.ArchiveNeedsFlattening(archive));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("non existent file.zip", false)]
|
|
[InlineData("winrar.rar", true)]
|
|
[InlineData("empty.zip", true)]
|
|
[InlineData("flat file.zip", true)]
|
|
[InlineData("file in folder in folder.zip", true)]
|
|
[InlineData("file in folder.zip", true)]
|
|
[InlineData("file in folder_alt.zip", true)]
|
|
public void IsValidArchiveTest(string archivePath, bool expected)
|
|
{
|
|
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/Archives");
|
|
Assert.Equal(expected, _archiveService.IsValidArchive(Path.Join(testDirectory, archivePath)));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("non existent file.zip", 0)]
|
|
[InlineData("winrar.rar", 0)]
|
|
[InlineData("empty.zip", 0)]
|
|
[InlineData("flat file.zip", 1)]
|
|
[InlineData("file in folder in folder.zip", 1)]
|
|
[InlineData("file in folder.zip", 1)]
|
|
[InlineData("file in folder_alt.zip", 1)]
|
|
[InlineData("macos_none.zip", 0)]
|
|
[InlineData("macos_one.zip", 1)]
|
|
[InlineData("macos_native.zip", 21)]
|
|
public void GetNumberOfPagesFromArchiveTest(string archivePath, int expected)
|
|
{
|
|
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/Archives");
|
|
var sw = Stopwatch.StartNew();
|
|
Assert.Equal(expected, _archiveService.GetNumberOfPagesFromArchive(Path.Join(testDirectory, archivePath)));
|
|
_testOutputHelper.WriteLine($"Processed Original in {sw.ElapsedMilliseconds} ms");
|
|
}
|
|
|
|
|
|
|
|
[Theory]
|
|
[InlineData("non existent file.zip", ArchiveLibrary.NotSupported)]
|
|
[InlineData("winrar.rar", ArchiveLibrary.SharpCompress)]
|
|
[InlineData("empty.zip", ArchiveLibrary.Default)]
|
|
[InlineData("flat file.zip", ArchiveLibrary.Default)]
|
|
[InlineData("file in folder in folder.zip", ArchiveLibrary.Default)]
|
|
[InlineData("file in folder.zip", ArchiveLibrary.Default)]
|
|
[InlineData("file in folder_alt.zip", ArchiveLibrary.Default)]
|
|
public void CanOpenArchive(string archivePath, ArchiveLibrary expected)
|
|
{
|
|
var sw = Stopwatch.StartNew();
|
|
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/Archives");
|
|
|
|
Assert.Equal(expected, _archiveService.CanOpen(Path.Join(testDirectory, archivePath)));
|
|
_testOutputHelper.WriteLine($"Processed Original in {sw.ElapsedMilliseconds} ms");
|
|
}
|
|
|
|
|
|
[Theory]
|
|
[InlineData("non existent file.zip", 0)]
|
|
[InlineData("winrar.rar", 0)]
|
|
[InlineData("empty.zip", 0)]
|
|
[InlineData("flat file.zip", 1)]
|
|
[InlineData("file in folder in folder.zip", 1)]
|
|
[InlineData("file in folder.zip", 1)]
|
|
[InlineData("file in folder_alt.zip", 1)]
|
|
public void CanExtractArchive(string archivePath, int expectedFileCount)
|
|
{
|
|
|
|
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/Archives");
|
|
var extractDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/Archives/Extraction");
|
|
|
|
_directoryService.ClearAndDeleteDirectory(extractDirectory);
|
|
|
|
var sw = Stopwatch.StartNew();
|
|
_archiveService.ExtractArchive(Path.Join(testDirectory, archivePath), extractDirectory);
|
|
var di1 = new DirectoryInfo(extractDirectory);
|
|
Assert.Equal(expectedFileCount, di1.Exists ? _directoryService.GetFiles(extractDirectory, searchOption:SearchOption.AllDirectories).Count() : 0);
|
|
_testOutputHelper.WriteLine($"Processed in {sw.ElapsedMilliseconds} ms");
|
|
|
|
_directoryService.ClearAndDeleteDirectory(extractDirectory);
|
|
}
|
|
|
|
|
|
[Theory]
|
|
[InlineData(new [] {"folder.jpg"}, "folder.jpg")]
|
|
[InlineData(new [] {"vol1/"}, "")]
|
|
[InlineData(new [] {"folder.jpg", "vol1/folder.jpg"}, "folder.jpg")]
|
|
[InlineData(new [] {"cover.jpg", "vol1/folder.jpg"}, "cover.jpg")]
|
|
[InlineData(new [] {"__MACOSX/cover.jpg", "vol1/page 01.jpg"}, "")]
|
|
[InlineData(new [] {"Akame ga KILL! ZERO - c055 (v10) - p000 [Digital] [LuCaZ].jpg", "Akame ga KILL! ZERO - c055 (v10) - p000 [Digital] [LuCaZ].jpg", "Akame ga KILL! ZERO - c060 (v10) - p200 [Digital] [LuCaZ].jpg", "folder.jpg"}, "folder.jpg")]
|
|
public void FindFolderEntry(string[] files, string expected)
|
|
{
|
|
var foundFile = ArchiveService.FindFolderEntry(files);
|
|
Assert.Equal(expected, string.IsNullOrEmpty(foundFile) ? "" : foundFile);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(new [] {"folder.jpg"}, "folder.jpg")]
|
|
[InlineData(new [] {"vol1/"}, "")]
|
|
[InlineData(new [] {"folder.jpg", "vol1/folder.jpg"}, "folder.jpg")]
|
|
[InlineData(new [] {"cover.jpg", "vol1/folder.jpg"}, "cover.jpg")]
|
|
[InlineData(new [] {"page 2.jpg", "page 10.jpg"}, "page 2.jpg")]
|
|
[InlineData(new [] {"__MACOSX/cover.jpg", "vol1/page 01.jpg"}, "vol1/page 01.jpg")]
|
|
[InlineData(new [] {"Akame ga KILL! ZERO - c055 (v10) - p000 [Digital] [LuCaZ].jpg", "Akame ga KILL! ZERO - c055 (v10) - p000 [Digital] [LuCaZ].jpg", "Akame ga KILL! ZERO - c060 (v10) - p200 [Digital] [LuCaZ].jpg", "folder.jpg"}, "Akame ga KILL! ZERO - c055 (v10) - p000 [Digital] [LuCaZ].jpg")]
|
|
[InlineData(new [] {"001.jpg", "001 - chapter 1/001.jpg"}, "001.jpg")]
|
|
[InlineData(new [] {"chapter 1/001.jpg", "chapter 2/002.jpg", "somefile.jpg"}, "somefile.jpg")]
|
|
public void FindFirstEntry(string[] files, string expected)
|
|
{
|
|
var foundFile = ArchiveService.FirstFileEntry(files, string.Empty);
|
|
Assert.Equal(expected, string.IsNullOrEmpty(foundFile) ? "" : foundFile);
|
|
}
|
|
|
|
|
|
|
|
// TODO: This is broken on GA due to DirectoryService.CoverImageDirectory
|
|
//[Theory]
|
|
[InlineData("v10.cbz", "v10.expected.jpg")]
|
|
[InlineData("v10 - with folder.cbz", "v10 - with folder.expected.jpg")]
|
|
[InlineData("v10 - nested folder.cbz", "v10 - nested folder.expected.jpg")]
|
|
[InlineData("macos_native.zip", "macos_native.jpg")]
|
|
[InlineData("v10 - duplicate covers.cbz", "v10 - duplicate covers.expected.jpg")]
|
|
[InlineData("sorting.zip", "sorting.expected.jpg")]
|
|
[InlineData("test.zip", "test.expected.jpg")] // https://github.com/kleisauke/net-vips/issues/155
|
|
public void GetCoverImage_Default_Test(string inputFile, string expectedOutputFile)
|
|
{
|
|
var ds = Substitute.For<DirectoryService>(_directoryServiceLogger, new FileSystem());
|
|
var imageService = new ImageService(Substitute.For<ILogger<ImageService>>(), ds);
|
|
var archiveService = Substitute.For<ArchiveService>(_logger, ds, imageService);
|
|
|
|
var testDirectory = Path.GetFullPath(Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/CoverImages"));
|
|
var expectedBytes = Image.Thumbnail(Path.Join(testDirectory, expectedOutputFile), 320).WriteToBuffer(".png");
|
|
|
|
archiveService.Configure().CanOpen(Path.Join(testDirectory, inputFile)).Returns(ArchiveLibrary.Default);
|
|
|
|
var outputDir = Path.Join(testDirectory, "output");
|
|
_directoryService.ClearDirectory(outputDir);
|
|
_directoryService.ExistOrCreate(outputDir);
|
|
|
|
var coverImagePath = archiveService.GetCoverImage(Path.Join(testDirectory, inputFile),
|
|
Path.GetFileNameWithoutExtension(inputFile) + "_output", outputDir);
|
|
var actual = File.ReadAllBytes(Path.Join(outputDir, coverImagePath));
|
|
|
|
|
|
Assert.Equal(expectedBytes, actual);
|
|
//_directoryService.ClearAndDeleteDirectory(outputDir);
|
|
}
|
|
|
|
|
|
// TODO: This is broken on GA due to DirectoryService.CoverImageDirectory
|
|
//[Theory]
|
|
[InlineData("v10.cbz", "v10.expected.jpg")]
|
|
[InlineData("v10 - with folder.cbz", "v10 - with folder.expected.jpg")]
|
|
[InlineData("v10 - nested folder.cbz", "v10 - nested folder.expected.jpg")]
|
|
[InlineData("macos_native.zip", "macos_native.jpg")]
|
|
[InlineData("v10 - duplicate covers.cbz", "v10 - duplicate covers.expected.jpg")]
|
|
[InlineData("sorting.zip", "sorting.expected.jpg")]
|
|
public void GetCoverImage_SharpCompress_Test(string inputFile, string expectedOutputFile)
|
|
{
|
|
var imageService = new ImageService(Substitute.For<ILogger<ImageService>>(), _directoryService);
|
|
var archiveService = Substitute.For<ArchiveService>(_logger,
|
|
new DirectoryService(_directoryServiceLogger, new FileSystem()), imageService);
|
|
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/CoverImages");
|
|
var expectedBytes = File.ReadAllBytes(Path.Join(testDirectory, expectedOutputFile));
|
|
|
|
var outputDir = Path.Join(testDirectory, "output");
|
|
_directoryService.ClearDirectory(outputDir);
|
|
_directoryService.ExistOrCreate(outputDir);
|
|
|
|
archiveService.Configure().CanOpen(Path.Join(testDirectory, inputFile)).Returns(ArchiveLibrary.SharpCompress);
|
|
var actualBytes = File.ReadAllBytes(archiveService.GetCoverImage(Path.Join(testDirectory, inputFile),
|
|
Path.GetFileNameWithoutExtension(inputFile) + "_output", outputDir));
|
|
Assert.Equal(expectedBytes, actualBytes);
|
|
|
|
_directoryService.ClearAndDeleteDirectory(outputDir);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("Archives/macos_native.zip")]
|
|
[InlineData("Formats/One File with DB_Supported.zip")]
|
|
public void CanParseCoverImage(string inputFile)
|
|
{
|
|
var imageService = Substitute.For<IImageService>();
|
|
imageService.WriteCoverThumbnail(Arg.Any<Stream>(), Arg.Any<string>(), Arg.Any<string>()).Returns(x => "cover.jpg");
|
|
var archiveService = new ArchiveService(_logger, _directoryService, imageService);
|
|
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/");
|
|
var inputPath = Path.GetFullPath(Path.Join(testDirectory, inputFile));
|
|
var outputPath = Path.Join(testDirectory, Path.GetFileNameWithoutExtension(inputFile) + "_output");
|
|
new DirectoryInfo(outputPath).Create();
|
|
var expectedImage = archiveService.GetCoverImage(inputPath, inputFile, outputPath);
|
|
Assert.Equal("cover.jpg", expectedImage);
|
|
new DirectoryInfo(outputPath).Delete();
|
|
}
|
|
|
|
#region ShouldHaveComicInfo
|
|
|
|
[Fact]
|
|
public void ShouldHaveComicInfo()
|
|
{
|
|
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/ComicInfos");
|
|
var archive = Path.Join(testDirectory, "ComicInfo.zip");
|
|
const string summaryInfo = "By all counts, Ryouta Sakamoto is a loser when he's not holed up in his room, bombing things into oblivion in his favorite online action RPG. But his very own uneventful life is blown to pieces when he's abducted and taken to an uninhabited island, where he soon learns the hard way that he's being pitted against others just like him in a explosives-riddled death match! How could this be happening? Who's putting them up to this? And why!? The name, not to mention the objective, of this very real survival game is eerily familiar to Ryouta, who has mastered its virtual counterpart-BTOOOM! Can Ryouta still come out on top when he's playing for his life!?";
|
|
|
|
var comicInfo = _archiveService.GetComicInfo(archive);
|
|
Assert.NotNull(comicInfo);
|
|
Assert.Equal(summaryInfo, comicInfo.Summary);
|
|
}
|
|
|
|
[Fact]
|
|
public void ShouldHaveComicInfo_WithAuthors()
|
|
{
|
|
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/ComicInfos");
|
|
var archive = Path.Join(testDirectory, "ComicInfo_authors.zip");
|
|
|
|
var comicInfo = _archiveService.GetComicInfo(archive);
|
|
Assert.NotNull(comicInfo);
|
|
Assert.Equal("Junya Inoue", comicInfo.Writer);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CanParseComicInfo
|
|
|
|
[Fact]
|
|
public void CanParseComicInfo()
|
|
{
|
|
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/ComicInfos");
|
|
var archive = Path.Join(testDirectory, "ComicInfo.zip");
|
|
var actual = _archiveService.GetComicInfo(archive);
|
|
var expected = new ComicInfo()
|
|
{
|
|
Publisher = "Yen Press",
|
|
Genre = "Manga, Movies & TV",
|
|
Summary =
|
|
"By all counts, Ryouta Sakamoto is a loser when he's not holed up in his room, bombing things into oblivion in his favorite online action RPG. But his very own uneventful life is blown to pieces when he's abducted and taken to an uninhabited island, where he soon learns the hard way that he's being pitted against others just like him in a explosives-riddled death match! How could this be happening? Who's putting them up to this? And why!? The name, not to mention the objective, of this very real survival game is eerily familiar to Ryouta, who has mastered its virtual counterpart-BTOOOM! Can Ryouta still come out on top when he's playing for his life!?",
|
|
PageCount = 194,
|
|
LanguageISO = "en",
|
|
Notes = "Scraped metadata from Comixology [CMXDB450184]",
|
|
Series = "BTOOOM!",
|
|
Title = "v01",
|
|
Web = "https://www.comixology.com/BTOOOM/digital-comic/450184"
|
|
};
|
|
|
|
Assert.NotStrictEqual(expected, actual);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region FindCoverImageFilename
|
|
|
|
[Theory]
|
|
[InlineData(new string[] {}, "", null)]
|
|
[InlineData(new [] {"001.jpg", "002.jpg"}, "Test.zip", "001.jpg")]
|
|
[InlineData(new [] {"001.jpg", "!002.jpg"}, "Test.zip", "!002.jpg")]
|
|
[InlineData(new [] {"001.jpg", "!001.jpg"}, "Test.zip", "!001.jpg")]
|
|
[InlineData(new [] {"001.jpg", "cover.jpg"}, "Test.zip", "cover.jpg")]
|
|
[InlineData(new [] {"001.jpg", "Chapter 20/cover.jpg", "Chapter 21/0001.jpg"}, "Test.zip", "Chapter 20/cover.jpg")]
|
|
[InlineData(new [] {"._/001.jpg", "._/cover.jpg", "010.jpg"}, "Test.zip", "010.jpg")]
|
|
[InlineData(new [] {"001.txt", "002.txt", "a.jpg"}, "Test.zip", "a.jpg")]
|
|
public void FindCoverImageFilename(string[] filenames, string archiveName, string expected)
|
|
{
|
|
Assert.Equal(expected, _archiveService.FindCoverImageFilename(archiveName, filenames));
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
#region CreateZipForDownload
|
|
|
|
//[Fact]
|
|
public void CreateZipForDownloadTest()
|
|
{
|
|
var fileSystem = new MockFileSystem();
|
|
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), fileSystem);
|
|
//_archiveService.CreateZipForDownload(new []{}, outputDirectory)
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|