Repository: aristanetworks/anta
Pull request #758 (2024-07-18 12:55:57)

refactor(anta): Change TestStatus to be an Enum for coding clarity
# Description

In #740 we end up doing `getargs(TestStatus)` on a `Literal` type which feels very awkward. This PR defines a proper type for TestStatus based on built-in Python Enum (can be made better in Python 3.11 with StrEnum)

# Checklist:

<!-- Delete not relevant items !-->

- [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`)


sonarcloud[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=758) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1 New issue](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=758&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=758&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=758&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=758&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=758&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=758)


sonarcloud[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=758) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1 New issue](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=758&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=758&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=758&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=758&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=758&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=758)


github-actions[bot]: This pull request has conflicts, please resolve those before we can evaluate the pull request.
github-actions[bot]: Conflicts have been resolved. A maintainer will review the pull request shortly.
sonarcloud[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=758) **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=758&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=758&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=758&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=758&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=758&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=758)


sonarcloud[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=758) **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=758&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=758&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=758&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=758&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=758&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=758)


sonarcloud[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=758) **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=758&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=758&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=758&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [97.3% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=758&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=758&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=758)


sonarcloud[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=758) **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=758&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=758&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=758&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [97.3% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=758&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=758&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=758)



Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index f716fb97..fbf8c3fd 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -43,7 +43,7 @@ repos:
           - '<!--|  ~|  -->'
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.6.2
+    rev: v0.6.3
     hooks:
     - id: ruff
       name: Run Ruff linter
diff --git a/anta/cli/nrfu/__init__.py b/anta/cli/nrfu/__init__.py
index 6263e845..d573b49c 100644
--- a/anta/cli/nrfu/__init__.py
+++ b/anta/cli/nrfu/__init__.py
@@ -5,14 +5,14 @@
 
 from __future__ import annotations
 
-from typing import TYPE_CHECKING, get_args
+from typing import TYPE_CHECKING
 
 import click
 
 from anta.cli.nrfu import commands
 from anta.cli.utils import AliasedGroup, catalog_options, inventory_options
-from anta.custom_types import TestStatus
 from anta.result_manager import ResultManager
+from anta.result_manager.models import AntaTestStatus
 
 if TYPE_CHECKING:
     from anta.catalog import AntaCatalog
@@ -49,7 +49,7 @@ class IgnoreRequiredWithHelp(AliasedGroup):
             return super().parse_args(ctx, args)
 
 
-HIDE_STATUS: list[str] = list(get_args(TestStatus))
+HIDE_STATUS: list[str] = list(AntaTestStatus)
 HIDE_STATUS.remove("unset")
 
 
diff --git a/anta/custom_types.py b/anta/custom_types.py
index 153fd701..322fa4ac 100644
--- a/anta/custom_types.py
+++ b/anta/custom_types.py
@@ -112,9 +112,6 @@ def validate_regex(value: str) -> str:
     return value
 
 
-# ANTA framework
-TestStatus = Literal["unset", "success", "failure", "error", "skipped"]
-
 # AntaTest.Input types
 AAAAuthMethod = Annotated[str, AfterValidator(aaa_group_prefix)]
 Vlan = Annotated[int, Field(ge=0, le=4094)]
diff --git a/anta/reporter/__init__.py b/anta/reporter/__init__.py
index c4e4f7bc..e74aaec5 100644
--- a/anta/reporter/__init__.py
+++ b/anta/reporter/__init__.py
@@ -18,9 +18,8 @@ from anta import RICH_COLOR_PALETTE, RICH_COLOR_THEME
 if TYPE_CHECKING:
     import pathlib
 
-    from anta.custom_types import TestStatus
     from anta.result_manager import ResultManager
-    from anta.result_manager.models import TestResult
+    from anta.result_manager.models import AntaTestStatus, TestResult
 
 logger = logging.getLogger(__name__)
 
@@ -80,19 +79,19 @@ class ReportTable:
                 table.add_column(header, justify="left")
         return table
 
-    def _color_result(self, status: TestStatus) -> str:
-        """Return a colored string based on the status value.
+    def _color_result(self, status: AntaTestStatus) -> str:
+        """Return a colored string based on an AntaTestStatus.
 
         Parameters
         ----------
-            status (TestStatus): status value to color.
+            status: AntaTestStatus enum to color.
 
         Returns
         -------
-        str: the colored string
+            The colored string.
 
         """
-        color = RICH_COLOR_THEME.get(status, "")
+        color = RICH_COLOR_THEME.get(str(status), "")
         return f"[{color}]{status}" if color != "" else str(status)
 
     def report_all(self, manager: ResultManager, title: str = "All tests results") -> Table:
diff --git a/anta/reporter/md_reporter.py b/anta/reporter/md_reporter.py
index 0cc5b03e..7b97fb17 100644
--- a/anta/reporter/md_reporter.py
+++ b/anta/reporter/md_reporter.py
@@ -12,6 +12,7 @@ from typing import TYPE_CHECKING, ClassVar
 
 from anta.constants import MD_REPORT_TOC
 from anta.logger import anta_log_exception
+from anta.result_manager.models import AntaTestStatus
 
 if TYPE_CHECKING:
     from collections.abc import Generator
@@ -203,10 +204,10 @@ class SummaryTotals(MDReportBase):
         """Generate the rows of the summary totals table."""
         yield (
             f"| {self.results.get_total_results()} "
-            f"| {self.results.get_total_results({'success'})} "
-            f"| {self.results.get_total_results({'skipped'})} "
-            f"| {self.results.get_total_results({'failure'})} "
-            f"| {self.results.get_total_results({'error'})} |\n"
+            f"| {self.results.get_total_results({AntaTestStatus.SUCCESS})} "
+            f"| {self.results.get_total_results({AntaTestStatus.SKIPPED})} "
+            f"| {self.results.get_total_results({AntaTestStatus.FAILURE})} "
+            f"| {self.results.get_total_results({AntaTestStatus.ERROR})} |\n"
         )
 
     def generate_section(self) -> None:
diff --git a/anta/result_manager/__init__.py b/anta/result_manager/__init__.py
index 1900a28b..95da4568 100644
--- a/anta/result_manager/__init__.py
+++ b/anta/result_manager/__init__.py
@@ -9,13 +9,9 @@ import json
 from collections import defaultdict
 from functools import cached_property
 from itertools import chain
-from typing import get_args
-
-from pydantic import TypeAdapter
 
 from anta.constants import ACRONYM_CATEGORIES
-from anta.custom_types import TestStatus
-from anta.result_manager.models import TestResult
+from anta.result_manager.models import AntaTestStatus, TestResult
 
 from .models import CategoryStats, DeviceStats, TestStats
 
@@ -95,7 +91,7 @@ class ResultManager:
         error_status is set to True.
         """
         self._result_entries: list[TestResult] = []
-        self.status: TestStatus = "unset"
+        self.status: AntaTestStatus = AntaTestStatus.UNSET
         self.error_status = False
 
         self.device_stats: defaultdict[str, DeviceStats] = defaultdict(DeviceStats)
@@ -116,7 +112,7 @@ class ResultManager:
         """Set the list of TestResult."""
         # When setting the results, we need to reset the state of the current instance
         self._result_entries = []
-        self.status = "unset"
+        self.status = AntaTestStatus.UNSET
         self.error_status = False
 
         # Also reset the stats attributes
@@ -138,26 +134,24 @@ class ResultManager:
         return dict(sorted(self.category_stats.items()))
 
     @cached_property
-    def results_by_status(self) -> dict[TestStatus, list[TestResult]]:
+    def results_by_status(self) -> dict[AntaTestStatus, list[TestResult]]:
         """A cached property that returns the results grouped by status."""
-        return {status: [result for result in self._result_entries if result.result == status] for status in get_args(TestStatus)}
+        return {status: [result for result in self._result_entries if result.result == status] for status in AntaTestStatus}
 
-    def _update_status(self, test_status: TestStatus) -> None:
+    def _update_status(self, test_status: AntaTestStatus) -> None:
         """Update the status of the ResultManager instance based on the test status.
 
         Parameters
         ----------
-            test_status: TestStatus to update the ResultManager status.
+            test_status: AntaTestStatus to update the ResultManager status.
         """
-        result_validator: TypeAdapter[TestStatus] = TypeAdapter(TestStatus)
-        result_validator.validate_python(test_status)
         if test_status == "error":
             self.error_status = True
             return
         if self.status == "unset" or self.status == "skipped" and test_status in {"success", "failure"}:
             self.status = test_status
         elif self.status == "success" and test_status == "failure":
-            self.status = "failure"
+            self.status = AntaTestStatus.FAILURE
 
     def _update_stats(self, result: TestResult) -> None:
         """Update the statistics based on the test result.
@@ -209,14 +203,14 @@ class ResultManager:
         # Every time a new result is added, we need to clear the cached property
         self.__dict__.pop("results_by_status", None)
 
-    def get_results(self, status: set[TestStatus] | None = None, sort_by: list[str] | None = None) -> list[TestResult]:
+    def get_results(self, status: set[AntaTestStatus] | None = None, sort_by: list[str] | None = None) -> list[TestResult]:
         """Get the results, optionally filtered by status and sorted by TestResult fields.
 
         If no status is provided, all results are returned.
 
         Parameters
         ----------
-            status: Optional set of TestStatus literals to filter the results.
+            status: Optional set of AntaTestStatus enum members to filter the results.
             sort_by: Optional list of TestResult fields to sort the results.
 
         Returns
@@ -235,14 +229,14 @@ class ResultManager:
 
         return results
 
-    def get_total_results(self, status: set[TestStatus] | None = None) -> int:
+    def get_total_results(self, status: set[AntaTestStatus] | None = None) -> int:
         """Get the total number of results, optionally filtered by status.
 
         If no status is provided, the total number of results is returned.
 
         Parameters
         ----------
-            status: Optional set of TestStatus literals to filter the results.
+            status: Optional set of AntaTestStatus enum members to filter the results.
 
         Returns
         -------
@@ -259,18 +253,18 @@ class ResultManager:
         """Return the current status including error_status if ignore_error is False."""
         return "error" if self.error_status and not ignore_error else self.status
 
-    def filter(self, hide: set[TestStatus]) -> ResultManager:
+    def filter(self, hide: set[AntaTestStatus]) -> ResultManager:
         """Get a filtered ResultManager based on test status.
 
         Parameters
         ----------
-            hide: set of TestStatus literals to select tests to hide based on their status.
+            hide: Set of AntaTestStatus enum members to select tests to hide based on their status.
 
         Returns
         -------
             A filtered `ResultManager`.
         """
-        possible_statuses = set(get_args(TestStatus))
+        possible_statuses = set(AntaTestStatus)
         manager = ResultManager()
         manager.results = self.get_results(possible_statuses - hide)
         return manager
diff --git a/anta/result_manager/models.py b/anta/result_manager/models.py
index 6abce023..2bb2aed2 100644
--- a/anta/result_manager/models.py
+++ b/anta/result_manager/models.py
@@ -6,10 +6,26 @@
 from __future__ import annotations
 
 from dataclasses import dataclass, field
+from enum import Enum
 
 from pydantic import BaseModel
 
-from anta.custom_types import TestStatus
+
+class AntaTestStatus(str, Enum):
+    """Test status Enum for the TestResult.
+
+    NOTE: This could be updated to StrEnum when Python 3.11 is the minimum supported version in ANTA.
+    """
+
+    UNSET = "unset"
+    SUCCESS = "success"
+    FAILURE = "failure"
+    ERROR = "error"
+    SKIPPED = "skipped"
+
+    def __str__(self) -> str:
+        """Override the __str__ method to return the value of the Enum, mimicking the behavior of StrEnum."""
+        return self.value
 
 
 class TestResult(BaseModel):
@@ -17,13 +33,13 @@ class TestResult(BaseModel):
 
     Attributes
     ----------
-        name: Device name where the test has run.
-        test: Test name runs on the device.
-        categories: List of categories the TestResult belongs to, by default the AntaTest categories.
-        description: TestResult description, by default the AntaTest description.
-        result: Result of the test. Can be one of "unset", "success", "failure", "error" or "skipped".
-        messages: Message to report after the test if any.
-        custom_field: Custom field to store a string for flexibility in integrating with ANTA
+        name: Name of the device where the test was run.
+        test: Name of the test run on the device.
+        categories: List of categories the TestResult belongs to. Defaults to the AntaTest categories.
+        description: Description of the TestResult. Defaults to the AntaTest description.
+        result: Result of the test. Must be one of the AntaTestStatus Enum values: unset, success, failure, error or skipped.
+        messages: Messages to report after the test, if any.
+        custom_field: Custom field to store a string for flexibility in integrating with ANTA.
 
     """
 
@@ -31,7 +47,7 @@ class TestResult(BaseModel):
     test: str
     categories: list[str]
     description: str
-    result: TestStatus = "unset"
+    result: AntaTestStatus = AntaTestStatus.UNSET
     messages: list[str] = []
     custom_field: str | None = None
 
@@ -43,7 +59,7 @@ class TestResult(BaseModel):
             message: Optional message related to the test
 
         """
-        self._set_status("success", message)
+        self._set_status(AntaTestStatus.SUCCESS, message)
 
     def is_failure(self, message: str | None = None) -> None:
         """Set status to failure.
@@ -53,7 +69,7 @@ class TestResult(BaseModel):
             message: Optional message related to the test
 
         """
-        self._set_status("failure", message)
+        self._set_status(AntaTestStatus.FAILURE, message)
 
     def is_skipped(self, message: str | None = None) -> None:
         """Set status to skipped.
@@ -63,7 +79,7 @@ class TestResult(BaseModel):
             message: Optional message related to the test
 
         """
-        self._set_status("skipped", message)
+        self._set_status(AntaTestStatus.SKIPPED, message)
 
     def is_error(self, message: str | None = None) -> None:
         """Set status to error.
@@ -73,9 +89,9 @@ class TestResult(BaseModel):
             message: Optional message related to the test
 
         """
-        self._set_status("error", message)
+        self._set_status(AntaTestStatus.ERROR, message)
 
-    def _set_status(self, status: TestStatus, message: str | None = None) -> None:
+    def _set_status(self, status: AntaTestStatus, message: str | None = None) -> None:
         """Set status and insert optional message.
 
         Parameters
diff --git a/asynceapi/aio_portcheck.py b/asynceapi/aio_portcheck.py
index fd8e7aee..79f4562f 100644
--- a/asynceapi/aio_portcheck.py
+++ b/asynceapi/aio_portcheck.py
@@ -33,7 +33,7 @@ __all__ = ["port_check_url"]
 # -----------------------------------------------------------------------------
 
 
-async def port_check_url(url: URL, timeout: int = 5) -> bool:  # noqa: ASYNC109
+async def port_check_url(url: URL, timeout: int = 5) -> bool:
     """
     Open the port designated by the URL given the timeout in seconds.
 


Test patch:
diff --git a/tests/units/reporter/test__init__.py b/tests/units/reporter/test__init__.py
index 2fc62ce9..f0e44b41 100644
--- a/tests/units/reporter/test__init__.py
+++ b/tests/units/reporter/test__init__.py
@@ -13,9 +13,9 @@ from rich.table import Table
 
 from anta import RICH_COLOR_PALETTE
 from anta.reporter import ReportJinja, ReportTable
+from anta.result_manager.models import AntaTestStatus
 
 if TYPE_CHECKING:
-    from anta.custom_types import TestStatus
     from anta.result_manager import ResultManager
 
 
@@ -73,15 +73,14 @@ class TestReportTable:
     @pytest.mark.parametrize(
         ("status", "expected_status"),
         [
-            pytest.param("unknown", "unknown", id="unknown status"),
-            pytest.param("unset", "[grey74]unset", id="unset status"),
-            pytest.param("skipped", "[bold orange4]skipped", id="skipped status"),
-            pytest.param("failure", "[bold red]failure", id="failure status"),
-            pytest.param("error", "[indian_red]error", id="error status"),
-            pytest.param("success", "[green4]success", id="success status"),
+            pytest.param(AntaTestStatus.UNSET, "[grey74]unset", id="unset status"),
+            pytest.param(AntaTestStatus.SKIPPED, "[bold orange4]skipped", id="skipped status"),
+            pytest.param(AntaTestStatus.FAILURE, "[bold red]failure", id="failure status"),
+            pytest.param(AntaTestStatus.ERROR, "[indian_red]error", id="error status"),
+            pytest.param(AntaTestStatus.SUCCESS, "[green4]success", id="success status"),
         ],
     )
-    def test__color_result(self, status: TestStatus, expected_status: str) -> None:
+    def test__color_result(self, status: AntaTestStatus, expected_status: str) -> None:
         """Test _build_headers."""
         # pylint: disable=protected-access
         report = ReportTable()
@@ -140,7 +139,7 @@ class TestReportTable:
         new_results = [result.model_copy() for result in manager.results]
         for result in new_results:
             result.name = "test_device"
-            result.result = "failure"
+            result.result = AntaTestStatus.FAILURE
 
         report = ReportTable()
         kwargs = {"tests": [test] if test is not None else None, "title": title}
@@ -175,7 +174,7 @@ class TestReportTable:
         new_results = [result.model_copy() for result in manager.results]
         for result in new_results:
             result.name = dev or "test_device"
-            result.result = "failure"
+            result.result = AntaTestStatus.FAILURE
         manager.results = new_results
 
         report = ReportTable()
diff --git a/tests/units/result_manager/test__init__.py b/tests/units/result_manager/test__init__.py
index 66a6cfb1..802d4a4e 100644
--- a/tests/units/result_manager/test__init__.py
+++ b/tests/units/result_manager/test__init__.py
@@ -13,9 +13,9 @@ from typing import TYPE_CHECKING, Callable
 import pytest
 
 from anta.result_manager import ResultManager, models
+from anta.result_manager.models import AntaTestStatus
 
 if TYPE_CHECKING:
-    from anta.custom_types import TestStatus
     from anta.result_manager.models import TestResult
 
 
@@ -56,7 +56,7 @@ class TestResultManager:
 
         success_list = list_result_factory(3)
         for test in success_list:
-            test.result = "success"
+            test.result = AntaTestStatus.SUCCESS
         result_manager.results = success_list
 
         json_res = result_manager.json
@@ -141,29 +141,27 @@ class TestResultManager:
                 nullcontext(),
                 id="failure, add success",
             ),
-            pytest.param(
-                "unset", "unknown", None, pytest.raises(ValueError, match="Input should be 'unset', 'success', 'failure', 'error' or 'skipped'"), id="wrong status"
-            ),
+            pytest.param("unset", "unknown", None, pytest.raises(ValueError, match="'unknown' is not a valid AntaTestStatus"), id="wrong status"),
         ],
     )
     def test_add(
         self,
         test_result_factory: Callable[[], TestResult],
-        starting_status: TestStatus,
-        test_status: TestStatus,
+        starting_status: str,
+        test_status: str,
         expected_status: str,
         expected_raise: AbstractContextManager[Exception],
     ) -> None:
         # pylint: disable=too-many-arguments
         """Test ResultManager_update_status."""
         result_manager = ResultManager()
-        result_manager.status = starting_status
+        result_manager.status = AntaTestStatus(starting_status)
         assert result_manager.error_status is False
         assert len(result_manager) == 0
 
         test = test_result_factory()
-        test.result = test_status
         with expected_raise:
+            test.result = AntaTestStatus(test_status)
             result_manager.add(test)
             if test_status == "error":
                 assert result_manager.error_status is True
@@ -199,12 +197,12 @@ class TestResultManager:
     def test_get_results(self, result_manager: ResultManager) -> None:
         """Test ResultManager.get_results."""
         # Check for single status
-        success_results = result_manager.get_results(status={"success"})
+        success_results = result_manager.get_results(status={AntaTestStatus.SUCCESS})
         assert len(success_results) == 7
         assert all(r.result == "success" for r in success_results)
 
         # Check for multiple statuses
-        failure_results = result_manager.get_results(status={"failure", "error"})
+        failure_results = result_manager.get_results(status={AntaTestStatus.FAILURE, AntaTestStatus.ERROR})
         assert len(failure_results) == 21
         assert all(r.result in {"failure", "error"} for r in failure_results)
 
@@ -226,7 +224,7 @@ class TestResultManager:
         assert all_results[-1].name == "DC1-SPINE1"
 
         # Check multiple statuses with sort_by categories
-        success_skipped_results = result_manager.get_results(status={"success", "skipped"}, sort_by=["categories"])
+        success_skipped_results = result_manager.get_results(status={AntaTestStatus.SUCCESS, AntaTestStatus.SKIPPED}, sort_by=["categories"])
         assert len(success_skipped_results) == 9
         assert success_skipped_results[0].categories == ["Interfaces"]
         assert success_skipped_results[-1].categories == ["VXLAN"]
@@ -246,15 +244,15 @@ class TestResultManager:
         assert result_manager.get_total_results() == 30
 
         # Test single status
-        assert result_manager.get_total_results(status={"success"}) == 7
-        assert result_manager.get_total_results(status={"failure"}) == 19
-        assert result_manager.get_total_results(status={"error"}) == 2
-        assert result_manager.get_total_results(status={"skipped"}) == 2
+        assert result_manager.get_total_results(status={AntaTestStatus.SUCCESS}) == 7
+        assert result_manager.get_total_results(status={AntaTestStatus.FAILURE}) == 19
+        assert result_manager.get_total_results(status={AntaTestStatus.ERROR}) == 2
+        assert result_manager.get_total_results(status={AntaTestStatus.SKIPPED}) == 2
 
         # Test multiple statuses
-        assert result_manager.get_total_results(status={"success", "failure"}) == 26
-        assert result_manager.get_total_results(status={"success", "failure", "error"}) == 28
-        assert result_manager.get_total_results(status={"success", "failure", "error", "skipped"}) == 30
+        assert result_manager.get_total_results(status={AntaTestStatus.SUCCESS, AntaTestStatus.FAILURE}) == 26
+        assert result_manager.get_total_results(status={AntaTestStatus.SUCCESS, AntaTestStatus.FAILURE, AntaTestStatus.ERROR}) == 28
+        assert result_manager.get_total_results(status={AntaTestStatus.SUCCESS, AntaTestStatus.FAILURE, AntaTestStatus.ERROR, AntaTestStatus.SKIPPED}) == 30
 
     @pytest.mark.parametrize(
         ("status", "error_status", "ignore_error", "expected_status"),
@@ -266,7 +264,7 @@ class TestResultManager:
     )
     def test_get_status(
         self,
-        status: TestStatus,
+        status: AntaTestStatus,
         error_status: bool,
         ignore_error: bool,
         expected_status: str,
@@ -284,28 +282,28 @@ class TestResultManager:
 
         success_list = list_result_factory(3)
         for test in success_list:
-            test.result = "success"
+            test.result = AntaTestStatus.SUCCESS
         result_manager.results = success_list
 
         test = test_result_factory()
-        test.result = "failure"
+        test.result = AntaTestStatus.FAILURE
         result_manager.add(test)
 
         test = test_result_factory()
-        test.result = "error"
+        test.result = AntaTestStatus.ERROR
         result_manager.add(test)
 
         test = test_result_factory()
-        test.result = "skipped"
+        test.result = AntaTestStatus.SKIPPED
         result_manager.add(test)
 
         assert len(result_manager) == 6
-        assert len(result_manager.filter({"failure"})) == 5
-        assert len(result_manager.filter({"error"})) == 5
-        assert len(result_manager.filter({"skipped"})) == 5
-        assert len(result_manager.filter({"failure", "error"})) == 4
-        assert len(result_manager.filter({"failure", "error", "skipped"})) == 3
-        assert len(result_manager.filter({"success", "failure", "error", "skipped"})) == 0
+        assert len(result_manager.filter({AntaTestStatus.FAILURE})) == 5
+        assert len(result_manager.filter({AntaTestStatus.ERROR})) == 5
+        assert len(result_manager.filter({AntaTestStatus.SKIPPED})) == 5
+        assert len(result_manager.filter({AntaTestStatus.FAILURE, AntaTestStatus.ERROR})) == 4
+        assert len(result_manager.filter({AntaTestStatus.FAILURE, AntaTestStatus.ERROR, AntaTestStatus.SKIPPED})) == 3
+        assert len(result_manager.filter({AntaTestStatus.SUCCESS, AntaTestStatus.FAILURE, AntaTestStatus.ERROR, AntaTestStatus.SKIPPED})) == 0
 
     def test_get_by_tests(self, test_result_factory: Callable[[], TestResult], result_manager_factory: Callable[[int], ResultManager]) -> None:
         """Test ResultManager.get_by_tests."""
diff --git a/tests/units/result_manager/test_models.py b/tests/units/result_manager/test_models.py
index 2276153f..bc44ccfd 100644
--- a/tests/units/result_manager/test_models.py
+++ b/tests/units/result_manager/test_models.py
@@ -9,6 +9,8 @@ from typing import TYPE_CHECKING, Any, Callable
 
 import pytest
 
+from anta.result_manager.models import AntaTestStatus
+
 # Import as Result to avoid pytest collection
 from tests.data.json_data import TEST_RESULT_SET_STATUS
 from tests.lib.fixture import DEVICE_NAME
@@ -45,7 +47,7 @@ class TestTestResultModels:
             assert data["message"] in testresult.messages
         # no helper for unset, testing _set_status
         if data["target"] == "unset":
-            testresult._set_status("unset", data["message"])  # pylint: disable=W0212
+            testresult._set_status(AntaTestStatus.UNSET, data["message"])  # pylint: disable=W0212
             assert testresult.result == data["target"]
             assert data["message"] in testresult.messages