Derive discover-backed aux automatically and fix BGP enrich join keys.

Overwrite explicit aux from discover_select at load time; join VRF CE on vrf,neighbor and global neighbors on afi,neighbor to avoid cross-AF collisions.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-22 14:17:12 +08:00
parent c14d4e81ff
commit f274d8ea72
5 changed files with 420 additions and 50 deletions

View file

@ -6,12 +6,18 @@ import unittest
from netx_api.biz_state.command_match import expand_from_bindings, match_command, preview_task_item
from netx_api.biz_state.profiles import (
AuxCommand,
ParseProfile,
all_profiles,
get_profile,
profile_to_public_dict,
profiles_for_vendor,
reload_profiles,
_finalize_profiles,
_normalize_discover_aux,
)
from netx_api.biz_state.parsers import normalize_lldp_neighbors
from netx_api.biz_state.profiles import PlaceholderDef
from netx_api.lldp_shared import parse_neighbor_output
import re
@ -64,8 +70,13 @@ class BizStateProfileTests(unittest.TestCase):
hits = parse_neighbor_output("", vendor="zte", device_type="zte_zxros")
self.assertEqual(hits, [])
def test_vrf_neighbor_discover_and_aux_both_config_bgp(self) -> None:
def test_bgp_neighbor_discover_and_aux_both_config_bgp(self) -> None:
reload_profiles()
for pid in (
"zte.bgp_vpnv4_neighbor_in",
"zte.bgp_vpnv4_neighbor_out",
"zte.bgp_vpnv6_neighbor_in",
"zte.bgp_vpnv6_neighbor_out",
"zte.bgp_vpnv4_vrf_neighbor_in",
"zte.bgp_vpnv4_vrf_neighbor_out",
"zte.bgp_vpnv6_vrf_neighbor_in",
@ -81,6 +92,87 @@ class BizStateProfileTests(unittest.TestCase):
self.assertEqual(len(aux), 1)
self.assertIn("running-config bgp", aux[0]["command_template"])
def test_discover_backed_profiles_omit_explicit_aux(self) -> None:
"""Source declarations should rely on framework derive (no redundant aux)."""
import inspect
from netx_api.biz_state import profiles as profiles_mod
src = inspect.getsource(profiles_mod._zte_status_profiles)
# ARP may keep explicit aux; discover-backed must not hardcode config_bgp_peer aux
self.assertNotIn(
'AuxCommand(key="config_bgp_peer"',
src,
"discover-backed profiles must not list config_bgp_peer aux explicitly",
)
reload_profiles()
p = get_profile("zte.bgp_vpnv4_neighbor_in")
assert p is not None
self.assertEqual([a.profile_id for a in p.aux_commands], ["zte.config_bgp_peer"])
def test_normalize_discover_aux_overwrites_wrong_aux(self) -> None:
"""Framework replaces explicit aux when discover_select is present."""
fake = ParseProfile(
profile_id="zte.test_neighbor_routes",
vendor_key="zte",
metric_id="bgp_route",
parser_id="bgp_route",
title="test",
command_template="show bgp neighbor in <neighbor> | one-line",
match=r".*",
placeholders=[
PlaceholderDef(
name="neighbor",
schema_field="neighbor",
bind_mode="discover_select",
discover_profile_id="zte.config_bgp_peer",
discover_value_field="neighbor",
)
],
# Intentionally wrong — summary instead of config
aux_commands=[
AuxCommand(key="bgp_summary", profile_id="zte.bgp_vpnv4_summary"),
],
)
_normalize_discover_aux(fake)
self.assertEqual(
[(a.key, a.profile_id) for a in fake.aux_commands],
[("config_bgp_peer", "zte.config_bgp_peer")],
)
def test_arp_keeps_explicit_aux_without_discover(self) -> None:
reload_profiles()
arp = get_profile("zte.arp")
assert arp is not None
self.assertTrue(any(a.profile_id == "zte.config_interface" for a in arp.aux_commands))
self.assertFalse(
any(str(ph.bind_mode or "") == "discover_select" for ph in (arp.placeholders or []))
)
def test_finalize_rejects_aux_placeholder_outside_primary(self) -> None:
target = ParseProfile(
profile_id="zte.fake_vrf_summary",
vendor_key="zte",
metric_id="bgp_peer",
parser_id="bgp_peer",
title="fake",
command_template="show bgp vpnv4 unicast vrf <vrf> summary | one-line",
match=r".*",
)
bad = ParseProfile(
profile_id="zte.bad_aux",
vendor_key="zte",
metric_id="x",
parser_id="x",
title="bad",
command_template="show foo",
match=r".*",
placeholders=[],
aux_commands=[AuxCommand(key="vrf_sum", profile_id="zte.fake_vrf_summary")],
)
with self.assertRaises(RuntimeError) as ctx:
_finalize_profiles([bad, target])
self.assertIn("placeholders", str(ctx.exception))
def test_aux_templates_placeholders_subset_of_primary(self) -> None:
"""Aux CLI must not introduce placeholders outside primary params.