Files
kubernetes.core/tests/unit/module_utils/test_kubeconfig.py
Youssef Ali e79ed52a4d Add kubeconfig module for managing Kubernetes config files (#1104)
* Add kubeconfig module for managing Kubernetes config files

* Remove unnecessary requirement & Change version

* Move functions to module_utils

* Add unit tests

* Add kubeconfig module for managing Kubernetes config files

* Remove unnecessary requirement & Change version

* Move functions to module_utils

* Add unit tests

* Avoid linter errors

* Improve documentation clarity

* Redact sensitive kubeconfig information

* Imprvoe verbosity

* Move import statement for to_native to avoid linters check failure

* Fix linting error

---------

Co-authored-by: Bianca Henderson <bianca@redhat.com>
2026-05-06 07:56:22 -04:00

232 lines
6.6 KiB
Python

# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import yaml
from ansible_collections.kubernetes.core.plugins.module_utils.kubeconfig import (
deep_merge,
hash_data,
load_yaml_file,
merge_by_name,
write_file,
)
# load_yaml_file
def test_load_yaml_file_returns_empty_dict_for_missing_file():
assert load_yaml_file("/nonexistent/path/config") == {}
def test_load_yaml_file_returns_empty_dict_for_none():
assert load_yaml_file(None) == {}
def test_load_yaml_file_returns_empty_dict_for_empty_string():
assert load_yaml_file("") == {}
def test_load_yaml_file_loads_valid_yaml(tmp_path):
config = {"apiVersion": "v1", "kind": "Config", "clusters": []}
f = tmp_path / "config"
f.write_text(yaml.safe_dump(config))
assert load_yaml_file(str(f)) == config
def test_load_yaml_file_returns_empty_dict_for_empty_file(tmp_path):
f = tmp_path / "config"
f.write_text("")
assert load_yaml_file(str(f)) == {}
# deep_merge
def test_deep_merge_adds_new_keys():
base = {"a": 1}
updates = {"b": 2}
assert deep_merge(base, updates) == {"a": 1, "b": 2}
def test_deep_merge_overwrites_scalar():
base = {"a": 1}
updates = {"a": 99}
assert deep_merge(base, updates) == {"a": 99}
def test_deep_merge_recursively_merges_dicts():
base = {
"cluster": {
"server": "https://old.example.com",
"insecure-skip-tls-verify": True,
}
}
updates = {"cluster": {"server": "https://new.example.com"}}
result = deep_merge(base, updates)
assert result["cluster"]["server"] == "https://new.example.com"
assert result["cluster"]["insecure-skip-tls-verify"] is True
def test_deep_merge_does_not_mutate_base():
base = {"a": {"b": 1}}
updates = {"a": {"c": 2}}
deep_merge(base, updates)
assert base == {"a": {"b": 1}}
def test_deep_merge_overwrites_dict_with_scalar():
base = {"a": {"nested": 1}}
updates = {"a": "flat"}
assert deep_merge(base, updates) == {"a": "flat"}
# merge_by_name
def test_merge_by_name_adds_new_entry():
existing = []
new = [{"name": "cluster-a", "cluster": {"server": "https://a.example.com"}}]
result = merge_by_name(existing, new)
assert len(result) == 1
assert result[0]["name"] == "cluster-a"
def test_merge_by_name_preserves_existing_when_no_new():
existing = [{"name": "cluster-a", "cluster": {"server": "https://a.example.com"}}]
result = merge_by_name(existing, [])
assert len(result) == 1
assert result[0]["name"] == "cluster-a"
def test_merge_by_name_default_behavior_merges_fields():
existing = [
{
"name": "cluster-a",
"cluster": {"server": "https://old.com", "insecure-skip-tls-verify": True},
}
]
new = [{"name": "cluster-a", "cluster": {"server": "https://new.com"}}]
result = merge_by_name(existing, new)
assert len(result) == 1
assert result[0]["cluster"]["server"] == "https://new.com"
assert result[0]["cluster"]["insecure-skip-tls-verify"] is True
def test_merge_by_name_replace_behavior_replaces_entire_entry():
existing = [
{
"name": "cluster-a",
"cluster": {"server": "https://old.com", "insecure-skip-tls-verify": True},
}
]
new = [
{
"name": "cluster-a",
"behavior": "replace",
"cluster": {"server": "https://new.com"},
}
]
result = merge_by_name(existing, new)
assert result[0]["cluster"] == {"server": "https://new.com"}
assert "insecure-skip-tls-verify" not in result[0]["cluster"]
def test_merge_by_name_keep_behavior_preserves_existing():
existing = [{"name": "cluster-a", "cluster": {"server": "https://old.com"}}]
new = [
{
"name": "cluster-a",
"behavior": "keep",
"cluster": {"server": "https://new.com"},
}
]
result = merge_by_name(existing, new)
assert result[0]["cluster"]["server"] == "https://old.com"
def test_merge_by_name_behavior_key_not_in_output():
existing = []
new = [
{
"name": "cluster-a",
"behavior": "replace",
"cluster": {"server": "https://a.com"},
}
]
result = merge_by_name(existing, new)
assert "behavior" not in result[0]
def test_merge_by_name_skips_items_without_name():
existing = []
new = [{"cluster": {"server": "https://a.com"}}]
result = merge_by_name(existing, new)
assert result == []
def test_merge_by_name_skips_non_dict_items():
existing = []
new = ["not-a-dict", 42]
result = merge_by_name(existing, new)
assert result == []
def test_merge_by_name_adds_multiple_new_entries():
existing = []
new = [
{"name": "cluster-a", "cluster": {"server": "https://a.com"}},
{"name": "cluster-b", "cluster": {"server": "https://b.com"}},
]
result = merge_by_name(existing, new)
names = [r["name"] for r in result]
assert "cluster-a" in names
assert "cluster-b" in names
def test_merge_by_name_existing_non_dict_items_are_skipped():
existing = ["not-a-dict", {"cluster": {"server": "https://a.com"}}]
new = [{"name": "cluster-b", "cluster": {"server": "https://b.com"}}]
result = merge_by_name(existing, new)
assert len(result) == 1
assert result[0]["name"] == "cluster-b"
# hash_data
def test_hash_data_returns_string():
assert isinstance(hash_data({}), str)
def test_hash_data_different_input_different_hash():
assert hash_data({"a": 1}) != hash_data({"a": 2})
def test_hash_data_order_independent():
a = {"x": 1, "y": 2}
b = {"y": 2, "x": 1}
assert hash_data(a) == hash_data(b)
# write_file
def test_write_file_returns_false_for_empty_dest():
assert write_file("", {"apiVersion": "v1"}) is False
def test_write_file_returns_false_for_none_dest():
assert write_file(None, {"apiVersion": "v1"}) is False
def test_write_file_writes_valid_yaml(tmp_path):
dest = str(tmp_path / "config")
data = {"apiVersion": "v1", "kind": "Config"}
result = write_file(dest, data)
assert result is True
with open(dest, "r") as f:
written = yaml.safe_load(f)
assert written == data
def test_write_file_overwrites_existing_file(tmp_path):
dest = str(tmp_path / "config")
write_file(dest, {"apiVersion": "v1"})
write_file(dest, {"apiVersion": "v2"})
with open(dest, "r") as f:
written = yaml.safe_load(f)
assert written["apiVersion"] == "v2"