package ebooks import ( "archive/zip" "encoding/xml" "os" "path/filepath" "testing" ) const testOPF = ` The Test Book Ada Lovelace Charles Babbage en urn:uuid:abc Vellum Press 2026-01-01 ` const testContainer = ` ` func writeEPUB(t *testing.T, opf, container string) string { t.Helper() p := filepath.Join(t.TempDir(), "test.epub") f, err := os.Create(p) if err != nil { t.Fatal(err) } defer f.Close() zw := zip.NewWriter(f) add := func(name, body string) { w, err := zw.Create(name) if err != nil { t.Fatal(err) } if _, err := w.Write([]byte(body)); err != nil { t.Fatal(err) } } add("mimetype", "application/epub+zip") add("META-INF/container.xml", container) add("OEBPS/content.opf", opf) add("OEBPS/images/cover.png", "not-a-real-png") add("OEBPS/chapter1.xhtml", "") if err := zw.Close(); err != nil { t.Fatal(err) } return p } func TestParseMetadata(t *testing.T) { md, err := Parse(writeEPUB(t, testOPF, testContainer)) if err != nil { t.Fatalf("Parse: %v", err) } if md.Title != "The Test Book" { t.Errorf("Title = %q", md.Title) } if len(md.Authors) != 2 || md.Authors[0] != "Ada Lovelace" { t.Errorf("Authors = %v", md.Authors) } if md.Language != "en" || md.Publisher != "Vellum Press" || md.Date != "2026-01-01" { t.Errorf("metadata = %+v", md) } if md.SpineLength != 2 { t.Errorf("SpineLength = %d, want 2", md.SpineLength) } // CoverHref must be resolved relative to the OPF directory. if md.CoverHref != "OEBPS/images/cover.png" { t.Errorf("CoverHref = %q, want OEBPS/images/cover.png", md.CoverHref) } } func TestCoverHrefFallbacks(t *testing.T) { // No properties="cover-image"; rely on . opfMeta := `x ` if got := coverHref(mustParseOPF(t, opfMeta)); got != "c.jpg" { t.Errorf("meta cover fallback = %q, want c.jpg", got) } // No marker at all; fall back to an image whose href mentions cover. opfPlain := `x ` if got := coverHref(mustParseOPF(t, opfPlain)); got != "images/my-cover.png" { t.Errorf("href heuristics = %q, want images/my-cover.png", got) } // No candidate at all. opfNone := `x ` if got := coverHref(mustParseOPF(t, opfNone)); got != "" { t.Errorf("no-cover case = %q, want empty", got) } } func TestJoinOPFRefusesEscape(t *testing.T) { if got := joinOPF("OEBPS", "../../etc/passwd"); got != "" { t.Errorf("joinOPF escaped the archive: %q", got) } if got := joinOPF("OEBPS", "images/c.png"); got != "OEBPS/images/c.png" { t.Errorf("joinOPF = %q", got) } } func TestParseRejectsNonEPUB(t *testing.T) { p := filepath.Join(t.TempDir(), "not.epub") if err := os.WriteFile(p, []byte("this is not a zip"), 0o644); err != nil { t.Fatal(err) } if _, err := Parse(p); err == nil { t.Error("Parse succeeded on a non-zip file") } } func mustParseOPF(t *testing.T, s string) opfXML { t.Helper() var o opfXML if err := xml.Unmarshal([]byte(s), &o); err != nil { t.Fatal(err) } return o }