def test_invalid_permission_for_agreement_bases(read_only_client, mocker, field): # verify missing base:read permission mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( permissions=["transfer_agreement:read"]) query = f"query {{ transferAgreement(id: 1) {{ {field} {{ id }} }} }}" assert_forbidden_request(read_only_client, query, value={field: None})
def test_shipment_mutations_cancel(client, mocker, default_shipment, another_marked_for_shipment_box, another_shipment): # Test case 3.2.7 shipment_id = str(default_shipment["id"]) mutation = f"""mutation {{ cancelShipment(id: {shipment_id}) {{ id state canceledBy {{ id }} canceledOn details {{ id }} }} }}""" shipment = assert_successful_request(client, mutation) assert shipment.pop("canceledOn").startswith(date.today().isoformat()) assert shipment == { "id": shipment_id, "state": ShipmentState.Canceled.name, "canceledBy": { "id": "8" }, "details": [], } identifier = another_marked_for_shipment_box["label_identifier"] query = f"""query {{ box(labelIdentifier: "{identifier}") {{ state }} }}""" box = assert_successful_request(client, query) assert box == {"state": BoxState.InStock.name} # Shipment does not have any details assigned mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( base_ids=[3], organisation_id=2, user_id=2) shipment_id = str(another_shipment["id"]) mutation = f"""mutation {{ cancelShipment(id: {shipment_id}) {{ state }} }}""" shipment = assert_successful_request(client, mutation) assert shipment == {"state": ShipmentState.Canceled.name}
def test_shipment_mutations_cancel_as_member_of_neither_org( read_only_client, mocker, default_shipment): # Test case 3.2.10 mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( organisation_id=3, user_id=2) mutation = f"mutation {{ cancelShipment(id: {default_shipment['id']}) {{ id }} }}" assert_forbidden_request(read_only_client, mutation)
def test_invalid_permission_for_qr_code_box(read_only_client, mocker, default_qr_code): # verify missing stock:read permission mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( permissions=["qr:read"]) code = default_qr_code["code"] query = f"""query {{ qrCode(qrCode: "{code}") {{ box {{ id }} }} }}""" assert_forbidden_request(read_only_client, query, value={"box": None})
def test_invalid_permission_for_resource_base(read_only_client, mocker, default_product, resource): # verify missing base:read permission mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( permissions=[f"{resource}:read"]) query = f"""query {{ {resource}(id: 1) {{ base {{ id }} }} }}""" assert_forbidden_request(read_only_client, query, value={"base": None})
def test_invalid_permission_for_box_field(read_only_client, mocker, default_box, field): # verify missing field:read permission mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( permissions=["stock:read"]) query = f"""query {{ box(labelIdentifier: "{default_box["label_identifier"]}") {{ {field} {{ id }} }} }}""" assert_forbidden_request(read_only_client, query, value={field: None})
def test_invalid_permission_for_base_locations(read_only_client, mocker): # verify missing location:read permission mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( permissions=["base:read"]) query = "query { base(id: 1) { locations { id } } }" assert_forbidden_request(read_only_client, query, value={"locations": None})
def test_invalid_permission_for_beneficiary_tokens(read_only_client, mocker, default_beneficiary): # verify missing transaction:read permission mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( permissions=["beneficiary:read"]) id = default_beneficiary["id"] query = f"query {{ beneficiary(id: {id}) {{ tokens }} }}" assert_forbidden_request(read_only_client, query, value={"tokens": None})
def test_invalid_permission_for_given_resource_id(read_only_client, mocker, query): """Verify missing resource:read permission, or missing permission to access specified resource (base or organisation). """ mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( permissions=["base_1/base:read"], organisation_id=1) assert_forbidden_request(read_only_client, f"query {{ {query} }}")
def test_transfer_agreement_mutations_cancel_as_member_of_neither_org( read_only_client, mocker, default_transfer_agreement): mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( organisation_id=3, user_id=2) # Test case 2.2.20 agreement_id = default_transfer_agreement["id"] mutation = f"mutation {{ cancelTransferAgreement(id: {agreement_id}) {{ id }} }}" assert_forbidden_request(read_only_client, mutation)
def test_base_specific_permissions(client, mocker): """Verify that a user can only create beneficiary if base-specific permission available. QR codes can be created regardless of any base but for the front-end the base-specific distinction is relevant. """ mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( organisation_id=2, user_id=1, permissions=[ "base_2/qr:create", "stock:write", "base_3/beneficiary:create", ], ) create_beneficiary_for_base2_mutation = """createBeneficiary( creationInput : { firstName: "First", lastName: "Last", dateOfBirth: "1990-09-01", baseId: 2, groupIdentifier: "1312", gender: Male, languages: [de], isVolunteer: true, registered: false }) { id }""" create_beneficiary_for_base3_mutation = ( create_beneficiary_for_base2_mutation.replace("baseId: 2", "baseId: 3") ) data = { "query": f"""mutation {{ bene2: {create_beneficiary_for_base2_mutation} bene3: {create_beneficiary_for_base3_mutation} }}""" } response = client.post("/graphql", json=data) assert response.status_code == 200 assert response.json["data"]["bene2"] is None assert response.json["data"]["bene3"] is not None assert len(response.json["errors"]) == 1 assert response.json["errors"][0]["extensions"]["code"] == "FORBIDDEN" assert response.json["errors"][0]["path"] == ["bene2"] data = { "query": """mutation { qr2: createQrCode { code } qr3: createQrCode { code } }""" } response = client.post("/graphql", json=data) assert response.status_code == 200 assert response.json["data"]["qr2"] is not None assert response.json["data"]["qr3"] is not None assert "errors" not in response.json
def test_shipment_mutations_create_as_member_of_neither_org( read_only_client, mocker, default_transfer_agreement): # Test case 3.2.4b mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( organisation_id=3, user_id=2) mutation = _generate_create_shipment_mutation( source_base={"id": 0}, target_base={"id": 0}, agreement=default_transfer_agreement, ) assert_forbidden_request(read_only_client, mutation)
def test_transfer_agreement_mutations_invalid_state(read_only_client, mocker, expired_transfer_agreement, action): # The client has to be permitted to perform the action in general mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( organisation_id=expired_transfer_agreement["target_organisation"], user_id=2) # Test cases 2.2.11, 2.2.12, 2.2.13 agreement_id = expired_transfer_agreement["id"] mutation = f"mutation {{ {action}TransferAgreement(id: {agreement_id}) {{ id }} }}" assert_bad_user_input(read_only_client, mutation)
def test_invalid_permission_for_shipment_details_field(read_only_client, mocker, default_box, field): # verify missing field:read permission mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( permissions=["shipment:read"]) query = f"""query {{ shipment(id: 1) {{ details {{ {field} {{ id }} }} }} }}""" assert_forbidden_request(read_only_client, query, value={"details": [{ field: None }]})
def auth_service(module_mocker): """Patch any interaction with the Auth0 service for the scope of the `endpoint_tests` test module. Mimick the requesting user in an appropriate way. This helps to run the module's tests offline, i.e. without the requirement to establish an actual connection to the Auth0 web service. Also the tests are decoupled from any changes of user attributes in Auth0. """ module_mocker.patch("boxtribute_server.auth.get_auth_string_from_header" ).return_value = "Bearer Some.Token" module_mocker.patch( "boxtribute_server.auth.get_public_key").return_value = None module_mocker.patch("jose.jwt.decode").return_value = create_jwt_payload()
def test_metrics_query_for_god_user( read_only_client, mocker, organisation_id, number_of_families_served, number_of_sales, ): mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( permissions=["*"], organisation_id=None) query = f"""query {{ metrics(organisationId: {organisation_id}) {{ numberOfFamiliesServed numberOfSales }} }}""" response = assert_successful_request(read_only_client, query, field="metrics") assert response == { "numberOfFamiliesServed": number_of_families_served, "numberOfSales": number_of_sales, }
def test_shipment_mutations_update_checked_in_boxes_when_shipment_in_non_sent_state( read_only_client, mocker, default_shipment, prepared_shipment_detail, another_location, another_product, ): # Test case 3.2.36 mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( base_ids=[3], organisation_id=2, user_id=2) assert_bad_user_input_when_updating_shipment( read_only_client, shipment=default_shipment, received_details=[prepared_shipment_detail], target_location=another_location, target_product=another_product, )
def test_transfer_agreement_mutations( client, default_organisation, another_organisation, mocker, ): def _create_mutation(creation_input): return f"""mutation {{ createTransferAgreement( creationInput: {{ {creation_input} }} ) {{ id sourceOrganisation {{ id }} targetOrganisation {{ id }} state type requestedBy {{ id }} validFrom validUntil sourceBases {{ id }} targetBases {{ id }} shipments {{ id }} }} }}""" # Leave all optional fields empty in input # Test case 2.2.1 creation_input = f"""targetOrganisationId: {another_organisation['id']}, type: {TransferAgreementType.Bidirectional.name}""" agreement = assert_successful_request(client, _create_mutation(creation_input)) first_agreement_id = agreement.pop("id") assert agreement.pop("validFrom").startswith(date.today().isoformat()) assert agreement == { "sourceOrganisation": { "id": str(default_organisation["id"]) }, "targetOrganisation": { "id": str(another_organisation["id"]) }, "state": TransferAgreementState.UnderReview.name, "type": TransferAgreementType.Bidirectional.name, "requestedBy": { "id": "8" }, "validUntil": None, "sourceBases": [{ "id": "1" }, { "id": "2" }], "targetBases": [{ "id": "3" }, { "id": "4" }], "shipments": [], } # Provide all available fields in input # Test case 2.2.2 valid_from = "2021-12-15" valid_until = "2022-06-30" creation_input = f"""targetOrganisationId: {another_organisation['id']}, type: {TransferAgreementType.Bidirectional.name}, validFrom: "{valid_from}", validUntil: "{valid_until}", timezone: "Europe/London", sourceBaseIds: [1], targetBaseIds: [3]""" agreement = assert_successful_request(client, _create_mutation(creation_input)) second_agreement_id = agreement.pop("id") assert agreement.pop("validFrom").startswith(valid_from) assert agreement.pop("validUntil").startswith(valid_until) assert agreement == { "sourceOrganisation": { "id": str(default_organisation["id"]) }, "targetOrganisation": { "id": str(another_organisation["id"]) }, "state": TransferAgreementState.UnderReview.name, "type": TransferAgreementType.Bidirectional.name, "requestedBy": { "id": "8" }, "sourceBases": [{ "id": "1" }], "targetBases": [{ "id": "3" }], "shipments": [], } mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( base_ids=[3], organisation_id=2, user_id=2) # Test case 2.2.3 mutation = f"""mutation {{ acceptTransferAgreement(id: {first_agreement_id}) {{ state acceptedBy {{ id }} acceptedOn }} }}""" agreement = assert_successful_request(client, mutation) assert agreement.pop("acceptedOn").startswith(date.today().isoformat()) assert agreement == { "state": TransferAgreementState.Accepted.name, "acceptedBy": { "id": "2" }, } # Test case 2.2.7 mutation = f"""mutation {{ cancelTransferAgreement(id: {first_agreement_id}) {{ state terminatedBy {{ id }} terminatedOn }} }}""" agreement = assert_successful_request(client, mutation) assert agreement.pop("terminatedOn").startswith(date.today().isoformat()) assert agreement == { "state": TransferAgreementState.Canceled.name, "terminatedBy": { "id": "2" }, } # Test case 2.2.5 mutation = f"""mutation {{ rejectTransferAgreement(id: {second_agreement_id}) {{ state terminatedBy {{ id }} terminatedOn }} }}""" agreement = assert_successful_request(client, mutation) assert agreement.pop("terminatedOn").startswith(date.today().isoformat()) assert agreement == { "state": TransferAgreementState.Rejected.name, "terminatedBy": { "id": "2" }, }
def unauthorized(mocker): """Effectively remove any permissions from current client.""" mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( permissions=[])
def test_shipment_mutations_on_target_side( client, mocker, default_transfer_agreement, unidirectional_transfer_agreement, default_bases, sent_shipment, default_shipment_detail, another_shipment_detail, another_location, another_product, default_product, default_location, box_without_qr_code, marked_for_shipment_box, ): mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( base_ids=[3], organisation_id=2, user_id=2) # Test cases 3.2.1b, 3.2.1c for agreement in [ default_transfer_agreement, unidirectional_transfer_agreement ]: source_base_id = str(default_bases[3]["id"]) target_base_id = str(default_bases[2]["id"]) agreement_id = agreement["id"] creation_input = f"""sourceBaseId: {source_base_id}, targetBaseId: {target_base_id}, transferAgreementId: {agreement_id}""" mutation = f"""mutation {{ createShipment(creationInput: {{ {creation_input} }}) {{ sourceBase {{ id }} targetBase {{ id }} state }} }}""" shipment = assert_successful_request(client, mutation) assert shipment == { "sourceBase": { "id": source_base_id }, "targetBase": { "id": target_base_id }, "state": ShipmentState.Preparing.name, } target_product_id = str(another_product["id"]) target_location_id = str(another_location["id"]) shipment_id = str(sent_shipment["id"]) detail_id = str(default_shipment_detail["id"]) another_detail_id = str(another_shipment_detail["id"]) def _create_mutation(*, detail_id, target_product_id, target_location_id): update_input = f"""id: {shipment_id}, receivedShipmentDetailUpdateInputs: {{ id: {detail_id}, targetProductId: {target_product_id}, targetLocationId: {target_location_id} }}""" return f"""mutation {{ updateShipment(updateInput: {{ {update_input} }}) {{ id state completedBy {{ id }} completedOn details {{ id targetProduct {{ id }} targetLocation {{ id }} box {{ state }} }} }} }}""" # Test case 3.2.34a shipment = assert_successful_request( client, _create_mutation( detail_id=detail_id, target_product_id=target_product_id, target_location_id=target_location_id, ), ) expected_shipment = { "id": shipment_id, "state": ShipmentState.Sent.name, "completedBy": None, "completedOn": None, "details": [ { "id": detail_id, "box": { "state": BoxState.Received.name }, "targetProduct": { "id": target_product_id }, "targetLocation": { "id": target_location_id }, }, { "id": another_detail_id, "box": { "state": BoxState.MarkedForShipment.name }, "targetProduct": None, "targetLocation": None, }, ], } assert shipment == expected_shipment # Verify that another_detail_id is not updated (invalid product) # Test cases 3.2.39ab for product in [default_product, {"id": 0}]: shipment = assert_successful_request( client, _create_mutation( detail_id=another_detail_id, target_product_id=product["id"], target_location_id=target_location_id, ), ) assert shipment == expected_shipment # Verify that another_detail_id is not updated (invalid location) # Test cases 3.2.38ab for location in [default_location, {"id": 0}]: shipment = assert_successful_request( client, _create_mutation( detail_id=another_detail_id, target_product_id=target_product_id, target_location_id=location["id"], ), ) assert shipment == expected_shipment # Test case 3.2.40, 3.2.34b box_label_identifier = marked_for_shipment_box["label_identifier"] mutation = f"""mutation {{ updateShipment( updateInput: {{ id: {shipment_id}, lostBoxLabelIdentifiers: ["{box_label_identifier}"] }} ) {{ id state completedBy {{ id }} completedOn details {{ id }} }} }}""" shipment = assert_successful_request(client, mutation) assert shipment.pop("completedOn").startswith(date.today().isoformat()) assert shipment == { "id": shipment_id, "state": ShipmentState.Completed.name, "completedBy": { "id": "2" }, "details": [], } box_label_identifier = box_without_qr_code["label_identifier"] query = f"""query {{ box(labelIdentifier: "{box_label_identifier}") {{ state product {{ id }} location {{ id }} }} }}""" box = assert_successful_request(client, query) assert box == { "state": BoxState.InStock.name, "product": { "id": target_product_id }, "location": { "id": target_location_id }, } # The box is still registered in the source base, hence any user from the target # organisation can't access it mocker.patch("jose.jwt.decode").return_value = create_jwt_payload() box_label_identifier = marked_for_shipment_box["label_identifier"] query = f"""query {{ box(labelIdentifier: "{box_label_identifier}") {{ state }} }}""" box = assert_successful_request(client, query) assert box == {"state": BoxState.Lost.name}
def test_permission_scope(read_only_client, mocker, default_bases, method): mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( permissions=[f"base:{method}"]) query = "query { bases { id } }" bases = assert_successful_request(read_only_client, query) assert len(bases) == len(default_bases)
def test_permission_for_god_user(read_only_client, mocker, default_users): mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( permissions=["*"]) query = "query { users { id } }" users = assert_successful_request(read_only_client, query) assert len(users) == len(default_users)
def test_query_non_existent_resource_for_god_user(read_only_client, mocker, resource): # Non-god users would not be authorized to access resource ID 0 mocker.patch("jose.jwt.decode").return_value = create_jwt_payload(permissions=["*"]) query = f"query {{ {resource}(id: 0) {{ id }} }}" response = assert_bad_user_input(read_only_client, query, field=resource) assert "SQL" not in response.json["errors"][0]["message"]
def test_invalid_permission_for_location_boxes(read_only_client, mocker): # verify missing stock:read permission mocker.patch("jose.jwt.decode").return_value = create_jwt_payload( permissions=["location:read"]) query = "query { location(id: 1) { boxes { elements { id } } } }" assert_forbidden_request(read_only_client, query, value={"boxes": None})