Skip to content

Commit

Permalink
moderation: restrict request duplication.
Browse files Browse the repository at this point in the history
  • Loading branch information
alejandromumo committed Aug 16, 2023
1 parent b765d8c commit 18fd835
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 20 deletions.
6 changes: 6 additions & 0 deletions invenio_requests/services/user_moderation/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,9 @@ class InvalidCreator(Exception):
"""Request creator is invalid."""

description = _("Invalid creator for user moderation request.")


class OpenRequestAlreadyExists(Exception):
"""An open request already exists for the user."""

description = _("There is already an open moderation request for this user.")
57 changes: 48 additions & 9 deletions invenio_requests/services/user_moderation/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
from invenio_accounts.models import Role
from invenio_i18n import gettext as _
from invenio_records_resources.services.uow import unit_of_work
from invenio_search.engine import dsl

from invenio_requests.customizations.user_moderation.user_moderation import (
UserModeration,
)
from invenio_requests.proxies import current_request_type_registry
from invenio_requests.services.user_moderation.errors import OpenRequestAlreadyExists


class UserModerationRequestService:
Expand All @@ -28,21 +30,32 @@ def request_type_cls(self):
"""User moderation request type."""
return current_request_type_registry.lookup(UserModeration.type_id)

@unit_of_work()
def request_moderation(self, identity, user_id, data=None, uow=None, **kwargs):
"""Creates a UserModeration request and submits it."""
REQUESTS_MODERATION_ROLE = current_app.config.get("REQUESTS_MODERATION_ROLE")
role = Role.query.filter(Role.name == REQUESTS_MODERATION_ROLE).one_or_none()
assert role, _("Moderation role must exist to enable user moderation requests.")
def _exists(self, identity, user_id):
"""Return the request id if an open request already exists, else None."""
results = self.requests_service.search(
identity,
extra_filter=dsl.query.Bool(
"must",
must=[
dsl.Q("term", **{"type": self.request_type_cls.type_id}),
dsl.Q("term", **{"topic.user": user_id}),
dsl.Q("term", **{"is_open": True}),
],
),
)
return next(results.hits)["id"] if results.total > 0 else None

data = data or {}
def _create_request(self, identity, user_id, creator, receiver, data, uow):
"""Creates and submits the request."""
if self._exists(identity, user_id):
raise OpenRequestAlreadyExists

# For user moderation, topic is the user to be moderated
topic = {"user": str(user_id)}

# Receiver can be configured, by default send the request to users with moderation role
receiver = {"group": role.name} # TODO to be changed to role id
creator = {"group": role.name} # TODO to be changed to role id
receiver = {"group": receiver} # TODO to be changed to role id
creator = {"group": creator} # TODO to be changed to role id

request_item = self.requests_service.create(
identity,
Expand All @@ -58,3 +71,29 @@ def request_moderation(self, identity, user_id, data=None, uow=None, **kwargs):
return self.requests_service.execute_action(
identity=identity, id_=request_item.id, action="submit", data=data, uow=uow
)

@unit_of_work()
def request_moderation(self, identity, user_id, data=None, uow=None, **kwargs):
"""Creates a UserModeration request and submits it."""
REQUESTS_MODERATION_ROLE = current_app.config.get("REQUESTS_MODERATION_ROLE")
role = Role.query.filter(Role.name == REQUESTS_MODERATION_ROLE).one_or_none()
assert role, _("Moderation role must exist to enable user moderation requests.")

data = data or {}

request = None
errors = []
try:
request = self._create_request(
identity,
user_id,
creator=role.name,
receiver=role.name,
data=data,
uow=uow,
)
except OpenRequestAlreadyExists:
# Avoid rolling back the uow if a request already exists, since there is nothing to rollback.
err = _("User already has an open moderation request.")
errors.append(err)
return request, errors
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@
def mod_request(app, user1, moderator_user):
service = current_user_moderation_service

request_item = service.request_moderation(moderator_user.identity, user_id=user1.id)
request_item, errors = service.request_moderation(
moderator_user.identity, user_id=user1.id
)
assert request_item
assert not errors

return request_item

Expand Down
58 changes: 48 additions & 10 deletions tests/services/user_moderation/test_user_moderation_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import pytest
from flask_principal import Need
from invenio_access.permissions import system_identity, system_user_id
from invenio_access.permissions import system_identity
from invenio_access.utils import get_identity
from invenio_records_resources.resources.errors import PermissionDeniedError
from marshmallow import ValidationError
Expand All @@ -18,27 +18,29 @@
current_requests_service,
current_user_moderation_service,
)
from invenio_requests.services.user_moderation.errors import OpenRequestAlreadyExists


