mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
* Added initial draft for machines * refactor: isPluginRegistryLoaded check into own ready function * Added suggestions from codereview * Refactor: base_drivers -> machine_types * Use new BaseInvenTreeSetting unique interface * Fix Django not ready error * Added get_machines function to driver - get_machines function on driver - get_machine function on driver - initialized attribute on machine * Added error handeling for driver and machine type * Extended get_machines functionality * Export everything from plugin module * Fix spelling mistakes * Better states handeling, BaseMachineType is now used instead of Machine Model * Use uuid as pk * WIP: machine termination hook * Remove termination hook as this does not work with gunicorn * Remove machine from registry after delete * Added ClassProviderMixin * Check for slug dupplication * Added config_type to MachineSettings to define machine/driver settings * Refactor helper mixins into own file in InvenTree app * Fixed typing and added required_attributes for BaseDriver * fix: generic status import * Added first draft for machine states * Added convention for status codes * Added update_machine hook * Removed unnecessary _key suffix from machine config model * Initil draft for machine API * Refactored BaseInvenTreeSetting all_items and allValues method * Added required to InvenTreeBaseSetting and check_settings method * check if all required machine settings are defined and refactor: use getattr * Fix: comment * Fix initialize error and python 3.9 compability * Make machine states available through the global states api * Added basic PUI machine admin implementation that is still in dev * Added basic machine setting UI to PUI * Added machine detail view to PUI admin center * Fix merge issues * Fix style issues * Added machine type,machine driver,error stack tables * Fix style in machine/serializers.py * Added pui link from machine to machine type/driver drawer * Removed only partially working django admin in favor of the PUI admin center implementation * Added required field to settings item * Added machine restart function * Added restart requird badge to machine table/drawer * Added driver init function * handle error functions for machines and registry * Added driver errors * Added machine table to driver drawer * Added back button to detail drawer component * Fix auto formatable pre-commit * fix: style * Fix deepsource * Removed slug field from table, added more links between drawers, remove detail drawer blur * Added initial docs * Removed description from driver/machine type select and fixed disabled driver select if no machine type is selected * Added basic label printing implementation * Remove translated column names because they are now retrieved from the api * Added printer location setting * Save last 10 used printer machine per user and sort them in the printing dialog * Added BasePrintingOptionsSerializer for common options * Fix not printing_options are not properly casted to its internal value * Fix type * Improved machine docs * Fix docs * Added UNKNOWN status code to label printer status * Skip machine loading when running migrations * Fix testing? * Fix: tests? * Fix: tests? * Disable docs check precommit * Disable docs check precommit * First draft for tests * fix test * Add type ignore * Added API tests * Test ci? * Add more tests * Added more tests * Bump api version * Changed driver/base driver naming schema * Added more tests * Fix tests * Added setting choice with kwargs and get_machines with initialized=None * Refetch table after deleting machine * Fix test --------- Co-authored-by: Matthias Mair <code@mjmair.com>
304 lines
11 KiB
Python
Executable File
304 lines
11 KiB
Python
Executable File
"""Machine app tests."""
|
|
|
|
from typing import cast
|
|
from unittest.mock import MagicMock, Mock
|
|
|
|
from django.apps import apps
|
|
from django.test import TestCase
|
|
from django.urls import reverse
|
|
|
|
from rest_framework import serializers
|
|
|
|
from InvenTree.unit_test import InvenTreeAPITestCase
|
|
from label.models import PartLabel
|
|
from machine.machine_type import BaseDriver, BaseMachineType, MachineStatus
|
|
from machine.machine_types.label_printer import LabelPrinterBaseDriver
|
|
from machine.models import MachineConfig
|
|
from machine.registry import registry
|
|
from part.models import Part
|
|
from plugin.models import PluginConfig
|
|
from plugin.registry import registry as plg_registry
|
|
|
|
|
|
class TestMachineRegistryMixin(TestCase):
|
|
"""Machine registry test mixin to setup the registry between tests correctly."""
|
|
|
|
placeholder_uuid = '00000000-0000-0000-0000-000000000000'
|
|
|
|
def tearDown(self) -> None:
|
|
"""Clean up after testing."""
|
|
registry.machine_types = {}
|
|
registry.drivers = {}
|
|
registry.driver_instances = {}
|
|
registry.machines = {}
|
|
registry.base_drivers = []
|
|
registry.errors = []
|
|
|
|
return super().tearDown()
|
|
|
|
|
|
class TestDriverMachineInterface(TestMachineRegistryMixin, TestCase):
|
|
"""Test the machine registry."""
|
|
|
|
def setUp(self):
|
|
"""Setup some testing drivers/machines."""
|
|
|
|
class TestingMachineBaseDriver(BaseDriver):
|
|
"""Test base driver for testing machines."""
|
|
|
|
machine_type = 'testing-type'
|
|
|
|
class TestingMachineType(BaseMachineType):
|
|
"""Test machine type for testing."""
|
|
|
|
SLUG = 'testing-type'
|
|
NAME = 'Testing machine type'
|
|
DESCRIPTION = 'This is a test machine type for testing.'
|
|
|
|
base_driver = TestingMachineBaseDriver
|
|
|
|
class TestingMachineTypeStatus(MachineStatus):
|
|
"""Test machine status."""
|
|
|
|
UNKNOWN = 100, 'Unknown', 'secondary'
|
|
|
|
MACHINE_STATUS = TestingMachineTypeStatus
|
|
default_machine_status = MACHINE_STATUS.UNKNOWN
|
|
|
|
class TestingDriver(TestingMachineBaseDriver):
|
|
"""Test driver for testing machines."""
|
|
|
|
SLUG = 'test-driver'
|
|
NAME = 'Test Driver'
|
|
DESCRIPTION = 'This is a test driver for testing.'
|
|
|
|
MACHINE_SETTINGS = {
|
|
'TEST_SETTING': {'name': 'Test Setting', 'description': 'Test setting'}
|
|
}
|
|
|
|
# mock driver implementation
|
|
self.driver_mocks = {
|
|
k: Mock()
|
|
for k in [
|
|
'init_driver',
|
|
'init_machine',
|
|
'update_machine',
|
|
'restart_machine',
|
|
]
|
|
}
|
|
|
|
for key, value in self.driver_mocks.items():
|
|
setattr(TestingDriver, key, value)
|
|
|
|
self.machine1 = MachineConfig.objects.create(
|
|
name='Test Machine 1',
|
|
machine_type='testing-type',
|
|
driver='test-driver',
|
|
active=True,
|
|
)
|
|
self.machine2 = MachineConfig.objects.create(
|
|
name='Test Machine 2',
|
|
machine_type='testing-type',
|
|
driver='test-driver',
|
|
active=True,
|
|
)
|
|
self.machine3 = MachineConfig.objects.create(
|
|
name='Test Machine 3',
|
|
machine_type='testing-type',
|
|
driver='test-driver',
|
|
active=False,
|
|
)
|
|
self.machines = [self.machine1, self.machine2, self.machine3]
|
|
|
|
# init registry
|
|
registry.initialize()
|
|
|
|
# mock machine implementation
|
|
self.machine_mocks = {
|
|
m: {k: MagicMock() for k in ['update', 'restart']} for m in self.machines
|
|
}
|
|
for machine_config, mock_dict in self.machine_mocks.items():
|
|
for key, mock in mock_dict.items():
|
|
mock.side_effect = getattr(machine_config.machine, key)
|
|
setattr(machine_config.machine, key, mock)
|
|
|
|
super().setUp()
|
|
|
|
def test_machine_lifecycle(self):
|
|
"""Test the machine lifecycle."""
|
|
# test that the registry is initialized correctly
|
|
self.assertEqual(len(registry.machines), 3)
|
|
self.assertEqual(len(registry.driver_instances), 1)
|
|
|
|
# test get_machines
|
|
self.assertEqual(len(registry.get_machines()), 2)
|
|
self.assertEqual(len(registry.get_machines(initialized=None)), 3)
|
|
self.assertEqual(len(registry.get_machines(active=False, initialized=False)), 1)
|
|
self.assertEqual(len(registry.get_machines(name='Test Machine 1')), 1)
|
|
self.assertEqual(
|
|
len(registry.get_machines(name='Test Machine 1', active=False)), 0
|
|
)
|
|
self.assertEqual(
|
|
len(registry.get_machines(name='Test Machine 1', active=True)), 1
|
|
)
|
|
|
|
# test get_machines with an unknown filter
|
|
with self.assertRaisesMessage(
|
|
ValueError,
|
|
"'unknown_filter' is not a valid filter field for registry.get_machines.",
|
|
):
|
|
registry.get_machines(unknown_filter='test')
|
|
|
|
# test get_machine
|
|
self.assertEqual(registry.get_machine(self.machine1.pk), self.machine1.machine)
|
|
|
|
# test get_drivers
|
|
self.assertEqual(len(registry.get_drivers('testing-type')), 1)
|
|
self.assertEqual(registry.get_drivers('testing-type')[0].SLUG, 'test-driver')
|
|
|
|
# test that init hooks where called correctly
|
|
self.driver_mocks['init_driver'].assert_called_once()
|
|
self.assertEqual(self.driver_mocks['init_machine'].call_count, 2)
|
|
|
|
# Test machine restart hook
|
|
registry.restart_machine(self.machine1.machine)
|
|
self.driver_mocks['restart_machine'].assert_called_once_with(
|
|
self.machine1.machine
|
|
)
|
|
self.assertEqual(self.machine_mocks[self.machine1]['restart'].call_count, 1)
|
|
|
|
# Test machine update hook
|
|
self.machine1.name = 'Test Machine 1 - Updated'
|
|
self.machine1.save()
|
|
self.driver_mocks['update_machine'].assert_called_once()
|
|
self.assertEqual(self.machine_mocks[self.machine1]['update'].call_count, 1)
|
|
old_machine_state, machine = self.driver_mocks['update_machine'].call_args.args
|
|
self.assertEqual(old_machine_state['name'], 'Test Machine 1')
|
|
self.assertEqual(machine.name, 'Test Machine 1 - Updated')
|
|
self.assertEqual(self.machine1.machine, machine)
|
|
self.machine_mocks[self.machine1]['update'].reset_mock()
|
|
|
|
# get ref to machine 1
|
|
machine1: BaseMachineType = self.machine1.machine # type: ignore
|
|
self.assertIsNotNone(machine1)
|
|
|
|
# Test machine setting update hook
|
|
self.assertEqual(machine1.get_setting('TEST_SETTING', 'D'), '')
|
|
machine1.set_setting('TEST_SETTING', 'D', 'test-value')
|
|
self.assertEqual(self.machine_mocks[self.machine1]['update'].call_count, 2)
|
|
old_machine_state, machine = self.driver_mocks['update_machine'].call_args.args
|
|
self.assertEqual(old_machine_state['settings']['D', 'TEST_SETTING'], '')
|
|
self.assertEqual(machine1.get_setting('TEST_SETTING', 'D'), 'test-value')
|
|
self.assertEqual(self.machine1.machine, machine)
|
|
|
|
# Test remove machine
|
|
self.assertEqual(len(registry.get_machines()), 2)
|
|
registry.remove_machine(machine1)
|
|
self.assertEqual(len(registry.get_machines()), 1)
|
|
|
|
|
|
class TestLabelPrinterMachineType(TestMachineRegistryMixin, InvenTreeAPITestCase):
|
|
"""Test the label printer machine type."""
|
|
|
|
fixtures = ['category', 'part', 'location', 'stock']
|
|
|
|
def setUp(self):
|
|
"""Setup the label printer machine type."""
|
|
super().setUp()
|
|
|
|
class TestingLabelPrinterDriver(LabelPrinterBaseDriver):
|
|
"""Label printer driver for testing."""
|
|
|
|
SLUG = 'testing-label-printer'
|
|
NAME = 'Testing Label Printer'
|
|
DESCRIPTION = 'This is a test label printer driver for testing.'
|
|
|
|
class PrintingOptionsSerializer(
|
|
LabelPrinterBaseDriver.PrintingOptionsSerializer
|
|
):
|
|
"""Test printing options serializer."""
|
|
|
|
test_option = serializers.IntegerField()
|
|
|
|
def print_label(self, *args, **kwargs):
|
|
"""Mock print label method so that there are no errors."""
|
|
|
|
self.machine = MachineConfig.objects.create(
|
|
name='Test Label Printer',
|
|
machine_type='label-printer',
|
|
driver='testing-label-printer',
|
|
active=True,
|
|
)
|
|
|
|
registry.initialize()
|
|
driver_instance = cast(
|
|
TestingLabelPrinterDriver,
|
|
registry.get_driver_instance('testing-label-printer'),
|
|
)
|
|
|
|
self.print_label = Mock()
|
|
driver_instance.print_label = self.print_label
|
|
|
|
self.print_labels = Mock(side_effect=driver_instance.print_labels)
|
|
driver_instance.print_labels = self.print_labels
|
|
|
|
def test_print_label(self):
|
|
"""Test the print label method."""
|
|
plugin_ref = 'inventreelabelmachine'
|
|
|
|
# setup the label app
|
|
apps.get_app_config('label').create_labels() # type: ignore
|
|
plg_registry.reload_plugins()
|
|
config = cast(PluginConfig, plg_registry.get_plugin(plugin_ref).plugin_config()) # type: ignore
|
|
config.active = True
|
|
config.save()
|
|
|
|
parts = Part.objects.all()[:2]
|
|
label = cast(PartLabel, PartLabel.objects.first())
|
|
|
|
url = reverse('api-part-label-print', kwargs={'pk': label.pk})
|
|
url += f'/?plugin={plugin_ref}&part[]={parts[0].pk}&part[]={parts[1].pk}'
|
|
|
|
self.post(
|
|
url,
|
|
{
|
|
'machine': str(self.machine.pk),
|
|
'driver_options': {'copies': '1', 'test_option': '2'},
|
|
},
|
|
expected_code=200,
|
|
)
|
|
|
|
# test the print labels method call
|
|
self.print_labels.assert_called_once()
|
|
self.assertEqual(self.print_labels.call_args.args[0], self.machine.machine)
|
|
self.assertEqual(self.print_labels.call_args.args[1], label)
|
|
self.assertQuerySetEqual(
|
|
self.print_labels.call_args.args[2], parts, transform=lambda x: x
|
|
)
|
|
self.assertIn('printing_options', self.print_labels.call_args.kwargs)
|
|
self.assertEqual(
|
|
self.print_labels.call_args.kwargs['printing_options'],
|
|
{'copies': 1, 'test_option': 2},
|
|
)
|
|
|
|
# test the single print label method calls
|
|
self.assertEqual(self.print_label.call_count, 2)
|
|
self.assertEqual(self.print_label.call_args.args[0], self.machine.machine)
|
|
self.assertEqual(self.print_label.call_args.args[1], label)
|
|
self.assertEqual(self.print_label.call_args.args[2], parts[1])
|
|
self.assertIn('printing_options', self.print_labels.call_args.kwargs)
|
|
self.assertEqual(
|
|
self.print_labels.call_args.kwargs['printing_options'],
|
|
{'copies': 1, 'test_option': 2},
|
|
)
|
|
|
|
# test with non existing machine
|
|
self.post(
|
|
url,
|
|
{
|
|
'machine': self.placeholder_uuid,
|
|
'driver_options': {'copies': '1', 'test_option': '2'},
|
|
},
|
|
expected_code=400,
|
|
)
|