Exemplo n.º 1
0
def test_patron_types_subscription(patron_type_youngsters_sion,
                                   patron_type_adults_martigny,
                                   patron_type_grown_sion, patron_sion):
    """Test subscription behavior for patron_types."""
    assert patron_type_youngsters_sion.is_subscription_required

    # A patron_type with a subscription amount equal to 0 doesn't
    # require a subscription
    patron_type_youngsters_sion['subscription_amount'] = 0
    assert not patron_type_youngsters_sion.is_subscription_required

    del (patron_type_youngsters_sion['subscription_amount'])
    assert not patron_type_youngsters_sion.is_subscription_required

    # Test the 'get_yearly_subscription_patron_types' function.
    assert len(list(PatronType.get_yearly_subscription_patron_types())) == 2

    # Test 'get_linked_patrons' functions.
    assert len(list(patron_type_grown_sion.get_linked_patron())) == 1
    assert len(list(patron_type_youngsters_sion.get_linked_patron())) == 0
    patron_sion['patron']['type']['$ref'] = get_ref_for_pid(
        'ptty', patron_type_youngsters_sion.pid)
    patron_sion.update(patron_sion, dbcommit=True)
    patron_sion.reindex()
    assert len(list(patron_type_grown_sion.get_linked_patron())) == 0
    assert len(list(patron_type_youngsters_sion.get_linked_patron())) == 1
    patron_sion['patron']['type']['$ref'] = get_ref_for_pid(
        'ptty', patron_type_grown_sion.pid)
    patron_sion.update(patron_sion, dbcommit=True)
    patron_sion.reindex()
Exemplo n.º 2
0
def test_get_ref_for_pid(app):
    """Test get $ref for pid."""
    url = 'https://ils.rero.ch/api/documents/3'
    assert get_ref_for_pid('documents', '3') == url
    assert get_ref_for_pid('doc', '3') == url
    assert get_ref_for_pid(Document, '3') == url
    assert get_ref_for_pid('test', '3') is None
Exemplo n.º 3
0
def create_ill_requests(input_file):
    """Create ILL request for each organisation."""
    locations = get_locations()
    patron_pids = {}

    with open(input_file, 'r', encoding='utf-8') as request_file:
        requests = json.load(request_file)
        for request_data in requests:
            for organisation_pid, location_pid in locations.items():
                if 'pid' in request_data:
                    del request_data['pid']
                if organisation_pid not in patron_pids:
                    patron_pids[organisation_pid] = list(
                        Patron.get_all_pids_for_organisation(organisation_pid))
                patron_pid = random.choice(patron_pids[organisation_pid])
                request_data['patron'] = {
                    '$ref': get_ref_for_pid('patrons', patron_pid)
                }
                request_data['pickup_location'] = {
                    '$ref': get_ref_for_pid('locations', location_pid)
                }
                request = ILLRequest.create(request_data,
                                            dbcommit=True,
                                            reindex=True)
                click.echo('\tRequest: #{pid}  \tfor org#{org_id}'.format(
                    pid=request.pid, org_id=request.organisation_pid))
Exemplo n.º 4
0
def test_items_after_holdings_update(holding_lib_martigny_w_patterns,
                                     item_type_on_site_martigny,
                                     loc_restricted_martigny):
    """Test location and item type after holdings of type serials changes."""
    martigny = holding_lib_martigny_w_patterns
    assert martigny.get('holdings_type') == HoldingTypes.SERIAL
    # ensure that all attached items of holdings record has the same location
    # and item type.
    item_pids = Item.get_items_pid_by_holding_pid(martigny.pid)
    for pid in item_pids:
        item = Item.get_record_by_pid(pid)
        assert item.location_pid == martigny.location_pid
        assert item.item_type_pid == martigny.circulation_category_pid

    assert martigny.location_pid != loc_restricted_martigny.pid
    assert martigny.circulation_category_pid != item_type_on_site_martigny.pid
    # change the holdings circulation_category and location.
    martigny['circulation_category'] = {
        '$ref': get_ref_for_pid('item_types', item_type_on_site_martigny.pid)
    }
    martigny['location'] = {
        '$ref': get_ref_for_pid('locations', loc_restricted_martigny.pid)
    }
    martigny = martigny.update(martigny, dbcommit=True, reindex=True)
    assert martigny.location_pid == loc_restricted_martigny.pid
    assert martigny.circulation_category_pid == item_type_on_site_martigny.pid
    # ensure that all attached items of holdings record has the same location
    # and item type after holdings changes.
    for pid in item_pids:
        item = Item.get_record_by_pid(pid)
        assert item.location_pid == martigny.location_pid
        assert item.item_type_pid == martigny.circulation_category_pid
Exemplo n.º 5
0
    def decorated_view(*args, **kwargs):
        try:
            data = flask_request.get_json()
            description = data.pop('description')
        except KeyError:
            # The description parameter is missing.
            abort(400, str('missing description parameter.'))

        try:
            holding_pid = data.pop('holding_pid', None)
            holding = Holding.get_record_by_pid(holding_pid)
            if not holding:
                abort(404, 'Holding not found')
            # create a provisional item
            item_metadata = {
                'type': 'provisional',
                'document': {
                    '$ref': get_ref_for_pid('doc', holding.document_pid)
                },
                'location': {
                    '$ref': get_ref_for_pid('loc', holding.location_pid)
                },
                'item_type': {
                    '$ref':
                    get_ref_for_pid('itty', holding.circulation_category_pid)
                },
                'enumerationAndChronology': description,
                'status': ItemStatus.ON_SHELF,
                'holding': {
                    '$ref': get_ref_for_pid('hold', holding.pid)
                }
            }
            item = Item.create(item_metadata, dbcommit=True, reindex=True)

            _, action_applied = func(holding, item, data, *args, **kwargs)
            return jsonify({'action_applied': action_applied})
        except NoCirculationActionIsPermitted:
            # The circulation specs do not allow updates on some loan states.
            return jsonify({'status': 'error: Forbidden'}), 403
        except MissingRequiredParameterError as error:
            # Return error 400 when there is a missing required parameter
            abort(400, str(error))
        except CirculationException as error:
            abort(403, error.description or str(error))
        except NotFound as error:
            raise error
        except exceptions.RequestError as error:
            # missing required parameters
            return jsonify({'status': f'error: {error}'}), 400
        except Exception as error:
            # TODO: need to know what type of exception and document there.
            # raise error
            current_app.logger.error(str(error))
            return jsonify({'status': f'error: {error}'}), 400
