Files
bookhoard/cmd/server/tests/comic_metadata_test.go
john-okeefe 158b15c1d8 Fix comic metadata tests: UUID handling, test isolation, and defaults
This commit fixes multiple issues in the comic metadata test suite that were causing test failures:

1. UUID Byte-Order Corruption
   - Fixed byte-order corruption when converting library IDs
   - Previously used [16]byte(uuid.MustParse(libraryID)) which corrupted bytes
   - Now parse UUID once and reuse the parsed UUID variable
   - Matches pattern used successfully in calibre_integration_test.go

2. Test Isolation
   - Each sub-test now creates its own isolated library
   - Previously all sub-tests shared one library, causing cross-test pollution
   - ListMediaItemsByLibrary returns items from previous tests
   - New libraries: "RTL Manga Test Library", "Western Comic Test Library", "Minimal Metadata Test Library"

3. Query Function Selection
   - Replaced SearchMediaItems with ListMediaItemsByLibrary
   - SearchMediaItems requires search_pattern parameter which was missing
   - ListMediaItemsByLibrary is simpler and more appropriate for these tests

4. Explicit Default Values
   - MangaType and ReadingDirection now explicitly set to expected defaults
   - Database defaults not applied when pgtype fields have Valid: false
   - "Comic with minimal metadata" test now sets: MangaType="unknown", ReadingDirection="auto"

5. Library Naming for Cleanup
   - All library names now include "Test" for proper cleanup
   - Test cleanup deletes libraries with "test" in name (case-insensitive)
   - Prevents orphaned libraries from accumulating in database

All tests in TestComicMetadataExtraction now pass:
- CBZ with RTL manga ✓
- CBZ with Western comic ✓
- Comic with minimal metadata ✓
2026-03-30 21:22:39 -04:00

295 lines
11 KiB
Go

