Skip to content

Commit

Permalink
TST: refactor server-side-handler test
Browse files Browse the repository at this point in the history
refactor server-side-handler tests to remove need for accessing protected methods
  • Loading branch information
anish-mudaraddi committed May 2, 2024
1 parent 8423e2c commit 77732a1
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 190 deletions.
4 changes: 3 additions & 1 deletion lib/custom_types/openstack_query/aliases.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
ServerSideFilters = List[ServerSideFilter]

# A type alias for a function that takes a number of filter params and returns a set of server-side filters
ServerSideFilterFunc = Callable[[FilterParams], ServerSideFilters]
ServerSideFilterFunc = Callable[
[FilterParams], Union[ServerSideFilters, ServerSideFilter]
]

# type aliases for mapping server side filter functions to a corresponding preset-property pairs
PropToServerSideFilterFunc = Dict[PropEnum, ServerSideFilterFunc]
Expand Down
45 changes: 9 additions & 36 deletions lib/openstack_query/handlers/server_side_handler.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging
from typing import Optional, Tuple, List
from typing import Optional, List
from enums.query.query_presets import QueryPresets
from enums.query.props.prop_enum import PropEnum

Expand All @@ -23,8 +23,8 @@ class ServerSideHandler(HandlerBase):
when calling openstacksdk commands when listing openstack resources
"""

def __init__(self, kwarg_mappings: ServerSideFilterMappings):
self._server_side_filter_mappings = kwarg_mappings
def __init__(self, server_side_mappings: ServerSideFilterMappings):
self._server_side_filter_mappings = server_side_mappings

def get_supported_props(self, preset: QueryPresets) -> List[PropEnum]:
"""
Expand Down Expand Up @@ -77,48 +77,21 @@ def get_filters(
filter_func = self._get_mapping(preset, prop)
if not filter_func:
return None

logger.debug(
"found server-side filter function for preset %s: prop %s pair",
preset.name,
prop.name,
)
res, reason = self._check_filter_mapping(filter_func, params)
if not res:

try:
filters = filter_func(**params)
except (KeyError, TypeError) as err:
raise QueryPresetMappingError(
"Preset Argument Error: failed to build server-side openstacksdk filters for preset:prop: "
f"'{preset.name}':'{prop.name}' "
f"reason: {reason}"
)
filters = filter_func(**params)
) from err

if not isinstance(filters, list):
return [filters]
return filters

@staticmethod
def _check_filter_mapping(
filter_func: ServerSideFilterFunc, filter_params: FilterParams
) -> Tuple[bool, str]:
"""
Method that checks if optional parameters are valid for the kwarg mapping function
:param filter_func: lambda filter func to check
:param filter_params: a dictionary of params to check if valid for filter func
"""
logger.debug(
"checking server-side filter function against provided parameters\n\t%s",
"\n\t".join([f"{key}: '{value}'" for key, value in filter_params.items()]),
)
try:
filter_func(**filter_params)
except KeyError as err:
return (
False,
f"server-side filter function expected a keyword argument: '{err.args[0]}'",
)
except TypeError as err:
# hacky way to get the arguments that are missing from TypeError error message
return (
False,
f"server-side filter function missing/unexpected positional argument: '{err.args[0]}'",
)
return True, ""
249 changes: 111 additions & 138 deletions tests/lib/openstack_query/handlers/test_server_side_handler.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from unittest.mock import MagicMock, patch
from unittest.mock import MagicMock, NonCallableMock

import pytest

Expand All @@ -8,25 +8,31 @@
from tests.lib.openstack_query.mocks.mocked_query_presets import MockQueryPresets


# pylint:disable=protected-access, unnecessary-lambda-assignment


