112 lines
4 KiB
Go
112 lines
4 KiB
Go
// Package chunktest builds the on-disk source fixtures the ingest path reads. It lives in its own
|
|
// package because two packages need the SAME epub shape: the ingest unit tests and the runner's
|
|
// end-to-end tests. Duplicating a zip/OPF builder in both would let the two drift apart, and the
|
|
// whole point of the e2e fixture is that it is the file the unit tests already pin.
|
|
package chunktest
|
|
|
|
import (
|
|
"archive/zip"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type Chapter struct {
|
|
ID string
|
|
Href string // relative to the OPF dir (OEBPS/), as the MANIFEST spells it
|
|
MType string // "" → application/xhtml+xml
|
|
Body string // inner xhtml of <body>
|
|
// EntryName is the zip entry the file is actually written to (relative to the OPF dir),
|
|
// for fixtures whose manifest href does NOT spell the entry name: a percent-encoded href
|
|
// or one carrying a #fragment. "" → the href itself, so every existing fixture is unchanged.
|
|
EntryName string
|
|
}
|
|
|
|
// entryName is where this chapter's bytes are written inside OEBPS/.
|
|
func (c Chapter) entryName() string {
|
|
if c.EntryName != "" {
|
|
return c.EntryName
|
|
}
|
|
return c.Href
|
|
}
|
|
|
|
// BuildEPUB writes a minimal but real epub (container.xml → OPF → spine → xhtml)
|
|
// to a temp .epub file and returns its path. spineIDs gives the reading order
|
|
// (may differ from manifest order to prove the spine drives it).
|
|
func BuildEPUB(t *testing.T, chapters []Chapter, spineIDs []string) string {
|
|
t.Helper()
|
|
path := filepath.Join(t.TempDir(), "book.epub")
|
|
BuildEPUBAt(t, path, chapters, spineIDs)
|
|
return path
|
|
}
|
|
|
|
// BuildEPUBAt writes the epub to a caller-chosen path (used by the runner e2e).
|
|
func BuildEPUBAt(t *testing.T, path string, chapters []Chapter, spineIDs []string) {
|
|
t.Helper()
|
|
f, err := os.Create(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer f.Close()
|
|
zw := zip.NewWriter(f)
|
|
add := func(name, content string) {
|
|
w, err := zw.Create(name)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := w.Write([]byte(content)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// OCF requires "mimetype" first in the archive and STORED, not deflated. Nothing in ingest
|
|
// reads it today; the fixture matches the spec so it stays a real epub as the reader grows.
|
|
mw, err := zw.CreateHeader(&zip.FileHeader{Name: "mimetype", Method: zip.Store})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := mw.Write([]byte("application/epub+zip")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
add("META-INF/container.xml", `<?xml version="1.0"?>
|
|
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
|
|
<rootfiles><rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/></rootfiles>
|
|
</container>`)
|
|
|
|
var manifest, spine strings.Builder
|
|
for _, c := range chapters {
|
|
mt := c.MType
|
|
if mt == "" {
|
|
mt = "application/xhtml+xml"
|
|
}
|
|
manifest.WriteString(`<item id="` + c.ID + `" href="` + c.Href + `" media-type="` + mt + `"/>` + "\n")
|
|
}
|
|
for _, id := range spineIDs {
|
|
spine.WriteString(`<itemref idref="` + id + `"/>` + "\n")
|
|
}
|
|
add("OEBPS/content.opf", `<?xml version="1.0" encoding="utf-8"?>
|
|
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid">
|
|
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:title>Test</dc:title></metadata>
|
|
<manifest>`+manifest.String()+`</manifest>
|
|
<spine>`+spine.String()+`</spine>
|
|
</package>`)
|
|
|
|
for _, c := range chapters {
|
|
// The FILE is (x)html when its ENTRY name says so, regardless of how the manifest
|
|
// media-type labels it — real epubs mislabel xhtml as application/xml, etc. The entry
|
|
// name (not the href) decides, because an href may be percent-encoded or fragment-bearing.
|
|
entry := c.entryName()
|
|
switch strings.ToLower(filepath.Ext(entry)) {
|
|
case ".xhtml", ".html", ".htm", ".xml":
|
|
add("OEBPS/"+entry, `<?xml version="1.0" encoding="utf-8"?>
|
|
<html xmlns="http://www.w3.org/1999/xhtml"><head><title>c</title><style>.x{color:red}</style></head>
|
|
<body>`+c.Body+`</body></html>`)
|
|
default:
|
|
add("OEBPS/"+entry, c.Body) // non-xhtml asset (e.g. image bytes stand-in)
|
|
}
|
|
}
|
|
if err := zw.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|