Skip to content

Commit

Permalink
Add GetRatingQuery
Browse files Browse the repository at this point in the history
  • Loading branch information
madnoberson committed Jan 21, 2024
1 parent 5423d2c commit 312d2b4
Show file tree
Hide file tree
Showing 10 changed files with 318 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/amdb/application/common/constants/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
LOGIN_ACCESS_DENIED = "Access to login in denied"
CREATE_MOVIE_ACCESS_DENIED = "Access to movie creation is denied"
DELETE_MOVIE_ACCESS_DENIED = "Access to movie deletion is denied"
GET_RATING_ACCESS_DENIED = "Access to getting movie rating is denied"
RATE_MOVIE_ACCESS_DENIED = "Access to movie rating is denied"
UNRATE_MOVIE_ACCESS_DENIED = "Access to movie unrating is denied"

Expand Down
3 changes: 3 additions & 0 deletions src/amdb/application/common/interfaces/permissions_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ def for_create_movie(self) -> int:
def for_delete_movie(self) -> int:
raise NotImplementedError

def for_get_rating(self) -> int:
raise NotImplementedError

def for_rate_movie(self) -> int:
raise NotImplementedError

Expand Down
15 changes: 15 additions & 0 deletions src/amdb/application/queries/get_rating.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from dataclasses import dataclass
from datetime import datetime

from amdb.domain.entities.movie import MovieId


@dataclass(frozen=True, slots=True)
class GetRatingQuery:
movie_id: MovieId


@dataclass(frozen=True, slots=True)
class GetRatingResult:
value: float
created_at: datetime
59 changes: 59 additions & 0 deletions src/amdb/application/query_handlers/get_rating.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from amdb.domain.services.access_concern import AccessConcern
from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.rating_gateway import RatingGateway
from amdb.application.common.interfaces.identity_provider import IdentityProvider
from amdb.application.queries.get_rating import GetRatingQuery, GetRatingResult
from amdb.application.common.constants.exceptions import (
GET_RATING_ACCESS_DENIED,
MOVIE_DOES_NOT_EXIST,
MOVIE_NOT_RATED,
)
from amdb.application.common.exception import ApplicationError


class GetRatingHandler:
def __init__(
self,
*,
access_concern: AccessConcern,
permissions_gateway: PermissionsGateway,
movie_gateway: MovieGateway,
rating_gateway: RatingGateway,
identity_provider: IdentityProvider,
) -> None:
self._access_concern = access_concern
self._permissions_gateway = permissions_gateway
self._movie_gateway = movie_gateway
self._rating_gateway = rating_gateway
self._identity_provider = identity_provider

def execute(self, query: GetRatingQuery) -> GetRatingResult:
current_permissions = self._identity_provider.get_permissions()
required_permissions = self._permissions_gateway.for_get_rating()
access = self._access_concern.authorize(
current_permissions=current_permissions,
required_permissions=required_permissions,
)
if not access:
raise ApplicationError(GET_RATING_ACCESS_DENIED)

movie = self._movie_gateway.with_id(query.movie_id)
if not movie:
raise ApplicationError(MOVIE_DOES_NOT_EXIST)

current_user_id = self._identity_provider.get_user_id()

rating = self._rating_gateway.with_user_id_and_movie_id(
user_id=current_user_id,
movie_id=movie.id,
)
if not rating:
raise ApplicationError(MOVIE_NOT_RATED)

get_rating_result = GetRatingResult(
value=rating.value,
created_at=rating.created_at,
)

return get_rating_result
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ def for_create_movie(self) -> int:
def for_delete_movie(self) -> int:
return 8

def for_get_rating(self) -> int:
return 4

def for_rate_movie(self) -> int:
return 4

Expand Down
17 changes: 17 additions & 0 deletions src/amdb/main/ioc.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from amdb.application.command_handlers.rate_movie import RateMovieHandler
from amdb.application.command_handlers.unrate_movie import UnrateMovieHandler
from amdb.application.query_handlers.login import LoginHandler
from amdb.application.query_handlers.get_rating import GetRatingHandler
from amdb.infrastructure.persistence.sqlalchemy.gateway_factory import (
build_sqlalchemy_gateway_factory,
)
Expand Down Expand Up @@ -58,7 +59,9 @@ def login(self) -> Iterator[LoginHandler]:
user_password_hash_gateway=gateway_factory.user_password_hash(),
)
yield LoginHandler(
access_concern=AccessConcern(),
user_gateway=gateway_factory.user(),
permissions_gateway=self._permissions_gateway,
password_manager=hashing_password_manager,
)

