From 4a5e312b2465381df4c26bf1101b6bd7eec84c6f Mon Sep 17 00:00:00 2001 From: sonicaj Date: Sat, 19 Oct 2024 05:23:30 +0500 Subject: [PATCH] Add unit tests for apps plugin (#14726) --- .../plugins/apps/test_construct_schema.py | 225 ++++++++++++ .../plugins/apps/test_get_latest_version.py | 90 +++++ .../apps/test_get_list_item_from_value.py | 56 +++ .../plugins/apps/test_get_normalized_gpus.py | 184 ++++++++++ .../unit/plugins/apps/test_get_schema.py | 319 ++++++++++++++++++ .../unit/plugins/apps/test_normalize_CA.py | 102 ++++++ .../unit/plugins/apps/test_normalize_acl.py | 69 ++++ .../apps/test_normalize_and_validate.py | 123 +++++++ .../apps/test_normalize_certificate.py | 103 ++++++ .../apps/test_normalize_gpu_options.py | 129 +++++++ .../plugins/apps/test_normalize_ix_volumes.py | 111 ++++++ .../plugins/apps/test_normalize_questions.py | 72 ++++ .../plugins/apps/test_normalize_values.py | 82 +++++ 13 files changed, 1665 insertions(+) create mode 100644 src/middlewared/middlewared/pytest/unit/plugins/apps/test_construct_schema.py create mode 100644 src/middlewared/middlewared/pytest/unit/plugins/apps/test_get_latest_version.py create mode 100644 src/middlewared/middlewared/pytest/unit/plugins/apps/test_get_list_item_from_value.py create mode 100644 src/middlewared/middlewared/pytest/unit/plugins/apps/test_get_normalized_gpus.py create mode 100644 src/middlewared/middlewared/pytest/unit/plugins/apps/test_get_schema.py create mode 100644 src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_CA.py create mode 100644 src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_acl.py create mode 100644 src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_and_validate.py create mode 100644 src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_certificate.py create mode 100644 src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_gpu_options.py create mode 100644 src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_ix_volumes.py create mode 100644 src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_questions.py create mode 100644 src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_values.py diff --git a/src/middlewared/middlewared/pytest/unit/plugins/apps/test_construct_schema.py b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_construct_schema.py new file mode 100644 index 0000000000000..b6437f8464f06 --- /dev/null +++ b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_construct_schema.py @@ -0,0 +1,225 @@ +import pytest + +from middlewared.plugins.apps.schema_utils import construct_schema +from middlewared.schema import Dict, ValidationErrors + + +@pytest.mark.parametrize('data, new_values, update', [ + ( + { + 'schema': { + 'groups': [ + { + 'name': 'Actual Budget Configuration', + 'description': 'Configure Actual Budget' + }, + ], + 'questions': [ + { + 'variable': 'actual_budget', + 'label': '', + 'group': 'Actual Budget Configuration', + 'schema': { + 'type': 'dict', + 'attrs': [ + { + 'variable': 'additional_envs', + 'label': 'Additional Environment Variables', + 'description': 'Configure additional environment variables for Actual Budget.', + 'schema': { + 'type': 'list', + 'default': [], + 'items': [ + { + 'variable': 'env', + 'label': 'Environment Variable', + 'schema': { + 'type': 'dict', + 'attrs': [ + { + 'variable': 'name', + 'label': 'Name', + 'schema': { + 'type': 'string', + 'required': True + } + }, + ] + } + } + ] + } + } + ] + } + }, + ] + } + }, + { + 'actual_budget': {'additional_envs': []} + }, + False, + ), +]) +def test_construct_schema_update_False(data, new_values, update): + result = construct_schema(data, new_values, update) + assert isinstance(result['verrors'], ValidationErrors) + assert len(result['verrors'].errors) == 0 + assert isinstance(result['dict_obj'], Dict) + assert result['new_values'] == new_values + assert result['schema_name'] == 'app_create' + + +@pytest.mark.parametrize('data, new_values, update', [ + ( + { + 'schema': { + 'groups': [ + { + 'name': 'Actual Budget Configuration', + 'description': 'Configure Actual Budget' + }, + ], + 'questions': [ + { + 'variable': 'actual_budget', + 'label': '', + 'group': 'Actual Budget Configuration', + 'schema': { + 'type': 'dict', + 'attrs': [ + { + 'variable': 'additional_envs', + 'label': 'Additional Environment Variables', + 'description': 'Configure additional environment variables for Actual Budget.', + 'schema': { + 'type': 'list', + 'default': [], + 'items': [ + { + 'variable': 'env', + 'label': 'Environment Variable', + 'schema': { + 'type': 'dict', + 'attrs': [ + { + 'variable': 'name', + 'label': 'Name', + 'schema': { + 'type': 'string', + 'required': True + } + }, + ] + } + } + ] + } + } + ] + } + }, + ] + } + }, + { + 'actual_budget': {'additional_envs': []} + }, + True + ) +]) +def test_construct_schema_update_True(data, new_values, update): + result = construct_schema(data, new_values, update) + assert isinstance(result['verrors'], ValidationErrors) + assert len(result['verrors'].errors) == 0 + assert isinstance(result['dict_obj'], Dict) + assert result['new_values'] == new_values + assert result['schema_name'] == 'app_update' + + +@pytest.mark.parametrize('data, update', [ + ( + { + 'schema': { + 'groups': [ + { + 'name': 'Actual Budget Configuration', + 'description': 'Configure Actual Budget' + }, + ], + } + }, + True, + ), +]) +def test_construct_schema_KeyError(data, update): + with pytest.raises(KeyError): + construct_schema(data, {}, update) + + +@pytest.mark.parametrize('data, new_values, update', [ + ( + { + 'schema': { + 'groups': [ + { + 'name': 'Actual Budget Configuration', + 'description': 'Configure Actual Budget' + }, + ], + 'questions': [ + { + 'variable': 'actual_budget', + 'label': '', + 'group': 'Actual Budget Configuration', + 'schema': { + 'type': 'dict', + 'attrs': [ + { + 'variable': 'additional_envs', + 'label': 'Additional Environment Variables', + 'description': 'Configure additional environment variables for Actual Budget.', + 'schema': { + 'type': 'list', + 'default': [], + 'items': [ + { + 'variable': 'env', + 'label': 'Environment Variable', + 'schema': { + 'type': 'dict', + 'attrs': [ + { + 'variable': 'name', + 'label': 'Name', + 'schema': { + 'type': 'string', + 'required': True + } + }, + ] + } + } + ] + } + } + ] + } + }, + ] + } + }, + { + 'actual_budget': {'additional_envs': 'abc'} + }, + True, + ), +]) +def test_construct_schema_ValidationError(data, new_values, update): + result = construct_schema(data, new_values, update) + assert isinstance(result['verrors'], ValidationErrors) + assert len(result['verrors'].errors) > 0 + assert isinstance(result['dict_obj'], Dict) + assert result['new_values'] == new_values + assert result['schema_name'] == 'app_update' if update else 'app_create' diff --git a/src/middlewared/middlewared/pytest/unit/plugins/apps/test_get_latest_version.py b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_get_latest_version.py new file mode 100644 index 0000000000000..be6a284976549 --- /dev/null +++ b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_get_latest_version.py @@ -0,0 +1,90 @@ +import pytest + +from middlewared.plugins.apps.version_utils import get_latest_version_from_app_versions +from middlewared.service import CallError + + +@pytest.mark.parametrize('versions, should_work, expected', [ + ( + { + '1.1.9': { + 'healthy': True, + 'supported': True, + 'healthy_error': None, + 'location': '/mnt/.ix-apps/truenas_catalog/trains/community/actual-budget/1.1.9', + 'last_update': '2024-10-02 18:57:15', + 'required_features': [ + 'definitions/certificate', + 'definitions/port', + 'normalize/acl', + 'normalize/ix_volume' + ], + } + }, + True, + '1.1.9' + ), + ( + { + '1.1.9': { + 'healthy': None, + 'supported': True, + 'healthy_error': None, + 'location': '/mnt/.ix-apps/truenas_catalog/trains/community/actual-budget/1.1.9', + 'last_update': '2024-10-02 18:57:15', + 'required_features': [ + 'definitions/certificate', + 'definitions/port', + 'normalize/acl', + 'normalize/ix_volume' + ], + } + }, + False, + None + ), + ( + {}, + False, + None + ), + ( + { + '1.1.9': { + 'healthy': None, + 'supported': True, + 'healthy_error': None, + 'location': '/mnt/.ix-apps/truenas_catalog/trains/community/actual-budget/1.1.9', + 'last_update': '2024-10-02 18:57:15', + 'required_features': [ + 'definitions/certificate', + 'definitions/port', + 'normalize/acl', + 'normalize/ix_volume' + ], + }, + '2.0.1': { + 'healthy': True, + 'supported': True, + 'healthy_error': None, + 'location': '/mnt/.ix-apps/truenas_catalog/trains/community/actual-budget/2.0.1', + 'last_update': '2024-10-02 18:57:15', + 'required_features': [ + 'definitions/certificate', + 'definitions/port', + 'normalize/acl', + 'normalize/ix_volume' + ], + } + }, + True, + '2.0.1' + ), +]) +def test_get_latest_version(versions, should_work, expected): + if should_work: + version = get_latest_version_from_app_versions(versions) + assert version == expected + else: + with pytest.raises(CallError): + get_latest_version_from_app_versions(versions) diff --git a/src/middlewared/middlewared/pytest/unit/plugins/apps/test_get_list_item_from_value.py b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_get_list_item_from_value.py new file mode 100644 index 0000000000000..e5eae779f1f22 --- /dev/null +++ b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_get_list_item_from_value.py @@ -0,0 +1,56 @@ +import pytest + +from middlewared.plugins.apps.schema_utils import get_list_item_from_value +from middlewared.schema import List + + +@pytest.mark.parametrize('values, question_attr, should_work', [ + ( + ['val1', 'val2', 'val3'], + List( + items=[ + List({'question1': 'desc1'}), + List({'question2': 'desc2'}), + List({'question3': 'desc3'}) + ] + ), + True + ), + ( + None, + List( + items=[ + List({'question1': 'desc1'}), + List({'question2': 'desc2'}), + List({'question3': 'desc3'}) + ] + ), + True + ), + ( + [{'val1': 'a'}, {'val2': 'b'}, {'val3': 'c'}], + List( + items=[ + List({'question1': 'desc1'}), + List({'question2': 'desc2'}), + List({'question3': 'desc3'}) + ] + ), + True + ), + ( + ['val1', 'val1'], + List( + items=[ + List({'question1': 'desc1'}, unique=True), + ], + ), + False + ), +]) +def test_get_list_item_from_value(values, question_attr, should_work): + if should_work: + result = get_list_item_from_value(values, question_attr) + assert result is not None + else: + assert get_list_item_from_value(values, question_attr) is None diff --git a/src/middlewared/middlewared/pytest/unit/plugins/apps/test_get_normalized_gpus.py b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_get_normalized_gpus.py new file mode 100644 index 0000000000000..9043a57ec418f --- /dev/null +++ b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_get_normalized_gpus.py @@ -0,0 +1,184 @@ +import pytest + +from middlewared.plugins.apps.resources_utils import get_normalized_gpu_choices + + +@pytest.mark.parametrize('all_gpu_info, nvidia_gpus, should_work', [ + ( + [ + { + 'addr': { + 'pci_slot': '0000:00:02.0', + 'domain': '0000', + 'bus': '00', + 'slot': '02' + }, + 'description': 'Red Hat, Inc. QXL paravirtual graphic card', + 'devices': [ + {'pci_id': '8086:1237', 'pci_slot': '0000:00:00.0', 'vm_pci_slot': 'pci_0000_00_00_0'}, + {'pci_id': '8086:7000', 'pci_slot': '0000:00:01.0', 'vm_pci_slot': 'pci_0000_00_01_0'}, + {'pci_id': '8086:7010', 'pci_slot': '0000:00:01.1', 'vm_pci_slot': 'pci_0000_00_01_1'}, + {'pci_id': '8086:7113', 'pci_slot': '0000:00:01.3', 'vm_pci_slot': 'pci_0000_00_01_3'}, + ], + 'vendor': None, + 'uses_system_critical_devices': True, + 'critical_reason': 'Critical devices found: 0000:00:01.0', + 'available_to_host': True + } + ], + {}, + True + ), + ( + [ + { + 'addr': { + 'pci_slot': '0000:00:02.0', + 'domain': '0000', + 'bus': '00', + 'slot': '02' + }, + 'description': 'Red Hat, Inc. QXL paravirtual graphic card', + 'devices': [ + {'pci_id': '8086:1237', 'pci_slot': '0000:00:00.0', 'vm_pci_slot': 'pci_0000_00_00_0'}, + {'pci_id': '8086:7000', 'pci_slot': '0000:00:01.0', 'vm_pci_slot': 'pci_0000_00_01_0'}, + {'pci_id': '8086:7010', 'pci_slot': '0000:00:01.1', 'vm_pci_slot': 'pci_0000_00_01_1'}, + {'pci_id': '8086:7113', 'pci_slot': '0000:00:01.3', 'vm_pci_slot': 'pci_0000_00_01_3'}, + ], + 'vendor': 'NVIDIA', + 'uses_system_critical_devices': True, + 'critical_reason': 'Critical devices found: 0000:00:01.0', + 'available_to_host': True + } + ], + { + 'gpu_uuid': 112, + 'model': 'A6000x2', + 'description': "NVIDIA's A6000 GPU with 2 cores", + 'pci_slot': 11111, + }, + False + ), + ( + [ + { + 'addr': { + 'pci_slot': '0000:00:02.0', + 'domain': '0000', + 'bus': '00', + 'slot': '02' + }, + 'description': 'Red Hat, Inc. QXL paravirtual graphic card', + 'devices': [ + {'pci_id': '8086:1237', 'pci_slot': '0000:00:00.0', 'vm_pci_slot': 'pci_0000_00_00_0'}, + {'pci_id': '8086:7000', 'pci_slot': '0000:00:01.0', 'vm_pci_slot': 'pci_0000_00_01_0'}, + {'pci_id': '8086:7010', 'pci_slot': '0000:00:01.1', 'vm_pci_slot': 'pci_0000_00_01_1'}, + {'pci_id': '8086:7113', 'pci_slot': '0000:00:01.3', 'vm_pci_slot': 'pci_0000_00_01_3'}, + ], + 'vendor': 'NVIDIA', + 'uses_system_critical_devices': True, + 'critical_reason': 'Critical devices found: 0000:00:01.0', + 'available_to_host': True + } + ], + { + 'model': 'A6000x2', + 'description': "NVIDIA's A6000 GPU with 2 cores", + '0000:00:02.0': { + 'gpu_uuid': '112', + }, + }, + True + ), + ( + [ + { + 'addr': { + 'pci_slot': '0000:00:02.0', + 'domain': '0000', + 'bus': '00', + 'slot': '02' + }, + 'description': 'Red Hat, Inc. QXL paravirtual graphic card', + 'devices': [ + {'pci_id': '8086:1237', 'pci_slot': '0000:00:00.0', 'vm_pci_slot': 'pci_0000_00_00_0'}, + {'pci_id': '8086:7000', 'pci_slot': '0000:00:01.0', 'vm_pci_slot': 'pci_0000_00_01_0'}, + {'pci_id': '8086:7010', 'pci_slot': '0000:00:01.1', 'vm_pci_slot': 'pci_0000_00_01_1'}, + {'pci_id': '8086:7113', 'pci_slot': '0000:00:01.3', 'vm_pci_slot': 'pci_0000_00_01_3'}, + ], + 'vendor': 'NVIDIA', + 'uses_system_critical_devices': True, + 'critical_reason': 'Critical devices found: 0000:00:01.0', + 'available_to_host': True + } + ], + { + 'model': 'A6000x2', + 'description': "NVIDIA's A6000 GPU with 2 cores", + '0000:00:02.0': {}, + }, + False + ), + ( + [ + { + 'addr': { + 'pci_slot': '0000:00:02.0', + 'domain': '0000', + 'bus': '00', + 'slot': '02' + }, + 'description': 'Red Hat, Inc. QXL paravirtual graphic card', + 'devices': [ + {'pci_id': '8086:1237', 'pci_slot': '0000:00:00.0', 'vm_pci_slot': 'pci_0000_00_00_0'}, + {'pci_id': '8086:7000', 'pci_slot': '0000:00:01.0', 'vm_pci_slot': 'pci_0000_00_01_0'}, + {'pci_id': '8086:7010', 'pci_slot': '0000:00:01.1', 'vm_pci_slot': 'pci_0000_00_01_1'}, + {'pci_id': '8086:7113', 'pci_slot': '0000:00:01.3', 'vm_pci_slot': 'pci_0000_00_01_3'}, + ], + 'vendor': 'NVIDIA', + 'uses_system_critical_devices': True, + 'critical_reason': 'Critical devices found: 0000:00:01.0', + 'available_to_host': True + } + ], + { + 'model': 'A6000x2', + 'description': "NVIDIA's A6000 GPU with 2 cores", + '0000:00:02.0': { + 'gpu_uuid': '1125?as' + }, + }, + False + ), + ( + [ + { + 'addr': { + 'pci_slot': '0000:00:02.0', + 'domain': '0000', + 'bus': '00', + 'slot': '02' + }, + 'description': 'Red Hat, Inc. QXL paravirtual graphic card', + 'devices': [ + {'pci_id': '8086:1237', 'pci_slot': '0000:00:00.0', 'vm_pci_slot': 'pci_0000_00_00_0'}, + {'pci_id': '8086:7000', 'pci_slot': '0000:00:01.0', 'vm_pci_slot': 'pci_0000_00_01_0'}, + {'pci_id': '8086:7010', 'pci_slot': '0000:00:01.1', 'vm_pci_slot': 'pci_0000_00_01_1'}, + {'pci_id': '8086:7113', 'pci_slot': '0000:00:01.3', 'vm_pci_slot': 'pci_0000_00_01_3'}, + ], + 'vendor': None, + 'uses_system_critical_devices': True, + 'critical_reason': 'Critical devices found: 0000:00:01.0', + 'available_to_host': False + } + ], + {}, + False + ), +]) +def test_get_normalized_gpus(all_gpu_info, nvidia_gpus, should_work): + result = get_normalized_gpu_choices(all_gpu_info, nvidia_gpus) + if should_work: + assert result[0]['error'] is None + else: + assert result[0]['error'] is not None diff --git a/src/middlewared/middlewared/pytest/unit/plugins/apps/test_get_schema.py b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_get_schema.py new file mode 100644 index 0000000000000..70944cbb3f155 --- /dev/null +++ b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_get_schema.py @@ -0,0 +1,319 @@ +import pytest + +from middlewared.plugins.apps.schema_utils import get_schema, SCHEMA_MAPPING + + +@pytest.mark.parametrize('data', [ + { + 'variable': 'actual_budget', + 'label': '', + 'group': 'Actual Budget Configuration', + 'schema': { + 'type': 'dict', + 'attrs': [ + { + 'variable': 'additional_envs', + 'label': 'Additional Environment Variables', + 'description': 'Configure additional environment variables for Actual Budget.', + 'schema': { + 'type': 'list', + 'default': [], + 'items': [ + { + 'variable': 'env', + 'label': 'Environment Variable', + 'schema': { + 'type': 'dict', + 'attrs': [ + { + 'variable': 'name', + 'label': 'Name', + 'schema': { + 'type': 'string', + 'required': True + } + }, + ] + } + } + ] + } + } + ] + } + }, +]) +def test_get_schema_success(data): + result = get_schema(data, False) + assert result is not None + valid_types = tuple(v for v in SCHEMA_MAPPING.values() if isinstance(v, type)) + assert isinstance(result[0], valid_types) + + +@pytest.mark.parametrize('data', [ + { + 'variable': 'actual_budget', + 'label': '', + 'group': 'Actual Budget Configuration', + }, + { + 'variable': 'actual_budget', + 'label': '', + 'group': 'Actual Budget Configuration', + 'schema': { + 'type': 'dict', + 'attrs': [ + { + 'variable': 'additional_envs', + 'label': 'Additional Environment Variables', + 'description': 'Configure additional environment variables for Actual Budget.', + 'schema': { + 'type': 'custom', + 'default': [], + 'items': [] + } + } + ] + } + } +]) +def test_get_schema_KeyError(data): + with pytest.raises(KeyError): + get_schema(data, False) + + +@pytest.mark.parametrize('data, existing', [ + ( + { + 'variable': 'actual_budget', + 'label': '', + 'group': 'Actual Budget Configuration', + 'schema': { + 'type': 'dict', + 'immutable': True, + 'attrs': [ + { + 'variable': 'additional_envs', + 'label': 'Additional Environment Variables', + 'description': 'Configure additional environment variables for Actual Budget.', + 'schema': { + 'type': 'list', + 'default': [], + 'immutable': True, + 'items': [ + { + 'variable': 'env', + 'label': 'Environment Variable', + 'schema': { + 'type': 'dict', + 'attrs': [ + { + 'variable': 'name', + 'label': 'Name', + 'schema': { + 'type': 'string', + 'required': True + } + }, + ] + } + } + ] + } + } + ] + } + }, + { + 'actual_budget': {'env': {'name': 'EXAMPLE_ENV', 'value': 'example_value'}} + } + ), +]) +def test_get_schema_existing(data, existing): + result = get_schema(data, False, existing) + assert result is not None + valid_types = tuple(v for v in SCHEMA_MAPPING.values() if isinstance(v, type)) + assert isinstance(result[0], valid_types) + + +@pytest.mark.parametrize('data', [ + { + 'variable': 'actual_budget', + 'label': '', + 'group': 'Actual Budget Configuration', + 'schema': { + 'type': 'dict', + 'attrs': [ + { + 'variable': 'additional_envs', + 'label': 'Additional Environment Variables', + 'description': 'Configure additional environment variables for Actual Budget.', + 'schema': { + 'type': 'list', + 'items': [ + { + 'variable': 'env', + 'label': 'Environment Variable', + 'schema': { + 'type': 'dict', + 'attrs': [ + { + 'variable': 'name', + 'label': 'Name', + 'schema': { + 'type': 'string', + 'enum': [], + 'required': True + } + }, + { + 'variable': 'network', + 'label': '', + 'group': 'Network Configuration', + 'schema': { + 'type': 'dict', + 'attrs': [ + { + 'variable': 'web_port', + 'label': 'WebUI Port', + 'description': 'The port for Actual Budget WebUI', + 'schema': { + 'type': 'int', + 'default': 31012, + 'required': True, + '$ref': [ + 'definitions/port' + ], + 'min': 1, + 'max': 65535 + } + } + ] + } + } + ] + } + } + ] + } + } + ] + } + }, +]) +def test_get_schema_port_min_max(data): + result = get_schema(data, False) + assert result is not None + valid_types = tuple(v for v in SCHEMA_MAPPING.values() if isinstance(v, type)) + assert isinstance(result[0], valid_types) + + +@pytest.mark.parametrize('data', [ + { + 'variable': 'actual_budget', + 'label': '', + 'group': 'Actual Budget Configuration', + 'schema': { + 'type': 'dict', + 'attrs': [ + { + 'variable': 'additional_envs', + 'label': 'Additional Environment Variables', + 'description': 'Configure additional environment variables for Actual Budget.', + 'schema': { + 'type': 'list', + 'default': [], + 'items': [ + { + 'variable': 'env', + 'label': 'Environment Variable', + 'schema': { + 'type': 'dict', + 'attrs': [ + { + 'variable': 'name', + 'label': 'Name', + 'schema': { + 'type': 'string', + 'valid_chars': ('char1'), + 'required': True + } + } + ] + } + } + ] + } + } + ] + } + }, +]) +def test_get_schema_valid_chars(data): + result = get_schema(data, False) + assert result is not None + valid_types = tuple(v for v in SCHEMA_MAPPING.values() if isinstance(v, type)) + assert isinstance(result[0], valid_types) + + +@pytest.mark.parametrize('data', [ + { + 'variable': 'actual_budget', + 'label': '', + 'group': 'Actual Budget Configuration', + 'schema': { + 'type': 'dict', + 'subquestions': [ + { + 'variable': 'sub_question_1', + 'schema': { + 'type': 'dict', + 'attrs': [] + } + }, + { + 'variable': 'sub_question_2', + 'schema': { + 'type': 'dict', + 'attrs': [] + } + } + ], + 'attrs': [ + { + 'variable': 'additional_envs', + 'label': 'Additional Environment Variables', + 'description': 'Configure additional environment variables for Actual Budget.', + 'schema': { + 'type': 'list', + 'default': [], + 'items': [ + { + 'variable': 'env', + 'label': 'Environment Variable', + 'schema': { + 'type': 'dict', + 'attrs': [ + { + 'variable': 'name', + 'label': 'Name', + 'schema': { + 'type': 'string', + 'required': True + } + } + ] + } + } + ] + } + } + ] + } + }, +]) +def test_get_schema_subquestions(data): + result = get_schema(data, False) + assert result is not None + valid_types = tuple(v for v in SCHEMA_MAPPING.values() if isinstance(v, type)) + assert isinstance(result[0], valid_types) diff --git a/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_CA.py b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_CA.py new file mode 100644 index 0000000000000..f6180a971c83c --- /dev/null +++ b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_CA.py @@ -0,0 +1,102 @@ +import textwrap + +import pytest + +from middlewared.plugins.apps.schema_normalization import AppSchemaService +from middlewared.pytest.unit.middleware import Middleware +from middlewared.schema import Int + + +@pytest.mark.parametrize('cert, value, should_work', [ + ( + textwrap.dedent( + ''' + -----BEGIN CERTIFICATE----- + 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 + -----END CERTIFICATE----- + ''', + ), + 12, + True + ), + ( + textwrap.dedent( + ''' + -----BEGIN CERTIFICATE----- + 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 + -----END CERTIFICATE----- + ''', + ), + None, + False + ), +], ids=['valid_ca', 'invalid_ca']) +@pytest.mark.asyncio +async def test_normalize_CA(cert, value, should_work): + middleware = Middleware() + app_schema_obj = AppSchemaService(middleware) + middleware['certificateauthority.get_instance'] = lambda *args: cert + complete_config = {'ix_certificate_authorities': {value: cert}} + result = await app_schema_obj.normalize_certificate_authorities(Int('CA'), value, complete_config, '') + if should_work: + assert result is not None + else: + assert result is None diff --git a/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_acl.py b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_acl.py new file mode 100644 index 0000000000000..516f30af084a4 --- /dev/null +++ b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_acl.py @@ -0,0 +1,69 @@ +import pytest + +from middlewared.plugins.apps.schema_normalization import AppSchemaService +from middlewared.pytest.unit.middleware import Middleware +from middlewared.schema import Dict + + +@pytest.mark.parametrize('attr, value, context', [ + ( + Dict(), + { + 'entries': [{'type': 'ALLOW', 'permissions': 'read'}], + 'path': '/mnt/data' + }, + {'actions': []}, + ), + ( + Dict(), + { + 'entries': [{'type': 'ALLOW', 'permissions': 'write'}], + 'path': '/mnt/data' + }, + { + 'actions': [ + { + 'method': 'apply_acls', + 'args': [ + { + 'path': { + 'entries': [ + { + 'type': 'ALLOW', + 'permissions': 'read' + } + ] + } + } + ] + } + ] + }, + ), + ( + Dict(), + { + 'entries': [], + 'path': '' + }, + {'actions': []}, + ), + ( + Dict(), + { + 'entries': [{'type': 'ALLOW', 'permissions': 'rw'}], + 'path': '' + }, + {'actions': []}, + ), +]) +@pytest.mark.asyncio +async def test_normalize_acl(attr, value, context): + middleware = Middleware() + app_schema_obj = AppSchemaService(middleware) + result = await app_schema_obj.normalize_acl(attr, value, '', context) + if all(value[k] for k in ('entries', 'path')): + assert len(context['actions']) > 0 + else: + assert len(context['actions']) == 0 + assert result == value diff --git a/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_and_validate.py b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_and_validate.py new file mode 100644 index 0000000000000..d3c92932e8c1a --- /dev/null +++ b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_and_validate.py @@ -0,0 +1,123 @@ +import pytest + +from middlewared.plugins.apps.schema_normalization import AppSchemaService +from middlewared.pytest.unit.middleware import Middleware +from middlewared.schema import Dict + + +@pytest.mark.parametrize('app_detail, values, expected', [ + ( + { + 'healthy': True, + 'supported': True, + 'healthy_error': None, + 'location': '/mnt/.ix-apps/truenas_catalog/trains/community/actual-budget/1.1.11', + 'last_update': '2024-10-13 21:17:53', + 'required_features': [], + 'human_version': '24.10.1_1.1.11', + 'version': '1.1.11', + 'app_metadata': { + 'app_version': '24.10.1', + 'capabilities': [], + 'categories': ['media'], + 'description': 'Actual Budget is a super fast and privacy-focused app for managing your finances.', + 'home': 'https://actualbudget.org', + 'host_mounts': [], + 'lib_version': '1.1.2', + 'lib_version_hash': '3bf14311f7547731c94dbd4059f7aca95272210409631acbc5603a06223921e4', + 'name': 'actual-budget', + 'run_as_context': [], + 'sources': [], + 'title': 'Actual Budget', + 'train': 'community', + 'version': '1.1.11' + }, + 'schema': { + 'groups': [ + { + 'name': 'Actual Budget Configuration', + 'description': 'Configure Actual Budget' + } + ], + 'questions': [ + { + 'variable': 'actual_budget', + 'label': '', + 'group': 'Actual Budget Configuration', + 'schema': { + 'type': 'dict', + 'attrs': [ + { + 'variable': 'additional_envs', + 'label': 'Additional Environment Variables', + 'description': 'Configure additional environment variables for Actual Budget.', + 'schema': { + 'type': 'list', + 'default': [], + 'items': [] + } + } + ] + } + } + ], + 'readme': '', + 'changelog': None, + 'values': { + 'actual_budget': { + 'additional_envs': [] + }, + 'run_as': { + 'user': 568, + 'group': 568 + }, + 'network': { + 'web_port': 31012, + 'host_network': False + }, + 'storage': { + 'data': { + 'type': 'ix_volume', + 'ix_volume_config': { + 'acl_enable': False, + 'dataset_name': 'data' + } + }, + 'additional_storage': [] + }, + 'resources': { + 'limits': { + 'cpus': 2, + 'memory': 4096 + } + } + } + } + }, + {}, + { + 'ix_certificates': {}, + 'ix_certificate_authorities': {}, + 'ix_volumes': {}, + 'ix_context': {} + } + ) +]) +@pytest.mark.asyncio +async def test_normalize_and_validate(app_detail, values, expected): + middleware = Middleware() + app_schema_obj = AppSchemaService(middleware) + dict_obj = Dict( + 'actual-budget', + Dict('run_as'), + Dict('network'), + Dict('resources') + ) + middleware['app.schema.validate_values'] = lambda *args: dict_obj + new_values = await app_schema_obj.normalize_and_validate_values( + item_details=app_detail, + values=values, + update=False, + app_dir='/path/to/app' + ) + assert new_values == expected diff --git a/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_certificate.py b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_certificate.py new file mode 100644 index 0000000000000..bce690ed9c6fd --- /dev/null +++ b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_certificate.py @@ -0,0 +1,103 @@ +import textwrap + +import pytest + +from middlewared.plugins.apps.schema_normalization import AppSchemaService +from middlewared.pytest.unit.middleware import Middleware +from middlewared.schema import Int + + +@pytest.mark.parametrize('cert, value, should_work', [ + ( + textwrap.dedent( + ''' + -----BEGIN CERTIFICATE----- + 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 + -----END CERTIFICATE----- + ''' + ), + 12, + True + ), + ( + textwrap.dedent( + ''' + -----BEGIN CERTIFICATE----- + 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 + -----END CERTIFICATE----- + ''' + ), + None, + False + ), + +], ids=['valid_cert', 'invalid_cert']) +@pytest.mark.asyncio +async def test_normalize_certificate(cert, value, should_work): + middleware = Middleware() + app_schema_obj = AppSchemaService(middleware) + middleware['certificate.get_instance'] = lambda *args: cert + complete_config = {'ix_certificates': {value: cert}} + result = await app_schema_obj.normalize_certificate(Int('Cert'), value, complete_config, '') + if should_work: + assert result is not None + else: + assert result is None diff --git a/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_gpu_options.py b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_gpu_options.py new file mode 100644 index 0000000000000..8b448097d36d6 --- /dev/null +++ b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_gpu_options.py @@ -0,0 +1,129 @@ + +import pytest + +from middlewared.plugins.apps.schema_normalization import AppSchemaService +from middlewared.pytest.unit.middleware import Middleware + + +@pytest.mark.parametrize('gpu_list, value, expected', [ + ( + [ + { + 'pci_slot': '0000:00:02.0', + 'addr': { + 'domain': '0000', + 'bus': '00', + 'slot': '02' + }, + 'description': 'Red Hat, Inc. QXL paravirtual graphic card', + 'devices': [ + {'pci_id': '8086:1237', 'pci_slot': '0000:00:00.0', 'vm_pci_slot': 'pci_0000_00_00_0'}, + {'pci_id': '8086:7000', 'pci_slot': '0000:00:01.0', 'vm_pci_slot': 'pci_0000_00_01_0'}, + + ], + 'vendor': 'NVIDIA', + 'uses_system_critical_devices': True, + 'critical_reason': 'Critical devices found: 0000:00:01.0', + 'available_to_host': True, + 'error': '' + } + ], + { + 'use_all_gpus': True, + 'nvidia_gpu_selection': { + '0000:01:00.0': 'NVIDIA GPU 1', + '0000:02:00.0': 'NVIDIA GPU 2' + } + }, + { + 'use_all_gpus': False, + 'nvidia_gpu_selection': {} + } + + ), + ( + [ + { + 'pci_slot': '0000:00:02.0', + 'addr': { + 'domain': '0000', + 'bus': '00', + 'slot': '02' + }, + 'description': 'Intel Integrated Graphics', + 'devices': [ + {'pci_id': '8086:1234', 'pci_slot': '0000:00:00.0', 'vm_pci_slot': 'pci_0000_00_00_0'}, + ], + 'vendor': 'Intel', # Non-NVIDIA vendor + 'uses_system_critical_devices': True, + 'critical_reason': 'No critical devices.', + 'available_to_host': True, + 'error': '' + } + ], + { + 'use_all_gpus': True, + 'nvidia_gpu_selection': {} + }, + { + 'use_all_gpus': True, + 'nvidia_gpu_selection': {} + } + + ), + ( + [ + { + 'pci_slot': '0000:00:02.0', + 'addr': { + 'domain': '0000', + 'bus': '00', + 'slot': '02' + }, + 'description': 'NVIDIA GeForce RTX 3080', + 'devices': [ + {'pci_id': '10de:2206', 'pci_slot': '0000:01:00.0', 'vm_pci_slot': 'pci_0000_01_00_0'}, + ], + 'vendor': 'NVIDIA', # This GPU is NVIDIA + 'uses_system_critical_devices': True, + 'critical_reason': 'No critical devices.', + 'available_to_host': True, + 'error': '' + }, + { + 'pci_slot': '0000:00:03.0', + 'addr': { + 'domain': '0000', + 'bus': '00', + 'slot': '03' + }, + 'description': 'AMD Radeon RX 6800', + 'devices': [ + {'pci_id': '1002:73bf', 'pci_slot': '0000:01:01.0', 'vm_pci_slot': 'pci_0000_01_01_0'}, + ], + 'vendor': 'AMD', # Non-NVIDIA vendor + 'uses_system_critical_devices': False, + 'critical_reason': 'No critical devices.', + 'available_to_host': True, + 'error': '' + } + ], + { + 'use_all_gpus': True, + 'nvidia_gpu_selection': {} + }, + { + 'use_all_gpus': True, + 'nvidia_gpu_selection': {} + } + + ) +]) +@pytest.mark.asyncio +async def test_normalize_gpu_option(gpu_list, value, expected): + middleware = Middleware() + app_schema_obj = AppSchemaService(middleware) + middleware['app.gpu_choices_internal'] = lambda *args: gpu_list + result = await app_schema_obj.normalize_gpu_configuration('', value, '', '') + assert result is not None + assert result == expected diff --git a/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_ix_volumes.py b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_ix_volumes.py new file mode 100644 index 0000000000000..6c6e8afc1c6e3 --- /dev/null +++ b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_ix_volumes.py @@ -0,0 +1,111 @@ +import pytest + +from middlewared.plugins.apps.schema_normalization import AppSchemaService +from middlewared.pytest.unit.middleware import Middleware +from middlewared.schema import Dict + + +@pytest.mark.parametrize('attr, value, complete_config, context', [ + ( + Dict(), + { + 'dataset_name': 'volume_1', + 'properties': {'prop_key': 'prop_value'}, + 'acl_entries': { + 'entries': [{'type': 'ALLOW', 'permissions': 'write'}], + 'path': '/mnt/data' + } + }, + { + 'ix_volumes': { + 'volume_1': '' + } + }, + {'actions': [], 'app': {'name': 'test_app'}} + ), + ( + Dict(), + { + 'dataset_name': 'volume_1', + 'properties': {'prop_key': 'prop_value'}, + 'acl_entries': { + 'entries': [], + 'path': '' + } + }, + { + 'ix_volumes': { + 'volume_1': '' + } + }, + {'actions': [], 'app': {'name': 'test_app'}} + ), + ( + Dict(), + { + 'dataset_name': 'volume_1', + 'properties': {'prop_key': 'prop_value'}, + 'acl_entries': { + 'entries': [], + 'path': '' + } + + }, + { + 'ix_volumes': { + 'volume_1': '' + } + }, + { + 'actions': [ + { + 'method': 'update_volumes', + 'args': [[ + { + 'name': 'volume_1' + } + ]] + } + ], + 'app': {'name': 'test_app'} + } + ), + ( + Dict(), + { + 'dataset_name': 'volume_1', + 'properties': {'prop_key': 'prop_value'}, + 'acl_entries': { + 'entries': [], + 'path': '' + } + + }, + { + 'ix_volumes': { + 'volume_1': '' + } + }, + { + 'actions': [ + { + 'method': 'update_volumes', + 'args': [[ + { + 'name': 'volume_2' + } + ]] + } + ], + 'app': {'name': 'test_app'} + } + ), +]) +@pytest.mark.asyncio +async def test_normalize_ix_volumes(attr, value, complete_config, context): + middleware = Middleware() + app_schema_obj = AppSchemaService(middleware) + result = await app_schema_obj.normalize_ix_volume(attr, value, complete_config, context) + assert len(context['actions']) > 0 + assert value['dataset_name'] in [v['name'] for v in context['actions'][0]['args'][-1]] + assert result == value diff --git a/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_questions.py b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_questions.py new file mode 100644 index 0000000000000..92393c8153fef --- /dev/null +++ b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_questions.py @@ -0,0 +1,72 @@ +import pytest + +from middlewared.plugins.apps.schema_normalization import AppSchemaService, REF_MAPPING +from middlewared.pytest.unit.middleware import Middleware +from middlewared.schema import Dict, List + + +@pytest.mark.parametrize('question_attr, ref, value, update', [ + ( + Dict(), + 'definitions/certificate', + {'attr1': 'some_value'}, + False + ), + ( + Dict(), + 'normalize/acl', + {'attr1': 'some_value'}, + False + ), + ( + Dict(), + 'normalize/acl', + {'attr1': 'some_value'}, + True + ), + ( + Dict(), + 'definitions/certificate', + None, + False + ) +]) +@pytest.mark.asyncio +async def test_normalize_question(question_attr, ref, value, update): + middleware = Middleware() + app_schema_obj = AppSchemaService(middleware) + middleware[f'app.schema.normalize_{REF_MAPPING[ref]}'] = lambda *args: value + question_attr.ref = [ref] + result = await app_schema_obj.normalize_question(question_attr, value, update, '', '') + assert result == value + + +@pytest.mark.parametrize('question_attr, ref, value, update', [ + ( + List( + items=[ + Dict('question1', additional_attrs=True), + Dict('question2', additional_attrs=True), + Dict('question3', additional_attrs=True), + ] + ), + 'definitions/certificate', + [ + {'question1': 'val1'}, + {'question2': 'val2'}, + {'question3': 'val3'} + ], + False + ), +]) +@pytest.mark.asyncio +async def test_normalize_question_List(question_attr, ref, value, update): + middleware = Middleware() + app_schema_obj = AppSchemaService(middleware) + middleware[f'app.schema.normalize_{REF_MAPPING[ref]}'] = lambda *args: value + for attr in question_attr.items: + attr.ref = [ref] + question_attr.ref = [ref] + + result = await app_schema_obj.normalize_question(question_attr, value, update, '', '') + assert result == value diff --git a/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_values.py b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_values.py new file mode 100644 index 0000000000000..f24c11e443760 --- /dev/null +++ b/src/middlewared/middlewared/pytest/unit/plugins/apps/test_normalize_values.py @@ -0,0 +1,82 @@ +import pytest + +from middlewared.plugins.apps.schema_normalization import AppSchemaService +from middlewared.pytest.unit.middleware import Middleware +from middlewared.schema import Dict + + +@pytest.mark.parametrize('dict_obj, values, update, context, expected', [ + ( + Dict( + 'actual-budget', + Dict('run_as'), + Dict('network'), + Dict('resources') + ), + { + 'ix_certificates': {}, + 'ix_certificate_authorities': {}, + 'ix_volumes': {}, + 'ix_context': {} + }, + False, + {'app': {'name': 'app', 'path': '/path/to/app'}, 'actions': []}, + ( + { + 'ix_certificates': {}, + 'ix_certificate_authorities': {}, + 'ix_volumes': {}, + 'ix_context': {} + }, + { + 'app': { + 'name': 'app', + 'path': '/path/to/app' + }, + 'actions': [] + } + ) + ), + ( + Dict( + 'actual-budget', + Dict('run_as'), + Dict('network'), + Dict('resources') + ), + { + 'ix_certificates': {}, + 'ix_certificate_authorities': {}, + 'ix_volumes': {}, + 'ix_context': {} + }, + True, + {'app': {'name': 'app', 'path': '/path/to/app'}, 'actions': []}, + ( + { + 'ix_certificates': {}, + 'ix_certificate_authorities': {}, + 'ix_volumes': {}, + 'ix_context': {} + }, + { + 'app': { + 'name': 'app', + 'path': '/path/to/app' + }, + 'actions': [] + } + ) + ), +]) +@pytest.mark.asyncio +async def test_normalize_values(dict_obj, values, update, context, expected): + middleware = Middleware() + app_schema_obj = AppSchemaService(middleware) + result = await app_schema_obj.normalize_values( + dict_obj, + values, + update, + context + ) + assert result == expected