testprotocols 0.7.2__tar.gz → 0.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {testprotocols-0.7.2/src/testprotocols.egg-info → testprotocols-0.8.0}/PKG-INFO +1 -1
- {testprotocols-0.7.2 → testprotocols-0.8.0}/pyproject.toml +1 -1
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/__init__.py +10 -1
- testprotocols-0.8.0/src/testprotocols/config_ownership.py +21 -0
- testprotocols-0.8.0/src/testprotocols/device_info.py +22 -0
- testprotocols-0.8.0/src/testprotocols/devices/__init__.py +80 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/devices/base.py +3 -1
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/devices/client.py +10 -31
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/devices/sdwan.py +10 -11
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/devices/switch.py +9 -5
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/devices/voice.py +4 -21
- testprotocols-0.8.0/src/testprotocols/network_attachment.py +56 -0
- testprotocols-0.8.0/src/testprotocols/placement.py +44 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/sip_phone.py +18 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/sip_server.py +8 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0/src/testprotocols.egg-info}/PKG-INFO +1 -1
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols.egg-info/SOURCES.txt +6 -0
- testprotocols-0.8.0/tests/test_archetype_purity.py +59 -0
- testprotocols-0.8.0/tests/test_boundary_capability_templates.py +47 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/tests/test_device_types.py +21 -6
- {testprotocols-0.7.2 → testprotocols-0.8.0}/tests/test_voice_wifi_templates.py +17 -0
- testprotocols-0.7.2/src/testprotocols/devices/__init__.py +0 -46
- {testprotocols-0.7.2 → testprotocols-0.8.0}/LICENSE +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/NOTICE +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/README.md +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/setup.cfg +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/aftr_gateway.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/appliance_nat.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/appliance_uplinks.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/appliance_vlans.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/arp_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/bgp.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/conntrack.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/content_filtering.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/device_lifecycle.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/device_management.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/devices/cpe.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/devices/infra.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/devices/traffic.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/devices/wan.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/dhcp_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/dhcp_server.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/discovery.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/dns_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/file_transfer.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/firewall.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/firewall_zones.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/first_hop_security.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/gateway_redundancy.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/http_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/http_server.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/hw_console.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/interface_dhcp.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/ip_interface.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/ip_routing.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/iperf_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/iperf_generator.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/iperf_server.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/l3_firewall.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/l7_firewall.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/link_aggregation.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/mac_table.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/models/__init__.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/models/dhcp.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/models/firewall.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/models/impairment.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/models/l2_common.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/models/multicast.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/models/networking.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/models/packets.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/models/qoe.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/models/radius.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/models/sdwan_appliance.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/models/switch.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/models/switch_routing.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/models/tr069.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/models/traffic.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/models/wan_edge.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/models/wifi.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/multicast_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/nat.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/netem_controller.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/network_endpoint.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/network_probe.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/nmap_scanner.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/ntp_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/ntp_config.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/ospf.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/packet_filter.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/pcap_capture.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/pdu_controller.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/port_poe.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/port_security.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/port_status.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/py.typed +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/qoe_browser.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/radius_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/radius_server.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/reachability_responder.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/routed_interfaces.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/router.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/routing_read.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/sdwan_policy_manager.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/site_to_site_vpn.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/snmp_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/spanning_tree.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/static_routes.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/storm_control.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/streaming_server.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/switch_acl.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/switch_ports.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/switch_qos.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/switch_vlans.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/syslog_config.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/tftp_server.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/threat_prevention.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/tr069_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/tr069_server.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/traffic_shaping.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/upnp_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/vlan_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/wan_link_admin.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/wifi_bss.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/wifi_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/wifi_mesh.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/wifi_onboarding.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/wifi_radio.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/wifi_rf.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/wifi_stations.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols/wifi_transitions.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols.egg-info/dependency_links.txt +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/src/testprotocols.egg-info/top_level.txt +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/tests/test_appliance_vlans.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/tests/test_cpe_templates.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/tests/test_dhcp_templates.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/tests/test_firewall_templates.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/tests/test_infra_templates.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/tests/test_ip_interface.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/tests/test_ip_routing.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/tests/test_network_tool_templates.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/tests/test_package_imports.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/tests/test_radius_templates.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/tests/test_sdwan_appliance_models.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/tests/test_sdwan_appliance_templates.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/tests/test_switch_models.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/tests/test_switch_routing_models.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/tests/test_switch_templates.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/tests/test_traffic_templates.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/tests/test_wan_edge_templates.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.8.0}/tests/test_wifi_templates.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: testprotocols
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Common Test Resource Layer — capability and device protocols for telco testing.
|
|
5
5
|
Author-email: Alottabits <rjvisser@alottabits.com>
|
|
6
6
|
Maintainer-email: Alottabits <rjvisser@alottabits.com>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""testprotocols — capability and device Protocols for telco resources under test."""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.
|
|
3
|
+
__version__ = "0.8.0"
|
|
4
4
|
|
|
5
5
|
from testprotocols.aftr_gateway import AftrGateway
|
|
6
6
|
from testprotocols.appliance_nat import ApplianceNat
|
|
@@ -8,8 +8,10 @@ from testprotocols.appliance_uplinks import ApplianceUplinks
|
|
|
8
8
|
from testprotocols.appliance_vlans import ApplianceVlans
|
|
9
9
|
from testprotocols.arp_client import ArpClient
|
|
10
10
|
from testprotocols.bgp import Bgp
|
|
11
|
+
from testprotocols.config_ownership import ConfigOwnership
|
|
11
12
|
from testprotocols.conntrack import Conntrack, ConntrackWhiteBox
|
|
12
13
|
from testprotocols.content_filtering import ContentFiltering
|
|
14
|
+
from testprotocols.device_info import DeviceInfo
|
|
13
15
|
from testprotocols.device_lifecycle import DeviceLifecycle
|
|
14
16
|
from testprotocols.device_management import DeviceManagement
|
|
15
17
|
from testprotocols.devices.base import BaseDeviceProtocol
|
|
@@ -60,6 +62,7 @@ from testprotocols.mac_table import MacTable
|
|
|
60
62
|
from testprotocols.multicast_client import MulticastClient
|
|
61
63
|
from testprotocols.nat import Nat
|
|
62
64
|
from testprotocols.netem_controller import NetemController
|
|
65
|
+
from testprotocols.network_attachment import NetworkAttachment
|
|
63
66
|
from testprotocols.nmap_scanner import NmapScanner
|
|
64
67
|
from testprotocols.ntp_client import NtpClient
|
|
65
68
|
from testprotocols.ntp_config import NtpConfig
|
|
@@ -67,6 +70,7 @@ from testprotocols.ospf import Ospf
|
|
|
67
70
|
from testprotocols.packet_filter import PacketFilter, PacketFilterWhiteBox
|
|
68
71
|
from testprotocols.pcap_capture import PcapCapture
|
|
69
72
|
from testprotocols.pdu_controller import PduController
|
|
73
|
+
from testprotocols.placement import PlacementPipe, PlacementPorts
|
|
70
74
|
from testprotocols.port_poe import PortPoe
|
|
71
75
|
from testprotocols.port_security import PortSecurity
|
|
72
76
|
from testprotocols.port_status import PortStatus
|
|
@@ -116,10 +120,12 @@ __all__ = [
|
|
|
116
120
|
"ArpClient",
|
|
117
121
|
"BaseDeviceProtocol",
|
|
118
122
|
"Bgp",
|
|
123
|
+
"ConfigOwnership",
|
|
119
124
|
"Conntrack",
|
|
120
125
|
"ConntrackWhiteBox",
|
|
121
126
|
"ContentFiltering",
|
|
122
127
|
"CpeDevice",
|
|
128
|
+
"DeviceInfo",
|
|
123
129
|
"DeviceLifecycle",
|
|
124
130
|
"DeviceManagement",
|
|
125
131
|
"DhcpClient",
|
|
@@ -153,6 +159,7 @@ __all__ = [
|
|
|
153
159
|
"MulticastClient",
|
|
154
160
|
"Nat",
|
|
155
161
|
"NetemController",
|
|
162
|
+
"NetworkAttachment",
|
|
156
163
|
"NmapScanner",
|
|
157
164
|
"NtpClient",
|
|
158
165
|
"NtpConfig",
|
|
@@ -161,6 +168,8 @@ __all__ = [
|
|
|
161
168
|
"PacketFilterWhiteBox",
|
|
162
169
|
"PcapCapture",
|
|
163
170
|
"PduController",
|
|
171
|
+
"PlacementPipe",
|
|
172
|
+
"PlacementPorts",
|
|
164
173
|
"PortPoe",
|
|
165
174
|
"PortSecurity",
|
|
166
175
|
"PortStatus",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Config ownership template.
|
|
2
|
+
|
|
3
|
+
Defines the abstract contract for session write-ownership of a device's
|
|
4
|
+
network configuration — e.g. a warm-spare member whose primary owns the
|
|
5
|
+
writes. The natural home for future redundancy-role facts.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Protocol, runtime_checkable
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@runtime_checkable
|
|
14
|
+
class ConfigOwnership(Protocol):
|
|
15
|
+
"""Abstract contract for network-config write ownership."""
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def manages_network(self) -> bool:
|
|
19
|
+
"""Whether this session owns the device's network configuration.
|
|
20
|
+
False for a warm-spare member whose primary owns the writes."""
|
|
21
|
+
...
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Device info template.
|
|
2
|
+
|
|
3
|
+
Defines the abstract contract for a device's inventory identity — today the
|
|
4
|
+
hardware model. Vendor-neutral metadata the driver resolves from its
|
|
5
|
+
management API, inventory config, or class knowledge; the coverage axis for
|
|
6
|
+
model-parameterised tests. Extensible with further identity facts (serial,
|
|
7
|
+
firmware version) when a consumer needs them.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Protocol, runtime_checkable
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@runtime_checkable
|
|
16
|
+
class DeviceInfo(Protocol):
|
|
17
|
+
"""Abstract contract for device inventory identity."""
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def model(self) -> str:
|
|
21
|
+
"""Hardware model identifier (e.g. ``"MX250"``)."""
|
|
22
|
+
...
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Device archetype registry — maps inventory device_type strings to Protocols."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any, cast, get_type_hints
|
|
8
|
+
|
|
9
|
+
_registry: dict[str, DeviceTypeSpec] = {}
|
|
10
|
+
|
|
11
|
+
#: The sole sanctioned non-capability members — BaseDeviceProtocol's universal
|
|
12
|
+
#: identity pair (see devices/base.py).
|
|
13
|
+
IDENTITY_MEMBERS = frozenset({"device_name", "device_type"})
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def non_capability_members(protocol: type) -> frozenset[str]:
|
|
17
|
+
"""Members of *protocol* that are neither capability Protocols nor identity.
|
|
18
|
+
|
|
19
|
+
A capability member is annotated with a ``typing.Protocol`` class. Scalar
|
|
20
|
+
annotations (``str``, ``bool``, ``tuple``…) and property-declared members
|
|
21
|
+
(which carry no annotation at all) are both offenders. ``__protocol_attrs__``
|
|
22
|
+
is the CPython 3.12 runtime detail ``typing.get_protocol_members``
|
|
23
|
+
supersedes in 3.13; one cast keeps that knowledge here.
|
|
24
|
+
"""
|
|
25
|
+
hints = get_type_hints(protocol)
|
|
26
|
+
members: frozenset[str] = frozenset(cast("Any", protocol).__protocol_attrs__)
|
|
27
|
+
return frozenset(
|
|
28
|
+
member
|
|
29
|
+
for member in members - IDENTITY_MEMBERS
|
|
30
|
+
if not getattr(hints.get(member), "_is_protocol", False)
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class DeviceTypeSpec:
|
|
36
|
+
"""Registration record: inventory string -> @runtime_checkable Protocol class."""
|
|
37
|
+
|
|
38
|
+
name: str
|
|
39
|
+
protocol: type
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def register_device_type(name: str, protocol: type) -> DeviceTypeSpec:
|
|
43
|
+
"""Register *protocol* as the contract for inventory devices typed *name*.
|
|
44
|
+
|
|
45
|
+
Fails closed on archetype drift: every member must be a capability
|
|
46
|
+
Protocol (the BaseDeviceProtocol identity pair excepted) — see
|
|
47
|
+
docs/architecture/capability-only-archetypes.md. Returns the spec; also
|
|
48
|
+
stored in the module-level registry so ``get_device_type(name)`` can look
|
|
49
|
+
it up by inventory string.
|
|
50
|
+
"""
|
|
51
|
+
offenders = non_capability_members(protocol)
|
|
52
|
+
if offenders:
|
|
53
|
+
raise TypeError(
|
|
54
|
+
f"device archetype {protocol.__name__!r} ({name!r}) declares "
|
|
55
|
+
f"non-capability members {sorted(offenders)}; archetypes compose "
|
|
56
|
+
"capability protocols only — move these into a capability protocol "
|
|
57
|
+
"(docs/architecture/capability-only-archetypes.md)"
|
|
58
|
+
)
|
|
59
|
+
spec = DeviceTypeSpec(name=name, protocol=protocol)
|
|
60
|
+
_registry[name] = spec
|
|
61
|
+
return spec
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_device_type(name: str) -> DeviceTypeSpec | None:
|
|
65
|
+
"""Return the spec for inventory *name*, or None if not registered."""
|
|
66
|
+
return _registry.get(name)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def all_device_types() -> dict[str, DeviceTypeSpec]:
|
|
70
|
+
"""Return a copy of the full registry."""
|
|
71
|
+
return dict(_registry)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# Auto-import all archetype modules to trigger registration as a side effect.
|
|
75
|
+
# ``importlib.import_module`` avoids the unused-import / out-of-order-import
|
|
76
|
+
# friction that direct ``from … import …`` statements would create here, and
|
|
77
|
+
# keeps the import section at the top of the file as PEP 8 prefers.
|
|
78
|
+
for _mod in ("client", "cpe", "infra", "sdwan", "switch", "traffic", "voice", "wan"):
|
|
79
|
+
importlib.import_module(f"testprotocols.devices.{_mod}")
|
|
80
|
+
del _mod
|
|
@@ -21,7 +21,9 @@ class BaseDeviceProtocol(Protocol):
|
|
|
21
21
|
Deliberately scoped to the universal pair — name + type. Anything a
|
|
22
22
|
specific framework wants to expose (vitro's ``config`` /
|
|
23
23
|
``get_interactive_consoles``, boardfarm's lifecycle hooks, etc.) is
|
|
24
|
-
the framework's own concern.
|
|
24
|
+
the framework's own concern. Every other archetype member must be a
|
|
25
|
+
capability Protocol — enforced by ``register_device_type``; see
|
|
26
|
+
docs/architecture/capability-only-archetypes.md. Frameworks declare a connector Protocol
|
|
25
27
|
on their side that extends this base; testbed plugins compose
|
|
26
28
|
archetype + framework-connector into a combined Protocol where step
|
|
27
29
|
defs need both.
|
|
@@ -17,12 +17,14 @@ from testprotocols.ip_routing import IpRouting
|
|
|
17
17
|
from testprotocols.iperf_client import IperfClient
|
|
18
18
|
from testprotocols.iperf_server import IperfServer
|
|
19
19
|
from testprotocols.multicast_client import MulticastClient
|
|
20
|
+
from testprotocols.network_attachment import NetworkAttachment
|
|
20
21
|
from testprotocols.network_endpoint import NetworkEndpoint
|
|
21
22
|
from testprotocols.network_probe import NetworkProbe
|
|
22
23
|
from testprotocols.nmap_scanner import NmapScanner
|
|
23
24
|
from testprotocols.ntp_client import NtpClient
|
|
24
25
|
from testprotocols.packet_filter import PacketFilter
|
|
25
26
|
from testprotocols.pcap_capture import PcapCapture
|
|
27
|
+
from testprotocols.placement import PlacementPipe
|
|
26
28
|
from testprotocols.qoe_browser import QoeBrowser
|
|
27
29
|
from testprotocols.reachability_responder import ReachabilityResponder
|
|
28
30
|
from testprotocols.syslog_config import SyslogConfig
|
|
@@ -128,37 +130,14 @@ class QoeMeasurementClientDevice(QoeClientDevice, Protocol):
|
|
|
128
130
|
responder: ReachabilityResponder
|
|
129
131
|
pcap: PcapCapture
|
|
130
132
|
|
|
131
|
-
|
|
132
|
-
"""
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
test leg sits on a segment the testbed does not manage), empty for managed
|
|
140
|
-
homing or when undeclared. A stable role name (e.g. ``"provider-vpn"``),
|
|
141
|
-
never an address: it lets tests resolve "the client on segment X" by
|
|
142
|
-
declaration when address facts are foreign-owned or discovered late.
|
|
143
|
-
Vendor-neutral metadata from the driver's attachment config."""
|
|
144
|
-
|
|
145
|
-
attachment_subnet: str
|
|
146
|
-
"""The declared subnet (CIDR) of a guest attachment, empty when undeclared.
|
|
147
|
-
Vendor-neutral metadata from the driver's attachment config — read it here
|
|
148
|
-
rather than reaching into a framework-specific config object."""
|
|
149
|
-
|
|
150
|
-
mgmt_ip: str
|
|
151
|
-
"""The client's management-plane address, empty when undeclared. Published
|
|
152
|
-
by the driver from its own config so isolation checks never consume a
|
|
153
|
-
framework-specific config object."""
|
|
154
|
-
|
|
155
|
-
pipe_switch: str
|
|
156
|
-
"""Name of the switch carrying this client's dedicated access port (its
|
|
157
|
-
placement pipe), empty when the client has no declared pipe."""
|
|
158
|
-
|
|
159
|
-
pipe_port: str
|
|
160
|
-
"""The dedicated access port whose VLAN is this client's placement knob,
|
|
161
|
-
empty when the client has no declared pipe."""
|
|
133
|
+
attachment: NetworkAttachment
|
|
134
|
+
"""Homing metadata — test/mgmt plane addresses and the declared guest
|
|
135
|
+
attachment (segment label + subnet). See ``NetworkAttachment`` for the
|
|
136
|
+
boundary against ``NetworkEndpoint``."""
|
|
137
|
+
|
|
138
|
+
pipe: PlacementPipe
|
|
139
|
+
"""This client's declared placement pipe (dedicated access port on a
|
|
140
|
+
managed switch); counterpart of the switch's ``placement`` authorization."""
|
|
162
141
|
|
|
163
142
|
|
|
164
143
|
register_device_type("linux_lan_client", LanClientDevice)
|
|
@@ -8,8 +8,10 @@ from testprotocols.appliance_nat import ApplianceNat
|
|
|
8
8
|
from testprotocols.appliance_uplinks import ApplianceUplinks
|
|
9
9
|
from testprotocols.appliance_vlans import ApplianceVlans
|
|
10
10
|
from testprotocols.bgp import Bgp
|
|
11
|
+
from testprotocols.config_ownership import ConfigOwnership
|
|
11
12
|
from testprotocols.conntrack import Conntrack
|
|
12
13
|
from testprotocols.content_filtering import ContentFiltering
|
|
14
|
+
from testprotocols.device_info import DeviceInfo
|
|
13
15
|
from testprotocols.devices import register_device_type
|
|
14
16
|
from testprotocols.devices.base import BaseDeviceProtocol
|
|
15
17
|
from testprotocols.ip_interface import IpInterface
|
|
@@ -93,17 +95,14 @@ class SdwanApplianceDevice(BaseDeviceProtocol, Protocol):
|
|
|
93
95
|
lan: ApplianceVlans
|
|
94
96
|
syslog: SyslogConfig
|
|
95
97
|
|
|
96
|
-
|
|
97
|
-
"""
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
a warm-spare member whose primary owns the writes. Vendor-neutral metadata
|
|
105
|
-
from the driver's config — read it here rather than reaching into a
|
|
106
|
-
framework-specific config object."""
|
|
98
|
+
info: DeviceInfo
|
|
99
|
+
"""Inventory identity (hardware model, e.g. ``"MX250"``) — the coverage
|
|
100
|
+
axis for model-parameterised tests, resolved by the driver from its
|
|
101
|
+
management API / inventory."""
|
|
102
|
+
|
|
103
|
+
ownership: ConfigOwnership
|
|
104
|
+
"""Whether this session owns the appliance's network configuration —
|
|
105
|
+
False for a warm-spare member whose primary owns the writes."""
|
|
107
106
|
|
|
108
107
|
|
|
109
108
|
register_device_type("sdwan_appliance", SdwanApplianceDevice)
|
|
@@ -12,6 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
from typing import Protocol, runtime_checkable
|
|
13
13
|
|
|
14
14
|
from testprotocols.bgp import Bgp
|
|
15
|
+
from testprotocols.device_info import DeviceInfo
|
|
15
16
|
from testprotocols.devices import register_device_type
|
|
16
17
|
from testprotocols.devices.base import BaseDeviceProtocol
|
|
17
18
|
from testprotocols.discovery import Discovery
|
|
@@ -22,6 +23,7 @@ from testprotocols.link_aggregation import LinkAggregation
|
|
|
22
23
|
from testprotocols.mac_table import MacTable
|
|
23
24
|
from testprotocols.ntp_config import NtpConfig
|
|
24
25
|
from testprotocols.ospf import Ospf
|
|
26
|
+
from testprotocols.placement import PlacementPorts
|
|
25
27
|
from testprotocols.port_poe import PortPoe
|
|
26
28
|
from testprotocols.port_security import PortSecurity
|
|
27
29
|
from testprotocols.port_status import PortStatus
|
|
@@ -68,11 +70,13 @@ class L2Switch(BaseDeviceProtocol, Protocol):
|
|
|
68
70
|
syslog: SyslogConfig
|
|
69
71
|
ntp: NtpConfig
|
|
70
72
|
|
|
71
|
-
|
|
72
|
-
"""
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
placement: PlacementPorts
|
|
74
|
+
"""Placement-write authorization — which ports a testbed may re-VLAN to
|
|
75
|
+
place a client; counterpart of a client's ``pipe`` declaration."""
|
|
76
|
+
|
|
77
|
+
info: DeviceInfo
|
|
78
|
+
"""Inventory identity (hardware model) — the coverage axis for
|
|
79
|
+
model-parameterised switch tests."""
|
|
76
80
|
|
|
77
81
|
|
|
78
82
|
register_device_type("managed_switch_l2", L2Switch)
|
|
@@ -21,20 +21,8 @@ class SipPhoneDevice(BaseDeviceProtocol, Protocol):
|
|
|
21
21
|
Used to drive call-control scenarios (register / invite / cancel / bye) and
|
|
22
22
|
media-plane verification against a SIP server under test.
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
``aors`` is the list of registered SIP URIs in RFC 3261 §6 address-of-record
|
|
28
|
-
form (``sip:user@host`` with optional ``;params``). ``host`` may be an FQDN,
|
|
29
|
-
an IPv4 literal (``10.0.0.5``), or an IPv6 bracketed literal
|
|
30
|
-
(``[2001:db8::5]``) — the driver decides which form matches its testbed's
|
|
31
|
-
address-resolution context. The list is never empty; ``aors[0]`` is the
|
|
32
|
-
primary line that single-line operations (``sip_phone.dial``,
|
|
33
|
-
``sip_phone.answer``, MWI subscriptions, …) act on.
|
|
34
|
-
|
|
35
|
-
``number`` is the user-part of ``aors[0]`` — the SIP phone's E.164 /
|
|
36
|
-
dial-string identifier for its primary line. Provided as a convenience so
|
|
37
|
-
single-line callers don't have to parse SIP URIs.
|
|
24
|
+
Line identity — ``number`` / ``aors`` — lives on the ``SipPhone``
|
|
25
|
+
capability protocol.
|
|
38
26
|
|
|
39
27
|
``device_name`` / ``device_type`` come from :class:`BaseDeviceProtocol`.
|
|
40
28
|
"""
|
|
@@ -42,8 +30,6 @@ class SipPhoneDevice(BaseDeviceProtocol, Protocol):
|
|
|
42
30
|
sip_phone: SipPhone
|
|
43
31
|
ip_interface: IpInterface
|
|
44
32
|
dhcp_client: DhcpClient
|
|
45
|
-
number: str
|
|
46
|
-
aors: list[str]
|
|
47
33
|
|
|
48
34
|
|
|
49
35
|
@runtime_checkable
|
|
@@ -53,16 +39,13 @@ class SipServerDevice(BaseDeviceProtocol, Protocol):
|
|
|
53
39
|
Provides the server-side SIP surface plus packet capture and file transfer
|
|
54
40
|
for inspecting and exporting signalling exchanges.
|
|
55
41
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
IPv4 literal, or an IPv6 bracketed literal — the driver decides which
|
|
59
|
-
form matches its testbed's address-resolution context.
|
|
42
|
+
The AOR host part — ``aor_domain`` — lives on the ``SipServer``
|
|
43
|
+
capability protocol.
|
|
60
44
|
"""
|
|
61
45
|
|
|
62
46
|
sip_server: SipServer
|
|
63
47
|
pcap: PcapCapture
|
|
64
48
|
file_transfer: FileTransfer
|
|
65
|
-
aor_domain: str
|
|
66
49
|
|
|
67
50
|
|
|
68
51
|
register_device_type("linux_sip_phone", SipPhoneDevice)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Network attachment template.
|
|
2
|
+
|
|
3
|
+
Defines the abstract contract for an endpoint's *declared or discovered
|
|
4
|
+
homing* onto the testbed's network planes: its test-plane address, its
|
|
5
|
+
management-plane address, and — for a guest-attached endpoint (one whose
|
|
6
|
+
test leg sits on a segment the testbed does not manage) — the declared
|
|
7
|
+
segment label and subnet.
|
|
8
|
+
|
|
9
|
+
Distinct from ``NetworkEndpoint``: that is the live role-address query
|
|
10
|
+
("give me the WAN address, now"), answered by whatever means the driver
|
|
11
|
+
chooses. ``NetworkAttachment`` is homing *metadata* the driver resolves
|
|
12
|
+
from its own config or discovers at attach time (e.g. a DHCP lease),
|
|
13
|
+
published so consumers never reach into a framework-specific config
|
|
14
|
+
object. Archetypes whose drivers already answer addressing via
|
|
15
|
+
``NetworkEndpoint`` should not also compose this; it earns its place
|
|
16
|
+
where declared homing (segment labels, plane separation) is the
|
|
17
|
+
mechanism under test.
|
|
18
|
+
|
|
19
|
+
All values are vendor-neutral; empty string means undeclared/not homed.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from typing import Protocol, runtime_checkable
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@runtime_checkable
|
|
28
|
+
class NetworkAttachment(Protocol):
|
|
29
|
+
"""Abstract contract for an endpoint's homing metadata."""
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def test_ip(self) -> str:
|
|
33
|
+
"""The endpoint's test-plane address as a CIDR (e.g. ``"10.1.30.50/24"``),
|
|
34
|
+
empty if not homed to a test segment. May be discovered at attach time
|
|
35
|
+
(e.g. from a DHCP lease) rather than declared."""
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def mgmt_ip(self) -> str:
|
|
40
|
+
"""The endpoint's management-plane address, empty when undeclared.
|
|
41
|
+
Published so isolation checks never consume a framework-specific
|
|
42
|
+
config object."""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def segment(self) -> str:
|
|
47
|
+
"""Declared segment LABEL of a guest attachment — a stable role name
|
|
48
|
+
(e.g. ``"provider-vpn"``), never an address — empty for managed homing
|
|
49
|
+
or when undeclared. Lets tests resolve "the endpoint on segment X" by
|
|
50
|
+
declaration when address facts are foreign-owned or discovered late."""
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def subnet(self) -> str:
|
|
55
|
+
"""Declared subnet (CIDR) of a guest attachment, empty when undeclared."""
|
|
56
|
+
...
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Client-placement templates — both halves of the placement mechanism.
|
|
2
|
+
|
|
3
|
+
A *placement pipe* is a client's dedicated access port on a managed switch:
|
|
4
|
+
re-VLANing that port is the client's placement knob. The mechanism has two
|
|
5
|
+
sides owned by two devices: the **client** declares which switch/port carries
|
|
6
|
+
its pipe (``PlacementPipe`` — topology fact), and the **switch** declares
|
|
7
|
+
which ports a testbed may re-VLAN at all (``PlacementPorts`` — write
|
|
8
|
+
authorization, owned by the resource's owner). Pipe ownership and write
|
|
9
|
+
authorization are deliberately independent declarations.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import Protocol, runtime_checkable
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@runtime_checkable
|
|
18
|
+
class PlacementPipe(Protocol):
|
|
19
|
+
"""Abstract contract for a client's declared placement pipe."""
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def switch(self) -> str:
|
|
23
|
+
"""Name of the switch carrying this client's dedicated access port,
|
|
24
|
+
empty when the client has no declared pipe."""
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def port(self) -> str:
|
|
29
|
+
"""The dedicated access port whose VLAN is this client's placement
|
|
30
|
+
knob, empty when the client has no declared pipe."""
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@runtime_checkable
|
|
35
|
+
class PlacementPorts(Protocol):
|
|
36
|
+
"""Abstract contract for a switch's placement-write authorization."""
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def ports(self) -> tuple[str, ...]:
|
|
40
|
+
"""Designated ports a testbed may re-VLAN to place a client; empty
|
|
41
|
+
means no placement write is permitted on this switch. Independent of
|
|
42
|
+
which device's pipe enters a port: pipe ownership is topology, this is
|
|
43
|
+
write authorization — declared by the switch, the resource's owner."""
|
|
44
|
+
...
|
|
@@ -13,6 +13,24 @@ from typing import Protocol, runtime_checkable
|
|
|
13
13
|
class SipPhone(Protocol):
|
|
14
14
|
"""Abstract contract for SIP phone operations."""
|
|
15
15
|
|
|
16
|
+
@property
|
|
17
|
+
def number(self) -> str:
|
|
18
|
+
"""The user-part of ``aors[0]`` — the phone's E.164 / dial-string
|
|
19
|
+
identifier for its primary line. A convenience so single-line callers
|
|
20
|
+
don't have to parse SIP URIs."""
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def aors(self) -> list[str]:
|
|
25
|
+
"""Registered SIP URIs in RFC 3261 §6 address-of-record form
|
|
26
|
+
(``sip:user@host`` with optional ``;params``). ``host`` may be an FQDN,
|
|
27
|
+
an IPv4 literal, or an IPv6 bracketed literal — the driver decides
|
|
28
|
+
which form matches its testbed's address-resolution context. Never
|
|
29
|
+
empty once configured; ``aors[0]`` is the primary line that single-line
|
|
30
|
+
operations (``dial``, ``answer``, MWI subscriptions, …) act on.
|
|
31
|
+
Multi-line phones expose additional entries."""
|
|
32
|
+
...
|
|
33
|
+
|
|
16
34
|
def phone_start(self) -> None:
|
|
17
35
|
"""Start the SIP phone software."""
|
|
18
36
|
...
|
|
@@ -32,6 +32,14 @@ class SipServer(Protocol):
|
|
|
32
32
|
"""IPv6 address of the SIP server, or None if not configured."""
|
|
33
33
|
...
|
|
34
34
|
|
|
35
|
+
@property
|
|
36
|
+
def aor_domain(self) -> str:
|
|
37
|
+
"""The host part callers should embed in SIP AORs targeting this
|
|
38
|
+
server (``sip:user@<aor_domain>``). May be an FQDN, an IPv4 literal,
|
|
39
|
+
or an IPv6 bracketed literal — the driver decides which form matches
|
|
40
|
+
its testbed's address-resolution context."""
|
|
41
|
+
...
|
|
42
|
+
|
|
35
43
|
# ------------------------------------------------------------------
|
|
36
44
|
# Abstract methods
|
|
37
45
|
# ------------------------------------------------------------------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: testprotocols
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Common Test Resource Layer — capability and device protocols for telco testing.
|
|
5
5
|
Author-email: Alottabits <rjvisser@alottabits.com>
|
|
6
6
|
Maintainer-email: Alottabits <rjvisser@alottabits.com>
|
|
@@ -9,8 +9,10 @@ src/testprotocols/appliance_uplinks.py
|
|
|
9
9
|
src/testprotocols/appliance_vlans.py
|
|
10
10
|
src/testprotocols/arp_client.py
|
|
11
11
|
src/testprotocols/bgp.py
|
|
12
|
+
src/testprotocols/config_ownership.py
|
|
12
13
|
src/testprotocols/conntrack.py
|
|
13
14
|
src/testprotocols/content_filtering.py
|
|
15
|
+
src/testprotocols/device_info.py
|
|
14
16
|
src/testprotocols/device_lifecycle.py
|
|
15
17
|
src/testprotocols/device_management.py
|
|
16
18
|
src/testprotocols/dhcp_client.py
|
|
@@ -38,6 +40,7 @@ src/testprotocols/mac_table.py
|
|
|
38
40
|
src/testprotocols/multicast_client.py
|
|
39
41
|
src/testprotocols/nat.py
|
|
40
42
|
src/testprotocols/netem_controller.py
|
|
43
|
+
src/testprotocols/network_attachment.py
|
|
41
44
|
src/testprotocols/network_endpoint.py
|
|
42
45
|
src/testprotocols/network_probe.py
|
|
43
46
|
src/testprotocols/nmap_scanner.py
|
|
@@ -47,6 +50,7 @@ src/testprotocols/ospf.py
|
|
|
47
50
|
src/testprotocols/packet_filter.py
|
|
48
51
|
src/testprotocols/pcap_capture.py
|
|
49
52
|
src/testprotocols/pdu_controller.py
|
|
53
|
+
src/testprotocols/placement.py
|
|
50
54
|
src/testprotocols/port_poe.py
|
|
51
55
|
src/testprotocols/port_security.py
|
|
52
56
|
src/testprotocols/port_status.py
|
|
@@ -120,6 +124,8 @@ src/testprotocols/models/traffic.py
|
|
|
120
124
|
src/testprotocols/models/wan_edge.py
|
|
121
125
|
src/testprotocols/models/wifi.py
|
|
122
126
|
tests/test_appliance_vlans.py
|
|
127
|
+
tests/test_archetype_purity.py
|
|
128
|
+
tests/test_boundary_capability_templates.py
|
|
123
129
|
tests/test_cpe_templates.py
|
|
124
130
|
tests/test_device_types.py
|
|
125
131
|
tests/test_dhcp_templates.py
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Archetype-purity drift gate.
|
|
2
|
+
|
|
3
|
+
Device archetypes must compose capability protocols only; the sole sanctioned
|
|
4
|
+
exception is the BaseDeviceProtocol identity pair. Enforced twice: here (the
|
|
5
|
+
merge gate — parametrized over the registry, so every future archetype is
|
|
6
|
+
covered the moment it is registered) and in register_device_type itself (the
|
|
7
|
+
import-time gate that also covers plugin-local archetypes downstream). See
|
|
8
|
+
docs/architecture/capability-only-archetypes.md.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Protocol, runtime_checkable
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
from testprotocols.devices import (
|
|
17
|
+
DeviceTypeSpec,
|
|
18
|
+
all_device_types,
|
|
19
|
+
get_device_type,
|
|
20
|
+
non_capability_members,
|
|
21
|
+
register_device_type,
|
|
22
|
+
)
|
|
23
|
+
from testprotocols.devices.base import BaseDeviceProtocol
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.mark.parametrize(
|
|
27
|
+
("name", "spec"),
|
|
28
|
+
sorted(all_device_types().items()),
|
|
29
|
+
ids=lambda v: v if isinstance(v, str) else "",
|
|
30
|
+
)
|
|
31
|
+
def test_archetype_members_are_capability_protocols(name: str, spec: DeviceTypeSpec) -> None:
|
|
32
|
+
offenders = non_capability_members(spec.protocol)
|
|
33
|
+
assert not offenders, (
|
|
34
|
+
f"{name}: non-capability members {sorted(offenders)} — archetypes compose "
|
|
35
|
+
"capability protocols only (docs/architecture/capability-only-archetypes.md)"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_checker_detects_scalar_members() -> None:
|
|
40
|
+
"""The checker flags annotated scalars AND property-declared scalars."""
|
|
41
|
+
|
|
42
|
+
@runtime_checkable
|
|
43
|
+
class _Rogue(BaseDeviceProtocol, Protocol):
|
|
44
|
+
rogue_scalar: str
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def rogue_property(self) -> int: ...
|
|
48
|
+
|
|
49
|
+
assert non_capability_members(_Rogue) == {"rogue_scalar", "rogue_property"}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_register_rejects_non_capability_archetype() -> None:
|
|
53
|
+
@runtime_checkable
|
|
54
|
+
class _Rogue(BaseDeviceProtocol, Protocol):
|
|
55
|
+
rogue_scalar: str
|
|
56
|
+
|
|
57
|
+
with pytest.raises(TypeError, match="rogue_scalar"):
|
|
58
|
+
register_device_type("_rogue_purity_probe", _Rogue)
|
|
59
|
+
assert get_device_type("_rogue_purity_probe") is None, "must not register on failure"
|