Exemplo n.º 6
0
def test_items_availability(item_type_missing_martigny,
                            item_type_standard_martigny,
                            item_lib_martigny_data_tmp, loc_public_martigny,
                            lib_martigny, org_martigny, document):
    """Test availability for an item."""

    # Create a temporary item with correct data for the test
    item_data = deepcopy(item_lib_martigny_data_tmp)
    del item_data['pid']
    item_data['barcode'] = 'TEST_AVAILABILITY'
    item_data['temporary_item_type'] = {
        '$ref': get_ref_for_pid(ItemType, item_type_missing_martigny.pid)
    }
    item = Item.create(item_data, dbcommit=True, reindex=True)

    # test the availability and availability_text
    assert not item.available
    assert len(item.availability_text) == \
        len(item_type_missing_martigny.get('displayed_status', [])) + 1

    del item['temporary_item_type']
    item = item.update(item, dbcommit=True, reindex=True)
    assert item.available
    assert len(item.availability_text) == 1  # only default value

    # delete the created item
    item.delete()
Exemplo n.º 7
0
def test_obsolete_temporary_item_types(item_lib_martigny,
                                       item_type_on_site_martigny):
    """Test obsolete temporary_item_types."""
    item = item_lib_martigny

    # First test - No items has temporary_item_type
    items = Item.get_items_with_obsolete_temporary_item_type()
    assert len(list(items)) == 0

    # Second test - add an infinite temporary_item_type to an item
    item['temporary_item_type'] = {
        '$ref': get_ref_for_pid('itty', item_type_on_site_martigny.pid)
    }
    item.update(item, dbcommit=True, reindex=True)
    items = Item.get_items_with_obsolete_temporary_item_type()
    assert len(list(items)) == 0

    # Third test - add an expiration date in the future for the temporary
    # item_type
    over_2_days = datetime.now() + timedelta(days=2)
    item['temporary_item_type']['end_date'] = over_2_days.strftime('%Y-%m-%d')
    item.update(data=item, dbcommit=True, reindex=True)
    items = Item.get_items_with_obsolete_temporary_item_type()
    assert len(list(items)) == 0

    # Fourth test - check obsolete with for a specified date in the future
    over_3_days = datetime.now() + timedelta(days=3)
    items = Item.get_items_with_obsolete_temporary_item_type(
        end_date=over_3_days)
    assert len(list(items)) == 1

    # reset the item to original values
    del item['temporary_item_type']
    item.update(data=item, dbcommit=True, reindex=True)
Exemplo n.º 8
0
def test_loans_build_refs(item_lib_martigny, patron_martigny, document):
    """Test functions buildings refs."""

    # Create "virtual" Loan (not registered)
    loan = Loan({
        'item_pid': item_pid_to_object(item_lib_martigny.pid),
        'document_pid': document.pid,
        'patron_pid': patron_martigny.pid
    })

    assert loan_build_item_ref(None, loan) == \
        get_ref_for_pid('items', item_lib_martigny.pid)
    assert loan_build_document_ref(None, loan) == \
        get_ref_for_pid('doc', document.pid)
    assert loan_build_patron_ref(None, loan) == \
        get_ref_for_pid('patrons', patron_martigny.pid)
Exemplo n.º 9
0
def test_limits(patron_type_schema, patron_type_tmp):
    """Test limits fr patron type JSON schema."""
    data = patron_type_tmp

    # checkout limits :: library limit > general limit
    data['limits'] = {
        'checkout_limits': {
            'global_limit': 20,
            'library_limit': 15
        }
    }
    validate(data, patron_type_schema)
    with pytest.raises(ValidationError):
        data['limits']['checkout_limits']['library_limit'] = 40
        validate(data, patron_type_schema)  # valid for JSON schema
        data.validate()  # invalid against extented_validation rules

    data['limits']['checkout_limits']['library_limit'] = 15
    with pytest.raises(ValidationError):
        lib_ref = get_ref_for_pid('lib', 'dummy')
        data['limits']['checkout_limits']['library_exceptions'] = [
            {'library': {'$ref': lib_ref}, 'value': 15}
        ]
        validate(data, patron_type_schema)  # valid for JSON schema
        data.validate()  # invalid against extented_validation rules

    with pytest.raises(ValidationError):
        data['limits']['checkout_limits']['library_exceptions'] = [
            {'library': {'$ref': lib_ref}, 'value': 5},
            {'library': {'$ref': lib_ref}, 'value': 7}
        ]
        validate(data, patron_type_schema)  # valid for JSON schema
        data.validate()  # invalid against extented_validation rules
Exemplo n.º 10
0
def create_collections(input_file, max_item=10):
    """Create collections."""
    organisation_items = {}
    with open(input_file, 'r', encoding='utf-8') as request_file:
        collections = json.load(request_file)
        for collection_data in collections:
            organisation_pid = extracted_data_from_ref(
                collection_data.get('organisation').get('$ref'))
            if organisation_pid not in organisation_items:
                organisation_items[organisation_pid] =\
                    get_items_by_organisation_pid(organisation_pid)
            items = random.choices(
                organisation_items[organisation_pid],
                k=random.randint(1, max_item)
            )
            collection_data['items'] = []
            for item_pid in items:
                ref = get_ref_for_pid('items', item_pid)
                collection_data['items'].append({'$ref': ref})
            request = Collection.create(
                collection_data,
                dbcommit=True,
                reindex=True
            )
            click.echo('\tCollection: #{pid}'.format(
                pid=request.pid,
            ))
Exemplo n.º 11
0
def test_item_type_circulation_category_pid(item_lib_martigny,
                                            item_type_on_site_martigny):
    """Test item_type circulation category pid."""
    assert item_lib_martigny.item_type_pid == \
        item_lib_martigny.item_type_circulation_category_pid

    past_2_days = datetime.now() - timedelta(days=2)
    over_2_days = datetime.now() + timedelta(days=2)

    # add an obsolete temporary item_type end_date :: In this case, the
    # circulation item_type must be the default item_type
    item_lib_martigny['temporary_item_type'] = {
        '$ref': get_ref_for_pid('itty', item_type_on_site_martigny.pid),
        'end_date': past_2_days.strftime('%Y-%m-%d')
    }
    assert item_lib_martigny.item_type_pid == \
        item_lib_martigny.item_type_circulation_category_pid

    # add a valid temporary item_type end_date :: In this case, the
    # circulation item_type must be the temporary item_type
    item_lib_martigny['temporary_item_type']['end_date'] = \
        over_2_days.strftime('%Y-%m-%d')
    assert item_type_on_site_martigny.pid == \
        item_lib_martigny.item_type_circulation_category_pid

    # removing any temporary item_type end_date :: In this case, the
    # circulation item_type must be the temporary item_type
    del item_lib_martigny['temporary_item_type']['end_date']
    assert item_type_on_site_martigny.pid == \
        item_lib_martigny.item_type_circulation_category_pid

    # reset the object with default value
    del item_lib_martigny['temporary_item_type']
