fix issues causing test failure

This commit is contained in:
thatmattlove 2024-03-01 23:19:08 -05:00
parent 92bc28ac05
commit 99ec07d055
10 changed files with 104 additions and 32 deletions

View file

@ -1,4 +1,5 @@
"""hyperglass Configuration.""" """hyperglass Configuration."""
# Standard Library # Standard Library
import typing as t import typing as t

View file

@ -68,7 +68,6 @@ class HyperglassError(Exception):
return template.format(**kwargs) return template.format(**kwargs)
def _parse_pydantic_errors(*errors: Dict[str, Any]) -> str: def _parse_pydantic_errors(*errors: Dict[str, Any]) -> str:
errs = ("\n",) errs = ("\n",)
for err in errors: for err in errors:
@ -121,10 +120,16 @@ class PublicHyperglassError(HyperglassError):
def __init__(self, **kwargs: str) -> None: def __init__(self, **kwargs: str) -> None:
"""Format error message with keyword arguments.""" """Format error message with keyword arguments."""
from hyperglass.state import use_state
if "error" in kwargs: if "error" in kwargs:
error = kwargs.pop("error") error = kwargs.pop("error")
error = self._safe_format(str(error), **kwargs) error = self._safe_format(str(error), **kwargs)
kwargs["error"] = error kwargs["error"] = error
(messages := use_state("params").messages)
if messages.has(self._message_template):
self._message_template = messages[self._message_template]
self._message = self._safe_format(self._message_template, **kwargs) self._message = self._safe_format(self._message_template, **kwargs)
self._keywords = list(kwargs.values()) self._keywords = list(kwargs.values())
super().__init__(message=self._message, level=self._level, keywords=self._keywords) super().__init__(message=self._message, level=self._level, keywords=self._keywords)

View file

