From 815f247df19055743515e19c40a03ab46ac0c72c Mon Sep 17 00:00:00 2001 From: Patrick Kanzler <4189642+patkan@users.noreply.github.com> Date: Wed, 6 Dec 2023 00:25:36 +0100 Subject: [PATCH] Improve test coverage and fix issue in path loading (#602) * reactivate skipped tests * remove unused internal interfaces * enable pytest in VS Code * simplify and fix path logic in loader * increase test coverage * test missing config * check for wrong printer names * Skipped appdir test --- .vscode/settings.json | 2 + CHANGELOG.rst | 2 + src/escpos/codepages.py | 4 - src/escpos/config.py | 23 +++-- test/test_cli.py | 7 +- test/test_config.py | 126 ++++++++++++++++++++++++++++ test/test_function_qr_native.py | 18 +--- test/test_function_qr_non-native.py | 24 +++++- 8 files changed, 167 insertions(+), 39 deletions(-) create mode 100644 test/test_config.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 0d847a6..a850fc9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,4 +14,6 @@ "editor.codeActionsOnSave": { "source.organizeImports": true }, + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, } \ No newline at end of file diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e3a386f..a21fe2b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ Changelog changes ^^^^^^^ - use version 0.15 and upwards of python-barcode +- fix an issue in the config provider that prevented + config files to be found when only a path was supplied contributors ^^^^^^^^^^^^ diff --git a/src/escpos/codepages.py b/src/escpos/codepages.py index ca7e002..0886213 100644 --- a/src/escpos/codepages.py +++ b/src/escpos/codepages.py @@ -12,10 +12,6 @@ class CodePageManager: """Initialize codepage manager.""" self.data = data - def get_all(self): - """Get list of all codepages.""" - return self.data.values() - @staticmethod def get_encoding_name(encoding): """Get encoding name. diff --git a/src/escpos/config.py b/src/escpos/config.py index d4868f3..f0d249b 100644 --- a/src/escpos/config.py +++ b/src/escpos/config.py @@ -3,6 +3,7 @@ This module contains the implementations of abstract base class :py:class:`Config`. """ import os +import pathlib import appdirs import yaml @@ -56,23 +57,19 @@ class Config(object): config_path = os.path.join( appdirs.user_config_dir(self._app_name), self._config_file ) + if isinstance(config_path, pathlib.Path): + # store string if posixpath + config_path = config_path.as_posix() + if not os.path.isfile(config_path): + # supplied path is not a file --> assume default file + config_path = os.path.join(config_path, self._config_file) try: - # First check if it's file like. If it is, pyyaml can load it. - # I'm checking type instead of catching exceptions to keep the - # exception handling simple - if hasattr(config_path, "read"): - config = yaml.safe_load(config_path) - else: - # If it isn't, it's a path. We have to open it first, otherwise - # pyyaml will try to read it as yaml - with open(config_path, "rb") as config_file: - config = yaml.safe_load(config_file) + with open(config_path, "rb") as config_file: + config = yaml.safe_load(config_file) except EnvironmentError: raise exceptions.ConfigNotFoundError( - "Couldn't read config at {config_path}".format( - config_path=str(config_path), - ) + f"Couldn't read config at {config_path}" ) except yaml.YAMLError: raise exceptions.ConfigSyntaxError("Error parsing YAML") diff --git a/test/test_cli.py b/test/test_cli.py index 648dd6e..c40f8cc 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -89,9 +89,6 @@ class TestCLI: # test that additional information on e.g. Serial is printed assert "Serial" in result.stdout - @pytest.mark.skip( - reason="disable this test as it is not that easy anymore to predict the outcome of this call" - ) def test_cli_text(self): """Make sure text returns what we sent it""" test_text = "this is some text" @@ -107,7 +104,9 @@ class TestCLI: ) assert not result.stderr assert DEVFILE_NAME in result.files_updated.keys() - assert result.files_updated[DEVFILE_NAME].bytes == test_text + "\n" + assert ( + result.files_updated[DEVFILE_NAME].bytes == "\x1bt\x00" + test_text + "\n" + ) def test_cli_text_invalid_args(self): """Test a failure to send valid arguments""" diff --git a/test/test_config.py b/test/test_config.py new file mode 100644 index 0000000..831c09c --- /dev/null +++ b/test/test_config.py @@ -0,0 +1,126 @@ +#!/usr/bin/python +"""tests for config module + +:author: `Patrick Kanzler `_ +:organization: `python-escpos `_ +:copyright: Copyright (c) 2023 `python-escpos `_ +:license: MIT +""" +import pathlib + +import appdirs +import pytest + +import escpos.exceptions + + +def generate_dummy_config(path, content=None): + """Generate a dummy config in path""" + dummy_config_content = content + if not content: + dummy_config_content = "printer:\n type: Dummy\n" + path.write_text(dummy_config_content) + assert path.read_text() == dummy_config_content + + +def simple_printer_test(config): + """Simple test for the dummy printer.""" + p = config.printer() + p._raw(b"1234") + + assert p.output == b"1234" + + +def test_config_load_with_invalid_config_yaml(tmp_path): + """Test the loading of a config with a invalid config file (yaml issue).""" + # generate a dummy config + config_file = tmp_path / "config.yaml" + generate_dummy_config(config_file, content="}invalid}yaml}") + + # test the config loading + from escpos import config + + c = config.Config() + with pytest.raises(escpos.exceptions.ConfigSyntaxError): + c.load(config_path=config_file) + + +def test_config_load_with_invalid_config_content(tmp_path): + """Test the loading of a config with a invalid config file (content issue).""" + # generate a dummy config + config_file = tmp_path / "config.yaml" + generate_dummy_config( + config_file, content="printer:\n type: NoPrinterWithThatName\n" + ) + + # test the config loading + from escpos import config + + c = config.Config() + with pytest.raises(escpos.exceptions.ConfigSyntaxError): + c.load(config_path=config_file) + + +def test_config_load_with_missing_config(tmp_path): + """Test the loading of a config that does not exist.""" + # test the config loading + from escpos import config + + c = config.Config() + with pytest.raises(escpos.exceptions.ConfigNotFoundError): + c.load(config_path=tmp_path) + + +@pytest.mark.skip( + "This test creates in the actual appdir files and is therefore skipped." +) +def test_config_load_from_appdir(): + """Test the loading of a config in appdir.""" + from escpos import config + + # generate a dummy config + config_file = ( + pathlib.Path(appdirs.user_config_dir(config.Config._app_name)) + / config.Config._config_file + ) + + generate_dummy_config(config_file) + + # test the config loading + c = config.Config() + c.load() + + # test the resulting printer object + simple_printer_test(c) + + +def test_config_load_with_file(tmp_path): + """Test the loading of a config with a config file.""" + # generate a dummy config + config_file = tmp_path / "config.yaml" + generate_dummy_config(config_file) + + # test the config loading + from escpos import config + + c = config.Config() + c.load(config_path=config_file) + + # test the resulting printer object + simple_printer_test(c) + + +def test_config_load_with_path(tmp_path): + """Test the loading of a config with a config path.""" + # generate a dummy config + config_file = tmp_path / "config.yaml" + generate_dummy_config(config_file) + + # test the config loading + from escpos import config + + c = config.Config() + c.load(config_path=tmp_path) + + # test the resulting printer object + simple_printer_test(c) diff --git a/test/test_function_qr_native.py b/test/test_function_qr_native.py index 876b38d..e8cac69 100644 --- a/test/test_function_qr_native.py +++ b/test/test_function_qr_native.py @@ -3,7 +3,7 @@ :author: `Michael Billington `_ :organization: `python-escpos `_ -:copyright: Copyright (c) 2016 `Michael Billington `_ +:copyright: Copyright (c) 2023 `Michael Billington `_ :license: MIT """ @@ -86,22 +86,6 @@ def test_invalid_model(): instance.qr("1234", native=True, model="Hello") -@pytest.mark.skip("this test has to be debugged") -def test_image(): - """Test QR as image""" - instance = printer.Dummy() - instance.qr("1", native=False, size=1) - print(instance.output) - expected = ( - b"\x1bt\x00\n" - b"\x1dv0\x00\x03\x00\x17\x00\x00\x00\x00\x7f]\xfcA\x19\x04]it]et" - b"]ItA=\x04\x7fU\xfc\x00\x0c\x00y~t4\x7f =\xa84j\xd9\xf0\x05\xd4\x90\x00" - b"i(\x7f<\xa8A \xd8]'\xc4]y\xf8]E\x80Ar\x94\x7fR@\x00\x00\x00" - b"\n\n" - ) - assert instance.output == expected - - def test_image_invalid_model(): """Test unsupported QR model as image""" instance = printer.Dummy() diff --git a/test/test_function_qr_non-native.py b/test/test_function_qr_non-native.py index 6b4ce92..04b768f 100644 --- a/test/test_function_qr_non-native.py +++ b/test/test_function_qr_non-native.py @@ -4,7 +4,7 @@ :author: `Patrick Kanzler `_ :organization: `python-escpos `_ -:copyright: Copyright (c) 2016 `python-escpos `_ +:copyright: Copyright (c) 2023 `python-escpos `_ :license: MIT """ @@ -18,6 +18,28 @@ from PIL import Image from escpos.printer import Dummy +def test_image(): + """Test QR as image""" + instance = Dummy() + image_arguments = { + "high_density_vertical": True, + "high_density_horizontal": True, + "impl": "bitImageRaster", + "fragment_height": 960, + "center": False, + } + instance.qr("1", native=False, image_arguments=image_arguments, size=1) + print(instance.output) + expected = ( + b"\x1bt\x00\n" + b"\x1dv0\x00\x03\x00\x17\x00\x00\x00\x00\x7f\x1d\xfcAu\x04]\x1dt]et" + b"]%tAI\x04\x7fU\xfc\x00 \x00}\xca\xa8h\xdf u\x95\x80x/ \x0b\xf4\x98\x00" + b"T\x90\x7fzxA\x00\xd0]zp]o ]u\x80Ao(\x7fd\x90\x00\x00\x00" + b"\n\n" + ) + assert instance.output == expected + + @mock.patch("escpos.printer.Dummy.image", spec=Dummy) def test_type_of_object_passed_to_image_function(img_function): """