Harden ZTE biz-state parsers and default | one-line collect CLIs.

Append secondary IPs with M/S tags, skip ND6 clock banners, split optical power vs thresholds including QSFP lanes, broaden static-route egress ifnames, and add | one-line to ZTE collect templates except interface detail.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-20 14:49:41 +08:00
parent d900aca633
commit ac4f115ee8
13 changed files with 373 additions and 109 deletions

View file

@ -36,7 +36,7 @@ class BizStateProfileTests(unittest.TestCase):
assert p is not None
pairs = expand_from_bindings(profile=p)
self.assertEqual(len(pairs), 1)
self.assertEqual(pairs[0][0], "show lldp neighbor brief")
self.assertEqual(pairs[0][0], "show lldp neighbor brief | one-line")
def test_preview_custom_raw(self) -> None:
prev = preview_task_item(

View file

@ -60,7 +60,7 @@ class AuxResolveTests(unittest.TestCase):
def test_resolve_from_profile_id(self) -> None:
ra = resolve_aux_command(AuxCommand(key="if_intf", profile_id="zte.config_interface"))
self.assertEqual(ra.command, "show running-config if-intf")
self.assertEqual(ra.command, "show running-config if-intf | one-line")
self.assertEqual(ra.parser_id, "config_interface")
def test_arp_profile_slim(self) -> None:

View file

@ -48,7 +48,9 @@ interface gei-0/0/0/1
description uplink
ip vrf forwarding CUST_A
ip address 10.0.0.1 255.255.255.0
ip address 10.0.0.2 255.255.255.0 secondary
ipv6 address 2001:db8::1/64
ipv6 address 2001:db8::2/64 secondary
mtu 9000
$
interface gei-0/0/0/2
@ -116,6 +118,9 @@ ip route vrf CUST_A 10.1.1.0 255.255.255.0 bvi12.100 10.1.1.1
ip route vrf CUST_A 10.2.2.0 255.255.255.0 gei-0/0/0/1.10 10.2.2.1 bfd enable
ip route vrf CUST_B 10.3.3.0 255.255.255.0 null1 metric 255
ip route vrf CUST_A 10.4.4.0 255.255.255.0 smartgroup1.1 10.4.4.1 track LINK_A
ip route 10.5.5.0 255.255.255.0 xxvgei-0/2/0/1
ip route 10.6.6.0 255.255.255.0 te_tunnel36
ip route 10.7.7.0 255.255.255.0 10.7.7.1 xxvgei-0/2/0/2 name nh_then_if
!</static>
"""
@ -256,10 +261,20 @@ class ZteConfigIntentTests(unittest.TestCase):
by = {r["interface"]: r for r in rows}
self.assertEqual(by["gei-0/0/0/1"]["vrf"], "CUST_A")
self.assertEqual(by["gei-0/0/0/1"]["admin"], "up")
self.assertIn("10.0.0.1", by["gei-0/0/0/1"]["ip_address"])
self.assertIn("2001:db8::1/64", by["gei-0/0/0/1"]["ipv6_address"])
self.assertEqual(
by["gei-0/0/0/1"]["ip_address"],
"10.0.0.1/255.255.255.0,10.0.0.2/255.255.255.0",
)
self.assertEqual(by["gei-0/0/0/1"]["secondary_tag"], "M,S")
self.assertEqual(
by["gei-0/0/0/1"]["ipv6_address"],
"2001:db8::1/64,2001:db8::2/64",
)
self.assertEqual(by["gei-0/0/0/1"]["ipv6_secondary_tag"], "M,S")
self.assertEqual(by["gei-0/0/0/1"]["mtu"], "9000")
self.assertEqual(by["gei-0/0/0/2"]["admin"], "down")
self.assertEqual(by["gei-0/0/0/2"]["secondary_tag"], "M")
self.assertEqual(by["gei-0/0/0/2"]["ipv6_secondary_tag"], "")
def test_config_bgp_peer_skips_password(self) -> None:
rows = normalize_config_bgp_peer(
@ -355,6 +370,13 @@ $
self.assertEqual(by[("CUST_A", "10.2.2.0", "gei-0/0/0/1.10", "10.2.2.1")]["bfd"], "enable")
self.assertEqual(by[("CUST_B", "10.3.3.0", "null1", "")]["metric"], "255")
self.assertEqual(by[("CUST_A", "10.4.4.0", "smartgroup1.1", "10.4.4.1")]["track"], "LINK_A")
# Interface-only (no next-hop address) — including xxvgei / te_tunnel.
self.assertEqual(by[("", "10.5.5.0", "xxvgei-0/2/0/1", "")]["mask"], "255.255.255.0")
self.assertEqual(by[("", "10.6.6.0", "te_tunnel36", "")]["interface"], "te_tunnel36")
# next-hop then interface
self.assertEqual(
by[("", "10.7.7.0", "xxvgei-0/2/0/2", "10.7.7.1")]["route_name"], "nh_then_if"
)
def test_config_static_route_v6(self) -> None:
rows = normalize_config_static_route(

View file

@ -107,12 +107,17 @@ class ZteConfigRealDumpTests(unittest.TestCase):
if m:
block_vrf = m.group(1)
continue
m = re.match(r"(?i)^\s*ip\s+address\s+(\S+)(?:\s+(\S+))?\s*$", line)
m = re.match(
r"(?i)^\s*ip\s+address\s+(\S+)(?:\s+(\S+))?(?:\s+secondary)?\s*$",
line,
)
if m:
a, mask = m.group(1), m.group(2) or ""
if mask.lower() == "secondary":
mask = ""
block_ips.append(f"{a}/{mask}" if mask else a)
continue
m = re.match(r"(?i)^\s*ipv6\s+address\s+(\S+)\s*$", line)
m = re.match(r"(?i)^\s*ipv6\s+address\s+(\S+)(?:\s+secondary)?\s*$", line)
if m:
block_ip6s.append(m.group(1))
continue

View file

@ -66,7 +66,12 @@ smartgroup1.5 5 110 1000 P Init 0.0.0.0 10.0.2.1
_OPTICAL_SAMPLE = """
Interface Type Wavelength RxPower(dBm) TxPower(dBm) Status Intensity(Rx)
gei-0/0/0/1 1G-10km-SFP 1310nm -6.0/[-20.0,-3.0] -5.9/[-9.0,-3.0] Normal Normal
cgei-0/1/0/1 100G-LR4 1310nm -2.1/[-8.0,2.0] -1.5/[-4.0,2.0] Normal Normal
cgei-0/1/0/1 100G-10km-QSFP28 1310nm 1.5/[-10.6,4.5] 0.9/[-4.3,5.5] Normal Normal
2.1/[-10.6,4.5] 1.5/[-4.3,5.5] Normal Normal
1.6/[-10.6,4.5] 2.1/[-4.3,5.5] Normal Normal
0.9/[-10.6,4.5] 2.2/[-4.3,5.5] Normal Normal
7.5 7.7
gei-0/0/0/11 offline
"""
_BGP_ROUTE_IN = """
@ -162,9 +167,24 @@ class ZteExtendedParserTests(unittest.TestCase):
states = {r["state"] for r in vrrp}
self.assertIn("Master", states)
opt = normalize_optical_brief(raw_text=_OPTICAL_SAMPLE, command="show opticalinfo brief")
self.assertEqual(len(opt), 2)
self.assertEqual(opt[0]["status"], "Normal")
opt = normalize_optical_brief(
raw_text=_OPTICAL_SAMPLE,
vendor="zte",
device_type="zte_zxros",
command="show opticalinfo brief",
)
self.assertEqual(len(opt), 3)
by = {r["interface"]: r for r in opt}
self.assertEqual(by["gei-0/0/0/1"]["rx_power"], "-6.0")
self.assertEqual(by["gei-0/0/0/1"]["rx_threshold"], "[-20.0,-3.0]")
self.assertEqual(by["gei-0/0/0/1"]["tx_power"], "-5.9")
self.assertEqual(by["gei-0/0/0/1"]["tx_threshold"], "[-9.0,-3.0]")
self.assertEqual(by["gei-0/0/0/1"]["status"], "Normal")
self.assertEqual(by["cgei-0/1/0/1"]["rx_power"], "1.5,2.1,1.6,0.9")
self.assertEqual(by["cgei-0/1/0/1"]["tx_power"], "0.9,1.5,2.1,2.2")
self.assertEqual(by["cgei-0/1/0/1"]["rx_threshold"], "[-10.6,4.5]")
self.assertEqual(by["cgei-0/1/0/1"]["tx_threshold"], "[-4.3,5.5]")
self.assertEqual(by["gei-0/0/0/11"]["optic_type"], "offline")
def test_bgp_route_and_aux_render(self) -> None:
routes = normalize_bgp_route(
@ -193,7 +213,7 @@ class ZteExtendedParserTests(unittest.TestCase):
AuxCommand(key="bgp_summary", profile_id="zte.bgp_vpnv4_vrf_summary"),
params={"vrf": "CUST_A"},
)
self.assertEqual(ra.command, "show bgp vpnv4 unicast vrf CUST_A summary")
self.assertEqual(ra.command, "show bgp vpnv4 unicast vrf CUST_A summary | one-line")
def test_ip_ipv6_route_and_pw(self) -> None:
ip = normalize_ip_route(

View file

@ -169,6 +169,25 @@ smartgroup11 MDN-SGI-P-ZM8SP UP L2 24 PPP
addrs = {r["address"] for r in rows}
self.assertTrue(any("fe80::" in a for a in addrs))
def test_nd6_skips_clock_banner(self) -> None:
text = """
14:06:53 Beijing Sun Sep 20 2026
S-static NP-near packet RP-remote packet B-bgp HB-hotback D-dhcp
Total Cache Number Is:5398
Only Current Valid Items Are Shown Below:
Address Link-Address Age Status Interface Type
10:460::2 (incomplete) 1s Incomplete smartgroup2345.1120 NP
10:794::2 (incomplete) 1s Incomplete smartgroup2345.1940 NP
fe80::1 aaaa.bbbb.cccc 03:22:07 Reachable gei-0/0/0/1 S
"""
rows = normalize_nd6_cache(raw_text=text)
self.assertEqual(len(rows), 3)
self.assertNotIn("14:06:53", {r["address"] for r in rows})
self.assertEqual(rows[0]["address"], "10:460::2")
self.assertEqual(rows[0]["status"], "Incomplete")
self.assertEqual(rows[0]["type"], "NP")
self.assertEqual(rows[2]["age"], "03:22:07")
def test_bgp_peers(self) -> None:
v4 = _section(self.log, "show bgp vpnv4 unicast summary", ("show bgp ipv4",))
rows = normalize_bgp_peer(raw_text=v4, command="show bgp vpnv4 unicast summary")