Files
mesh-catalog/modules/brother/cmd/brother/ipp_test.go
T

144 lines
4.9 KiB
Go

package main
import (
"bytes"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"time"
)
func ippFixture(t *testing.T, name string) []byte {
t.Helper()
b, err := os.ReadFile(filepath.Join("testdata", "ipp", name))
if err != nil {
t.Fatal(err)
}
return b
}
// fakeIPP replays the captured Get-Printer-Attributes answer (testdata/ipp, 2026-10-20) and records what it
// was sent. It stands in for the printer's port 631: no test reaches a real printer.
func fakeIPP(t *testing.T, answer []byte) (url string, got *[]byte) {
t.Helper()
var sent []byte
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sent, _ = io.ReadAll(r.Body)
if r.Header.Get("Content-Type") != "application/ipp" {
http.Error(w, "not ipp", http.StatusUnsupportedMediaType)
return
}
w.Header().Set("Content-Type", "application/ipp")
w.Write(answer)
}))
t.Cleanup(srv.Close)
return srv.URL, &sent
}
// 1453/AC5: the request the module encodes is byte for byte the captured one the printer answered.
func TestTheIPPRequestIsTheCapturedOne(t *testing.T) {
got := EncodeGetPrinterAttributes(ippPrinterURI("192.0.2.10"), statusAttributes)
if want := ippFixture(t, "get-printer-attributes.request"); !bytes.Equal(got, want) {
t.Errorf("request =\n%x\nwant\n%x", got, want)
}
}
// 1453/AC5: the captured IPP answer decodes to the printer's state, its reasons and its marker levels.
func TestTheCapturedIPPAnswerDecodes(t *testing.T) {
ans, err := DecodeIPP(ippFixture(t, "get-printer-attributes.answer"))
if err != nil {
t.Fatal(err)
}
st, err := ans.State()
if err != nil {
t.Fatal(err)
}
want := PrinterState{State: "idle", StateCode: 3, Reasons: []string{"toner-low-warning"},
MakeAndModel: "Brother MFC-L8390CDW series",
Markers: []Marker{{"BK", 40, "#000000"}, {"C", 10, "#00FFFF"}, {"M", 0, "#FF00FF"}, {"Y", 100, "#FFFF00"}}}
if !reflect.DeepEqual(st, want) {
t.Errorf("state = %+v\nwant %+v", st, want)
}
}
// 1453/AC5: an answer cut short, or one with a failing status, is an error and not a state.
func TestABrokenIPPAnswerIsAnError(t *testing.T) {
good := ippFixture(t, "get-printer-attributes.answer")
if _, err := DecodeIPP(good[:len(good)-20]); err == nil {
t.Errorf("a truncated answer decoded")
}
refused := append([]byte{}, good...)
refused[2], refused[3] = 0x04, 0x00
ans, err := DecodeIPP(refused)
if err != nil {
t.Fatal(err)
}
if _, err := ans.State(); err == nil || !strings.Contains(err.Error(), "0x0400") {
t.Errorf("error = %v", err)
}
}
// 1453/AC5: printer.status gives the IPP state and the eSCL state, replayed from the captures, and sends the
// captured request.
func TestPrinterStatusToolGivesTheIPPAndESCLState(t *testing.T) {
url, sent := fakeIPP(t, ippFixture(t, "get-printer-attributes.answer"))
old := ippURL
ippURL = func(string) string { return url }
t.Cleanup(func() { ippURL = old })
f := newFakeScanner(t, nil)
tl := tools(settingsFile(t, `{"printer":"192.0.2.10","scanner":"`+f.base()+`"}`), time.Now)
got, err := tool(t, tl, "printer.status").Run(nil)
if err != nil {
t.Fatal(err)
}
m := got.(map[string]any)
if m["answers"] != true || m["state"] != "idle" || m["make-and-model"] != "Brother MFC-L8390CDW series" {
t.Errorf("answer = %v", m)
}
if r := m["state-reasons"].([]string); !reflect.DeepEqual(r, []string{"toner-low-warning"}) {
t.Errorf("reasons = %v", r)
}
if mk := m["markers"].([]Marker); len(mk) != 4 || mk[0] != (Marker{"BK", 40, "#000000"}) {
t.Errorf("markers = %v", mk)
}
if sc := m["scanner"].(map[string]any); sc["answers"] != true || sc["state"] != "Idle" || sc["adf-state"] != "ScannerAdfEmpty" {
t.Errorf("scanner = %v", sc)
}
if !bytes.Equal(*sent, ippFixture(t, "get-printer-attributes.request")) {
t.Errorf("the printer was sent %x", *sent)
}
}
// 1453/AC4 and AC5: a part that does not answer is answers false with why, the other part is still given; an
// unset printer or scanner is said by name and nothing is contacted.
func TestPrinterStatusSaysWhatDoesNotAnswerAndWhatIsUnset(t *testing.T) {
old := ippURL
ippURL = func(string) string { return "http://127.0.0.1:1/ipp/port1" }
t.Cleanup(func() { ippURL = old })
f := newFakeScanner(t, nil)
tl := tools(settingsFile(t, `{"printer":"192.0.2.10","scanner":"`+f.base()+`"}`), time.Now)
got, err := tool(t, tl, "printer.status").Run(nil)
if err != nil {
t.Fatal(err)
}
m := got.(map[string]any)
if m["answers"] != false || m["why"] == nil || m["scanner"].(map[string]any)["answers"] != true {
t.Errorf("answer = %v", m)
}
for _, c := range []struct{ js, want string }{
{`{"scanner":"` + f.base() + `"}`, "printer is not set"},
{`{"printer":"192.0.2.10"}`, "scanner is not set"},
{`{}`, "printer and scanner"},
} {
tl := tools(settingsFile(t, c.js), time.Now)
if _, err := tool(t, tl, "printer.status").Run(nil); err == nil || !strings.Contains(err.Error(), c.want) {
t.Errorf("%s: error = %v, want %q", c.js, err, c.want)
}
}
}