package main
import (
"context"
"testing"
"bookhoard/internal/database"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestComicMetadataExtraction tests that comic metadata fields are stored correctly
func TestComicMetadataExtraction(t *testing.T) {
setup := setupDeviceTest(t)
defer setup.Server.Close()
ctx := context.Background()
t.Run("CBZ with RTL manga", func(t *testing.T) {
libraryID := setup.CreateLibrary(t, "RTL Manga Test Library", "comics")
parsedLibraryID, err := uuid.Parse(libraryID)
require.NoError(t, err, "Should parse library UUID")
// Insert test media item with full comic metadata
_, err = setup.DB.CreateMediaItem(ctx, database.CreateMediaItemParams{
LibraryID: pgtype.UUID{Bytes: parsedLibraryID, Valid: true},
Title: "Test Manga",
FilePath: "/test/manga.cbz",
MangaType: pgtype.Text{String: "yes_and_right_to_left", Valid: true},
ReadingDirection: pgtype.Text{String: "rtl", Valid: true},
SeriesCount: pgtype.Int4{Int32: 12, Valid: true},
Volume: pgtype.Int4{Int32: 1, Valid: true},
StoryArc: pgtype.Text{String: "The Dark Phoenix Saga", Valid: true},
AgeRating: pgtype.Text{String: "Teen", Valid: true},
CommunityRating: pgtype.Float8{Float64: 8.5, Valid: true},
Imprint: pgtype.Text{String: "Shonen Jump", Valid: true},
IsBlackAndWhite: pgtype.Bool{Bool: false, Valid: true},
})
require.NoError(t, err)
// Query it back
items, err := setup.DB.ListMediaItemsByLibrary(ctx, pgtype.UUID{Bytes: parsedLibraryID, Valid: true})
require.NoError(t, err)
require.Greater(t, len(items), 0)
item := items[0]
assert.Equal(t, "Test Manga", item.Title)
assert.Equal(t, "yes_and_right_to_left", item.MangaType.String)
assert.Equal(t, "rtl", item.ReadingDirection.String)
assert.Equal(t, int32(12), item.SeriesCount.Int32)
assert.Equal(t, int32(1), item.Volume.Int32)
assert.Equal(t, "The Dark Phoenix Saga", item.StoryArc.String)
assert.Equal(t, "Teen", item.AgeRating.String)
assert.Equal(t, 8.5, item.CommunityRating.Float64)
assert.Equal(t, "Shonen Jump", item.Imprint.String)
assert.False(t, item.IsBlackAndWhite.Bool)
})
t.Run("CBZ with Western comic", func(t *testing.T) {
libraryID := setup.CreateLibrary(t, "Western Comic Test Library", "comics")
parsedLibraryID, err := uuid.Parse(libraryID)
require.NoError(t, err, "Should parse library UUID")
_, err = setup.DB.CreateMediaItem(ctx, database.CreateMediaItemParams{
LibraryID: pgtype.UUID{Bytes: parsedLibraryID, Valid: true},
Title: "Test Comic",
FilePath: "/test/comic.cbz",
MangaType: pgtype.Text{String: "no", Valid: true},
ReadingDirection: pgtype.Text{String: "ltr", Valid: true},
Imprint: pgtype.Text{String: "Vertigo", Valid: true},
IsBlackAndWhite: pgtype.Bool{Bool: true, Valid: true},
StoryArc: pgtype.Text{String: "Batman: Year One", Valid: true},
SeriesCount: pgtype.Int4{Int32: 4, Valid: true},
})
require.NoError(t, err)
items, err := setup.DB.ListMediaItemsByLibrary(ctx, pgtype.UUID{Bytes: parsedLibraryID, Valid: true})
require.NoError(t, err)
item := items[0]
assert.Equal(t, "no", item.MangaType.String)
assert.Equal(t, "ltr", item.ReadingDirection.String)
assert.Equal(t, "Vertigo", item.Imprint.String)
assert.True(t, item.IsBlackAndWhite.Bool)
assert.Equal(t, "Batman: Year One", item.StoryArc.String)
assert.Equal(t, int32(4), item.SeriesCount.Int32)
})
t.Run("Comic with minimal metadata", func(t *testing.T) {
libraryID := setup.CreateLibrary(t, "Minimal Metadata Test Library", "comics")
parsedLibraryID, err := uuid.Parse(libraryID)
require.NoError(t, err, "Should parse library UUID")
_, err = setup.DB.CreateMediaItem(ctx, database.CreateMediaItemParams{
LibraryID: pgtype.UUID{Bytes: parsedLibraryID, Valid: true},
Title: "Minimal Comic",
FilePath: "/test/minimal.cbz",
MangaType: pgtype.Text{String: "unknown", Valid: true},
ReadingDirection: pgtype.Text{String: "auto", Valid: true},
})
require.NoError(t, err)
items, err := setup.DB.ListMediaItemsByLibrary(ctx, pgtype.UUID{Bytes: parsedLibraryID, Valid: true})
require.NoError(t, err)
item := items[0]
assert.Equal(t, "Minimal Comic", item.Title)
// Verify defaults
assert.Equal(t, "unknown", item.MangaType.String)
assert.Equal(t, "auto", item.ReadingDirection.String)
})
}
// TestReadingDirectionAPI tests reading direction in API responses
func TestReadingDirectionAPI(t *testing.T) {
setup := setupDeviceTest(t)
defer setup.Server.Close()
ctx := context.Background()
libraryID := setup.CreateLibrary(t, "Reading Direction Test Library", "comics")
parsedLibraryID, err := uuid.Parse(libraryID)
require.NoError(t, err, "Should parse library UUID")
// Create test items with different reading directions
testCases := []struct {
title string
manga string
dir string
}{
{"Japanese Manga", "yes_and_right_to_left", "rtl"},
{"Western Comic", "no", "ltr"},
{"Webtoon", "unknown", "auto"},
}
for _, tc := range testCases {
_, err := setup.DB.CreateMediaItem(ctx, database.CreateMediaItemParams{
LibraryID: pgtype.UUID{Bytes: parsedLibraryID, Valid: true},
Title: tc.title,
FilePath: "/test/" + tc.title + ".cbz",
MangaType: pgtype.Text{String: tc.manga, Valid: true},
ReadingDirection: pgtype.Text{String: tc.dir, Valid: true},
})
require.NoError(t, err)
}
t.Run("Search API includes reading_direction", func(t *testing.T) {
items, err := setup.DB.ListMediaItemsByLibrary(ctx, pgtype.UUID{Bytes: parsedLibraryID, Valid: true})
require.NoError(t, err)
require.Len(t, items, 3)
// Verify all items have reading direction set
for _, item := range items {
assert.NotEmpty(t, item.ReadingDirection.String)
assert.NotEmpty(t, item.MangaType.String)
assert.True(t, item.ReadingDirection.Valid)
}
})
t.Run("Filter by reading_direction - RTL only", func(t *testing.T) {
// Query all items and filter in-memory
items, err := setup.DB.ListMediaItemsByLibrary(ctx, pgtype.UUID{Bytes: parsedLibraryID, Valid: true})
require.NoError(t, err)
// Count RTL items
rtlCount := 0
for _, item := range items {
if item.ReadingDirection.String == "rtl" {
rtlCount++
}
}
assert.Equal(t, 1, rtlCount)
})
t.Run("Verify all reading directions present", func(t *testing.T) {
items, err := setup.DB.ListMediaItemsByLibrary(ctx, pgtype.UUID{Bytes: parsedLibraryID, Valid: true})
require.NoError(t, err)
directions := make(map[string]bool)
for _, item := range items {
directions[item.ReadingDirection.String] = true
}
assert.True(t, directions["rtl"])
assert.True(t, directions["ltr"])
assert.True(t, directions["auto"])
})
}
// TestUniversalMetadataFields tests universal fields apply to all formats
func TestUniversalMetadataFields(t *testing.T) {
setup := setupDeviceTest(t)
defer setup.Server.Close()
ctx := context.Background()
// Test with both comic and ebook libraries
comicLibraryID := setup.CreateLibrary(t, "Comic Test Library", "comics")
ebookLibraryID := setup.CreateLibrary(t, "Ebook Test Library", "ebooks")
parsedComicLibraryID, err := uuid.Parse(comicLibraryID)
require.NoError(t, err, "Should parse comic library UUID")
parsedEbookLibraryID, err := uuid.Parse(ebookLibraryID)
require.NoError(t, err, "Should parse ebook library UUID")
t.Run("Comic with universal fields", func(t *testing.T) {
_, err := setup.DB.CreateMediaItem(ctx, database.CreateMediaItemParams{
LibraryID: pgtype.UUID{Bytes: parsedComicLibraryID, Valid: true},
Title: "Comic with Universal Metadata",
FilePath: "/test/comic.cbz",
SeriesCount: pgtype.Int4{Int32: 10, Valid: true},
Volume: pgtype.Int4{Int32: 2, Valid: true},
Imprint: pgtype.Text{String: "DC Black Label", Valid: true},
AgeRating: pgtype.Text{String: "Mature", Valid: true},
WebUrl: pgtype.Text{String: "https://example.com/comic", Valid: true},
CommunityRating: pgtype.Float8{Float64: 9.2, Valid: true},
})
require.NoError(t, err)
items, err := setup.DB.ListMediaItemsByLibrary(ctx, pgtype.UUID{Bytes: parsedComicLibraryID, Valid: true})
require.NoError(t, err)
item := items[0]
assert.Equal(t, int32(10), item.SeriesCount.Int32)
assert.Equal(t, int32(2), item.Volume.Int32)
assert.Equal(t, "DC Black Label", item.Imprint.String)
assert.Equal(t, "Mature", item.AgeRating.String)
assert.Equal(t, "https://example.com/comic", item.WebUrl.String)
assert.Equal(t, 9.2, item.CommunityRating.Float64)
})
t.Run("Ebook with universal fields", func(t *testing.T) {
_, err := setup.DB.CreateMediaItem(ctx, database.CreateMediaItemParams{
LibraryID: pgtype.UUID{Bytes: parsedEbookLibraryID, Valid: true},
Title: "Ebook with Universal Metadata",
FilePath: "/test/book.epub",
SeriesCount: pgtype.Int4{Int32: 7, Valid: true},
Volume: pgtype.Int4{Int32: 1, Valid: true},
Imprint: pgtype.Text{String: "HarperCollins", Valid: true},
AgeRating: pgtype.Text{String: "Everyone", Valid: true},
WebUrl: pgtype.Text{String: "https://example.com/book", Valid: true},
CommunityRating: pgtype.Float8{Float64: 4.5, Valid: true},
})
require.NoError(t, err)
items, err := setup.DB.ListMediaItemsByLibrary(ctx, pgtype.UUID{Bytes: parsedEbookLibraryID, Valid: true})
require.NoError(t, err)
item := items[0]
assert.Equal(t, int32(7), item.SeriesCount.Int32)
assert.Equal(t, int32(1), item.Volume.Int32)
assert.Equal(t, "HarperCollins", item.Imprint.String)
assert.Equal(t, "Everyone", item.AgeRating.String)
assert.Equal(t, "https://example.com/book", item.WebUrl.String)
assert.Equal(t, 4.5, item.CommunityRating.Float64)
})
}
// TestComicSpecificFields tests comic-specific fields
func TestComicSpecificFields(t *testing.T) {
setup := setupDeviceTest(t)
defer setup.Server.Close()
ctx := context.Background()
libraryID := setup.CreateLibrary(t, "Comic Test Library", "comics")
parsedLibraryID, err := uuid.Parse(libraryID)
require.NoError(t, err, "Should parse library UUID")
t.Run("Alternate series info as JSONB", func(t *testing.T) {
alternateInfo := `{"alternate_series":"Ultimate X-Men","alternate_number":1,"alternate_count":12}`
_, err := setup.DB.CreateMediaItem(ctx, database.CreateMediaItemParams{
LibraryID: pgtype.UUID{Bytes: parsedLibraryID, Valid: true},
Title: "X-Men with Alternate Series",
FilePath: "/test/xmen.cbz",
AlternateInfo: []byte(alternateInfo),
ScanInformation: pgtype.Text{String: "Scanned by Minutemen", Valid: true},
Summary: pgtype.Text{String: "Professor X creates mutant team", Valid: true},
MetadataNotes: pgtype.Text{String: "From collection", Valid: true},
})
require.NoError(t, err)
items, err := setup.DB.ListMediaItemsByLibrary(ctx, pgtype.UUID{Bytes: parsedLibraryID, Valid: true})
require.NoError(t, err)
item := items[0]
assert.NotNil(t, item.AlternateInfo)
assert.JSONEq(t, alternateInfo, string(item.AlternateInfo))
assert.Equal(t, "Scanned by Minutemen", item.ScanInformation.String)
assert.Equal(t, "Professor X creates mutant team", item.Summary.String)
assert.Equal(t, "From collection", item.MetadataNotes.String)
})
}