def test_request_moderation(app, es_clear, users, identity_simple, mod_identity):
"""Tests the service for request moderation."""

user = users["user1"]
user1 = users["user1"]
user2 = users["user2"]

service = current_user_moderation_service

# Use system identity
request_item = service.request_moderation(system_identity, user_id=user.id)
request_item, errors = service.request_moderation(system_identity, user_id=user1.id)
assert request_item
# Request moderation creates and submits the moderation request
assert request_item._request.status == "submitted"

# User identity is not allowed to submit moderation requests
with pytest.raises(Exception):
service.request_moderation(identity_simple, user_id=user.id)
service.request_moderation(identity_simple, user_id=user1.id)

# Use system identity
request_item = service.request_moderation(mod_identity, user_id=user.id)
# Use moderator identity
request_item, errors = service.request_moderation(mod_identity, user_id=user2.id)
assert request_item
# Request moderation creates and submits the moderation request
assert request_item._request.status == "submitted"
Expand All @@ -51,7 +53,7 @@ def test_search_moderation(app, es_clear, users, submit_request, mod_identity):

service = current_user_moderation_service

request_item = service.request_moderation(system_identity, user_id=user.id)
request_item, errors = service.request_moderation(system_identity, user_id=user.id)
assert request_item

# Create a generic request to test the user moderation search filter
Expand Down Expand Up @@ -84,7 +86,7 @@ def test_moderation_accept(app, es_clear, users, mod_identity):

service = current_user_moderation_service

request_item = service.request_moderation(system_identity, user_id=user.id)
request_item, errors = service.request_moderation(system_identity, user_id=user.id)
assert request_item
assert request_item._request.status == "submitted"

Expand All @@ -107,7 +109,7 @@ def test_moderation_decline(app, es_clear, users, mod_identity):

service = current_user_moderation_service

request_item = service.request_moderation(system_identity, user_id=user.id)
request_item, errors = service.request_moderation(system_identity, user_id=user.id)
assert request_item
assert request_item._request.status == "submitted"

Expand All @@ -130,7 +132,7 @@ def test_read(app, es_clear, users, mod_identity):

service = current_user_moderation_service

request_item = service.request_moderation(system_identity, user_id=user.id)
request_item, errors = service.request_moderation(system_identity, user_id=user.id)
assert request_item
assert request_item._request.status == "submitted"

Expand All @@ -154,3 +156,39 @@ def test_invalid_request_data(app, es_clear, users, mod_identity):
service.request_moderation(
mod_identity, user_id=user.id, data={"invalid": "data"}
)


def test_duplicate_request(app, es_clear, users, mod_identity):
"""Test request creation when a request is already open."""
user = users["user1"]
service = current_user_moderation_service

# Create a moderation request
first_request, errors = service.request_moderation(mod_identity, user_id=user.id)
assert first_request
assert first_request._request.status == "submitted"

# Duplicated request are not allowed
second_request, errors = service.request_moderation(
mod_identity, user_id=user.id
) # nothing will happen
assert errors
assert not second_request

# Verify that only the first request was created
res = current_requests_service.search(
mod_identity, params={"q": f"topic.user:{user.id}"}
)
assert res.total == 1
assert res.to_dict()["hits"]["hits"][0]["id"] == first_request.id

# Resolve the request and re-request moderation
current_requests_service.execute_action(
mod_identity, id_=first_request.id, action="decline"
)

# New request
request_item, errors = service.request_moderation(mod_identity, user_id=user.id)
assert not errors
assert request_item
assert request_item._request.status == "submitted"

0 comments on commit 18fd835

Please sign in to comment.