@ -3,8 +3,6 @@
# Standard Library # Standard Library
from typing import TYPE_CHECKING, Any, Dict, Optional from typing import TYPE_CHECKING, Any, Dict, Optional
# Project
from hyperglass.state import use_state
# Local # Local
from ._common import PublicHyperglassError from ._common import PublicHyperglassError
@ -14,13 +12,10 @@ if TYPE_CHECKING:
from hyperglass.models.api.query import Query from hyperglass.models.api.query import Query
from hyperglass.models.config.devices import Device from hyperglass.models.config.devices import Device
(MESSAGES := use_state("params").messages)
(TEXT := use_state("params").web.text)
class ScrapeError( class ScrapeError(
PublicHyperglassError, PublicHyperglassError,
template=MESSAGES.connection_error, template="connection_error",
level="danger", level="danger",
): ):
"""Raised when an SSH driver error occurs.""" """Raised when an SSH driver error occurs."""
@ -30,7 +25,7 @@ class ScrapeError(
super().__init__(error=str(error), device=device.name, proxy=device.proxy) super().__init__(error=str(error), device=device.name, proxy=device.proxy)
class AuthError(PublicHyperglassError, template=MESSAGES.authentication_error, level="danger"): class AuthError(PublicHyperglassError, template="authentication_error", level="danger"):
"""Raised when authentication to a device fails.""" """Raised when authentication to a device fails."""
def __init__(self, *, error: BaseException, device: "Device"): def __init__(self, *, error: BaseException, device: "Device"):
@ -38,7 +33,7 @@ class AuthError(PublicHyperglassError, template=MESSAGES.authentication_error, l
super().__init__(error=str(error), device=device.name, proxy=device.proxy) super().__init__(error=str(error), device=device.name, proxy=device.proxy)
class RestError(PublicHyperglassError, template=MESSAGES.connection_error, level="danger"): class RestError(PublicHyperglassError, template="connection_error", level="danger"):
"""Raised upon a rest API client error.""" """Raised upon a rest API client error."""
def __init__(self, *, error: BaseException, device: "Device"): def __init__(self, *, error: BaseException, device: "Device"):
@ -46,7 +41,7 @@ class RestError(PublicHyperglassError, template=MESSAGES.connection_error, level
super().__init__(error=str(error), device=device.name) super().__init__(error=str(error), device=device.name)
class DeviceTimeout(PublicHyperglassError, template=MESSAGES.request_timeout, level="danger"): class DeviceTimeout(PublicHyperglassError, template="request_timeout", level="danger"):
"""Raised when the connection to a device times out.""" """Raised when the connection to a device times out."""
def __init__(self, *, error: BaseException, device: "Device"): def __init__(self, *, error: BaseException, device: "Device"):
@ -54,7 +49,7 @@ class DeviceTimeout(PublicHyperglassError, template=MESSAGES.request_timeout, le
super().__init__(error=str(error), device=device.name, proxy=device.proxy) super().__init__(error=str(error), device=device.name, proxy=device.proxy)
class InvalidQuery(PublicHyperglassError, template=MESSAGES.invalid_query): class InvalidQuery(PublicHyperglassError, template="request_timeout"):
"""Raised when input validation fails.""" """Raised when input validation fails."""
def __init__( def __init__(
@ -74,7 +69,7 @@ class InvalidQuery(PublicHyperglassError, template=MESSAGES.invalid_query):
super().__init__(**kwargs) super().__init__(**kwargs)
class NotFound(PublicHyperglassError, template=MESSAGES.not_found): class NotFound(PublicHyperglassError, template="not_found"):
"""Raised when an object is not found.""" """Raised when an object is not found."""
def __init__(self, type: str, name: str, **kwargs: Dict[str, str]) -> None: def __init__(self, type: str, name: str, **kwargs: Dict[str, str]) -> None:
@ -87,8 +82,11 @@ class QueryLocationNotFound(NotFound):
def __init__(self, location: Any, **kwargs: Dict[str, Any]) -> None: def __init__(self, location: Any, **kwargs: Dict[str, Any]) -> None:
"""Initialize a NotFound error for a query location.""" """Initialize a NotFound error for a query location."""
from hyperglass.state import use_state
super().__init__(type=TEXT.query_location, name=str(location), **kwargs) (text := use_state("params").web.text)
super().__init__(type=text.query_location, name=str(location), **kwargs)
class QueryTypeNotFound(NotFound): class QueryTypeNotFound(NotFound):
@ -96,10 +94,13 @@ class QueryTypeNotFound(NotFound):
def __init__(self, query_type: Any, **kwargs: Dict[str, Any]) -> None: def __init__(self, query_type: Any, **kwargs: Dict[str, Any]) -> None:
"""Initialize a NotFound error for a query type.""" """Initialize a NotFound error for a query type."""
super().__init__(type=TEXT.query_type, name=str(query_type), **kwargs) from hyperglass.state import use_state
(text := use_state("params").web.text)
super().__init__(type=text.query_type, name=str(query_type), **kwargs)
class InputInvalid(PublicHyperglassError, template=MESSAGES.invalid_input): class InputInvalid(PublicHyperglassError, template="invalid_input"):
"""Raised when input validation fails.""" """Raised when input validation fails."""
def __init__( def __init__(
@ -115,7 +116,7 @@ class InputInvalid(PublicHyperglassError, template=MESSAGES.invalid_input):
super().__init__(**kwargs) super().__init__(**kwargs)
class InputNotAllowed(PublicHyperglassError, template=MESSAGES.target_not_allowed): class InputNotAllowed(PublicHyperglassError, template="target_not_allowed"):
"""Raised when input validation fails due to a configured check.""" """Raised when input validation fails due to a configured check."""
def __init__( def __init__(
@ -135,7 +136,7 @@ class InputNotAllowed(PublicHyperglassError, template=MESSAGES.target_not_allowe
super().__init__(**kwargs) super().__init__(**kwargs)
class ResponseEmpty(PublicHyperglassError, template=MESSAGES.no_output): class ResponseEmpty(PublicHyperglassError, template="no_output"):
"""Raised when hyperglass can connect to the device but the response is empty.""" """Raised when hyperglass can connect to the device but the response is empty."""
def __init__( def __init__(

View file

@ -1,16 +1,14 @@
# Project # Project
from hyperglass.models.api import Query from hyperglass.models.api import Query
from hyperglass.configuration import init_user_config from hyperglass.state import use_state
from hyperglass.models.directive import Directives from hyperglass.test import initialize_state
from hyperglass.models.config.devices import Devices
# Local # Local
from .._construct import Construct from .._construct import Construct
def test_construct(): def test_construct():
devices = [
devices = Devices(
{ {
"name": "test1", "name": "test1",
"address": "127.0.0.1", "address": "127.0.0.1",
@ -19,15 +17,24 @@ def test_construct():
"attrs": {"source4": "192.0.2.1", "source6": "2001:db8::1"}, "attrs": {"source4": "192.0.2.1", "source6": "2001:db8::1"},
"directives": ["juniper_bgp_route"], "directives": ["juniper_bgp_route"],
} }
) ]
directives = Directives( directives = [
{"juniper_bgp_route": {"name": "BGP Route", "plugins": [], "rules": [], "groups": []}} {
) "juniper_bgp_route": {
init_user_config(devices=devices, directives=directives) "name": "BGP Route",
"field": {"description": "test"},
}
}
]
initialize_state(params={}, directives=directives, devices=devices)
state = use_state()
query = Query( query = Query(
queryLocation="test1", queryLocation="test1",
queryTarget="192.0.2.0/24", queryTarget="192.0.2.0/24",
queryType="juniper_bgp_route", queryType="juniper_bgp_route",
) )
constructor = Construct(device=devices["test1"], query=query) constructor = Construct(device=state.devices["test1"], query=query)
assert constructor.target == "192.0.2.0/24" assert constructor.target == "192.0.2.0/24"

View file

@ -20,7 +20,6 @@ from hyperglass.exceptions.private import InputValidationError
# Local # Local
from ..config.devices import Device from ..config.devices import Device
(TEXT := use_state("params").web.text)
QueryLocation = constr(strip_whitespace=True, strict=True, min_length=1) QueryLocation = constr(strip_whitespace=True, strict=True, min_length=1)
QueryTarget = constr(strip_whitespace=True, min_length=1) QueryTarget = constr(strip_whitespace=True, min_length=1)

View file

@ -143,7 +143,6 @@ class Device(HyperglassModelWithId, extra="allow"):
return self.platform return self.platform
def _validate_directive_attrs(self) -> None: def _validate_directive_attrs(self) -> None:
# Set of all keys except for built-in key `target`. # Set of all keys except for built-in key `target`.
keys = { keys = {
key key

View file

@ -75,6 +75,17 @@ class Messages(HyperglassModel):
description="Displayed when hyperglass can connect to a device and execute a query, but the response is empty.", description="Displayed when hyperglass can connect to a device and execute a query, but the response is empty.",
) )
def has(self, attr: str) -> bool:
"""Determine if message type exists in Messages model."""
return attr in self.dict().keys()
def __getitem__(self, attr: str) -> StrictStr:
"""Make messages subscriptable."""
if not self.has(attr):
raise KeyError(f"'{attr}' does not exist on Messages model")
return getattr(self, attr)
class Config: class Config:
"""Pydantic model configuration.""" """Pydantic model configuration."""

View file

@ -253,8 +253,8 @@ class Directive(HyperglassUniqueModel, unique_by=("id", "table_output")):
id: StrictStr id: StrictStr
name: StrictStr name: StrictStr
rules: t.List[RuleType] rules: t.List[RuleType] = [RuleWithPattern(condition="*")]
field: t.Union[Text, Select, None] field: t.Union[Text, Select]
info: t.Optional[FilePath] info: t.Optional[FilePath]
plugins: t.List[StrictStr] = [] plugins: t.List[StrictStr] = []
table_output: t.Optional[StrictStr] table_output: t.Optional[StrictStr]
@ -350,3 +350,13 @@ class Directives(MultiModel[Directive], model=Directive, unique_by="id"):
if _directive.id == directive.table_output: if _directive.id == directive.table_output:
return _directive return _directive
return directive return directive
@classmethod
def new(cls, /, *raw_directives: t.Dict[str, t.Any]) -> "Directives":
"""Create a new Directives collection from raw directive configurations."""
directives = (
Directive(id=name, **directive)
for raw_directive in raw_directives
for name, directive in raw_directive.items()
)
return Directives(*directives)

View file

@ -0,0 +1,4 @@
"""Global test helpers."""
from .state import initialize_state
__all__ = ("initialize_state",)

35
hyperglass/test/state.py Normal file
View file

@ -0,0 +1,35 @@
"""State-related test helpers."""
import typing as t
from hyperglass.state import use_state
from hyperglass.models.config.params import Params
from hyperglass.models.config.devices import Devices
from hyperglass.models.directive import Directives
from hyperglass.configuration import init_ui_params
def initialize_state(
*,
params: t.Dict[str, t.Any],
directives: t.Sequence[t.Dict[str, t.Any]],
devices: t.Sequence[t.Dict[str, t.Any]],
) -> None:
"""Test fixture to initialize Redis store."""
state = use_state()
_params = Params(**params)
_directives = Directives.new(*directives)
with state.cache.pipeline() as pipeline:
# Write params and directives to the cache first to avoid a race condition where ui_params
# or devices try to access params or directives before they're available.
pipeline.set("params", _params)
pipeline.set("directives", _directives)
# _devices = Devices.new(*devices)
_devices = Devices(*devices)
ui_params = init_ui_params(params=_params, devices=_devices)
with state.cache.pipeline() as pipeline:
pipeline.set("devices", _devices)
pipeline.set("ui_params", ui_params)