Skip to content
This repository has been archived by the owner on Sep 12, 2023. It is now read-only.

Commit

Permalink
chore(tests): adding coverage (#97)
Browse files Browse the repository at this point in the history
* chore(tests): tests for endpoint_decorator.py

* chore(tests): tests for http.py

* chore(tests): linting

* chore(tests): tests for init_plugin.py

* chore(tests): tests cleaning up various missing.
  • Loading branch information
peterschutt committed Nov 7, 2022
1 parent a7ff981 commit b1cc226
Show file tree
Hide file tree
Showing 9 changed files with 228 additions and 13 deletions.
9 changes: 4 additions & 5 deletions src/starlite_saqlalchemy/init_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ class PluginConfig(BaseModel):
application.
"""

# why isn't the callback defined here?
worker_functions: list[Callable[..., Any] | tuple[str, Callable[..., Any]]] = [
(make_service_callback.__qualname__, make_service_callback)
]
Expand Down Expand Up @@ -193,6 +192,7 @@ def __call__(self, app_config: AppConfig) -> AppConfig:
self.configure_exception_handlers(app_config)
self.configure_health_check(app_config)
self.configure_logging(app_config)
self.configure_openapi(app_config)
self.configure_response_class(app_config)
self.configure_sentry(app_config)
self.configure_sqlalchemy_plugin(app_config)
Expand All @@ -208,8 +208,7 @@ def configure_after_exception(self, app_config: AppConfig) -> None:
app_config: The Starlite application config object.
"""
if self.config.do_after_exception:
if not isinstance(app_config.after_exception, list):
app_config.after_exception = [app_config.after_exception]
app_config.after_exception = self._ensure_list(app_config.after_exception)
app_config.after_exception.append(exceptions.after_exception_hook_handler)

def configure_cache(self, app_config: AppConfig) -> None:
Expand All @@ -221,7 +220,7 @@ def configure_cache(self, app_config: AppConfig) -> None:
Args:
app_config: The Starlite application config object.
"""
if self.config.do_cache and app_config.cache_config is DEFAULT_CACHE_CONFIG:
if self.config.do_cache and app_config.cache_config == DEFAULT_CACHE_CONFIG:
app_config.cache_config = cache.config

def configure_collection_dependencies(self, app_config: AppConfig) -> None:
Expand Down Expand Up @@ -300,7 +299,7 @@ def configure_openapi(self, app_config: AppConfig) -> None:
Args:
app_config: The Starlite application config object.
"""
if self.config.do_openapi and app_config.openapi_config is DEFAULT_OPENAPI_CONFIG:
if self.config.do_openapi and app_config.openapi_config == DEFAULT_OPENAPI_CONFIG:
app_config.openapi_config = openapi.config

def configure_response_class(self, app_config: AppConfig) -> None:
Expand Down
2 changes: 1 addition & 1 deletion src/starlite_saqlalchemy/log/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def _make_filtering_bound_logger(min_level: int) -> type[structlog.types.Filteri
class _WrappedFilteringBoundLogger(filtering_bound_logger): # type:ignore[misc,valid-type]
async def alog(self: Any, level: int, event: str, *args: Any, **kw: Any) -> Any:
"""This method will exist in the next release of structlog."""
if level < min_level:
if level < min_level: # pragma: no cover
return None
# pylint: disable=protected-access
name = structlog._log_levels._LEVEL_TO_NAME[level] # pyright: ignore
Expand Down
2 changes: 2 additions & 0 deletions src/starlite_saqlalchemy/repository/sqlalchemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ async def list(self, *filters: FilterTypes, **kwargs: Any) -> list[ModelT]:
self._filter_on_datetime_field(field_name, before, after)
case CollectionFilter(field_name, values):
self._filter_in_collection(field_name, values)
case _:
raise RepositoryException(f"Unexpected filter: {filter}")
self._filter_select_by_kwargs(**kwargs)

with wrap_sqlalchemy_exception():
Expand Down
23 changes: 23 additions & 0 deletions tests/unit/repository/test_abc.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
"""Tests for the repository base class."""
from __future__ import annotations

from typing import TYPE_CHECKING
from unittest.mock import MagicMock

import pytest

from starlite_saqlalchemy.repository.exceptions import RepositoryNotFoundException
from starlite_saqlalchemy.testing.repository import GenericMockRepository

if TYPE_CHECKING:
from pytest import MonkeyPatch


def test_repository_check_not_found_raises() -> None:
"""Test `check_not_found()` raises if `None`."""
Expand All @@ -17,3 +23,20 @@ def test_repository_check_not_found_returns_item() -> None:
"""Test `check_not_found()` returns the item if not `None`."""
mock_item = MagicMock()
assert GenericMockRepository.check_not_found(mock_item) is mock_item


def test_repository_get_id_attribute_value(monkeypatch: MonkeyPatch) -> None:
"""Test id attribute value retrieval."""
monkeypatch.setattr(GenericMockRepository, "id_attribute", "random_attribute")
mock = MagicMock()
mock.random_attribute = "this one"
assert GenericMockRepository.get_id_attribute_value(mock) == "this one"


def test_repository_set_id_attribute_value(monkeypatch: MonkeyPatch) -> None:
"""Test id attribute value setter."""
monkeypatch.setattr(GenericMockRepository, "id_attribute", "random_attribute")
mock = MagicMock()
mock.random_attribute = "this one"
mock = GenericMockRepository.set_id_attribute_value("no this one", mock)
assert mock.random_attribute == "no this one"
40 changes: 33 additions & 7 deletions tests/unit/repository/test_sqlalchemy.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Unit tests for the SQLAlchemy Repository implementation."""
# pylint: disable=protected-access,redefined-outer-name
from __future__ import annotations

from datetime import datetime
from typing import TYPE_CHECKING
from unittest.mock import AsyncMock, MagicMock, call
Expand Down Expand Up @@ -63,7 +65,7 @@ async def test_sqlalchemy_repo_add(mock_repo: SQLAlchemyRepository) -> None:


async def test_sqlalchemy_repo_delete(
mock_repo: SQLAlchemyRepository, monkeypatch: "MonkeyPatch"
mock_repo: SQLAlchemyRepository, monkeypatch: MonkeyPatch
) -> None:
"""Test expected method calls for delete operation."""
mock_instance = MagicMock()
Expand All @@ -77,7 +79,7 @@ async def test_sqlalchemy_repo_delete(


async def test_sqlalchemy_repo_get_member(
mock_repo: SQLAlchemyRepository, monkeypatch: "MonkeyPatch"
mock_repo: SQLAlchemyRepository, monkeypatch: MonkeyPatch
) -> None:
"""Test expected method calls for member get operation."""
mock_instance = MagicMock()
Expand All @@ -92,7 +94,7 @@ async def test_sqlalchemy_repo_get_member(


async def test_sqlalchemy_repo_list(
mock_repo: SQLAlchemyRepository, monkeypatch: "MonkeyPatch"
mock_repo: SQLAlchemyRepository, monkeypatch: MonkeyPatch
) -> None:
"""Test expected method calls for list operation."""
mock_instances = [MagicMock(), MagicMock()]
Expand All @@ -107,7 +109,7 @@ async def test_sqlalchemy_repo_list(


async def test_sqlalchemy_repo_list_with_pagination(
mock_repo: SQLAlchemyRepository, monkeypatch: "MonkeyPatch"
mock_repo: SQLAlchemyRepository, monkeypatch: MonkeyPatch
) -> None:
"""Test list operation with pagination."""
result_mock = MagicMock()
Expand All @@ -121,7 +123,7 @@ async def test_sqlalchemy_repo_list_with_pagination(


async def test_sqlalchemy_repo_list_with_before_after_filter(
mock_repo: SQLAlchemyRepository, monkeypatch: "MonkeyPatch"
mock_repo: SQLAlchemyRepository, monkeypatch: MonkeyPatch
) -> None:
"""Test list operation with BeforeAfter filter."""
field_name = "updated"
Expand All @@ -138,7 +140,7 @@ async def test_sqlalchemy_repo_list_with_before_after_filter(


async def test_sqlalchemy_repo_list_with_collection_filter(
mock_repo: SQLAlchemyRepository, monkeypatch: "MonkeyPatch"
mock_repo: SQLAlchemyRepository, monkeypatch: MonkeyPatch
) -> None:
"""Test behavior of list operation given CollectionFilter."""
field_name = "id"
Expand All @@ -152,8 +154,14 @@ async def test_sqlalchemy_repo_list_with_collection_filter(
getattr(mock_repo.model_type, field_name).in_.assert_called_once_with(values)


async def test_sqlalchemy_repo_unknown_filter_type_raises(mock_repo: SQLAlchemyRepository) -> None:
"""Test that repo raises exception if list receives unknown filter type."""
with pytest.raises(RepositoryException):
await mock_repo.list("not a filter") # type:ignore[arg-type]


async def test_sqlalchemy_repo_update(
mock_repo: SQLAlchemyRepository, monkeypatch: "MonkeyPatch"
mock_repo: SQLAlchemyRepository, monkeypatch: MonkeyPatch
) -> None:
"""Test the sequence of repo calls for update operation."""
id_ = 3
Expand Down Expand Up @@ -203,3 +211,21 @@ def test_filter_in_collection_noop_if_collection_empty(mock_repo: SQLAlchemyRepo
"""Ensures we don't filter on an empty collection."""
mock_repo._filter_in_collection("id", [])
mock_repo._select.where.assert_not_called()


@pytest.mark.parametrize(
("before", "after"),
[
(datetime.max, datetime.min),
(None, datetime.min),
(datetime.max, None),
],
)
def test__filter_on_datetime_field(
before: datetime, after: datetime, mock_repo: SQLAlchemyRepository
) -> None:
"""Test through branches of _filter_on_datetime_field()"""
field_mock = MagicMock()
field_mock.__gt__ = field_mock.__lt__ = lambda self, other: True
mock_repo.model_type.updated = field_mock
mock_repo._filter_on_datetime_field("updated", before, after)
30 changes: 30 additions & 0 deletions tests/unit/test_endpoint_decorator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Tests for endpoint_decorator.py."""
# pylint: disable=too-few-public-methods
from __future__ import annotations

import pytest

from starlite_saqlalchemy import endpoint_decorator


def test_endpoint_decorator() -> None:
"""Test for basic functionality."""

@endpoint_decorator.endpoint(base_url="/something")
class Endpoint:
"""Endpoints for something."""

root = ""
nested = "/somewhere"

assert Endpoint.root == "/something/"
assert Endpoint.nested == "/something/somewhere"


def test_endpoint_decorator_raises_if_no_base_url() -> None:
"""Test raising behavior when no base_url provided."""
with pytest.raises(RuntimeError):

@endpoint_decorator.endpoint
class Endpoint: # pylint: disable=unused-variable
"""whoops."""
44 changes: 44 additions & 0 deletions tests/unit/test_http.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Tests for http.py."""
# pylint: disable=protected-access
from __future__ import annotations

from typing import TYPE_CHECKING
from unittest.mock import AsyncMock, MagicMock

import httpx
import pytest

from starlite_saqlalchemy import http

if TYPE_CHECKING:

from pytest import MonkeyPatch


async def test_client_request(monkeypatch: MonkeyPatch) -> None:
"""Tests logic of request() method."""
response_mock = MagicMock()
request_mock = AsyncMock(return_value=response_mock)
monkeypatch.setattr(http.Client._client, "request", request_mock)
res = await http.Client().request("with", "args", and_some="kwargs")
request_mock.assert_called_once_with("with", "args", and_some="kwargs")
response_mock.raise_for_status.assert_called_once()
assert res is response_mock


async def test_client_raises_client_exception(monkeypatch: MonkeyPatch) -> None:
"""Tests that we convert httpx exceptions into ClientException."""
exc = httpx.HTTPError("a message")
req = AsyncMock(side_effect=exc)
req.url = "http://whatever.com"
exc.request = req
monkeypatch.setattr(http.Client._client, "request", req)
with pytest.raises(http.ClientException):
await http.Client().request()


def test_client_json() -> None:
"""Tests the json() and unwrap_json() passthrough."""
resp = MagicMock()
resp.json.return_value = {"data": "data"}
assert http.Client().json(resp) == {"data": "data"}
82 changes: 82 additions & 0 deletions tests/unit/test_init_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""Tests for init_plugin.py."""
from __future__ import annotations

from typing import TYPE_CHECKING
from unittest.mock import MagicMock

import pytest
from starlite import Starlite
from starlite.cache import SimpleCacheBackend

from starlite_saqlalchemy import init_plugin

if TYPE_CHECKING:
from typing import Any

from pytest import MonkeyPatch


def test_config_switches() -> None:
"""Tests that the app produced with all config switches off is as we
expect."""
config = init_plugin.PluginConfig(
do_after_exception=False,
do_cache=False,
do_compression=False,
# pyright reckons this parameter doesn't exist, I beg to differ
do_collection_dependencies=False, # pyright:ignore
do_exception_handlers=False,
do_health_check=False,
do_logging=False,
do_openapi=False,
do_response_class=False,
do_sentry=False,
do_sqlalchemy_plugin=False,
do_worker=False,
)
app = Starlite(
route_handlers=[],
openapi_config=None,
on_app_init=[init_plugin.ConfigureApp(config=config)],
)
assert app.compression_config is None
assert app.logging_config is None
assert app.openapi_config is None
assert app.response_class is None
assert isinstance(app.cache.backend, SimpleCacheBackend)
# client.close and redis.close go in there unconditionally atm
assert len(app.on_shutdown) == 2
assert not app.after_exception
assert not app.dependencies
assert not app.exception_handlers
assert not app.on_startup
assert not app.plugins
assert not app.routes


def test_do_worker_but_not_logging(monkeypatch: MonkeyPatch) -> None:
"""Tests branch where we can have the worker enabled, but logging
disabled."""
mock = MagicMock()
monkeypatch.setattr(init_plugin, "create_worker_instance", mock)
config = init_plugin.PluginConfig(do_logging=False)
Starlite(route_handlers=[], on_app_init=[init_plugin.ConfigureApp(config=config)])
mock.assert_called_once()
call = mock.mock_calls[0]
assert "before_process" not in call.kwargs
assert "after_process" not in call.kwargs


@pytest.mark.parametrize(
("in_", "out"),
[
(["something"], ["something"]),
("something", ["something"]),
([], []),
(None, []),
],
)
def test_ensure_list(in_: Any, out: Any) -> None:
"""Test _ensure_list() functionality."""
# pylint: disable=protected-access
assert init_plugin.ConfigureApp._ensure_list(in_) == out
9 changes: 9 additions & 0 deletions tests/unit/test_orm.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from unittest.mock import MagicMock

from starlite_saqlalchemy import orm
from tests.utils.domain import Author, CreateDTO


def test_sqla_touch_updated_timestamp() -> None:
Expand All @@ -12,3 +13,11 @@ def test_sqla_touch_updated_timestamp() -> None:
orm.touch_updated_timestamp(mock_session)
for mock_instance in mock_session.dirty:
assert isinstance(mock_instance.updated, datetime.datetime)


def test_from_dto() -> None:
"""Test conversion of a DTO instance to a model instance."""
data = CreateDTO(name="someone", dob="1982-03-22")
author = Author.from_dto(data)
assert author.name == "someone"
assert author.dob == datetime.date(1982, 3, 22)

0 comments on commit b1cc226

Please sign in to comment.