Exemplo n.º 12
0
def test_replace_refs(item_lib_martigny, item_type_on_site_martigny):
    """Test specific replace_refs for items."""
    item_lib_martigny['temporary_item_type'] = {
        '$ref': get_ref_for_pid('itty', item_type_on_site_martigny.pid),
        'end_date': '2020-12-31'
    }
    assert 'end_date' in item_lib_martigny.replace_refs().\
        get('temporary_item_type')
Exemplo n.º 13
0
def test_clear_obsolete_temporary_item_type_and_location(
        item_lib_martigny, item_type_on_site_martigny,
        loc_restricted_martigny, item2_lib_martigny):
    """test task test_clear_obsolete_temporary_item_type_and_location"""
    item = item_lib_martigny
    end_date = datetime.now() + timedelta(days=2)
    item['temporary_item_type'] = {
        '$ref': get_ref_for_pid('itty', item_type_on_site_martigny.pid),
        'end_date': end_date.strftime('%Y-%m-%d')
    }
    item['temporary_location'] = {
        '$ref': get_ref_for_pid('loc', loc_restricted_martigny.pid),
        'end_date': end_date.strftime('%Y-%m-%d')
    }
    item = item.update(item, dbcommit=True, reindex=True)
    assert item.get('temporary_item_type', {}).get('end_date')
    assert item.get('temporary_location', {}).get('end_date')

    end_date = datetime.now() + timedelta(days=25)
    item2_lib_martigny['temporary_item_type'] = {
        '$ref': get_ref_for_pid('itty', item_type_on_site_martigny.pid),
        'end_date': end_date.strftime('%Y-%m-%d')
    }
    item2_lib_martigny['temporary_location'] = {
        '$ref': get_ref_for_pid('loc', loc_restricted_martigny.pid),
        'end_date': end_date.strftime('%Y-%m-%d')
    }
    item2_lib_martigny = item2_lib_martigny.update(
        item2_lib_martigny, dbcommit=True, reindex=True)
    assert item2_lib_martigny.get('temporary_item_type', {}).get('end_date')
    assert item2_lib_martigny.get('temporary_location', {}).get('end_date')

    over_4_days = datetime.now() + timedelta(days=4)
    with freeze_time(over_4_days.strftime('%Y-%m-%d')):
        items = Item.get_items_with_obsolete_temporary_item_type_or_location()
        assert len(list(items))
        # run the tasks
        msg = clean_obsolete_temporary_item_types_and_locations()
        assert msg['deleted fields'] == 2
        # check after task was ran
        items = Item.get_items_with_obsolete_temporary_item_type_or_location()
        assert len(list(items)) == 0
        item = Item.get_record_by_pid(item.pid)
        assert not item.get('temporary_item_type')
        assert not item.get('temporary_location')
Exemplo n.º 14
0
def test_patron_payment(client, librarian_martigny,
                        patron_transaction_overdue_event_martigny):
    """Test patron payment."""
    ptre = patron_transaction_overdue_event_martigny
    transaction = ptre.patron_transaction
    calculated_amount = sum(event.amount for event in transaction.events)
    transaction = PatronTransaction.get_record_by_pid(transaction.pid)
    assert calculated_amount == transaction.total_amount == 2.00

    login_user_via_session(client, librarian_martigny.user)
    post_entrypoint = 'invenio_records_rest.ptre_list'
    payment = deepcopy(ptre)

    # STEP#1 :: PARTIAL PAYMENT WITH TOO MUCH DECIMAL
    #   Try to pay a part of the transaction amount, but according to
    #   event amount restriction, only 2 decimals are allowed.
    del payment['pid']
    payment['type'] = 'payment'
    payment['subtype'] = 'cash'
    payment['amount'] = 0.545
    payment['operator'] = {
        '$ref': get_ref_for_pid('patrons', librarian_martigny.pid)
    }
    res, _ = postdata(client, post_entrypoint, payment)
    assert res.status_code == 400

    # STEP#2 :: PARTIAL PAYMENT WITH GOOD NUMBER OF DECIMALS
    #   Despite if a set a number with 3 decimals, if this number represent
    #   the value of a 2 decimals, it's allowed
    payment['amount'] = 0.540
    res, _ = postdata(client, post_entrypoint, payment)
    assert res.status_code == 201
    transaction = PatronTransaction.get_record_by_pid(transaction.pid)
    assert transaction.total_amount == 1.46
    assert transaction.status == 'open'

    # STEP#3 :: PAY TOO MUCH MONEY
    #   Try to proceed a payment with too much money, the system must
    #   reject the payment
    payment['amount'] = 2
    res, data = postdata(client, post_entrypoint, payment)
    assert res.status_code == 400

    # STEP#4 :: PAY THE REST
    #   Conclude the transaction by creation of a payment for the rest of the
    #   transaction
    payment['amount'] = transaction.total_amount
    res, _ = postdata(client, post_entrypoint, payment)
    assert res.status_code == 201
    transaction = PatronTransaction.get_record_by_pid(transaction.pid)
    assert transaction.total_amount == 0
    assert transaction.status == 'closed'
Exemplo n.º 15
0
def test_checkout_temporary_item_type(client, librarian_martigny, lib_martigny,
                                      loc_public_martigny, patron_martigny,
                                      item_lib_martigny,
                                      item_type_on_site_martigny,
                                      circ_policy_short_martigny,
                                      circ_policy_default_martigny):
    """Test checkout or item with temporary item_types"""
    login_user_via_session(client, librarian_martigny.user)
    item = item_lib_martigny
    assert item.status == ItemStatus.ON_SHELF

    # test basic behavior
    cipo_used = CircPolicy.provide_circ_policy(
        lib_martigny.organisation_pid, lib_martigny.pid,
        patron_martigny.patron_type_pid,
        item.item_type_circulation_category_pid)
    assert cipo_used == circ_policy_short_martigny

    # add a temporary_item_type on item
    #   due to this change, the cipo used during circulation operation should
    #   be different from the first cipo found.
    item['temporary_item_type'] = {
        '$ref': get_ref_for_pid('itty', item_type_on_site_martigny.pid)
    }
    item = item.update(data=item, dbcommit=True, reindex=True)
    cipo_tmp_used = CircPolicy.provide_circ_policy(
        lib_martigny.organisation_pid, lib_martigny.pid,
        patron_martigny.patron_type_pid,
        item.item_type_circulation_category_pid)
    assert cipo_tmp_used == circ_policy_default_martigny

    delta = timedelta(cipo_tmp_used.get('checkout_duration'))
    expected_date = datetime.now() + delta
    expected_dates = [expected_date, lib_martigny.next_open(expected_date)]
    expected_dates = [d.strftime('%Y-%m-%d') for d in expected_dates]

    # try a checkout and check the transaction end_date is related to the cipo
    # corresponding to the temporary item_type
    params = dict(item_pid=item.pid,
                  patron_pid=patron_martigny.pid,
                  transaction_user_pid=librarian_martigny.pid,
                  transaction_location_pid=loc_public_martigny.pid)
    res, data = postdata(client, 'api_item.checkout', params)
    assert res.status_code == 200
    transaction_end_date = data['action_applied']['checkout']['end_date']
    transaction_end_date = ciso8601.parse_datetime(transaction_end_date).date()
    transaction_end_date = transaction_end_date.strftime('%Y-%m-%d')
    assert transaction_end_date in expected_dates

    # reset the item to original value
    del item['temporary_item_type']
    item.update(data=item, dbcommit=True, reindex=True)
