package main import ( "context" "fmt" "io" "net/http" "strings" "testing" "bookhoard/internal/database" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // TestComicMetadataDisplay_ComicDetailPage tests that comic metadata fields display correctly on the book detail page func TestComicMetadataDisplay_ComicDetailPage(t *testing.T) { setup := setupTestServer(t) client := &http.Client{} ctx := context.Background() // Create a test comics library createLibrary := func(t *testing.T, name, libraryType string) uuid.UUID { t.Helper() // Get the library_type_id for the specified type libraryTypeRow, err := setup.DB.GetLibraryTypeByName(ctx, libraryType) require.NoError(t, err, "Should find library type") adminUUID := getTestUserID(t, setup.DB) library, err := setup.DB.CreateLibrary(ctx, database.CreateLibraryParams{ Name: name, Description: pgtype.Text{String: "Test library description", Valid: true}, LibraryTypeID: libraryTypeRow.ID, CreatedByAdminID: pgtype.UUID{Bytes: adminUUID, Valid: true}, }) require.NoError(t, err, "Should create library") libraryUUID, err := uuid.FromBytes(library.ID.Bytes[0:16]) require.NoError(t, err, "Should parse library UUID") t.Cleanup(func() { setup.DB.DeleteLibrary(ctx, library.ID) }) return libraryUUID } libraryID := createLibrary(t, "Comic Metadata Display Test Library", "comics") parsedLibraryID := libraryID // Helper function to create a test media item with specific metadata createComicMediaItem := func(t *testing.T, title string, metadata map[string]interface{}) string { t.Helper() // Build media item params params := database.CreateMediaItemParams{ LibraryID: pgtype.UUID{Bytes: parsedLibraryID, Valid: true}, Title: title, FilePath: "/test/" + title + ".cbz", FileSize: pgtype.Int8{Int64: 1024000, Valid: true}, MimeType: pgtype.Text{String: "application/x-cbz", Valid: true}, MangaType: pgtype.Text{String: "unknown", Valid: true}, ReadingDirection: pgtype.Text{String: "auto", Valid: true}, } // Apply metadata overrides if v, ok := metadata["manga_type"].(string); ok { params.MangaType = pgtype.Text{String: v, Valid: true} } if v, ok := metadata["reading_direction"].(string); ok { params.ReadingDirection = pgtype.Text{String: v, Valid: true} } if v, ok := metadata["series_count"].(int32); ok { params.SeriesCount = pgtype.Int4{Int32: v, Valid: true} } if v, ok := metadata["volume"].(int32); ok { params.Volume = pgtype.Int4{Int32: v, Valid: true} } if v, ok := metadata["imprint"].(string); ok { params.Imprint = pgtype.Text{String: v, Valid: true} } if v, ok := metadata["age_rating"].(string); ok { params.AgeRating = pgtype.Text{String: v, Valid: true} } if v, ok := metadata["web_url"].(string); ok { params.WebUrl = pgtype.Text{String: v, Valid: true} } if v, ok := metadata["community_rating"].(float64); ok { params.CommunityRating = pgtype.Float8{Float64: v, Valid: true} } if v, ok := metadata["story_arc"].(string); ok { params.StoryArc = pgtype.Text{String: v, Valid: true} } if v, ok := metadata["is_black_and_white"].(bool); ok { params.IsBlackAndWhite = pgtype.Bool{Bool: v, Valid: true} } if v, ok := metadata["scan_information"].(string); ok { params.ScanInformation = pgtype.Text{String: v, Valid: true} } if v, ok := metadata["summary"].(string); ok { params.Summary = pgtype.Text{String: v, Valid: true} } if v, ok := metadata["metadata_notes"].(string); ok { params.MetadataNotes = pgtype.Text{String: v, Valid: true} } if v, ok := metadata["alternate_info"].(string); ok { params.AlternateInfo = []byte(v) } mediaItem, err := setup.DB.CreateMediaItem(ctx, params) require.NoError(t, err, "Should create media item") mediaUUID, err := uuid.FromBytes(mediaItem.ID.Bytes[0:16]) require.NoError(t, err, "Should parse media UUID") return mediaUUID.String() } t.Run("Step 1: Reading Direction Badge - RTL Manga", func(t *testing.T) { mediaUUID := createComicMediaItem(t, "RTL Manga Test", map[string]interface{}{ "manga_type": "yes_and_right_to_left", "reading_direction": "rtl", }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) // Verify reading direction badge is displayed assert.Contains(t, body, "📖", "Should display reading direction icon") assert.Contains(t, body, "RTL", "Should display RTL reading direction") }) t.Run("Step 1: Reading Direction Badge - Western Comic (LTR)", func(t *testing.T) { mediaUUID := createComicMediaItem(t, "Western Comic Test", map[string]interface{}{ "manga_type": "no", "reading_direction": "ltr", }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) assert.Contains(t, body, "LTR", "Should display LTR reading direction") }) t.Run("Step 1: Reading Direction Badge - Hidden when auto", func(t *testing.T) { mediaUUID := createComicMediaItem(t, "Auto Direction Test", map[string]interface{}{ "reading_direction": "auto", }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) // When reading direction is "auto", the badge should not be displayed // Check for specific badge text instead of emoji (which appears elsewhere) assert.NotContains(t, body, "RTL", "Should not display RTL badge when auto") assert.NotContains(t, body, "LTR", "Should not display LTR badge when auto") assert.NotContains(t, body, "VERTICAL", "Should not display VERTICAL badge when auto") }) t.Run("Step 2: Community Rating Display", func(t *testing.T) { mediaUUID := createComicMediaItem(t, "Community Rating Test", map[string]interface{}{ "community_rating": 8.5, }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) // Verify community rating is displayed assert.Contains(t, body, "Community Rating", "Should display community rating label") assert.Contains(t, body, "8.5 / 10", "Should display formatted rating out of 10") assert.Contains(t, body, "★", "Should render star rating") }) t.Run("Step 3: Comic-Specific Badges - Age Rating", func(t *testing.T) { mediaUUID := createComicMediaItem(t, "Age Rating Test", map[string]interface{}{ "age_rating": "Teen", }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) assert.Contains(t, body, "Teen", "Should display age rating badge") }) t.Run("Step 3: Comic-Specific Badges - Black and White", func(t *testing.T) { mediaUUID := createComicMediaItem(t, "B&W Test", map[string]interface{}{ "is_black_and_white": true, }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) assert.Contains(t, body, "B&W", "Should display black and white badge") }) t.Run("Step 3: Comic-Specific Badges - Story Arc", func(t *testing.T) { mediaUUID := createComicMediaItem(t, "Story Arc Test", map[string]interface{}{ "story_arc": "The Dark Phoenix Saga", }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) assert.Contains(t, body, "📚", "Should display story arc icon") assert.Contains(t, body, "The Dark Phoenix Saga", "Should display story arc name") }) t.Run("Step 4: Universal Series Info - Series Count", func(t *testing.T) { mediaUUID := createComicMediaItem(t, "Series Count Test", map[string]interface{}{ "series_count": int32(12), }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) assert.Contains(t, body, "Series Count", "Should display series count label") assert.Contains(t, body, "12 items", "Should display series count") }) t.Run("Step 4: Universal Series Info - Volume", func(t *testing.T) { mediaUUID := createComicMediaItem(t, "Volume Test", map[string]interface{}{ "volume": int32(3), }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) assert.Contains(t, body, "Volume", "Should display volume label") assert.Contains(t, body, "Vol. 3", "Should display volume number") }) t.Run("Step 4: Universal Series Info - Imprint", func(t *testing.T) { mediaUUID := createComicMediaItem(t, "Imprint Test", map[string]interface{}{ "imprint": "Vertigo", }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) assert.Contains(t, body, "Imprint", "Should display imprint label") assert.Contains(t, body, "Vertigo", "Should display imprint name") }) t.Run("Step 5: Comic-Specific Metadata - Manga Type", func(t *testing.T) { mediaUUID := createComicMediaItem(t, "Manga Type Test", map[string]interface{}{ "manga_type": "yes_and_right_to_left", }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) assert.Contains(t, body, "Manga Type", "Should display manga type label") assert.Contains(t, body, "yes and right to left", "Should display formatted manga type") }) t.Run("Step 5: Comic-Specific Metadata - Scan Information", func(t *testing.T) { mediaUUID := createComicMediaItem(t, "Scan Info Test", map[string]interface{}{ "scan_information": "Scanned by Minutemen", }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) assert.Contains(t, body, "Scan Info", "Should display scan info label") assert.Contains(t, body, "Scanned by Minutemen", "Should display scan information") }) t.Run("Step 5: Comic-Specific Metadata - Alternate Series", func(t *testing.T) { alternateInfo := `{"alternate_series":"Ultimate X-Men","alternate_number":1,"alternate_count":12}` mediaUUID := createComicMediaItem(t, "Alternate Series Test", map[string]interface{}{ "alternate_info": alternateInfo, }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) assert.Contains(t, body, "Alternate Series", "Should display alternate series label") assert.Contains(t, body, "Ultimate X-Men", "Should display alternate series name") }) t.Run("Step 6: Summary Section - Different from description", func(t *testing.T) { mediaUUID := createComicMediaItem(t, "Summary Test", map[string]interface{}{ "summary": "Professor X creates mutant team", }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) assert.Contains(t, body, "Comic Summary", "Should display comic summary section") assert.Contains(t, body, "Professor X creates mutant team", "Should display summary content") }) t.Run("Step 7: Metadata Notes Section", func(t *testing.T) { mediaUUID := createComicMediaItem(t, "Metadata Notes Test", map[string]interface{}{ "metadata_notes": "From collection: John's Comics", }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) // DEBUG: Print the actual HTML to see what we're dealing with t.Logf("HTML body length: %d bytes", len(body)) // Find the Metadata Notes section in HTML startIdx := strings.Index(body, "Metadata Notes") if startIdx > 0 { // Print 500 characters around the Metadata Notes section endIdx := startIdx + 500 if endIdx > len(body) { endIdx = len(body) } if startIdx > 250 { t.Logf("Metadata Notes section (context):\n%s", body[startIdx-250:startIdx+250]) } else { t.Logf("Metadata Notes section (context):\n%s", body[0:endIdx]) } } else { t.Logf("WARNING: 'Metadata Notes' string not found in HTML") // Print first 1000 chars to see what we got if len(body) > 1000 { t.Logf("First 1000 chars of HTML:\n%s", body[0:1000]) } else { t.Logf("Full HTML:\n%s", body) } } // Check that the metadata notes section is present assert.Contains(t, body, "Metadata Notes", "Should display metadata notes section header") // The content is HTML-escaped by templ, so apostrophes become ' assert.Contains(t, body, "John's Comics", "Should display HTML-escaped metadata notes content") // Also check for a simpler substring without special characters assert.Contains(t, body, "collection", "Should display metadata notes content") }) t.Run("Step 8: Web URL Link - External Link", func(t *testing.T) { mediaUUID := createComicMediaItem(t, "Web URL Test", map[string]interface{}{ "web_url": "https://comicvine.gamespot.com/batman-year-one/1", "isbn": "9781234567890", }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) assert.Contains(t, body, "comicvine.gamespot.com", "Should display web URL domain") assert.Contains(t, body, "https://comicvine.gamespot.com/batman-year-one/1", "Should include full URL in href") assert.Contains(t, body, "target=\"_blank\"", "Should open in new tab") assert.Contains(t, body, "rel=\"noopener noreferrer\"", "Should have security attributes") }) t.Run("Test Case 1: Japanese Manga - Complete Display", func(t *testing.T) { alternateInfo := `{"alternate_series":"Ultimate X-Men","alternate_number":1,"alternate_count":12}` mediaUUID := createComicMediaItem(t, "Japanese Manga Complete", map[string]interface{}{ "manga_type": "yes_and_right_to_left", "reading_direction": "rtl", "series_count": int32(12), "age_rating": "Teen", "community_rating": 8.5, "story_arc": "The Dark Phoenix Saga", "alternate_info": alternateInfo, "scan_information": "Scanned by Minutemen", "imprint": "Shonen Jump", "is_black_and_white": false, }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) // Verify all manga-specific fields are displayed assert.Contains(t, body, "RTL", "Should display RTL reading direction") assert.Contains(t, body, "Teen", "Should display age rating") assert.Contains(t, body, "8.5 / 10", "Should display community rating") assert.Contains(t, body, "The Dark Phoenix Saga", "Should display story arc") assert.Contains(t, body, "Ultimate X-Men", "Should display alternate series") assert.Contains(t, body, "Shonen Jump", "Should display imprint") }) t.Run("Test Case 2: Western Comic - Complete Display", func(t *testing.T) { mediaUUID := createComicMediaItem(t, "Western Comic Complete", map[string]interface{}{ "manga_type": "no", "reading_direction": "ltr", "age_rating": "Mature", "community_rating": 9.2, "story_arc": "Batman: Year One", "series_count": int32(4), "volume": int32(1), "imprint": "Vertigo", "is_black_and_white": true, }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) // Verify Western comic fields assert.Contains(t, body, "LTR", "Should display LTR reading direction") assert.Contains(t, body, "Mature", "Should display age rating") assert.Contains(t, body, "B&W", "Should display black and white badge") assert.Contains(t, body, "Batman: Year One", "Should display story arc") assert.Contains(t, body, "Vol. 1", "Should display volume") assert.Contains(t, body, "Vertigo", "Should display imprint") }) t.Run("Test Case 3: Webtoon/Manhwa - Complete Display", func(t *testing.T) { mediaUUID := createComicMediaItem(t, "Webtoon Complete", map[string]interface{}{ "manga_type": "unknown", "reading_direction": "vertical", "age_rating": "Teen", "community_rating": 7.8, }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) // Verify webtoon fields assert.Contains(t, body, "VERTICAL", "Should display vertical reading direction") assert.Contains(t, body, "Teen", "Should display age rating") }) t.Run("Test Case 4: Regular Ebook - No Comic Metadata", func(t *testing.T) { // Create ebook library for this test ebookLibraryID := createLibrary(t, "Ebook Test Library", "ebooks") parsedEbookLibraryID := ebookLibraryID // Create ebook with only universal metadata params := database.CreateMediaItemParams{ LibraryID: pgtype.UUID{Bytes: parsedEbookLibraryID, Valid: true}, Title: "Regular Ebook", FilePath: "/test/book.epub", FileSize: pgtype.Int8{Int64: 512000, Valid: true}, MimeType: pgtype.Text{String: "application/epub+zip", Valid: true}, 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://goodreads.com/book", Valid: true}, CommunityRating: pgtype.Float8{Float64: 4.5, Valid: true}, Isbn: pgtype.Text{String: "9781234567890", Valid: true}, } mediaItem, err := setup.DB.CreateMediaItem(ctx, params) require.NoError(t, err, "Should create ebook") mediaUUID, err := uuid.FromBytes(mediaItem.ID.Bytes[0:16]) require.NoError(t, err, "Should parse media UUID") req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID.String(), nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) // Verify universal fields are displayed assert.Contains(t, body, "7 items", "Should display series count") assert.Contains(t, body, "Vol. 1", "Should display volume") assert.Contains(t, body, "HarperCollins", "Should display imprint") assert.Contains(t, body, "Everyone", "Should display age rating") assert.Contains(t, body, "goodreads.com", "Should display web URL") // Verify comic-specific fields are NOT displayed assert.NotContains(t, body, "Manga Type", "Should not display manga type for ebook") assert.NotContains(t, body, "Scan Info", "Should not display scan info for ebook") assert.NotContains(t, body, "B&W", "Should not display B&W badge for ebook") }) t.Run("Authentication Required", func(t *testing.T) { mediaUUID := createComicMediaItem(t, "Auth Test", map[string]interface{}{ "age_rating": "Teen", }) // Test without authentication req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusUnauthorized, resp.StatusCode, "Should require authentication") }) t.Run("Regular User Can View Metadata", func(t *testing.T) { mediaUUID := createComicMediaItem(t, "User View Test", map[string]interface{}{ "manga_type": "yes_and_right_to_left", "reading_direction": "rtl", "age_rating": "Teen", }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.RegularToken) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) // Regular users should see all metadata assert.Contains(t, body, "RTL", "Regular user should see reading direction") assert.Contains(t, body, "Teen", "Regular user should see age rating") }) t.Run("Minimal Metadata - Only Required Fields", func(t *testing.T) { mediaUUID := createComicMediaItem(t, "Minimal Comic", map[string]interface{}{ // No optional metadata set }) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID, nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) // Page should still render successfully assert.Contains(t, body, "Minimal Comic", "Should display title") // Use "Community Rating:" with colon to avoid matching the HTML comment assert.NotContains(t, body, "Community Rating:", "Should not show community rating when not set") assert.NotContains(t, body, "Manga Type", "Should not show manga type when unknown") }) } // readBody is a helper to read response body func readBody(resp *http.Response) string { body, _ := io.ReadAll(resp.Body) return string(body) } // TestComicMetadataDisplay_AllFieldsTogether tests all metadata fields together func TestComicMetadataDisplay_AllFieldsTogether(t *testing.T) { setup := setupTestServer(t) client := &http.Client{} ctx := context.Background() var err error // Helper function to create library createLibrary := func(t *testing.T, name, libraryType string) uuid.UUID { t.Helper() libraryTypeRow, err := setup.DB.GetLibraryTypeByName(ctx, libraryType) require.NoError(t, err, "Should find library type") adminUUID := getTestUserID(t, setup.DB) library, err := setup.DB.CreateLibrary(ctx, database.CreateLibraryParams{ Name: name, Description: pgtype.Text{String: "Test library description", Valid: true}, LibraryTypeID: libraryTypeRow.ID, CreatedByAdminID: pgtype.UUID{Bytes: adminUUID, Valid: true}, }) require.NoError(t, err, "Should create library") libraryUUID, err := uuid.FromBytes(library.ID.Bytes[0:16]) require.NoError(t, err, "Should parse library UUID") t.Cleanup(func() { setup.DB.DeleteLibrary(ctx, library.ID) }) return libraryUUID } libraryID := createLibrary(t, "Complete Metadata Test Library", "comics") parsedLibraryID := libraryID // Create a media item with ALL metadata fields populated alternateInfoStr := `{"alternate_series":"Ultimate X-Men","alternate_number":1,"alternate_count":12}` var mediaItem database.MediaItems mediaItem, err = setup.DB.CreateMediaItem(ctx, database.CreateMediaItemParams{ LibraryID: pgtype.UUID{Bytes: parsedLibraryID, Valid: true}, Title: "Complete Metadata Comic", FilePath: "/test/complete.cbz", FileSize: pgtype.Int8{Int64: 2048000, Valid: true}, MimeType: pgtype.Text{String: "application/x-cbz", Valid: true}, 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}, Imprint: pgtype.Text{String: "Marvel", Valid: true}, AgeRating: pgtype.Text{String: "Teen", Valid: true}, WebUrl: pgtype.Text{String: "https://marvel.com/comics/ultimate-xmen", Valid: true}, CommunityRating: pgtype.Float8{Float64: 8.7, Valid: true}, StoryArc: pgtype.Text{String: "The Dark Phoenix Saga", Valid: true}, IsBlackAndWhite: pgtype.Bool{Bool: false, Valid: true}, ScanInformation: pgtype.Text{String: "Scanned by Minutemen-HDC", Valid: true}, Summary: pgtype.Text{String: "Professor X creates mutant team to protect humanity", Valid: true}, MetadataNotes: pgtype.Text{String: "From personal collection", Valid: true}, AlternateInfo: []byte(alternateInfoStr), }) require.NoError(t, err) mediaUUID, err := uuid.FromBytes(mediaItem.ID.Bytes[0:16]) require.NoError(t, err) req, _ := http.NewRequest("GET", setup.Server.URL+"/media/"+mediaUUID.String(), nil) req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) body := readBody(resp) // Verify all fields are present in the rendered page expectedFields := []struct { field string expected string }{ {"Reading Direction", "RTL"}, {"Community Rating", "8.7 / 10"}, {"Age Rating", "Teen"}, {"Story Arc", "The Dark Phoenix Saga"}, {"Series Count", "12 items"}, {"Volume", "Vol. 1"}, {"Imprint", "Marvel"}, {"Manga Type", "yes and right to left"}, {"Scan Info", "Scanned by Minutemen-HDC"}, {"Alternate Series", "Ultimate X-Men"}, {"Comic Summary", "Professor X creates mutant team"}, {"Metadata Notes", "From personal collection"}, {"Web URL", "marvel.com"}, } for _, field := range expectedFields { assert.Contains(t, body, field.expected, fmt.Sprintf("Should display %s", field.field)) } }