diff --git a/src/printers/P700Printer.cpp b/src/printers/P700Printer.cpp index 1ebcdeb..006cebd 100644 --- a/src/printers/P700Printer.cpp +++ b/src/printers/P700Printer.cpp @@ -90,6 +90,11 @@ libusbwrap::usbId P700Printer::getUsbId() { } bool P700Printer::attachUsbDevice(std::shared_ptr usbHndl) { + if (!usbHndl) { + spdlog::error("Cannot attach null USB device"); + return false; + } + if (!usbHndl->open()) { spdlog::error("Unable to open USB device: {}", usbHndl->getLastErrorString()); return false; @@ -182,6 +187,11 @@ bool P700Printer::printMonochromeData(const graphics::MonochromeData& data) { } bool P700Printer::printLabel(std::unique_ptr label) { + if (!label) { + spdlog::error("Cannot print null label"); + return false; + } + // Convert label directly to MonochromeData // getRaw() returns data in Cairo surface coordinates matching getWidth() × getHeight() auto pixels = label->getRaw(); @@ -200,9 +210,15 @@ bool P700Printer::printLabel(std::unique_ptr label) { } bool P700Printer::print() { - send(p700::commands::LF); - send(p700::commands::FF); - send(p700::commands::EJECT); + if (!send(p700::commands::LF)) { + return false; + } + if (!send(p700::commands::FF)) { + return false; + } + if (!send(p700::commands::EJECT)) { + return false; + } return true; } diff --git a/tests/meson.build b/tests/meson.build index 5bffb57..5a61a74 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -5,11 +5,25 @@ test_sources = [ 'bitmap_test/bitmap_test.cpp', 'monochrome_test/monochrome_test.cpp', 'label_test/label_test.cpp', + 'printer_service_test/printer_service_test.cpp', + 'p700_printer_test/p700_printer_test.cpp', - # Source files under test + # Source files under test - graphics '../src/graphics/Bitmap.cpp', '../src/graphics/Monochrome.cpp', '../src/graphics/Label.cpp', + + # Source files under test - core + '../src/core/PrinterService.cpp', + '../src/core/PrinterDriverFactory.cpp', + + # Source files under test - printers + '../src/printers/P700Printer.cpp', + '../src/printers/FakePrinter.cpp', + + # Source files under test - USB + '../src/libusbwrap/UsbDevice.cpp', + '../src/libusbwrap/UsbDeviceFactory.cpp', ] test_exe = executable( diff --git a/tests/mocks/MockPrinterDriver.hpp b/tests/mocks/MockPrinterDriver.hpp new file mode 100644 index 0000000..47c442d --- /dev/null +++ b/tests/mocks/MockPrinterDriver.hpp @@ -0,0 +1,50 @@ +/* + ptrnt - print labels on linux + Copyright (C) 2025 Moritz Martinius + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + */ + +#pragma once + +#include + +#include "printers/interface/IPrinterDriver.hpp" + +namespace ptprnt { + +/** + * @brief GMock implementation of IPrinterDriver for unit testing + * + * This mock allows tests to verify printer interactions without + * requiring actual printer hardware. + */ +class MockPrinterDriver : public IPrinterDriver { + public: + MOCK_METHOD(std::string_view, getDriverName, (), (override)); + MOCK_METHOD(std::string_view, getName, (), (override)); + MOCK_METHOD(std::string_view, getVersion, (), (override)); + MOCK_METHOD(libusbwrap::usbId, getUsbId, (), (override)); + MOCK_METHOD(PrinterInfo, getPrinterInfo, (), (override)); + MOCK_METHOD(PrinterStatus, getPrinterStatus, (), (override)); + MOCK_METHOD(bool, attachUsbDevice, (std::shared_ptr usbHndl), (override)); + MOCK_METHOD(bool, detachUsbDevice, (), (override)); + MOCK_METHOD(bool, printBitmap, (const graphics::Bitmap& bitmap), (override)); + MOCK_METHOD(bool, printMonochromeData, (const graphics::MonochromeData& data), (override)); + MOCK_METHOD(bool, printLabel, (const std::unique_ptr label), (override)); + MOCK_METHOD(bool, print, (), (override)); +}; + +} // namespace ptprnt diff --git a/tests/mocks/MockUsbDevice.hpp b/tests/mocks/MockUsbDevice.hpp new file mode 100644 index 0000000..1a38360 --- /dev/null +++ b/tests/mocks/MockUsbDevice.hpp @@ -0,0 +1,57 @@ +/* + ptrnt - print labels on linux + Copyright (C) 2025 Moritz Martinius + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + */ + +#pragma once + +#include + +#include "libusbwrap/interface/IUsbDevice.hpp" + +namespace libusbwrap { + +/** + * @brief GMock implementation of IUsbDevice for unit testing + * + * This mock allows tests to verify USB device interactions without + * requiring actual hardware or libusb context. + */ +class MockUsbDevice : public IUsbDevice { + public: + MOCK_METHOD(bool, open, (), (override)); + MOCK_METHOD(void, close, (), (override)); + + // libusb wrappers + MOCK_METHOD(bool, detachKernelDriver, (int interfaceNo), (override)); + MOCK_METHOD(bool, claimInterface, (int interfaceNo), (override)); + MOCK_METHOD(bool, releaseInterface, (int interfaceNo), (override)); + MOCK_METHOD(bool, bulkTransfer, + (uint8_t endpoint, const std::vector& data, int* tx, unsigned int timeout), (override)); + + // getters + MOCK_METHOD(const usbId, getUsbId, (), (override)); + MOCK_METHOD(const device::Speed, getSpeed, (), (override)); + MOCK_METHOD(const uint8_t, getBusNumber, (), (override)); + MOCK_METHOD(const uint8_t, getPortNumber, (), (override)); + + // errors + MOCK_METHOD(const Error, getLastError, (), (override)); + MOCK_METHOD(const std::string, getLastErrorString, (), (override)); +}; + +} // namespace libusbwrap diff --git a/tests/mocks/MockUsbDeviceFactory.hpp b/tests/mocks/MockUsbDeviceFactory.hpp new file mode 100644 index 0000000..4089dbb --- /dev/null +++ b/tests/mocks/MockUsbDeviceFactory.hpp @@ -0,0 +1,40 @@ +/* + ptrnt - print labels on linux + Copyright (C) 2025 Moritz Martinius + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + */ + +#pragma once + +#include + +#include "libusbwrap/interface/IUsbDeviceFactory.hpp" + +namespace libusbwrap { + +/** + * @brief GMock implementation of IUsbDeviceFactory for unit testing + * + * This mock allows tests to control USB device discovery without + * requiring actual libusb context or hardware. + */ +class MockUsbDeviceFactory : public IUsbDeviceFactory { + public: + MOCK_METHOD(std::vector>, findAllDevices, (), (override)); + MOCK_METHOD(std::vector>, findDevices, (uint16_t vid, uint16_t pid), (override)); +}; + +} // namespace libusbwrap diff --git a/tests/p700_printer_test/p700_printer_test.cpp b/tests/p700_printer_test/p700_printer_test.cpp new file mode 100644 index 0000000..003de02 --- /dev/null +++ b/tests/p700_printer_test/p700_printer_test.cpp @@ -0,0 +1,169 @@ +/* + ptrnt - print labels on linux + Copyright (C) 2025 Moritz Martinius + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + */ + +#include +#include + +#include + +#include "../../tests/mocks/MockUsbDevice.hpp" +#include "graphics/Bitmap.hpp" +#include "graphics/Monochrome.hpp" +#include "printers/P700Printer.hpp" + +using ::testing::_; +using ::testing::DoAll; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::SetArgPointee; + +namespace ptprnt::printer { + +// Test fixture for P700Printer tests +class P700PrinterTest : public ::testing::Test { + protected: + void SetUp() override { + printer = std::make_unique(); + mockUsbDev = std::make_shared>(); + + // Default mock behaviors + ON_CALL(*mockUsbDev, open()).WillByDefault(Return(true)); + ON_CALL(*mockUsbDev, close()).WillByDefault(Return()); + ON_CALL(*mockUsbDev, detachKernelDriver(_)).WillByDefault(Return(true)); + ON_CALL(*mockUsbDev, claimInterface(_)).WillByDefault(Return(true)); + ON_CALL(*mockUsbDev, releaseInterface(_)).WillByDefault(Return(true)); + ON_CALL(*mockUsbDev, bulkTransfer(_, _, _, _)).WillByDefault(Return(true)); + ON_CALL(*mockUsbDev, getUsbId()).WillByDefault(Return(libusbwrap::usbId{0x04f9, 0x2061})); + } + + void TearDown() override { printer.reset(); } + + std::unique_ptr printer; + std::shared_ptr mockUsbDev; +}; + +// Test: Get printer driver name +TEST_F(P700PrinterTest, GetDriverName) { + EXPECT_EQ(printer->getDriverName(), "P700"); +} + +// Test: Get printer name +TEST_F(P700PrinterTest, GetName) { + auto name = printer->getName(); + EXPECT_FALSE(name.empty()); +} + +// Test: Get USB ID +TEST_F(P700PrinterTest, GetUsbId) { + auto usbId = printer->getUsbId(); + EXPECT_EQ(usbId.first, 0x04f9); // Brother VID + EXPECT_EQ(usbId.second, 0x2061); // P700 PID +} + +// Test: Get printer version +TEST_F(P700PrinterTest, GetVersion) { + auto version = printer->getVersion(); + EXPECT_FALSE(version.empty()); +} + +// Test: Get printer info +TEST_F(P700PrinterTest, GetPrinterInfo) { + auto info = printer->getPrinterInfo(); + EXPECT_EQ(info.pixelLines, 128); + EXPECT_FALSE(info.name.empty()); +} + +// Test: Attach USB device +TEST_F(P700PrinterTest, AttachUsbDevice) { + bool result = printer->attachUsbDevice(mockUsbDev); + + EXPECT_TRUE(result); +} + +// Test: Attach USB device with null pointer +TEST_F(P700PrinterTest, AttachNullUsbDevice) { + bool result = printer->attachUsbDevice(nullptr); + + EXPECT_FALSE(result); +} + +// Test: Detach USB device +TEST_F(P700PrinterTest, DetachUsbDevice) { + printer->attachUsbDevice(mockUsbDev); + + bool result = printer->detachUsbDevice(); + + EXPECT_TRUE(result); +} + +// Test: Detach when no device attached +TEST_F(P700PrinterTest, DetachNoDevice) { + bool result = printer->detachUsbDevice(); + + // Production code returns true when no device is attached (just logs warning) + EXPECT_TRUE(result); +} + +// Test: Get printer status without device +TEST_F(P700PrinterTest, GetStatusNoDevice) { + auto status = printer->getPrinterStatus(); + + EXPECT_EQ(status.tapeWidthPixel, 0); // Should be 0 when no device +} + +// Test: Print without attached device +TEST_F(P700PrinterTest, PrintWithoutDevice) { + bool result = printer->print(); + + // Should fail when no device is attached + EXPECT_FALSE(result); +} + +// Test: Print bitmap without device +TEST_F(P700PrinterTest, PrintBitmapWithoutDevice) { + graphics::Bitmap bitmap(10, 10); + + bool result = printer->printBitmap(bitmap); + + EXPECT_FALSE(result); +} + +// Test: Print monochrome data without device +TEST_F(P700PrinterTest, PrintMonochromeDataWithoutDevice) { + graphics::MonochromeData data; + data.width = 10; + data.height = 10; + data.bytes = std::vector(20, 0xFF); + data.stride = 2; + + bool result = printer->printMonochromeData(data); + + EXPECT_FALSE(result); +} + +// Test: Print label with null pointer +TEST_F(P700PrinterTest, PrintNullLabel) { + std::unique_ptr label = nullptr; + + bool result = printer->printLabel(std::move(label)); + + EXPECT_FALSE(result); +} + +} // namespace ptprnt::printer diff --git a/tests/printer_service_test/printer_service_test.cpp b/tests/printer_service_test/printer_service_test.cpp new file mode 100644 index 0000000..b8d0332 --- /dev/null +++ b/tests/printer_service_test/printer_service_test.cpp @@ -0,0 +1,116 @@ +/* + ptrnt - print labels on linux + Copyright (C) 2025 Moritz Martinius + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + */ + +#include +#include + +#include +#include + +#include "../../tests/mocks/MockPrinterDriver.hpp" +#include "../../tests/mocks/MockUsbDevice.hpp" +#include "core/PrinterDriverFactory.hpp" +#include "core/PrinterService.hpp" + +using ::testing::_; +using ::testing::NiceMock; +using ::testing::Return; + +namespace ptprnt::core { + +// Test fixture for PrinterService tests +class PrinterServiceTest : public ::testing::Test { + protected: + void SetUp() override { + // Service under test + service = std::make_unique(); + } + + void TearDown() override { service.reset(); } + + std::unique_ptr service; +}; + +// Test: PrinterService initialization +TEST_F(PrinterServiceTest, InitializeSuccess) { + // PrinterService::initialize() calls UsbDeviceFactory::init() + // This will attempt to initialize libusb - we can't easily mock this + // without dependency injection, but we can test the call succeeds + // when libusb is available + EXPECT_TRUE(service->initialize()); +} + +// Test: Detect printers when none are connected +TEST_F(PrinterServiceTest, DetectPrintersNoneFound) { + service->initialize(); + + auto printers = service->detectPrinters(); + + // With no compatible USB devices, we should get an empty list + // (This depends on actual USB devices present, so it might find real hardware) + // In a real test environment without hardware, this should be empty + EXPECT_GE(printers.size(), 0); // Non-negative count +} + +// Test: Select printer with auto-detect +TEST_F(PrinterServiceTest, SelectPrinterAuto) { + service->initialize(); + service->detectPrinters(); + + auto printer = service->selectPrinter("auto"); + + // This will be nullptr if no printers detected + // In test environment without hardware, expect nullptr + // (Test passes either way - just exercises the code path) + if (printer != nullptr) { + EXPECT_NE(printer, nullptr); + EXPECT_EQ(service->getCurrentPrinter(), printer); + } else { + EXPECT_EQ(printer, nullptr); + } +} + +// Test: Select non-existent printer +TEST_F(PrinterServiceTest, SelectPrinterNotFound) { + service->initialize(); + service->detectPrinters(); + + auto printer = service->selectPrinter("NonExistentPrinter"); + + EXPECT_EQ(printer, nullptr); + EXPECT_EQ(service->getCurrentPrinter(), nullptr); +} + +// Test: Get current printer when none selected +TEST_F(PrinterServiceTest, GetCurrentPrinterNoneSelected) { + EXPECT_EQ(service->getCurrentPrinter(), nullptr); +} + +// Test: Print label without selecting printer +TEST_F(PrinterServiceTest, PrintLabelNoPrinterSelected) { + // Create a simple label (we need a valid ILabel pointer) + std::unique_ptr label = nullptr; // nullptr label for this test + + bool result = service->printLabel(std::move(label)); + + // Should fail because no printer is selected + EXPECT_FALSE(result); +} + +} // namespace ptprnt::core