Exemplo n.º 16
0
def test_order_line_validation_extension(acq_order_line_fiction_martigny_data,
                                         acq_account_fiction_martigny,
                                         ebook_1):
    """Test order line validation extension."""
    data = deepcopy(acq_order_line_fiction_martigny_data)
    del data['pid']

    # An order line cannot be linked to an harvested document
    ebook_ref = get_ref_for_pid('doc', ebook_1.pid)
    test_data = deepcopy(data)
    test_data['document']['$ref'] = ebook_ref
    with pytest.raises(ValidationError) as error:
        AcqOrderLine.create(test_data, delete_pid=True)
    assert 'Cannot link to an harvested document' in str(error.value)
Exemplo n.º 17
0
def test_patron_pending_subscription(client, patron_type_grown_sion,
                                     patron_sion_no_email,
                                     librarian_sion_no_email,
                                     patron_transaction_overdue_event_martigny,
                                     lib_sion):
    """Test get pending subscription for patron."""
    # At the beginning, `patron_sion_no_email` should have one pending
    # subscription.
    pending_subscription = patron_sion_no_email.get_pending_subscriptions()
    assert len(pending_subscription) == 1

    # Pay this subscription.
    login_user_via_session(client, librarian_sion_no_email.user)
    post_entrypoint = 'invenio_records_rest.ptre_list'
    trans_pid = extracted_data_from_ref(
        pending_subscription[0]['patron_transaction'], data='pid')
    transaction = PatronTransaction.get_record_by_pid(trans_pid)
    payment = deepcopy(patron_transaction_overdue_event_martigny)
    del payment['pid']
    payment['type'] = 'payment'
    payment['subtype'] = 'cash'
    payment['amount'] = transaction.total_amount
    payment['operator'] = {
        '$ref': get_ref_for_pid('patrons', librarian_sion_no_email.pid)
    }
    payment['library'] = {'$ref': get_ref_for_pid('libraries', lib_sion.pid)}
    payment['parent'] = pending_subscription[0]['patron_transaction']
    res, _ = postdata(client, post_entrypoint, payment)
    assert res.status_code == 201
    transaction = PatronTransaction.get_record_by_pid(transaction.pid)
    assert transaction.status == 'closed'

    # reload the patron and check the pending subscription. As we paid the
    # previous subscription, there will be none pending subscription
    patron_sion_no_email = Patron.get_record_by_pid(patron_sion_no_email.pid)
    pending_subscription = patron_sion_no_email.get_pending_subscriptions()
    assert len(pending_subscription) == 0
Exemplo n.º 18
0
def test_document_referenced_subject(mock_contributions_mef_get,
                                     contribution_person_response_data,
                                     contribution_person):
    """Test referenced document subjects."""
    mock_contributions_mef_get.return_value = mock_response(
        json_data=contribution_person_response_data)

    # REFERENCED SUBJECTS - SUCCESS
    data = {
        '$ref': get_ref_for_pid(Contribution, contribution_person.pid),
        'type': DocumentSubjectType.PERSON
    }
    subject = SubjectFactory.create_subject(data)
    assert subject.render(language='ger') == 'Loy, Georg, 1885-19..'
    assert subject.render(language='dummy') == 'Loy, Georg, 1885-19..'
    assert subject.render() == 'Loy, Georg, 1885-19..'

    # REFERENCED SUBJECTS - ERRORS
    data = {
        '$dummy_ref': get_ref_for_pid(Contribution, contribution_person.pid),
        'type': DocumentSubjectType.PERSON
    }
    with pytest.raises(AttributeError):
        SubjectFactory.create_subject(data).render()
Exemplo n.º 19
0
def test_request_notifications_temp_item_type(
    client, patron_martigny, patron_sion, lib_martigny, lib_fully,
    item_lib_martigny, librarian_martigny, loc_public_martigny,
    circulation_policies, loc_public_fully, item_type_missing_martigny, mailbox
):
    """Test request notifications with item type with negative availability."""
    mailbox.clear()
    login_user_via_session(client, librarian_martigny.user)
    item_lib_martigny['temporary_item_type'] = {
        '$ref': get_ref_for_pid('itty', item_type_missing_martigny.pid)
    }
    item_lib_martigny.update(item_lib_martigny, dbcommit=True, reindex=True)

    res, data = postdata(
        client,
        'api_item.librarian_request',
        dict(
            item_pid=item_lib_martigny.pid,
            pickup_location_pid=loc_public_fully.pid,
            patron_pid=patron_martigny.pid,
            transaction_library_pid=lib_martigny.pid,
            transaction_user_pid=librarian_martigny.pid
        )
    )
    assert res.status_code == 200

    request_loan_pid = data.get(
        'action_applied')[LoanAction.REQUEST].get('pid')

    flush_index(NotificationsSearch.Meta.index)
    assert len(mailbox) == 0

    # cancel request
    res, _ = postdata(
        client,
        'api_item.cancel_item_request',
        dict(
            item_pid=item_lib_martigny.pid,
            pid=request_loan_pid,
            transaction_user_pid=librarian_martigny.pid,
            transaction_library_pid=lib_martigny.pid
        )
    )
    assert res.status_code == 200
    mailbox.clear()

    del(item_lib_martigny['temporary_item_type'])
    item_lib_martigny.update(item_lib_martigny, dbcommit=True, reindex=True)
