test: add integration tests for comic metadata display features
Add comprehensive integration tests for all 8 comic metadata display steps
on the book detail page, ensuring frontend rendering works correctly with
real database data.
## Test Coverage
### Step Tests (8 individual tests)
1. Reading Direction Badge - Tests RTL, LTR, vertical, and auto-hide behavior
2. Community Rating Display - Validates star rendering and numeric score
3. Comic-Specific Badges - Tests age rating, B&W, and story arc badges
4. Universal Series Info - Tests series count, volume, and imprint display
5. Comic-Specific Metadata - Tests manga type, scan info, alternate series
6. Summary Section - Tests ComicInfo.xml summary rendering
7. Metadata Notes Section - Tests technical notes display
8. Web URL Link - Tests external link rendering with security attributes
### Test Case Scenarios (4 complete scenarios)
1. Japanese Manga - Complete metadata display (RTL + all badges)
2. Western Comic - LTR direction with story arc
3. Webtoon/Manhwa - Vertical reading direction
4. Regular Ebook - No comic metadata (minimal display)
### Authentication Tests (2 tests)
- Anonymous users are denied access (401)
- Regular users can view metadata (same as admins)
### Edge Case Tests (2 tests)
- Minimal Metadata - Only required fields (no optional metadata)
- All Fields Together - Comprehensive metadata display
## Test Infrastructure
- Uses setupTestServer() helper for isolated test environment
- Uses createComicMediaItem() helper for flexible test data creation
- Uses createLibrary() helper with automatic cleanup
- Tests use pgtype types matching production code
- All tests run with admin authentication by default
- Tests check both structure and content in rendered HTML
## Test Details
- 21 total subtests covering all metadata display features
- Tests verify HTML structure, content presence, and proper escaping
- Uses t.Run() for organized test output
- Tests clean up resources automatically with t.Cleanup()
- Checks for proper HTML entity encoding (e.g., apostrophes)
- Validates conditional rendering (hide when values not set)
## Known Issues
- Metadata Notes content validation uses partial string matching to handle
HTML escaping variations
- Reading Direction test checks specific direction strings (RTL/LTR/VERTICAL)
to avoid false positives from emoji appearing elsewhere in the UI
- Community Rating test uses colon ("Community Rating:") to avoid matching
HTML comments
Related: Template implementation commit (562ca53)
This commit is contained in:
@@ -0,0 +1,750 @@
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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 resp.Body.Close()
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user