Kavita/API.Tests/Helpers/KoreaderHelperTests.cs
Joe Milazzo 53a7e690a1
Almost there (#4343)
Co-authored-by: Amelia <77553571+Fesaa@users.noreply.github.com>
2026-01-12 15:29:55 -08:00

196 lines
6.7 KiB
C#

using API.DTOs.Progress;
using API.Helpers;
using Xunit;
namespace API.Tests.Helpers;
#nullable enable
public class KoreaderHelperTests
{
#region UpdateProgressDto Tests
[Theory]
[InlineData("/body/DocFragment[11]/body/div/a", 10, null)] // Anchor tags return null BookScrollId
[InlineData("/body/DocFragment[1]/body/div/p[40]", 0, "//body/div/p[40]")]
[InlineData("/body/DocFragment[5]/body/section/div[2]", 4, "//body/section/div[2]")]
public void UpdateProgressDto_StandardXPath(string koreaderPosition, int expectedPage, string? expectedScrollId)
{
var actual = EmptyProgressDto();
KoreaderHelper.UpdateProgressDto(actual, koreaderPosition);
Assert.Equal(expectedPage, actual.PageNum);
Assert.Equal(expectedScrollId, actual.BookScrollId);
}
[Theory]
[InlineData("/body/DocFragment[8]/body/div/p[28]/text().264", 7, "//body/div/p[28]")]
[InlineData("/body/DocFragment[3]/body/h1/text().0", 2, "//body/h1")]
[InlineData("/body/DocFragment[9]/body/p[52]/text().248", 8, "//body/p[52]")]
[InlineData("/body/DocFragment[6]/body/div/span.0", 5, "//body/div/span")] // Trailing .0 stripped
public void UpdateProgressDto_WithTextOffsets(string koreaderPosition, int expectedPage, string? expectedScrollId)
{
var actual = EmptyProgressDto();
KoreaderHelper.UpdateProgressDto(actual, koreaderPosition);
Assert.Equal(expectedPage, actual.PageNum);
Assert.Equal(expectedScrollId, actual.BookScrollId);
}
[Theory]
[InlineData("/body/DocFragment[10].0", 9, null)] // Short path - no scroll ID determinable
[InlineData("/body/DocFragment[5]", 4, null)]
[InlineData("/body/DocFragment[1]/body", 0, null)] // Too short for full path extraction
public void UpdateProgressDto_ShortPaths(string koreaderPosition, int expectedPage, string? expectedScrollId)
{
var actual = EmptyProgressDto();
KoreaderHelper.UpdateProgressDto(actual, koreaderPosition);
Assert.Equal(expectedPage, actual.PageNum);
Assert.Equal(expectedScrollId, actual.BookScrollId);
}
[Theory]
[InlineData("#_doc_fragment_10", 9, null)]
[InlineData("#_doc_fragment_1", 0, null)]
[InlineData("#_doc_fragment_10_ some_anchor", 9, null)] // With trailing anchor
[InlineData("#_doc_fragment10", 9, null)] // Legacy format without underscore
[InlineData("#_doc_fragment1", 0, null)]
public void UpdateProgressDto_HashFragmentFormat(string koreaderPosition, int expectedPage, string? expectedScrollId)
{
var actual = EmptyProgressDto();
KoreaderHelper.UpdateProgressDto(actual, koreaderPosition);
Assert.Equal(expectedPage, actual.PageNum);
Assert.Equal(expectedScrollId, actual.BookScrollId);
}
[Theory]
[InlineData("5", 4)] // Archive/PDF page number (1-indexed from KOReader)
[InlineData("1", 0)]
[InlineData("100", 99)]
public void UpdateProgressDto_NumericOnly(string koreaderPosition, int expectedPage)
{
var actual = EmptyProgressDto();
KoreaderHelper.UpdateProgressDto(actual, koreaderPosition);
Assert.Equal(expectedPage, actual.PageNum);
}
[Theory]
[InlineData("/body/DocFragment[11]/body/id(\"chapter1\")", 10, null)] // id() selectors not supported
[InlineData("/body/DocFragment[5]/body/div/id(\"section2\")/p[1]", 4, null)]
public void UpdateProgressDto_IdSelectors(string koreaderPosition, int expectedPage, string? expectedScrollId)
{
var actual = EmptyProgressDto();
KoreaderHelper.UpdateProgressDto(actual, koreaderPosition);
Assert.Equal(expectedPage, actual.PageNum);
Assert.Equal(expectedScrollId, actual.BookScrollId);
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void UpdateProgressDto_EmptyOrNull_NoChanges(string? koreaderPosition)
{
var actual = EmptyProgressDto();
actual.PageNum = 5;
actual.BookScrollId = "//body/p[1]";
KoreaderHelper.UpdateProgressDto(actual, koreaderPosition!);
// Should remain unchanged
Assert.Equal(5, actual.PageNum);
Assert.Equal("//body/p[1]", actual.BookScrollId);
}
#endregion
#region GetKoreaderPosition Tests
[Theory]
[InlineData("//body/p[20]", 4, "/body/DocFragment[5]/body/p[20].0")]
[InlineData("//body/div/section[3]", 0, "/body/DocFragment[1]/body/div/section[3].0")]
[InlineData("//body/h1", 9, "/body/DocFragment[10]/body/h1.0")]
public void GetKoreaderPosition_WithScrollId(string? scrollId, int page, string expectedPosition)
{
var given = EmptyProgressDto();
given.BookScrollId = scrollId;
given.PageNum = page;
var result = KoreaderHelper.GetKoreaderPosition(given);
Assert.Equal(expectedPosition, result, ignoreCase: true);
}
[Theory]
[InlineData(null, 9, "/body/DocFragment[10].0")]
[InlineData("", 0, "/body/DocFragment[1].0")]
[InlineData(null, 4, "/body/DocFragment[5].0")]
public void GetKoreaderPosition_NoScrollId_ReturnsFragmentOnly(string? scrollId, int page, string expectedPosition)
{
var given = EmptyProgressDto();
given.BookScrollId = scrollId;
given.PageNum = page;
var result = KoreaderHelper.GetKoreaderPosition(given);
Assert.Equal(expectedPosition, result, ignoreCase: true);
}
[Theory]
[InlineData("id(\"chapter1\")", 5, "/body/DocFragment[6].0")]
[InlineData("id(\"h2\")", 10, "/body/DocFragment[11].0")]
public void GetKoreaderPosition_IdSelector_ReturnsFragmentOnly(string scrollId, int page, string expectedPosition)
{
var given = EmptyProgressDto();
given.BookScrollId = scrollId;
given.PageNum = page;
var result = KoreaderHelper.GetKoreaderPosition(given);
Assert.Equal(expectedPosition, result, ignoreCase: true);
}
#endregion
#region HashContents Tests
[Theory]
[InlineData("./Data/AesopsFables.epub", "8795ACA4BF264B57C1EEDF06A0CEE688")]
public void HashContents_ValidFile_ReturnsExpectedHash(string filePath, string expectedHash)
{
Assert.Equal(expectedHash, KoreaderHelper.HashContents(filePath));
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("./Data/NonExistent.epub")]
public void HashContents_InvalidFile_ReturnsNull(string? filePath)
{
Assert.Null(KoreaderHelper.HashContents(filePath!));
}
#endregion
private static ProgressDto EmptyProgressDto()
{
return new ProgressDto
{
ChapterId = 0,
PageNum = 0,
VolumeId = 0,
SeriesId = 0,
LibraryId = 0
};
}
}