Exemplo n.º 20
0
def test_template_replace_refs(templ_doc_public_martigny):
    """Test template replace_refs method."""
    tmpl = templ_doc_public_martigny
    tmpl.setdefault('data', {})['document'] = {
        '$ref': get_ref_for_pid('doc', 'dummy_pid')
    }
    tmpl = tmpl.update(tmpl, dbcommit=True, reindex=True)
    assert '$ref' in tmpl['data']['document']
    assert '$ref' in tmpl['creator']
    replace_data = tmpl.replace_refs()
    assert '$ref' in replace_data['data']['document']
    assert '$ref' not in replace_data['creator']

    # reset changes
    del tmpl['data']['document']
    tmpl.update(tmpl, dbcommit=True, reindex=True)
Exemplo n.º 21
0
def test_patron_payment(
        client, librarian_martigny_no_email,
        librarian_sion_no_email, patron_transaction_overdue_event_martigny):
    """Test patron payment."""
    transaction = \
        patron_transaction_overdue_event_martigny.patron_transaction()
    calculated_amount = sum([event.amount
                             for event in
                             transaction.events])
    transaction = PatronTransaction.get_record_by_pid(transaction.pid)
    assert calculated_amount == transaction.total_amount == 2.00

    login_user_via_session(client, librarian_martigny_no_email.user)
    post_entrypoint = 'invenio_records_rest.ptre_list'
    payment = deepcopy(patron_transaction_overdue_event_martigny)

    # partial payment
    del payment['pid']
    payment['type'] = 'payment'
    payment['subtype'] = 'cash'
    payment['amount'] = 1.00
    payment['operator'] = {'$ref': get_ref_for_pid(
        'patrons', librarian_martigny_no_email.pid)}
    res, _ = postdata(
        client,
        post_entrypoint,
        payment
    )
    assert res.status_code == 201
    transaction = PatronTransaction.get_record_by_pid(transaction.pid)
    assert transaction.total_amount == calculated_amount - 1.00
    assert transaction.status == 'open'

    # full payment
    payment['type'] = 'payment'
    payment['subtype'] = 'cash'
    payment['amount'] = transaction.total_amount
    res, _ = postdata(
        client,
        post_entrypoint,
        payment
    )
    assert res.status_code == 201

    transaction = PatronTransaction.get_record_by_pid(transaction.pid)
    assert transaction.total_amount == 0.00
    assert transaction.status == 'closed'
Exemplo n.º 22
0
def test_items_availability(item_type_missing_martigny,
                            item_type_standard_martigny,
                            item_lib_martigny_data_tmp, loc_public_martigny,
                            lib_martigny, org_martigny, document):
    """Test availability for an item."""

    # Create a temporary item with correct data for the test
    item_data = deepcopy(item_lib_martigny_data_tmp)
    del item_data['pid']
    item_data['barcode'] = 'TEST_AVAILABILITY'
    item_data['temporary_item_type'] = {
        '$ref': get_ref_for_pid(ItemType, item_type_missing_martigny.pid)
    }
    item = Item.create(item_data, dbcommit=True, reindex=True)

    # test the availability and availability_text
    assert not item.available
    assert len(item.availability_text) == \
        len(item_type_missing_martigny.get('displayed_status', [])) + 1

    del item['temporary_item_type']
    item = item.update(item, dbcommit=True, reindex=True)
    assert item.available
    assert len(item.availability_text) == 1  # only default value

    # test availability and availability_text for an issue
    item['type'] = TypeOfItem.ISSUE
    item['enumerationAndChronology'] = 'dummy'
    item['issue'] = {
        'regular': False,
        'status': ItemIssueStatus.RECEIVED,
        'received_date': '1970-01-01',
        'expected_date': '1970-01-01'
    }
    item = item.update(item, dbcommit=True, reindex=True)
    assert item.available
    assert item.availability_text[0]['label'] == item.status

    item['issue']['status'] = ItemIssueStatus.LATE
    item = item.update(item, dbcommit=True, reindex=True)
    assert not item.available
    assert item.availability_text[0]['label'] == ItemIssueStatus.LATE

    # delete the created item
    item.delete()
Exemplo n.º 23
0
def test_temporary_item_type(item_schema, item_lib_martigny):
    """Test temporary item type for item jsonschemas."""
    data = item_lib_martigny

    # tmp_itty cannot be the same than main_itty
    with pytest.raises(RecordValidationError):
        data['temporary_item_type'] = {'$ref': data['item_type']['$ref']}
        validate(data, item_schema)
        data.validate()  # check extented_validation

    # tmp_itty_enddate must be older than current date
    with pytest.raises(RecordValidationError):
        current_date = datetime.datetime.now().strftime('%Y-%m-%d')
        data['temporary_item_type'] = {
            '$ref': get_ref_for_pid('itty', 'sample'),
            'end_date': current_date
        }
        validate(data, item_schema)
        data.validate()  # check extented_validation
Exemplo n.º 24
0
    def get_data(self):
        """Return the form as a valid ILLRequest data structure."""
        data = remove_empties_from_dict({
            'document': {
                'title': self.document.title.data,
                'authors': self.document.authors.data,
                'publisher': self.document.publisher.data,
                'year': str(self.document.year.data or ''),
                'identifier': self.document.identifier.data,
                'source': {
                    'journal_title': self.document.source.journal_title.data,
                    'volume': self.document.source.volume.data,
                    'number': self.document.source.number.data,
                }
            },
            'pickup_location': {
                '$ref': get_ref_for_pid('locations', self.pickup_location.data)
            },
            'pages': self.pages.data,
            'found_in': {
                'source': self.source.origin.data,
                'url': self.source.url.data
            }
        })
        if self.note.data:
            data['notes'] = [{
                'type': 'public_note',
                'content': self.note.data
            }]

        # if we put 'copy' in the dict before the dict cleaning and if 'copy'
        # is set to 'No', then it will be removed by `remove_empties_from_dict`
        # So we need to add it after the cleaning
        data['copy'] = self.request_copy.data == 1

        # if user select 'not specified' into the ILL request form, this value
        # must be removed from the dict.
        if data.get('document', {}).get('year') == 'n/a':
            del data['document']['year']

        return data
Exemplo n.º 25
0
def test_clear_obsolete_temporary_item_type(item_lib_martigny,
                                            item_type_on_site_martigny):
    """test task clear_obsolete_temporary_item_type"""
    item = item_lib_martigny
    end_date = datetime.now() + timedelta(days=2)
    item['temporary_item_type'] = {
        '$ref': get_ref_for_pid('itty', item_type_on_site_martigny.pid),
        'end_date': end_date.strftime('%Y-%m-%d')
    }
    item.update(item, dbcommit=True, reindex=True)
    assert item.item_type_circulation_category_pid == \
        item_type_on_site_martigny.pid

    over_4_days = datetime.now() + timedelta(days=4)
    with freeze_time(over_4_days.strftime('%Y-%m-%d')):
        items = Item.get_items_with_obsolete_temporary_item_type()
        assert len(list(items)) == 1
        # run the tasks
        clean_obsolete_temporary_item_types()
        # check after task was ran
        items = Item.get_items_with_obsolete_temporary_item_type()
        assert len(list(items)) == 0
        assert item.item_type_circulation_category_pid == item.item_type_pid
