"""Unit tests for generate_inventory(). Uses SimpleNamespace duck-typed objects so no DB or SQLAlchemy is required. """ from types import SimpleNamespace import yaml from steward.ansible.inventory_gen import generate_inventory, inventory_to_yaml def _group(name, ansible_vars=None): g = SimpleNamespace() g.name = name g.ansible_vars = ansible_vars or {} return g def _target(name, address, ansible_vars=None, groups=None): t = SimpleNamespace() t.name = name t.address = address t.ansible_vars = ansible_vars or {} t.groups = groups or [] return t def test_empty_targets(): result = generate_inventory([]) assert result == {"all": {"hosts": []}, "_meta": {"hostvars": {}}} def test_single_target_no_groups(): target = _target("webserver", "192.168.1.10", {"ansible_user": "ubuntu"}) result = generate_inventory([target]) assert result["all"]["hosts"] == ["webserver"] assert result["_meta"]["hostvars"]["webserver"]["ansible_host"] == "192.168.1.10" assert result["_meta"]["hostvars"]["webserver"]["ansible_user"] == "ubuntu" assert set(result.keys()) == {"all", "_meta"} def test_ansible_host_overrides_group_var(): """ansible_host is always target.address, even if a group sets it to something else.""" grp = _group("servers", {"ansible_host": "10.0.0.1"}) target = _target("host1", "192.168.1.50", groups=[grp]) result = generate_inventory([target]) assert result["_meta"]["hostvars"]["host1"]["ansible_host"] == "192.168.1.50" def test_group_vars_applied(): grp = _group("webservers", {"http_port": 80, "ansible_user": "deploy"}) target = _target("web1", "192.168.1.10", groups=[grp]) result = generate_inventory([target]) hostvars = result["_meta"]["hostvars"]["web1"] assert hostvars["http_port"] == 80 assert hostvars["ansible_user"] == "deploy" assert result["webservers"]["hosts"] == ["web1"] assert result["webservers"]["vars"]["http_port"] == 80 def test_host_vars_override_group_vars(): """Per-target ansible_vars beat group vars for the same key.""" grp = _group("g1", {"ansible_user": "group_user"}) target = _target("t1", "1.2.3.4", {"ansible_user": "host_user"}, groups=[grp]) result = generate_inventory([target]) assert result["_meta"]["hostvars"]["t1"]["ansible_user"] == "host_user" def test_group_var_precedence_is_alphabetical(): """When multiple groups set the same key, alphabetically-last group wins (Ansible default).""" g_a = _group("aaa", {"timeout": 10}) g_b = _group("bbb", {"timeout": 20}) target = _target("t1", "1.1.1.1", groups=[g_b, g_a]) # deliberately reversed order result = generate_inventory([target]) # Groups sorted alphabetically: aaa then bbb → bbb wins assert result["_meta"]["hostvars"]["t1"]["timeout"] == 20 def test_multiple_targets_in_group(): grp = _group("db", {"db_port": 5432}) t1 = _target("db1", "10.0.0.1", groups=[grp]) t2 = _target("db2", "10.0.0.2", groups=[grp]) result = generate_inventory([t1, t2]) assert set(result["db"]["hosts"]) == {"db1", "db2"} assert result["all"]["hosts"] == ["db1", "db2"] def test_target_in_multiple_groups(): g1 = _group("web", {"role": "web"}) g2 = _group("prod", {"env": "production"}) target = _target("web-prod-01", "192.168.1.1", groups=[g1, g2]) result = generate_inventory([target]) assert "web-prod-01" in result["web"]["hosts"] assert "web-prod-01" in result["prod"]["hosts"] assert result["_meta"]["hostvars"]["web-prod-01"]["role"] == "web" assert result["_meta"]["hostvars"]["web-prod-01"]["env"] == "production" # ── inventory_to_yaml: STATIC inventory (hosts is a dict, not a list) ────────── def test_yaml_hosts_is_dict_with_hostvars(): target = _target("web1", "192.168.1.10", {"ansible_user": "ubuntu"}) parsed = yaml.safe_load(inventory_to_yaml(generate_inventory([target]))) # The bug was a LIST here; a static YAML inventory requires a dict keyed by host. assert isinstance(parsed["all"]["hosts"], dict) assert parsed["all"]["hosts"]["web1"]["ansible_host"] == "192.168.1.10" assert parsed["all"]["hosts"]["web1"]["ansible_user"] == "ubuntu" def test_yaml_groups_under_children(): grp = _group("db", {"db_port": 5432}) t1 = _target("db1", "10.0.0.1", groups=[grp]) t2 = _target("db2", "10.0.0.2", groups=[grp]) parsed = yaml.safe_load(inventory_to_yaml(generate_inventory([t1, t2]))) assert set(parsed["all"]["hosts"]) == {"db1", "db2"} assert set(parsed["all"]["children"]["db"]["hosts"]) == {"db1", "db2"} assert parsed["all"]["children"]["db"]["vars"]["db_port"] == 5432 def test_yaml_injected_hostvars_survive(): """Extra per-host vars injected after generate_inventory (e.g. steward_token).""" inv = generate_inventory([_target("h1", "1.2.3.4")]) inv["_meta"]["hostvars"]["h1"]["steward_token"] = "tok with space" parsed = yaml.safe_load(inventory_to_yaml(inv)) assert parsed["all"]["hosts"]["h1"]["steward_token"] == "tok with space" def test_yaml_empty_targets(): parsed = yaml.safe_load(inventory_to_yaml(generate_inventory([]))) assert parsed == {"all": {"hosts": {}}}