1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-29 07:52:50 -05:00

cups: add comprehensive CUPs setting page

- Add printers
- Delete printers
- Use polkit APIs as fallback on auth errors
- Fix ref system to conditionally subscribe to cups when wanted
This commit is contained in:
bbedward
2025-11-29 17:35:21 -05:00
parent df663aceb9
commit e6c3ae9397
31 changed files with 5993 additions and 558 deletions

View File

@@ -1,12 +1,34 @@
package cups
import (
"errors"
"strings"
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
"github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp"
)
func isAuthError(err error) bool {
if err == nil {
return false
}
var httpErr ipp.HTTPError
if errors.As(err, &httpErr) {
return httpErr.Code == 401 || httpErr.Code == 403
}
var ippErr ipp.IPPError
if errors.As(err, &ippErr) {
return ippErr.Status == ipp.StatusErrorForbidden ||
ippErr.Status == ipp.StatusErrorNotAuthenticated ||
ippErr.Status == ipp.StatusErrorNotAuthorized
}
return false
}
func (m *Manager) GetPrinters() ([]Printer, error) {
attributes := []string{
ipp.AttributePrinterName,
@@ -21,6 +43,9 @@ func (m *Manager) GetPrinters() ([]Printer, error) {
printerAttrs, err := m.client.GetPrinters(attributes)
if err != nil {
if isNoPrintersError(err) {
return []Printer{}, nil
}
return nil, err
}
@@ -91,17 +116,289 @@ func (m *Manager) GetJobs(printerName string, whichJobs string) ([]Job, error) {
}
func (m *Manager) CancelJob(jobID int) error {
return m.client.CancelJob(jobID, false)
err := m.client.CancelJob(jobID, false)
if isAuthError(err) && m.pkHelper != nil {
err = m.pkHelper.JobCancelPurge(jobID, false)
}
if err == nil {
m.RefreshState()
}
return err
}
func (m *Manager) PausePrinter(printerName string) error {
return m.client.PausePrinter(printerName)
err := m.client.PausePrinter(printerName)
if isAuthError(err) && m.pkHelper != nil {
err = m.pkHelper.PrinterSetEnabled(printerName, false)
}
if err == nil {
m.RefreshState()
}
return err
}
func (m *Manager) ResumePrinter(printerName string) error {
return m.client.ResumePrinter(printerName)
err := m.client.ResumePrinter(printerName)
if isAuthError(err) && m.pkHelper != nil {
err = m.pkHelper.PrinterSetEnabled(printerName, true)
}
if err == nil {
m.RefreshState()
}
return err
}
func (m *Manager) PurgeJobs(printerName string) error {
return m.client.CancelAllJob(printerName, true)
err := m.client.CancelAllJob(printerName, true)
if err == nil {
m.RefreshState()
}
return err
}
func (m *Manager) GetDevices() ([]Device, error) {
if m.pkHelper != nil {
return m.pkHelper.DevicesGet(10, 0, nil, nil)
}
deviceAttrs, err := m.client.GetDevices()
if err != nil {
return nil, err
}
devices := make([]Device, 0, len(deviceAttrs))
for uri, attrs := range deviceAttrs {
device := Device{
URI: uri,
Class: getStringAttr(attrs, "device-class"),
Info: getStringAttr(attrs, "device-info"),
MakeModel: getStringAttr(attrs, "device-make-and-model"),
ID: getStringAttr(attrs, "device-id"),
Location: getStringAttr(attrs, "device-location"),
}
devices = append(devices, device)
}
return devices, nil
}
func (m *Manager) GetPPDs() ([]PPD, error) {
ppdAttrs, err := m.client.GetPPDs()
if err != nil {
return nil, err
}
ppds := make([]PPD, 0, len(ppdAttrs))
for name, attrs := range ppdAttrs {
ppd := PPD{
Name: name,
NaturalLanguage: getStringAttr(attrs, "ppd-natural-language"),
MakeModel: getStringAttr(attrs, ipp.AttributePPDMakeAndModel),
DeviceID: getStringAttr(attrs, "ppd-device-id"),
Product: getStringAttr(attrs, "ppd-product"),
PSVersion: getStringAttr(attrs, "ppd-psversion"),
Type: getStringAttr(attrs, "ppd-type"),
}
ppds = append(ppds, ppd)
}
return ppds, nil
}
func (m *Manager) GetClasses() ([]PrinterClass, error) {
attributes := []string{
ipp.AttributePrinterName,
ipp.AttributePrinterUriSupported,
ipp.AttributePrinterState,
ipp.AttributeMemberURIs,
ipp.AttributeMemberNames,
ipp.AttributePrinterLocation,
ipp.AttributePrinterInfo,
}
classAttrs, err := m.client.GetClasses(attributes)
if err != nil {
return nil, err
}
classes := make([]PrinterClass, 0, len(classAttrs))
for _, attrs := range classAttrs {
class := PrinterClass{
Name: getStringAttr(attrs, ipp.AttributePrinterName),
URI: getStringAttr(attrs, ipp.AttributePrinterUriSupported),
State: parsePrinterState(attrs),
Location: getStringAttr(attrs, ipp.AttributePrinterLocation),
Info: getStringAttr(attrs, ipp.AttributePrinterInfo),
Members: getStringSliceAttr(attrs, ipp.AttributeMemberNames),
}
classes = append(classes, class)
}
return classes, nil
}
func (m *Manager) CreatePrinter(name, deviceURI, ppd string, shared bool, errorPolicy, information, location string) error {
usedPkHelper := false
err := m.client.CreatePrinter(name, deviceURI, ppd, shared, errorPolicy, information, location)
if isAuthError(err) && m.pkHelper != nil {
if err = m.pkHelper.PrinterAdd(name, deviceURI, ppd, information, location); err != nil {
return err
}
usedPkHelper = true
} else if err != nil {
return err
}
if usedPkHelper {
m.pkHelper.PrinterSetEnabled(name, true)
m.pkHelper.PrinterSetAcceptJobs(name, true, "")
} else {
if err := m.client.ResumePrinter(name); isAuthError(err) && m.pkHelper != nil {
m.pkHelper.PrinterSetEnabled(name, true)
}
if err := m.client.AcceptJobs(name); isAuthError(err) && m.pkHelper != nil {
m.pkHelper.PrinterSetAcceptJobs(name, true, "")
}
}
m.RefreshState()
return nil
}
func (m *Manager) DeletePrinter(printerName string) error {
err := m.client.DeletePrinter(printerName)
if isAuthError(err) && m.pkHelper != nil {
err = m.pkHelper.PrinterDelete(printerName)
}
if err == nil {
m.RefreshState()
}
return err
}
func (m *Manager) AcceptJobs(printerName string) error {
err := m.client.AcceptJobs(printerName)
if isAuthError(err) && m.pkHelper != nil {
err = m.pkHelper.PrinterSetAcceptJobs(printerName, true, "")
}
if err == nil {
m.RefreshState()
}
return err
}
func (m *Manager) RejectJobs(printerName string) error {
err := m.client.RejectJobs(printerName)
if isAuthError(err) && m.pkHelper != nil {
err = m.pkHelper.PrinterSetAcceptJobs(printerName, false, "")
}
if err == nil {
m.RefreshState()
}
return err
}
func (m *Manager) SetPrinterShared(printerName string, shared bool) error {
err := m.client.SetPrinterIsShared(printerName, shared)
if isAuthError(err) && m.pkHelper != nil {
err = m.pkHelper.PrinterSetShared(printerName, shared)
}
if err == nil {
m.RefreshState()
}
return err
}
func (m *Manager) SetPrinterLocation(printerName, location string) error {
err := m.client.SetPrinterLocation(printerName, location)
if isAuthError(err) && m.pkHelper != nil {
err = m.pkHelper.PrinterSetLocation(printerName, location)
}
if err == nil {
m.RefreshState()
}
return err
}
func (m *Manager) SetPrinterInfo(printerName, info string) error {
err := m.client.SetPrinterInformation(printerName, info)
if isAuthError(err) && m.pkHelper != nil {
err = m.pkHelper.PrinterSetInfo(printerName, info)
}
if err == nil {
m.RefreshState()
}
return err
}
func (m *Manager) MoveJob(jobID int, destPrinter string) error {
err := m.client.MoveJob(jobID, destPrinter)
if err == nil {
m.RefreshState()
}
return err
}
func (m *Manager) PrintTestPage(printerName string) (int, error) {
jobID, err := m.client.PrintTestPage(printerName, strings.NewReader(config.TestPage), len(config.TestPage))
if err == nil {
m.RefreshState()
}
return jobID, err
}
func (m *Manager) AddPrinterToClass(className, printerName string) error {
err := m.client.AddPrinterToClass(className, printerName)
if isAuthError(err) && m.pkHelper != nil {
err = m.pkHelper.ClassAddPrinter(className, printerName)
}
if err == nil {
m.RefreshState()
}
return err
}
func (m *Manager) RemovePrinterFromClass(className, printerName string) error {
err := m.client.DeletePrinterFromClass(className, printerName)
if isAuthError(err) && m.pkHelper != nil {
err = m.pkHelper.ClassDeletePrinter(className, printerName)
}
if err == nil {
m.RefreshState()
}
return err
}
func (m *Manager) DeleteClass(className string) error {
err := m.client.DeleteClass(className)
if isAuthError(err) && m.pkHelper != nil {
err = m.pkHelper.ClassDelete(className)
}
if err == nil {
m.RefreshState()
}
return err
}
func (m *Manager) RestartJob(jobID int) error {
err := m.client.RestartJob(jobID)
if isAuthError(err) && m.pkHelper != nil {
err = m.pkHelper.JobRestart(jobID)
}
if err == nil {
m.RefreshState()
}
return err
}
func (m *Manager) HoldJob(jobID int, holdUntil string) error {
err := m.client.HoldJobUntil(jobID, holdUntil)
if isAuthError(err) && m.pkHelper != nil {
err = m.pkHelper.JobSetHoldUntil(jobID, holdUntil)
}
if err == nil {
m.RefreshState()
}
return err
}

View File

@@ -0,0 +1,235 @@
package cups_test
import (
"testing"
mocks_cups "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/cups"
mocks_pkhelper "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/cups_pkhelper"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
"github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func authErr() error {
return ipp.IPPError{Status: ipp.StatusErrorForbidden}
}
func TestManager_CancelJob_WithPkHelper(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().CancelJob(1, false).Return(authErr())
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
mockPk := mocks_pkhelper.NewMockPkHelper(t)
mockPk.EXPECT().JobCancelPurge(1, false).Return(nil)
m := cups.NewTestManager(mockClient, mockPk)
assert.NoError(t, m.CancelJob(1))
}
func TestManager_CancelJob_PkHelperError(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().CancelJob(1, false).Return(authErr())
mockPk := mocks_pkhelper.NewMockPkHelper(t)
mockPk.EXPECT().JobCancelPurge(1, false).Return(assert.AnError)
m := cups.NewTestManager(mockClient, mockPk)
assert.Error(t, m.CancelJob(1))
}
func TestManager_PausePrinter_WithPkHelper(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().PausePrinter("printer1").Return(authErr())
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
mockPk := mocks_pkhelper.NewMockPkHelper(t)
mockPk.EXPECT().PrinterSetEnabled("printer1", false).Return(nil)
m := cups.NewTestManager(mockClient, mockPk)
assert.NoError(t, m.PausePrinter("printer1"))
}
func TestManager_ResumePrinter_WithPkHelper(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().ResumePrinter("printer1").Return(authErr())
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
mockPk := mocks_pkhelper.NewMockPkHelper(t)
mockPk.EXPECT().PrinterSetEnabled("printer1", true).Return(nil)
m := cups.NewTestManager(mockClient, mockPk)
assert.NoError(t, m.ResumePrinter("printer1"))
}
func TestManager_GetDevices_WithPkHelper(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockPk := mocks_pkhelper.NewMockPkHelper(t)
mockPk.EXPECT().DevicesGet(10, 0, []string(nil), []string(nil)).Return([]cups.Device{
{URI: "usb://HP/LaserJet", Class: "direct"},
}, nil)
m := cups.NewTestManager(mockClient, mockPk)
got, err := m.GetDevices()
assert.NoError(t, err)
assert.Len(t, got, 1)
assert.Equal(t, "usb://HP/LaserJet", got[0].URI)
}
func TestManager_GetDevices_PkHelperError(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockPk := mocks_pkhelper.NewMockPkHelper(t)
mockPk.EXPECT().DevicesGet(10, 0, []string(nil), []string(nil)).Return(nil, assert.AnError)
m := cups.NewTestManager(mockClient, mockPk)
_, err := m.GetDevices()
assert.Error(t, err)
}
func TestManager_CreatePrinter_WithPkHelper(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().CreatePrinter("newprinter", "usb://HP", "generic.ppd", true, "stop-printer", "info", "location").Return(authErr())
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
mockPk := mocks_pkhelper.NewMockPkHelper(t)
mockPk.EXPECT().PrinterAdd("newprinter", "usb://HP", "generic.ppd", "info", "location").Return(nil)
mockPk.EXPECT().PrinterSetEnabled("newprinter", true).Return(nil)
mockPk.EXPECT().PrinterSetAcceptJobs("newprinter", true, "").Return(nil)
m := cups.NewTestManager(mockClient, mockPk)
assert.NoError(t, m.CreatePrinter("newprinter", "usb://HP", "generic.ppd", true, "stop-printer", "info", "location"))
}
func TestManager_DeletePrinter_WithPkHelper(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().DeletePrinter("printer1").Return(authErr())
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
mockPk := mocks_pkhelper.NewMockPkHelper(t)
mockPk.EXPECT().PrinterDelete("printer1").Return(nil)
m := cups.NewTestManager(mockClient, mockPk)
assert.NoError(t, m.DeletePrinter("printer1"))
}
func TestManager_AcceptJobs_WithPkHelper(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().AcceptJobs("printer1").Return(authErr())
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
mockPk := mocks_pkhelper.NewMockPkHelper(t)
mockPk.EXPECT().PrinterSetAcceptJobs("printer1", true, "").Return(nil)
m := cups.NewTestManager(mockClient, mockPk)
assert.NoError(t, m.AcceptJobs("printer1"))
}
func TestManager_RejectJobs_WithPkHelper(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().RejectJobs("printer1").Return(authErr())
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
mockPk := mocks_pkhelper.NewMockPkHelper(t)
mockPk.EXPECT().PrinterSetAcceptJobs("printer1", false, "").Return(nil)
m := cups.NewTestManager(mockClient, mockPk)
assert.NoError(t, m.RejectJobs("printer1"))
}
func TestManager_SetPrinterShared_WithPkHelper(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().SetPrinterIsShared("printer1", true).Return(authErr())
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
mockPk := mocks_pkhelper.NewMockPkHelper(t)
mockPk.EXPECT().PrinterSetShared("printer1", true).Return(nil)
m := cups.NewTestManager(mockClient, mockPk)
assert.NoError(t, m.SetPrinterShared("printer1", true))
}
func TestManager_SetPrinterLocation_WithPkHelper(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().SetPrinterLocation("printer1", "Office").Return(authErr())
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
mockPk := mocks_pkhelper.NewMockPkHelper(t)
mockPk.EXPECT().PrinterSetLocation("printer1", "Office").Return(nil)
m := cups.NewTestManager(mockClient, mockPk)
assert.NoError(t, m.SetPrinterLocation("printer1", "Office"))
}
func TestManager_SetPrinterInfo_WithPkHelper(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().SetPrinterInformation("printer1", "Main Printer").Return(authErr())
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
mockPk := mocks_pkhelper.NewMockPkHelper(t)
mockPk.EXPECT().PrinterSetInfo("printer1", "Main Printer").Return(nil)
m := cups.NewTestManager(mockClient, mockPk)
assert.NoError(t, m.SetPrinterInfo("printer1", "Main Printer"))
}
func TestManager_AddPrinterToClass_WithPkHelper(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().AddPrinterToClass("office", "printer1").Return(authErr())
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
mockPk := mocks_pkhelper.NewMockPkHelper(t)
mockPk.EXPECT().ClassAddPrinter("office", "printer1").Return(nil)
m := cups.NewTestManager(mockClient, mockPk)
assert.NoError(t, m.AddPrinterToClass("office", "printer1"))
}
func TestManager_RemovePrinterFromClass_WithPkHelper(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().DeletePrinterFromClass("office", "printer1").Return(authErr())
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
mockPk := mocks_pkhelper.NewMockPkHelper(t)
mockPk.EXPECT().ClassDeletePrinter("office", "printer1").Return(nil)
m := cups.NewTestManager(mockClient, mockPk)
assert.NoError(t, m.RemovePrinterFromClass("office", "printer1"))
}
func TestManager_DeleteClass_WithPkHelper(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().DeleteClass("office").Return(authErr())
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
mockPk := mocks_pkhelper.NewMockPkHelper(t)
mockPk.EXPECT().ClassDelete("office").Return(nil)
m := cups.NewTestManager(mockClient, mockPk)
assert.NoError(t, m.DeleteClass("office"))
}
func TestManager_RestartJob_WithPkHelper(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().RestartJob(1).Return(authErr())
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
mockPk := mocks_pkhelper.NewMockPkHelper(t)
mockPk.EXPECT().JobRestart(1).Return(nil)
m := cups.NewTestManager(mockClient, mockPk)
assert.NoError(t, m.RestartJob(1))
}
func TestManager_HoldJob_WithPkHelper(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().HoldJobUntil(1, "indefinite").Return(authErr())
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
mockPk := mocks_pkhelper.NewMockPkHelper(t)
mockPk.EXPECT().JobSetHoldUntil(1, "indefinite").Return(nil)
m := cups.NewTestManager(mockClient, mockPk)
assert.NoError(t, m.HoldJob(1, "indefinite"))
}

View File

@@ -137,114 +137,30 @@ func TestManager_GetJobs(t *testing.T) {
}
func TestManager_CancelJob(t *testing.T) {
tests := []struct {
name string
mockErr error
wantErr bool
}{
{
name: "success",
mockErr: nil,
wantErr: false,
},
{
name: "error",
mockErr: errors.New("test error"),
wantErr: true,
},
}
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().CancelJob(1, false).Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().CancelJob(1, false).Return(tt.mockErr)
m := &Manager{
client: mockClient,
}
err := m.CancelJob(1)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
m := NewTestManager(mockClient, nil)
assert.NoError(t, m.CancelJob(1))
}
func TestManager_PausePrinter(t *testing.T) {
tests := []struct {
name string
mockErr error
wantErr bool
}{
{
name: "success",
mockErr: nil,
wantErr: false,
},
{
name: "error",
mockErr: errors.New("test error"),
wantErr: true,
},
}
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().PausePrinter("printer1").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().PausePrinter("printer1").Return(tt.mockErr)
m := &Manager{
client: mockClient,
}
err := m.PausePrinter("printer1")
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
m := NewTestManager(mockClient, nil)
assert.NoError(t, m.PausePrinter("printer1"))
}
func TestManager_ResumePrinter(t *testing.T) {
tests := []struct {
name string
mockErr error
wantErr bool
}{
{
name: "success",
mockErr: nil,
wantErr: false,
},
{
name: "error",
mockErr: errors.New("test error"),
wantErr: true,
},
}
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().ResumePrinter("printer1").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().ResumePrinter("printer1").Return(tt.mockErr)
m := &Manager{
client: mockClient,
}
err := m.ResumePrinter("printer1")
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
m := NewTestManager(mockClient, nil)
assert.NoError(t, m.ResumePrinter("printer1"))
}
func TestManager_PurgeJobs(t *testing.T) {
@@ -269,11 +185,12 @@ func TestManager_PurgeJobs(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().CancelAllJob("printer1", true).Return(tt.mockErr)
m := &Manager{
client: mockClient,
if !tt.wantErr {
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
}
m := NewTestManager(mockClient, nil)
err := m.PurgeJobs("printer1")
if tt.wantErr {
assert.Error(t, err)
@@ -283,3 +200,251 @@ func TestManager_PurgeJobs(t *testing.T) {
})
}
}
func TestManager_GetDevices(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().GetDevices().Return(map[string]ipp.Attributes{
"usb://HP/LaserJet": {
"device-class": []ipp.Attribute{{Value: "direct"}},
"device-info": []ipp.Attribute{{Value: "HP LaserJet"}},
"device-make-and-model": []ipp.Attribute{{Value: "HP LaserJet 1020"}},
},
}, nil)
m := &Manager{client: mockClient}
got, err := m.GetDevices()
assert.NoError(t, err)
assert.Len(t, got, 1)
assert.Equal(t, "usb://HP/LaserJet", got[0].URI)
assert.Equal(t, "direct", got[0].Class)
}
func TestManager_GetPPDs(t *testing.T) {
tests := []struct {
name string
mockRet map[string]ipp.Attributes
mockErr error
want int
wantErr bool
}{
{
name: "success",
mockRet: map[string]ipp.Attributes{
"drv:///sample.drv/generic.ppd": {
"ppd-make-and-model": []ipp.Attribute{{Value: "Generic PostScript"}},
"ppd-type": []ipp.Attribute{{Value: "ppd"}},
},
},
mockErr: nil,
want: 1,
wantErr: false,
},
{
name: "error",
mockRet: nil,
mockErr: errors.New("test error"),
want: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().GetPPDs().Return(tt.mockRet, tt.mockErr)
m := &Manager{client: mockClient}
got, err := m.GetPPDs()
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want, len(got))
})
}
}
func TestManager_GetClasses(t *testing.T) {
tests := []struct {
name string
mockRet map[string]ipp.Attributes
mockErr error
want int
wantErr bool
}{
{
name: "success",
mockRet: map[string]ipp.Attributes{
"office": {
ipp.AttributePrinterName: []ipp.Attribute{{Value: "office"}},
ipp.AttributePrinterState: []ipp.Attribute{{Value: 3}},
ipp.AttributeMemberNames: []ipp.Attribute{{Value: "printer1"}, {Value: "printer2"}},
},
},
mockErr: nil,
want: 1,
wantErr: false,
},
{
name: "error",
mockRet: nil,
mockErr: errors.New("test error"),
want: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().GetClasses(mock.Anything).Return(tt.mockRet, tt.mockErr)
m := &Manager{client: mockClient}
got, err := m.GetClasses()
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want, len(got))
if len(got) > 0 {
assert.Equal(t, "office", got[0].Name)
assert.Equal(t, 2, len(got[0].Members))
}
})
}
}
func TestManager_CreatePrinter(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().CreatePrinter("newprinter", "usb://HP", "generic.ppd", true, "stop-printer", "info", "location").Return(nil)
mockClient.EXPECT().ResumePrinter("newprinter").Return(nil)
mockClient.EXPECT().AcceptJobs("newprinter").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
assert.NoError(t, m.CreatePrinter("newprinter", "usb://HP", "generic.ppd", true, "stop-printer", "info", "location"))
}
func TestManager_DeletePrinter(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().DeletePrinter("printer1").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
assert.NoError(t, m.DeletePrinter("printer1"))
}
func TestManager_AcceptJobs(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().AcceptJobs("printer1").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
assert.NoError(t, m.AcceptJobs("printer1"))
}
func TestManager_RejectJobs(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().RejectJobs("printer1").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
assert.NoError(t, m.RejectJobs("printer1"))
}
func TestManager_SetPrinterShared(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().SetPrinterIsShared("printer1", true).Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
assert.NoError(t, m.SetPrinterShared("printer1", true))
}
func TestManager_SetPrinterLocation(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().SetPrinterLocation("printer1", "Office").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
assert.NoError(t, m.SetPrinterLocation("printer1", "Office"))
}
func TestManager_SetPrinterInfo(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().SetPrinterInformation("printer1", "Main Printer").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
assert.NoError(t, m.SetPrinterInfo("printer1", "Main Printer"))
}
func TestManager_MoveJob(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().MoveJob(1, "printer2").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
err := m.MoveJob(1, "printer2")
assert.NoError(t, err)
}
func TestManager_PrintTestPage(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().PrintTestPage("printer1", mock.Anything, mock.Anything).Return(42, nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
jobID, err := m.PrintTestPage("printer1")
assert.NoError(t, err)
assert.Equal(t, 42, jobID)
}
func TestManager_AddPrinterToClass(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().AddPrinterToClass("office", "printer1").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
assert.NoError(t, m.AddPrinterToClass("office", "printer1"))
}
func TestManager_RemovePrinterFromClass(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().DeletePrinterFromClass("office", "printer1").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
assert.NoError(t, m.RemovePrinterFromClass("office", "printer1"))
}
func TestManager_DeleteClass(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().DeleteClass("office").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
assert.NoError(t, m.DeleteClass("office"))
}
func TestManager_RestartJob(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().RestartJob(1).Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
assert.NoError(t, m.RestartJob(1))
}
func TestManager_HoldJob(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().HoldJobUntil(1, "indefinite").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
assert.NoError(t, m.HoldJob(1, "indefinite"))
}

View File

@@ -40,6 +40,40 @@ func HandleRequest(conn net.Conn, req Request, manager *Manager) {
handleCancelJob(conn, req, manager)
case "cups.purgeJobs":
handlePurgeJobs(conn, req, manager)
case "cups.getDevices":
handleGetDevices(conn, req, manager)
case "cups.getPPDs":
handleGetPPDs(conn, req, manager)
case "cups.getClasses":
handleGetClasses(conn, req, manager)
case "cups.createPrinter":
handleCreatePrinter(conn, req, manager)
case "cups.deletePrinter":
handleDeletePrinter(conn, req, manager)
case "cups.acceptJobs":
handleAcceptJobs(conn, req, manager)
case "cups.rejectJobs":
handleRejectJobs(conn, req, manager)
case "cups.setPrinterShared":
handleSetPrinterShared(conn, req, manager)
case "cups.setPrinterLocation":
handleSetPrinterLocation(conn, req, manager)
case "cups.setPrinterInfo":
handleSetPrinterInfo(conn, req, manager)
case "cups.moveJob":
handleMoveJob(conn, req, manager)
case "cups.printTestPage":
handlePrintTestPage(conn, req, manager)
case "cups.addPrinterToClass":
handleAddPrinterToClass(conn, req, manager)
case "cups.removePrinterFromClass":
handleRemovePrinterFromClass(conn, req, manager)
case "cups.deleteClass":
handleDeleteClass(conn, req, manager)
case "cups.restartJob":
handleRestartJob(conn, req, manager)
case "cups.holdJob":
handleHoldJob(conn, req, manager)
default:
models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
}
@@ -158,3 +192,291 @@ func handleSubscribe(conn net.Conn, req Request, manager *Manager) {
}
}
}
func handleGetDevices(conn net.Conn, req Request, manager *Manager) {
devices, err := manager.GetDevices()
if err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, devices)
}
func handleGetPPDs(conn net.Conn, req Request, manager *Manager) {
ppds, err := manager.GetPPDs()
if err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, ppds)
}
func handleGetClasses(conn net.Conn, req Request, manager *Manager) {
classes, err := manager.GetClasses()
if err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, classes)
}
func handleCreatePrinter(conn net.Conn, req Request, manager *Manager) {
name, ok := req.Params["name"].(string)
if !ok || name == "" {
models.RespondError(conn, req.ID, "missing or invalid 'name' parameter")
return
}
deviceURI, ok := req.Params["deviceURI"].(string)
if !ok || deviceURI == "" {
models.RespondError(conn, req.ID, "missing or invalid 'deviceURI' parameter")
return
}
ppd, ok := req.Params["ppd"].(string)
if !ok || ppd == "" {
models.RespondError(conn, req.ID, "missing or invalid 'ppd' parameter")
return
}
shared, _ := req.Params["shared"].(bool)
errorPolicy, _ := req.Params["errorPolicy"].(string)
information, _ := req.Params["information"].(string)
location, _ := req.Params["location"].(string)
if err := manager.CreatePrinter(name, deviceURI, ppd, shared, errorPolicy, information, location); err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "printer created"})
}
func handleDeletePrinter(conn net.Conn, req Request, manager *Manager) {
printerName, ok := req.Params["printerName"].(string)
if !ok || printerName == "" {
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
return
}
if err := manager.DeletePrinter(printerName); err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "printer deleted"})
}
func handleAcceptJobs(conn net.Conn, req Request, manager *Manager) {
printerName, ok := req.Params["printerName"].(string)
if !ok || printerName == "" {
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
return
}
if err := manager.AcceptJobs(printerName); err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "accepting jobs"})
}
func handleRejectJobs(conn net.Conn, req Request, manager *Manager) {
printerName, ok := req.Params["printerName"].(string)
if !ok || printerName == "" {
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
return
}
if err := manager.RejectJobs(printerName); err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "rejecting jobs"})
}
func handleSetPrinterShared(conn net.Conn, req Request, manager *Manager) {
printerName, ok := req.Params["printerName"].(string)
if !ok || printerName == "" {
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
return
}
shared, ok := req.Params["shared"].(bool)
if !ok {
models.RespondError(conn, req.ID, "missing or invalid 'shared' parameter")
return
}
if err := manager.SetPrinterShared(printerName, shared); err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "sharing updated"})
}
func handleSetPrinterLocation(conn net.Conn, req Request, manager *Manager) {
printerName, ok := req.Params["printerName"].(string)
if !ok || printerName == "" {
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
return
}
location, ok := req.Params["location"].(string)
if !ok {
models.RespondError(conn, req.ID, "missing or invalid 'location' parameter")
return
}
if err := manager.SetPrinterLocation(printerName, location); err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "location updated"})
}
func handleSetPrinterInfo(conn net.Conn, req Request, manager *Manager) {
printerName, ok := req.Params["printerName"].(string)
if !ok || printerName == "" {
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
return
}
info, ok := req.Params["info"].(string)
if !ok {
models.RespondError(conn, req.ID, "missing or invalid 'info' parameter")
return
}
if err := manager.SetPrinterInfo(printerName, info); err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "info updated"})
}
func handleMoveJob(conn net.Conn, req Request, manager *Manager) {
jobIDFloat, ok := req.Params["jobID"].(float64)
if !ok {
models.RespondError(conn, req.ID, "missing or invalid 'jobID' parameter")
return
}
destPrinter, ok := req.Params["destPrinter"].(string)
if !ok || destPrinter == "" {
models.RespondError(conn, req.ID, "missing or invalid 'destPrinter' parameter")
return
}
if err := manager.MoveJob(int(jobIDFloat), destPrinter); err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "job moved"})
}
type TestPageResult struct {
Success bool `json:"success"`
JobID int `json:"jobId"`
Message string `json:"message"`
}
func handlePrintTestPage(conn net.Conn, req Request, manager *Manager) {
printerName, ok := req.Params["printerName"].(string)
if !ok || printerName == "" {
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
return
}
jobID, err := manager.PrintTestPage(printerName)
if err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, TestPageResult{Success: true, JobID: jobID, Message: "test page queued"})
}
func handleAddPrinterToClass(conn net.Conn, req Request, manager *Manager) {
className, ok := req.Params["className"].(string)
if !ok || className == "" {
models.RespondError(conn, req.ID, "missing or invalid 'className' parameter")
return
}
printerName, ok := req.Params["printerName"].(string)
if !ok || printerName == "" {
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
return
}
if err := manager.AddPrinterToClass(className, printerName); err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "printer added to class"})
}
func handleRemovePrinterFromClass(conn net.Conn, req Request, manager *Manager) {
className, ok := req.Params["className"].(string)
if !ok || className == "" {
models.RespondError(conn, req.ID, "missing or invalid 'className' parameter")
return
}
printerName, ok := req.Params["printerName"].(string)
if !ok || printerName == "" {
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
return
}
if err := manager.RemovePrinterFromClass(className, printerName); err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "printer removed from class"})
}
func handleDeleteClass(conn net.Conn, req Request, manager *Manager) {
className, ok := req.Params["className"].(string)
if !ok || className == "" {
models.RespondError(conn, req.ID, "missing or invalid 'className' parameter")
return
}
if err := manager.DeleteClass(className); err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "class deleted"})
}
func handleRestartJob(conn net.Conn, req Request, manager *Manager) {
jobIDFloat, ok := req.Params["jobID"].(float64)
if !ok {
models.RespondError(conn, req.ID, "missing or invalid 'jobID' parameter")
return
}
if err := manager.RestartJob(int(jobIDFloat)); err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "job restarted"})
}
func handleHoldJob(conn net.Conn, req Request, manager *Manager) {
jobIDFloat, ok := req.Params["jobID"].(float64)
if !ok {
models.RespondError(conn, req.ID, "missing or invalid 'jobID' parameter")
return
}
holdUntil, _ := req.Params["holdUntil"].(string)
if holdUntil == "" {
holdUntil = "indefinite"
}
if err := manager.HoldJob(int(jobIDFloat), holdUntil); err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "job held"})
}

