diff --git a/oarepo_requests/components/__init__.py b/oarepo_requests/components/__init__.py index 8cd6c40b..7cfd1456 100644 --- a/oarepo_requests/components/__init__.py +++ b/oarepo_requests/components/__init__.py @@ -1,11 +1,6 @@ -from .requests import ( - AllowedRequestsComponent, - OAICreateRequestsComponent, - PublishDraftComponent, -) +from .requests import AllowedRequestsComponent, PublishDraftComponent __all__ = [ "AllowedRequestsComponent", "PublishDraftComponent", - "OAICreateRequestsComponent", ] diff --git a/oarepo_requests/components/requests.py b/oarepo_requests/components/requests.py index 79ed09f5..f2a415c2 100644 --- a/oarepo_requests/components/requests.py +++ b/oarepo_requests/components/requests.py @@ -11,14 +11,29 @@ current_requests_service, ) +""" +class RecordCommunitiesAction(CommunityRoles): + + def __init__(self, action): + self._action = action + + def roles(self, **kwargs): + return {r.name for r in current_roles.can(self._action)} +""" + class AllowedRequestsComponent(ServiceComponent): """Service component which sets all data in the record.""" + def _add_available_requests(self, identity, record, dict_to_save_result, **kwargs): # todo discriminate requests from other stuff which can be on parent in the future # todo what to throw if parent doesn't exist - requests = copy.deepcopy(record["parent"]) - requests.pop("id") + parent_copy = copy.deepcopy(record["parent"]) + requests = { + k: v + for k, v in parent_copy.items() + if isinstance(v, dict) and "receiver" in v + } # todo more sensible request identification available_requests = {} for request_name, request_dict in requests.items(): @@ -43,10 +58,13 @@ def _add_available_requests(self, identity, record, dict_to_save_result, **kwarg dict_to_save_result = kwargs[dict_to_save_result] dict_to_save_result["allowed_requests"] = available_requests + def before_ui_detail(self, identity, data=None, record=None, errors=None, **kwargs): self._add_available_requests(identity, record, "extra_context", **kwargs) + def before_ui_edit(self, identity, data=None, record=None, errors=None, **kwargs): self._add_available_requests(identity, record, "extra_context", **kwargs) + def form_config(self, identity, data=None, record=None, errors=None, **kwargs): self._add_available_requests(identity, record, "form_config", **kwargs) @@ -102,27 +120,7 @@ def publish(self, identity, data=None, record=None, **kwargs): self.uow.register(RecordCommitOp(record.parent)) -class OAICreateRequestsComponentPrivate(ServiceComponent): - def __init__(self, delete_request_type, *args, **kwargs): - super().__init__(*args, **kwargs) - self.delete_request_type = delete_request_type - - def create(self, identity, data=None, record=None, **kwargs): - type_ = current_request_type_registry.lookup( - self.delete_request_type, quiet=True - ) - request_item = current_requests_service.create( - identity, {}, type_, receiver=None, topic=record, uow=self.uow - ) - setattr(record.parent, self.delete_request_type, request_item._request) - self.uow.register(RecordCommitOp(record.parent)) - - def PublishDraftComponent(publish_request_type, delete_request_type): return functools.partial( PublishDraftComponentPrivate, publish_request_type, delete_request_type ) - - -def OAICreateRequestsComponent(delete_request_type): - return functools.partial(OAICreateRequestsComponentPrivate, delete_request_type) diff --git a/run_tests.sh b/run_tests.sh index 76c6e89c..85f582ce 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -1,28 +1,39 @@ #!/bin/bash set -e +OAREPO_VERSION=${OAREPO_VERSION:-11} +OAREPO_VERSION_MAX=$((OAREPO_VERSION+1)) + MODEL="thesis" -MODEL_VENV=".model_venv" -CODE_TEST_DIR="tests" + +BUILDER_VENV=.venv-builder BUILD_TEST_DIR="tests" +CODE_TEST_DIR="tests" -if test ! -d $BUILD_TEST_DIR; then - mkdir $BUILD_TEST_DIR +if test -d $BUILDER_VENV ; then + rm -rf $BUILDER_VENV fi +python3 -m venv $BUILDER_VENV +. $BUILDER_VENV/bin/activate +pip install -U setuptools pip wheel +pip install -U oarepo-model-builder-tests oarepo-model-builder-requests oarepo-model-builder-drafts + if test -d $BUILD_TEST_DIR/$MODEL; then rm -rf $MODEL fi +oarepo-compile-model ./$CODE_TEST_DIR/$MODEL.yaml --output-directory ./$BUILD_TEST_DIR/$MODEL -vvv + +MODEL_VENV=".venv-tests" + if test -d $MODEL_VENV; then rm -rf $MODEL_VENV fi - -oarepo-compile-model ./$CODE_TEST_DIR/$MODEL.yaml --output-directory ./$BUILD_TEST_DIR/$MODEL -vvv - python3 -m venv $MODEL_VENV . $MODEL_VENV/bin/activate pip install -U setuptools pip wheel +pip install "oarepo>=$OAREPO_VERSION,<$OAREPO_VERSION_MAX" pip install "./$BUILD_TEST_DIR/$MODEL[tests]" pip install . pip install oarepo-ui diff --git a/setup.cfg b/setup.cfg index 2b47d69f..f2f66b91 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = oarepo-requests -version = 1.0.2 +version = 1.0.3 description = authors = Ronald Krist readme = README.md diff --git a/tests/conftest.py b/tests/conftest.py index f310f9af..745653b5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,8 +9,8 @@ from invenio_accounts.testutils import login_user_via_session from invenio_app.factory import create_api from invenio_requests.customizations import CommentEventType, LogEventType -from invenio_requests.proxies import current_requests -from invenio_requests.records.api import RequestEventFormat +from invenio_requests.proxies import current_request_type_registry, current_requests +from invenio_requests.records.api import Request, RequestEventFormat from thesis.proxies import current_service from thesis.records.api import ThesisRecord @@ -37,6 +37,7 @@ def app_config(app_config): app_config[ "RECORDS_REFRESOLVER_STORE" ] = "invenio_jsonschemas.proxies.current_refresolver_store" + app_config["CACHE_TYPE"] = "SimpleCache" return app_config @@ -136,17 +137,6 @@ def users(app): return [user1, user2, admin] -@pytest.fixture(scope="module") -def identity_simple(users): - """Simple identity fixture.""" - user = users[0] - i = Identity(user.id) - i.provides.add(UserNeed(user.id)) - i.provides.add(Need(method="system_role", value="any_user")) - i.provides.add(Need(method="system_role", value="authenticated_user")) - return i - - @pytest.fixture() def client_with_login(client, users): """Log in a user to the client.""" @@ -187,3 +177,26 @@ def example_topic(record_service, identity_simple): id_ = record.id record = ThesisRecord.pid.resolve(id_) return record + + +@pytest.fixture(scope="module") +def identity_creator(identity_simple): # for readability + return identity_simple + + +@pytest.fixture(scope="module") +def identity_receiver(identity_simple_2): # for readability + return identity_simple_2 + + +@pytest.fixture(scope="function") +def request_with_receiver_user( + requests_service, example_topic, identity_creator, users +): + receiver = users[1] + type_ = current_request_type_registry.lookup("generic_request", quiet=True) + request_item = requests_service.create( + identity_creator, {}, type_, receiver=receiver, topic=example_topic + ) + request = Request.get_record(request_item.id) + return request_item diff --git a/tests/test_requests/test_requests.py b/tests/test_requests/test_requests.py index 18934b1c..86ce77b4 100644 --- a/tests/test_requests/test_requests.py +++ b/tests/test_requests/test_requests.py @@ -1,5 +1,6 @@ import pytest from invenio_pidstore.errors import PIDDeletedError, PIDUnregistered +from invenio_records_resources.services.errors import PermissionDeniedError from sqlalchemy.orm.exc import NoResultFound @@ -71,3 +72,27 @@ def test_parent_dump( record_service.read_draft(identity_simple, record_id) with pytest.raises(PIDDeletedError): record_service.read(identity_simple, record_id) + + +def test_receiver_permissions_user( + request_with_receiver_user, requests_service, identity_creator, identity_receiver +): + request_id = request_with_receiver_user.id + + with pytest.raises(PermissionDeniedError): + receiver_submit = requests_service.execute_action( + identity=identity_receiver, id_=request_id, action="submit" + ) + creator_submit = requests_service.execute_action( + identity=identity_creator, id_=request_id, action="submit" + ) + assert creator_submit.data["status"] == "submitted" + + with pytest.raises(PermissionDeniedError): + creator_accept = requests_service.execute_action( + identity=identity_creator, id_=request_id, action="accept" + ) + receiver_accept = requests_service.execute_action( + identity=identity_receiver, id_=request_id, action="accept" + ) + assert receiver_accept.data["status"] == "accepted" diff --git a/tests/test_ui/model.py b/tests/test_ui/model.py index ef0d8d4d..56d5ab5f 100644 --- a/tests/test_ui/model.py +++ b/tests/test_ui/model.py @@ -1,4 +1,3 @@ - from oarepo_ui.resources import ( BabelComponent, RecordsUIResource, @@ -18,13 +17,14 @@ class ModelUIResourceConfig(RecordsUIResourceConfig): blueprint_name = "thesis" url_prefix = "/thesis/" ui_serializer_class = ThesisUIJSONSerializer + templates = { **RecordsUIResourceConfig.templates, - "detail": {"layout": "test_detail.html", "blocks": {}}, + "detail": {"layout": "TestDetail.jinja", "blocks": {}}, "search": { - "layout": "test_detail.html", + "layout": "TestDetail.jinja", }, - "edit": {"layout": "test_edit.html"} + "edit": {"layout": "TestEdit.jinja"}, } components = [BabelComponent, PermissionsComponent, AllowedRequestsComponent] diff --git a/tests/test_ui/templates/TestDetail.jinja b/tests/test_ui/templates/TestDetail.jinja new file mode 100644 index 00000000..a355fa0b --- /dev/null +++ b/tests/test_ui/templates/TestDetail.jinja @@ -0,0 +1,5 @@ +{#def extra_context #} +available_requests= +{% for request_name, request in extra_context.allowed_requests.items() %} +{{ request_name }}: {{ request }} +{% endfor %} diff --git a/tests/test_ui/templates/test_edit.html b/tests/test_ui/templates/TestEdit.jinja similarity index 90% rename from tests/test_ui/templates/test_edit.html rename to tests/test_ui/templates/TestEdit.jinja index 62d10503..8bcd6b6b 100644 --- a/tests/test_ui/templates/test_edit.html +++ b/tests/test_ui/templates/TestEdit.jinja @@ -1,4 +1,4 @@ -links={{ links }} +{#def form_config, extra_context #} available_requests= {% for request_name, request in extra_context.allowed_requests.items() %} {{ request_name }}: {{ request }} diff --git a/tests/test_ui/templates/test_detail.html b/tests/test_ui/templates/test_detail.html deleted file mode 100644 index ba6fd402..00000000 --- a/tests/test_ui/templates/test_detail.html +++ /dev/null @@ -1,5 +0,0 @@ -links={{ links }} -available_requests= -{% for request_name, request in allowed_requests.items() %} -{{ request_name }}: {{ request }} -{% endfor %} diff --git a/tests/test_ui/test_ui_resource.py b/tests/test_ui/test_ui_resource.py index 891a9f5d..df3ffaef 100644 --- a/tests/test_ui/test_ui_resource.py +++ b/tests/test_ui/test_ui_resource.py @@ -1,6 +1,5 @@ - def test_draft_publish_request_present( - app, record_ui_resource, example_topic_draft, client_with_login, fake_manifest + app, record_ui_resource, example_topic_draft, client_with_login, fake_manifest ): def check_request(ctext): assert "publish_draft:" in ctext @@ -9,13 +8,13 @@ def check_request(ctext): assert "'receiver': None" in ctext assert "'actions': ['submit']" in ctext - with client_with_login.get(f"/thesis/{example_topic_draft['id']}/edit") as c: assert c.status_code == 200 base_part, form_part = c.text.split("allowed requests in form config:") check_request(base_part) check_request(form_part) + def test_draft_publish_unauthorized( app, record_ui_resource, example_topic, client, fake_manifest ): @@ -23,6 +22,7 @@ def test_draft_publish_unauthorized( assert c.status_code == 200 assert "publish_draft" not in c.text + def test_record_delete_request_present( app, record_ui_resource, example_topic, client_with_login, fake_manifest ): @@ -35,6 +35,7 @@ def test_record_delete_request_present( assert "'links': {}" in c.text assert "'actions': ['submit']" in c.text + def test_record_delete_unauthorized( app, record_ui_resource, example_topic, client, fake_manifest ): diff --git a/tests/thesis.yaml b/tests/thesis.yaml index 9dee4159..5143a910 100644 --- a/tests/thesis.yaml +++ b/tests/thesis.yaml @@ -34,6 +34,7 @@ record: submit: class: oarepo_requests.actions.delete_topic.DeleteTopicSubmitAction generate: False + generic-request: {} settings: schema-server: 'local://'