Exemplo n.º 26
0
def test_item_can_request(client, document, holding_lib_martigny,
                          item_lib_martigny, librarian_martigny, lib_martigny,
                          patron_martigny, circulation_policies,
                          patron_type_children_martigny,
                          loc_public_martigny_data, system_librarian_martigny,
                          item_lib_martigny_data):
    """Test item can request API."""
    # test no logged user
    res = client.get(
        url_for('api_item.can_request',
                item_pid=item_lib_martigny.pid,
                library_pid=lib_martigny.pid,
                patron_barcode=patron_martigny.get('patron',
                                                   {}).get('barcode')[0]))
    assert res.status_code == 401

    can, _ = can_request(item_lib_martigny)
    assert not can

    login_user_via_session(client, librarian_martigny.user)
    # valid test
    res = client.get(
        url_for('api_item.can_request',
                item_pid=item_lib_martigny.pid,
                library_pid=lib_martigny.pid,
                patron_barcode=patron_martigny.get('patron',
                                                   {}).get('barcode')[0]))
    assert res.status_code == 200
    data = get_json(res)
    assert data.get('can')

    # test no valid item
    res = client.get(
        url_for('api_item.can_request',
                item_pid='no_item',
                library_pid=lib_martigny.pid,
                patron_barcode=patron_martigny.get('patron',
                                                   {}).get('barcode')[0]))
    assert res.status_code == 404

    # test no valid library
    res = client.get(
        url_for('api_item.can_request',
                item_pid=item_lib_martigny.pid,
                library_pid='no_library',
                patron_barcode=patron_martigny.get('patron',
                                                   {}).get('barcode')[0]))
    assert res.status_code == 404

    # test no valid patron
    res = client.get(
        url_for('api_item.can_request',
                item_pid=item_lib_martigny.pid,
                library_pid=lib_martigny.pid,
                patron_barcode='no_barcode'))
    assert res.status_code == 404

    # test no valid item status
    item_lib_martigny['status'] = ItemStatus.MISSING
    item_lib_martigny.update(item_lib_martigny, dbcommit=True, reindex=True)
    res = client.get(
        url_for('api_item.can_request',
                item_pid=item_lib_martigny.pid,
                library_pid=lib_martigny.pid,
                patron_barcode=patron_martigny.get('patron',
                                                   {}).get('barcode')[0]))
    assert res.status_code == 200
    data = get_json(res)
    assert not data.get('can')
    item_lib_martigny['status'] = ItemStatus.ON_SHELF
    item_lib_martigny.update(item_lib_martigny, dbcommit=True, reindex=True)

    # Location :: allow_request == false
    #   create a new location and set 'allow_request' to false. Assign a new
    #   item to this location. Chek if this item can be requested : it couldn't
    #   with 'Item location doesn't allow request' reason.
    new_location = deepcopy(loc_public_martigny_data)
    del new_location['pid']
    new_location['allow_request'] = False
    new_location = Location.create(new_location, dbcommit=True, reindex=True)
    assert new_location
    new_item = deepcopy(item_lib_martigny_data)
    del new_item['pid']
    new_item['barcode'] = 'dummy_barcode_allow_request'
    new_item['location']['$ref'] = get_ref_for_pid(Location, new_location.pid)
    new_item = Item.create(new_item, dbcommit=True, reindex=True)
    assert new_item

    res = client.get(url_for('api_item.can_request', item_pid=new_item.pid))
    assert res.status_code == 200
    data = get_json(res)
    assert not data.get('can')

    # remove created data
    item_url = url_for('invenio_records_rest.item_item',
                       pid_value=new_item.pid)
    hold_url = url_for('invenio_records_rest.hold_item',
                       pid_value=new_item.holding_pid)
    loc_url = url_for('invenio_records_rest.loc_item',
                      pid_value=new_location.pid)
    client.delete(item_url)
    client.delete(hold_url)
    client.delete(loc_url)