View File

@@ -145,10 +145,9 @@ func TestHandleGetJobs_MissingParam(t *testing.T) {
func TestHandlePausePrinter(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().PausePrinter("printer1").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := &Manager{
client: mockClient,
}
m := NewTestManager(mockClient, nil)
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
@@ -173,10 +172,9 @@ func TestHandlePausePrinter(t *testing.T) {
func TestHandleResumePrinter(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().ResumePrinter("printer1").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := &Manager{
client: mockClient,
}
m := NewTestManager(mockClient, nil)
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
@@ -201,10 +199,9 @@ func TestHandleResumePrinter(t *testing.T) {
func TestHandleCancelJob(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().CancelJob(1, false).Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := &Manager{
client: mockClient,
}
m := NewTestManager(mockClient, nil)
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
@@ -229,10 +226,9 @@ func TestHandleCancelJob(t *testing.T) {
func TestHandlePurgeJobs(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().CancelAllJob("printer1", true).Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := &Manager{
client: mockClient,
}
m := NewTestManager(mockClient, nil)
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
@@ -277,3 +273,439 @@ func TestHandleRequest_UnknownMethod(t *testing.T) {
assert.Nil(t, resp.Result)
assert.NotNil(t, resp.Error)
}
func TestHandleGetDevices(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().GetDevices().Return(map[string]ipp.Attributes{
"usb://HP/LaserJet": {
"device-class": []ipp.Attribute{{Value: "direct"}},
"device-info": []ipp.Attribute{{Value: "HP LaserJet"}},
},
}, nil)
m := &Manager{client: mockClient}
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
req := Request{ID: 1, Method: "cups.getDevices"}
handleGetDevices(conn, req, m)
var resp models.Response[[]Device]
err := json.NewDecoder(buf).Decode(&resp)
assert.NoError(t, err)
assert.NotNil(t, resp.Result)
assert.Equal(t, 1, len(*resp.Result))
}
func TestHandleGetPPDs(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().GetPPDs().Return(map[string]ipp.Attributes{
"generic.ppd": {
"ppd-make-and-model": []ipp.Attribute{{Value: "Generic"}},
},
}, nil)
m := &Manager{client: mockClient}
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
req := Request{ID: 1, Method: "cups.getPPDs"}
handleGetPPDs(conn, req, m)
var resp models.Response[[]PPD]
err := json.NewDecoder(buf).Decode(&resp)
assert.NoError(t, err)
assert.NotNil(t, resp.Result)
assert.Equal(t, 1, len(*resp.Result))
}
func TestHandleGetClasses(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().GetClasses(mock.Anything).Return(map[string]ipp.Attributes{
"office": {
ipp.AttributePrinterName: []ipp.Attribute{{Value: "office"}},
ipp.AttributePrinterState: []ipp.Attribute{{Value: 3}},
},
}, nil)
m := &Manager{client: mockClient}
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
req := Request{ID: 1, Method: "cups.getClasses"}
handleGetClasses(conn, req, m)
var resp models.Response[[]PrinterClass]
err := json.NewDecoder(buf).Decode(&resp)
assert.NoError(t, err)
assert.NotNil(t, resp.Result)
assert.Equal(t, 1, len(*resp.Result))
}
func TestHandleCreatePrinter(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().CreatePrinter("newprinter", "usb://HP", "generic.ppd", false, "", "", "").Return(nil)
mockClient.EXPECT().ResumePrinter("newprinter").Return(nil)
mockClient.EXPECT().AcceptJobs("newprinter").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
req := Request{
ID: 1,
Method: "cups.createPrinter",
Params: map[string]interface{}{
"name": "newprinter",
"deviceURI": "usb://HP",
"ppd": "generic.ppd",
},
}
handleCreatePrinter(conn, req, m)
var resp models.Response[SuccessResult]
err := json.NewDecoder(buf).Decode(&resp)
assert.NoError(t, err)
assert.NotNil(t, resp.Result)
assert.True(t, resp.Result.Success)
}
func TestHandleCreatePrinter_MissingParams(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
m := &Manager{client: mockClient}
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
req := Request{ID: 1, Method: "cups.createPrinter", Params: map[string]interface{}{}}
handleCreatePrinter(conn, req, m)
var resp models.Response[interface{}]
err := json.NewDecoder(buf).Decode(&resp)
assert.NoError(t, err)
assert.Nil(t, resp.Result)
assert.NotNil(t, resp.Error)
}
func TestHandleDeletePrinter(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().DeletePrinter("printer1").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
req := Request{
ID: 1,
Method: "cups.deletePrinter",
Params: map[string]interface{}{"printerName": "printer1"},
}
handleDeletePrinter(conn, req, m)
var resp models.Response[SuccessResult]
err := json.NewDecoder(buf).Decode(&resp)
assert.NoError(t, err)
assert.NotNil(t, resp.Result)
assert.True(t, resp.Result.Success)
}
func TestHandleAcceptJobs(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().AcceptJobs("printer1").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
req := Request{
ID: 1,
Method: "cups.acceptJobs",
Params: map[string]interface{}{"printerName": "printer1"},
}
handleAcceptJobs(conn, req, m)
var resp models.Response[SuccessResult]
err := json.NewDecoder(buf).Decode(&resp)
assert.NoError(t, err)
assert.NotNil(t, resp.Result)
assert.True(t, resp.Result.Success)
}
func TestHandleRejectJobs(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().RejectJobs("printer1").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
req := Request{
ID: 1,
Method: "cups.rejectJobs",
Params: map[string]interface{}{"printerName": "printer1"},
}
handleRejectJobs(conn, req, m)
var resp models.Response[SuccessResult]
err := json.NewDecoder(buf).Decode(&resp)
assert.NoError(t, err)
assert.NotNil(t, resp.Result)
assert.True(t, resp.Result.Success)
}
func TestHandleSetPrinterShared(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().SetPrinterIsShared("printer1", true).Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
req := Request{
ID: 1,
Method: "cups.setPrinterShared",
Params: map[string]interface{}{"printerName": "printer1", "shared": true},
}
handleSetPrinterShared(conn, req, m)
var resp models.Response[SuccessResult]
err := json.NewDecoder(buf).Decode(&resp)
assert.NoError(t, err)
assert.NotNil(t, resp.Result)
assert.True(t, resp.Result.Success)
}
func TestHandleSetPrinterLocation(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().SetPrinterLocation("printer1", "Office").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
req := Request{
ID: 1,
Method: "cups.setPrinterLocation",
Params: map[string]interface{}{"printerName": "printer1", "location": "Office"},
}
handleSetPrinterLocation(conn, req, m)
var resp models.Response[SuccessResult]
err := json.NewDecoder(buf).Decode(&resp)
assert.NoError(t, err)
assert.NotNil(t, resp.Result)
assert.True(t, resp.Result.Success)
}
func TestHandleSetPrinterInfo(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().SetPrinterInformation("printer1", "Main Printer").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
req := Request{
ID: 1,
Method: "cups.setPrinterInfo",
Params: map[string]interface{}{"printerName": "printer1", "info": "Main Printer"},
}
handleSetPrinterInfo(conn, req, m)
var resp models.Response[SuccessResult]
err := json.NewDecoder(buf).Decode(&resp)
assert.NoError(t, err)
assert.NotNil(t, resp.Result)
assert.True(t, resp.Result.Success)
}
func TestHandleMoveJob(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().MoveJob(1, "printer2").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
req := Request{
ID: 1,
Method: "cups.moveJob",
Params: map[string]interface{}{"jobID": float64(1), "destPrinter": "printer2"},
}
handleMoveJob(conn, req, m)
var resp models.Response[SuccessResult]
err := json.NewDecoder(buf).Decode(&resp)
assert.NoError(t, err)
assert.NotNil(t, resp.Result)
assert.True(t, resp.Result.Success)
}
func TestHandlePrintTestPage(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().PrintTestPage("printer1", mock.Anything, mock.Anything).Return(42, nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
req := Request{
ID: 1,
Method: "cups.printTestPage",
Params: map[string]interface{}{"printerName": "printer1"},
}
handlePrintTestPage(conn, req, m)
var resp models.Response[TestPageResult]
err := json.NewDecoder(buf).Decode(&resp)
assert.NoError(t, err)
assert.NotNil(t, resp.Result)
assert.True(t, resp.Result.Success)
assert.Equal(t, 42, resp.Result.JobID)
}
func TestHandleAddPrinterToClass(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().AddPrinterToClass("office", "printer1").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
req := Request{
ID: 1,
Method: "cups.addPrinterToClass",
Params: map[string]interface{}{"className": "office", "printerName": "printer1"},
}
handleAddPrinterToClass(conn, req, m)
var resp models.Response[SuccessResult]
err := json.NewDecoder(buf).Decode(&resp)
assert.NoError(t, err)
assert.NotNil(t, resp.Result)
assert.True(t, resp.Result.Success)
}
func TestHandleRemovePrinterFromClass(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().DeletePrinterFromClass("office", "printer1").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
req := Request{
ID: 1,
Method: "cups.removePrinterFromClass",
Params: map[string]interface{}{"className": "office", "printerName": "printer1"},
}
handleRemovePrinterFromClass(conn, req, m)
var resp models.Response[SuccessResult]
err := json.NewDecoder(buf).Decode(&resp)
assert.NoError(t, err)
assert.NotNil(t, resp.Result)
assert.True(t, resp.Result.Success)
}
func TestHandleDeleteClass(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().DeleteClass("office").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
req := Request{
ID: 1,
Method: "cups.deleteClass",
Params: map[string]interface{}{"className": "office"},
}
handleDeleteClass(conn, req, m)
var resp models.Response[SuccessResult]
err := json.NewDecoder(buf).Decode(&resp)
assert.NoError(t, err)
assert.NotNil(t, resp.Result)
assert.True(t, resp.Result.Success)
}
func TestHandleRestartJob(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().RestartJob(1).Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
req := Request{
ID: 1,
Method: "cups.restartJob",
Params: map[string]interface{}{"jobID": float64(1)},
}
handleRestartJob(conn, req, m)
var resp models.Response[SuccessResult]
err := json.NewDecoder(buf).Decode(&resp)
assert.NoError(t, err)
assert.NotNil(t, resp.Result)
assert.True(t, resp.Result.Success)
}
func TestHandleHoldJob(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().HoldJobUntil(1, "indefinite").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
req := Request{
ID: 1,
Method: "cups.holdJob",
Params: map[string]interface{}{"jobID": float64(1)},
}
handleHoldJob(conn, req, m)
var resp models.Response[SuccessResult]
err := json.NewDecoder(buf).Decode(&resp)
assert.NoError(t, err)
assert.NotNil(t, resp.Result)
assert.True(t, resp.Result.Success)
}
func TestHandleHoldJob_WithHoldUntil(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
mockClient.EXPECT().HoldJobUntil(1, "no-hold").Return(nil)
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
m := NewTestManager(mockClient, nil)
buf := &bytes.Buffer{}
conn := &mockConn{Buffer: buf}
req := Request{
ID: 1,
Method: "cups.holdJob",
Params: map[string]interface{}{"jobID": float64(1), "holdUntil": "no-hold"},
}
handleHoldJob(conn, req, m)
var resp models.Response[SuccessResult]
err := json.NewDecoder(buf).Decode(&resp)
assert.NoError(t, err)
assert.NotNil(t, resp.Result)
assert.True(t, resp.Result.Success)
}

View File

@@ -1,6 +1,7 @@
package cups
import (
"errors"
"fmt"
"os"
"strconv"
@@ -31,11 +32,21 @@ func NewManager() (*Manager, error) {
client := ipp.NewCUPSClient(host, port, username, password, false)
baseURL := fmt.Sprintf("http://%s:%d", host, port)
var pkHelper PkHelper
if isLocalCUPS(host) {
var err error
pkHelper, err = NewPkHelper()
if err != nil {
log.Warnf("[CUPS] Failed to initialize pkhelper: %v", err)
}
}
m := &Manager{
state: &CUPSState{
Printers: make(map[string]*Printer),
},
client: client,
pkHelper: pkHelper,
baseURL: baseURL,
stateMutex: sync.RWMutex{},
stopChan: make(chan struct{}),
@@ -98,6 +109,12 @@ func (m *Manager) eventHandler() {
func (m *Manager) updateState() error {
printers, err := m.GetPrinters()
if err != nil {
if isNoPrintersError(err) {
m.stateMutex.Lock()
m.state.Printers = make(map[string]*Printer)
m.stateMutex.Unlock()
return nil
}
return err
}
@@ -119,6 +136,19 @@ func (m *Manager) updateState() error {
return nil
}
func isNoPrintersError(err error) bool {
if err == nil {
return false
}
var ippErr ipp.IPPError
if errors.As(err, &ippErr) {
return ippErr.Status == 1030
}
return false
}
func (m *Manager) notifier() {
defer m.notifierWg.Done()
const minGap = 100 * time.Millisecond
@@ -170,6 +200,14 @@ func (m *Manager) notifySubscribers() {
}
}
func (m *Manager) RefreshState() {
if err := m.updateState(); err != nil {
log.Warnf("[CUPS] Failed to refresh state: %v", err)
return
}
m.notifySubscribers()
}
func (m *Manager) GetState() CUPSState {
return m.snapshotState()
}
@@ -256,6 +294,7 @@ func stateChanged(old, new *CUPSState) bool {
}
if oldPrinter.State != newPrinter.State ||
oldPrinter.StateReason != newPrinter.StateReason ||
oldPrinter.Accepting != newPrinter.Accepting ||
len(oldPrinter.Jobs) != len(newPrinter.Jobs) {
return true
}
@@ -334,3 +373,18 @@ func getBoolAttr(attrs ipp.Attributes, key string) bool {
}
return false
}
func getStringSliceAttr(attrs ipp.Attributes, key string) []string {
attr, ok := attrs[key]
if !ok {
return nil
}
result := make([]string, 0, len(attr))
for _, a := range attr {
if val, ok := a.Value.(string); ok {
result = append(result, val)
}
}
return result
}

View File

@@ -0,0 +1,184 @@
package cups
import (
"fmt"
"strings"
"github.com/godbus/dbus/v5"
)
const (
pkHelperDest = "org.opensuse.CupsPkHelper.Mechanism"
pkHelperPath = "/"
pkHelperInterface = "org.opensuse.CupsPkHelper.Mechanism"
)
type PkHelper interface {
DevicesGet(timeout, limit int, includeSchemes, excludeSchemes []string) ([]Device, error)
PrinterAdd(name, uri, ppd, info, location string) error
PrinterDelete(name string) error
PrinterSetEnabled(name string, enabled bool) error
PrinterSetAcceptJobs(name string, enabled bool, reason string) error
PrinterSetInfo(name, info string) error
PrinterSetLocation(name, location string) error
PrinterSetShared(name string, shared bool) error
ClassAddPrinter(className, printerName string) error
ClassDeletePrinter(className, printerName string) error
ClassDelete(className string) error
JobCancelPurge(jobID int, purge bool) error
JobRestart(jobID int) error
JobSetHoldUntil(jobID int, holdUntil string) error
}
type DBusPkHelper struct {
conn *dbus.Conn
obj dbus.BusObject
}
func NewPkHelper() (*DBusPkHelper, error) {
conn, err := dbus.SystemBus()
if err != nil {
return nil, fmt.Errorf("failed to connect to system bus: %w", err)
}
return &DBusPkHelper{
conn: conn,
obj: conn.Object(pkHelperDest, pkHelperPath),
}, nil
}
func (p *DBusPkHelper) DevicesGet(timeout, limit int, includeSchemes, excludeSchemes []string) ([]Device, error) {
if includeSchemes == nil {
includeSchemes = []string{}
}
if excludeSchemes == nil {
excludeSchemes = []string{}
}
var errStr string
var devicesMap map[string]string
call := p.obj.Call(pkHelperInterface+".DevicesGet", 0, int32(timeout), int32(limit), includeSchemes, excludeSchemes)
if call.Err != nil {
return nil, call.Err
}
if err := call.Store(&errStr, &devicesMap); err != nil {
return nil, err
}
if errStr != "" {
return nil, fmt.Errorf("%s", errStr)
}
return parseDevicesMap(devicesMap), nil
}
func parseDevicesMap(devicesMap map[string]string) []Device {
devicesByIndex := make(map[string]*Device)
for key, value := range devicesMap {
idx := strings.LastIndex(key, ":")
if idx == -1 {
continue
}
attr := key[:idx]
index := key[idx+1:]
dev, ok := devicesByIndex[index]
if !ok {
dev = &Device{}
devicesByIndex[index] = dev
}
switch attr {
case "device-uri":
dev.URI = value
case "device-class":
dev.Class = value
case "device-info":
dev.Info = value
case "device-make-and-model":
dev.MakeModel = value
case "device-id":
dev.ID = value
case "device-location":
dev.Location = value
}
}
devices := make([]Device, 0, len(devicesByIndex))
for _, dev := range devicesByIndex {
if dev.URI != "" {
devices = append(devices, *dev)
}
}
return devices
}
func (p *DBusPkHelper) PrinterAdd(name, uri, ppd, info, location string) error {
return p.callSimple("PrinterAdd", name, uri, ppd, info, location)
}
func (p *DBusPkHelper) PrinterDelete(name string) error {
return p.callSimple("PrinterDelete", name)
}
func (p *DBusPkHelper) PrinterSetEnabled(name string, enabled bool) error {
return p.callSimple("PrinterSetEnabled", name, enabled)
}
func (p *DBusPkHelper) PrinterSetAcceptJobs(name string, enabled bool, reason string) error {
return p.callSimple("PrinterSetAcceptJobs", name, enabled, reason)
}
func (p *DBusPkHelper) PrinterSetInfo(name, info string) error {
return p.callSimple("PrinterSetInfo", name, info)
}
func (p *DBusPkHelper) PrinterSetLocation(name, location string) error {
return p.callSimple("PrinterSetLocation", name, location)
}
func (p *DBusPkHelper) PrinterSetShared(name string, shared bool) error {
return p.callSimple("PrinterSetShared", name, shared)
}
func (p *DBusPkHelper) ClassAddPrinter(className, printerName string) error {
return p.callSimple("ClassAddPrinter", className, printerName)
}
func (p *DBusPkHelper) ClassDeletePrinter(className, printerName string) error {
return p.callSimple("ClassDeletePrinter", className, printerName)
}
func (p *DBusPkHelper) ClassDelete(className string) error {
return p.callSimple("ClassDelete", className)
}
func (p *DBusPkHelper) JobCancelPurge(jobID int, purge bool) error {
return p.callSimple("JobCancelPurge", int32(jobID), purge)
}
func (p *DBusPkHelper) JobRestart(jobID int) error {
return p.callSimple("JobRestart", int32(jobID))
}
func (p *DBusPkHelper) JobSetHoldUntil(jobID int, holdUntil string) error {
return p.callSimple("JobSetHoldUntil", int32(jobID), holdUntil)
}
func (p *DBusPkHelper) callSimple(method string, args ...interface{}) error {
var errStr string
call := p.obj.Call(pkHelperInterface+"."+method, 0, args...)
if call.Err != nil {
return call.Err
}
if err := call.Store(&errStr); err != nil {
return err
}
if errStr != "" {
return fmt.Errorf("%s", errStr)
}
return nil
}

View File

@@ -0,0 +1,95 @@
package cups
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseDevicesMap(t *testing.T) {
tests := []struct {
name string
input map[string]string
wantLen int
wantURIs []string
}{
{
name: "empty",
input: map[string]string{},
wantLen: 0,
wantURIs: nil,
},
{
name: "single_device",
input: map[string]string{
"device-uri:0": "usb://HP/LaserJet",
"device-class:0": "direct",
"device-info:0": "HP LaserJet",
"device-make-and-model:0": "HP LaserJet 1020",
"device-id:0": "MFG:HP;MDL:LaserJet",
},
wantLen: 1,
wantURIs: []string{"usb://HP/LaserJet"},
},
{
name: "multiple_devices",
input: map[string]string{
"device-uri:0": "usb://HP/LaserJet",
"device-class:0": "direct",
"device-info:0": "HP LaserJet",
"device-uri:1": "socket://192.168.1.100",
"device-class:1": "network",
"device-info:1": "Network Printer",
},
wantLen: 2,
wantURIs: []string{"usb://HP/LaserJet", "socket://192.168.1.100"},
},
{
name: "malformed_key",
input: map[string]string{
"no-colon-here": "value",
},
wantLen: 0,
wantURIs: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := parseDevicesMap(tt.input)
assert.Len(t, got, tt.wantLen)
if tt.wantURIs != nil {
gotURIs := make(map[string]bool)
for _, d := range got {
gotURIs[d.URI] = true
}
for _, uri := range tt.wantURIs {
assert.True(t, gotURIs[uri], "expected URI %s not found", uri)
}
}
})
}
}
func TestParseDevicesMap_Attributes(t *testing.T) {
input := map[string]string{
"device-uri:0": "usb://HP/LaserJet",
"device-class:0": "direct",
"device-info:0": "HP LaserJet",
"device-make-and-model:0": "HP LaserJet 1020",
"device-id:0": "MFG:HP;MDL:LaserJet",
"device-location:0": "USB Port",
}
got := parseDevicesMap(input)
assert.Len(t, got, 1)
dev := got[0]
assert.Equal(t, "usb://HP/LaserJet", dev.URI)
assert.Equal(t, "direct", dev.Class)
assert.Equal(t, "HP LaserJet", dev.Info)
assert.Equal(t, "HP LaserJet 1020", dev.MakeModel)
assert.Equal(t, "MFG:HP;MDL:LaserJet", dev.ID)
assert.Equal(t, "USB Port", dev.Location)
}

View File

@@ -0,0 +1,13 @@
package cups
func NewTestManager(client CUPSClientInterface, pkHelper PkHelper) *Manager {
return &Manager{
client: client,
pkHelper: pkHelper,
state: &CUPSState{
Printers: make(map[string]*Printer),
},
stopChan: make(chan struct{}),
dirty: make(chan struct{}, 1),
}
}

View File

@@ -35,9 +35,38 @@ type Job struct {
TimeCreated time.Time `json:"timeCreated"`
}
type Device struct {
URI string `json:"uri"`
Class string `json:"class"`
Info string `json:"info"`
MakeModel string `json:"makeModel"`
ID string `json:"id"`
Location string `json:"location"`
}
type PPD struct {
Name string `json:"name"`
NaturalLanguage string `json:"naturalLanguage"`
MakeModel string `json:"makeModel"`
DeviceID string `json:"deviceId"`
Product string `json:"product"`
PSVersion string `json:"psVersion"`
Type string `json:"type"`
}
type PrinterClass struct {
Name string `json:"name"`
URI string `json:"uri"`
State string `json:"state"`
Members []string `json:"members"`
Location string `json:"location"`
Info string `json:"info"`
}
type Manager struct {
state *CUPSState
client CUPSClientInterface
pkHelper PkHelper
subscription SubscriptionManagerInterface
stateMutex sync.RWMutex
subscribers syncmap.Map[string, chan CUPSState]
@@ -63,6 +92,24 @@ type CUPSClientInterface interface {
ResumePrinter(printer string) error
CancelAllJob(printer string, purge bool) error
SendRequest(url string, req *ipp.Request, additionalResponseData io.Writer) (*ipp.Response, error)
GetDevices() (map[string]ipp.Attributes, error)
GetPPDs() (map[string]ipp.Attributes, error)
GetClasses(attributes []string) (map[string]ipp.Attributes, error)
CreatePrinter(name, deviceURI, ppd string, shared bool, errorPolicy, information, location string) error
DeletePrinter(printer string) error
AcceptJobs(printer string) error
RejectJobs(printer string) error
SetPrinterIsShared(printer string, shared bool) error
SetPrinterLocation(printer, location string) error
SetPrinterInformation(printer, information string) error
MoveJob(jobID int, destPrinter string) error
PrintTestPage(printer string, testPageData io.Reader, size int) (int, error)
AddPrinterToClass(class, printer string) error
DeletePrinterFromClass(class, printer string) error
DeleteClass(class string) error
RestartJob(jobID int) error
HoldJobUntil(jobID int, holdUntil string) error
}
type SubscriptionEvent struct {