// 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/) MType string // "" → application/xhtml+xml Body string // inner xhtml of } // 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) } } add("mimetype", "application/epub+zip") add("META-INF/container.xml", ` `) var manifest, spine strings.Builder for _, c := range chapters { mt := c.MType if mt == "" { mt = "application/xhtml+xml" } manifest.WriteString(`` + "\n") } for _, id := range spineIDs { spine.WriteString(`` + "\n") } add("OEBPS/content.opf", ` Test `+manifest.String()+` `+spine.String()+` `) for _, c := range chapters { // The FILE is (x)html when its href says so, regardless of how the manifest // media-type labels it — real epubs mislabel xhtml as application/xml, etc. switch strings.ToLower(filepath.Ext(c.Href)) { case ".xhtml", ".html", ".htm", ".xml": add("OEBPS/"+c.Href, ` c `+c.Body+``) default: add("OEBPS/"+c.Href, c.Body) // non-xhtml asset (e.g. image bytes stand-in) } } if err := zw.Close(); err != nil { t.Fatal(err) } }