195 lines
6.1 KiB
Go
195 lines
6.1 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
var scanName = regexp.MustCompile(`^scan-\d{4}-\d{2}-\d{2}-\d{6}(-\d+)?\.pdf$`)
|
|
|
|
// 1440/AC5: a valid one-page PDF holding the JPEG as DCTDecode, at the image's real size.
|
|
func TestPDFHoldsTheJPEGAtItsRealSize(t *testing.T) {
|
|
jpg := fixture(t, "scan.jpg")
|
|
pdf, err := PDF(jpg, 200)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s := string(pdf)
|
|
if !strings.HasPrefix(s, "%PDF-1.4") || !strings.HasSuffix(s, "%%EOF\n") {
|
|
t.Fatalf("not a PDF: starts %q ends %q", s[:8], s[len(s)-6:])
|
|
}
|
|
if !bytes.Contains(pdf, jpg) {
|
|
t.Error("the JPEG is not embedded as it is")
|
|
}
|
|
for _, want := range []string{"/Filter /DCTDecode", "/Width 1696", "/Height 2203", "/ColorSpace /DeviceRGB", "/Count 1"} {
|
|
if !strings.Contains(s, want) {
|
|
t.Errorf("the PDF lacks %s", want)
|
|
}
|
|
}
|
|
// 1696 x 2203 px at 200 dpi is 610.56 x 793.08 pt, never the 300 dpi that was asked.
|
|
if !strings.Contains(s, "/MediaBox [0 0 610.56 793.08]") {
|
|
t.Errorf("page size wrong:\n%s", regexp.MustCompile(`/MediaBox[^\]]*\]`).FindString(s))
|
|
}
|
|
if n := strings.Count(s, "/Type /Page "); n != 1 {
|
|
t.Errorf("%d pages", n)
|
|
}
|
|
// Every xref entry points at its object.
|
|
at := strings.LastIndex(s, "startxref\n")
|
|
xref, _ := strconv.Atoi(strings.Fields(s[at+len("startxref\n"):])[0])
|
|
lines := strings.Split(s[xref:], "\n")
|
|
for i := 3; i <= 7 && i < len(lines); i++ {
|
|
off, err := strconv.Atoi(lines[i][:10])
|
|
if err != nil || !strings.HasPrefix(s[off:], fmt.Sprintf("%d 0 obj", i-2)) {
|
|
t.Errorf("xref entry %d (%q) does not point at object %d", i-2, lines[i], i-2)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1440/AC2: the page size follows the density it is given.
|
|
func TestPDFPageSizeFollowsTheDensity(t *testing.T) {
|
|
jpg := fixture(t, "scan.jpg")
|
|
a, _ := PDF(jpg, 200)
|
|
b, _ := PDF(jpg, 300)
|
|
if bytes.Equal(a, b) {
|
|
t.Fatal("200 and 300 dpi give the same page")
|
|
}
|
|
if !strings.Contains(string(b), "/MediaBox [0 0 407.04 528.72]") {
|
|
t.Errorf("300 dpi page wrong")
|
|
}
|
|
if _, err := PDF(jpg, 0); err == nil {
|
|
t.Error("0 dpi accepted")
|
|
}
|
|
}
|
|
|
|
// 1440/AC5: exactly one file, named by local time, no temporary left, its bytes the PDF.
|
|
func TestFileWritesOneNamedFileAndLeavesNoTemporary(t *testing.T) {
|
|
folder := t.TempDir()
|
|
at := time.Date(2026, 10, 20, 19, 5, 9, 0, time.Local)
|
|
path, err := File(folder, []byte("%PDF-x"), at)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if want := filepath.Join(folder, "scan-2026-10-20-190509.pdf"); path != want {
|
|
t.Fatalf("path = %s, want %s", path, want)
|
|
}
|
|
entries, _ := os.ReadDir(folder)
|
|
if len(entries) != 1 || !scanName.MatchString(entries[0].Name()) {
|
|
t.Fatalf("folder holds %v, want the one scan", entries)
|
|
}
|
|
if got, _ := os.ReadFile(path); string(got) != "%PDF-x" {
|
|
t.Fatalf("file holds %q", got)
|
|
}
|
|
}
|
|
|
|
// 1440/AC5: a name taken gets -2, -3; what was there is never overwritten.
|
|
func TestFileNeverOverwritesAndSuffixes(t *testing.T) {
|
|
folder := t.TempDir()
|
|
at := time.Date(2026, 10, 20, 19, 5, 9, 0, time.Local)
|
|
first, _ := File(folder, []byte("one"), at)
|
|
second, err := File(folder, []byte("two"), at)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
third, _ := File(folder, []byte("three"), at)
|
|
for p, want := range map[string]string{
|
|
first: "one", second: "two", third: "three",
|
|
} {
|
|
if got, _ := os.ReadFile(p); string(got) != want {
|
|
t.Errorf("%s holds %q, want %q", p, got, want)
|
|
}
|
|
}
|
|
if filepath.Base(second) != "scan-2026-10-20-190509-2.pdf" || filepath.Base(third) != "scan-2026-10-20-190509-3.pdf" {
|
|
t.Errorf("names %s and %s", filepath.Base(second), filepath.Base(third))
|
|
}
|
|
}
|
|
|
|
// 1440/AC11: a folder that is not there is made, 0755, and the scan is filed in it.
|
|
func TestFileMakesAMissingFolder(t *testing.T) {
|
|
folder := filepath.Join(t.TempDir(), "a", "scans")
|
|
path, err := File(folder, []byte("x"), time.Now())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
info, err := os.Stat(folder)
|
|
if err != nil || info.Mode().Perm() != 0o755 {
|
|
t.Fatalf("folder: %v, mode %v, want 0755", err, info)
|
|
}
|
|
if _, err := os.Stat(path); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// 1440/AC11: a file in the folder that the module did not write is left as it was by every tool.
|
|
func TestFileLeavesForeignFilesAlone(t *testing.T) {
|
|
folder := t.TempDir()
|
|
old := time.Date(2020, 1, 2, 3, 4, 5, 0, time.UTC)
|
|
foreign := map[string]string{"notes.txt": "mine", ".scan-theirs.tmp": "half", "scan-2020-01-01-000000.pdf": "someone else's"}
|
|
for n, c := range foreign {
|
|
p := filepath.Join(folder, n)
|
|
os.WriteFile(p, []byte(c), 0o600)
|
|
os.Chtimes(p, old, old)
|
|
}
|
|
if _, err := File(folder, []byte("x"), time.Now()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := List(folder, 10); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for n, c := range foreign {
|
|
p := filepath.Join(folder, n)
|
|
got, err := os.ReadFile(p)
|
|
info, _ := os.Stat(p)
|
|
if err != nil || string(got) != c || !info.ModTime().Equal(old) || info.Mode().Perm() != 0o600 {
|
|
t.Errorf("%s changed: %q %v", n, got, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1440/AC7: a relative folder writes nothing.
|
|
func TestFileRefusesARelativeFolder(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Chdir(dir)
|
|
if _, err := File("scans", []byte("x"), time.Now()); err == nil {
|
|
t.Fatal("a relative folder was accepted")
|
|
}
|
|
if entries, _ := os.ReadDir(dir); len(entries) != 0 {
|
|
t.Fatalf("wrote %v", entries)
|
|
}
|
|
}
|
|
|
|
// 1440/AC8: scan_list: newest first, only scan PDFs, limited.
|
|
func TestListIsNewestFirstAndLimited(t *testing.T) {
|
|
folder := t.TempDir()
|
|
base := time.Date(2026, 10, 1, 12, 0, 0, 0, time.UTC)
|
|
for i, n := range []string{"scan-a.pdf", "scan-b.pdf", "scan-c.pdf", "other.pdf", "scan-d.txt"} {
|
|
p := filepath.Join(folder, n)
|
|
os.WriteFile(p, []byte("x"), 0o644)
|
|
at := base.Add(time.Duration(i) * time.Hour)
|
|
os.Chtimes(p, at, at)
|
|
}
|
|
got, err := List(folder, 20)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
names := []string{}
|
|
for _, e := range got {
|
|
names = append(names, filepath.Base(e.Path))
|
|
}
|
|
if strings.Join(names, ",") != "scan-c.pdf,scan-b.pdf,scan-a.pdf" {
|
|
t.Fatalf("list = %v", names)
|
|
}
|
|
if got, _ = List(folder, 2); len(got) != 2 || filepath.Base(got[0].Path) != "scan-c.pdf" {
|
|
t.Fatalf("limit 2 = %v", got)
|
|
}
|
|
if got, err = List(filepath.Join(folder, "none"), 5); err != nil || len(got) != 0 {
|
|
t.Fatalf("missing folder = %v, %v", got, err)
|
|
}
|
|
}
|