1
0

epubtest.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. // Package epubtest has test helpers for checking generated EPUB files.
  2. package epubtest
  3. import (
  4. "archive/zip"
  5. "bytes"
  6. "io"
  7. "os"
  8. "os/exec"
  9. "path/filepath"
  10. "strings"
  11. "testing"
  12. "git.monoexp.com/bajdev/rekopub/internal/clean"
  13. )
  14. // Book is an opened EPUB.
  15. type Book struct {
  16. Files map[string]string // zip entry name → content
  17. Order []string // zip entry names in archive order
  18. }
  19. // Open reads an EPUB and checks the container basics every reader relies
  20. // on: "mimetype" is the first entry, stored uncompressed, and
  21. // META-INF/container.xml exists.
  22. func Open(t *testing.T, data []byte) *Book {
  23. t.Helper()
  24. zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
  25. if err != nil {
  26. t.Fatalf("not a zip file: %v", err)
  27. }
  28. b := &Book{Files: map[string]string{}}
  29. for _, f := range zr.File {
  30. rc, err := f.Open()
  31. if err != nil {
  32. t.Fatal(err)
  33. }
  34. content, err := io.ReadAll(rc)
  35. rc.Close()
  36. if err != nil {
  37. t.Fatal(err)
  38. }
  39. b.Files[f.Name] = string(content)
  40. b.Order = append(b.Order, f.Name)
  41. }
  42. if len(zr.File) == 0 || zr.File[0].Name != "mimetype" {
  43. t.Errorf("first zip entry = %v, want mimetype", b.Order)
  44. } else if zr.File[0].Method != zip.Store {
  45. t.Errorf("mimetype is compressed, want stored")
  46. }
  47. if got := b.Files["mimetype"]; got != "application/epub+zip" {
  48. t.Errorf("mimetype = %q", got)
  49. }
  50. if _, ok := b.Files["META-INF/container.xml"]; !ok {
  51. t.Errorf("META-INF/container.xml missing")
  52. }
  53. return b
  54. }
  55. // File returns the content of the single entry whose name ends in suffix.
  56. func (b *Book) File(t *testing.T, suffix string) string {
  57. t.Helper()
  58. var found []string
  59. for _, name := range b.Order {
  60. if strings.HasSuffix(name, suffix) {
  61. found = append(found, name)
  62. }
  63. }
  64. if len(found) != 1 {
  65. t.Fatalf("entries ending in %q: %v (all: %v)", suffix, found, b.Order)
  66. }
  67. return b.Files[found[0]]
  68. }
  69. // AssertWellFormed checks that every XHTML, OPF and NCX file in the book is
  70. // well-formed XML, which reading systems require.
  71. func (b *Book) AssertWellFormed(t *testing.T) {
  72. t.Helper()
  73. for _, name := range b.Order {
  74. switch filepath.Ext(name) {
  75. case ".xhtml", ".opf", ".ncx", ".xml":
  76. if err := clean.WellFormed(b.Files[name]); err != nil {
  77. t.Errorf("%s is not well-formed XML: %v", name, err)
  78. }
  79. }
  80. }
  81. }
  82. // EPUBCheck runs epubcheck on the book at path and fails the test if it
  83. // reports errors. Set EPUBCHECK_JAR to the path of epubcheck.jar to enable
  84. // it; otherwise the check is skipped. CI always sets it.
  85. func EPUBCheck(t *testing.T, path string) {
  86. t.Helper()
  87. jar := os.Getenv("EPUBCHECK_JAR")
  88. if jar == "" {
  89. t.Log("EPUBCHECK_JAR not set; skipping epubcheck")
  90. return
  91. }
  92. out, err := exec.Command("java", "-jar", jar, "--quiet", path).CombinedOutput()
  93. if err != nil {
  94. t.Errorf("epubcheck failed: %v\n%s", err, out)
  95. }
  96. }