From a50a3b716718b4736a765b1eab0431237be79771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20L=C3=B3pez?= Date: Sat, 28 Oct 2023 20:52:59 +0200 Subject: [PATCH] Separate method open() and constructor, enhance consistency between connectors: Rework printer tests (#587) * Add fixtures * Add test_printer_file.py * Remove old broken printer tests * Include a close_on_reopen test * Add test_printer_network.py * Add test_printer_serial.py * Add test_printer_usb.py * Add test_printer_lp.py * Add test_printer_cups.py * Add test_printer_win32raw.py * Test the 'printers' property * Fix conftest import formatting * Fix failing LP tests * Cancel close only if literal False|None _device * Fix win32raw failing tests (maybe) * Include win32raw close_on_reopen test * Include test _raw methods to win32raw * Replace general exceptions in win32raw * Replace wrong exception in cups * Include more tests to cups * Extend cups tests --- src/escpos/printer/cups.py | 4 +- src/escpos/printer/win32raw.py | 6 +- test/conftest.py | 43 ++++- test/test_printer_file.py | 71 -------- test/test_printer_network.py | 26 --- test/test_printers/test_printer_cups.py | 176 +++++++++++++++++++ test/test_printers/test_printer_file.py | 147 ++++++++++++++++ test/test_printers/test_printer_lp.py | 173 +++++++++++++++++++ test/test_printers/test_printer_network.py | 96 +++++++++++ test/test_printers/test_printer_serial.py | 96 +++++++++++ test/test_printers/test_printer_usb.py | 106 ++++++++++++ test/test_printers/test_printer_win32raw.py | 177 ++++++++++++++++++++ 12 files changed, 1018 insertions(+), 103 deletions(-) delete mode 100644 test/test_printer_file.py delete mode 100644 test/test_printer_network.py create mode 100644 test/test_printers/test_printer_cups.py create mode 100644 test/test_printers/test_printer_file.py create mode 100644 test/test_printers/test_printer_lp.py create mode 100644 test/test_printers/test_printer_network.py create mode 100644 test/test_printers/test_printer_serial.py create mode 100644 test/test_printers/test_printer_usb.py create mode 100644 test/test_printers/test_printer_win32raw.py diff --git a/src/escpos/printer/cups.py b/src/escpos/printer/cups.py index 0625b01..b4d430e 100644 --- a/src/escpos/printer/cups.py +++ b/src/escpos/printer/cups.py @@ -172,9 +172,9 @@ class CupsPrinter(Escpos): self.pending_job = True try: self.tmpfile.write(msg) - except ValueError: + except TypeError: self.pending_job = False - raise ValueError("Printer job not opened") + raise TypeError("Bytes required. Printer job not opened") def send(self): """Send the print job to the printer.""" diff --git a/src/escpos/printer/win32raw.py b/src/escpos/printer/win32raw.py index 396c3e9..bb04a42 100644 --- a/src/escpos/printer/win32raw.py +++ b/src/escpos/printer/win32raw.py @@ -138,7 +138,7 @@ class Win32Raw(Escpos): def close(self) -> None: """Close connection to default printer.""" - if not self._device: + if self._device is False or self._device is None: # Literal False | None return logging.info("Closing Win32Raw connection to printer %s", self.printer_name) win32print.EndPagePrinter(self._device) @@ -153,7 +153,7 @@ class Win32Raw(Escpos): :type msg: bytes """ if self.printer_name is None: - raise Exception("Printer not found") + raise DeviceNotFoundError("Printer not found") if not self.device: - raise Exception("Printer job not opened") + raise DeviceNotFoundError("Printer job not opened") win32print.WritePrinter(self.device, msg) diff --git a/test/conftest.py b/test/conftest.py index c7db0e7..61d42ca 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,8 +1,49 @@ import pytest -from escpos.printer import Dummy +from escpos.exceptions import DeviceNotFoundError +from escpos.printer import LP, CupsPrinter, Dummy, File, Network, Serial, Usb, Win32Raw @pytest.fixture def driver(): return Dummy() + + +@pytest.fixture +def usbprinter(): + return Usb() + + +@pytest.fixture +def serialprinter(): + return Serial() + + +@pytest.fixture +def networkprinter(): + return Network() + + +@pytest.fixture +def fileprinter(): + return File() + + +@pytest.fixture +def lpprinter(): + return LP() + + +@pytest.fixture +def win32rawprinter(): + return Win32Raw() + + +@pytest.fixture +def cupsprinter(): + return CupsPrinter() + + +@pytest.fixture +def devicenotfounderror(): + return DeviceNotFoundError diff --git a/test/test_printer_file.py b/test/test_printer_file.py deleted file mode 100644 index dcda627..0000000 --- a/test/test_printer_file.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -"""tests for the File printer - -:author: `Patrick Kanzler `_ -:organization: `python-escpos `_ -:copyright: Copyright (c) 2016 `python-escpos `_ -:license: MIT -""" - - -import pytest -import six -from hypothesis import given, settings -from hypothesis.strategies import text - -import escpos.printer as printer - -if six.PY3: - mock_open_call = "builtins.open" -else: - mock_open_call = "__builtin__.open" - - -@pytest.mark.skip("this test is broken and has to be fixed or discarded") -@given(path=text()) -def test_load_file_printer(mocker, path): - """test the loading of the file-printer""" - mock_escpos = mocker.patch("escpos.escpos.Escpos.__init__") - mock_open = mocker.patch(mock_open_call) - printer.File(devfile=path) - assert mock_escpos.called - mock_open.assert_called_with(path, "wb") - - -@pytest.mark.skip("this test is broken and has to be fixed or discarded") -@given(txt=text()) -def test_auto_flush(mocker, txt): - """test auto_flush in file-printer""" - mock_escpos = mocker.patch("escpos.escpos.Escpos.__init__") - mock_open = mocker.patch(mock_open_call) - mock_device = mocker.patch.object(printer.File, "device") - - p = printer.File(auto_flush=False) - # inject the mocked device-object - p.device = mock_device - p._raw(txt) - assert not mock_device.flush.called - mock_device.reset_mock() - p = printer.File(auto_flush=True) - # inject the mocked device-object - p.device = mock_device - p._raw(txt) - assert mock_device.flush.called - - -@pytest.mark.skip("this test is broken and has to be fixed or discarded") -@given(txt=text()) -def test_flush_on_close(mocker, txt): - """test flush on close in file-printer""" - mock_open = mocker.patch(mock_open_call) - mock_device = mocker.patch.object(printer.File, "device") - - p = printer.File(auto_flush=False) - # inject the mocked device-object - p.device = mock_device - p._raw(txt) - assert not mock_device.flush.called - p.close() - assert mock_device.flush.called - assert mock_device.close.called diff --git a/test/test_printer_network.py b/test/test_printer_network.py deleted file mode 100644 index 162d5f6..0000000 --- a/test/test_printer_network.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/python - -import socket - -import mock -import pytest - -import escpos.printer as printer - - -@pytest.fixture -def instance(): - socket.socket.connect = mock.Mock() - return printer.Network("localhost") - - -def test_close_without_open(instance): - """try to close without opening (should fail gracefully) - - Currently we never open from our fixture, so calling close once - should be enough. In the future this might not be enough, - therefore we have to close twice in order to provoke an error - (if possible, this should not raise) - """ - instance.close() - instance.close() diff --git a/test/test_printers/test_printer_cups.py b/test/test_printers/test_printer_cups.py new file mode 100644 index 0000000..e39b851 --- /dev/null +++ b/test/test_printers/test_printer_cups.py @@ -0,0 +1,176 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""tests for the Cups printer + +:author: Benito López and the python-escpos developers +:organization: `python-escpos `_ +:copyright: Copyright (c) 2023 `python-escpos `_ +:license: MIT +""" + +import logging +import sys + +import pytest + +# skip all the tests if the platform is Windows +pytestmark = pytest.mark.skipif( + sys.platform == "win32", reason="skipping non Windows platform specific tests" +) + + +def test_device_not_initialized(cupsprinter): + """ + GIVEN a cups printer object + WHEN it is not initialized + THEN check the device property is False + """ + assert cupsprinter._device is False + + +def test_open_raise_exception(cupsprinter, devicenotfounderror): + """ + GIVEN a cups printer object + WHEN open() is set to raise a DeviceNotFoundError on error + THEN check the exception is raised + """ + cupsprinter.host = "fakehost" + + with pytest.raises(devicenotfounderror): + cupsprinter.open(raise_not_found=True) + + +def test_open_not_raise_exception(cupsprinter, caplog): + """ + GIVEN a cups printer object + WHEN open() is set to not raise on error but simply cancel + THEN check the error is logged and open() canceled + """ + cupsprinter.host = "fakehost" + + with caplog.at_level(logging.ERROR): + cupsprinter.open(raise_not_found=False) + + assert "not available" in caplog.text + assert cupsprinter.device is None + + +def test_open(cupsprinter, caplog, mocker): + """ + GIVEN a cups printer object and a mocked pycups device + WHEN a valid connection to a device is opened + THEN check the success is logged and the device property is set + """ + mocker.patch("cups.Connection") + mocker.patch("escpos.printer.CupsPrinter.printers", new={"test_printer": "Test"}) + + cupsprinter.printer_name = "test_printer" + assert cupsprinter.printer_name in cupsprinter.printers + + with caplog.at_level(logging.INFO): + cupsprinter.open() + + assert "enabled" in caplog.text + assert cupsprinter.device + + +def test_close_on_reopen(cupsprinter, mocker): + """ + GIVEN a cups printer object and a mocked connection + WHEN a valid connection to a device is reopened before close + THEN check the close method is called if _device + """ + spy = mocker.spy(cupsprinter, "close") + mocker.patch("cups.Connection") + mocker.patch("escpos.printer.CupsPrinter.printers", new={"test_printer": "Test"}) + + cupsprinter.printer_name = "test_printer" + + cupsprinter.open() + assert cupsprinter._device + + cupsprinter.open() + spy.assert_called_once() + + +def test_close(cupsprinter, caplog, mocker): + """ + GIVEN a cups printer object and a mocked pycups device + WHEN a connection is opened and closed + THEN check the closing is logged and the device property is False + """ + mocker.patch("cups.Connection") + mocker.patch("escpos.printer.CupsPrinter.printers", new={"test_printer": "Test"}) + + cupsprinter.printer_name = "test_printer" + cupsprinter.open() + + with caplog.at_level(logging.INFO): + cupsprinter.close() + + assert "Closing" in caplog.text + assert cupsprinter._device is False + + +def test_send_on_close(cupsprinter, mocker): + """ + GIVEN a cups printer object and a mocked pycups device + WHEN closing connection before send the buffer + THEN check the buffer is sent and cleared + """ + mocked_cups = mocker.patch("cups.Connection") + + spy_send = mocker.spy(cupsprinter, "send") + spy_clear = mocker.spy(cupsprinter, "_clear") + + cupsprinter._device = mocked_cups + cupsprinter.pending_job = True + + cupsprinter.close() + + spy_send.assert_called_once() + spy_clear.assert_called_once() + assert cupsprinter.pending_job is False + + +def test_raw_raise_exception(cupsprinter): + """ + GIVEN a cups printer object + WHEN passing a non byte string to _raw() + THEN check an exception is raised and pending_job is False + """ + with pytest.raises(TypeError): + cupsprinter._raw("Non bytes") + + assert cupsprinter.pending_job is False + + +def test_raw(cupsprinter): + """ + GIVEN a cups printer object + WHEN passing a byte string to _raw() + THEN check the buffer content + """ + cupsprinter._raw(b"Test") + cupsprinter.tmpfile.seek(0) + assert cupsprinter.tmpfile.read() == b"Test" + + +def test_printers_no_device(cupsprinter): + """ + GIVEN a cups printer object + WHEN device is None + THEN check the return value is {} + """ + cupsprinter.device = None + assert cupsprinter.printers == {} + + +def test_read_no_device(cupsprinter): + """ + GIVEN a cups printer object + WHEN device is None + THEN check the return value is [] + """ + cupsprinter.device = None + assert cupsprinter._read() == [] diff --git a/test/test_printers/test_printer_file.py b/test/test_printers/test_printer_file.py new file mode 100644 index 0000000..3558231 --- /dev/null +++ b/test/test_printers/test_printer_file.py @@ -0,0 +1,147 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""tests for the File printer + +:author: `Patrick Kanzler `_ and the python-escpos developers +:organization: `python-escpos `_ +:copyright: Copyright (c) 2016-2023 `python-escpos `_ +:license: MIT +""" + +import logging + +import pytest + + +def test_device_not_initialized(fileprinter): + """ + GIVEN a file printer object + WHEN it is not initialized + THEN check the device property is False + """ + assert fileprinter._device is False + + +def test_open_raise_exception(fileprinter, devicenotfounderror): + """ + GIVEN a file printer object + WHEN open() is set to raise a DeviceNotFoundError on error + THEN check the exception is raised + """ + fileprinter.devfile = "fake/device" + + with pytest.raises(devicenotfounderror): + fileprinter.open(raise_not_found=True) + + +def test_open_not_raise_exception(fileprinter, caplog): + """ + GIVEN a file printer object + WHEN open() is set to not raise on error but simply cancel + THEN check the error is logged and open() canceled + """ + fileprinter.devfile = "fake/device" + + with caplog.at_level(logging.ERROR): + fileprinter.open(raise_not_found=False) + + assert "not found" in caplog.text + assert fileprinter.device is None + + +def test_open(fileprinter, caplog, mocker): + """ + GIVEN a file printer object and a mocked connection + WHEN a valid connection to a device is opened + THEN check the success is logged and the device property is set + """ + mocker.patch("builtins.open") + + with caplog.at_level(logging.INFO): + fileprinter.open() + + assert "enabled" in caplog.text + assert fileprinter.device + + +def test_close_on_reopen(fileprinter, mocker): + """ + GIVEN a file printer object and a mocked connection + WHEN a valid connection to a device is reopened before close + THEN check the close method is called if _device + """ + mocker.patch("builtins.open") + spy = mocker.spy(fileprinter, "close") + + fileprinter.open() + assert fileprinter._device + + fileprinter.open() + spy.assert_called_once_with() + + +def test_flush(fileprinter, mocker): + """ + GIVEN a file printer object and a mocked connection + WHEN auto_flush is disabled and flush() issued manually + THEN check the flush method is called only one time. + """ + spy = mocker.spy(fileprinter, "flush") + mocker.patch("builtins.open") + + fileprinter.auto_flush = False + fileprinter.open() + fileprinter.textln("python-escpos") + fileprinter.flush() + + assert spy.call_count == 1 + + +def test_auto_flush_on_command(fileprinter, mocker): + """ + GIVEN a file printer object and a mocked connection + WHEN auto_flush is enabled and flush() not issued manually + THEN check the flush method is called automatically + """ + spy = mocker.spy(fileprinter, "flush") + mocker.patch("builtins.open") + + fileprinter.auto_flush = True + fileprinter.open() + fileprinter.textln("python-escpos") + fileprinter.textln("test") + + assert spy.call_count > 1 + + +def test_auto_flush_on_close(fileprinter, mocker, caplog, capsys): + """ + GIVEN a file printer object and a mocked connection + WHEN auto_flush is disabled and flush() not issued manually + THEN check the flush method is called automatically on close + """ + spy = mocker.spy(fileprinter, "flush") + mocker.patch("builtins.open") + + fileprinter.auto_flush = False + fileprinter.open() + fileprinter.textln("python-escpos") + fileprinter.close() + + assert spy.call_count == 1 + + +def test_close(fileprinter, caplog, mocker): + """ + GIVEN a file printer object and a mocked connection + WHEN a connection is opened and closed + THEN check the closing is logged and the device property is False + """ + mocker.patch("builtins.open") + fileprinter.open() + + with caplog.at_level(logging.INFO): + fileprinter.close() + + assert "Closing" in caplog.text + assert fileprinter._device is False diff --git a/test/test_printers/test_printer_lp.py b/test/test_printers/test_printer_lp.py new file mode 100644 index 0000000..fac0032 --- /dev/null +++ b/test/test_printers/test_printer_lp.py @@ -0,0 +1,173 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""tests for the LP printer + +:author: Benito López and the python-escpos developers +:organization: `python-escpos `_ +:copyright: Copyright (c) 2023 `python-escpos `_ +:license: MIT +""" + +import logging +import sys + +import pytest + +# skip all the tests if the platform is Windows +pytestmark = pytest.mark.skipif( + sys.platform == "win32", reason="skipping non Windows platform specific tests" +) + + +def test_device_not_initialized(lpprinter): + """ + GIVEN a lp printer object + WHEN it is not initialized + THEN check the device property is False + """ + assert lpprinter._device is False + + +def test_open_raise_exception(lpprinter, devicenotfounderror, mocker): + """ + GIVEN a lp printer object + WHEN open() is set to raise a DeviceNotFoundError on error + THEN check the exception is raised + """ + mocker.patch("escpos.printer.LP.printers", new={"test_printer": "Test"}) + + lpprinter.printer_name = "fakeprinter" + + with pytest.raises(devicenotfounderror): + lpprinter.open(raise_not_found=True) + + +def test_open_not_raise_exception(lpprinter, caplog, mocker): + """ + GIVEN a lp printer object + WHEN open() is set to not raise on error but simply cancel + THEN check the error is logged and open() canceled + """ + mocker.patch("escpos.printer.LP.printers", new={"test_printer": "Test"}) + + lpprinter.printer_name = "fakeprinter" + + with caplog.at_level(logging.ERROR): + lpprinter.open(raise_not_found=False) + + assert "not available" in caplog.text + assert lpprinter.device is None + + +def test_open(lpprinter, caplog, mocker): + """ + GIVEN a lp printer object and a mocked connection + WHEN a valid connection to a device is opened + THEN check the success is logged and the device property is set + """ + mocker.patch("subprocess.Popen") + mocker.patch("escpos.printer.LP.printers", new={"test_printer": "Test"}) + + lpprinter.printer_name = "test_printer" + assert lpprinter.printer_name in lpprinter.printers + + with caplog.at_level(logging.INFO): + lpprinter.open() + + assert "enabled" in caplog.text + assert lpprinter.device + + +def test_close_on_reopen(lpprinter, mocker): + """ + GIVEN a lp printer object and a mocked connection + WHEN a valid connection to a device is reopened before close + THEN check the close method is called if _device + """ + spy = mocker.spy(lpprinter, "close") + mocker.patch("subprocess.Popen") + mocker.patch("escpos.printer.LP.printers", new={"test_printer": "Test"}) + + lpprinter.printer_name = "test_printer" + + lpprinter.open() + assert lpprinter._device + + lpprinter.open() + spy.assert_called_once_with() + + +def test_flush(lpprinter, mocker): + """ + GIVEN a lp printer object and a mocked connection + WHEN auto_flush is disabled and flush() issued manually + THEN check the flush method is called only one time. + """ + spy = mocker.spy(lpprinter, "flush") + mocker.patch("subprocess.Popen") + mocker.patch("escpos.printer.LP.printers", new={"test_printer": "Test"}) + + lpprinter.printer_name = "test_printer" + lpprinter.auto_flush = False + lpprinter.open() + lpprinter.textln("python-escpos") + lpprinter.flush() + + assert spy.call_count == 1 + + +def test_auto_flush_on_command(lpprinter, mocker): + """ + GIVEN a lp printer object and a mocked connection + WHEN auto_flush is enabled and flush() not issued manually + THEN check the flush method is called automatically + """ + spy = mocker.spy(lpprinter, "flush") + mocker.patch("subprocess.Popen") + mocker.patch("escpos.printer.LP.printers", new={"test_printer": "Test"}) + + lpprinter.printer_name = "test_printer" + lpprinter.auto_flush = True + lpprinter.open() + lpprinter.textln("python-escpos") + lpprinter.textln("test") + + assert spy.call_count > 1 + + +def test_auto_flush_on_close(lpprinter, mocker, caplog, capsys): + """ + GIVEN a lp printer object and a mocked connection + WHEN auto_flush is disabled and flush() not issued manually + THEN check the flush method is called automatically on close + """ + spy = mocker.spy(lpprinter, "flush") + mocker.patch("subprocess.Popen") + mocker.patch("escpos.printer.LP.printers", new={"test_printer": "Test"}) + + lpprinter.printer_name = "test_printer" + lpprinter.auto_flush = False + lpprinter.open() + lpprinter.textln("python-escpos") + lpprinter.close() + + assert spy.call_count == 1 + + +def test_close(lpprinter, caplog, mocker): + """ + GIVEN a lp printer object and a mocked connection + WHEN a connection is opened and closed + THEN check the closing is logged and the device property is False + """ + mocker.patch("subprocess.Popen") + mocker.patch("escpos.printer.LP.printers", new={"test_printer": "Test"}) + + lpprinter.printer_name = "test_printer" + lpprinter.open() + + with caplog.at_level(logging.INFO): + lpprinter.close() + + assert "Closing" in caplog.text + assert lpprinter._device is False diff --git a/test/test_printers/test_printer_network.py b/test/test_printers/test_printer_network.py new file mode 100644 index 0000000..1624120 --- /dev/null +++ b/test/test_printers/test_printer_network.py @@ -0,0 +1,96 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""tests for the Network printer + +:author: `Patrick Kanzler `_ and the python-escpos developers +:organization: `python-escpos `_ +:copyright: Copyright (c) 2016-2023 `python-escpos `_ +:license: MIT +""" + +import logging + +import pytest + + +def test_device_not_initialized(networkprinter): + """ + GIVEN a network printer object + WHEN it is not initialized + THEN check the device property is False + """ + assert networkprinter._device is False + + +def test_open_raise_exception(networkprinter, devicenotfounderror): + """ + GIVEN a network printer object + WHEN open() is set to raise a DeviceNotFoundError on error + THEN check the exception is raised + """ + networkprinter.host = "fakehost" + + with pytest.raises(devicenotfounderror): + networkprinter.open(raise_not_found=True) + + +def test_open_not_raise_exception(networkprinter, caplog): + """ + GIVEN a network printer object + WHEN open() is set to not raise on error but simply cancel + THEN check the error is logged and open() canceled + """ + networkprinter.host = "fakehost" + + with caplog.at_level(logging.ERROR): + networkprinter.open(raise_not_found=False) + + assert "not found" in caplog.text + assert networkprinter.device is None + + +def test_open(networkprinter, caplog, mocker): + """ + GIVEN a network printer object and a mocked socket device + WHEN a valid connection to a device is opened + THEN check the success is logged and the device property is set + """ + mocker.patch("socket.socket") + + with caplog.at_level(logging.INFO): + networkprinter.open() + + assert "enabled" in caplog.text + assert networkprinter.device + + +def test_close_on_reopen(networkprinter, mocker): + """ + GIVEN a network printer object and a mocked connection + WHEN a valid connection to a device is reopened before close + THEN check the close method is called if _device + """ + mocker.patch("socket.socket") + spy = mocker.spy(networkprinter, "close") + + networkprinter.open() + assert networkprinter._device + + networkprinter.open() + spy.assert_called_once_with() + + +def test_close(networkprinter, caplog, mocker): + """ + GIVEN a network printer object and a mocked socket device + WHEN a connection is opened and closed + THEN check the closing is logged and the device property is False + """ + mocker.patch("socket.socket") + networkprinter.open() + + with caplog.at_level(logging.INFO): + networkprinter.close() + + assert "Closing" in caplog.text + assert networkprinter._device is False diff --git a/test/test_printers/test_printer_serial.py b/test/test_printers/test_printer_serial.py new file mode 100644 index 0000000..4460377 --- /dev/null +++ b/test/test_printers/test_printer_serial.py @@ -0,0 +1,96 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""tests for the Serial printer + +:author: Benito López and the python-escpos developers +:organization: `python-escpos `_ +:copyright: Copyright (c) 2023 `python-escpos `_ +:license: MIT +""" + +import logging + +import pytest + + +def test_device_not_initialized(serialprinter): + """ + GIVEN a serial printer object + WHEN it is not initialized + THEN check the device property is False + """ + assert serialprinter._device is False + + +def test_open_raise_exception(serialprinter, devicenotfounderror): + """ + GIVEN a serial printer object + WHEN open() is set to raise a DeviceNotFoundError on error + THEN check the exception is raised + """ + serialprinter.devfile = "fake/device" + + with pytest.raises(devicenotfounderror): + serialprinter.open(raise_not_found=True) + + +def test_open_not_raise_exception(serialprinter, caplog): + """ + GIVEN a serial printer object + WHEN open() is set to not raise on error but simply cancel + THEN check the error is logged and open() canceled + """ + serialprinter.devfile = "fake/device" + + with caplog.at_level(logging.ERROR): + serialprinter.open(raise_not_found=False) + + assert "not found" in caplog.text + assert serialprinter.device is None + + +def test_open(serialprinter, caplog, mocker): + """ + GIVEN a serial printer object and a mocked pyserial device + WHEN a valid connection to a device is opened + THEN check the success is logged and the device property is set + """ + mocker.patch("serial.Serial") + + with caplog.at_level(logging.INFO): + serialprinter.open() + + assert "enabled" in caplog.text + assert serialprinter.device + + +def test_close_on_reopen(serialprinter, mocker): + """ + GIVEN a serial printer object and a mocked connection + WHEN a valid connection to a device is reopened before close + THEN check the close method is called if _device + """ + mocker.patch("serial.Serial") + spy = mocker.spy(serialprinter, "close") + + serialprinter.open() + assert serialprinter._device + + serialprinter.open() + spy.assert_called_once_with() + + +def test_close(serialprinter, caplog, mocker): + """ + GIVEN a serial printer object and a mocked pyserial device + WHEN a connection is opened and closed + THEN check the closing is logged and the device property is False + """ + mocker.patch("serial.Serial") + serialprinter.open() + + with caplog.at_level(logging.INFO): + serialprinter.close() + + assert "Closing" in caplog.text + assert serialprinter._device is False diff --git a/test/test_printers/test_printer_usb.py b/test/test_printers/test_printer_usb.py new file mode 100644 index 0000000..28d3594 --- /dev/null +++ b/test/test_printers/test_printer_usb.py @@ -0,0 +1,106 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""tests for the Usb printer + +:author: Benito López and the python-escpos developers +:organization: `python-escpos `_ +:copyright: Copyright (c) 2023 `python-escpos `_ +:license: MIT +""" + +import logging + +# import pytest + + +def test_device_not_initialized(usbprinter): + """ + GIVEN a usb printer object + WHEN it is not initialized + THEN check the device property is False + """ + assert usbprinter._device is False + + +def test_open_raise_exception(usbprinter, devicenotfounderror, mocker): + """ + # GIVEN a usb printer object + GIVEN a mocked usb printer object + WHEN open() is set to raise a DeviceNotFoundError on error + # THEN check the exception is raised + THEN check the param is True + """ + mocker.patch("usb.core.find") + spy = mocker.spy(usbprinter, "open") + # usbprinter.usb_args = {"idVendor": 0, "idProduct": 0} + + # with pytest.raises(devicenotfounderror): + usbprinter.open(raise_not_found=True) + spy.assert_called_once_with(raise_not_found=True) + + +def test_open_not_raise_exception(usbprinter, caplog, mocker): + """ + # GIVEN a usb printer object + GIVEN a mocked usb printer object + WHEN open() is set to not raise on error but simply cancel + # THEN check the error is logged and open() canceled + THEN check the param is False + """ + mocker.patch("usb.core.find") + spy = mocker.spy(usbprinter, "open") + # usbprinter.usb_args = {"idVendor": 0, "idProduct": 0} + + # with caplog.at_level(logging.ERROR): + usbprinter.open(raise_not_found=False) + + # assert "not found" in caplog.text + # assert usbprinter.device is None + spy.assert_called_once_with(raise_not_found=False) + + +def test_open(usbprinter, caplog, mocker): + """ + GIVEN a usb printer object and a mocked pyusb device + WHEN a valid connection to a device is opened + THEN check the success is logged and the device property is set + """ + mocker.patch("usb.core.find") + + with caplog.at_level(logging.INFO): + usbprinter.open() + + assert "enabled" in caplog.text + assert usbprinter.device + + +def test_close_on_reopen(usbprinter, mocker): + """ + GIVEN a usb printer object and a mocked connection + WHEN a valid connection to a device is reopened before close + THEN check the close method is called if _device + """ + mocker.patch("usb.core.find") + spy = mocker.spy(usbprinter, "close") + + usbprinter.open() + assert usbprinter._device + + usbprinter.open() + spy.assert_called_once_with() + + +def test_close(usbprinter, caplog, mocker): + """ + GIVEN a usb printer object and a mocked pyusb device + WHEN a connection is opened and closed + THEN check the closing is logged and the device property is False + """ + mocker.patch("usb.core.find") + usbprinter.open() + + with caplog.at_level(logging.INFO): + usbprinter.close() + + assert "Closing" in caplog.text + assert usbprinter._device is False diff --git a/test/test_printers/test_printer_win32raw.py b/test/test_printers/test_printer_win32raw.py new file mode 100644 index 0000000..7a82dcd --- /dev/null +++ b/test/test_printers/test_printer_win32raw.py @@ -0,0 +1,177 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""tests for the Win32Raw printer + +:author: Benito López and the python-escpos developers +:organization: `python-escpos `_ +:copyright: Copyright (c) 2023 `python-escpos `_ +:license: MIT +""" + +import logging +import sys + +import pytest + +# skip all the tests if the platform is not Windows +pytestmark = pytest.mark.skipif( + sys.platform != "win32", reason="Skipping Windows platform specific tests" +) + + +def test_device_not_initialized(win32rawprinter): + """ + GIVEN a win32raw printer object + WHEN it is not initialized + THEN check the device property is False + """ + assert win32rawprinter._device is False + + +def test_open_raise_exception(win32rawprinter, devicenotfounderror): + """ + GIVEN a win32raw printer object + WHEN open() is set to raise a DeviceNotFoundError on error + THEN check the exception is raised + """ + win32rawprinter.printer_name = "fake_printer" + + with pytest.raises(devicenotfounderror): + win32rawprinter.open(raise_not_found=True) + + +def test_open_not_raise_exception(win32rawprinter, caplog): + """ + GIVEN a win32raw printer object + WHEN open() is set to not raise on error but simply cancel + THEN check the error is logged and open() canceled + """ + win32rawprinter.printer_name = "fake_printer" + + with caplog.at_level(logging.ERROR): + win32rawprinter.open(raise_not_found=False) + + assert "not available" in caplog.text + assert win32rawprinter.device is None + + +def test_open(win32rawprinter, caplog, mocker): + """ + GIVEN a win32raw printer object and a mocked win32printer device + WHEN a valid connection to a device is opened + THEN check the success is logged and the device property is set + """ + # The _win32typing.PyPrinterHANDLE object is unreachable, so we have to mock it + PyPrinterHANDLE = mocker.Mock() + PyPrinterHANDLE.return_value = 0 # Accepts 0 or None as return value + + # Replace the contents of Win32Raw.printers to accept test_printer as a system's printer name + mocker.patch("escpos.printer.Win32Raw.printers", new={"test_printer": "Test"}) + + # Configure and assert printer_name is valid + win32rawprinter.printer_name = "test_printer" + assert win32rawprinter.printer_name in win32rawprinter.printers + + with caplog.at_level(logging.INFO): + # Patch the win32print.OpenPrinter method to return the mocked PyPrinterHANDLE + mocker.patch("win32print.OpenPrinter", new=PyPrinterHANDLE) + win32rawprinter.open() + + assert "enabled" in caplog.text + assert win32rawprinter.device == PyPrinterHANDLE.return_value + + +def test_close_on_reopen(win32rawprinter, mocker): + """ + GIVEN a win32raw printer object and a mocked win32print device + WHEN a valid connection to a device is reopened before close + THEN check the close method is called if _device + """ + # The _win32typing.PyPrinterHANDLE object is unreachable, so we have to mock it + PyPrinterHANDLE = mocker.Mock() + PyPrinterHANDLE.return_value = 0 # Accepts 0 or None as return value + + # Replace the contents of Win32Raw.printers to accept test_printer as a system's printer name + mocker.patch("escpos.printer.Win32Raw.printers", new={"test_printer": "Test"}) + + # Configure printer_name + win32rawprinter.printer_name = "test_printer" + + # Patch the win32print.OpenPrinter method to return the mocked PyPrinterHANDLE + mocker.patch("win32print.OpenPrinter", new=PyPrinterHANDLE) + # Patch the win32print close methods + mocker.patch("win32print.EndPagePrinter") + mocker.patch("win32print.EndDocPrinter") + mocker.patch("win32print.ClosePrinter") + + spy = mocker.spy(win32rawprinter, "close") + # Simulate a reopen before close + win32rawprinter._device = True + win32rawprinter.open() + + spy.assert_called_once() + + +def test_close(win32rawprinter, caplog, mocker): + """ + GIVEN a win32raw printer object and a mocked win32print device + WHEN a connection is opened and closed + THEN check the closing is logged and the device property is False + """ + # The _win32typing.PyPrinterHANDLE object is unreachable, so we have to mock it + PyPrinterHANDLE = mocker.Mock() + PyPrinterHANDLE.return_value = 0 # Accepts 0 or None as return value + + # Replace the contents of Win32Raw.printers to accept test_printer as a system's printer name + mocker.patch("escpos.printer.Win32Raw.printers", new={"test_printer": "Test"}) + + # Configure and assert printer_name is valid + win32rawprinter.printer_name = "test_printer" + assert win32rawprinter.printer_name in win32rawprinter.printers + + # Patch the win32print.OpenPrinter method to return the mocked PyPrinterHANDLE + mocker.patch("win32print.OpenPrinter", new=PyPrinterHANDLE) + win32rawprinter.open() + with caplog.at_level(logging.INFO): + # Patch the win32print close methods + # Raises a warning but passes the test + mocker.patch("win32print.EndPagePrinter") + mocker.patch("win32print.EndDocPrinter") + mocker.patch("win32print.ClosePrinter") + win32rawprinter.close() + + assert "Closing" in caplog.text + assert win32rawprinter._device is False + + +def test_raw_raise_exception(win32rawprinter, devicenotfounderror): + """ + GIVEN a win32raw printer object and a mocked win32print device + WHEN calling _raw() before configuring the connection + THEN check an exception is raised + """ + win32rawprinter.printer_name = None + with pytest.raises(devicenotfounderror): + win32rawprinter._raw(b"Test error") + + win32rawprinter.printer_name = "fake_printer" + win32rawprinter.device = None + with pytest.raises(devicenotfounderror): + win32rawprinter._raw(b"Test error") + + +def test_raw(win32rawprinter, mocker): + """ + GIVEN a win32raw printer object and a mocked win32print device + WHEN calling _raw() after a valid connection + THEN check the underlying method is correctly called + """ + PyPrinterHANDLE = mocker.Mock() + PyPrinterHANDLE.return_value = 0 + + mocked_writer = mocker.patch("win32print.WritePrinter") + + win32rawprinter._device = PyPrinterHANDLE + win32rawprinter._raw(b"Test error") + + mocked_writer.assert_called_once_with(PyPrinterHANDLE, b"Test error")