Repository: aristanetworks/anta
Pull request #1038 (2025-02-11 10:55:59)

refactor(anta.tests): Nicer result failure messages PTP, software test module
Nicer result failure messages for following test module.
     1 . PTP
     2.  Software
  
**Note:** Test with unit test only, as this testcases are skipped on ATD lab
Fixes https://github.com/aristanetworks/anta/issues/587

# Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have run pre-commit for code linting and typing (`pre-commit run`)
- [ ] I have made corresponding changes to the documentation
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes (`tox -e testenv`)


sonarqubecloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1038) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1038&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1038&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=aristanetworks_anta&pullRequest=1038&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1038&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1038&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1038)


sonarqubecloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1038) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1038&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1038&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=aristanetworks_anta&pullRequest=1038&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1038&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1038&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1038)


sonarqubecloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1038) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1038&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1038&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=aristanetworks_anta&pullRequest=1038&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1038&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1038&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1038)



Patch:
diff --git a/anta/custom_types.py b/anta/custom_types.py
index ccd0b5f6..aa36d2fc 100644
--- a/anta/custom_types.py
+++ b/anta/custom_types.py
@@ -24,6 +24,13 @@ REGEXP_TYPE_HOSTNAME = r"^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)
 """Match hostname like `my-hostname`, `my-hostname-1`, `my-hostname-1-2`."""
 
 
+# Regular expression for BGP redistributed routes
+REGEX_IPV4_UNICAST = r"ipv4[-_ ]?unicast$"
+REGEX_IPV4_MULTICAST = r"ipv4[-_ ]?multicast$"
+REGEX_IPV6_UNICAST = r"ipv6[-_ ]?unicast$"
+REGEX_IPV6_MULTICAST = r"ipv6[-_ ]?multicast$"
+
+
 def aaa_group_prefix(v: str) -> str:
     """Prefix the AAA method with 'group' if it is known."""
     built_in_methods = ["local", "none", "logging"]
