diff --git a/back/boxtribute_server/business_logic/warehouse/qr_code/crud.py b/back/boxtribute_server/business_logic/warehouse/qr_code/crud.py index b00cf2db4d..f7ac87b76b 100644 --- a/back/boxtribute_server/business_logic/warehouse/qr_code/crud.py +++ b/back/boxtribute_server/business_logic/warehouse/qr_code/crud.py @@ -1,8 +1,11 @@ import hashlib from ....db import db +from ....errors import DeletedLocation, ResourceDoesNotExist from ....models.definitions.box import Box from ....models.definitions.history import DbChangeHistory +from ....models.definitions.location import Location +from ....models.definitions.product import Product from ....models.definitions.qr_code import QrCode from ....models.utils import utcnow @@ -16,6 +19,30 @@ def create_qr_code(*, user_id, box_label_identifier=None): All operations are run inside an atomic transaction. If e.g. the box look-up fails, the operations are rolled back (i.e. no new QR code is inserted). """ + box = None + if box_label_identifier is not None: + box = ( + Box.select( + Box, + Product.id, # otherwise box.save violates FK constraint + Product.deleted_on, + Product.name, + Location.id, + Location.deleted_on, + Location.name, + ) + .join(Product) + .join(Location, src=Box) + .where(Box.label_identifier == box_label_identifier) + .get_or_none() + ) + + if box is None: + return ResourceDoesNotExist(name="Box") + + if box.location.deleted_on is not None: + return DeletedLocation(name=box.location.name) + with db.database.atomic(): now = utcnow() new_qr_code = QrCode.create(created_on=now) @@ -33,8 +60,7 @@ def create_qr_code(*, user_id, box_label_identifier=None): change_date=now, ) - if box_label_identifier is not None: - box = Box.get(Box.label_identifier == box_label_identifier) + if box is not None: box.qr_code = new_qr_code.id box.save() diff --git a/back/boxtribute_server/business_logic/warehouse/qr_code/mutations.py b/back/boxtribute_server/business_logic/warehouse/qr_code/mutations.py index 72da2dc151..5c838d5dc9 100644 --- a/back/boxtribute_server/business_logic/warehouse/qr_code/mutations.py +++ b/back/boxtribute_server/business_logic/warehouse/qr_code/mutations.py @@ -1,13 +1,14 @@ from ariadne import MutationType from flask import g -from ....authz import authorize +from ....authz import authorize, handle_unauthorized from .crud import create_qr_code mutation = MutationType() @mutation.field("createQrCode") +@handle_unauthorized def resolve_create_qr_code(*_, box_label_identifier=None): authorize(permission="qr:create") return create_qr_code(user_id=g.user.id, box_label_identifier=box_label_identifier) diff --git a/back/boxtribute_server/graph_ql/bindables.py b/back/boxtribute_server/graph_ql/bindables.py index 5b737decbe..0701e6105d 100644 --- a/back/boxtribute_server/graph_ql/bindables.py +++ b/back/boxtribute_server/graph_ql/bindables.py @@ -194,6 +194,7 @@ def resolve_data_cube_type(obj, *_): UnionType("DeleteBoxesResult", resolve_type_by_class_name), UnionType("MoveBoxesResult", resolve_type_by_class_name), UnionType("QrCodeResult", resolve_type_by_class_name), + UnionType("CreateQrCodeResult", resolve_type_by_class_name), UnionType("BoxResult", resolve_type_by_class_name), UnionType("ShareableLinkCreationResult", resolve_type_by_class_name), UnionType("TagError", resolve_type_by_class_name), diff --git a/back/boxtribute_server/graph_ql/definitions/protected/mutations.graphql b/back/boxtribute_server/graph_ql/definitions/protected/mutations.graphql index d01747639e..ff5fe019b3 100644 --- a/back/boxtribute_server/graph_ql/definitions/protected/mutations.graphql +++ b/back/boxtribute_server/graph_ql/definitions/protected/mutations.graphql @@ -2,7 +2,7 @@ # - input argument: creationInput/updateInput # - input type: CreationInput/UpdateInput type Mutation { - createQrCode(boxLabelIdentifier: String): QrCode + createQrCode(boxLabelIdentifier: String): CreateQrCodeResult " Create a new box in a location, containing items of certain product and size. Optionally pass tags to assign to the box. " createBox(creationInput: BoxCreationInput): Box " Update one or more properties of a box with specified label identifier. " diff --git a/back/boxtribute_server/graph_ql/definitions/protected/types.graphql b/back/boxtribute_server/graph_ql/definitions/protected/types.graphql index 42bee9241b..c00241875c 100644 --- a/back/boxtribute_server/graph_ql/definitions/protected/types.graphql +++ b/back/boxtribute_server/graph_ql/definitions/protected/types.graphql @@ -697,10 +697,21 @@ type DeletedTagError { type DeletedBaseError { name: String! } +type DeletedProductError { + name: String! +} +type DeletedBoxError { + labelIdentifier: String! +} +type QrCodeAlreadyAssignedToBox { + code: String! + labelIdentifier: String! +} type InvalidDateError { date: Datetime! } +union CreateQrCodeResult = QrCode | DeletedBoxError | DeletedLocationError | DeletedProductError | InsufficientPermissionError | ResourceDoesNotExistError | UnauthorizedForBaseError | QrCodeAlreadyAssignedToBox union MoveBoxesResult = BoxesResult | InsufficientPermissionError | ResourceDoesNotExistError | UnauthorizedForBaseError | DeletedLocationError union DeleteBoxesResult = BoxesResult | InsufficientPermissionError union CreateCustomProductResult = Product | InsufficientPermissionError | ResourceDoesNotExistError | UnauthorizedForBaseError | InvalidPriceError | EmptyNameError diff --git a/back/test/auth0_integration_tests/test_operations.py b/back/test/auth0_integration_tests/test_operations.py index e6b3ee772e..6dbf2b96a4 100644 --- a/back/test/auth0_integration_tests/test_operations.py +++ b/back/test/auth0_integration_tests/test_operations.py @@ -77,7 +77,7 @@ def test_mutations(auth0_client, mocker): ) user_id = "100000001" - mutation = "mutation { createQrCode { id } }" + mutation = "mutation { createQrCode { ...on QrCode { id } } }" response = assert_successful_request(auth0_client, mutation, field="createQrCode") assert response is not None diff --git a/back/test/auth0_integration_tests/test_permissions.py b/back/test/auth0_integration_tests/test_permissions.py index 06970e2462..935105d171 100644 --- a/back/test/auth0_integration_tests/test_permissions.py +++ b/back/test/auth0_integration_tests/test_permissions.py @@ -233,7 +233,7 @@ def test_check_beta_feature_access(dropapp_dev_client, mocker): "dev_coordinator@boxaid.org" ) - mutation = "mutation { createQrCode { id } }" + mutation = "mutation { createQrCode { ...on QrCode { id } } }" assert_successful_request(dropapp_dev_client, mutation) mutation = "mutation { deleteTag(id: 1) { id } }" diff --git a/back/test/endpoint_tests/test_app.py b/back/test/endpoint_tests/test_app.py index 9575dc47ec..3cb8e259c7 100644 --- a/back/test/endpoint_tests/test_app.py +++ b/back/test/endpoint_tests/test_app.py @@ -63,8 +63,8 @@ def test_base_specific_permissions(client, mocker): data = { "query": """mutation { - qr2: createQrCode { code } - qr3: createQrCode { code } + qr2: createQrCode { ...on QrCode { code } } + qr3: createQrCode { ...on QrCode { code } } }""" } response = client.post("/graphql", json=data) @@ -354,6 +354,13 @@ def test_update_non_existent_resource( "...on UnauthorizedForBaseError { id name organisationName }", {"id": "0", "name": "", "organisationName": ""}, ], + # Test case 8.2.32 + [ + "createQrCode", + 'boxLabelIdentifier: "xxx"', + "...on ResourceDoesNotExistError { id name }", + {"id": None, "name": "Box"}, + ], ], ) def test_mutate_resource_does_not_exist( @@ -395,7 +402,7 @@ def test_mutation_arbitrary_database_error(read_only_client, mocker): mocker.patch( "boxtribute_server.business_logic.warehouse.qr_code.mutations.create_qr_code" ).side_effect = peewee.PeeweeException - mutation = "mutation { createQrCode { id } }" + mutation = "mutation { createQrCode { ...on QrCode { id } } }" assert_internal_server_error(read_only_client, mutation, field="createQrCode") diff --git a/back/test/endpoint_tests/test_cron.py b/back/test/endpoint_tests/test_cron.py index e7f4da6dac..5d9f70da56 100644 --- a/back/test/endpoint_tests/test_cron.py +++ b/back/test/endpoint_tests/test_cron.py @@ -62,7 +62,7 @@ def test_reseed_db(cron_client, monkeypatch, mocker, default_users): # Success; perform actual sourcing of seed (takes about 2s) # Create QR code and verify that it is removed after reseeding - mutation = "mutation { createQrCode { id code } }" + mutation = "mutation { createQrCode { ...on QrCode { id code } } }" response = assert_successful_request(cron_client, mutation) code = response["code"] response = cron_client.get(reseed_db_path, headers=headers) diff --git a/back/test/endpoint_tests/test_permissions.py b/back/test/endpoint_tests/test_permissions.py index 9339ad209e..4fcd0bd670 100644 --- a/back/test/endpoint_tests/test_permissions.py +++ b/back/test/endpoint_tests/test_permissions.py @@ -148,8 +148,6 @@ def test_invalid_permission_for_given_resource_id(read_only_client, query): }) { id }""", - # Test case 8.2.33 - "createQrCode { id }", """createTransferAgreement( creationInput : { initiatingOrganisationId: 1, @@ -613,6 +611,13 @@ def test_invalid_permission_for_user_read( "...on InsufficientPermissionError { name }", {"name": "beneficiary:create"}, ], + # Test case 8.2.33 + [ + "createQrCode", + 'boxLabelIdentifier: "xyz"', + "...on InsufficientPermissionError { name }", + {"name": "qr:create"}, + ], ], ) def test_mutate_insufficient_permission( diff --git a/back/test/endpoint_tests/test_qr.py b/back/test/endpoint_tests/test_qr.py index 3890b8fc8e..654a6af5ee 100644 --- a/back/test/endpoint_tests/test_qr.py +++ b/back/test/endpoint_tests/test_qr.py @@ -1,7 +1,7 @@ from datetime import date from boxtribute_server.models.definitions.history import DbChangeHistory -from utils import assert_bad_user_input, assert_successful_request +from utils import assert_successful_request def test_qr_exists_query(read_only_client, default_qr_code): @@ -52,7 +52,7 @@ def test_code_not_associated_with_box(read_only_client, qr_code_without_box): def test_qr_code_mutation(client, box_without_qr_code): # Test case 8.2.30 - mutation = "mutation { createQrCode { id } }" + mutation = "mutation { createQrCode { ...on QrCode { id } } }" qr_code = assert_successful_request(client, mutation) qr_code_id = int(qr_code["id"]) assert qr_code_id > 2 @@ -60,13 +60,13 @@ def test_qr_code_mutation(client, box_without_qr_code): # Test case 8.2.31 mutation = f"""mutation {{ createQrCode(boxLabelIdentifier: "{box_without_qr_code['label_identifier']}") - {{ + {{ ...on QrCode {{ id box {{ ...on Box {{ id numberOfItems }} }} - }} + }} }} }}""" created_qr_code = assert_successful_request(client, mutation) assert int(created_qr_code["id"]) == qr_code_id + 1 @@ -76,11 +76,6 @@ def test_qr_code_mutation(client, box_without_qr_code): ) assert int(created_qr_code["box"]["id"]) == box_without_qr_code["id"] - # Test case 8.2.32 - assert_bad_user_input( - client, """mutation { createQrCode(boxLabelIdentifier: "xxx") { id } }""" - ) - history_entries = list( DbChangeHistory.select( DbChangeHistory.changes,