diff --git a/attribute_test.go b/attribute_test.go index 9793638..3934570 100644 --- a/attribute_test.go +++ b/attribute_test.go @@ -2,6 +2,7 @@ package ipp import ( "bytes" + "github.com/stretchr/testify/assert" "testing" ) @@ -42,25 +43,18 @@ var attributeTestCases = []struct { }, } -func TestAttributeEncoding(t *testing.T) { +func TestAttributeDecoder_Decode(t *testing.T) { buf := new(bytes.Buffer) enc := NewAttributeEncoder(buf) for _, c := range attributeTestCases { - if err := enc.Encode(c.Attribute, c.Value); err != nil { - t.Errorf("error while encoding attribute %s with value %v: %v", c.Attribute, c.Value, err) - } - - result := buf.Bytes() - if !bytes.Equal(result, c.Bytes) { - t.Errorf("encoding result is not correct, expected %v, got %v", c.Bytes, result) - } - + assert.Nil(t, enc.Encode(c.Attribute, c.Value)) + assert.Equal(t, buf.Bytes(), c.Bytes, "encoding result is not correct") buf.Reset() } } -func TestAttributeDecoder(t *testing.T) { +func TestAttributeEncoder_Encode(t *testing.T) { buf := new(bytes.Buffer) dec := NewAttributeDecoder(buf) @@ -70,17 +64,9 @@ func TestAttributeDecoder(t *testing.T) { buf.Write(c.Bytes[1:]) attr, err := dec.Decode(tag) - if err != nil { - t.Errorf("error while decoding bytes %v: %v", c.Bytes, err) - } - - if attr.Name != c.Attribute { - t.Errorf("decoded attribute is not correct, expected %v, got %v", c.Attribute, attr.Name) - } - - if attr.Value != c.Value { - t.Errorf("decoded value is not correct, expected %v, got %v", c.Attribute, attr.Name) - } + assert.Nil(t, err) + assert.Equal(t, c.Attribute, attr.Name, "decoded attribute is not correct") + assert.Equal(t, c.Value, attr.Value, "decoded value is not correct") buf.Reset() } diff --git a/cups-client.go b/cups-client.go index 54c4589..9e2efc3 100644 --- a/cups-client.go +++ b/cups-client.go @@ -16,7 +16,7 @@ func NewCUPSClient(host string, port int, username, password string, useTLS bool return &CUPSClient{ippClient} } -// NewCUPSClient creates a new cups ipp client with given Adapter +// NewCUPSClientWithAdapter creates a new cups ipp client with given Adapter func NewCUPSClientWithAdapter(username string, adapter Adapter) *CUPSClient { ippClient := NewIPPClientWithAdapter(username, adapter) return &CUPSClient{ippClient} @@ -177,7 +177,7 @@ func (c *CUPSClient) CreatePrinter(name, deviceURI, ppd string, shared bool, err req.PrinterAttributes[AttributeDeviceURI] = deviceURI req.PrinterAttributes[AttributePrinterInfo] = information req.PrinterAttributes[AttributePrinterLocation] = location - req.PrinterAttributes[AttributePrinterErrorPolicy] = string(errorPolicy) + req.PrinterAttributes[AttributePrinterErrorPolicy] = errorPolicy _, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) return err @@ -217,7 +217,7 @@ func (c *CUPSClient) SetPrinterIsShared(printer string, shared bool) error { func (c *CUPSClient) SetPrinterErrorPolicy(printer string, errorPolicy string) error { req := NewRequest(OperationCupsAddModifyPrinter, 1) req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) - req.PrinterAttributes[AttributePrinterErrorPolicy] = string(errorPolicy) + req.PrinterAttributes[AttributePrinterErrorPolicy] = errorPolicy _, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) return err diff --git a/go.mod b/go.mod index 7b412fc..ad49fbc 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/phin1x/go-ipp go 1.13 + +require github.com/stretchr/testify v1.8.4 // indirect diff --git a/ipp-client.go b/ipp-client.go index 03af4be..7a7cc53 100644 --- a/ipp-client.go +++ b/ipp-client.go @@ -241,7 +241,7 @@ func (c *IPPClient) GetJobAttributes(jobID int, attributes []string) (Attributes // GetJobs returns jobs from a printer or class func (c *IPPClient) GetJobs(printer, class string, whichJobs string, myJobs bool, firstJobId, limit int, attributes []string) (map[int]Attributes, error) { req := NewRequest(OperationGetJobs, 1) - req.OperationAttributes[AttributeWhichJobs] = string(whichJobs) + req.OperationAttributes[AttributeWhichJobs] = whichJobs req.OperationAttributes[AttributeMyJobs] = myJobs if printer != "" { diff --git a/request.go b/request.go index 580c312..5d069d4 100644 --- a/request.go +++ b/request.go @@ -58,26 +58,28 @@ func (r *Request) Encode() ([]byte, error) { return nil, err } - if err := binary.Write(buf, binary.BigEndian, int8(TagOperation)); err != nil { + if err := binary.Write(buf, binary.BigEndian, TagOperation); err != nil { return nil, err } - if err := enc.Encode(AttributeCharset, Charset); err != nil { - return nil, err + if r.OperationAttributes == nil { + r.OperationAttributes = make(map[string]interface{}, 2) } - if err := enc.Encode(AttributeNaturalLanguage, CharsetLanguage); err != nil { - return nil, err + if _, found := r.OperationAttributes[AttributeCharset]; !found { + r.OperationAttributes[AttributeCharset] = Charset } - if len(r.OperationAttributes) > 0 { - if err := r.encodeOperationAttributes(enc); err != nil { - return nil, err - } + if _, found := r.OperationAttributes[AttributeNaturalLanguage]; !found { + r.OperationAttributes[AttributeNaturalLanguage] = CharsetLanguage + } + + if err := r.encodeOperationAttributes(enc); err != nil { + return nil, err } if len(r.JobAttributes) > 0 { - if err := binary.Write(buf, binary.BigEndian, int8(TagJob)); err != nil { + if err := binary.Write(buf, binary.BigEndian, TagJob); err != nil { return nil, err } for attr, value := range r.JobAttributes { @@ -88,7 +90,7 @@ func (r *Request) Encode() ([]byte, error) { } if len(r.PrinterAttributes) > 0 { - if err := binary.Write(buf, binary.BigEndian, int8(TagPrinter)); err != nil { + if err := binary.Write(buf, binary.BigEndian, TagPrinter); err != nil { return nil, err } for attr, value := range r.PrinterAttributes { @@ -98,7 +100,7 @@ func (r *Request) Encode() ([]byte, error) { } } - if err := binary.Write(buf, binary.BigEndian, int8(TagEnd)); err != nil { + if err := binary.Write(buf, binary.BigEndian, TagEnd); err != nil { return nil, err } @@ -127,6 +129,7 @@ func (r *Request) encodeOperationAttributes(enc *AttributeEncoder) error { return err } } + return nil } diff --git a/request_test.go b/request_test.go new file mode 100644 index 0000000..d2c848c --- /dev/null +++ b/request_test.go @@ -0,0 +1,59 @@ +package ipp + +import ( + "bytes" + "github.com/stretchr/testify/assert" + "testing" +) + +var requestTestCases = []struct { + Request Request + Bytes []byte + // some test cases are only for encoding, skip them in decoding tests + SkipDecoding bool +}{ + { + Request: Request{ + ProtocolVersionMajor: ProtocolVersionMajor, + ProtocolVersionMinor: ProtocolVersionMinor, + Operation: OperationPrintJob, + RequestId: 12345, + }, + Bytes: []byte{2, 0, 0, 2, 0, 0, 48, 57, 1, 71, 0, 18, 97, 116, 116, 114, 105, 98, 117, 116, 101, 115, 45, 99, 104, 97, 114, 115, 101, 116, 0, 5, 117, 116, 102, 45, 56, 72, 0, 27, 97, 116, 116, 114, 105, 98, 117, 116, 101, 115, 45, 110, 97, 116, 117, 114, 97, 108, 45, 108, 97, 110, 103, 117, 97, 103, 101, 0, 5, 101, 110, 45, 85, 83, 3}, + SkipDecoding: true, + }, + { + Request: Request{ + ProtocolVersionMajor: ProtocolVersionMajor, + ProtocolVersionMinor: ProtocolVersionMinor, + Operation: OperationPrintJob, + RequestId: 12345, + OperationAttributes: map[string]interface{}{ + AttributeCharset: Charset, + AttributeNaturalLanguage: CharsetLanguage, + }, + }, + Bytes: []byte{2, 0, 0, 2, 0, 0, 48, 57, 1, 71, 0, 18, 97, 116, 116, 114, 105, 98, 117, 116, 101, 115, 45, 99, 104, 97, 114, 115, 101, 116, 0, 5, 117, 116, 102, 45, 56, 72, 0, 27, 97, 116, 116, 114, 105, 98, 117, 116, 101, 115, 45, 110, 97, 116, 117, 114, 97, 108, 45, 108, 97, 110, 103, 117, 97, 103, 101, 0, 5, 101, 110, 45, 85, 83, 3}, + SkipDecoding: true, + }, +} + +func TestRequest_Encode(t *testing.T) { + for _, c := range requestTestCases { + data, err := c.Request.Encode() + assert.Nil(t, err) + assert.Equal(t, c.Bytes, data, "encoded request is not correct") + } +} + +func TestRequestDecoder_Decode(t *testing.T) { + for _, c := range requestTestCases { + if c.SkipDecoding { + continue + } + + request, err := NewRequestDecoder(bytes.NewReader(c.Bytes)).Decode(nil) + assert.Nil(t, err) + assert.Equal(t, &c.Request, request, "decoded request is not correct") + } +} diff --git a/response.go b/response.go index 0ba4ed9..e90b88c 100644 --- a/response.go +++ b/response.go @@ -74,44 +74,37 @@ func (r *Response) Encode() ([]byte, error) { return nil, err } - if err := binary.Write(buf, binary.BigEndian, int8(TagOperation)); err != nil { + if err := binary.Write(buf, binary.BigEndian, TagOperation); err != nil { return nil, err } - if err := enc.Encode(AttributeCharset, Charset); err != nil { - return nil, err + if r.OperationAttributes == nil { + r.OperationAttributes = make(Attributes, 0) } - if err := enc.Encode(AttributeNaturalLanguage, CharsetLanguage); err != nil { - return nil, err + if _, found := r.OperationAttributes[AttributeCharset]; !found { + r.OperationAttributes[AttributeCharset] = []Attribute{ + { + Value: Charset, + }, + } } - if len(r.OperationAttributes) > 0 { - for name, attr := range r.OperationAttributes { - if len(attr) == 0 { - continue - } - - values := make([]interface{}, len(attr)) - for i, v := range attr { - values[i] = v.Value - } - - if len(values) == 1 { - if err := enc.Encode(name, values[0]); err != nil { - return nil, err - } - } else { - if err := enc.Encode(name, values); err != nil { - return nil, err - } - } + if _, found := r.OperationAttributes[AttributeNaturalLanguage]; !found { + r.OperationAttributes[AttributeNaturalLanguage] = []Attribute{ + { + Value: CharsetLanguage, + }, } } + if err := r.encodeOperationAttributes(enc); err != nil { + return nil, err + } + if len(r.PrinterAttributes) > 0 { for _, printerAttr := range r.PrinterAttributes { - if err := binary.Write(buf, binary.BigEndian, int8(TagPrinter)); err != nil { + if err := binary.Write(buf, binary.BigEndian, TagPrinter); err != nil { return nil, err } @@ -140,7 +133,7 @@ func (r *Response) Encode() ([]byte, error) { if len(r.JobAttributes) > 0 { for _, jobAttr := range r.JobAttributes { - if err := binary.Write(buf, binary.BigEndian, int8(TagJob)); err != nil { + if err := binary.Write(buf, binary.BigEndian, TagJob); err != nil { return nil, err } @@ -174,6 +167,49 @@ func (r *Response) Encode() ([]byte, error) { return buf.Bytes(), nil } +func (r *Response) encodeOperationAttributes(enc *AttributeEncoder) error { + ordered := []string{ + AttributeCharset, + AttributeNaturalLanguage, + AttributePrinterURI, + AttributeJobID, + } + + for _, name := range ordered { + if attr, ok := r.OperationAttributes[name]; ok { + delete(r.OperationAttributes, name) + if err := encodeOperationAttribute(enc, name, attr); err != nil { + return err + } + } + } + + for name, attr := range r.OperationAttributes { + if err := encodeOperationAttribute(enc, name, attr); err != nil { + return err + } + } + + return nil +} + +func encodeOperationAttribute(enc *AttributeEncoder, name string, attr []Attribute) error { + if len(attr) == 0 { + return nil + } + + values := make([]interface{}, len(attr)) + for i, v := range attr { + values[i] = v.Value + } + + if len(values) == 1 { + return enc.Encode(name, values[0]) + } + + return enc.Encode(name, values) +} + // ResponseDecoder reads and decodes a response from a stream type ResponseDecoder struct { reader io.Reader diff --git a/response_test.go b/response_test.go new file mode 100644 index 0000000..63b2781 --- /dev/null +++ b/response_test.go @@ -0,0 +1,69 @@ +package ipp + +import ( + "bytes" + "github.com/stretchr/testify/assert" + "testing" +) + +var responseTestCases = []struct { + Response Response + Bytes []byte + // some test cases are only for encoding, skip them in decoding tests + SkipDecoding bool +}{ + { + Response: Response{ + ProtocolVersionMajor: ProtocolVersionMajor, + ProtocolVersionMinor: ProtocolVersionMinor, + StatusCode: StatusOk, + RequestId: 12345, + }, + Bytes: []byte{2, 0, 0, 0, 0, 0, 48, 57, 1, 71, 0, 18, 97, 116, 116, 114, 105, 98, 117, 116, 101, 115, 45, 99, 104, 97, 114, 115, 101, 116, 0, 5, 117, 116, 102, 45, 56, 72, 0, 27, 97, 116, 116, 114, 105, 98, 117, 116, 101, 115, 45, 110, 97, 116, 117, 114, 97, 108, 45, 108, 97, 110, 103, 117, 97, 103, 101, 0, 5, 101, 110, 45, 85, 83, 3}, + SkipDecoding: true, + }, + { + Response: Response{ + ProtocolVersionMajor: ProtocolVersionMajor, + ProtocolVersionMinor: ProtocolVersionMinor, + StatusCode: StatusOk, + RequestId: 12345, + OperationAttributes: Attributes{ + AttributeCharset: []Attribute{ + { + Value: Charset, + Tag: AttributeTagMapping[Charset], + }, + }, + AttributeNaturalLanguage: []Attribute{ + { + Value: CharsetLanguage, + Tag: AttributeTagMapping[CharsetLanguage], + }, + }, + }, + }, + Bytes: []byte{2, 0, 0, 0, 0, 0, 48, 57, 1, 71, 0, 18, 97, 116, 116, 114, 105, 98, 117, 116, 101, 115, 45, 99, 104, 97, 114, 115, 101, 116, 0, 5, 117, 116, 102, 45, 56, 72, 0, 27, 97, 116, 116, 114, 105, 98, 117, 116, 101, 115, 45, 110, 97, 116, 117, 114, 97, 108, 45, 108, 97, 110, 103, 117, 97, 103, 101, 0, 5, 101, 110, 45, 85, 83, 3}, + SkipDecoding: true, + }, +} + +func TestResponse_Encode(t *testing.T) { + for _, c := range responseTestCases { + data, err := c.Response.Encode() + assert.Nil(t, err) + assert.Equal(t, c.Bytes, data, "encoded response is not correct") + } +} + +func TestResponseDecoder_Decode(t *testing.T) { + for _, c := range responseTestCases { + if c.SkipDecoding { + continue + } + + response, err := NewResponseDecoder(bytes.NewReader(c.Bytes)).Decode(nil) + assert.Nil(t, err) + assert.Equal(t, &c.Response, response, "decoded response is not correct") + } +}