summaryrefslogtreecommitdiff
path: root/tests/unit/modules/test_vyos_route_maps.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unit/modules/test_vyos_route_maps.py')
-rw-r--r--tests/unit/modules/test_vyos_route_maps.py506
1 files changed, 374 insertions, 132 deletions
diff --git a/tests/unit/modules/test_vyos_route_maps.py b/tests/unit/modules/test_vyos_route_maps.py
index 97d814b..4eec600 100644
--- a/tests/unit/modules/test_vyos_route_maps.py
+++ b/tests/unit/modules/test_vyos_route_maps.py
@@ -4,189 +4,431 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import json
-import os
import unittest
from unittest.mock import MagicMock
from ansible_collections.vyos.rest.plugins.modules.vyos_route_maps import (
- _want_to_api_match,
- _want_to_api_set,
+ ARGUMENT_SPEC,
+ _derive_key_field,
+ _device_to_argspec,
+ _keyed_list_from_device,
+ _keyed_list_to_device,
+ _match_from_device,
+ _match_to_device,
+ _rule_entry_from_device,
+ _rule_entry_to_device,
+ _seed_route_map_placeholders,
+ _set_from_device,
+ _set_to_device,
+ _want_to_device,
build_commands,
get_running_config,
)
+from .base import load_fixture
-def load_fixture(filename):
- fixtures_dir = os.path.join(os.path.dirname(__file__), "..", "fixtures")
- path = os.path.join(fixtures_dir, filename)
- with open(path) as f:
- return json.load(f)
+
+_BASE = ["policy", "route-map"]
class VyOSModuleTestCase(unittest.TestCase):
def setUp(self):
self.mock_vyos = MagicMock()
- self.mock_vyos.get_config = MagicMock(return_value={})
-
- def set_running_config(self, data):
- self.mock_vyos.get_config.return_value = data
-
+ self.fixture = load_fixture("route_maps_running.json")
+ self.mock_vyos.get_config = MagicMock(return_value=self.fixture)
-class TestVyOSRouteMapsGetRunning(VyOSModuleTestCase):
- def setUp(self):
- super().setUp()
- self.fixture = load_fixture("route_maps_running.json")
+class TestGetRunningConfig(VyOSModuleTestCase):
+ def test_unwraps_route_map_wrapper_key(self):
+ """Confirmed against the pre-existing fixture (built from real
+ device data): the REST API wraps the response in an extra
+ "route-map" key even when querying at the policy/route-map
+ path itself -- the same defensive-unwrap pattern every other
+ module this session needed for its own top-level get_config."""
+ result = get_running_config(self.mock_vyos)
+ self.assertIn("RM-TEST-EXPORT-POLICY", result)
+ self.assertNotIn("route-map", result)
- def test_unwraps_route_map_nesting(self):
- """API returns {"route-map": {"NAME": {...}}} — must unwrap."""
- self.set_running_config(self.fixture)
+ def test_no_wrapper_key_passes_through(self):
+ self.mock_vyos.get_config = MagicMock(return_value={"RM1": {"rule": {}}})
result = get_running_config(self.mock_vyos)
- names = [e["route_map"] for e in result]
+ self.assertIn("RM1", result)
+
+ def test_empty_config(self):
+ self.mock_vyos.get_config = MagicMock(return_value=None)
+ self.assertEqual(get_running_config(self.mock_vyos), {})
+
+
+class TestDeriveKeyField(unittest.TestCase):
+ def test_derives_route_map_key(self):
+ opts = ARGUMENT_SPEC["config"]["options"]
+ self.assertEqual(_derive_key_field(opts), "route_map")
+
+ def test_derives_sequence_key(self):
+ entry_opts = ARGUMENT_SPEC["config"]["options"]["entries"]["options"]
+ self.assertEqual(_derive_key_field(entry_opts), "sequence")
+
+ def test_raises_if_none_required(self):
+ with self.assertRaises(ValueError):
+ _derive_key_field({"a": {"type": "str"}})
+
+ def test_raises_if_more_than_one_required(self):
+ with self.assertRaises(ValueError):
+ _derive_key_field({"a": {"required": True}, "b": {"required": True}})
+
+
+class TestKeyedListHelper(unittest.TestCase):
+ def test_to_device_default_transform_is_autoclean(self):
+ result = _keyed_list_to_device([{"route_map": "RM1", "description": "x"}], "route_map")
+ self.assertEqual(result, {"RM1": {"description": "x"}})
+
+ def test_from_device_default_transform_is_from_device(self):
+ result = _keyed_list_from_device({"RM1": {"description": "x"}}, "route_map")
+ self.assertEqual(result, [{"route_map": "RM1", "description": "x"}])
+
+ def test_empty(self):
+ self.assertEqual(_keyed_list_to_device([], "route_map"), {})
+ self.assertEqual(_keyed_list_from_device({}, "route_map"), [])
+
+
+class TestMatchToDeviceFromDevice(unittest.TestCase):
+ """Most match options are fully generic; only prefix_list/
+ prefix_list6 and ip/ipv6 nexthop matching are genuine structural
+ exceptions (confirmed against vyos-1x: the device nests these
+ deeper than the argspec)."""
+
+ def test_simple_fields_generic(self):
+ result = _match_to_device({"peer": "192.0.2.1", "protocol": "bgp", "metric": 100})
+ self.assertEqual(result, {"peer": "192.0.2.1", "protocol": "bgp", "metric": 100})
+
+ def test_prefix_list_nested_two_levels(self):
+ result = _match_to_device({"prefix_list": "PL1"})
+ self.assertEqual(result, {"ip": {"address": {"prefix-list": "PL1"}}})
+
+ def test_prefix_list6_nested_two_levels(self):
+ result = _match_to_device({"prefix_list6": "PL6"})
+ self.assertEqual(result, {"ipv6": {"address": {"prefix-list": "PL6"}}})
+
+ def test_ip_nexthop_extra_nesting_level(self):
+ result = _match_to_device(
+ {"ip": {"nexthop_address": "10.0.0.1", "nexthop_prefix_list": "PL2"}},
+ )
+ self.assertEqual(
+ result,
+ {"ip": {"nexthop": {"address": "10.0.0.1", "prefix-list": "PL2"}}},
+ )
+
+ def test_ipv6_nexthop_extra_nesting_level(self):
+ result = _match_to_device({"ipv6": {"nexthop_address": "2001:db8::1"}})
+ self.assertEqual(result, {"ipv6": {"nexthop": {"address": "2001:db8::1"}}})
+
+ def test_from_device_prefix_list(self):
+ entry = _match_from_device({"ip": {"address": {"prefix-list": "PL1"}}})
+ self.assertEqual(entry["prefix_list"], "PL1")
+
+ def test_from_device_nexthop(self):
+ entry = _match_from_device({"ip": {"nexthop": {"address": "10.0.0.1"}}})
+ self.assertEqual(entry["ip"], {"nexthop_address": "10.0.0.1"})
+
+ def test_from_device_generic_fields(self):
+ entry = _match_from_device({"peer": "192.0.2.1", "protocol": "bgp"})
+ self.assertEqual(entry, {"peer": "192.0.2.1", "protocol": "bgp"})
+
+ def test_empty(self):
+ self.assertEqual(_match_to_device({}), {})
+ self.assertEqual(_match_to_device(None), {})
+ self.assertEqual(_match_from_device({}), {})
+ self.assertEqual(_match_from_device(None), {})
+
+
+class TestSetToDeviceFromDevice(unittest.TestCase):
+ """as_path_* collapse onto one nested device node. community/
+ large_community/ipv6_next_hop are fully generic once modeled as
+ real nested dicts. "as_" is a genuine Python-keyword-collision
+ rename, nested inside aggregator specifically."""
+
+ def test_atomic_aggregate_fully_generic(self):
+ result = _set_to_device({"atomic_aggregate": True})
+ self.assertEqual(result, {"atomic_aggregate": {}})
+
+ def test_as_path_options_collapse_onto_one_node(self):
+ result = _set_to_device(
+ {"as_path_exclude": "111", "as_path_prepend": "65001", "as_path_prepend_last_as": 2},
+ )
+ self.assertEqual(
+ result["as-path"],
+ {"exclude": "111", "prepend": "65001", "prepend-last-as": 2},
+ )
+
+ def test_aggregator_as_rename(self):
+ """Regression test for the real bug caught this session: "as_"
+ is nested inside "aggregator", not a top-level set field -- a
+ flat rename map applied only at the top level misses it
+ entirely."""
+ result = _set_to_device({"aggregator": {"as_": 100, "ip": "10.0.0.5"}})
+ self.assertEqual(result, {"aggregator": {"as": 100, "ip": "10.0.0.5"}})
+
+ def test_aggregator_as_only(self):
+ result = _set_to_device({"aggregator": {"as_": 100}})
+ self.assertEqual(result, {"aggregator": {"as": 100}})
+
+ def test_community_add_stays_a_plain_list(self):
+ result = _set_to_device({"community": {"add": ["no-export", "no-advertise"]}})
+ self.assertEqual(result["community"], {"add": ["no-export", "no-advertise"]})
+
+ def test_large_community_none_presence(self):
+ result = _set_to_device({"large_community": {"none": True}})
+ self.assertEqual(result["large_community"], {"none": {}})
+
+ def test_ipv6_next_hop_generic(self):
+ result = _set_to_device({"ipv6_next_hop": {"global": "2001:db8::1"}})
+ self.assertEqual(result["ipv6_next_hop"], {"global": "2001:db8::1"})
+
+ def test_ipv6_next_hop_valueless_options(self):
+ result = _set_to_device({"ipv6_next_hop": {"peer_address": True, "prefer_global": True}})
+ self.assertEqual(
+ result["ipv6_next_hop"],
+ {"peer_address": {}, "prefer_global": {}},
+ )
+
+ def test_from_device_community_add(self):
+ entry = _set_from_device({"community": {"add": ["no-export"]}})
+ self.assertEqual(entry["community"], {"add": ["no-export"]})
+
+ def test_from_device_large_community_none(self):
+ entry = _set_from_device({"large-community": {"none": {}}})
+ self.assertEqual(entry["large_community"], {"none": True})
+
+ def test_from_device_as_path(self):
+ entry = _set_from_device({"as-path": {"exclude": "111", "prepend-last-as": "2"}})
+ self.assertEqual(entry["as_path_exclude"], "111")
+ self.assertEqual(entry["as_path_prepend_last_as"], 2)
+
+ def test_from_device_aggregator_as_rename_with_int_cast(self):
+ entry = _set_from_device({"aggregator": {"as": "100", "ip": "10.0.0.5"}})
+ self.assertEqual(entry["aggregator"]["as_"], 100)
+ self.assertEqual(entry["aggregator"]["ip"], "10.0.0.5")
+
+ def test_empty(self):
+ self.assertEqual(_set_to_device({}), {})
+ self.assertEqual(_set_to_device(None), {})
+ self.assertEqual(_set_from_device({}), {})
+ self.assertEqual(_set_from_device(None), {})
+
+
+class TestRuleEntryToDeviceFromDevice(unittest.TestCase):
+ def test_continue_sequence_renamed(self):
+ """ "continue" is a Python keyword and can't be used as a
+ dict() kwarg -- "continue_sequence" is the unavoidable argspec
+ name, handled directly at the rule level (not a set field)."""
+ result = _rule_entry_to_device({"continue_sequence": 20})
+ self.assertEqual(result["continue"], 20)
+
+ def test_generic_fields(self):
+ result = _rule_entry_to_device({"action": "permit", "call": "RM2"})
+ self.assertEqual(result, {"action": "permit", "call": "RM2"})
+
+ def test_from_device_continue(self):
+ entry = _rule_entry_from_device({"continue": "20"})
+ self.assertEqual(entry["continue_sequence"], 20)
+
+ def test_from_device_bare_collapse(self):
+ entry = _rule_entry_from_device(None)
+ self.assertEqual(entry, {})
+
+
+class TestWantToDevice(unittest.TestCase):
+ def test_empty(self):
+ self.assertEqual(_want_to_device([]), {})
+ self.assertEqual(_want_to_device(None), {})
+
+ def test_route_map_without_entries_omitted(self):
+ self.assertEqual(_want_to_device([{"route_map": "RM1"}]), {})
+
+ def test_keyed_by_route_map_name(self):
+ config = [{"route_map": "RM1", "entries": [{"sequence": 10, "action": "permit"}]}]
+ result = _want_to_device(config)
+ self.assertIn("10", result["RM1"]["rule"])
+
+ def test_underscore_route_map_name_stays_verbatim(self):
+ """Confirmed against vyos-1x: route-map names may legitimately
+ contain underscores. _want_to_device itself must not alter the
+ key -- the dict_op-level protection is tested separately in
+ TestBuildCommands."""
+ config = [{"route_map": "my_route_map", "entries": [{"sequence": 10}]}]
+ result = _want_to_device(config)
+ self.assertIn("my_route_map", result)
+
+
+class TestSeedRouteMapPlaceholders(unittest.TestCase):
+ """Regression tests for the confirmed bug: dict_op's fallback
+ guesses a kebab-cased device key whenever a want key is missing
+ from have -- correct for schema field names, wrong for a route-map
+ name (an opaque value that may contain an underscore). Reproduced
+ directly before this fix: "my_route_map" became "my-route-map" in
+ the generated command on first creation."""
+
+ def test_seeds_new_route_map_verbatim(self):
+ want = {"my_route_map": {"rule": {"10": {}}}}
+ have = {}
+ _seed_route_map_placeholders(want, have)
+ self.assertIn("my_route_map", have)
+
+ def test_seeds_new_rule_with_none_not_empty_dict(self):
+ """Seeding with {} instead of None would make dict_op think a
+ presence-only rule already matches and skip emitting its set
+ command -- the same mistake caught once already this session."""
+ want = {"RM1": {"rule": {"10": {}}}}
+ have = {"RM1": {"rule": {}}}
+ _seed_route_map_placeholders(want, have)
+ self.assertIsNone(have["RM1"]["rule"]["10"])
+
+ def test_does_not_overwrite_existing_entries(self):
+ want = {"RM1": {"rule": {"10": {}}}}
+ have = {"RM1": {"rule": {"10": {"action": "permit"}}}}
+ _seed_route_map_placeholders(want, have)
+ self.assertEqual(have["RM1"]["rule"]["10"], {"action": "permit"})
+
+
+class TestDeviceToArgspecFixture(VyOSModuleTestCase):
+ def test_all_route_maps_parsed(self):
+ raw = get_running_config(self.mock_vyos)
+ result = _device_to_argspec(raw)
+ names = [rm["route_map"] for rm in result]
self.assertIn("RM-TEST-EXPORT-POLICY", names)
self.assertIn("rm1", names)
- # "route-map" itself must NOT appear as a route map name
- self.assertNotIn("route-map", names)
-
- def test_parses_rule_action(self):
- self.set_running_config(self.fixture)
- result = get_running_config(self.mock_vyos)
- rm = next(e for e in result if e["route_map"] == "RM-TEST-EXPORT-POLICY")
- rule = rm["entries"][0]
- self.assertEqual(rule["action"], "permit")
- self.assertEqual(rule["sequence"], 10)
- def test_parses_match_peer(self):
- self.set_running_config(self.fixture)
- result = get_running_config(self.mock_vyos)
- rm = next(e for e in result if e["route_map"] == "RM-TEST-EXPORT-POLICY")
+ def test_prefix_list_and_nexthop_match_parsed(self):
+ raw = get_running_config(self.mock_vyos)
+ result = _device_to_argspec(raw)
+ rm = next(rm for rm in result if rm["route_map"] == "RM-TEST-EXPORT-POLICY")
rule = rm["entries"][0]
- self.assertEqual(rule["match"]["peer"], "192.0.2.32")
+ self.assertEqual(rule["match"]["prefix_list"], "PL-MATCH")
+ self.assertEqual(rule["match"]["ip"]["nexthop_address"], "10.0.0.1")
- def test_parses_set_fields(self):
- self.set_running_config(self.fixture)
- result = get_running_config(self.mock_vyos)
- rm = next(e for e in result if e["route_map"] == "RM-TEST-EXPORT-POLICY")
+ def test_community_add_parsed_as_list(self):
+ raw = get_running_config(self.mock_vyos)
+ result = _device_to_argspec(raw)
+ rm = next(rm for rm in result if rm["route_map"] == "RM-TEST-EXPORT-POLICY")
rule = rm["entries"][0]
- self.assertEqual(rule["set"]["metric"], "5")
- self.assertEqual(rule["set"]["aggregator"]["as"], "100")
- self.assertEqual(rule["set"]["as-path"]["exclude"], "111")
-
- def test_empty_returns_empty_list(self):
- self.set_running_config({})
- result = get_running_config(self.mock_vyos)
- self.assertEqual(result, [])
-
-
-class TestVyOSRouteMapsWantToApi(unittest.TestCase):
-
- def test_as_path_exclude_nested(self):
- """as_path_exclude maps to nested as-path.exclude."""
- result = _want_to_api_set({"as_path_exclude": "111"})
- self.assertEqual(result["as-path"]["exclude"], "111")
-
- def test_metric_flat(self):
- result = _want_to_api_set({"metric": "5"})
- self.assertEqual(result["metric"], "5")
+ self.assertEqual(rule["set"]["community"]["add"], ["no-export", "no-advertise"])
- def test_aggregator_as(self):
- result = _want_to_api_set({"aggregator": {"as": 100}})
- self.assertEqual(result["aggregator"]["as"], "100")
+ def test_large_community_none_parsed(self):
+ raw = get_running_config(self.mock_vyos)
+ result = _device_to_argspec(raw)
+ rm1 = next(rm for rm in result if rm["route_map"] == "rm1")
+ self.assertTrue(rm1["entries"][0]["set"]["large_community"]["none"])
- def test_aggregator_as_underscore(self):
- """aggregator.as_ is an alias for aggregator.as."""
- result = _want_to_api_set({"aggregator": {"as_": 100}})
- self.assertEqual(result["aggregator"]["as"], "100")
+ def test_aggregator_as_parsed(self):
+ raw = get_running_config(self.mock_vyos)
+ result = _device_to_argspec(raw)
+ rm = next(rm for rm in result if rm["route_map"] == "RM-TEST-EXPORT-POLICY")
+ self.assertEqual(rm["entries"][0]["set"]["aggregator"]["as_"], 100)
- def test_large_community_presence_node(self):
- result = _want_to_api_set({"large_community": "none"})
- self.assertEqual(result["large-community"], {"none": {}})
+ def test_empty_config(self):
+ self.assertEqual(_device_to_argspec({}), [])
+ self.assertEqual(_device_to_argspec(None), [])
- def test_match_peer(self):
- result = _want_to_api_match({"peer": "192.0.2.32"})
- self.assertEqual(result["peer"], "192.0.2.32")
-
-class TestVyOSRouteMapsBuildCommands(unittest.TestCase):
-
- def _have_empty(self):
- return []
-
- def _have_with_rm(self):
- return [
+class TestBuildCommands(VyOSModuleTestCase):
+ def setUp(self):
+ super().setUp()
+ self.raw = get_running_config(self.mock_vyos)
+
+ def test_merged_idempotent_against_own_fixture(self):
+ have = _device_to_argspec(self.raw)
+ self.assertEqual(build_commands(have, self.raw, "merged"), [])
+
+ def test_replaced_idempotent_against_own_fixture(self):
+ have = _device_to_argspec(self.raw)
+ self.assertEqual(build_commands(have, self.raw, "replaced"), [])
+
+ def test_overridden_idempotent_against_own_fixture(self):
+ have = _device_to_argspec(self.raw)
+ self.assertEqual(build_commands(have, self.raw, "overridden"), [])
+
+ def test_underscore_route_map_name_not_kebab_cased_on_creation(self):
+ """The primary confirmed bug this session, reproduced directly
+ end to end before the fix: "my_route_map" became
+ "my-route-map" in the generated command on first creation."""
+ config = [{"route_map": "my_route_map", "entries": [{"sequence": 10, "action": "permit"}]}]
+ cmds = build_commands(config, {}, "merged")
+ self.assertFalse(any("my-route-map" in str(c) for c in cmds))
+ self.assertTrue(any("my_route_map" in str(c) for c in cmds))
+
+ def test_replaced_scoped_to_named_route_map_only(self):
+ have = _device_to_argspec(self.raw)
+ config = [
{
- "route_map": "RM1",
- "entries": [
- {
- "sequence": 10,
- "action": "permit",
- "match": {"peer": "192.0.2.32"},
- "set": {"metric": "5", "as-path": {"exclude": "111"}},
- },
- ],
+ "route_map": "RM-TEST-EXPORT-POLICY",
+ "entries": have[0]["entries"],
},
]
+ cmds = build_commands(config, self.raw, "replaced")
+ self.assertEqual(cmds, [])
+ self.assertFalse(any("rm1" in str(c) for c in cmds))
- def test_merged_adds_new_rm(self):
+ def test_replaced_removes_omitted_field_full_replace_semantic(self):
+ """Confirms this is the intended "replaced" semantic (matching
+ every other module this session), not a bug: omitting a field
+ from a route map named in "replaced" removes it."""
config = [
{
- "route_map": "RM-NEW",
+ "route_map": "RM-TEST-EXPORT-POLICY",
"entries": [{"sequence": 10, "action": "permit"}],
},
]
- cmds = build_commands(config, self._have_empty(), "merged")
- paths = [c[1] for c in cmds]
- self.assertIn(["policy", "route-map", "RM-NEW", "rule", "10", "action", "permit"], paths)
+ cmds = build_commands(config, self.raw, "replaced")
+ self.assertIn(("delete", _BASE + ["RM-TEST-EXPORT-POLICY", "rule", "10", "set"]), cmds)
+ self.assertIn(("delete", _BASE + ["RM-TEST-EXPORT-POLICY", "rule", "10", "match"]), cmds)
- def test_merged_idempotent(self):
+ def test_overridden_deletes_omitted_route_map(self):
+ have = _device_to_argspec(self.raw)
config = [
{
- "route_map": "RM1",
- "entries": [
- {
- "sequence": 10,
- "action": "permit",
- "match": {"peer": "192.0.2.32"},
- "set": {"metric": "5", "as_path_exclude": "111"},
- },
- ],
+ "route_map": "RM-TEST-EXPORT-POLICY",
+ "entries": have[0]["entries"],
},
]
- cmds = build_commands(config, self._have_with_rm(), "merged")
- self.assertEqual(cmds, [])
+ cmds = build_commands(config, self.raw, "overridden")
+ self.assertIn(("delete", _BASE + ["rm1"]), cmds)
+
+ def test_deleted_scoped_to_named_route_map(self):
+ cmds = build_commands([{"route_map": "rm1"}], self.raw, "deleted")
+ self.assertEqual(cmds, [("delete", _BASE + ["rm1"])])
- def test_deleted_no_config_deletes_all(self):
- cmds = build_commands([], self._have_with_rm(), "deleted")
- self.assertIn(("delete", ["policy", "route-map"]), cmds)
+ def test_deleted_no_config_removes_all(self):
+ cmds = build_commands([], self.raw, "deleted")
+ self.assertEqual(cmds, [("delete", _BASE)])
- def test_deleted_with_config_deletes_named(self):
- config = [{"route_map": "RM1"}]
- cmds = build_commands(config, self._have_with_rm(), "deleted")
- self.assertIn(("delete", ["policy", "route-map", "RM1"]), cmds)
+ def test_deleted_named_nonexistent_is_noop(self):
+ cmds = build_commands([{"route_map": "NONEXISTENT"}], self.raw, "deleted")
+ self.assertEqual(cmds, [])
+
+ def test_collapsed_single_rule_no_char_iteration_bug(self):
+ raw_have = {"RM1": {"rule": "10"}}
+ config = [{"route_map": "RM1", "entries": [{"sequence": 10}]}]
+ self.assertEqual(build_commands(config, raw_have, "merged"), [])
- def test_replaced_deletes_then_resets(self):
+ def test_merged_new_rule_with_community(self):
config = [
{
- "route_map": "RM1",
- "entries": [{"sequence": 10, "action": "deny"}],
+ "route_map": "RM-NEW",
+ "entries": [
+ {
+ "sequence": 10,
+ "action": "permit",
+ "set": {"community": {"add": ["no-export"]}},
+ },
+ ],
},
]
- cmds = build_commands(config, self._have_with_rm(), "replaced")
- ops = [c[0] for c in cmds]
- # delete must come before set
- self.assertIn("delete", ops)
- self.assertIn("set", ops)
- delete_idx = ops.index("delete")
- set_idx = ops.index("set")
- self.assertLess(delete_idx, set_idx)
-
- def test_overridden_removes_extra_rm(self):
- config = [{"route_map": "RM-NEW", "entries": []}]
- have = self._have_with_rm() # has RM1
- cmds = build_commands(config, have, "overridden")
- self.assertIn(("delete", ["policy", "route-map", "RM1"]), cmds)
+ cmds = build_commands(config, {}, "merged")
+ self.assertIn(
+ ("set", _BASE + ["RM-NEW", "rule", "10", "set", "community", "add", "no-export"]),
+ cmds,
+ )
if __name__ == "__main__":