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

chore(tests): adding coverage #97

Merged
merged 6 commits into from
Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)