- Add delete button (SVG X, hover-reveal) and confirmation modal to comic cards - Add DELETE /api/comics/delete endpoint with path traversal protection - Fix container downloads: delegate Cloudflare-blocked requests to FlareSolverr (headless Chrome sidecar) instead of retrying with Go HTTP client, whose Linux TCP fingerprint is flagged by Cloudflare even with network_mode: host - Add FlareSolverr service to docker-compose; inject FLARESOLVERR_URL env var - Add diagnostic logging to BatcaveBizMarkup request flow - Trim URL whitespace before storing in download job - Guard Archive() against empty filelist; fix runJob error-check ordering
111 lines
2.8 KiB
Go
111 lines
2.8 KiB
Go
package comic
|
|
|
|
import (
|
|
"archive/zip"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestArchiveError(t *testing.T) {
|
|
err := ArchiveError{Message: "archive failed", Code: 1}
|
|
if err.Error() != "archive failed" {
|
|
t.Errorf("Error() = %q, want %q", err.Error(), "archive failed")
|
|
}
|
|
}
|
|
|
|
func TestArchive(t *testing.T) {
|
|
t.Run("creates cbz with image files", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
title := "TestComic"
|
|
comicDir := filepath.Join(tmpDir, title)
|
|
os.MkdirAll(comicDir, os.ModePerm)
|
|
|
|
// Create fake image files
|
|
for _, name := range []string{"TestComic 001.jpg", "TestComic 002.jpg", "TestComic 003.png"} {
|
|
os.WriteFile(filepath.Join(comicDir, name), []byte("fake image"), 0644)
|
|
}
|
|
|
|
c := &Comic{
|
|
Title: title,
|
|
LibraryPath: tmpDir,
|
|
Filelist: []string{"TestComic 001.jpg", "TestComic 002.jpg", "TestComic 003.png"},
|
|
}
|
|
|
|
err := c.Archive()
|
|
if err != nil {
|
|
t.Fatalf("Archive() unexpected error: %v", err)
|
|
}
|
|
|
|
archivePath := filepath.Join(comicDir, title+".cbz")
|
|
if _, err := os.Stat(archivePath); os.IsNotExist(err) {
|
|
t.Fatalf("expected archive %s to exist", archivePath)
|
|
}
|
|
|
|
// Verify the zip contains the image files
|
|
reader, err := zip.OpenReader(archivePath)
|
|
if err != nil {
|
|
t.Fatalf("failed to open archive: %v", err)
|
|
}
|
|
defer reader.Close()
|
|
|
|
if len(reader.File) != 3 {
|
|
t.Errorf("archive contains %d files, want 3", len(reader.File))
|
|
}
|
|
})
|
|
|
|
t.Run("excludes non-image files from archive", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
title := "TestComic"
|
|
comicDir := filepath.Join(tmpDir, title)
|
|
os.MkdirAll(comicDir, os.ModePerm)
|
|
|
|
// Create mixed files
|
|
os.WriteFile(filepath.Join(comicDir, "page-001.jpg"), []byte("image"), 0644)
|
|
os.WriteFile(filepath.Join(comicDir, "readme.txt"), []byte("text"), 0644)
|
|
os.WriteFile(filepath.Join(comicDir, "data.json"), []byte("json"), 0644)
|
|
|
|
c := &Comic{
|
|
Title: title,
|
|
LibraryPath: tmpDir,
|
|
Filelist: []string{"page-001.jpg"},
|
|
}
|
|
|
|
err := c.Archive()
|
|
if err != nil {
|
|
t.Fatalf("Archive() unexpected error: %v", err)
|
|
}
|
|
|
|
archivePath := filepath.Join(comicDir, title+".cbz")
|
|
reader, err := zip.OpenReader(archivePath)
|
|
if err != nil {
|
|
t.Fatalf("failed to open archive: %v", err)
|
|
}
|
|
defer reader.Close()
|
|
|
|
if len(reader.File) != 1 {
|
|
t.Errorf("archive contains %d files, want 1 (only .jpg)", len(reader.File))
|
|
}
|
|
})
|
|
|
|
t.Run("creates nothing when filelist is empty", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
title := "EmptyComic"
|
|
|
|
c := &Comic{
|
|
Title: title,
|
|
LibraryPath: tmpDir,
|
|
}
|
|
|
|
err := c.Archive()
|
|
if err != nil {
|
|
t.Fatalf("Archive() unexpected error: %v", err)
|
|
}
|
|
|
|
archivePath := filepath.Join(tmpDir, title, title+".cbz")
|
|
if _, err := os.Stat(archivePath); !os.IsNotExist(err) {
|
|
t.Fatalf("expected no archive to be created for empty filelist")
|
|
}
|
|
})
|
|
}
|