@@ -92,10 +99,10 @@ def bgp_multiprotocol_capabilities_abbreviations(value: str) -> str:
     patterns = {
         f"{r'dynamic[-_ ]?path[-_ ]?selection$'}": "dps",
         f"{r'dps$'}": "dps",
-        f"{r'ipv4[-_ ]?unicast$'}": "ipv4Unicast",
-        f"{r'ipv6[-_ ]?unicast$'}": "ipv6Unicast",
-        f"{r'ipv4[-_ ]?multicast$'}": "ipv4Multicast",
-        f"{r'ipv6[-_ ]?multicast$'}": "ipv6Multicast",
+        f"{REGEX_IPV4_UNICAST}": "ipv4Unicast",
+        f"{REGEX_IPV6_UNICAST}": "ipv6Unicast",
+        f"{REGEX_IPV4_MULTICAST}": "ipv4Multicast",
+        f"{REGEX_IPV6_MULTICAST}": "ipv6Multicast",
         f"{r'ipv4[-_ ]?labeled[-_ ]?Unicast$'}": "ipv4MplsLabels",
         f"{r'ipv4[-_ ]?mpls[-_ ]?labels$'}": "ipv4MplsLabels",
         f"{r'ipv6[-_ ]?labeled[-_ ]?Unicast$'}": "ipv6MplsLabels",
@@ -132,6 +139,54 @@ def validate_regex(value: str) -> str:
     return value
 
 
+def bgp_redistributed_route_proto_abbreviations(value: str) -> str:
+    """Abbreviations for different BGP redistributed route protocols.
+
+    Handles different separators (hyphen, underscore, space) and case sensitivity.
+
+    Examples
+    --------
+    ```python
+    >>> bgp_redistributed_route_proto_abbreviations("IPv4 Unicast")
+    'v4u'
+    >>> bgp_redistributed_route_proto_abbreviations("IPv4-multicast")
+    'v4m'
+    >>> bgp_redistributed_route_proto_abbreviations("IPv6_multicast")
+    'v6m'
+    >>> bgp_redistributed_route_proto_abbreviations("ipv6unicast")
+    'v6u'
+    ```
+    """
+    patterns = {REGEX_IPV4_UNICAST: "v4u", REGEX_IPV4_MULTICAST: "v4m", REGEX_IPV6_UNICAST: "v6u", REGEX_IPV6_MULTICAST: "v6m"}
+
+    for pattern, replacement in patterns.items():
+        match = re.match(pattern, value, re.IGNORECASE)
+        if match:
+            return replacement
+
+    return value
+
+
+def update_bgp_redistributed_proto_user(value: str) -> str:
+    """Update BGP redistributed route `User` proto with EOS SDK.
+
+    Examples
+    --------
+    ```python
+    >>> update_bgp_redistributed_proto_user("User")
+    'EOS SDK'
+    >>> update_bgp_redistributed_proto_user("Bgp")
+    'Bgp'
+    >>> update_bgp_redistributed_proto_user("RIP")
+    'RIP'
+    ```
+    """
+    if value == "User":
+        value = "EOS SDK"
+
+    return value
+
+
 # AntaTest.Input types
 AAAAuthMethod = Annotated[str, AfterValidator(aaa_group_prefix)]
 Vlan = Annotated[int, Field(ge=0, le=4094)]
@@ -319,3 +374,23 @@ SnmpErrorCounter = Literal[
     "inVersionErrs", "inBadCommunityNames", "inBadCommunityUses", "inParseErrs", "outTooBigErrs", "outNoSuchNameErrs", "outBadValueErrs", "outGeneralErrs"
 ]
 SnmpVersionV3AuthType = Annotated[Literal["auth", "priv", "noauth"], AfterValidator(snmp_v3_prefix)]
+RedistributedProtocol = Annotated[
+    Literal[
+        "AttachedHost",
+        "Bgp",
+        "Connected",
+        "Dynamic",
+        "IS-IS",
+        "OSPF Internal",
+        "OSPF External",
+        "OSPF Nssa-External",
+        "OSPFv3 Internal",
+        "OSPFv3 External",
+        "OSPFv3 Nssa-External",
+        "RIP",
+        "Static",
+        "User",
+    ],
+    AfterValidator(update_bgp_redistributed_proto_user),
+]
+RedistributedAfiSafi = Annotated[Literal["v4u", "v4m", "v6u", "v6m"], BeforeValidator(bgp_redistributed_route_proto_abbreviations)]
diff --git a/anta/input_models/routing/bgp.py b/anta/input_models/routing/bgp.py
index 945b0305..5c9226ec 100644
--- a/anta/input_models/routing/bgp.py
+++ b/anta/input_models/routing/bgp.py
@@ -12,7 +12,7 @@ from warnings import warn
 from pydantic import BaseModel, ConfigDict, Field, PositiveInt, model_validator
 from pydantic_extra_types.mac_address import MacAddress
 
-from anta.custom_types import Afi, BgpDropStats, BgpUpdateError, MultiProtocolCaps, Safi, Vni
+from anta.custom_types import Afi, BgpDropStats, BgpUpdateError, MultiProtocolCaps, RedistributedAfiSafi, RedistributedProtocol, Safi, Vni
 
 if TYPE_CHECKING:
     import sys
@@ -68,8 +68,7 @@ class BgpAddressFamily(BaseModel):
     check_peer_state: bool = False
     """Flag to check if the peers are established with negotiated AFI/SAFI. Defaults to `False`.
 
-    Can be enabled in the `VerifyBGPPeerCount` tests.
-    """
+    Can be enabled in the `VerifyBGPPeerCount` tests."""
 
     @model_validator(mode="after")
     def validate_inputs(self) -> Self:
@@ -256,3 +255,73 @@ class BgpRoutePath(BaseModel):
         - Next-hop: 192.168.66.101 Origin: Igp
         """
         return f"Next-hop: {self.nexthop} Origin: {self.origin}"
+
+
+class BgpVrf(BaseModel):
+    """Model representing a VRF in a BGP instance."""
+
+    vrf: str = "default"
+    """VRF context."""
+    address_families: list[AddressFamilyConfig]
+    """List of address family configuration."""
+
+    def __str__(self) -> str:
+        """Return a human-readable string representation of the BgpVrf for reporting.
+
+        Examples
+        --------
+        - VRF: default
+        """
+        return f"VRF: {self.vrf}"
+
+
+class RedistributedRouteConfig(BaseModel):
+    """Model representing a BGP redistributed route configuration."""
+
+    proto: RedistributedProtocol
+    """The redistributed protocol."""
+    include_leaked: bool = False
+    """Flag to include leaked routes of the redistributed protocol while redistributing."""
+    route_map: str | None = None
+    """Optional route map applied to the redistribution."""
+
+    @model_validator(mode="after")
+    def validate_inputs(self) -> Self:
+        """Validate that 'include_leaked' is not set when the redistributed protocol is AttachedHost, User, Dynamic, or RIP."""
+        if self.include_leaked and self.proto in ["AttachedHost", "EOS SDK", "Dynamic", "RIP"]:
+            msg = f"'include_leaked' field is not supported for redistributed protocol '{self.proto}'"
+            raise ValueError(msg)
+        return self
+
+    def __str__(self) -> str:
+        """Return a human-readable string representation of the RedistributedRouteConfig for reporting.
+
+        Examples
+        --------
+        - Proto: Connected, Include Leaked: True, Route Map: RM-CONN-2-BGP
+        """
+        base_string = f"Proto: {self.proto}"
+        if self.include_leaked:
+            base_string += f", Include Leaked: {self.include_leaked}"
+        if self.route_map:
+            base_string += f", Route Map: {self.route_map}"
+        return base_string
+
+
+class AddressFamilyConfig(BaseModel):
+    """Model representing a BGP address family configuration."""
+
+    afi_safi: RedistributedAfiSafi
+    """AFI/SAFI abbreviation per EOS."""
+    redistributed_routes: list[RedistributedRouteConfig]
+    """List of redistributed route configuration."""
+
+    def __str__(self) -> str:
+        """Return a human-readable string representation of the AddressFamilyConfig for reporting.
+
+        Examples
+        --------
+        - AFI-SAFI: IPv4 Unicast
+        """
+        mappings = {"v4u": "IPv4 Unicast", "v4m": "IPv4 Multicast", "v6u": "IPv6 Unicast", "v6m": "IPv6 Multicast"}
+        return f"AFI-SAFI: {mappings[self.afi_safi]}"
diff --git a/anta/input_models/services.py b/anta/input_models/services.py
index 25d772e4..0c602c8e 100644
--- a/anta/input_models/services.py
+++ b/anta/input_models/services.py
@@ -32,7 +32,7 @@ class DnsServer(BaseModel):
         --------
         Server 10.0.0.1 (VRF: default, Priority: 1)
         """
-        return f"Server {self.server_address} (VRF: {self.vrf}, Priority: {self.priority})"
+        return f"Server {self.server_address} VRF: {self.vrf} Priority: {self.priority}"
 
 
 class ErrdisableRecovery(BaseModel):


Test patch:
diff --git a/anta/tests/ptp.py b/anta/tests/ptp.py
index e38d58ef..5fcd8554 100644
--- a/anta/tests/ptp.py
+++ b/anta/tests/ptp.py
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
 
 
 class VerifyPtpModeStatus(AntaTest):
-    """Verifies that the device is configured as a Precision Time Protocol (PTP) Boundary Clock (BC).
+    """Verifies that the device is configured as a PTP Boundary Clock.
 
     Expected Results
     ----------------
@@ -33,7 +33,6 @@ class VerifyPtpModeStatus(AntaTest):
     ```
     """
 
-    description = "Verifies that the device is configured as a PTP Boundary Clock."
     categories: ClassVar[list[str]] = ["ptp"]
     commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show ptp", revision=2)]
 
@@ -48,13 +47,13 @@ class VerifyPtpModeStatus(AntaTest):
             return
 
         if ptp_mode != "ptpBoundaryClock":
-            self.result.is_failure(f"The device is not configured as a PTP Boundary Clock: '{ptp_mode}'")
+            self.result.is_failure(f"Not configured as a PTP Boundary Clock - Actual: {ptp_mode}")
         else:
             self.result.is_success()
 
 
 class VerifyPtpGMStatus(AntaTest):
-    """Verifies that the device is locked to a valid Precision Time Protocol (PTP) Grandmaster (GM).
+    """Verifies that the device is locked to a valid PTP Grandmaster.
 
     To test PTP failover, re-run the test with a secondary GMID configured.
 
@@ -79,7 +78,6 @@ class VerifyPtpGMStatus(AntaTest):
         gmid: str
         """Identifier of the Grandmaster to which the device should be locked."""
 
-    description = "Verifies that the device is locked to a valid PTP Grandmaster."
     categories: ClassVar[list[str]] = ["ptp"]
     commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show ptp", revision=2)]
 
@@ -102,7 +100,7 @@ class VerifyPtpGMStatus(AntaTest):
 
 
 class VerifyPtpLockStatus(AntaTest):
-    """Verifies that the device was locked to the upstream Precision Time Protocol (PTP) Grandmaster (GM) in the last minute.
+    """Verifies that the device was locked to the upstream PTP GM in the last minute.
 
     Expected Results
     ----------------
@@ -118,7 +116,6 @@ class VerifyPtpLockStatus(AntaTest):
     ```
     """
 
-    description = "Verifies that the device was locked to the upstream PTP GM in the last minute."
     categories: ClassVar[list[str]] = ["ptp"]
     commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show ptp", revision=2)]
 
@@ -136,13 +133,13 @@ class VerifyPtpLockStatus(AntaTest):
         time_difference = ptp_clock_summary["currentPtpSystemTime"] - ptp_clock_summary["lastSyncTime"]
 
         if time_difference >= threshold:
-            self.result.is_failure(f"The device lock is more than {threshold}s old: {time_difference}s")
+            self.result.is_failure(f"Lock is more than {threshold}s old - Actual: {time_difference}s")
         else:
             self.result.is_success()
 
 
 class VerifyPtpOffset(AntaTest):
-    """Verifies that the Precision Time Protocol (PTP) timing offset is within +/- 1000ns from the master clock.
+    """Verifies that the PTP timing offset is within +/- 1000ns from the master clock.
 
     Expected Results
     ----------------
@@ -158,7 +155,6 @@ class VerifyPtpOffset(AntaTest):
     ```
     """
 
-    description = "Verifies that the PTP timing offset is within +/- 1000ns from the master clock."
     categories: ClassVar[list[str]] = ["ptp"]
     commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show ptp monitor", revision=1)]
 
@@ -167,9 +163,9 @@ class VerifyPtpOffset(AntaTest):
     def test(self) -> None:
         """Main test function for VerifyPtpOffset."""
         threshold = 1000
-        offset_interfaces: dict[str, list[int]] = {}
+        self.result.is_success()
         command_output = self.instance_commands[0].json_output
-
+        offset_interfaces: dict[str, list[int]] = {}
         if not command_output["ptpMonitorData"]:
             self.result.is_skipped("PTP is not configured")
             return
@@ -178,14 +174,12 @@ class VerifyPtpOffset(AntaTest):
             if abs(interface["offsetFromMaster"]) > threshold:
                 offset_interfaces.setdefault(interface["intf"], []).append(interface["offsetFromMaster"])
 
-        if offset_interfaces:
-            self.result.is_failure(f"The device timing offset from master is greater than +/- {threshold}ns: {offset_interfaces}")
-        else:
-            self.result.is_success()
+        for interface, data in offset_interfaces.items():
+            self.result.is_failure(f"Interface: {interface} - Timing offset from master is greater than +/- {threshold}ns: Actual: {', '.join(map(str, data))}")
 
 
 class VerifyPtpPortModeStatus(AntaTest):
-    """Verifies that all interfaces are in a valid Precision Time Protocol (PTP) state.
+    """Verifies the PTP interfaces state.
 
     The interfaces can be in one of the following state: Master, Slave, Passive, or Disabled.
 
@@ -202,7 +196,6 @@ class VerifyPtpPortModeStatus(AntaTest):
     ```
     """
 
-    description = "Verifies the PTP interfaces state."
     categories: ClassVar[list[str]] = ["ptp"]
     commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show ptp", revision=2)]
 
@@ -227,4 +220,4 @@ class VerifyPtpPortModeStatus(AntaTest):
         if not invalid_interfaces:
             self.result.is_success()
         else:
-            self.result.is_failure(f"The following interface(s) are not in a valid PTP state: '{invalid_interfaces}'")
+            self.result.is_failure(f"The following interface(s) are not in a valid PTP state: {', '.join(invalid_interfaces)}")
diff --git a/anta/tests/routing/bgp.py b/anta/tests/routing/bgp.py
index b2677340..7522c254 100644
--- a/anta/tests/routing/bgp.py
+++ b/anta/tests/routing/bgp.py
@@ -11,13 +11,15 @@ from typing import Any, ClassVar, TypeVar
 
 from pydantic import field_validator
 
-from anta.input_models.routing.bgp import BgpAddressFamily, BgpAfi, BgpNeighbor, BgpPeer, BgpRoute, VxlanEndpoint
+from anta.input_models.routing.bgp import BgpAddressFamily, BgpAfi, BgpNeighbor, BgpPeer, BgpRoute, BgpVrf, VxlanEndpoint
 from anta.models import AntaCommand, AntaTemplate, AntaTest
 from anta.tools import format_data, get_item, get_value
 
 # Using a TypeVar for the BgpPeer model since mypy thinks it's a ClassVar and not a valid type when used in field validators
 T = TypeVar("T", bound=BgpPeer)
 
+# TODO: Refactor to reduce the number of lines in this module later
+
 
 def _check_bgp_neighbor_capability(capability_status: dict[str, bool]) -> bool:
     """Check if a BGP neighbor capability is advertised, received, and enabled.
@@ -1797,3 +1799,97 @@ class VerifyBGPRouteECMP(AntaTest):
             # Verify BGP and RIB nexthops are same.
             if len(bgp_nexthops) != len(route_entry["vias"]):
                 self.result.is_failure(f"{route} - Nexthops count mismatch - BGP: {len(bgp_nexthops)}, RIB: {len(route_entry['vias'])}")
+
+
+class VerifyBGPRedistribution(AntaTest):
+    """Verifies BGP redistribution.
+
+    This test performs the following checks for each specified VRF in the BGP instance:
+
+      1. Ensures that the expected address-family is configured on the device.
+      2. Confirms that the redistributed route protocol, include leaked and route map match the expected values.
+
+
+    Expected Results
+    ----------------
+    * Success: If all of the following conditions are met:
+        - The expected address-family is configured on the device.
+        - The redistributed route protocol, include leaked and route map align with the expected values for the route.
+    * Failure: If any of the following occur:
+        - The expected address-family is not configured on device.
+        - The redistributed route protocol, include leaked or route map does not match the expected values.
+
+    Examples
+    --------
+    ```yaml
+    anta.tests.routing:
+      bgp:
+        - VerifyBGPRedistribution:
+            vrfs:
+              - vrf: default
+                address_families:
+                  - afi_safi: ipv4Unicast
+                    redistributed_routes:
+                      - proto: Connected
+                        include_leaked: True
+                        route_map: RM-CONN-2-BGP
+                      - proto: Static
+                        include_leaked: True
+                        route_map: RM-CONN-2-BGP
+                  - afi_safi: IPv6 Unicast
+                    redistributed_routes:
+                      - proto: User # Converted to EOS SDK
+                        route_map: RM-CONN-2-BGP
+                      - proto: Static
+                        include_leaked: True
+                        route_map: RM-CONN-2-BGP
+    ```
+    """
+
+    categories: ClassVar[list[str]] = ["bgp"]
+    commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show bgp instance vrf all", revision=4)]
+
+    class Input(AntaTest.Input):
+        """Input model for the VerifyBGPRedistribution test."""
+
+        vrfs: list[BgpVrf]
+        """List of VRFs in the BGP instance."""
+
+    def _validate_redistribute_route(self, vrf_data: str, addr_family: str, afi_safi_configs: list[dict[str, Any]], route_info: dict[str, Any]) -> list[Any]:
+        """Validate the redstributed route details for a given address family."""
+        failure_msg = []
+        # If the redistributed route protocol does not match the expected value, test fails.
+        if not (actual_route := get_item(afi_safi_configs.get("redistributedRoutes"), "proto", route_info.proto)):
+            failure_msg.append(f"{vrf_data}, {addr_family}, Proto: {route_info.proto} - Not configured")
+            return failure_msg
+
+        # If includes leaked field applicable, and it does not matches the expected value, test fails.
+        if (act_include_leaked := actual_route.get("includeLeaked", False)) != route_info.include_leaked:
+            failure_msg.append(f"{vrf_data}, {addr_family}, {route_info} - Include leaked mismatch - Actual: {act_include_leaked}")
+
+        # If route map is required and it is not matching the expected value, test fails.
+        if all([route_info.route_map, (act_route_map := actual_route.get("routeMap", "Not Found")) != route_info.route_map]):
+            failure_msg.append(f"{vrf_data}, {addr_family}, {route_info} - Route map mismatch - Actual: {act_route_map}")
+        return failure_msg
+
+    @AntaTest.anta_test
+    def test(self) -> None:
+        """Main test function for VerifyBGPRedistribution."""
+        self.result.is_success()
+        command_output = self.instance_commands[0].json_output
+
+        for vrf_data in self.inputs.vrfs:
+            # If the specified VRF details are not found, test fails.
+            if not (instance_details := get_value(command_output, f"vrfs.{vrf_data.vrf}")):
+                self.result.is_failure(f"{vrf_data} - Not configured")
+                continue
+            for address_family in vrf_data.address_families:
+                # If the AFI-SAFI configuration details are not found, test fails.
+                if not (afi_safi_configs := get_value(instance_details, f"afiSafiConfig.{address_family.afi_safi}")):
+                    self.result.is_failure(f"{vrf_data}, {address_family} - Not redistributed")
+                    continue
+
+                for route_info in address_family.redistributed_routes:
+                    failure_msg = self._validate_redistribute_route(str(vrf_data), str(address_family), afi_safi_configs, route_info)
+                    for msg in failure_msg:
+                        self.result.is_failure(msg)
diff --git a/anta/tests/services.py b/anta/tests/services.py
index 8d894216..a2b09da3 100644
--- a/anta/tests/services.py
+++ b/anta/tests/services.py
@@ -46,7 +46,7 @@ class VerifyHostname(AntaTest):
         hostname = self.instance_commands[0].json_output["hostname"]
 
         if hostname != self.inputs.hostname:
-            self.result.is_failure(f"Expected `{self.inputs.hostname}` as the hostname, but found `{hostname}` instead.")
+            self.result.is_failure(f"Incorrect Hostname - Expected: {self.inputs.hostname} Actual: {hostname}")
         else:
             self.result.is_success()
 
diff --git a/anta/tests/snmp.py b/anta/tests/snmp.py
index e0aecaf6..09bdd768 100644
--- a/anta/tests/snmp.py
+++ b/anta/tests/snmp.py
@@ -21,7 +21,7 @@ if TYPE_CHECKING:
 
 
 class VerifySnmpStatus(AntaTest):
-    """Verifies whether the SNMP agent is enabled in a specified VRF.
+    """Verifies if the SNMP agent is enabled.
 
     Expected Results
     ----------------
@@ -37,7 +37,6 @@ class VerifySnmpStatus(AntaTest):
     ```
     """
 
-    description = "Verifies if the SNMP agent is enabled."
     categories: ClassVar[list[str]] = ["snmp"]
     commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show snmp", revision=1)]
 
@@ -50,15 +49,14 @@ class VerifySnmpStatus(AntaTest):
     @AntaTest.anta_test
     def test(self) -> None:
         """Main test function for VerifySnmpStatus."""
+        self.result.is_success()
         command_output = self.instance_commands[0].json_output
-        if command_output["enabled"] and self.inputs.vrf in command_output["vrfs"]["snmpVrfs"]:
-            self.result.is_success()
-        else:
-            self.result.is_failure(f"SNMP agent disabled in vrf {self.inputs.vrf}")
+        if not (command_output["enabled"] and self.inputs.vrf in command_output["vrfs"]["snmpVrfs"]):
+            self.result.is_failure(f"VRF: {self.inputs.vrf} - SNMP agent disabled")
 
 
 class VerifySnmpIPv4Acl(AntaTest):
-    """Verifies if the SNMP agent has the right number IPv4 ACL(s) configured for a specified VRF.
+    """Verifies if the SNMP agent has IPv4 ACL(s) configured.
 
     Expected Results
     ----------------
@@ -75,7 +73,6 @@ class VerifySnmpIPv4Acl(AntaTest):
     ```
     """
 
-    description = "Verifies if the SNMP agent has IPv4 ACL(s) configured."
     categories: ClassVar[list[str]] = ["snmp"]
     commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show snmp ipv4 access-list summary", revision=1)]
 
@@ -90,23 +87,22 @@ class VerifySnmpIPv4Acl(AntaTest):
     @AntaTest.anta_test
     def test(self) -> None:
         """Main test function for VerifySnmpIPv4Acl."""
+        self.result.is_success()
         command_output = self.instance_commands[0].json_output
         ipv4_acl_list = command_output["ipAclList"]["aclList"]
         ipv4_acl_number = len(ipv4_acl_list)
         if ipv4_acl_number != self.inputs.number:
-            self.result.is_failure(f"Expected {self.inputs.number} SNMP IPv4 ACL(s) in vrf {self.inputs.vrf} but got {ipv4_acl_number}")
+            self.result.is_failure(f"VRF: {self.inputs.vrf} - Incorrect SNMP IPv4 ACL(s) - Expected: {self.inputs.number} Actual: {ipv4_acl_number}")
             return
 
         not_configured_acl = [acl["name"] for acl in ipv4_acl_list if self.inputs.vrf not in acl["configuredVrfs"] or self.inputs.vrf not in acl["activeVrfs"]]
 
         if not_configured_acl:
-            self.result.is_failure(f"SNMP IPv4 ACL(s) not configured or active in vrf {self.inputs.vrf}: {not_configured_acl}")
-        else:
-            self.result.is_success()
+            self.result.is_failure(f"VRF: {self.inputs.vrf} - Following SNMP IPv4 ACL(s) not configured or active: {', '.join(not_configured_acl)}")
 
 
 class VerifySnmpIPv6Acl(AntaTest):
-    """Verifies if the SNMP agent has the right number IPv6 ACL(s) configured for a specified VRF.
+    """Verifies if the SNMP agent has IPv6 ACL(s) configured.
 
     Expected Results
     ----------------
@@ -123,7 +119,6 @@ class VerifySnmpIPv6Acl(AntaTest):
     ```
     """
 
-    description = "Verifies if the SNMP agent has IPv6 ACL(s) configured."
     categories: ClassVar[list[str]] = ["snmp"]
     commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show snmp ipv6 access-list summary", revision=1)]
 
@@ -139,18 +134,17 @@ class VerifySnmpIPv6Acl(AntaTest):
     def test(self) -> None:
         """Main test function for VerifySnmpIPv6Acl."""
         command_output = self.instance_commands[0].json_output
+        self.result.is_success()
         ipv6_acl_list = command_output["ipv6AclList"]["aclList"]
         ipv6_acl_number = len(ipv6_acl_list)
         if ipv6_acl_number != self.inputs.number:
-            self.result.is_failure(f"Expected {self.inputs.number} SNMP IPv6 ACL(s) in vrf {self.inputs.vrf} but got {ipv6_acl_number}")
+            self.result.is_failure(f"VRF: {self.inputs.vrf} - Incorrect SNMP IPv6 ACL(s) - Expected: {self.inputs.number} Actual: {ipv6_acl_number}")
             return
 
         acl_not_configured = [acl["name"] for acl in ipv6_acl_list if self.inputs.vrf not in acl["configuredVrfs"] or self.inputs.vrf not in acl["activeVrfs"]]
 
         if acl_not_configured:
-            self.result.is_failure(f"SNMP IPv6 ACL(s) not configured or active in vrf {self.inputs.vrf}: {acl_not_configured}")
-        else:
-            self.result.is_success()
+            self.result.is_failure(f"VRF: {self.inputs.vrf} - Following SNMP IPv6 ACL(s) not configured or active: {', '.join(acl_not_configured)}")
 
 
 class VerifySnmpLocation(AntaTest):
@@ -182,6 +176,7 @@ class VerifySnmpLocation(AntaTest):
     @AntaTest.anta_test
     def test(self) -> None:
         """Main test function for VerifySnmpLocation."""
+        self.result.is_success()
         # Verifies the SNMP location is configured.
         if not (location := get_value(self.instance_commands[0].json_output, "location.location")):
             self.result.is_failure("SNMP location is not configured.")
@@ -189,9 +184,7 @@ class VerifySnmpLocation(AntaTest):
 
         # Verifies the expected SNMP location.
         if location != self.inputs.location:
-            self.result.is_failure(f"Expected `{self.inputs.location}` as the location, but found `{location}` instead.")
-        else:
-            self.result.is_success()
+            self.result.is_failure(f"Incorrect SNMP location - Expected: {self.inputs.location} Actual: {location}")
 
 
 class VerifySnmpContact(AntaTest):
@@ -223,6 +216,7 @@ class VerifySnmpContact(AntaTest):
     @AntaTest.anta_test
     def test(self) -> None:
         """Main test function for VerifySnmpContact."""
+        self.result.is_success()
         # Verifies the SNMP contact is configured.
         if not (contact := get_value(self.instance_commands[0].json_output, "contact.contact")):
             self.result.is_failure("SNMP contact is not configured.")
@@ -230,9 +224,7 @@ class VerifySnmpContact(AntaTest):
 
         # Verifies the expected SNMP contact.
         if contact != self.inputs.contact:
-            self.result.is_failure(f"Expected `{self.inputs.contact}` as the contact, but found `{contact}` instead.")
-        else:
-            self.result.is_success()
+            self.result.is_failure(f"Incorrect SNMP contact - Expected: {self.inputs.contact} Actual: {contact}")
 
 
 class VerifySnmpPDUCounters(AntaTest):
@@ -269,6 +261,7 @@ class VerifySnmpPDUCounters(AntaTest):
     @AntaTest.anta_test
     def test(self) -> None:
         """Main test function for VerifySnmpPDUCounters."""
+        self.result.is_success()
         snmp_pdus = self.inputs.pdus
         command_output = self.instance_commands[0].json_output
 
@@ -281,13 +274,11 @@ class VerifySnmpPDUCounters(AntaTest):
         if not snmp_pdus:
             snmp_pdus = list(get_args(SnmpPdu))
 
-        failures = {pdu: value for pdu in snmp_pdus if (value := pdu_counters.get(pdu, "Not Found")) == "Not Found" or value == 0}
+        failures = {pdu for pdu in snmp_pdus if (value := pdu_counters.get(pdu, "Not Found")) == "Not Found" or value == 0}
 
         # Check if any failures
-        if not failures:
-            self.result.is_success()
-        else:
-            self.result.is_failure(f"The following SNMP PDU counters are not found or have zero PDU counters:\n{failures}")
+        if failures:
+            self.result.is_failure(f"The following SNMP PDU counters are not found or have zero PDU counters: {', '.join(sorted(failures))}")
 
 
 class VerifySnmpErrorCounters(AntaTest):
@@ -323,6 +314,7 @@ class VerifySnmpErrorCounters(AntaTest):
     @AntaTest.anta_test
     def test(self) -> None:
         """Main test function for VerifySnmpErrorCounters."""
+        self.result.is_success()
         error_counters = self.inputs.error_counters
         command_output = self.instance_commands[0].json_output
 
@@ -335,13 +327,11 @@ class VerifySnmpErrorCounters(AntaTest):
         if not error_counters:
             error_counters = list(get_args(SnmpErrorCounter))
 
-        error_counters_not_ok = {counter: value for counter in error_counters if (value := snmp_counters.get(counter))}
+        error_counters_not_ok = {counter for counter in error_counters if snmp_counters.get(counter)}
 
         # Check if any failures
-        if not error_counters_not_ok:
-            self.result.is_success()
-        else:
-            self.result.is_failure(f"The following SNMP error counters are not found or have non-zero error counters:\n{error_counters_not_ok}")
+        if error_counters_not_ok:
+            self.result.is_failure(f"The following SNMP error counters are not found or have non-zero error counters: {', '.join(sorted(error_counters_not_ok))}")
 
 
 class VerifySnmpHostLogging(AntaTest):
diff --git a/anta/tests/software.py b/anta/tests/software.py
index 113f2a4f..84228ca3 100644
--- a/anta/tests/software.py
+++ b/anta/tests/software.py
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
 
 
 class VerifyEOSVersion(AntaTest):
-    """Verifies that the device is running one of the allowed EOS version.
+    """Verifies the EOS version of the device.
 
     Expected Results
     ----------------
@@ -34,7 +34,6 @@ class VerifyEOSVersion(AntaTest):
     ```
     """
 
-    description = "Verifies the EOS version of the device."
     categories: ClassVar[list[str]] = ["software"]
     commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show version", revision=1)]
 
@@ -48,14 +47,13 @@ class VerifyEOSVersion(AntaTest):
     def test(self) -> None:
         """Main test function for VerifyEOSVersion."""
         command_output = self.instance_commands[0].json_output
-        if command_output["version"] in self.inputs.versions:
-            self.result.is_success()
-        else:
-            self.result.is_failure(f'device is running version "{command_output["version"]}" not in expected versions: {self.inputs.versions}')
+        self.result.is_success()
+        if command_output["version"] not in self.inputs.versions:
+            self.result.is_failure(f"EOS version mismatch - Actual: {command_output['version']} not in Expected: {', '.join(self.inputs.versions)}")
 
 
 class VerifyTerminAttrVersion(AntaTest):
-    """Verifies that he device is running one of the allowed TerminAttr version.
+    """Verifies the TerminAttr version of the device.
 
     Expected Results
     ----------------
@@ -73,7 +71,6 @@ class VerifyTerminAttrVersion(AntaTest):
     ```
     """
 
-    description = "Verifies the TerminAttr version of the device."
     categories: ClassVar[list[str]] = ["software"]
     commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show version detail", revision=1)]
 
@@ -87,11 +84,10 @@ class VerifyTerminAttrVersion(AntaTest):
     def test(self) -> None:
         """Main test function for VerifyTerminAttrVersion."""
         command_output = self.instance_commands[0].json_output
+        self.result.is_success()
         command_output_data = command_output["details"]["packages"]["TerminAttr-core"]["version"]
-        if command_output_data in self.inputs.versions:
-            self.result.is_success()
-        else:
-            self.result.is_failure(f"device is running TerminAttr version {command_output_data} and is not in the allowed list: {self.inputs.versions}")
+        if command_output_data not in self.inputs.versions:
+            self.result.is_failure(f"TerminAttr version mismatch - Actual: {command_output_data} not in Expected: {', '.join(self.inputs.versions)}")
 
 
 class VerifyEOSExtensions(AntaTest):
@@ -120,6 +116,7 @@ class VerifyEOSExtensions(AntaTest):
     def test(self) -> None:
         """Main test function for VerifyEOSExtensions."""
         boot_extensions = []
+        self.result.is_success()
         show_extensions_command_output = self.instance_commands[0].json_output
         show_boot_extensions_command_output = self.instance_commands[1].json_output
         installed_extensions = [
@@ -131,7 +128,7 @@ class VerifyEOSExtensions(AntaTest):
                 boot_extensions.append(formatted_extension)
         installed_extensions.sort()
         boot_extensions.sort()
-        if installed_extensions == boot_extensions:
-            self.result.is_success()
-        else:
-            self.result.is_failure(f"Missing EOS extensions: installed {installed_extensions} / configured: {boot_extensions}")
+        if installed_extensions != boot_extensions:
+            str_installed_extensions = ", ".join(installed_extensions) if installed_extensions else "Not found"
+            str_boot_extensions = ", ".join(boot_extensions) if boot_extensions else "Not found"
+            self.result.is_failure(f"EOS extensions mismatch - Installed: {str_installed_extensions}, Configured: {str_boot_extensions}")
diff --git a/examples/tests.yaml b/examples/tests.yaml
index e4f08a93..f5fd3ebd 100644
--- a/examples/tests.yaml
+++ b/examples/tests.yaml
@@ -513,6 +513,26 @@ anta.tests.routing.bgp:
   - VerifyBGPPeersHealthRibd:
       # Verifies the health of all the BGP IPv4 peer(s).
       check_tcp_queues: True
+  - VerifyBGPRedistribution:
+      # Verifies BGP redistribution.
+      vrfs:
+        - vrf: default
+          address_families:
+            - afi_safi: ipv4Unicast
+              redistributed_routes:
+                - proto: Connected
+                  include_leaked: True
+                  route_map: RM-CONN-2-BGP
+                - proto: Static
+                  include_leaked: True
+                  route_map: RM-CONN-2-BGP
+            - afi_safi: IPv6 Unicast
+              redistributed_routes:
+                - proto: User # Converted to EOS SDK
+                  route_map: RM-CONN-2-BGP
+                - proto: Static
+                  include_leaked: True
+                  route_map: RM-CONN-2-BGP
   - VerifyBGPRouteECMP:
       # Verifies BGP IPv4 route ECMP paths.
       route_entries:
diff --git a/tests/units/anta_tests/routing/test_bgp.py b/tests/units/anta_tests/routing/test_bgp.py
index 418c2d0e..ee26155f 100644
--- a/tests/units/anta_tests/routing/test_bgp.py
+++ b/tests/units/anta_tests/routing/test_bgp.py
@@ -28,6 +28,7 @@ from anta.tests.routing.bgp import (
     VerifyBGPPeersHealth,
     VerifyBGPPeersHealthRibd,
     VerifyBGPPeerUpdateErrors,
+    VerifyBGPRedistribution,
     VerifyBGPRouteECMP,
     VerifyBgpRouteMaps,
     VerifyBGPRoutePaths,
@@ -5788,4 +5789,333 @@ DATA: list[dict[str, Any]] = [
         "inputs": {"route_entries": [{"prefix": "10.111.134.0/24", "vrf": "default", "ecmp_count": 2}]},
         "expected": {"result": "failure", "messages": ["Prefix: 10.111.134.0/24 VRF: default - Nexthops count mismatch - BGP: 2, RIB: 1"]},
     },
+    {
+        "name": "success",
+        "test": VerifyBGPRedistribution,
+        "eos_data": [
+            {
+                "vrfs": {
+                    "default": {
+                        "afiSafiConfig": {
+                            "v4u": {
+                                "redistributedRoutes": [
+                                    {"proto": "Connected", "includeLeaked": True, "routeMap": "RM-CONN-2-BGP"},
+                                    {"proto": "Static", "includeLeaked": True, "routeMap": "RM-CONN-2-BGP"},
+                                ]
+                            },
+                            "v6m": {
+                                "redistributedRoutes": [
+                                    {"proto": "Dynamic", "routeMap": "RM-CONN-2-BGP"},
+                                    {"proto": "IS-IS", "includeLeaked": True, "routeMap": "RM-CONN-2-BGP"},
+                                ]
+                            },
+                        }
+                    },
+                    "test": {
+                        "afiSafiConfig": {
+                            "v4u": {
+                                "redistributedRoutes": [
+                                    {"proto": "EOS SDK", "routeMap": "RM-CONN-2-BGP"},
+                                    {"proto": "OSPF Internal", "includeLeaked": True, "routeMap": "RM-CONN-2-BGP"},
+                                ]
+                            },
+                            "v6m": {
+                                "redistributedRoutes": [
+                                    {"proto": "RIP", "routeMap": "RM-CONN-2-BGP"},
+                                    {"proto": "Bgp", "includeLeaked": True, "routeMap": "RM-CONN-2-BGP"},
+                                ]
+                            },
+                        }
+                    },
+                }
+            }
+        ],
+        "inputs": {
+            "vrfs": [
+                {
+                    "vrf": "default",
+                    "address_families": [
+                        {
+                            "afi_safi": "ipv4Unicast",
+                            "redistributed_routes": [
+                                {"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"},
+                                {"proto": "Static", "include_leaked": True, "route_map": "RM-CONN-2-BGP"},
+                            ],
+                        },
+                        {
+                            "afi_safi": "IPv6 multicast",
+                            "redistributed_routes": [
+                                {"proto": "Dynamic", "route_map": "RM-CONN-2-BGP"},
+                                {"proto": "IS-IS", "include_leaked": True, "route_map": "RM-CONN-2-BGP"},
+                            ],
+                        },
+                    ],
+                },
+                {
+                    "vrf": "test",
+                    "address_families": [
+                        {
+                            "afi_safi": "ipv4 Unicast",
+                            "redistributed_routes": [
+                                {"proto": "User", "route_map": "RM-CONN-2-BGP"},
+                                {"proto": "OSPF Internal", "include_leaked": True, "route_map": "RM-CONN-2-BGP"},
+                            ],
+                        },
+                        {
+                            "afi_safi": "IPv6Multicast",
+                            "redistributed_routes": [
+                                {"proto": "RIP", "route_map": "RM-CONN-2-BGP"},
+                                {"proto": "Bgp", "include_leaked": True, "route_map": "RM-CONN-2-BGP"},
+                            ],
+                        },
+                    ],
+                },
+            ]
+        },
+        "expected": {"result": "success"},
+    },
+    {
+        "name": "failure-vrf-not-found",
+        "test": VerifyBGPRedistribution,
+        "eos_data": [
+            {
+                "vrfs": {
+                    "default": {"afiSafiConfig": {"v6m": {"redistributedRoutes": [{"proto": "Connected", "routeMap": "RM-CONN-2-BGP"}]}}},
+                    "tenant": {"afiSafiConfig": {"v4u": {"redistributedRoutes": [{"proto": "Connected"}]}}},
+                }
+            }
+        ],
+        "inputs": {
+            "vrfs": [
+                {
+                    "vrf": "default",
+                    "address_families": [
+                        {
+                            "afi_safi": "ipv6 Multicast",
+                            "redistributed_routes": [{"proto": "Connected", "include_leaked": False, "route_map": "RM-CONN-2-BGP"}],
+                        },
+                    ],
+                },
+                {
+                    "vrf": "test",
+                    "address_families": [
+                        {
+                            "afi_safi": "ipv6 Multicast",
+                            "redistributed_routes": [
+                                {"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"},
+                            ],
+                        },
+                    ],
+                },
+            ]
+        },
+        "expected": {"result": "failure", "messages": ["VRF: test - Not configured"]},
+    },
+    {
+        "name": "failure-afi-safi-config-not-found",
+        "test": VerifyBGPRedistribution,
+        "eos_data": [
+            {
+                "vrfs": {
+                    "default": {"afiSafiConfig": {"v6m": {}}},
+                    "test": {"afiSafiConfig": {"v4u": {"redistributedRoutes": [{"proto": "Connected", "routeMap": "RM-CONN-2-BGP"}]}}},
+                }
+            }
+        ],
+        "inputs": {
+            "vrfs": [
+                {
+                    "vrf": "default",
+                    "address_families": [
+                        {
+                            "afi_safi": "ipv6 Multicast",
+                            "redistributed_routes": [
+                                {"proto": "Connected", "include_leaked": True, "route_map": "RM-CONN-2-BGP"},
+                                {"proto": "Static", "include_leaked": True, "route_map": "RM-CONN-2-BGP"},
+                            ],
+                        },
+                    ],
+                },
+            ]
+        },
+        "expected": {"result": "failure", "messages": ["VRF: default, AFI-SAFI: IPv6 Multicast - Not redistributed"]},
+    },
+    {
+        "name": "failure-expected-proto-not-found",
+        "test": VerifyBGPRedistribution,
+        "eos_data": [
+            {
+                "vrfs": {
+                    "default": {
+                        "afiSafiConfig": {
+                            "v4m": {"redistributedRoutes": [{"proto": "RIP", "routeMap": "RM-CONN-2-BGP"}, {"proto": "IS-IS", "routeMap": "RM-MLAG-PEER-IN"}]}
+                        }
+                    },
+                    "test": {
+                        "afiSafiConfig": {
+                            "v6u": {
+                                "redistributedRoutes": [{"proto": "Static", "routeMap": "RM-CONN-2-BGP"}],
+                            }
+                        }
+                    },
+                }
+            }
+        ],
+        "inputs": {
+            "vrfs": [
+                {
+                    "vrf": "default",
+                    "address_families": [
+                        {
+                            "afi_safi": "ipv4 multicast",
+                            "redistributed_routes": [
+                                {"proto": "OSPFv3 External", "include_leaked": True, "route_map": "RM-CONN-2-BGP"},
+                                {"proto": "OSPFv3 Nssa-External", "include_leaked": True, "route_map": "RM-CONN-2-BGP"},
+                            ],
+                        }
+                    ],
+                },
+                {
+                    "vrf": "test",
+                    "address_families": [
+                        {
+                            "afi_safi": "IPv6Unicast",
+                            "redistributed_routes": [
+                                {"proto": "RIP", "route_map": "RM-CONN-2-BGP"},
+                                {"proto": "Bgp", "include_leaked": True, "route_map": "RM-CONN-2-BGP"},
+                            ],
+                        },
+                    ],
+                },
+            ]
+        },
+        "expected": {
+            "result": "failure",
+            "messages": [
+                "VRF: default, AFI-SAFI: IPv4 Multicast, Proto: OSPFv3 External - Not configured",
+                "VRF: default, AFI-SAFI: IPv4 Multicast, Proto: OSPFv3 Nssa-External - Not configured",
+                "VRF: test, AFI-SAFI: IPv6 Unicast, Proto: RIP - Not configured",
+                "VRF: test, AFI-SAFI: IPv6 Unicast, Proto: Bgp - Not configured",
+            ],
+        },
+    },
+    {
+        "name": "failure-route-map-not-found",
+        "test": VerifyBGPRedistribution,
+        "eos_data": [
+            {
+                "vrfs": {
+                    "default": {"afiSafiConfig": {"v4u": {"redistributedRoutes": [{"proto": "Connected", "routeMap": "RM-CONN-10-BGP"}, {"proto": "Static"}]}}},
+                    "test": {
+                        "afiSafiConfig": {
+                            "v6u": {
+                                "redistributedRoutes": [{"proto": "EOS SDK", "routeMap": "RM-MLAG-PEER-IN"}, {"proto": "OSPF Internal"}],
+                            }
+                        }
+                    },
+                }
+            }
+        ],
+        "inputs": {
+            "vrfs": [
+                {
+                    "vrf": "default",
+                    "address_families": [
+                        {
+                            "afi_safi": "ipv4 UNicast",
+                            "redistributed_routes": [
+                                {"proto": "Connected", "route_map": "RM-CONN-2-BGP"},
+                                {"proto": "Static", "route_map": "RM-CONN-2-BGP"},
+                            ],
+                        },
+                    ],
+                },
+                {
+                    "vrf": "test",
+                    "address_families": [
+                        {
+                            "afi_safi": "ipv6-Unicast",
+                            "redistributed_routes": [
+                                {"proto": "User", "route_map": "RM-CONN-2-BGP"},
+                                {"proto": "OSPF Internal", "route_map": "RM-CONN-2-BGP"},
+                            ],
+                        },
+                    ],
+                },
+            ]
+        },
+        "expected": {
+            "result": "failure",
+            "messages": [
+                "VRF: default, AFI-SAFI: IPv4 Unicast, Proto: Connected, Route Map: RM-CONN-2-BGP - Route map mismatch - Actual: RM-CONN-10-BGP",
+                "VRF: default, AFI-SAFI: IPv4 Unicast, Proto: Static, Route Map: RM-CONN-2-BGP - Route map mismatch - Actual: Not Found",
+                "VRF: test, AFI-SAFI: IPv6 Unicast, Proto: EOS SDK, Route Map: RM-CONN-2-BGP - Route map mismatch - Actual: RM-MLAG-PEER-IN",
+                "VRF: test, AFI-SAFI: IPv6 Unicast, Proto: OSPF Internal, Route Map: RM-CONN-2-BGP - Route map mismatch - Actual: Not Found",
+            ],
+        },
+    },
+    {
+        "name": "failure-incorrect-value-include-leaked",
+        "test": VerifyBGPRedistribution,
+        "eos_data": [
+            {
+                "vrfs": {
+                    "default": {
+                        "afiSafiConfig": {
+                            "v4m": {
+                                "redistributedRoutes": [
+                                    {"proto": "Dynamic", "routeMap": "RM-CONN-2-BGP"},
+                                    {"proto": "IS-IS", "includeLeaked": False, "routeMap": "RM-CONN-2-BGP"},
+                                ]
+                            },
+                        }
+                    },
+                    "test": {
+                        "afiSafiConfig": {
+                            "v6u": {
+                                "redistributedRoutes": [
+                                    {"proto": "RIP", "routeMap": "RM-CONN-2-BGP"},
+                                    {"proto": "Bgp", "includeLeaked": True, "routeMap": "RM-CONN-2-BGP"},
+                                ]
+                            },
+                        }
+                    },
+                }
+            }
+        ],
+        "inputs": {
+            "vrfs": [
+                {
+                    "vrf": "default",
+                    "address_families": [
+                        {
+                            "afi_safi": "ipv4-multicast",
+                            "redistributed_routes": [
+                                {"proto": "IS-IS", "include_leaked": True, "route_map": "RM-CONN-2-BGP"},
+                            ],
+                        },
+                    ],
+                },
+                {
+                    "vrf": "test",
+                    "address_families": [
+                        {
+                            "afi_safi": "IPv6_unicast",
+                            "redistributed_routes": [
+                                {"proto": "RIP", "route_map": "RM-CONN-2-BGP"},
+                                {"proto": "Bgp", "include_leaked": False, "route_map": "RM-CONN-2-BGP"},
+                            ],
+                        },
+                    ],
+                },
+            ]
+        },
+        "expected": {
+            "result": "failure",
+            "messages": [
+                "VRF: default, AFI-SAFI: IPv4 Multicast, Proto: IS-IS, Include Leaked: True, Route Map: RM-CONN-2-BGP - Include leaked mismatch - Actual: False",
+                "VRF: test, AFI-SAFI: IPv6 Unicast, Proto: Bgp, Route Map: RM-CONN-2-BGP - Include leaked mismatch - Actual: True",
+            ],
+        },
+    },
 ]
diff --git a/tests/units/anta_tests/test_ptp.py b/tests/units/anta_tests/test_ptp.py
index 112e3347..91f7f035 100644
--- a/tests/units/anta_tests/test_ptp.py
+++ b/tests/units/anta_tests/test_ptp.py
@@ -39,7 +39,7 @@ DATA: list[dict[str, Any]] = [
         "test": VerifyPtpModeStatus,
         "eos_data": [{"ptpMode": "ptpDisabled", "ptpIntfSummaries": {}}],
         "inputs": None,
-        "expected": {"result": "failure", "messages": ["The device is not configured as a PTP Boundary Clock: 'ptpDisabled'"]},
+        "expected": {"result": "failure", "messages": ["Not configured as a PTP Boundary Clock - Actual: ptpDisabled"]},
     },
     {
         "name": "skipped",
@@ -158,7 +158,7 @@ DATA: list[dict[str, Any]] = [
             }
         ],
         "inputs": None,
-        "expected": {"result": "failure", "messages": ["The device lock is more than 60s old: 157s"]},
+        "expected": {"result": "failure", "messages": ["Lock is more than 60s old - Actual: 157s"]},
     },
     {
         "name": "skipped",
@@ -236,7 +236,9 @@ DATA: list[dict[str, Any]] = [
         "inputs": None,
         "expected": {
             "result": "failure",
-            "messages": [("The device timing offset from master is greater than +/- 1000ns: {'Ethernet27/1': [1200, -1300]}")],
+            "messages": [
+                "Interface: Ethernet27/1 - Timing offset from master is greater than +/- 1000ns: Actual: 1200, -1300",
+            ],
         },
     },
     {
@@ -335,6 +337,6 @@ DATA: list[dict[str, Any]] = [
             }
         ],
         "inputs": None,
-        "expected": {"result": "failure", "messages": ["The following interface(s) are not in a valid PTP state: '['Ethernet53', 'Ethernet1']'"]},
+        "expected": {"result": "failure", "messages": ["The following interface(s) are not in a valid PTP state: Ethernet53, Ethernet1"]},
     },
 ]
diff --git a/tests/units/anta_tests/test_services.py b/tests/units/anta_tests/test_services.py
index 955aab0f..2b9011c3 100644
--- a/tests/units/anta_tests/test_services.py
+++ b/tests/units/anta_tests/test_services.py
@@ -25,7 +25,7 @@ DATA: list[dict[str, Any]] = [
         "inputs": {"hostname": "s1-spine1"},
         "expected": {
             "result": "failure",
-            "messages": ["Expected `s1-spine1` as the hostname, but found `s1-spine2` instead."],
+            "messages": ["Incorrect Hostname - Expected: s1-spine1 Actual: s1-spine2"],
         },
     },
     {
@@ -88,7 +88,7 @@ DATA: list[dict[str, Any]] = [
         },
         "expected": {
             "result": "failure",
-            "messages": ["Server 10.14.0.10 (VRF: default, Priority: 0) - Not configured", "Server 10.14.0.21 (VRF: MGMT, Priority: 1) - Not configured"],
+            "messages": ["Server 10.14.0.10 VRF: default Priority: 0 - Not configured", "Server 10.14.0.21 VRF: MGMT Priority: 1 - Not configured"],
         },
     },
     {
@@ -109,9 +109,9 @@ DATA: list[dict[str, Any]] = [
         "expected": {
             "result": "failure",
             "messages": [
-                "Server 10.14.0.1 (VRF: CS, Priority: 0) - Incorrect priority - Priority: 1",
-                "Server 10.14.0.11 (VRF: default, Priority: 0) - Not configured",
-                "Server 10.14.0.110 (VRF: MGMT, Priority: 0) - Not configured",
+                "Server 10.14.0.1 VRF: CS Priority: 0 - Incorrect priority - Priority: 1",
+                "Server 10.14.0.11 VRF: default Priority: 0 - Not configured",
+                "Server 10.14.0.110 VRF: MGMT Priority: 0 - Not configured",
             ],
         },
     },
diff --git a/tests/units/anta_tests/test_snmp.py b/tests/units/anta_tests/test_snmp.py
index 2c844c71..48c6bbb1 100644
--- a/tests/units/anta_tests/test_snmp.py
+++ b/tests/units/anta_tests/test_snmp.py
@@ -36,14 +36,14 @@ DATA: list[dict[str, Any]] = [
         "test": VerifySnmpStatus,
         "eos_data": [{"vrfs": {"snmpVrfs": ["default"]}, "enabled": True}],
         "inputs": {"vrf": "MGMT"},
-        "expected": {"result": "failure", "messages": ["SNMP agent disabled in vrf MGMT"]},
+        "expected": {"result": "failure", "messages": ["VRF: MGMT - SNMP agent disabled"]},
     },
     {
         "name": "failure-disabled",
         "test": VerifySnmpStatus,
         "eos_data": [{"vrfs": {"snmpVrfs": ["default"]}, "enabled": False}],
         "inputs": {"vrf": "default"},
-        "expected": {"result": "failure", "messages": ["SNMP agent disabled in vrf default"]},
+        "expected": {"result": "failure", "messages": ["VRF: default - SNMP agent disabled"]},
     },
     {
         "name": "success",
@@ -57,14 +57,14 @@ DATA: list[dict[str, Any]] = [
         "test": VerifySnmpIPv4Acl,
         "eos_data": [{"ipAclList": {"aclList": []}}],
         "inputs": {"number": 1, "vrf": "MGMT"},
-        "expected": {"result": "failure", "messages": ["Expected 1 SNMP IPv4 ACL(s) in vrf MGMT but got 0"]},
+        "expected": {"result": "failure", "messages": ["VRF: MGMT - Incorrect SNMP IPv4 ACL(s) - Expected: 1 Actual: 0"]},
     },
     {
         "name": "failure-wrong-vrf",
         "test": VerifySnmpIPv4Acl,
         "eos_data": [{"ipAclList": {"aclList": [{"type": "Ip4Acl", "name": "ACL_IPV4_SNMP", "configuredVrfs": ["default"], "activeVrfs": ["default"]}]}}],
         "inputs": {"number": 1, "vrf": "MGMT"},
-        "expected": {"result": "failure", "messages": ["SNMP IPv4 ACL(s) not configured or active in vrf MGMT: ['ACL_IPV4_SNMP']"]},
+        "expected": {"result": "failure", "messages": ["VRF: MGMT - Following SNMP IPv4 ACL(s) not configured or active: ACL_IPV4_SNMP"]},
     },
     {
         "name": "success",
@@ -78,14 +78,14 @@ DATA: list[dict[str, Any]] = [
         "test": VerifySnmpIPv6Acl,
         "eos_data": [{"ipv6AclList": {"aclList": []}}],
         "inputs": {"number": 1, "vrf": "MGMT"},
-        "expected": {"result": "failure", "messages": ["Expected 1 SNMP IPv6 ACL(s) in vrf MGMT but got 0"]},
+        "expected": {"result": "failure", "messages": ["VRF: MGMT - Incorrect SNMP IPv6 ACL(s) - Expected: 1 Actual: 0"]},
     },
     {
         "name": "failure-wrong-vrf",
         "test": VerifySnmpIPv6Acl,
         "eos_data": [{"ipv6AclList": {"aclList": [{"type": "Ip6Acl", "name": "ACL_IPV6_SNMP", "configuredVrfs": ["default"], "activeVrfs": ["default"]}]}}],
         "inputs": {"number": 1, "vrf": "MGMT"},
-        "expected": {"result": "failure", "messages": ["SNMP IPv6 ACL(s) not configured or active in vrf MGMT: ['ACL_IPV6_SNMP']"]},
+        "expected": {"result": "failure", "messages": ["VRF: MGMT - Following SNMP IPv6 ACL(s) not configured or active: ACL_IPV6_SNMP"]},
     },
     {
         "name": "success",
@@ -109,7 +109,7 @@ DATA: list[dict[str, Any]] = [
         "inputs": {"location": "New York"},
         "expected": {
             "result": "failure",
-            "messages": ["Expected `New York` as the location, but found `Europe` instead."],
+            "messages": ["Incorrect SNMP location - Expected: New York Actual: Europe"],
         },
     },
     {
@@ -148,7 +148,7 @@ DATA: list[dict[str, Any]] = [
         "inputs": {"contact": "Bob@example.com"},
         "expected": {
             "result": "failure",
-            "messages": ["Expected `Bob@example.com` as the contact, but found `Jon@example.com` instead."],
+            "messages": ["Incorrect SNMP contact - Expected: Bob@example.com Actual: Jon@example.com"],
         },
     },
     {
@@ -227,7 +227,7 @@ DATA: list[dict[str, Any]] = [
         "inputs": {},
         "expected": {
             "result": "failure",
-            "messages": ["The following SNMP PDU counters are not found or have zero PDU counters:\n{'inGetPdus': 0, 'inSetPdus': 0}"],
+            "messages": ["The following SNMP PDU counters are not found or have zero PDU counters: inGetPdus, inSetPdus"],
         },
     },
     {
@@ -245,7 +245,7 @@ DATA: list[dict[str, Any]] = [
         "inputs": {"pdus": ["inGetPdus", "outTrapPdus"]},
         "expected": {
             "result": "failure",
-            "messages": ["The following SNMP PDU counters are not found or have zero PDU counters:\n{'inGetPdus': 'Not Found', 'outTrapPdus': 'Not Found'}"],
+            "messages": ["The following SNMP PDU counters are not found or have zero PDU counters: inGetPdus, outTrapPdus"],
         },
     },
     {
@@ -319,9 +319,7 @@ DATA: list[dict[str, Any]] = [
         "inputs": {},
         "expected": {
             "result": "failure",
-            "messages": [
-                "The following SNMP error counters are not found or have non-zero error counters:\n{'inVersionErrs': 1, 'inParseErrs': 2, 'outBadValueErrs': 2}"
-            ],
+            "messages": ["The following SNMP error counters are not found or have non-zero error counters: inParseErrs, inVersionErrs, outBadValueErrs"],
         },
     },
     {
diff --git a/tests/units/anta_tests/test_software.py b/tests/units/anta_tests/test_software.py
index f0e5ea94..0bf88b83 100644
--- a/tests/units/anta_tests/test_software.py
+++ b/tests/units/anta_tests/test_software.py
@@ -35,7 +35,7 @@ DATA: list[dict[str, Any]] = [
             },
         ],
         "inputs": {"versions": ["4.27.1F"]},
-        "expected": {"result": "failure", "messages": ["device is running version \"4.27.0F\" not in expected versions: ['4.27.1F']"]},
+        "expected": {"result": "failure", "messages": ["EOS version mismatch - Actual: 4.27.0F not in Expected: 4.27.1F"]},
     },
     {
         "name": "success",
@@ -77,9 +77,8 @@ DATA: list[dict[str, Any]] = [
             },
         ],
         "inputs": {"versions": ["v1.17.1", "v1.18.1"]},
-        "expected": {"result": "failure", "messages": ["device is running TerminAttr version v1.17.0 and is not in the allowed list: ['v1.17.1', 'v1.18.1']"]},
+        "expected": {"result": "failure", "messages": ["TerminAttr version mismatch - Actual: v1.17.0 not in Expected: v1.17.1, v1.18.1"]},
     },
-    # TODO: add a test with a real extension?
     {
         "name": "success-no-extensions",
         "test": VerifyEOSExtensions,
@@ -91,11 +90,30 @@ DATA: list[dict[str, Any]] = [
         "expected": {"result": "success"},
     },
     {
-        "name": "success-empty-extension",
+        "name": "success-extensions",
         "test": VerifyEOSExtensions,
         "eos_data": [
-            {"extensions": {}, "extensionStoredDir": "flash:", "warnings": ["No extensions are available"]},
-            {"extensions": [""]},
+            {
+                "extensions": {
+                    "AristaCloudGateway-1.0.1-1.swix": {
+                        "version": "1.0.1",
+                        "release": "1",
+                        "presence": "present",
+                        "status": "installed",
+                        "boot": True,
+                        "numPackages": 1,
+                        "error": False,
+                        "vendor": "",
+                        "summary": "Arista Cloud Connect",
+                        "installedSize": 60532424,
+                        "packages": {"AristaCloudGateway-1.0.1-1.x86_64.rpm": {"version": "1.0.1", "release": "1"}},
+                        "description": "An extension for Arista Cloud Connect gateway",
+                        "affectedAgents": [],
+                        "agentsToRestart": [],
+                    },
+                }
+            },
+            {"extensions": ["AristaCloudGateway-1.0.1-1.swix"]},
         ],
         "inputs": None,
         "expected": {"result": "success"},
@@ -104,10 +122,80 @@ DATA: list[dict[str, Any]] = [
         "name": "failure",
         "test": VerifyEOSExtensions,
         "eos_data": [
-            {"extensions": {}, "extensionStoredDir": "flash:", "warnings": ["No extensions are available"]},
-            {"extensions": ["dummy"]},
+            {
+                "extensions": {
+                    "AristaCloudGateway-1.0.1-1.swix": {
+                        "version": "1.0.1",
+                        "release": "1",
+                        "presence": "present",
+                        "status": "installed",
+                        "boot": False,
+                        "numPackages": 1,
+                        "error": False,
+                        "vendor": "",
+                        "summary": "Arista Cloud Connect",
+                        "installedSize": 60532424,
+                        "packages": {"AristaCloudGateway-1.0.1-1.x86_64.rpm": {"version": "1.0.1", "release": "1"}},
+                        "description": "An extension for Arista Cloud Connect gateway",
+                        "affectedAgents": [],
+                        "agentsToRestart": [],
+                    },
+                }
+            },
+            {"extensions": []},
+        ],
+        "inputs": None,
+        "expected": {"result": "failure", "messages": ["EOS extensions mismatch - Installed: AristaCloudGateway-1.0.1-1.swix, Configured: Not found"]},
+    },
+    {
+        "name": "failure-multiple-extensions",
+        "test": VerifyEOSExtensions,
+        "eos_data": [
+            {
+                "extensions": {
+                    "AristaCloudGateway-1.0.1-1.swix": {
+                        "version": "1.0.1",
+                        "release": "1",
+                        "presence": "present",
+                        "status": "installed",
+                        "boot": False,
+                        "numPackages": 1,
+                        "error": False,
+                        "vendor": "",
+                        "summary": "Arista Cloud Connect",
+                        "installedSize": 60532424,
+                        "packages": {"AristaCloudGateway-1.0.1-1.x86_64.rpm": {"version": "1.0.1", "release": "1"}},
+                        "description": "An extension for Arista Cloud Connect gateway",
+                        "affectedAgents": [],
+                        "agentsToRestart": [],
+                    },
+                    "EOS-4.33.0F-NDRSensor.swix": {
+                        "version": "4.33.0",
+                        "release": "39050855.4330F",
+                        "presence": "present",
+                        "status": "notInstalled",
+                        "boot": True,
+                        "numPackages": 9,
+                        "error": False,
+                        "statusDetail": "No RPMs are compatible with current EOS version.",
+                        "vendor": "",
+                        "summary": "NDR sensor",
+                        "installedSize": 0,
+                        "packages": {},
+                        "description": "NDR sensor provides libraries to generate flow activity records using DPI\nmetadata and IPFIX flow records.",
+                        "affectedAgents": [],
+                        "agentsToRestart": [],
+                    },
+                }
+            },
+            {"extensions": ["AristaCloudGateway-1.0.1-1.swix", "EOS-4.33.0F-NDRSensor.swix"]},
         ],
         "inputs": None,
-        "expected": {"result": "failure", "messages": ["Missing EOS extensions: installed [] / configured: ['dummy']"]},
+        "expected": {
+            "result": "failure",
+            "messages": [
+                "EOS extensions mismatch - Installed: AristaCloudGateway-1.0.1-1.swix, Configured: AristaCloudGateway-1.0.1-1.swix, EOS-4.33.0F-NDRSensor.swix"
+            ],
+        },
     },
 ]
diff --git a/tests/units/input_models/routing/test_bgp.py b/tests/units/input_models/routing/test_bgp.py
index d8d64507..0ff859ff 100644
--- a/tests/units/input_models/routing/test_bgp.py
+++ b/tests/units/input_models/routing/test_bgp.py
@@ -6,12 +6,12 @@
 # pylint: disable=C0302
 from __future__ import annotations
 
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Any
 
 import pytest
 from pydantic import ValidationError
 
-from anta.input_models.routing.bgp import BgpAddressFamily, BgpPeer, BgpRoute
+from anta.input_models.routing.bgp import AddressFamilyConfig, BgpAddressFamily, BgpPeer, BgpRoute, RedistributedRouteConfig
 from anta.tests.routing.bgp import (
     VerifyBGPExchangedRoutes,
     VerifyBGPNlriAcceptance,
@@ -27,7 +27,7 @@ from anta.tests.routing.bgp import (
 )
 
 if TYPE_CHECKING:
-    from anta.custom_types import Afi, Safi
+    from anta.custom_types import Afi, RedistributedAfiSafi, RedistributedProtocol, Safi
 
 
 class TestBgpAddressFamily:
@@ -348,3 +348,90 @@ class TestVerifyBGPRoutePathsInput:
         """Test VerifyBGPRoutePaths.Input invalid inputs."""
         with pytest.raises(ValidationError):
             VerifyBGPRoutePaths.Input(route_entries=route_entries)
+
+
+class TestVerifyBGPRedistributedRoute:
+    """Test anta.input_models.routing.bgp.RedistributedRouteConfig."""
+
+    @pytest.mark.parametrize(
+        ("proto", "include_leaked"),
+        [
+            pytest.param("Connected", True, id="proto-valid"),
+            pytest.param("Static", False, id="proto-valid-leaked-false"),
+            pytest.param("User", False, id="proto-User"),
+        ],
+    )
+    def test_validate_inputs(self, proto: RedistributedProtocol, include_leaked: bool) -> None:
+        """Test RedistributedRouteConfig valid inputs."""
+        RedistributedRouteConfig(proto=proto, include_leaked=include_leaked)
+
+    @pytest.mark.parametrize(
+        ("proto", "include_leaked"),
+        [
+            pytest.param("Dynamic", True, id="proto-valid"),
+            pytest.param("User", True, id="proto-valid-leaked-false"),
+        ],
+    )
+    def test_invalid(self, proto: RedistributedProtocol, include_leaked: bool) -> None:
+        """Test RedistributedRouteConfig invalid inputs."""
+        with pytest.raises(ValidationError):
+            RedistributedRouteConfig(proto=proto, include_leaked=include_leaked)
+
+    @pytest.mark.parametrize(
+        ("proto", "include_leaked", "route_map", "expected"),
+        [
+            pytest.param("Connected", True, "RM-CONN-2-BGP", "Proto: Connected, Include Leaked: True, Route Map: RM-CONN-2-BGP", id="check-all-params"),
+            pytest.param("Static", False, None, "Proto: Static", id="check-proto-include_leaked-false"),
+            pytest.param("User", False, "RM-CONN-2-BGP", "Proto: EOS SDK, Route Map: RM-CONN-2-BGP", id="check-proto-route_map"),
+            pytest.param("Dynamic", False, None, "Proto: Dynamic", id="check-proto-only"),
+        ],
+    )
+    def test_valid_str(self, proto: RedistributedProtocol, include_leaked: bool, route_map: str | None, expected: str) -> None:
+        """Test RedistributedRouteConfig __str__."""
+        assert str(RedistributedRouteConfig(proto=proto, include_leaked=include_leaked, route_map=route_map)) == expected
+
+
+class TestVerifyBGPAddressFamilyConfig:
+    """Test anta.input_models.routing.bgp.AddressFamilyConfig."""
+
+    @pytest.mark.parametrize(
+        ("afi_safi", "redistributed_routes"),
+        [
+            pytest.param("ipv4Unicast", [{"proto": "OSPFv3 External", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv4-unicast"),
+            pytest.param("ipv6 Multicast", [{"proto": "OSPF Internal", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv6-multicast"),
+            pytest.param("ipv4-Multicast", [{"proto": "IS-IS", "include_leaked": False, "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv4-multicast"),
+            pytest.param("ipv6_Unicast", [{"proto": "AttachedHost", "route_map": "RM-CONN-2-BGP"}], id="afisafi-ipv6-unicast"),
+        ],
+    )
+    def test_valid(self, afi_safi: RedistributedAfiSafi, redistributed_routes: list[Any]) -> None:
+        """Test AddressFamilyConfig valid inputs."""
+        AddressFamilyConfig(afi_safi=afi_safi, redistributed_routes=redistributed_routes)
+
+    @pytest.mark.parametrize(
+        ("afi_safi", "redistributed_routes"),
+        [
+            pytest.param("evpn", [{"proto": "OSPFv3 Nssa-External", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="invalid-address-family"),
+            pytest.param("ipv6 sr-te", [{"proto": "RIP", "route_map": "RM-CONN-2-BGP"}], id="ipv6-invalid-address-family"),
+            pytest.param("iipv6_Unicast", [{"proto": "Bgp", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="ipv6-unicast-invalid-address-family"),
+            pytest.param("ipv6_Unicastt", [{"proto": "Static", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], id="ipv6-unicast-invalid-address-family"),
+        ],
+    )
+    def test_invalid(self, afi_safi: RedistributedAfiSafi, redistributed_routes: list[Any]) -> None:
+        """Test AddressFamilyConfig invalid inputs."""
+        with pytest.raises(ValidationError):
+            AddressFamilyConfig(afi_safi=afi_safi, redistributed_routes=redistributed_routes)
+
+    @pytest.mark.parametrize(
+        ("afi_safi", "redistributed_routes", "expected"),
+        [
+            pytest.param(
+                "v4u", [{"proto": "OSPFv3 Nssa-External", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], "AFI-SAFI: IPv4 Unicast", id="valid-ipv4-unicast"
+            ),
+            pytest.param("v4m", [{"proto": "RIP", "route_map": "RM-CONN-2-BGP"}], "AFI-SAFI: IPv4 Multicast", id="valid-ipv4-multicast"),
+            pytest.param("v6u", [{"proto": "Bgp", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], "AFI-SAFI: IPv6 Unicast", id="valid-ipv6-unicast"),
+            pytest.param("v6m", [{"proto": "Static", "include_leaked": True, "route_map": "RM-CONN-2-BGP"}], "AFI-SAFI: IPv6 Multicast", id="valid-ipv6-multicast"),
+        ],
+    )
+    def test_valid_str(self, afi_safi: RedistributedAfiSafi, redistributed_routes: list[Any], expected: str) -> None:
+        """Test AddressFamilyConfig __str__."""
+        assert str(AddressFamilyConfig(afi_safi=afi_safi, redistributed_routes=redistributed_routes)) == expected