# -*- coding: utf-8 -*-

# Import python libs
from __future__ import absolute_import, print_function, unicode_literals

import copy
import os

import salt.modules.grains as grainsmod
import salt.utils.dictupdate as dictupdate

# Import Salt libs
from salt.exceptions import SaltException

# Import 3rd-party libs
from salt.utils.odict import OrderedDict
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.mock import MagicMock, patch

# Import Salt Testing libs
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})
