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>
308 lines
11 KiB
Python
308 lines
11 KiB
Python
"""Machine API tests."""
|
|
|
|
import re
|
|
from typing import cast
|
|
|
|
from django.urls import reverse
|
|
|
|
from InvenTree.unit_test import InvenTreeAPITestCase
|
|
from machine import registry
|
|
from machine.machine_type import BaseDriver, BaseMachineType
|
|
from machine.machine_types import LabelPrinterBaseDriver
|
|
from machine.models import MachineConfig
|
|
from machine.tests import TestMachineRegistryMixin
|
|
from stock.models import StockLocation
|
|
|
|
|
|
class MachineAPITest(TestMachineRegistryMixin, InvenTreeAPITestCase):
|
|
"""Class for unit testing machine API endpoints."""
|
|
|
|
roles = ['admin.add', 'admin.view', 'admin.change', 'admin.delete']
|
|
|
|
def setUp(self):
|
|
"""Setup some testing drivers/machines."""
|
|
|
|
class TestingLabelPrinterDriver(LabelPrinterBaseDriver):
|
|
"""Test driver for label printing."""
|
|
|
|
SLUG = 'test-label-printer-api'
|
|
NAME = 'Test label printer'
|
|
DESCRIPTION = 'This is a test label printer driver for testing.'
|
|
|
|
MACHINE_SETTINGS = {
|
|
'TEST_SETTING': {
|
|
'name': 'Test setting',
|
|
'description': 'This is a test setting',
|
|
}
|
|
}
|
|
|
|
def restart_machine(self, machine: BaseMachineType):
|
|
"""Override restart_machine."""
|
|
machine.set_status_text('Restarting...')
|
|
|
|
def print_label(self, *args, **kwargs) -> None:
|
|
"""Override print_label."""
|
|
|
|
class TestingLabelPrinterDriverError1(LabelPrinterBaseDriver):
|
|
"""Test driver for label printing."""
|
|
|
|
SLUG = 'test-label-printer-error'
|
|
NAME = 'Test label printer error'
|
|
DESCRIPTION = 'This is a test label printer driver for testing.'
|
|
|
|
def print_label(self, *args, **kwargs) -> None:
|
|
"""Override print_label."""
|
|
|
|
class TestingLabelPrinterDriverError2(LabelPrinterBaseDriver):
|
|
"""Test driver for label printing."""
|
|
|
|
SLUG = 'test-label-printer-error'
|
|
NAME = 'Test label printer error'
|
|
DESCRIPTION = 'This is a test label printer driver for testing.'
|
|
|
|
def print_label(self, *args, **kwargs) -> None:
|
|
"""Override print_label."""
|
|
|
|
class TestingLabelPrinterDriverNotImplemented(LabelPrinterBaseDriver):
|
|
"""Test driver for label printing."""
|
|
|
|
SLUG = 'test-label-printer-not-implemented'
|
|
NAME = 'Test label printer error not implemented'
|
|
DESCRIPTION = 'This is a test label printer driver for testing.'
|
|
|
|
registry.initialize()
|
|
|
|
super().setUp()
|
|
|
|
def test_machine_type_list(self):
|
|
"""Test machine types list API endpoint."""
|
|
response = self.get(reverse('api-machine-types'))
|
|
machine_type = [t for t in response.data if t['slug'] == 'label-printer']
|
|
self.assertEqual(len(machine_type), 1)
|
|
machine_type = machine_type[0]
|
|
self.assertDictContainsSubset(
|
|
{
|
|
'slug': 'label-printer',
|
|
'name': 'Label Printer',
|
|
'description': 'Directly print labels for various items.',
|
|
'provider_plugin': None,
|
|
'is_builtin': True,
|
|
},
|
|
machine_type,
|
|
)
|
|
self.assertTrue(
|
|
machine_type['provider_file'].endswith(
|
|
'machine/machine_types/label_printer.py'
|
|
)
|
|
)
|
|
|
|
def test_machine_driver_list(self):
|
|
"""Test machine driver list API endpoint."""
|
|
response = self.get(reverse('api-machine-drivers'))
|
|
driver = [a for a in response.data if a['slug'] == 'test-label-printer-api']
|
|
self.assertEqual(len(driver), 1)
|
|
driver = driver[0]
|
|
self.assertDictContainsSubset(
|
|
{
|
|
'slug': 'test-label-printer-api',
|
|
'name': 'Test label printer',
|
|
'description': 'This is a test label printer driver for testing.',
|
|
'provider_plugin': None,
|
|
'is_builtin': True,
|
|
'machine_type': 'label-printer',
|
|
'driver_errors': [],
|
|
},
|
|
driver,
|
|
)
|
|
self.assertEqual(driver['provider_file'], __file__)
|
|
|
|
# Test driver with errors
|
|
driver_instance = cast(
|
|
BaseDriver, registry.get_driver_instance('test-label-printer-api')
|
|
)
|
|
self.assertIsNotNone(driver_instance)
|
|
driver_instance.handle_error('Test error')
|
|
|
|
response = self.get(reverse('api-machine-drivers'))
|
|
driver = [a for a in response.data if a['slug'] == 'test-label-printer-api']
|
|
self.assertEqual(len(driver), 1)
|
|
driver = driver[0]
|
|
self.assertEqual(driver['driver_errors'], ['Test error'])
|
|
|
|
def test_machine_status(self):
|
|
"""Test machine status API endpoint."""
|
|
response = self.get(reverse('api-machine-registry-status'))
|
|
errors_msgs = [e['message'] for e in response.data['registry_errors']]
|
|
|
|
required_patterns = [
|
|
r'\'<class \'.*\.TestingLabelPrinterDriverNotImplemented\'>\' did not override the required attributes: one of print_label or print_labels',
|
|
"Cannot re-register driver 'test-label-printer-error'",
|
|
]
|
|
|
|
for pattern in required_patterns:
|
|
for error in errors_msgs:
|
|
if re.match(pattern, error):
|
|
break
|
|
else:
|
|
errors_str = '\n'.join([f'- {e}' for e in errors_msgs])
|
|
self.fail(
|
|
f"""Error message matching pattern '{pattern}' not found in machine registry errors:\n{errors_str}"""
|
|
)
|
|
|
|
def test_machine_list(self):
|
|
"""Test machine list API endpoint."""
|
|
response = self.get(reverse('api-machine-list'))
|
|
self.assertEqual(len(response.data), 0)
|
|
|
|
MachineConfig.objects.create(
|
|
machine_type='label-printer',
|
|
driver='test-label-printer-api',
|
|
name='Test Machine',
|
|
active=True,
|
|
)
|
|
|
|
response = self.get(reverse('api-machine-list'))
|
|
self.assertEqual(len(response.data), 1)
|
|
self.assertDictContainsSubset(
|
|
{
|
|
'name': 'Test Machine',
|
|
'machine_type': 'label-printer',
|
|
'driver': 'test-label-printer-api',
|
|
'initialized': True,
|
|
'active': True,
|
|
'status': 101,
|
|
'status_model': 'LabelPrinterStatus',
|
|
'status_text': '',
|
|
'is_driver_available': True,
|
|
},
|
|
response.data[0],
|
|
)
|
|
|
|
def test_machine_detail(self):
|
|
"""Test machine detail API endpoint."""
|
|
self.assertFalse(len(MachineConfig.objects.all()), 0)
|
|
self.get(
|
|
reverse('api-machine-detail', kwargs={'pk': self.placeholder_uuid}),
|
|
expected_code=404,
|
|
)
|
|
|
|
machine_data = {
|
|
'machine_type': 'label-printer',
|
|
'driver': 'test-label-printer-api',
|
|
'name': 'Test Machine',
|
|
'active': True,
|
|
}
|
|
|
|
# Create a machine
|
|
response = self.post(reverse('api-machine-list'), machine_data)
|
|
self.assertDictContainsSubset(machine_data, response.data)
|
|
pk = response.data['pk']
|
|
|
|
# Retrieve the machine
|
|
response = self.get(reverse('api-machine-detail', kwargs={'pk': pk}))
|
|
self.assertDictContainsSubset(machine_data, response.data)
|
|
|
|
# Update the machine
|
|
response = self.patch(
|
|
reverse('api-machine-detail', kwargs={'pk': pk}),
|
|
{'name': 'Updated Machine'},
|
|
)
|
|
self.assertDictContainsSubset({'name': 'Updated Machine'}, response.data)
|
|
self.assertEqual(MachineConfig.objects.get(pk=pk).name, 'Updated Machine')
|
|
|
|
# Delete the machine
|
|
response = self.delete(
|
|
reverse('api-machine-detail', kwargs={'pk': pk}), expected_code=204
|
|
)
|
|
self.assertFalse(len(MachineConfig.objects.all()), 0)
|
|
|
|
# Create machine where the driver does not exist
|
|
machine_data['driver'] = 'non-existent-driver'
|
|
machine_data['name'] = 'Machine with non-existent driver'
|
|
response = self.post(reverse('api-machine-list'), machine_data)
|
|
self.assertIn(
|
|
"Driver 'non-existent-driver' not found", response.data['machine_errors']
|
|
)
|
|
self.assertFalse(response.data['initialized'])
|
|
self.assertFalse(response.data['is_driver_available'])
|
|
|
|
def test_machine_detail_settings(self):
|
|
"""Test machine detail settings API endpoint."""
|
|
machine_setting_url = reverse(
|
|
'api-machine-settings-detail',
|
|
kwargs={'pk': self.placeholder_uuid, 'config_type': 'M', 'key': 'LOCATION'},
|
|
)
|
|
|
|
# Test machine settings for non-existent machine
|
|
self.get(machine_setting_url, expected_code=404)
|
|
|
|
# Create a machine
|
|
machine = MachineConfig.objects.create(
|
|
machine_type='label-printer',
|
|
driver='test-label-printer-api',
|
|
name='Test Machine with settings',
|
|
active=True,
|
|
)
|
|
|
|
machine_setting_url = reverse(
|
|
'api-machine-settings-detail',
|
|
kwargs={'pk': machine.pk, 'config_type': 'M', 'key': 'LOCATION'},
|
|
)
|
|
driver_setting_url = reverse(
|
|
'api-machine-settings-detail',
|
|
kwargs={'pk': machine.pk, 'config_type': 'D', 'key': 'TEST_SETTING'},
|
|
)
|
|
|
|
# Get settings
|
|
response = self.get(machine_setting_url)
|
|
self.assertEqual(response.data['value'], '')
|
|
|
|
response = self.get(driver_setting_url)
|
|
self.assertEqual(response.data['value'], '')
|
|
|
|
# Update machine setting
|
|
location = StockLocation.objects.create(name='Test Location')
|
|
response = self.patch(machine_setting_url, {'value': str(location.pk)})
|
|
self.assertEqual(response.data['value'], str(location.pk))
|
|
|
|
response = self.get(machine_setting_url)
|
|
self.assertEqual(response.data['value'], str(location.pk))
|
|
|
|
# Update driver setting
|
|
response = self.patch(driver_setting_url, {'value': 'test value'})
|
|
self.assertEqual(response.data['value'], 'test value')
|
|
|
|
response = self.get(driver_setting_url)
|
|
self.assertEqual(response.data['value'], 'test value')
|
|
|
|
# Get list of all settings for a machine
|
|
settings_url = reverse('api-machine-settings', kwargs={'pk': machine.pk})
|
|
response = self.get(settings_url)
|
|
self.assertEqual(len(response.data), 2)
|
|
self.assertEqual(
|
|
[('M', 'LOCATION'), ('D', 'TEST_SETTING')],
|
|
[(s['config_type'], s['key']) for s in response.data],
|
|
)
|
|
|
|
def test_machine_restart(self):
|
|
"""Test machine restart API endpoint."""
|
|
machine = MachineConfig.objects.create(
|
|
machine_type='label-printer',
|
|
driver='test-label-printer-api',
|
|
name='Test Machine',
|
|
active=True,
|
|
)
|
|
|
|
# verify machine status before restart
|
|
response = self.get(reverse('api-machine-detail', kwargs={'pk': machine.pk}))
|
|
self.assertEqual(response.data['status_text'], '')
|
|
|
|
# restart the machine
|
|
response = self.post(
|
|
reverse('api-machine-restart', kwargs={'pk': machine.pk}), expected_code=200
|
|
)
|
|
|
|
# verify machine status after restart
|
|
response = self.get(reverse('api-machine-detail', kwargs={'pk': machine.pk}))
|
|
self.assertEqual(response.data['status_text'], 'Restarting...')
|