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) }) }