Expand Down Expand Up @@ -92,6 +95,20 @@ def delete_movie(
identity_provider=identity_provider,
)

@contextmanager
def get_rating(
self,
identity_provider: IdentityProvider,
) -> Iterator[GetRatingHandler]:
with build_sqlalchemy_gateway_factory(self._sessionmaker) as gateway_factory:
yield GetRatingHandler(
access_concern=AccessConcern(),
permissions_gateway=self._permissions_gateway,
movie_gateway=gateway_factory.movie(),
rating_gateway=gateway_factory.rating(),
identity_provider=identity_provider,
)

@contextmanager
def rate_movie(
self,
Expand Down
8 changes: 8 additions & 0 deletions src/amdb/presentation/handler_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from amdb.application.command_handlers.rate_movie import RateMovieHandler
from amdb.application.command_handlers.unrate_movie import UnrateMovieHandler
from amdb.application.query_handlers.login import LoginHandler
from amdb.application.query_handlers.get_rating import GetRatingHandler


class HandlerFactory(ABC):
Expand All @@ -33,6 +34,13 @@ def delete_movie(
) -> ContextManager[DeleteMovieHandler]:
raise NotImplementedError

@abstractmethod
def get_rating(
self,
identity_provider: IdentityProvider,
) -> ContextManager[GetRatingHandler]:
raise NotImplementedError

@abstractmethod
def rate_movie(
self,
Expand Down
23 changes: 23 additions & 0 deletions src/amdb/presentation/web_api/routers/ratings/get_rating.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from typing import Annotated

from fastapi import Depends

from amdb.domain.entities.movie import MovieId
from amdb.application.common.interfaces.identity_provider import IdentityProvider
from amdb.application.queries.get_rating import GetRatingQuery, GetRatingResult
from amdb.presentation.handler_factory import HandlerFactory
from amdb.presentation.web_api.dependencies.identity_provider import get_identity_provider


async def get_rating(
ioc: Annotated[HandlerFactory, Depends()],
identity_provider: Annotated[IdentityProvider, Depends(get_identity_provider)],
movie_id: MovieId,
) -> GetRatingResult:
with ioc.get_rating(identity_provider) as get_rating_handler:
get_rating_query = GetRatingQuery(
movie_id=movie_id,
)
get_rating_result = get_rating_handler.execute(get_rating_query)

return get_rating_result
6 changes: 6 additions & 0 deletions src/amdb/presentation/web_api/routers/ratings/router.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from fastapi import APIRouter

from .get_rating import get_rating
from .rate_movie import rate_movie
from .unrate_movie import unrate_movie

Expand All @@ -10,6 +11,11 @@ def create_ratings_router() -> APIRouter:
tags=["ratings"],
)

router.add_api_route(
path="/{movie_id}",
endpoint=get_rating,
methods=["GET"],
)
router.add_api_route(
path="/{movie_id}",
endpoint=rate_movie,
Expand Down
183 changes: 183 additions & 0 deletions tests/unit/application/query_handlers/test_get_rating.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
from unittest.mock import Mock
from datetime import datetime, timezone

import pytest
from uuid_extensions import uuid7

from amdb.domain.entities.user import UserId, User
from amdb.domain.entities.movie import MovieId, Movie
from amdb.domain.entities.rating import Rating
from amdb.domain.services.access_concern import AccessConcern
from amdb.application.common.interfaces.user_gateway import UserGateway
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.rating_gateway import RatingGateway
from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
from amdb.application.common.interfaces.identity_provider import IdentityProvider
from amdb.application.queries.get_rating import GetRatingQuery, GetRatingResult
from amdb.application.query_handlers.get_rating import GetRatingHandler
from amdb.application.common.constants.exceptions import (
GET_RATING_ACCESS_DENIED,
MOVIE_DOES_NOT_EXIST,
MOVIE_NOT_RATED,
)
from amdb.application.common.exception import ApplicationError


@pytest.fixture
def identity_provider_with_correct_permissions(
permissions_gateway: PermissionsGateway,
) -> IdentityProvider:
identity_provider = Mock()

correct_permissions = permissions_gateway.for_get_rating()
identity_provider.get_permissions = Mock(return_value=correct_permissions)

return identity_provider


def test_get_rating(
user_gateway: UserGateway,
movie_gateway: MovieGateway,
rating_gateway: RatingGateway,
permissions_gateway: PermissionsGateway,
unit_of_work: UnitOfWork,
identity_provider_with_correct_permissions: IdentityProvider,
):
user = User(
id=UserId(uuid7()),
name="John Doe",
)
user_gateway.save(user)

movie = Movie(
id=MovieId(uuid7()),
title="Matrix",
rating=10,
rating_count=1,
)
movie_gateway.save(movie)

rating = Rating(
movie_id=movie.id,
user_id=user.id,
value=10,
created_at=datetime.now(timezone.utc),
)
rating_gateway.save(rating)

unit_of_work.commit()

identity_provider_with_correct_permissions.get_user_id = Mock(
return_value=user.id,
)

get_rating_query = GetRatingQuery(
movie_id=movie.id,
)
get_rating_handler = GetRatingHandler(
access_concern=AccessConcern(),
permissions_gateway=permissions_gateway,
movie_gateway=movie_gateway,
rating_gateway=rating_gateway,
identity_provider=identity_provider_with_correct_permissions,
)

get_rating_result = get_rating_handler.execute(get_rating_query)
expected_get_rating_result = GetRatingResult(
value=rating.value,
created_at=rating.created_at,
)

assert get_rating_result == expected_get_rating_result


def test_get_rating_should_raise_error_when_access_is_denied(
movie_gateway: MovieGateway,
rating_gateway: RatingGateway,
permissions_gateway: PermissionsGateway,
identity_provider_with_incorrect_permissions: IdentityProvider,
):
get_rating_query = GetRatingQuery(
movie_id=MovieId(uuid7()),
)
get_rating_handler = GetRatingHandler(
access_concern=AccessConcern(),
permissions_gateway=permissions_gateway,
movie_gateway=movie_gateway,
rating_gateway=rating_gateway,
identity_provider=identity_provider_with_incorrect_permissions,
)

with pytest.raises(ApplicationError) as error:
get_rating_handler.execute(get_rating_query)

assert error.value.message == GET_RATING_ACCESS_DENIED


def test_get_rating_should_raise_error_when_movie_does_not_exist(
movie_gateway: MovieGateway,
rating_gateway: RatingGateway,
permissions_gateway: PermissionsGateway,
identity_provider_with_correct_permissions: IdentityProvider,
):
get_rating_query = GetRatingQuery(
movie_id=MovieId(uuid7()),
)
get_rating_handler = GetRatingHandler(
access_concern=AccessConcern(),
permissions_gateway=permissions_gateway,
movie_gateway=movie_gateway,
rating_gateway=rating_gateway,
identity_provider=identity_provider_with_correct_permissions,
)

with pytest.raises(ApplicationError) as error:
get_rating_handler.execute(get_rating_query)

assert error.value.message == MOVIE_DOES_NOT_EXIST


def test_get_rating_should_raise_error_when_movie_is_not_rated(
user_gateway: UserGateway,
movie_gateway: MovieGateway,
rating_gateway: RatingGateway,
permissions_gateway: PermissionsGateway,
unit_of_work: UnitOfWork,
identity_provider_with_correct_permissions: IdentityProvider,
):
user = User(
id=UserId(uuid7()),
name="John Doe",
)
user_gateway.save(user)

movie = Movie(
id=MovieId(uuid7()),
title="Matrix",
rating=0,
rating_count=0,
)
movie_gateway.save(movie)

unit_of_work.commit()

identity_provider_with_correct_permissions.get_user_id = Mock(
return_value=user.id,
)

get_rating_query = GetRatingQuery(
movie_id=movie.id,
)
get_rating_handler = GetRatingHandler(
access_concern=AccessConcern(),
permissions_gateway=permissions_gateway,
movie_gateway=movie_gateway,
rating_gateway=rating_gateway,
identity_provider=identity_provider_with_correct_permissions,
)

with pytest.raises(ApplicationError) as error:
get_rating_handler.execute(get_rating_query)

assert error.value.message == MOVIE_NOT_RATED

0 comments on commit 312d2b4

Please sign in to comment.