import copy
import os

import salt.modules.grains as grainsmod
import salt.utils.dictupdate as dictupdate
from salt.exceptions import SaltException
from salt.utils.odict import OrderedDict
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.mock import MagicMock, patch
from tests.support.runtests import RUNTIME_VARS
from tests.support.unit import TestCase


class GrainsModuleTestCase(TestCase, LoaderModuleMockMixin):
    def setup_loader_modules(self):
        conf_file = os.path.join(RUNTIME_VARS.TMP, "__salt_test_grains")
        cachedir = os.path.join(RUNTIME_VARS.TMP, "__salt_test_grains_cache_dir")
        if not os.path.isdir(cachedir):
            os.makedirs(cachedir)
        return {
            grainsmod: {
                "__opts__": {"conf_file": conf_file, "cachedir": cachedir},
                "__salt__": {"saltutil.refresh_grains": MagicMock()},
            }
        }

    def test_filter_by(self):
        with patch.dict(
            grainsmod.__grains__,
            {"os_family": "MockedOS", "1": "1", "2": "2", "roles": ["A", "B"]},
        ):

            dict1 = {"A": "B", "C": {"D": {"E": "F", "G": "H"}}}
            dict2 = {
                "default": {"A": "B", "C": {"D": "E"}},
                "1": {"A": "X"},
                "2": {"C": {"D": "H"}},
                "MockedOS": {"A": "Z"},
            }

            mdict1 = {"D": {"E": "I"}, "J": "K"}
            mdict2 = {"A": "Z"}
            mdict3 = {"C": {"D": "J"}}

            # test None result with non existent grain and no default
            res = grainsmod.filter_by(dict1, grain="xxx")
            self.assertIs(res, None)

            # test None result with os_family grain and no matching result
            res = grainsmod.filter_by(dict1)
            self.assertIs(res, None)

            # test with non existent grain, and a given default key
            res = grainsmod.filter_by(dict1, grain="xxx", default="C")
            self.assertEqual(res, {"D": {"E": "F", "G": "H"}})

            # add a merge dictionary, F disappears
            res = grainsmod.filter_by(dict1, grain="xxx", merge=mdict1, default="C")
            self.assertEqual(res, {"D": {"E": "I", "G": "H"}, "J": "K"})
            # dict1 was altered, reestablish
            dict1 = {"A": "B", "C": {"D": {"E": "F", "G": "H"}}}

            # default is not present in dict1, check we only have merge in result
            res = grainsmod.filter_by(dict1, grain="xxx", merge=mdict1, default="Z")
            self.assertEqual(res, mdict1)

            # default is not present in dict1, and no merge, should get None
            res = grainsmod.filter_by(dict1, grain="xxx", default="Z")
            self.assertIs(res, None)

            # test giving a list as merge argument raise exception
            self.assertRaises(
                SaltException, grainsmod.filter_by, dict1, "xxx", ["foo"], "C"
            )

            # Now, re-test with an existing grain (os_family), but with no match.
            res = grainsmod.filter_by(dict1)
            self.assertIs(res, None)
            res = grainsmod.filter_by(dict1, default="C")
            self.assertEqual(res, {"D": {"E": "F", "G": "H"}})
            res = grainsmod.filter_by(dict1, merge=mdict1, default="C")
            self.assertEqual(res, {"D": {"E": "I", "G": "H"}, "J": "K"})
            # dict1 was altered, reestablish
            dict1 = {"A": "B", "C": {"D": {"E": "F", "G": "H"}}}
            res = grainsmod.filter_by(dict1, merge=mdict1, default="Z")
            self.assertEqual(res, mdict1)
            res = grainsmod.filter_by(dict1, default="Z")
            self.assertIs(res, None)
            # this one is in fact a traceback in updatedict, merging a string with a dictionary
            self.assertRaises(
                TypeError, grainsmod.filter_by, dict1, merge=mdict1, default="A"
            )

            # Now, re-test with a matching grain.
            dict1 = {"A": "B", "MockedOS": {"D": {"E": "F", "G": "H"}}}
            res = grainsmod.filter_by(dict1)
            self.assertEqual(res, {"D": {"E": "F", "G": "H"}})
            res = grainsmod.filter_by(dict1, default="A")
            self.assertEqual(res, {"D": {"E": "F", "G": "H"}})
            res = grainsmod.filter_by(dict1, merge=mdict1, default="A")
            self.assertEqual(res, {"D": {"E": "I", "G": "H"}, "J": "K"})
            # dict1 was altered, reestablish
            dict1 = {"A": "B", "MockedOS": {"D": {"E": "F", "G": "H"}}}
            res = grainsmod.filter_by(dict1, merge=mdict1, default="Z")
            self.assertEqual(res, {"D": {"E": "I", "G": "H"}, "J": "K"})
            # dict1 was altered, reestablish
            dict1 = {"A": "B", "MockedOS": {"D": {"E": "F", "G": "H"}}}
            res = grainsmod.filter_by(dict1, default="Z")
            self.assertEqual(res, {"D": {"E": "F", "G": "H"}})

            # Test when grain value is a list
            dict1 = {"A": "B", "C": {"D": {"E": "F", "G": "H"}}}
            res = grainsmod.filter_by(dict1, grain="roles", default="C")
            self.assertEqual(res, "B")
            # Test default when grain value is a list
            dict1 = {"Z": "B", "C": {"D": {"E": "F", "G": "H"}}}
            res = grainsmod.filter_by(dict1, grain="roles", default="C")
            self.assertEqual(res, {"D": {"E": "F", "G": "H"}})

            # Test with wildcard pattern in the lookup_dict keys
            dict1 = {"*OS": "B", "C": {"D": {"E": "F", "G": "H"}}}
            res = grainsmod.filter_by(dict1)
            self.assertEqual(res, "B")
            # Test with non-strings in lookup_dict keys
            # Issue #38094
            dict1 = {1: 2, 3: {4: 5}, "*OS": "B"}
            res = grainsmod.filter_by(dict1)
            self.assertEqual(res, "B")
            # Test with sequence pattern with roles
            dict1 = {"Z": "B", "[BC]": {"D": {"E": "F", "G": "H"}}}
            res = grainsmod.filter_by(dict1, grain="roles", default="Z")
            self.assertEqual(res, {"D": {"E": "F", "G": "H"}})

            # Base tests
            # NOTE: these may fail to detect errors if dictupdate.update() is broken
            # but then the unit test for dictupdate.update() should fail and expose
            # that.  The purpose of these tests is it validate the logic of how
            # in filter_by() processes its arguments.

            # Test with just the base
            res = grainsmod.filter_by(dict2, grain="xxx", default="xxx", base="default")
            self.assertEqual(res, dict2["default"])

            # Test the base with the OS grain look-up
            res = grainsmod.filter_by(dict2, default="xxx", base="default")
            self.assertEqual(
                res,
                dictupdate.update(copy.deepcopy(dict2["default"]), dict2["MockedOS"]),
            )

            # Test the base with default
            res = grainsmod.filter_by(dict2, grain="xxx", base="default")
            self.assertEqual(res, dict2["default"])

            res = grainsmod.filter_by(dict2, grain="1", base="default")
            self.assertEqual(
                res, dictupdate.update(copy.deepcopy(dict2["default"]), dict2["1"])
            )

            res = grainsmod.filter_by(dict2, base="default", merge=mdict2)
            self.assertEqual(
                res,
                dictupdate.update(
                    dictupdate.update(
                        copy.deepcopy(dict2["default"]), dict2["MockedOS"]
                    ),
                    mdict2,
                ),
            )

            res = grainsmod.filter_by(dict2, base="default", merge=mdict3)
            self.assertEqual(
                res,
                dictupdate.update(
                    dictupdate.update(
                        copy.deepcopy(dict2["default"]), dict2["MockedOS"]
                    ),
                    mdict3,
                ),
            )

    def test_append_not_a_list(self):
        # Failing append to an existing string, without convert
        with patch.dict(grainsmod.__grains__, {"b": "bval"}):
            res = grainsmod.append("b", "d")
            self.assertEqual(res, "The key b is not a valid list")
            self.assertEqual(grainsmod.__grains__, {"b": "bval"})

            # Failing append to an existing dict
            with patch.dict(grainsmod.__grains__, {"b": {"b1": "bval1"}}):
                res = grainsmod.append("b", "d")
                self.assertEqual(res, "The key b is not a valid list")
                self.assertEqual(grainsmod.__grains__, {"b": {"b1": "bval1"}})

    def test_append_already_in_list(self):
        # Append an existing value
        with patch.dict(grainsmod.__grains__, {"a_list": ["a", "b", "c"], "b": "bval"}):
            res = grainsmod.append("a_list", "b")
            self.assertEqual(res, "The val b was already in the list a_list")
            self.assertEqual(
                grainsmod.__grains__, {"a_list": ["a", "b", "c"], "b": "bval"}
            )

    def test_append_ok(self):
        # Append to an existing list
        with patch.dict(grainsmod.__grains__, {"a_list": ["a", "b", "c"], "b": "bval"}):
            res = grainsmod.append("a_list", "d")
            self.assertEqual(res, {"a_list": ["a", "b", "c", "d"]})
            self.assertEqual(
                grainsmod.__grains__, {"a_list": ["a", "b", "c", "d"], "b": "bval"}
            )

        # Append to an non existing list
        with patch.dict(grainsmod.__grains__, {"b": "bval"}):
            res = grainsmod.append("a_list", "d")
            self.assertEqual(res, {"a_list": ["d"]})
            self.assertEqual(grainsmod.__grains__, {"a_list": ["d"], "b": "bval"})

        # Append to an existing string, with convert
        with patch.dict(grainsmod.__grains__, {"b": "bval"}):
            res = grainsmod.append("b", "d", convert=True)
            self.assertEqual(res, {"b": ["bval", "d"]})
            self.assertEqual(grainsmod.__grains__, {"b": ["bval", "d"]})

        # Append to an existing dict, with convert
        with patch.dict(grainsmod.__grains__, {"b": {"b1": "bval1"}}):
            res = grainsmod.append("b", "d", convert=True)
            self.assertEqual(res, {"b": [{"b1": "bval1"}, "d"]})
            self.assertEqual(grainsmod.__grains__, {"b": [{"b1": "bval1"}, "d"]})

    def test_append_nested_not_a_list(self):
        # Failing append to an existing string, without convert
        with patch.dict(grainsmod.__grains__, {"a": {"b": "bval"}}):
            res = grainsmod.append("a:b", "d")
            self.assertEqual(res, "The key a:b is not a valid list")
            self.assertEqual(grainsmod.__grains__, {"a": {"b": "bval"}})

        # Failing append to an existing dict
        with patch.dict(grainsmod.__grains__, {"a": {"b": {"b1": "bval1"}}}):
            res = grainsmod.append("a:b", "d")
            self.assertEqual(res, "The key a:b is not a valid list")
            self.assertEqual(grainsmod.__grains__, {"a": {"b": {"b1": "bval1"}}})

    def test_append_nested_already_in_list(self):
        # Append an existing value
        with patch.dict(
            grainsmod.__grains__, {"a": {"a_list": ["a", "b", "c"], "b": "bval"}}
        ):
            res = grainsmod.append("a:a_list", "b")
            self.assertEqual(res, "The val b was already in the list a:a_list")
            self.assertEqual(
                grainsmod.__grains__, {"a": {"a_list": ["a", "b", "c"], "b": "bval"}}
            )

    def test_append_nested_ok(self):
        # Append to an existing list
        with patch.dict(
            grainsmod.__grains__, {"a": {"a_list": ["a", "b", "c"], "b": "bval"}}
        ):
            res = grainsmod.append("a:a_list", "d")
            self.assertEqual(res, {"a": {"a_list": ["a", "b", "c", "d"], "b": "bval"}})
            self.assertEqual(
                grainsmod.__grains__,
                {"a": {"a_list": ["a", "b", "c", "d"], "b": "bval"}},
            )

        # Append to an non existing list
        with patch.dict(grainsmod.__grains__, {"a": {"b": "bval"}}):
            res = grainsmod.append("a:a_list", "d")
            self.assertEqual(res, {"a": {"a_list": ["d"], "b": "bval"}})
            self.assertEqual(
                grainsmod.__grains__, {"a": {"a_list": ["d"], "b": "bval"}}
            )

        # Append to an existing string, with convert
        with patch.dict(grainsmod.__grains__, {"a": {"b": "bval"}}):
            res = grainsmod.append("a:b", "d", convert=True)
            self.assertEqual(res, {"a": {"b": ["bval", "d"]}})
            self.assertEqual(grainsmod.__grains__, {"a": {"b": ["bval", "d"]}})

        # Append to an existing dict, with convert
        with patch.dict(grainsmod.__grains__, {"a": {"b": {"b1": "bval1"}}}):
            res = grainsmod.append("a:b", "d", convert=True)
            self.assertEqual(res, {"a": {"b": [{"b1": "bval1"}, "d"]}})
            self.assertEqual(grainsmod.__grains__, {"a": {"b": [{"b1": "bval1"}, "d"]}})

    def test_append_to_an_element_of_a_list(self):
        # Append to an element in a list
        # It currently fails silently
        with patch.dict(grainsmod.__grains__, {"a": ["b", "c"]}):
            res = grainsmod.append("a:b", "d")
            self.assertEqual(res, {"a": ["b", "c"]})
            self.assertEqual(grainsmod.__grains__, {"a": ["b", "c"]})

    def test_set_value_already_set(self):
        # Set a grain to the same simple value
        with patch.dict(grainsmod.__grains__, {"a": 12, "c": 8}):
            res = grainsmod.set("a", 12)
            self.assertTrue(res["result"])
            self.assertEqual(res["comment"], "Grain is already set")
            self.assertEqual(grainsmod.__grains__, {"a": 12, "c": 8})

        # Set a grain to the same complex value
        with patch.dict(grainsmod.__grains__, {"a": ["item", 12], "c": 8}):
            res = grainsmod.set("a", ["item", 12])
            self.assertTrue(res["result"])
            self.assertEqual(res["comment"], "Grain is already set")
            self.assertEqual(grainsmod.__grains__, {"a": ["item", 12], "c": 8})

        # Set a key to the same simple value in a nested grain
        with patch.dict(
            grainsmod.__grains__, {"a": "aval", "b": {"nested": "val"}, "c": 8}
        ):
            res = grainsmod.set("b,nested", "val", delimiter=",")
            self.assertTrue(res["result"])
            self.assertEqual(res["comment"], "Grain is already set")
            self.assertEqual(
                grainsmod.__grains__, {"a": "aval", "b": {"nested": "val"}, "c": 8}
            )

    def test_set_fail_replacing_existing_complex_key(self):
        # Fails to set a complex value without 'force'
        with patch.dict(grainsmod.__grains__, {"a": "aval", "c": 8}):
            res = grainsmod.set("a", ["item", 12])
            self.assertFalse(res["result"])
            self.assertEqual(
                res["comment"],
                "The key 'a' exists and the given value is a "
                "dict or a list. Use 'force=True' to overwrite.",
            )
            self.assertEqual(grainsmod.__grains__, {"a": "aval", "c": 8})

        # Fails to overwrite a complex value without 'force'
        with patch.dict(grainsmod.__grains__, {"a": ["item", 12], "c": 8}):
            res = grainsmod.set("a", ["item", 14])
            self.assertFalse(res["result"])
            self.assertEqual(
                res["comment"],
                "The key 'a' exists but is a dict or a list. "
                + "Use 'force=True' to overwrite.",
            )
            self.assertEqual(grainsmod.__grains__, {"a": ["item", 12], "c": 8})

        # Fails to overwrite a complex value without 'force' in a nested grain
        with patch.dict(
            grainsmod.__grains__, {"a": "aval", "b": ["l1", {"l2": ["val1"]}], "c": 8}
        ):
            res = grainsmod.set("b,l2", "val2", delimiter=",")
            self.assertFalse(res["result"])
            self.assertEqual(
                res["comment"],
                "The key 'b,l2' exists but is a dict or a "
                + "list. Use 'force=True' to overwrite.",
            )
            self.assertEqual(
                grainsmod.__grains__,
                {"a": "aval", "b": ["l1", {"l2": ["val1"]}], "c": 8},
            )

    def test_set_nested_fails_replace_simple_value(self):
        # Fails to replace a simple value with a new dictionary consisting
        # of the specified key and value
        with patch.dict(grainsmod.__grains__, {"a": "aval", "b": "l1", "c": 8}):
            res = grainsmod.set("b,l3", "val3", delimiter=",")
            self.assertFalse(res["result"])
            self.assertEqual(
                res["comment"],
                "The key 'b' value is 'l1', which is "
                + "different from the provided key 'l3'. "
                + "Use 'force=True' to overwrite.",
            )
            self.assertEqual(grainsmod.__grains__, {"a": "aval", "b": "l1", "c": 8})

    def test_set_simple_value(self):
        with patch.dict(grainsmod.__grains__, {"a": ["b", "c"], "c": 8}):
            res = grainsmod.set("b", "bval")
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"b": "bval"})
            self.assertEqual(
                grainsmod.__grains__, {"a": ["b", "c"], "b": "bval", "c": 8}
            )

    def test_set_replace_value(self):
        with patch.dict(grainsmod.__grains__, {"a": "aval", "c": 8}):
            res = grainsmod.set("a", 12)
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"a": 12})
            self.assertEqual(grainsmod.__grains__, {"a": 12, "c": 8})

    def test_set_None_ok(self):
        with patch.dict(grainsmod.__grains__, {"a": "aval", "c": 8}):
            res = grainsmod.set("b", None)
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"b": None})
            self.assertEqual(grainsmod.__grains__, {"a": "aval", "b": None, "c": 8})

    def test_set_None_ok_destructive(self):
        with patch.dict(grainsmod.__grains__, {"a": "aval", "c": 8}):
            res = grainsmod.set("b", None, destructive=True)
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"b": None})
            self.assertEqual(grainsmod.__grains__, {"a": "aval", "c": 8})

    def test_set_None_replace_ok(self):
        with patch.dict(grainsmod.__grains__, {"a": "aval", "c": 8}):
            res = grainsmod.set("a", None)
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"a": None})
            self.assertEqual(grainsmod.__grains__, {"a": None, "c": 8})

    def test_set_None_force_destructive(self):
        with patch.dict(grainsmod.__grains__, {"a": "aval", "c": 8}):
            res = grainsmod.set("a", None, force=True, destructive=True)
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"a": None})
            self.assertEqual(grainsmod.__grains__, {"c": 8})

    def test_set_replace_value_was_complex_force(self):
        with patch.dict(grainsmod.__grains__, {"a": ["item", 12], "c": 8}):
            res = grainsmod.set("a", "aval", force=True)
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"a": "aval"})
            self.assertEqual(grainsmod.__grains__, {"a": "aval", "c": 8})

    def test_set_complex_value_force(self):
        with patch.dict(grainsmod.__grains__, {"a": "aval", "c": 8}):
            res = grainsmod.set("a", ["item", 12], force=True)
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"a": ["item", 12]})
            self.assertEqual(grainsmod.__grains__, {"a": ["item", 12], "c": 8})

    def test_set_nested_create(self):
        with patch.dict(grainsmod.__grains__, {"a": "aval", "c": 8}):
            res = grainsmod.set("b,nested", "val", delimiter=",")
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"b": {"nested": "val"}})
            self.assertEqual(
                grainsmod.__grains__, {"a": "aval", "b": {"nested": "val"}, "c": 8}
            )

    def test_set_nested_update_dict(self):
        with patch.dict(
            grainsmod.__grains__, {"a": "aval", "b": {"nested": "val"}, "c": 8}
        ):
            res = grainsmod.set("b,nested", "val2", delimiter=",")
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"b": {"nested": "val2"}})
            self.assertEqual(
                grainsmod.__grains__, {"a": "aval", "b": {"nested": "val2"}, "c": 8}
            )

    def test_set_nested_update_dict_remove_key(self):
        with patch.dict(
            grainsmod.__grains__, {"a": "aval", "b": {"nested": "val"}, "c": 8}
        ):
            res = grainsmod.set("b,nested", None, delimiter=",", destructive=True)
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"b": {}})
            self.assertEqual(grainsmod.__grains__, {"a": "aval", "b": {}, "c": 8})

    def test_set_nested_update_dict_new_key(self):
        with patch.dict(
            grainsmod.__grains__, {"a": "aval", "b": {"nested": "val"}, "c": 8}
        ):
            res = grainsmod.set("b,b2", "val2", delimiter=",")
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"b": {"b2": "val2", "nested": "val"}})
            self.assertEqual(
                grainsmod.__grains__,
                {"a": "aval", "b": {"b2": "val2", "nested": "val"}, "c": 8},
            )

    def test_set_nested_list_replace_key(self):
        with patch.dict(
            grainsmod.__grains__, {"a": "aval", "b": ["l1", "l2", "l3"], "c": 8}
        ):
            res = grainsmod.set("b,l2", "val2", delimiter=",")
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"b": ["l1", {"l2": "val2"}, "l3"]})
            self.assertEqual(
                grainsmod.__grains__,
                {"a": "aval", "b": ["l1", {"l2": "val2"}, "l3"], "c": 8},
            )

    def test_set_nested_list_update_dict_key(self):
        with patch.dict(
            grainsmod.__grains__, {"a": "aval", "b": ["l1", {"l2": "val1"}], "c": 8}
        ):
            res = grainsmod.set("b,l2", "val2", delimiter=",")
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"b": ["l1", {"l2": "val2"}]})
            self.assertEqual(
                grainsmod.__grains__, {"a": "aval", "b": ["l1", {"l2": "val2"}], "c": 8}
            )

    def test_set_nested_list_update_dict_key_overwrite(self):
        with patch.dict(
            grainsmod.__grains__, {"a": "aval", "b": ["l1", {"l2": ["val1"]}], "c": 8}
        ):
            res = grainsmod.set("b,l2", "val2", delimiter=",", force=True)
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"b": ["l1", {"l2": "val2"}]})
            self.assertEqual(
                grainsmod.__grains__, {"a": "aval", "b": ["l1", {"l2": "val2"}], "c": 8}
            )

    def test_set_nested_list_append_dict_key(self):
        with patch.dict(
            grainsmod.__grains__, {"a": "aval", "b": ["l1", {"l2": "val2"}], "c": 8}
        ):
            res = grainsmod.set("b,l3", "val3", delimiter=",")
            self.assertTrue(res["result"])
            self.assertEqual(
                res["changes"], {"b": ["l1", {"l2": "val2"}, {"l3": "val3"}]}
            )
            self.assertEqual(
                grainsmod.__grains__,
                {"a": "aval", "b": ["l1", {"l2": "val2"}, {"l3": "val3"}], "c": 8},
            )

    def test_set_nested_existing_value_is_the_key(self):
        with patch.dict(grainsmod.__grains__, {"a": "aval", "b": "l3", "c": 8}):
            res = grainsmod.set("b,l3", "val3", delimiter=",")
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"b": {"l3": "val3"}})
            self.assertEqual(
                grainsmod.__grains__, {"a": "aval", "b": {"l3": "val3"}, "c": 8}
            )

    def test_set_nested_existing_value_overwrite(self):
        with patch.dict(grainsmod.__grains__, {"a": "aval", "b": "l1", "c": 8}):
            res = grainsmod.set("b,l3", "val3", delimiter=",", force=True)
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"b": {"l3": "val3"}})
            self.assertEqual(
                grainsmod.__grains__, {"a": "aval", "b": {"l3": "val3"}, "c": 8}
            )

    def test_set_deeply_nested_update(self):
        with patch.dict(
            grainsmod.__grains__,
            {"a": "aval", "b": {"l1": ["l21", "l22", {"l23": "l23val"}]}, "c": 8},
        ):
            res = grainsmod.set("b,l1,l23", "val", delimiter=",")
            self.assertTrue(res["result"])
            self.assertEqual(
                res["changes"], {"b": {"l1": ["l21", "l22", {"l23": "val"}]}}
            )
            self.assertEqual(
                grainsmod.__grains__,
                {"a": "aval", "b": {"l1": ["l21", "l22", {"l23": "val"}]}, "c": 8},
            )

    def test_set_deeply_nested_create(self):
        with patch.dict(
            grainsmod.__grains__,
            {"a": "aval", "b": {"l1": ["l21", "l22", {"l23": "l23val"}]}, "c": 8},
        ):
            res = grainsmod.set("b,l1,l24,l241", "val", delimiter=",")
            self.assertTrue(res["result"])
            self.assertEqual(
                res["changes"],
                {
                    "b": {
                        "l1": [
                            "l21",
                            "l22",
                            {"l23": "l23val"},
                            {"l24": {"l241": "val"}},
                        ]
                    }
                },
            )
            self.assertEqual(
                grainsmod.__grains__,
                {
                    "a": "aval",
                    "b": {
                        "l1": [
                            "l21",
                            "l22",
                            {"l23": "l23val"},
                            {"l24": {"l241": "val"}},
                        ]
                    },
                    "c": 8,
                },
            )

    def test_get_ordered(self):
        with patch.dict(
            grainsmod.__grains__,
            OrderedDict(
                [
                    ("a", "aval"),
                    (
                        "b",
                        OrderedDict(
                            [
                                ("z", "zval"),
                                (
                                    "l1",
                                    ["l21", "l22", OrderedDict([("l23", "l23val")])],
                                ),
                            ]
                        ),
                    ),
                    ("c", 8),
                ]
            ),
        ):
            res = grainsmod.get("b")
            self.assertEqual(type(res), OrderedDict)
            # Check that order really matters
            self.assertTrue(
                res
                == OrderedDict(
                    [
                        ("z", "zval"),
                        ("l1", ["l21", "l22", OrderedDict([("l23", "l23val")])]),
                    ]
                )
            )
            self.assertFalse(
                res
                == OrderedDict(
                    [
                        ("l1", ["l21", "l22", OrderedDict([("l23", "l23val")])]),
                        ("z", "zval"),
                    ]
                )
            )

    def test_get_unordered(self):
        with patch.dict(
            grainsmod.__grains__,
            OrderedDict(
                [
                    ("a", "aval"),
                    (
                        "b",
                        OrderedDict(
                            [
                                ("z", "zval"),
                                (
                                    "l1",
                                    ["l21", "l22", OrderedDict([("l23", "l23val")])],
                                ),
                            ]
                        ),
                    ),
                    ("c", 8),
                ]
            ),
        ):
            res = grainsmod.get("b", ordered=False)
            self.assertEqual(type(res), dict)
            # Check that order doesn't matter
            self.assertTrue(
                res
                == OrderedDict(
                    [
                        ("l1", ["l21", "l22", OrderedDict([("l23", "l23val")])]),
                        ("z", "zval"),
                    ]
                )
            )

    def test_equals(self):
        with patch.dict(
            grainsmod.__grains__,
            OrderedDict(
                [
                    ("a", "aval"),
                    (
                        "b",
                        OrderedDict(
                            [
                                ("z", "zval"),
                                (
                                    "l1",
                                    ["l21", "l22", OrderedDict([("l23", "l23val")])],
                                ),
                            ]
                        ),
                    ),
                    ("c", 8),
                ]
            ),
        ):
            res = grainsmod.equals("a", "aval")
            self.assertEqual(type(res), bool)
            self.assertTrue(res)
            res = grainsmod.equals("b:z", "zval")
            self.assertTrue(res)
            res = grainsmod.equals("b:z", "aval")
            self.assertFalse(res)

    def test_grains_setval_refresh_pillar(self):
        """
        Test that refresh_pillar kwarg is being passed correctly from grains.setval to saltutil.refresh_grains
        """
        ret = grainsmod.setval("test_grains_setval_refresh_pillar", "saltopotamus")
        grainsmod.__salt__["saltutil.refresh_grains"].assert_called_with(
            refresh_pillar=True
        )
        ret = grainsmod.setval(
            "test_grains_setval_refresh_pillar", "saltopotamus", refresh_pillar=True
        )
        grainsmod.__salt__["saltutil.refresh_grains"].assert_called_with(
            refresh_pillar=True
        )
        ret = grainsmod.setval(
            "test_grains_setval_refresh_pillar", "saltopotamus", refresh_pillar=False
        )
        grainsmod.__salt__["saltutil.refresh_grains"].assert_called_with(
            refresh_pillar=False
        )

    def test_setval_unicode(self):
        key = "塩"  # salt
        value = "塩人生です"  # salt is life

        # Note: call setvals 2 times is important
        # 1: add key to conf grains
        # 2: update and read key from conf grains
        for _ in range(2):
            ret = grainsmod.setvals({key: value})
            self.assertIn(key, ret)
            self.assertEqual(ret[key], value)

    def test_delval_single(self):
        with patch.dict(
            grainsmod.__grains__, {"a": "aval", "b": {"nested": "val"}, "c": 8}
        ):
            res = grainsmod.delval("a")
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"a": None})
            self.assertEqual(
                grainsmod.__grains__, {"a": None, "b": {"nested": "val"}, "c": 8}
            )

    def test_delval_nested(self):
        with patch.dict(
            grainsmod.__grains__, {"a": "aval", "b": {"nested": "val"}, "c": 8}
        ):
            res = grainsmod.delval("b:nested")
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"b": {"nested": None}})
            self.assertEqual(
                grainsmod.__grains__, {"a": "aval", "b": {"nested": None}, "c": 8}
            )

    def test_delkey_single_key(self):
        with patch.dict(
            grainsmod.__grains__, {"a": "aval", "b": {"nested": "val"}, "c": 8}
        ):
            res = grainsmod.delkey("a")
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"a": None})
            self.assertEqual(grainsmod.__grains__, {"b": {"nested": "val"}, "c": 8})

    def test_delkey_nested_key(self):
        with patch.dict(
            grainsmod.__grains__, {"a": "aval", "b": {"nested": "val"}, "c": 8}
        ):
            res = grainsmod.delkey("b:nested")
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"b": {}})
            self.assertEqual(grainsmod.__grains__, {"a": "aval", "b": {}, "c": 8})

    def test_delkey_nested_key_force_needed(self):
        with patch.dict(
            grainsmod.__grains__, {"a": "aval", "b": {"nested": "val"}, "c": 8}
        ):
            res = grainsmod.delkey("b", force=True)
            assert res["comment"].find("Use 'force=True' to overwrite.") == -1
            self.assertTrue(res["result"])
            self.assertEqual(res["changes"], {"b": None})
            self.assertEqual(grainsmod.__grains__, {"a": "aval", "c": 8})