@pytest.fixture(name="instance")
def instance_fixture():
@pytest.fixture(name="mock_server_side_mappings")
def server_side_mapping_fixture():
"""
Prepares an instance to be used in tests
Sets up a set of mock mappings between a preset-prop pair and
a set of server side filters
"""
server_side_function_mappings = {

return {
MockQueryPresets.ITEM_1: {
MockProperties.PROP_1: "item1-prop1-kwarg",
MockProperties.PROP_2: "item2-prop2-kwarg",
MockProperties.PROP_1: MagicMock(),
MockProperties.PROP_2: MagicMock(),
},
MockQueryPresets.ITEM_2: {
MockProperties.PROP_3: "item2-prop3-kwarg",
MockProperties.PROP_4: "item2-prop4-kwarg",
MockProperties.PROP_3: MagicMock(),
MockProperties.PROP_4: MagicMock(),
},
}
return ServerSideHandler(server_side_function_mappings)


@pytest.fixture(name="instance")
def instance_fixture(mock_server_side_mappings):
"""
Prepares an instance to be used in tests
"""
return ServerSideHandler(mock_server_side_mappings)


def test_check_supported_true(instance):
Expand Down Expand Up @@ -58,164 +64,131 @@ def test_get_supported_props(instance):
assert instance.get_supported_props(MockQueryPresets.ITEM_1)


def test_get_mapping_valid(instance):
@pytest.fixture(name="get_filters_runner")
def get_filters_runner_fixture(mock_server_side_mappings, instance):
"""
Tests that get_mapping method works expectedly
returns server-side filter func if Prop Enum and Preset Enum are supported
Fixture to setup and test get_filters function
"""
assert (
instance._get_mapping(MockQueryPresets.ITEM_1, MockProperties.PROP_1)
== "item1-prop1-kwarg"
)

def _get_filters_runner(preset, prop, filter_func_output, expected_out):
mock_filter_func = mock_server_side_mappings[preset][prop]
mock_filter_func.return_value = filter_func_output
mock_params = {"arg1": "val1", "arg2": "val2"}

def test_get_mapping_invalid(instance):
"""
Tests that get_mapping method works expectedly
returns None if Prop Enum and Preset Enum are not supported or don't have a mapping
"""
# when preset is not supported
assert instance._get_mapping(MockQueryPresets.ITEM_3, MockProperties.PROP_1) is None
res = instance.get_filters(
MockQueryPresets.ITEM_1, MockProperties.PROP_1, mock_params
)

# when preset found, but prop is not supported
assert instance._get_mapping(MockQueryPresets.ITEM_1, MockProperties.PROP_3) is None
mock_filter_func.assert_called_once_with(**mock_params)
assert res == expected_out

return _get_filters_runner

@patch(
"openstack_query.handlers.server_side_handler.ServerSideHandler._check_filter_mapping"
)
@patch("openstack_query.handlers.server_side_handler.ServerSideHandler._get_mapping")
def test_get_filters_valid_list(mock_get_mapping, mock_check_filter_mapping, instance):

def test_get_filters_return_single_filter(get_filters_runner):
"""
Tests that get_filters method works expectedly - filters returned are a list
returns server-side filters as a set of kwargs
- Prop Enum and Preset Enum are supported
- and filter_params are valid
Tests that get_filters method, where a single set of server-side filters
is returned appropriately for valid preset-property pair. Should return as a singleton list
"""
mock_params = {"arg1": "val1", "arg2": "val2"}
mock_filter_func = MagicMock()

mock_filters = ["filter1", "filter2"]
mock_filter_func.return_value = mock_filters

mock_check_filter_mapping.return_value = True, ""
mock_get_mapping.return_value = mock_filter_func

res = instance.get_filters(
MockQueryPresets.ITEM_1, MockProperties.PROP_1, mock_params
mock_filter_return = NonCallableMock()
get_filters_runner(
MockQueryPresets.ITEM_1,
MockProperties.PROP_1,
mock_filter_return,
[mock_filter_return],
)

mock_check_filter_mapping.assert_called_once_with(mock_filter_func, mock_params)
mock_filter_func.assert_called_once_with(**mock_params)
assert res == mock_filters


@patch(
"openstack_query.handlers.server_side_handler.ServerSideHandler._check_filter_mapping"
)
@patch("openstack_query.handlers.server_side_handler.ServerSideHandler._get_mapping")
def test_get_filters_valid_not_list(
mock_get_mapping, mock_check_filter_mapping, instance
):
def test_get_filters_return_multi_filter(get_filters_runner):
"""
Tests that get_filters method works expectedly - filters returned are a not a list
returns server-side filters as a set of kwargs
- Prop Enum and Preset Enum are supported
- and filter_params are valid
Tests that get_filters method, where a list of server-side filters returned
is returned appropriately for valid preset-property pair. Should return as is
"""
mock_params = {"arg1": "val1", "arg2": "val2"}
mock_filter_func = MagicMock()

mock_filters = "filter1"
mock_filter_func.return_value = mock_filters
mock_filter_return = [NonCallableMock()]
get_filters_runner(
MockQueryPresets.ITEM_1,
MockProperties.PROP_1,
mock_filter_return,
mock_filter_return,
)

mock_check_filter_mapping.return_value = True, ""
mock_get_mapping.return_value = mock_filter_func

res = instance.get_filters(
MockQueryPresets.ITEM_1, MockProperties.PROP_1, mock_params
def test_get_filters_no_mapping(instance):
"""
Tests get_filters method, where there is no mapping for preset-property pair given
"""
# preset has mappings, but property not compatible
assert (
instance.get_filters(
MockQueryPresets.ITEM_1, MockProperties.PROP_4, {"arg1": "val1"}
)
is None
)

mock_check_filter_mapping.assert_called_once_with(mock_filter_func, mock_params)
mock_filter_func.assert_called_once_with(**mock_params)
assert res == [mock_filters]
# preset does not have mappings
assert (
instance.get_filters(
MockQueryPresets.ITEM_3, MockProperties.PROP_4, {"arg1": "val1"}
)
is None
)


@patch(
"openstack_query.handlers.server_side_handler.ServerSideHandler._check_filter_mapping"
)
def test_get_filters_invalid(mock_check_filter_mapping, instance):
@pytest.mark.parametrize("error_type", [KeyError, TypeError])
def test_get_filters_filter_func_raises_error(
error_type, mock_server_side_mappings, instance
):
"""
Tests that get_filters method works expectedly
raises QueryPresetMappingError if:
- Prop Enum and Preset Enum are not supported
- or filter_params is not valid
Tests get_filters method, when trying to run the function to get server-side filters
returns an error indicating that required parameters were missing/malformed. In this case, we should raise a
QueryPresetMappingError
"""
mock_params = {"arg1": "val1", "arg2": "val2"}

# when preset/prop not supported
res = instance.get_filters(
MockQueryPresets.ITEM_3, MockProperties.PROP_1, mock_params
)
assert res is None

# when preset/prop are supported, but wrong filter_params
mock_check_filter_mapping.return_value = False, "some-reason"
mock_filter_func = mock_server_side_mappings[MockQueryPresets.ITEM_1][
MockProperties.PROP_1
]
mock_filter_func.side_effect = error_type
with pytest.raises(QueryPresetMappingError):
instance.get_filters(
MockQueryPresets.ITEM_1, MockProperties.PROP_1, mock_params
MockQueryPresets.ITEM_1, MockProperties.PROP_1, {"arg1": "val1"}
)


@pytest.mark.parametrize(
"valid_params_to_test",
[
# "required args only",
{"arg1": 12},
# "required and default"
{"arg1": 12, "arg2": "non-default"},
# "required and kwarg"
{"arg1": 12, "some_kwarg": "some-val"},
# "all possible"
{"arg1": 12, "arg2": "non-default", "some_kwarg": "some-val"},
# "different order"
{"arg2": "non-default", "some_kwarg": "some-val", "arg1": 12},
],
)
def test_check_filter_mapping_valid(valid_params_to_test, instance):
"""
Tests that check_filter_mapping method works expectedly - valid
returns True only if args match expected args required by filter function lambda
"""
mock_server_side_mapping = lambda arg1, arg2="some-default", **kwargs: {
"kwarg1": "val1"
}
assert instance._check_filter_mapping(
mock_server_side_mapping, filter_params=valid_params_to_test
)[0]


def test_check_filter_mapping_invalid_positional(instance):
def test_get_filters_valid_not_list(mock_server_side_mappings, instance):
"""
Tests that check_filter_mapping method works expectedly - invalid positional args
returns False if args don't match expected positional args required by filter function lambda
Tests that get_filters method, server-side filters returned appropriately for valid
preset-property pair
"""
mock_server_side_mapping = lambda arg1: {"kwarg1": "val1"}
assert not (
instance._check_filter_mapping(
mock_server_side_mapping, filter_params={"arg2": "val2"}
)[0]
mock_params = {"arg1": "val1", "arg2": "val2"}
mock_filter = NonCallableMock()
mock_filter_func = mock_server_side_mappings[MockQueryPresets.ITEM_1][
MockProperties.PROP_1
]
mock_filter_func.return_value = mock_filter

res = instance.get_filters(
MockQueryPresets.ITEM_1, MockProperties.PROP_1, mock_params
)

mock_filter_func.assert_called_once_with(**mock_params)
assert res == [mock_filter]


def test_check_filter_mapping_invalid_kwargs(instance):
def test_get_filters_valid_list(mock_server_side_mappings, instance):
"""
Tests that check_filter_mapping method works expectedly - invalid key-word args
returns False if kwargs is passed and is used that is required
Tests that get_filters method, server-side filters returned appropriately for valid
preset-property pair where filters returned by filter_func is a list
"""
mock_server_side_mapping = lambda **kwargs: {"arg": kwargs["val1"]}
assert not (
instance._check_filter_mapping(
mock_server_side_mapping, filter_params={"arg2": "val2"}
)[0]
mock_params = {"arg1": "val1", "arg2": "val2"}
mock_filter = NonCallableMock()
mock_filter_func = mock_server_side_mappings[MockQueryPresets.ITEM_1][
MockProperties.PROP_1
]
mock_filter_func.return_value = [mock_filter]

res = instance.get_filters(
MockQueryPresets.ITEM_1, MockProperties.PROP_1, mock_params
)

mock_filter_func.assert_called_once_with(**mock_params)
assert res == [mock_filter]
Loading

0 comments on commit 77732a1

Please sign in to comment.