diff --git a/hive b/hive index fa16723f4197c9c2a94b9ce9c1c2c266743f9212..bda8d9d497cea53bf679b9cee929b583c1c5212b 160000 --- a/hive +++ b/hive @@ -1 +1 @@ -Subproject commit fa16723f4197c9c2a94b9ce9c1c2c266743f9212 +Subproject commit bda8d9d497cea53bf679b9cee929b583c1c5212b diff --git a/python/tests/helpy_test/functional/test_overseer.py b/python/tests/helpy_test/functional/test_overseer.py deleted file mode 100644 index 063bfe3a3495aab88472d4e62b7ad27393e12222..0000000000000000000000000000000000000000 --- a/python/tests/helpy_test/functional/test_overseer.py +++ /dev/null @@ -1,90 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, Final - -import pytest - -from beekeepy.communication import ( - CommonOverseer, - CommunicationSettings, - StrictOverseer, - get_communicator_cls, -) -from beekeepy.exceptions import ( - ApiNotFoundError, - JussiResponseError, - NullResultError, - OverseerError, - UnparsableResponseError, -) -from tests.utils.testing_server import run_simple_server - -if TYPE_CHECKING: - from beekeepy.communication import AbstractCommunicator, AbstractOverseer - - -ERRORS_TO_DETECT: Final[list[tuple[type[OverseerError], str]]] = [ - ( - NullResultError, - """{"jsonrpc": "2.0", "result": null, "id": 1}""", - ), - ( - ApiNotFoundError, - """{"jsonrpc": "2.0", "error": {"code": -32003, "message": - "Assert Exception:api_itr != data._registered_apis.end(): Could not find API debug_node_api" - }, "id": 1}""", - ), - ( - JussiResponseError, - """{"jsonrpc":"2.0","id":null,"error":{"code":-32603,"message": - "Internal Error","data":{"error_id":"b6384d8c-95ad-4af0-92dc-dd7828d3c707", - "jussi_request_id":"000312363819934224"}}}""", - ), - (UnparsableResponseError, """404: Not Found"""), -] - -SYNC_COMMUNICATORS: Final[list[type[AbstractCommunicator]]] = [ - get_communicator_cls("request"), - get_communicator_cls("httpx"), -] -ASYNC_COMMUNICATORS: Final[list[type[AbstractCommunicator]]] = [ - get_communicator_cls("aiohttp"), - get_communicator_cls("httpx"), -] -OVERSEERS: Final[list[type[AbstractOverseer]]] = [CommonOverseer, StrictOverseer] - -REQUEST: Final[str] = """{"method": "aaa", "id": 1, "jsonrpc": "2.0"}""" - - -@pytest.mark.parametrize("error_and_message", ERRORS_TO_DETECT) -@pytest.mark.parametrize("overseer_cls", OVERSEERS) -@pytest.mark.parametrize("communicator", SYNC_COMMUNICATORS) -def test_sync_overseer( - error_and_message: tuple[type[OverseerError], str], - overseer_cls: type[AbstractOverseer], - communicator: type[AbstractCommunicator], -) -> None: - error, message = error_and_message - overseer = overseer_cls(communicator=communicator(settings=CommunicationSettings())) - try: - with run_simple_server(message) as url, pytest.raises(error): - overseer.send(url=url, method="POST", data=REQUEST) - finally: - overseer.teardown() - - -@pytest.mark.parametrize("error_and_message", ERRORS_TO_DETECT) -@pytest.mark.parametrize("overseer_cls", OVERSEERS) -@pytest.mark.parametrize("communicator", ASYNC_COMMUNICATORS) -async def test_async_overseer( - error_and_message: tuple[type[OverseerError], str], - overseer_cls: type[AbstractOverseer], - communicator: type[AbstractCommunicator], -) -> None: - error, message = error_and_message - overseer = overseer_cls(communicator=communicator(settings=CommunicationSettings())) - try: - with run_simple_server(message) as url, pytest.raises(error): - await overseer.async_send(url=url, method="POST", data=REQUEST) - finally: - overseer.teardown() diff --git a/python/tests/utils/testing_server.py b/python/tests/utils/testing_server.py deleted file mode 100644 index a7042f881a4bf3571e563b0e6f95b2faf5312b57..0000000000000000000000000000000000000000 --- a/python/tests/utils/testing_server.py +++ /dev/null @@ -1,153 +0,0 @@ -from __future__ import annotations - -import asyncio -from socket import socket -import time -from abc import ABC, abstractmethod -from contextlib import contextmanager -from http import HTTPStatus -from threading import Thread -from typing import Any, Iterator -from typing_extensions import Self - -from aiohttp import web -from beekeepy.interfaces import HttpUrl, SelfContextAsync - - -class AsyncHttpServerError(BaseException): - pass - - -class ServerNotRunningError(AsyncHttpServerError): - def __init__(self) -> None: - super().__init__("Server is not running. Call run() first.") - - -class ServerAlreadyRunningError(AsyncHttpServerError): - def __init__(self) -> None: - super().__init__("Server is already running. Call close() first.") - - -class ServerSetupError(AsyncHttpServerError): - def __init__(self, message: str) -> None: - super().__init__(message) - - - -class HttpServerObserver(ABC): - @abstractmethod - async def data_received(self, data: dict[str, Any]) -> None: - """Called when any data is received via PUT method. - - Args: - data: data received as body - - Returns: - Nothing. - """ - - - -class AsyncHttpServer(SelfContextAsync): - __ADDRESS = HttpUrl("0.0.0.0:0") - - def __init__(self, observer: HttpServerObserver, notification_endpoint: HttpUrl | None) -> None: - self.__observer = observer - self._app = web.Application() - self.__site: web.TCPSite | None = None - self.__running: bool = False - self.__notification_endpoint = notification_endpoint - self._setup_routes() - - def _setup_routes(self) -> None: - async def handle_put_method(request: web.Request) -> web.Response: - await self.__observer.data_received(await request.json()) - return web.Response(status=HTTPStatus.NO_CONTENT) - - self._app.router.add_route("PUT", "/", handle_put_method) - - @property - def port(self) -> int: - if not self.__site: - raise ServerNotRunningError - server: asyncio.base_events.Server | None = self.__site._server # type: ignore[assignment] - if server is None: - raise ServerSetupError("self.__site.server is None") - - server_socket: socket = server.sockets[0] - address_tuple: tuple[str, int] = server_socket.getsockname() - - if not ( - isinstance(address_tuple, tuple) and isinstance(address_tuple[0], str) and isinstance(address_tuple[1], int) - ): - raise ServerSetupError(f"address_tuple has not recognizable types: {address_tuple}") - - return address_tuple[1] - - async def run(self) -> None: - if self.__site: - raise ServerAlreadyRunningError - - time_between_checks_is_server_running = 0.5 - - runner = web.AppRunner(self._app, access_log=False) - await runner.setup() - address = self.__notification_endpoint or self.__ADDRESS - self.__site = web.TCPSite(runner, address.address, address.port) - await self.__site.start() - self.__running = True - try: - while self.__running: # noqa: ASYNC110 - await asyncio.sleep(time_between_checks_is_server_running) - finally: - await self.__site.stop() - self.__site = None - - def close(self) -> None: - if not self.__site: - raise ServerNotRunningError - self.__running = False - - async def _aenter(self) -> Self: - await self.run() - return self - - async def _afinally(self) -> None: - self.close() - - - -class DummyObserver(HttpServerObserver): - async def data_received(self, data: dict[str, Any]) -> None: # noqa: ARG002 - return None - - -class TestAsyncHttpServer(AsyncHttpServer): - def __init__(self, response: str) -> None: - self.__response = response - super().__init__(DummyObserver(), None) - - def _setup_routes(self) -> None: - async def handle_post_method(request: web.Request) -> web.Response: # noqa: ARG001 - return web.Response(text=self.__response) - - self._app.router.add_route("POST", "/", handle_post_method) - - -def create_simple_server(response: str) -> TestAsyncHttpServer: - return TestAsyncHttpServer(response=response) - - -@contextmanager -def run_simple_server(response: str) -> Iterator[HttpUrl]: - server = create_simple_server(response) - - worker = Thread(target=asyncio.run, args=(server.run(),)) - worker.start() - time.sleep(0.5) - - try: - yield HttpUrl(f"http://127.0.0.1:{server.port}") - finally: - server.close() - worker.join()