- Create comprehensive unit tests for sync package - format_test.go: 60+ tests for format detection - EPUB format detection (mimetype, extension, uppercase) - MOBI/AZW3/FB2/TXT reflowable formats - PDF/DJVU fixed layout formats - CBZ/CBR/CB7/CBT comic archive formats - Unknown format handling - MimeType lookup tests - IsReflowable/HasFixedLayout/IsComicArchive helpers - progress_test.go: 45+ tests for progress conversion - PageToPercentage/PercentageToPage (with clamping) - CharacterToPercentage/PercentageToCharacter - ConvertProgress between format groups - MergeProgress with 'max progress wins' strategy - FormatProgressForDisplay for UI rendering - Round-trip conversion tests - Edge cases (very small/large values, floating point precision) - All tests pass successfully - Test coverage: format detection, progress conversion, display formatting - Validates Phase 1 implementation quality
459 lines
9.7 KiB
Go
459 lines
9.7 KiB
Go
package sync
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestPageToPercentage(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
page int
|
|
totalPages int
|
|
want float64
|
|
}{
|
|
{
|
|
name: "Beginning of book",
|
|
page: 0,
|
|
totalPages: 200,
|
|
want: 0.0,
|
|
},
|
|
{
|
|
name: "Middle of book",
|
|
page: 100,
|
|
totalPages: 200,
|
|
want: 0.5,
|
|
},
|
|
{
|
|
name: "End of book",
|
|
page: 200,
|
|
totalPages: 200,
|
|
want: 1.0,
|
|
},
|
|
{
|
|
name: "25% through book",
|
|
page: 50,
|
|
totalPages: 200,
|
|
want: 0.25,
|
|
},
|
|
{
|
|
name: "Clamp to 1.0",
|
|
page: 250,
|
|
totalPages: 200,
|
|
want: 1.0,
|
|
},
|
|
{
|
|
name: "Clamp to 0.0",
|
|
page: -10,
|
|
totalPages: 200,
|
|
want: 0.0,
|
|
},
|
|
{
|
|
name: "Zero total pages",
|
|
page: 100,
|
|
totalPages: 0,
|
|
want: 0.0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := PageToPercentage(tt.page, tt.totalPages)
|
|
assert.InDelta(t, tt.want, got, 0.001)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPercentageToPage(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
percentage float64
|
|
totalPages int
|
|
want int
|
|
}{
|
|
{
|
|
name: "0% progress",
|
|
percentage: 0.0,
|
|
totalPages: 200,
|
|
want: 0,
|
|
},
|
|
{
|
|
name: "50% progress",
|
|
percentage: 0.5,
|
|
totalPages: 200,
|
|
want: 100,
|
|
},
|
|
{
|
|
name: "100% progress",
|
|
percentage: 1.0,
|
|
totalPages: 200,
|
|
want: 200,
|
|
},
|
|
{
|
|
name: "25% progress",
|
|
percentage: 0.25,
|
|
totalPages: 200,
|
|
want: 50,
|
|
},
|
|
{
|
|
name: "Clamp below 0",
|
|
percentage: -0.1,
|
|
totalPages: 200,
|
|
want: 0,
|
|
},
|
|
{
|
|
name: "Clamp above 1",
|
|
percentage: 1.5,
|
|
totalPages: 200,
|
|
want: 200,
|
|
},
|
|
{
|
|
name: "33.3% progress",
|
|
percentage: 0.333,
|
|
totalPages: 300,
|
|
want: 100,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := PercentageToPage(tt.percentage, tt.totalPages)
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCharacterToPercentage(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
character int64
|
|
totalCharacters int64
|
|
want float64
|
|
}{
|
|
{
|
|
name: "Beginning of book",
|
|
character: 0,
|
|
totalCharacters: 100000,
|
|
want: 0.0,
|
|
},
|
|
{
|
|
name: "Middle of book",
|
|
character: 50000,
|
|
totalCharacters: 100000,
|
|
want: 0.5,
|
|
},
|
|
{
|
|
name: "End of book",
|
|
character: 100000,
|
|
totalCharacters: 100000,
|
|
want: 1.0,
|
|
},
|
|
{
|
|
name: "25% through book",
|
|
character: 25000,
|
|
totalCharacters: 100000,
|
|
want: 0.25,
|
|
},
|
|
{
|
|
name: "Clamp to 1.0",
|
|
character: 150000,
|
|
totalCharacters: 100000,
|
|
want: 1.0,
|
|
},
|
|
{
|
|
name: "Zero total characters",
|
|
character: 50000,
|
|
totalCharacters: 0,
|
|
want: 0.0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := CharacterToPercentage(tt.character, tt.totalCharacters)
|
|
assert.InDelta(t, tt.want, got, 0.001)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPercentageToCharacter(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
percentage float64
|
|
totalCharacters int64
|
|
want int64
|
|
}{
|
|
{
|
|
name: "0% progress",
|
|
percentage: 0.0,
|
|
totalCharacters: 100000,
|
|
want: 0,
|
|
},
|
|
{
|
|
name: "50% progress",
|
|
percentage: 0.5,
|
|
totalCharacters: 100000,
|
|
want: 50000,
|
|
},
|
|
{
|
|
name: "100% progress",
|
|
percentage: 1.0,
|
|
totalCharacters: 100000,
|
|
want: 100000,
|
|
},
|
|
{
|
|
name: "25% progress",
|
|
percentage: 0.25,
|
|
totalCharacters: 100000,
|
|
want: 25000,
|
|
},
|
|
{
|
|
name: "Clamp below 0",
|
|
percentage: -0.1,
|
|
totalCharacters: 100000,
|
|
want: 0,
|
|
},
|
|
{
|
|
name: "Clamp above 1",
|
|
percentage: 1.5,
|
|
totalCharacters: 100000,
|
|
want: 100000,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := PercentageToCharacter(tt.percentage, tt.totalCharacters)
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestConvertProgress_ReflowableToFixed(t *testing.T) {
|
|
source := map[string]interface{}{
|
|
"percentage": 0.45,
|
|
"epubcfi": "epubcfi(/6/4/2:45)",
|
|
"total_characters": int64(100000),
|
|
}
|
|
|
|
result, err := ConvertProgress(FormatGroupReflowable, FormatGroupFixedLayout, source)
|
|
assert.NoError(t, err)
|
|
|
|
assert.InDelta(t, 0.45, result["percentage"], 0.001)
|
|
assert.Equal(t, 90, result["page"])
|
|
assert.Equal(t, 200, result["total_pages"])
|
|
}
|
|
|
|
func TestConvertProgress_FixedToReflowable(t *testing.T) {
|
|
source := map[string]interface{}{
|
|
"page": 100,
|
|
"total_pages": 200,
|
|
}
|
|
|
|
result, err := ConvertProgress(FormatGroupFixedLayout, FormatGroupReflowable, source)
|
|
assert.NoError(t, err)
|
|
|
|
assert.InDelta(t, 0.5, result["percentage"], 0.001)
|
|
assert.Contains(t, result["epubcfi"], "epubcfi")
|
|
}
|
|
|
|
func TestConvertProgress_ComicToReflowable(t *testing.T) {
|
|
source := map[string]interface{}{
|
|
"page": 16,
|
|
"total_pages": 32,
|
|
}
|
|
|
|
result, err := ConvertProgress(FormatGroupComicArchive, FormatGroupReflowable, source)
|
|
assert.NoError(t, err)
|
|
|
|
assert.InDelta(t, 0.5, result["percentage"], 0.001)
|
|
assert.Contains(t, result["epubcfi"], "epubcfi")
|
|
}
|
|
|
|
func TestConvertProgress_UnsupportedFormat(t *testing.T) {
|
|
source := map[string]interface{}{
|
|
"page": 100,
|
|
"total_pages": 200,
|
|
}
|
|
|
|
result, err := ConvertProgress(FormatGroupUnknown, FormatGroupReflowable, source)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 0.0, result["percentage"])
|
|
}
|
|
|
|
func TestMergeProgress(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
progressA map[string]interface{}
|
|
progressB map[string]interface{}
|
|
wantPercent float64
|
|
wantSource string
|
|
}{
|
|
{
|
|
name: "Progress A wins",
|
|
progressA: map[string]interface{}{
|
|
"source": "koreader",
|
|
"percentage": 0.60,
|
|
},
|
|
progressB: map[string]interface{}{
|
|
"source": "kobo",
|
|
"percentage": 0.40,
|
|
},
|
|
wantPercent: 0.60,
|
|
wantSource: "koreader",
|
|
},
|
|
{
|
|
name: "Progress B wins",
|
|
progressA: map[string]interface{}{
|
|
"source": "koreader",
|
|
"percentage": 0.30,
|
|
},
|
|
progressB: map[string]interface{}{
|
|
"source": "kobo",
|
|
"percentage": 0.70,
|
|
},
|
|
wantPercent: 0.70,
|
|
wantSource: "kobo",
|
|
},
|
|
{
|
|
name: "Equal progress keeps B",
|
|
progressA: map[string]interface{}{
|
|
"source": "koreader",
|
|
"percentage": 0.50,
|
|
},
|
|
progressB: map[string]interface{}{
|
|
"source": "kobo",
|
|
"percentage": 0.50,
|
|
},
|
|
wantPercent: 0.50,
|
|
wantSource: "kobo",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := MergeProgress(tt.progressA, tt.progressB)
|
|
|
|
percent, ok := result["percentage"].(float64)
|
|
assert.True(t, ok)
|
|
assert.InDelta(t, tt.wantPercent, percent, 0.001)
|
|
|
|
sources, ok := result["merged_from"].([]string)
|
|
assert.True(t, ok)
|
|
assert.Len(t, sources, 2)
|
|
assert.Contains(t, result["merge_timestamp"], "now")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFormatProgressForDisplay(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
formatGroup FormatGroup
|
|
progress map[string]interface{}
|
|
want string
|
|
}{
|
|
{
|
|
name: "Reflowable with chapter",
|
|
formatGroup: FormatGroupReflowable,
|
|
progress: map[string]interface{}{
|
|
"percentage": 0.45678,
|
|
"chapter": 3,
|
|
},
|
|
want: "45.7% (Chapter 3)",
|
|
},
|
|
{
|
|
name: "Reflowable without chapter",
|
|
formatGroup: FormatGroupReflowable,
|
|
progress: map[string]interface{}{
|
|
"percentage": 0.45678,
|
|
},
|
|
want: "45.7%",
|
|
},
|
|
{
|
|
name: "Fixed layout with pages",
|
|
formatGroup: FormatGroupFixedLayout,
|
|
progress: map[string]interface{}{
|
|
"percentage": 0.45,
|
|
"page": 90,
|
|
"total_pages": 200,
|
|
},
|
|
want: "Page 90 of 200 (45.0%)",
|
|
},
|
|
{
|
|
name: "Fixed layout percentage only",
|
|
formatGroup: FormatGroupFixedLayout,
|
|
progress: map[string]interface{}{
|
|
"percentage": 0.45,
|
|
},
|
|
want: "45.0%",
|
|
},
|
|
{
|
|
name: "Comic archive with pages",
|
|
formatGroup: FormatGroupComicArchive,
|
|
progress: map[string]interface{}{
|
|
"percentage": 0.375,
|
|
"page": 12,
|
|
"total_pages": 32,
|
|
},
|
|
want: "Page 12 of 32 (38%)",
|
|
},
|
|
{
|
|
name: "Unknown format defaults to 0%",
|
|
formatGroup: FormatGroupUnknown,
|
|
progress: map[string]interface{}{
|
|
"page": 50,
|
|
"total_pages": 100,
|
|
},
|
|
want: "0.0%",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := FormatProgressForDisplay(tt.formatGroup, tt.progress)
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRoundTripConversion(t *testing.T) {
|
|
t.Run("Page -> Percentage -> Page", func(t *testing.T) {
|
|
originalPage := 75
|
|
totalPages := 300
|
|
|
|
percentage := PageToPercentage(originalPage, totalPages)
|
|
resultPage := PercentageToPage(percentage, totalPages)
|
|
|
|
assert.Equal(t, originalPage, resultPage)
|
|
})
|
|
|
|
t.Run("Character -> Percentage -> Character", func(t *testing.T) {
|
|
originalChar := int64(25000)
|
|
totalChars := int64(100000)
|
|
|
|
percentage := CharacterToPercentage(originalChar, totalChars)
|
|
resultChar := PercentageToCharacter(percentage, totalChars)
|
|
|
|
assert.Equal(t, originalChar, resultChar)
|
|
})
|
|
}
|
|
|
|
func TestEdgeCases(t *testing.T) {
|
|
t.Run("Very small percentages", func(t *testing.T) {
|
|
assert.InDelta(t, 0.001, PageToPercentage(1, 1000), 0.0001)
|
|
})
|
|
|
|
t.Run("Very large page numbers", func(t *testing.T) {
|
|
page := PercentageToPage(0.999, 10000)
|
|
assert.Equal(t, 9990, page)
|
|
})
|
|
|
|
t.Run("Floating point precision", func(t *testing.T) {
|
|
percentage := 1.0 / 3.0
|
|
page := PercentageToPage(percentage, 300)
|
|
assert.InDelta(t, 100, page, 1)
|
|
})
|
|
}
|