From aaba6250c0f22e8ae5f45cddc3b4bdf39aaf196d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20=C3=81lvaro?= Date: Wed, 8 May 2024 11:14:55 +0200 Subject: [PATCH] feat(macdefaults): Add basic support for array-add and dict-add --- salt/modules/macdefaults.py | 89 +++-- salt/states/macdefaults.py | 76 ++++- .../pytests/unit/modules/test_macdefaults.py | 63 ++-- tests/pytests/unit/states/test_macdefaults.py | 317 +++++++++++++++++- 4 files changed, 437 insertions(+), 108 deletions(-) diff --git a/salt/modules/macdefaults.py b/salt/modules/macdefaults.py index 5df22a51734..4a457e1beb6 100644 --- a/salt/modules/macdefaults.py +++ b/salt/modules/macdefaults.py @@ -1,11 +1,11 @@ """ -Set defaults on macOS. +Set defaults settings on macOS. This module uses defaults cli under the hood to read and write defaults on macOS. -So the module is limited to the capabilities of the defaults command. +Thus, the module is limited to the capabilities of the defaults command. -Read macOS defaults help page for more information on the defaults command. +Read macOS defaults help page for more information on defaults command. """ @@ -33,11 +33,11 @@ def __virtual__(): def write(domain, key, value, vtype=None, user=None, type=None): """ - Write a default to the system. + Write a default to the system Limitations: - - There is no multi-level support for arrays and dictionaries. - - Internal values types for arrays and dictionaries cannot be specified. + - There is no multi-level support for arrays and dictionaries + - Internal values types for arrays and dictionaries cannot be specified CLI Example: @@ -45,28 +45,28 @@ def write(domain, key, value, vtype=None, user=None, type=None): salt '*' macdefaults.write com.apple.CrashReporter DialogType Server - salt '*' macdefaults.write NSGlobalDomain ApplePersistence True type=bool + salt '*' macdefaults.write NSGlobalDomain ApplePersistence True vtype=bool domain - The name of the domain to write to. + The name of the domain to write to key - The key of the given domain to write to. + The key of the given domain to write to value - The value to write to the given key. + The value to write to the given key vtype The type of value to be written, valid types are string, data, int[eger], float, bool[ean], date, array, array-add, dict, dict-add type - Deprecated! Use vtype instead. - type collides with Python's built-in type() function. - This parameter will be removed in 3009. + Deprecated! Use vtype instead + type collides with Python's built-in type() function + This parameter will be removed in 3009 user - The user to write the defaults to. + The user to write the defaults to """ if type is not None: @@ -95,7 +95,7 @@ def write(domain, key, value, vtype=None, user=None, type=None): elif not isinstance(value, list): raise ValueError("Value must be a list, dict, int, float, bool, or string") - # Quote values that are not integers or floats + # Quote values that are neither integers nor floats value = map(lambda v: str(v) if isinstance(v, (int, float)) else f'"{v}"', value) cmd = f'write "{domain}" "{key}" -{vtype} {" ".join(value)}' @@ -169,25 +169,25 @@ def delete(domain, key, user=None): def read_type(domain, key, user=None): """ - Read the type of the given type. - If the given key is not found, then return None. + Read a default type from the system + If the key is not found, None is returned. CLI Example: .. code-block:: bash - salt '*' macdefaults.read-type com.apple.CrashReporter DialogType + salt '*' macdefaults.read_type com.apple.CrashReporter DialogType salt '*' macdefaults.read_type NSGlobalDomain ApplePersistence domain - The name of the domain to read from. + The name of the domain to read from key - The key of the given domain to read the type of. + The key of the given domain to read the type of user - The user to read the defaults as. + The user to read the defaults as """ cmd = f'read-type "{domain}" "{key}"' @@ -203,23 +203,13 @@ def read_type(domain, key, user=None): def _default_to_python(value, vtype=None): """ - Cast a value returned by defaults in vytpe to Python type. - - CLI Example: - - .. code-block:: bash - - salt '*' macdefaults.cast_value_to_type "1" int - - salt '*' macdefaults.cast_value_to_type "1.0" float - - salt '*' macdefaults.cast_value_to_type "TRUE" bool + Cast the value returned by the defaults command in vytpe to Python type value - The value to cast. + The value to cast vtype - The type to cast the value to. + The type to cast the value to """ if vtype in ["integer", "int"]: @@ -238,10 +228,10 @@ def _default_to_python(value, vtype=None): def _parse_defaults_array(value): """ Parse an array from a string returned by `defaults read` - and returns the array content as a list. + and returns the array content as a list value - A multiline string with the array content, including the surrounding parenthesis. + A multiline string with the array content, including the surrounding parenthesis """ lines = value.splitlines() @@ -268,12 +258,11 @@ def _parse_defaults_array(value): def _parse_defaults_dict(value): """ Parse a dictionary from a string returned by `defaults read` + and returns the dictionary content as a Python dictionary value (str): - A multiline string with the dictionary content, including the surrounding curly braces. + A multiline string with the dictionary content, including the surrounding curly braces - Returns: - dict: The dictionary content as a Python dictionary. """ lines = value.splitlines() if not re.match(r"\s*\{", lines[0]) or not re.match(r"\s*\}", lines[-1]): @@ -294,10 +283,10 @@ def _parse_defaults_dict(value): def _convert_to_number_if_possible(value): """ - Convert a string to a number if possible. + Convert a string to a number if possible value - The string to convert. + The string to convert """ try: @@ -311,15 +300,15 @@ def _convert_to_number_if_possible(value): def _convert_to_defaults_boolean(value): """ - Convert a boolean to a string that can be used with the defaults command. + Convert a boolean to a string that can be used with the defaults command value - The boolean value to convert. + The boolean value to convert """ - if value is True: + if value in (True, 1): return "TRUE" - if value is False: + if value in (False, 0): return "FALSE" BOOLEAN_ALLOWED_VALUES = ["TRUE", "YES", "FALSE", "NO"] @@ -333,14 +322,14 @@ def _convert_to_defaults_boolean(value): def _run_defaults_cmd(action, runas=None): """ - Run a 'defaults' command. + Run the 'defaults' command with the given action action - The action to perform with all of its parameters. + The action to perform with all of its parameters Example: 'write com.apple.CrashReporter DialogType "Server"' runas - The user to run the command as. + The user to run the command as """ ret = __salt__["cmd.run_all"](f"defaults {action}", runas=runas) @@ -354,10 +343,10 @@ def _run_defaults_cmd(action, runas=None): def _remove_timestamp(text): """ - Remove the timestamp from the output of the defaults command. + Remove the timestamp from the output of the defaults command if found text - The text to remove the timestamp from. + The text to remove the timestamp from """ pattern = r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d{3})?\s+defaults\[\d+\:\d+\]" diff --git a/salt/states/macdefaults.py b/salt/states/macdefaults.py index 421561ecc7f..20207ec5ee6 100644 --- a/salt/states/macdefaults.py +++ b/salt/states/macdefaults.py @@ -4,13 +4,8 @@ Writing/reading defaults from a macOS minion """ -import logging -import re - import salt.utils.platform -log = logging.getLogger(__name__) - __virtualname__ = "macdefaults" @@ -43,14 +38,13 @@ def write(name, domain, value, vtype="string", user=None): user The user to write the defaults to - """ ret = {"name": name, "result": True, "comment": "", "changes": {}} current_value = __salt__["macdefaults.read"](domain, name, user) value = _cast_value(value, vtype) - if _compare_values(value, current_value, strict=re.match(r"-add$", vtype) is None): + if _compare_values(value, current_value, vtype): ret["comment"] += f"{domain} {name} is already set to {value}" else: out = __salt__["macdefaults.write"](domain, name, value, vtype, user) @@ -76,7 +70,6 @@ def absent(name, domain, user=None): user The user to write the defaults to - """ ret = {"name": name, "result": True, "comment": "", "changes": {}} @@ -90,9 +83,9 @@ def absent(name, domain, user=None): return ret -def _compare_values(new, current, strict=True): +def _compare_values(new, current, vtype): """ - Compare two values + Compare two values based on their type new The new value to compare @@ -100,24 +93,73 @@ def _compare_values(new, current, strict=True): current The current value to compare - strict - If True, the values must be exactly the same, if False, the new value - must be in the current value + vtype + The type of default value to be compared + """ - if strict: - return new == current - return new in current + if vtype == "array-add": + return _is_subarray(new, current) + if vtype == "dict-add": + return all([key in current and new[key] == current[key] for key in new.keys()]) + + return new == current + + +def _is_subarray(new, current): + """ + Check if new is a subarray of current array. + + This method does not check only whether all elements in new array + are present in current array, but also whether the elements are in + the same order. + + new + The new array to compare + + current + The current array to compare + + """ + current_len = len(current) + new_len = len(new) + + if new_len == 0: + return True + if new_len > current_len: + return False + + for i in range(current_len - new_len + 1): + # Check if the new array is found at this position + if current[i : i + new_len] == new: + return True + + return False def _cast_value(value, vtype): + """ + Cast the given macOS default value to Python type + + value + The value to cast from macOS default + + vtype + The type to cast the value from + + """ + def safe_cast(val, to_type, default=None): + """ + Auxiliary function to safely cast a value to a given type + + """ try: return to_type(val) except ValueError: return default if vtype in ("bool", "boolean"): - if value not in [True, "TRUE", "YES", False, "FALSE", "NO"]: + if value not in [True, 1, "TRUE", "YES", False, 0, "FALSE", "NO"]: raise ValueError(f"Invalid value for boolean: {value}") return value in [True, "TRUE", "YES"] diff --git a/tests/pytests/unit/modules/test_macdefaults.py b/tests/pytests/unit/modules/test_macdefaults.py index bf1b3c7b1fc..7cf0d0a1c5f 100644 --- a/tests/pytests/unit/modules/test_macdefaults.py +++ b/tests/pytests/unit/modules/test_macdefaults.py @@ -52,7 +52,7 @@ def test_write_default(): ) -def test_write_with_user(): +def test_write_default_with_user(): """ Test writing a default setting with a specific user """ @@ -67,9 +67,9 @@ def test_write_with_user(): ) -def test_write_true_boolean(): +def test_write_default_true_boolean(): """ - Test writing a True boolean setting + Test writing a default True boolean setting """ mock = MagicMock(return_value={"retcode": 0}) with patch("salt.modules.macdefaults._run_defaults_cmd", mock): @@ -80,9 +80,9 @@ def test_write_true_boolean(): ) -def test_write_false_bool(): +def test_write_default_false_bool(): """ - Test writing a False boolean setting + Test writing a default False boolean setting """ mock = MagicMock(return_value={"retcode": 0}) with patch("salt.modules.macdefaults._run_defaults_cmd", mock): @@ -93,9 +93,9 @@ def test_write_false_bool(): ) -def test_write_int(): +def test_write_default_int(): """ - Test writing an int setting + Test writing a default int setting """ mock = MagicMock(return_value={"retcode": 0}) with patch("salt.modules.macdefaults._run_defaults_cmd", mock): @@ -106,9 +106,9 @@ def test_write_int(): ) -def test_write_integer(): +def test_write_default_integer(): """ - Test writing an integer setting + Test writing a default integer setting """ mock = MagicMock(return_value={"retcode": 0}) with patch("salt.modules.macdefaults._run_defaults_cmd", mock): @@ -119,9 +119,9 @@ def test_write_integer(): ) -def test_write_float(): +def test_write_default_float(): """ - Test writing a float setting + Test writing a default float setting """ mock = MagicMock(return_value={"retcode": 0}) with patch("salt.modules.macdefaults._run_defaults_cmd", mock): @@ -132,9 +132,9 @@ def test_write_float(): ) -def test_write_array(): +def test_write_default_array(): """ - Test writing an array setting + Test writing a default array setting """ mock = MagicMock(return_value={"retcode": 0}) with patch("salt.modules.macdefaults._run_defaults_cmd", mock): @@ -147,9 +147,9 @@ def test_write_array(): ) -def test_write_dictionary(): +def test_write_default_dictionary(): """ - Test writing a dictionary setting + Test writing a default dictionary setting """ mock = MagicMock(return_value={"retcode": 0}) with patch("salt.modules.macdefaults._run_defaults_cmd", mock): @@ -186,7 +186,7 @@ def test_read_default(): assert result == "Server" -def test_read_with_user(): +def test_read_default_with_user(): """ Test reading a default setting as a specific user """ @@ -210,9 +210,9 @@ def test_read_with_user(): assert result == "Server" -def test_read_integer(): +def test_read_default_integer(): """ - Test reading an integer setting + Test reading a default integer setting """ def custom_run_defaults_cmd(action, runas=None): @@ -234,9 +234,9 @@ def test_read_integer(): assert result == 12 -def test_read_float(): +def test_read_default_float(): """ - Test reading a float setting + Test reading a default float setting """ def custom_run_defaults_cmd(action, runas=None): @@ -258,15 +258,15 @@ def test_read_float(): assert result == 0.85 -def test_read_array(): +def test_read_default_array(): """ - Test reading an array setting + Test reading a default array setting """ defaults_output = """( element 1, element 2, - 0.1, + 0.1000, 1 )""" @@ -289,14 +289,16 @@ def test_read_array(): assert result == ["element 1", "element 2", 0.1, 1] -def test_read_dictionary(): +def test_read_default_dictionary(): """ - Test reading a dictionary setting + Test reading a default dictionary setting """ defaults_output = """{ keyCode = 36; modifierFlags = 786432; + anotherKey = "another value with spaces"; + floatNumber = 0.8500; }""" def custom_run_defaults_cmd(action, runas=None): @@ -315,7 +317,12 @@ def test_read_dictionary(): call('read-type "com.apple.CrashReporter" "Crash"', runas=None), ] ) - assert result == {"keyCode": 36, "modifierFlags": 786432} + assert result == { + "keyCode": 36, + "modifierFlags": 786432, + "anotherKey": "another value with spaces", + "floatNumber": 0.85, + } def test_delete_default(): @@ -331,9 +338,9 @@ def test_delete_default(): ) -def test_delete_with_user(): +def test_delete_default_with_user(): """ - Test delete a setting as a specific user + Test delete a default setting as a specific user """ mock = MagicMock(return_value={"retcode": 0}) with patch("salt.modules.macdefaults._run_defaults_cmd", mock): diff --git a/tests/pytests/unit/states/test_macdefaults.py b/tests/pytests/unit/states/test_macdefaults.py index 43699af2426..b293027ace8 100644 --- a/tests/pytests/unit/states/test_macdefaults.py +++ b/tests/pytests/unit/states/test_macdefaults.py @@ -9,7 +9,7 @@ def configure_loader_modules(): return {macdefaults: {}} -def test_write(): +def test_write_default(): """ Test writing a default setting """ @@ -34,7 +34,7 @@ def test_write(): assert out == expected -def test_write_set(): +def test_write_default_already_set(): """ Test writing a default setting that is already set """ @@ -57,7 +57,7 @@ def test_write_set(): assert out == expected -def test_write_boolean(): +def test_write_default_boolean(): """ Test writing a default setting with a boolean """ @@ -68,7 +68,7 @@ def test_write_boolean(): "result": True, } - read_mock = MagicMock(return_value="0") + read_mock = MagicMock(return_value=False) write_mock = MagicMock(return_value={"retcode": 0}) with patch.dict( macdefaults.__salt__, @@ -82,9 +82,9 @@ def test_write_boolean(): assert out == expected -def test_write_boolean_match(): +def test_write_default_boolean_already_set(): """ - Test writing a default setting with a boolean that is already set to the same value + Test writing a default setting with a boolean that is already set """ expected = { "changes": {}, @@ -105,7 +105,7 @@ def test_write_boolean_match(): assert out == expected -def test_write_integer(): +def test_write_default_integer(): """ Test writing a default setting with a integer """ @@ -116,7 +116,7 @@ def test_write_integer(): "result": True, } - read_mock = MagicMock(return_value="99") + read_mock = MagicMock(return_value=99) write_mock = MagicMock(return_value={"retcode": 0}) with patch.dict( macdefaults.__salt__, @@ -130,9 +130,9 @@ def test_write_integer(): assert out == expected -def test_write_integer_match(): +def test_write_default_integer_already_set(): """ - Test writing a default setting with a integer that is already set to the same value + Test writing a default setting with an integer that is already set """ expected = { "changes": {}, @@ -153,7 +153,298 @@ def test_write_integer_match(): assert out == expected -def test_absent_already(): +def test_write_default_float(): + """ + Test writing a default setting with a float + """ + expected = { + "changes": {"written": "com.apple.something Key is set to 0.865"}, + "comment": "", + "name": "Key", + "result": True, + } + + read_mock = MagicMock(return_value=0.4) + write_mock = MagicMock(return_value={"retcode": 0}) + with patch.dict( + macdefaults.__salt__, + {"macdefaults.read": read_mock, "macdefaults.write": write_mock}, + ): + out = macdefaults.write("Key", "com.apple.something", 0.865, vtype="float") + read_mock.assert_called_once_with("com.apple.something", "Key", None) + write_mock.assert_called_once_with( + "com.apple.something", "Key", 0.865, "float", None + ) + assert out == expected + + +def test_write_default_float_already_set(): + """ + Test writing a default setting with a float that is already set_default + """ + expected = { + "changes": {}, + "comment": "com.apple.something Key is already set to 0.865", + "name": "Key", + "result": True, + } + + read_mock = MagicMock(return_value=0.865) + write_mock = MagicMock(return_value={"retcode": 0}) + with patch.dict( + macdefaults.__salt__, + {"macdefaults.read": read_mock, "macdefaults.write": write_mock}, + ): + out = macdefaults.write("Key", "com.apple.something", 0.86500, vtype="float") + read_mock.assert_called_once_with("com.apple.something", "Key", None) + assert not write_mock.called + assert out == expected + + +def test_write_default_array(): + """ + Test writing a default setting with an array + """ + value = ["a", 1, 0.5, True] + expected = { + "changes": {"written": f"com.apple.something Key is set to {value}"}, + "comment": "", + "name": "Key", + "result": True, + } + + read_mock = MagicMock(return_value=None) + write_mock = MagicMock(return_value={"retcode": 0}) + with patch.dict( + macdefaults.__salt__, + {"macdefaults.read": read_mock, "macdefaults.write": write_mock}, + ): + out = macdefaults.write("Key", "com.apple.something", value, vtype="array") + read_mock.assert_called_once_with("com.apple.something", "Key", None) + write_mock.assert_called_once_with( + "com.apple.something", "Key", value, "array", None + ) + assert out == expected + + +def test_write_default_array_already_set(): + """ + Test writing a default setting with an array that is already set + """ + value = ["a", 1, 0.5, True] + expected = { + "changes": {}, + "comment": f"com.apple.something Key is already set to {value}", + "name": "Key", + "result": True, + } + + read_mock = MagicMock(return_value=value) + write_mock = MagicMock(return_value={"retcode": 0}) + with patch.dict( + macdefaults.__salt__, + {"macdefaults.read": read_mock, "macdefaults.write": write_mock}, + ): + out = macdefaults.write("Key", "com.apple.something", value, vtype="array") + read_mock.assert_called_once_with("com.apple.something", "Key", None) + assert not write_mock.called + assert out == expected + + +def test_write_default_array_add(): + """ + Test writing a default setting adding an array to another + """ + write_value = ["a", 1] + read_value = ["b", 2] + expected = { + "changes": {"written": f"com.apple.something Key is set to {write_value}"}, + "comment": "", + "name": "Key", + "result": True, + } + + read_mock = MagicMock(return_value=read_value) + write_mock = MagicMock(return_value={"retcode": 0}) + with patch.dict( + macdefaults.__salt__, + {"macdefaults.read": read_mock, "macdefaults.write": write_mock}, + ): + out = macdefaults.write( + "Key", "com.apple.something", write_value, vtype="array-add" + ) + read_mock.assert_called_once_with("com.apple.something", "Key", None) + write_mock.assert_called_once_with( + "com.apple.something", "Key", write_value, "array-add", None + ) + assert out == expected + + +def test_write_default_array_add_already_set_distinct_order(): + """ + Test writing a default setting adding an array to another that is already set + The new array is in a different order than the existing one + """ + write_value = ["a", 1] + read_value = ["b", 1, "a", 2] + expected = { + "changes": {"written": f"com.apple.something Key is set to {write_value}"}, + "comment": "", + "name": "Key", + "result": True, + } + + read_mock = MagicMock(return_value=read_value) + write_mock = MagicMock(return_value={"retcode": 0}) + with patch.dict( + macdefaults.__salt__, + {"macdefaults.read": read_mock, "macdefaults.write": write_mock}, + ): + out = macdefaults.write( + "Key", "com.apple.something", write_value, vtype="array-add" + ) + read_mock.assert_called_once_with("com.apple.something", "Key", None) + write_mock.assert_called_once_with( + "com.apple.something", "Key", write_value, "array-add", None + ) + assert out == expected + + +def test_write_default_array_add_already_set_same_order(): + """ + Test writing a default setting adding an array to another that is already set + The new array is already in the same order as the existing one + """ + write_value = ["a", 1] + read_value = ["b", "a", 1, 2] + expected = { + "changes": {}, + "comment": f"com.apple.something Key is already set to {write_value}", + "name": "Key", + "result": True, + } + + read_mock = MagicMock(return_value=read_value) + write_mock = MagicMock(return_value={"retcode": 0}) + with patch.dict( + macdefaults.__salt__, + {"macdefaults.read": read_mock, "macdefaults.write": write_mock}, + ): + out = macdefaults.write( + "Key", "com.apple.something", write_value, vtype="array-add" + ) + read_mock.assert_called_once_with("com.apple.something", "Key", None) + assert not write_mock.called + assert out == expected + + +def test_write_default_dict(): + """ + Test writing a default setting with a dictionary + """ + value = {"string": "bar", "integer": 1, "float": 0.5, "boolean": True} + expected = { + "changes": {"written": f"com.apple.something Key is set to {value}"}, + "comment": "", + "name": "Key", + "result": True, + } + + read_mock = MagicMock(return_value=None) + write_mock = MagicMock(return_value={"retcode": 0}) + with patch.dict( + macdefaults.__salt__, + {"macdefaults.read": read_mock, "macdefaults.write": write_mock}, + ): + out = macdefaults.write("Key", "com.apple.something", value, vtype="dict") + read_mock.assert_called_once_with("com.apple.something", "Key", None) + write_mock.assert_called_once_with( + "com.apple.something", "Key", value, "dict", None + ) + assert out == expected + + +def test_write_default_dict_already_set(): + """ + Test writing a default setting with a dictionary that is already set + """ + value = {"string": "bar", "integer": 1, "float": 0.5, "boolean": True} + expected = { + "changes": {}, + "comment": f"com.apple.something Key is already set to {value}", + "name": "Key", + "result": True, + } + + read_mock = MagicMock(return_value=value) + write_mock = MagicMock(return_value={"retcode": 0}) + with patch.dict( + macdefaults.__salt__, + {"macdefaults.read": read_mock, "macdefaults.write": write_mock}, + ): + out = macdefaults.write("Key", "com.apple.something", value, vtype="dict") + read_mock.assert_called_once_with("com.apple.something", "Key", None) + assert not write_mock.called + assert out == expected + + +def test_write_default_dict_add(): + """ + Test writing a default setting adding elements to a dictionary + """ + write_value = {"string": "bar", "integer": 1} + read_value = {"integer": 1, "float": 0.5, "boolean": True} + expected = { + "changes": {"written": f"com.apple.something Key is set to {write_value}"}, + "comment": "", + "name": "Key", + "result": True, + } + + read_mock = MagicMock(return_value=read_value) + write_mock = MagicMock(return_value={"retcode": 0}) + with patch.dict( + macdefaults.__salt__, + {"macdefaults.read": read_mock, "macdefaults.write": write_mock}, + ): + out = macdefaults.write( + "Key", "com.apple.something", write_value, vtype="dict-add" + ) + read_mock.assert_called_once_with("com.apple.something", "Key", None) + write_mock.assert_called_once_with( + "com.apple.something", "Key", write_value, "dict-add", None + ) + assert out == expected + + +def test_write_default_dict_add_already_set(): + """ + Test writing a default setting adding elements to a dictionary that is already set + """ + write_value = {"string": "bar", "integer": 1} + read_value = {"string": "bar", "integer": 1, "float": 0.5, "boolean": True} + expected = { + "changes": {}, + "comment": f"com.apple.something Key is already set to {write_value}", + "name": "Key", + "result": True, + } + + read_mock = MagicMock(return_value=read_value) + write_mock = MagicMock(return_value={"retcode": 0}) + with patch.dict( + macdefaults.__salt__, + {"macdefaults.read": read_mock, "macdefaults.write": write_mock}, + ): + out = macdefaults.write( + "Key", "com.apple.something", write_value, vtype="dict-add" + ) + read_mock.assert_called_once_with("com.apple.something", "Key", None) + assert not write_mock.called + assert out == expected + + +def test_absent_default_already(): """ Test ensuring non-existent defaults value is absent """ @@ -171,9 +462,9 @@ def test_absent_already(): assert out == expected -def test_absent_deleting_existing(): +def test_absent_default_deleting_existing(): """ - Test removing an existing value + Test removing an existing default value """ expected = { "changes": {"absent": "com.apple.something Key is now absent"},