Exemplo n.º 27
0
def test_cancel_notifications(
    client, patron_martigny, lib_martigny, item_lib_martigny,
    librarian_martigny, loc_public_martigny, circulation_policies, mailbox
):
    """Test cancel notifications."""
    login_user_via_session(client, librarian_martigny.user)
    # CREATE and VALIDATE a request ...
    res, data = postdata(
        client,
        'api_item.librarian_request',
        dict(
            item_pid=item_lib_martigny.pid,
            pickup_location_pid=loc_public_martigny.pid,
            patron_pid=patron_martigny.pid,
            transaction_library_pid=lib_martigny.pid,
            transaction_user_pid=librarian_martigny.pid
        )
    )
    assert res.status_code == 200
    request_loan_pid = data.get(
        'action_applied')[LoanAction.REQUEST].get('pid')

    flush_index(NotificationsSearch.Meta.index)
    res, data = postdata(
        client,
        'api_item.validate_request',
        dict(
            pid=request_loan_pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid
        )
    )
    assert res.status_code == 200
    # At this time, an AVAILABILITY notification should be create but not yet
    # dispatched
    loan = Loan.get_record_by_pid(request_loan_pid)
    notification = get_notification(loan, NotificationType.AVAILABILITY)
    assert notification \
           and notification['status'] == NotificationStatus.CREATED

    # BORROW the requested item
    res, data = postdata(
        client,
        'api_item.checkout',
        dict(
            item_pid=item_lib_martigny.pid,
            patron_pid=patron_martigny.pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        )
    )
    assert res.status_code == 200
    loan_pid = data.get(
        'action_applied')[LoanAction.CHECKOUT].get('pid')
    loan = Loan.get_record_by_pid(loan_pid)

    # Try to dispatch pending availability notifications.
    # As the item is now checkout, then the availability notification is not
    # yet relevant.
    mailbox.clear()
    process_notifications(NotificationType.AVAILABILITY)

    notification = get_notification(loan, NotificationType.AVAILABILITY)
    assert notification and \
           notification['status'] == NotificationStatus.CANCELLED
    assert len(mailbox) == 0

    # restore to initial state
    res, data = postdata(
        client,
        'api_item.checkin',
        dict(
            item_pid=item_lib_martigny.pid,
            # patron_pid=patron_martigny.pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        )
    )
    assert res.status_code == 200
    mailbox.clear()

    # Test REMINDERS notifications.
    #   reminders notification check about the end_date. As the loan end_date
    #   is not yet over, the notification could be cancelled.

    notification = loan.create_notification(_type=NotificationType.DUE_SOON)[0]
    can_cancel, _ = notification.can_be_cancelled()
    assert not can_cancel
    process_notifications(NotificationType.DUE_SOON)
    notification = Notification.get_record_by_pid(notification.pid)
    assert notification['status'] == NotificationStatus.DONE
    flush_index(NotificationsSearch.Meta.index)

    # try to create a new DUE_SOON notification for the same loan
    record = {
        'creation_date': datetime.now(timezone.utc).isoformat(),
        'notification_type': NotificationType.DUE_SOON,
        'context': {
            'loan': {'$ref': get_ref_for_pid('loans', loan.pid)},
            'reminder_counter': 0
        }
    }
    notification = Notification.create(record)
    can_cancel, _ = notification.can_be_cancelled()
    assert can_cancel
    Dispatcher.dispatch_notifications([notification.pid])
    notification = Notification.get_record_by_pid(notification.pid)
    assert notification['status'] == NotificationStatus.CANCELLED
Exemplo n.º 28
0
def test_notifications_post_put_delete(
        client, dummy_notification, loan_validated_martigny, json_header):
    """Test record delete and update."""

    record = deepcopy(dummy_notification)
    del record['pid']
    loan_ref = get_ref_for_pid('loans', loan_validated_martigny.get('pid'))
    record['context']['loan'] = {'$ref': loan_ref}
    notif = Notification.create(
        record,
        dbcommit=True,
        reindex=True,
        delete_pid=True
    )
    assert notif == record
    flush_index(NotificationsSearch.Meta.index)
    pid = notif.get('pid')

    item_url = url_for('invenio_records_rest.notif_item', pid_value=pid)
    list_url = url_for('invenio_records_rest.notif_list', q='pid:pid')

    new_record = deepcopy(record)

    # Create record / POST
    new_record['pid'] = 'x'
    res, data = postdata(
        client,
        'invenio_records_rest.notif_list',
        new_record
    )
    assert res.status_code == 201

    flush_index(NotificationsSearch.Meta.index)

    # Check that the returned record matches the given data
    assert data['metadata'] == new_record

    res = client.get(item_url)
    assert res.status_code == 200
    data = get_json(res)
    assert notif == data['metadata']

    # Update record/PUT
    data = data['metadata']
    data['notification_type'] = NotificationType.DUE_SOON
    res = client.put(
        item_url,
        data=json.dumps(data),
        headers=json_header
    )
    assert res.status_code == 200

    # Check that the returned record matches the given data
    data = get_json(res)
    assert data['metadata']['notification_type'] == NotificationType.DUE_SOON

    res = client.get(item_url)
    assert res.status_code == 200

    data = get_json(res)
    assert data['metadata']['notification_type'] == NotificationType.DUE_SOON

    res = client.get(list_url)
    assert res.status_code == 200

    # Delete record/DELETE
    res = client.delete(item_url)
    assert res.status_code == 204

    res = client.get(item_url)
    assert res.status_code == 410

    can, reasons = notif.can_delete
    assert can
    assert reasons == {}

    notif.delete(dbcommit=True, delindex=True)
Exemplo n.º 29
0
def update_items_locations_and_types(sender, record=None, **kwargs):
    """This method checks if the items of the parent record needs an update.

    This method checks the location and item_type of each item attached to the
    holding record and update the item record accordingly.
    This method should be connect with 'after_record_update'.
    :param record: the holding record.
    """
    if not isinstance(record, Holding) and \
            record.get('holdings_type') == HoldingTypes.SERIAL:
        # identify all items records attached to this serials holdings record
        # with different location and item_type.
        hold_circ_pid = record.circulation_category_pid
        hold_loc_pid = record.location_pid
        search = ItemsSearch().filter('term', holding__pid=record.pid)
        item_hits = search.\
            filter('bool', should=[
                        Q('bool', must_not=[
                            Q('match', item_type__pid=hold_circ_pid)]),
                        Q('bool', must_not=[
                            Q('match', location__pid=hold_loc_pid)])])\
            .source(['pid'])
        items = [hit.meta.id for hit in item_hits.scan()]
        items_to_index = []
        # update these items and make sure they have the same location/category
        # as the parent holdings record.
        for id in items:
            try:
                item = Item.get_record_by_id(id)
                if not item:
                    continue
                items_to_index.append(id)
                item_temp_loc_pid, item_temp_type_pid = None, None
                # remove the item temporary_location if it is equal to the
                # new item location.
                temporary_location = item.get('temporary_location', {})
                if temporary_location:
                    item_temp_loc_pid = extracted_data_from_ref(
                        temporary_location.get('$ref'))
                if hold_loc_pid != item.location_pid:
                    if item_temp_loc_pid == hold_loc_pid:
                        item.pop('temporary_location', None)
                    item['location'] = {
                        '$ref': get_ref_for_pid('locations', hold_loc_pid)
                    }
                # remove the item temporary_item_type if it is equal to the
                # new item item_type.
                temporary_type = item.get('temporary_item_type', {})
                if temporary_type:
                    item_temp_type_pid = extracted_data_from_ref(
                        temporary_type.get('$ref'))
                if hold_circ_pid != item.item_type_pid:
                    if item_temp_type_pid == hold_circ_pid:
                        item.pop('temporary_item_type', None)
                    item['item_type'] = {
                        '$ref': get_ref_for_pid('item_types', hold_circ_pid)
                    }
                # update directly in database.
                db.session.query(item.model_cls).filter_by(id=item.id).update(
                    {item.model_cls.json: item})
            except Exception as err:
                pass
        if items_to_index:
            # commit session
            db.session.commit()
            # bulk indexing of item records.
            indexer = ItemsIndexer()
            indexer.bulk_index(items_to_index)
            process_bulk_queue.apply_async()
Exemplo n.º 30
0
def test_acquisition_order(client, rero_json_header, org_martigny,
                           lib_martigny, budget_2020_martigny, vendor_martigny,
                           librarian_martigny, document):
    """Scenario to test orders creation."""

    login_user_via_session(client, librarian_martigny.user)

    # STEP 0 :: Create the account tree
    basic_data = {
        'allocated_amount': 1000,
        'budget': {
            '$ref': get_ref_for_pid('budg', budget_2020_martigny.pid)
        },
        'library': {
            '$ref': get_ref_for_pid('lib', lib_martigny.pid)
        }
    }
    account_a = dict(name='A', allocated_amount=2000)
    account_a = {**basic_data, **account_a}
    account_a = _make_resource(client, 'acac', account_a)
    account_a_ref = {'$ref': get_ref_for_pid('acac', account_a.pid)}

    account_b = dict(name='B', allocated_amount=500, parent=account_a_ref)
    account_b = {**basic_data, **account_b}
    account_b = _make_resource(client, 'acac', account_b)
    account_b_ref = {'$ref': get_ref_for_pid('acac', account_b.pid)}

    # TEST 1 :: Create an order and add some order lines on it.
    #   * The creation of the order will be successful
    #   * We create first order line linked to account B. After this creation,
    #     we can check the encumbrance of this account and its parent account.
    order_data = {
        'vendor': {
            '$ref': get_ref_for_pid('vndr', vendor_martigny.pid)
        },
        'library': {
            '$ref': get_ref_for_pid('lib', lib_martigny.pid)
        },
        'type': 'monograph',
    }
    order = _make_resource(client, 'acor', order_data)
    assert order['reference'] == f'ORDER-{order.pid}'
    assert order.get_order_provisional_total_amount() == 0
    assert order.status == AcqOrderStatus.PENDING
    assert order.can_delete

    basic_data = {
        'acq_account': account_b_ref,
        'acq_order': {
            '$ref': get_ref_for_pid('acor', order.pid)
        },
        'document': {
            '$ref': get_ref_for_pid('doc', document.pid)
        },
        'quantity': 4,
        'amount': 25
    }
    order_line_1 = _make_resource(client, 'acol', basic_data)
    assert order_line_1.get('total_amount') == 100

    assert account_b.encumbrance_amount[0] == 100
    assert account_b.remaining_balance[0] == 400  # 500 - 100
    assert account_a.encumbrance_amount == (0, 100)
    assert account_a.remaining_balance[0] == 1500
    assert account_a.expenditure_amount == (0, 0)

    # TEST 2 :: update the number of received item from the order line.
    #   * The encumbrance amount account should be decrease by quantity
    #     received * amount.
    # field received_quantity is now dynamically calculated at the receive of
    # receipt_lines
    assert order_line_1.received_quantity == 0

    # TEST 3 :: add a new cancelled order line.
    #   * As this new order line has CANCELLED status, its amount is not
    #     calculated into the encumbrance_amount
    basic_data = {
        'acq_account': account_b_ref,
        'acq_order': {
            '$ref': get_ref_for_pid('acor', order.pid)
        },
        'document': {
            '$ref': get_ref_for_pid('doc', document.pid)
        },
        'quantity': 2,
        'amount': 10,
        'is_cancelled': True
    }
    order_line_1_1 = _make_resource(client, 'acol', basic_data)
    assert order_line_1_1.get('total_amount') == 20

    assert account_b.encumbrance_amount[0] == 100
    assert account_b.remaining_balance[0] == 400  # 500 - 100
    assert account_a.encumbrance_amount == (0, 100)
    assert account_a.remaining_balance[0] == 1500
    assert account_a.expenditure_amount == (0, 0)

    # TEST 4 :: new order line raises the limit of account available money.
    #   * Create a new order line on the same account ; but the total amount
    #     of the line must be larger than account available money --> should
    #     be raise an ValidationError
    #   * Update the first order line to raise the limit and check than the
    #     same validation error occurs.
    #   * Update the first order line to reach the limit without exceeding it
    order_line_2 = dict(quantity=50)
    order_line_2 = {**basic_data, **order_line_2}
    with pytest.raises(Exception) as excinfo:
        _make_resource(client, 'acol', order_line_2)
    assert 'Parent account available amount too low' in str(excinfo.value)

    order_line_1['quantity'] = 50
    with pytest.raises(Exception) as excinfo:
        order_line_1.update(order_line_1, dbcommit=True, reindex=True)
    assert 'Parent account available amount too low' in str(excinfo.value)

    order_line_1['quantity'] = 20
    order_line_1 = order_line_1.update(order_line_1,
                                       dbcommit=True,
                                       reindex=True)
    assert account_b.encumbrance_amount[0] == 500
    assert account_b.remaining_balance[0] == 0
    assert account_a.encumbrance_amount == (0, 500)
    assert account_a.remaining_balance[0] == 1500

    # TEST 5 :: Update the account encumbrance exceedance and test it.
    #   * At this time, the account B doesn't have any available money to
    #     place any nex order line. Try to add an other item to existing order
    #     line will raise a ValidationError
    #   * Update the account 'encumbrance_exceedance' setting to allow more
    #     encumbrance and try to add an item to order_line. It will be OK
    order_line_1['quantity'] += 1
    with pytest.raises(Exception) as excinfo:
        order_line_1.update(order_line_1, dbcommit=True, reindex=True)
    assert 'Parent account available amount too low' in str(excinfo.value)

    account_b['encumbrance_exceedance'] = 5  # 5% of 500 = 25
    account_b = account_b.update(account_b, dbcommit=True, reindex=True)
    order_line_1 = order_line_1.update(order_line_1,
                                       dbcommit=True,
                                       reindex=True)
    assert account_b.encumbrance_amount[0] == 525
    assert account_b.remaining_balance[0] == -25
    assert account_a.encumbrance_amount == (0, 525)
    assert account_a.remaining_balance[0] == 1500

    # Test cascade deleting of order lines when attempting to delete a
    # PENDING order.
    order_line_1 = AcqOrderLine.get_record_by_pid(order_line_1.pid)
    order_line_1['is_cancelled'] = True
    order_line_1.update(order_line_1, dbcommit=True, reindex=True)

    order = AcqOrder.get_record_by_pid(order.pid)
    assert order.status == AcqOrderStatus.CANCELLED

    # Delete CANCELLED order is not permitted
    with pytest.raises(IlsRecordError.NotDeleted):
        _del_resource(client, 'acor', order.pid)

    order_line_1['is_cancelled'] = False
    order_line_1.update(order_line_1, dbcommit=True, reindex=True)

    order = AcqOrder.get_record_by_pid(order.pid)
    assert order.status == AcqOrderStatus.PENDING

    # DELETE created resources
    _del_resource(client, 'acor', order.pid)
    # Deleting the parent PENDING order does delete all of its order lines
    order_line_1 = AcqOrderLine.get_record_by_pid(order_line_1.pid)
    order_line_1_1 = AcqOrderLine.get_record_by_pid(order_line_1_1.pid)
    assert not order_line_1
    assert not order_line_1_1

    _del_resource(client, 'acac', account_b.pid)
    _del_resource(client, 'acac', account_a.pid)