package main import ( "strings" "testing" ) func TestTheManifestIsTheSchedulerAndDriverlessPrintingAndNoVendorDriver(t *testing.T) { m := readManifest(t) holdsTheBundle(t, m, "cups") if got := strings.Join(m.packages(), ","); got != "cups,cups-filters" { t.Errorf("packages %s: a vendor driver is from outside the official repositories, and no queue measured needs one but the desktop's Canon", got) } s := m.services() for _, u := range []string{"cups.socket", "cups.service"} { if s[u] == nil || s[u]["state"] != "running" || s[u]["boot"] != "enabled" { t.Errorf("%s: %v", u, s[u]) } } for _, r := range m.Resources { if r["type"] == "file" { t.Errorf("the queues and cupsd's files are CUPS's own: %v", r["id"]) } } } // The desktop's two queues on 2026-10-04. func theDesktop(t *testing.T, more func(line string, c Cmd) (Result, bool)) *fake { return using(t, func(line string, c Cmd) Result { if more != nil { if r, handled := more(line, c); handled { return r } } switch line { case "lpstat -r": return ok("scheduler is running\n") case "lpstat -d": return ok("system default destination: Brother_MFC_Novox\n") case "lpstat -p": return ok("printer Brother_MFC_Novox is idle. enabled since Mon Jun 29 21:34:30 2026\n" + "printer Kanjuro disabled since Sun Jun 9 11:16:03 2024 -\n\tPaused\n") case "lpstat -a": return ok("Brother_MFC_Novox accepting requests since Mon Jun 29 21:34:30 2026\nKanjuro not accepting requests since Sun Jun 9 11:16:03 2024 -\n\tRejecting Jobs\n") case "lpoptions -p Brother_MFC_Novox": return ok(`copies=1 device-uri=ipp://192.0.2.171/ipp/port1 finishings=3 marker-levels=70,100,8,100 marker-low-levels=10,10,10,10 marker-names='Black\ Toner\ Cartridge,Cyan\ Toner\ Cartridge,Magenta\ Toner\ Cartridge,Yellow\ Toner\ Cartridge' marker-types=toner,toner,toner,toner printer-is-shared=false printer-make-and-model='Printer - IPP Everywhere' printer-state-reasons=none`) case "lpoptions -p Kanjuro": return ok(`device-uri=cnijnet:/18-0C-AC-B0-62-83 printer-is-shared=false printer-make-and-model='Canon MG4200 series Ver.3.80' printer-state-reasons=paused`) } return Result{Status: 9, Stderr: "unexpected " + line} }) } func TestPrintersReadsStateDeviceModelAndSupplies(t *testing.T) { theDesktop(t, nil) got, err := Printers() if err != nil || len(got.Printers) != 2 || got.Default != "Brother_MFC_Novox" || got.Scheduler != "scheduler is running" { t.Fatalf("%+v %v", got, err) } b, k := got.Printers[0], got.Printers[1] if b.State != "idle" || !b.Enabled || !b.Accepting || !b.Default || !b.Driverless || b.URI != "ipp://192.0.2.171/ipp/port1" || len(b.Reasons) != 0 { t.Errorf("%+v", b) } if len(b.Markers) != 4 || b.Markers[0].Name != "Black Toner Cartridge" || b.Markers[2].Level != 8 || !b.Markers[2].Low || b.Markers[0].Low { t.Errorf("markers %+v", b.Markers) } if k.State != "stopped" || k.Enabled || k.Accepting || k.Driverless || strings.Join(k.Reasons, ",") != "paused" || k.Message != "Paused" { t.Errorf("%+v", k) } } func TestPrintersWithoutAQueueOrAScheduler(t *testing.T) { using(t, func(line string, c Cmd) Result { if line == "lpstat -r" { return ok("scheduler is running\n") } return Result{Status: 1, Stderr: "lpstat: No destinations added.\n"} }) got, err := Printers() if err != nil || len(got.Printers) != 0 { t.Fatalf("no queue is an empty answer, not a failure: %+v %v", got, err) } using(t, func(string, Cmd) Result { return Result{Status: 1, Stderr: "lpstat: Scheduler is not running.\n"} }) if _, err := Printers(); err == nil || !strings.Contains(err.Error(), "scheduler is not running") { t.Fatalf("%v", err) } } func TestLpoptionsQuotingIsRead(t *testing.T) { o := lpoptionsOf(`a=1 b='x y' c=p\ q d e=`) if o["a"] != "1" || o["b"] != "x y" || o["c"] != "p q" || o["d"] != "" || o["e"] != "" { t.Errorf("%v", o) } } func TestQueueReadsJobsNewestFirstAndBounded(t *testing.T) { f := using(t, func(string, Cmd) Result { return ok("Brother-6 jochen 1024 Mon Jul 8 12:15:34 2024\nBrother-8 jochen 2048 Mon Jul 8 12:19:56 2024\nBrother-7 other 1024 Mon Jul 8 12:15:23 2024\n") }) got, err := Queue("Brother", true, 2) jobs := got["jobs"].([]Job) if err != nil || got["count"] != 3 || len(jobs) != 2 || jobs[0].ID != "Brother-8" || jobs[0].Printer != "Brother" || jobs[0].Bytes != 2048 || jobs[0].Submitted != "Mon Jul 8 12:19:56 2024" { t.Fatalf("%+v %v", got, err) } if f.lines()[0] != "lpstat -W completed -o Brother" { t.Errorf("%v", f.lines()) } if _, err := Queue("-h", false, 5); err == nil { t.Error("an option as a printer") } } func TestCancelTriesTheAccountThenRootWhenCUPSRefusesIt(t *testing.T) { f := using(t, func(line string, c Cmd) Result { if !strings.HasPrefix(line, "sudo") { return Result{Status: 1, Stderr: "cancel: Forbidden\n"} } return ok("") }) got, err := Cancel("Brother-12", "", false) if err != nil || got["as_root"] != true { t.Fatalf("%v %v", got, err) } if strings.Join(f.lines(), "|") != "cancel Brother-12|sudo -n cancel Brother-12" { t.Errorf("%v", f.lines()) } f = using(t, func(string, Cmd) Result { return ok("") }) if got, err := Cancel("", "Brother", true); err != nil || got["as_root"] != false || f.lines()[0] != "cancel -a Brother" { t.Fatalf("%v %v %v", got, err, f.lines()) } using(t, func(string, Cmd) Result { return Result{Status: 1, Stderr: "cancel: Unknown job 99\n"} }) if _, err := Cancel("99", "", false); err == nil || !strings.Contains(err.Error(), "Unknown job") { t.Errorf("a failure that is not a refusal is not retried as root: %v", err) } for _, bad := range [][3]string{{"", "", ""}, {"12; rm", "", ""}, {"", "", "all"}} { if _, err := Cancel(bad[0], bad[1], bad[2] == "all"); err == nil { t.Errorf("%v accepted", bad) } } } func TestPrintChecksTheFileAndOptionsAndAnswersTheJob(t *testing.T) { was := statFile defer func() { statFile = was }() statFile = func(p string) error { if p == "/home/op/doc.pdf" { return nil } return errString("no such file") } f := using(t, func(string, Cmd) Result { return ok("request id is Brother-13 (1 file(s))\n") }) got, err := Print("/home/op/doc.pdf", "Brother", 2, map[string]string{"sides": "two-sided-long-edge", "media": "A4"}, "") if err != nil || got["job"] != "Brother-13" { t.Fatalf("%v %v", got, err) } if l := f.lines()[0]; l != "lp -d Brother -n 2 -o media=A4 -o sides=two-sided-long-edge -t doc.pdf -- /home/op/doc.pdf" { t.Errorf("%s", l) } if _, err := Print("doc.pdf", "", 1, nil, ""); err == nil { t.Error("a relative file") } if _, err := Print("/home/op/missing.pdf", "", 1, nil, ""); err == nil { t.Error("a missing file") } if _, err := optionsOf(map[string]any{"options": map[string]any{"sides": "x y"}}); err == nil { t.Error("an option value with a space") } if _, err := optionsOf(map[string]any{"options": map[string]any{"-o": "x"}}); err == nil { t.Error("an option name that is an option") } using(t, func(string, Cmd) Result { return Result{Status: 1, Stderr: "lp: Error - No default destination."} }) if _, err := Print("/home/op/doc.pdf", "", 1, nil, ""); err == nil || !strings.Contains(err.Error(), "no default printer") { t.Errorf("%v", err) } } type errString string func (e errString) Error() string { return string(e) } func TestDefaultReadsAndSetsThroughSudo(t *testing.T) { f := theDesktop(t, func(line string, c Cmd) (Result, bool) { if strings.HasPrefix(line, "sudo -n lpadmin") { return ok(""), true } return Result{}, false }) got, err := Default("") if err != nil || got["default"] != "Brother_MFC_Novox" { t.Fatalf("%v %v", got, err) } got, err = Default("Kanjuro") if err != nil || got["default"] != "Kanjuro" || got["was"] != "Brother_MFC_Novox" { t.Fatalf("%v %v", got, err) } if l := f.lines(); l[len(l)-1] != "sudo -n lpadmin -d Kanjuro" { t.Errorf("%v", l) } } func TestResumeEnablesAndAcceptsThroughSudo(t *testing.T) { f := using(t, func(string, Cmd) Result { return ok("") }) if _, err := Resume("Kanjuro"); err != nil { t.Fatal(err) } if strings.Join(f.lines(), "|") != "sudo -n cupsenable Kanjuro|sudo -n cupsaccept Kanjuro" { t.Errorf("%v", f.lines()) } } func TestDriversNamesForeignDriverPackagesAndOnesNoQueueUses(t *testing.T) { theDesktop(t, func(line string, c Cmd) (Result, bool) { switch { case strings.HasPrefix(line, "pacman -Qo"): return ok("/usr/lib/cups/filter/ is owned by brother-mfc-l8390cdw 3.5.1-2\n/usr/lib/cups/filter/ is owned by cups 2:2.4.19-1\n/usr/lib/cups/filter/ is owned by cups-filters 2.0.1-2\n/usr/lib/cups/backend/ is owned by cnijfilter-mg4200 3.80-6\n/usr/lib/cups/backend/ is owned by cups 2:2.4.19-1\n"), true case line == "pacman -Qqm": return ok("brother-mfc-l8390cdw\ncnijfilter-mg4200\nsnapd\n"), true case line == "lpinfo -m": return ok("drv:///sample.drv/dymo.ppd DYMO Label Printer\ncanonmg4200.ppd Canon MG4200 series Ver.3.80\neverywhere IPP Everywhere\n"), true } return Result{}, false }) got, err := Drivers("canon", 10) if err != nil || len(got.Queues) != 2 || len(got.Packages) != 2 || got.Matched != 1 || got.Models[0].PPD != "canonmg4200.ppd" { t.Fatalf("%+v %v", got, err) } if !got.Packages[0].Foreign || got.Packages[0].Package != "brother-mfc-l8390cdw" { t.Errorf("%+v", got.Packages) } all := strings.Join(got.Findings, ";") if !strings.Contains(all, "cnijfilter-mg4200 is not from the official") || strings.Contains(all, "every queue prints driverless") { t.Errorf("the Canon queue uses its driver, so not every queue is driverless: %s", all) } }