Repository: aristanetworks/anta (python)

Issue:
Have nicer result failure messages
Would be nice to have a more human readable format for the test result failures messages:
![image](https://github.com/arista-netdevops-community/anta/assets/63206086/bd3d551d-f910-4e95-a7c5-6ecd462f93d9)

BGP tests are also not very user friendly:
![image](https://github.com/arista-netdevops-community/anta/assets/63206086/da408c18-6b1e-4cdb-a92c-0d6a2f4fd7da)


Could be related to #427

Pull request:
refactor(anta): Refactor VerifyDNSServers test for nicer failure message 
# Description

Refactor VerifyDNSServers test for nicer failure message 

Related to #587 #388 

# 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`)
- [X] 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`)


Patch:
diff --git a/anta/input_models/services.py b/anta/input_models/services.py
new file mode 100644
index 00000000..596a3e38
--- /dev/null
+++ b/anta/input_models/services.py
@@ -0,0 +1,31 @@
+# Copyright (c) 2023-2024 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the LICENSE file.
+"""Module containing input models for services tests."""
+
+from __future__ import annotations
+
+from ipaddress import IPv4Address, IPv6Address
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class DnsServer(BaseModel):
+    """Model for a DNS server configuration."""
+
+    model_config = ConfigDict(extra="forbid")
+    server_address: IPv4Address | IPv6Address
+    """The IPv4 or IPv6 address of the DNS server."""
+    vrf: str = "default"
+    """The VRF instance in which the DNS server resides. Defaults to 'default'."""
+    priority: int = Field(ge=0, le=4)
+    """The priority level of the DNS server, ranging from 0 to 4. Lower values indicate a higher priority, with 0 being the highest and 4 the lowest."""
+
+    def __str__(self) -> str:
+        """Return a human-readable string representation of the DnsServer for reporting.
+
+        Examples
+        --------
+        Server 10.0.0.1 (VRF: default, Priority: 1)
+        """
+        return f"Server {self.server_address} (VRF: {self.vrf}, Priority: {self.priority})"


Test patch:
diff --git a/anta/tests/logging.py b/anta/tests/logging.py
index 74ae875c..c391947c 100644
--- a/anta/tests/logging.py
+++ b/anta/tests/logging.py
@@ -170,10 +170,22 @@ class VerifyLoggingHosts(AntaTest):
 class VerifyLoggingLogsGeneration(AntaTest):
     """Verifies if logs are generated.
 
+    This test performs the following checks:
+
+      1. Sends a test log message at the **informational** level
+      2. Retrieves the most recent logs (last 30 seconds)
+      3. Verifies that the test message was successfully logged
+
+    !!! warning
+        EOS logging buffer should be set to severity level `informational` or higher for this test to work.
+
     Expected Results
     ----------------
-    * Success: The test will pass if logs are generated.
-    * Failure: The test will fail if logs are NOT generated.
+    * Success: If logs are being generated and the test message is found in recent logs.
+    * Failure: If any of the following occur:
+        - The test message is not found in recent logs
+        - The logging system is not capturing new messages
+        - No logs are being generated
 
     Examples
     --------
@@ -205,10 +217,23 @@ class VerifyLoggingLogsGeneration(AntaTest):
 class VerifyLoggingHostname(AntaTest):
     """Verifies if logs are generated with the device FQDN.
 
+    This test performs the following checks:
+
+      1. Retrieves the device's configured FQDN
+      2. Sends a test log message at the **informational** level
+      3. Retrieves the most recent logs (last 30 seconds)
+      4. Verifies that the test message includes the complete FQDN of the device
+
+    !!! warning
+          EOS logging buffer should be set to severity level `informational` or higher for this test to work.
+
     Expected Results
     ----------------
-    * Success: The test will pass if logs are generated with the device FQDN.
-    * Failure: The test will fail if logs are NOT generated with the device FQDN.
+    * Success: If logs are generated with the device's complete FQDN.
+    * Failure: If any of the following occur:
+        - The test message is not found in recent logs
+        - The log message does not include the device's FQDN
+        - The FQDN in the log message doesn't match the configured FQDN
 
     Examples
     --------
@@ -247,10 +272,24 @@ class VerifyLoggingHostname(AntaTest):
 class VerifyLoggingTimestamp(AntaTest):
     """Verifies if logs are generated with the appropriate timestamp.
 
+    This test performs the following checks:
+
+      1. Sends a test log message at the **informational** level
+      2. Retrieves the most recent logs (last 30 seconds)
+      3. Verifies that the test message is present with a high-resolution RFC3339 timestamp format
+        - Example format: `2024-01-25T15:30:45.123456+00:00`
+        - Includes microsecond precision
+        - Contains timezone offset
+
+    !!! warning
+          EOS logging buffer should be set to severity level `informational` or higher for this test to work.
+
     Expected Results
     ----------------
-    * Success: The test will pass if logs are generated with the appropriate timestamp.
-    * Failure: The test will fail if logs are NOT generated with the appropriate timestamp.
+    * Success: If logs are generated with the correct high-resolution RFC3339 timestamp format.
+    * Failure: If any of the following occur:
+        - The test message is not found in recent logs
+        - The timestamp format does not match the expected RFC3339 format
 
     Examples
     --------
diff --git a/anta/tests/services.py b/anta/tests/services.py
index 4b1e45d0..1e59791a 100644
--- a/anta/tests/services.py
+++ b/anta/tests/services.py
@@ -7,14 +7,14 @@ from __future__ import annotations
 
 # Mypy does not understand AntaTest.Input typing
 # mypy: disable-error-code=attr-defined
-from ipaddress import IPv4Address, IPv6Address
 from typing import ClassVar
 
-from pydantic import BaseModel, Field
+from pydantic import BaseModel
 
 from anta.custom_types import ErrDisableInterval, ErrDisableReasons
+from anta.input_models.services import DnsServer
 from anta.models import AntaCommand, AntaTemplate, AntaTest
-from anta.tools import get_dict_superset, get_failed_logs, get_item
+from anta.tools import get_dict_superset, get_failed_logs
 
 
 class VerifyHostname(AntaTest):
@@ -106,10 +106,17 @@ class VerifyDNSLookup(AntaTest):
 class VerifyDNSServers(AntaTest):
     """Verifies if the DNS (Domain Name Service) servers are correctly configured.
 
+    This test performs the following checks for each specified DNS Server:
+
+      1. Confirming correctly registered with a valid IPv4 or IPv6 address with the designated VRF.
+      2. Ensuring an appropriate priority level.
+
     Expected Results
     ----------------
     * Success: The test will pass if the DNS server specified in the input is configured with the correct VRF and priority.
-    * Failure: The test will fail if the DNS server is not configured or if the VRF and priority of the DNS server do not match the input.
+    * Failure: The test will fail if any of the following conditions are met:
+        - The provided DNS server is not configured.
+        - The provided DNS server with designated VRF and priority does not match the expected information.
 
     Examples
     --------
@@ -135,38 +142,28 @@ class VerifyDNSServers(AntaTest):
 
         dns_servers: list[DnsServer]
         """List of DNS servers to verify."""
-
-        class DnsServer(BaseModel):
-            """Model for a DNS server."""
-
-            server_address: IPv4Address | IPv6Address
-            """The IPv4/IPv6 address of the DNS server."""
-            vrf: str = "default"
-            """The VRF for the DNS server. Defaults to 'default' if not provided."""
-            priority: int = Field(ge=0, le=4)
-            """The priority of the DNS server from 0 to 4, lower is first."""
+        DnsServer: ClassVar[type[DnsServer]] = DnsServer
 
     @AntaTest.anta_test
     def test(self) -> None:
         """Main test function for VerifyDNSServers."""
-        command_output = self.instance_commands[0].json_output["nameServerConfigs"]
         self.result.is_success()
+
+        command_output = self.instance_commands[0].json_output["nameServerConfigs"]
         for server in self.inputs.dns_servers:
             address = str(server.server_address)
             vrf = server.vrf
             priority = server.priority
             input_dict = {"ipAddr": address, "vrf": vrf}
 
-            if get_item(command_output, "ipAddr", address) is None:
-                self.result.is_failure(f"DNS server `{address}` is not configured with any VRF.")
-                continue
-
+            # Check if the DNS server is configured with specified VRF.
             if (output := get_dict_superset(command_output, input_dict)) is None:
-                self.result.is_failure(f"DNS server `{address}` is not configured with VRF `{vrf}`.")
+                self.result.is_failure(f"{server} - Not configured")
                 continue
 
+            # Check if the DNS server priority matches with expected.
             if output["priority"] != priority:
-                self.result.is_failure(f"For DNS server `{address}`, the expected priority is `{priority}`, but `{output['priority']}` was found instead.")
+                self.result.is_failure(f"{server} - Incorrect priority; Priority: {output['priority']}")
 
 
 class VerifyErrdisableRecovery(AntaTest):
diff --git a/docs/api/tests.services.md b/docs/api/tests.services.md
index 63d9234c..fba716ab 100644
--- a/docs/api/tests.services.md
+++ b/docs/api/tests.services.md
@@ -7,7 +7,10 @@ anta_title: ANTA catalog for services tests
   ~ that can be found in the LICENSE file.
   -->
 
+# Tests
+
 ::: anta.tests.services
+
     options:
       show_root_heading: false
       show_root_toc_entry: false
@@ -18,3 +21,15 @@ anta_title: ANTA catalog for services tests
       filters:
         - "!test"
         - "!render"
+
+# Input models
+
+::: anta.input_models.services
+
+    options:
+      show_root_heading: false
+      show_root_toc_entry: false
+      show_bases: false
+      anta_hide_test_module_description: true
+      show_labels: true
+      filters: ["!^__str__"]
diff --git a/tests/units/anta_tests/test_services.py b/tests/units/anta_tests/test_services.py
index 3f13dfc0..6802aab4 100644
--- a/tests/units/anta_tests/test_services.py
+++ b/tests/units/anta_tests/test_services.py
@@ -59,30 +59,22 @@ DATA: list[dict[str, Any]] = [
         "test": VerifyDNSServers,
         "eos_data": [
             {
-                "nameServerConfigs": [{"ipAddr": "10.14.0.1", "vrf": "default", "priority": 0}, {"ipAddr": "10.14.0.11", "vrf": "MGMT", "priority": 1}],
+                "nameServerConfigs": [
+                    {"ipAddr": "10.14.0.1", "vrf": "default", "priority": 0},
+                    {"ipAddr": "10.14.0.11", "vrf": "MGMT", "priority": 1},
+                    {"ipAddr": "fd12:3456:789a::1", "vrf": "default", "priority": 0},
+                ],
             }
         ],
         "inputs": {
-            "dns_servers": [{"server_address": "10.14.0.1", "vrf": "default", "priority": 0}, {"server_address": "10.14.0.11", "vrf": "MGMT", "priority": 1}]
+            "dns_servers": [
+                {"server_address": "10.14.0.1", "vrf": "default", "priority": 0},
+                {"server_address": "10.14.0.11", "vrf": "MGMT", "priority": 1},
+                {"server_address": "fd12:3456:789a::1", "vrf": "default", "priority": 0},
+            ]
         },
         "expected": {"result": "success"},
     },
-    {
-        "name": "failure-dns-missing",
-        "test": VerifyDNSServers,
-        "eos_data": [
-            {
-                "nameServerConfigs": [{"ipAddr": "10.14.0.1", "vrf": "default", "priority": 0}, {"ipAddr": "10.14.0.11", "vrf": "MGMT", "priority": 1}],
-            }
-        ],
-        "inputs": {
-            "dns_servers": [{"server_address": "10.14.0.10", "vrf": "default", "priority": 0}, {"server_address": "10.14.0.21", "vrf": "MGMT", "priority": 1}]
-        },
-        "expected": {
-            "result": "failure",
-            "messages": ["DNS server `10.14.0.10` is not configured with any VRF.", "DNS server `10.14.0.21` is not configured with any VRF."],
-        },
-    },
     {
         "name": "failure-no-dns-found",
         "test": VerifyDNSServers,
@@ -96,7 +88,7 @@ DATA: list[dict[str, Any]] = [
         },
         "expected": {
             "result": "failure",
-            "messages": ["DNS server `10.14.0.10` is not configured with any VRF.", "DNS server `10.14.0.21` is not configured with any VRF."],
+            "messages": ["Server 10.14.0.10 (VRF: default, Priority: 0) - Not configured", "Server 10.14.0.21 (VRF: MGMT, Priority: 1) - Not configured"],
         },
     },
     {
@@ -117,9 +109,9 @@ DATA: list[dict[str, Any]] = [
         "expected": {
             "result": "failure",
             "messages": [
-                "For DNS server `10.14.0.1`, the expected priority is `0`, but `1` was found instead.",
-                "DNS server `10.14.0.11` is not configured with VRF `default`.",
-                "DNS server `10.14.0.110` is not configured with any VRF.",
+                "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",
             ],
         },
     },