Example #1
0
def test_clear_and_renew_subscription(patron_type_grown_sion,
                                      patron_sion_no_email):
    """Test the `task patrons.tasks.clear_and_renew_subscription`."""

    patron_sion = patron_sion_no_email

    # To test correctly all the code we need to disconnect the listener
    # `create_subscription_patron_transaction` method. Otherwise, the
    # first part of the task (clean_obsolete_subscriptions) will automatically
    # create new subscriptions because the last instruction is a
    # `patron.update()` call.
    after_record_update.disconnect(create_subscription_patron_transaction)

    # first step : clear all subscription for the patron and crate an new
    # obsolete subscription.
    if 'subscriptions' in patron_sion_no_email.get('patron', {}):
        del patron_sion_no_email['patron']['subscriptions']
    start = datetime.now() - timedelta(days=200)
    end = start + timedelta(days=100)
    patron_sion.add_subscription(patron_type_grown_sion, start, end)
    assert len(patron_sion.get('patron', {}).get('subscriptions', [])) == 1
    assert patron_sion.get('patron', {})['subscriptions'][0]['end_date'] == \
        end.strftime('%Y-%m-%d')

    # clean old subscription - Reload the patron and check they are no more
    # subscriptions
    clean_obsolete_subscriptions()
    patron_sion = Patron.get_record_by_pid(patron_sion.pid)
    assert len(patron_sion.get('patron', {}).get('subscriptions', [])) == 0

    # check for patron needed subscriptions and create new subscription if
    # needed. As our patron has no subscription and is still connected to
    # a patron type requiring subscription, this task should create a
    # new subscription for this patron
    check_patron_types_and_add_subscriptions()
    patron_sion = Patron.get_record_by_pid(patron_sion.pid)
    assert len(patron_sion.get('patron', {}).get('subscriptions', [])) == 1
    assert patron_sion.get('patron', {})['subscriptions'][0]['end_date'] == \
        add_years(datetime.now(), 1).strftime('%Y-%m-%d')

    # run both operation using task_clear_and_renew_subscriptions` and check
    # the result. The patron should still have one subscription but end_date
    # must be today.
    del patron_sion['patron']['subscriptions']
    start = datetime.now() - timedelta(days=200)
    end = start + timedelta(days=100)
    patron_sion.add_subscription(patron_type_grown_sion, start, end)
    task_clear_and_renew_subscriptions()
    patron_sion = Patron.get_record_by_pid(patron_sion.pid)
    assert len(patron_sion.get('patron', {}).get('subscriptions', [])) == 1
    assert patron_sion.get('patron', {})['subscriptions'][0]['end_date'] != \
        end.strftime('%Y-%m-%d')

    # as we disconnect the `create_subscription_patron_transaction` listener
    # at the beginning, we need to connect it now.
    after_record_update.connect(create_subscription_patron_transaction)
Example #2
0
def test_patron_create(app, roles, librarian_martigny_data_tmp,
                       mailbox):
    """Test Patron creation."""
    ds = app.extensions['invenio-accounts'].datastore
    email = librarian_martigny_data_tmp.get('email')
    assert not ds.find_user(email=email)
    assert len(mailbox) == 0
    ptrn = Patron.create(
        librarian_martigny_data_tmp,
        dbcommit=True,
        delete_pid=True
    )
    user = ds.find_user(email=email)
    assert user
    user_roles = [r.name for r in user.roles]
    assert set(user_roles) == set(ptrn.get('roles'))
    assert len(mailbox) == 1
    assert ptrn.get('email') in mailbox[0].recipients
    assert ptrn == librarian_martigny_data_tmp
    assert ptrn.get('pid') == '1'

    ptrn = Patron.get_record_by_pid('1')
    assert ptrn == librarian_martigny_data_tmp

    fetched_pid = patron_id_fetcher(ptrn.id, ptrn)
    assert fetched_pid.pid_value == '1'
    assert fetched_pid.pid_type == 'ptrn'

    # set librarian
    roles = ['librarian']
    ptrn.update({'roles': roles}, dbcommit=True)
    user_roles = [r.name for r in user.roles]
    assert set(user_roles) == set(roles)
    roles = Patron.available_roles
    ptrn.update({'roles': Patron.available_roles}, dbcommit=True)
    user_roles = [r.name for r in user.roles]
    assert set(user_roles) == set(Patron.available_roles)

    # remove patron
    ptrn.delete()
    # user still exist in the invenio db
    user = ds.find_user(email=email)
    assert user
    # all roles has been removed
    assert not user.roles
    assert len(mailbox) == 1
    # patron does not exists anymore
    ptrn = Patron.get_record_by_pid('1')
    assert ptrn is None
    ptrn = Patron.get_record_by_pid('1', with_deleted=True)
    assert ptrn == {}
    assert ptrn.persistent_identifier.pid_value == '1'
Example #3
0
def test_patron_multiple(patron_sion_multiple, patron2_martigny, lib_martigny):
    """Test changing roles for multiple patron accounts."""
    assert patron2_martigny.user == patron_sion_multiple.user
    data = dict(patron_sion_multiple)
    assert set(patron2_martigny.user.roles) == set(['librarian', 'patron'])
    data['roles'] = ['patron']
    del data['libraries']
    patron_sion_multiple.update(data, dbcommit=True, reindex=True)
    assert patron2_martigny.user.roles == ['patron']
    assert Patron.get_record_by_pid(patron_sion_multiple.pid).get('roles') == \
        ['patron']
    assert Patron.get_record_by_pid(patron2_martigny.pid).get('roles') == \
        ['patron']
Example #4
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] = [
                        pid for pid in Patron.get_all_pids_for_organisation(
                            organisation_pid)
                        if Patron.get_record_by_pid(pid).is_patron
                    ]
                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))
def test_extend_loan(client, librarian_martigny_no_email, lib_martigny,
                     loc_public_martigny, circulation_policies,
                     item_on_loan_martigny_patron_and_loan_on_loan):
    """Test frontend extend action."""
    login_user_via_session(client, librarian_martigny_no_email.user)
    item, patron, loan = item_on_loan_martigny_patron_and_loan_on_loan
    assert item.status == ItemStatus.ON_LOAN

    # Test extend for a blocked patron
    patron['patron']['blocked'] = True
    patron['patron']['blocked_note'] = 'Dummy reason'
    patron.update(patron, dbcommit=True, reindex=True)
    patron = Patron.get_record_by_pid(patron.pid)

    res, _ = postdata(
        client, 'api_item.extend_loan',
        dict(item_pid=item.pid,
             transaction_user_pid=librarian_martigny_no_email.pid,
             transaction_location_pid=loc_public_martigny.pid))
    assert res.status_code == 403

    del patron['patron']['blocked']
    del patron['patron']['blocked_note']
    patron.update(patron, dbcommit=True, reindex=True)

    # With only needed parameters
    res, _ = postdata(
        client, 'api_item.extend_loan',
        dict(item_pid=item.pid,
             transaction_user_pid=librarian_martigny_no_email.pid,
             transaction_location_pid=loc_public_martigny.pid))
    assert res.status_code == 200
Example #6
0
def item_record_to_a_specific_loan_state(item=None,
                                         loan_state=None,
                                         params=None,
                                         copy_item=True):
    """Put an item into a specific circulation loan state.

    :param item: the item record
    :param loan_state: the desired loan state and attached to the given item
    :param params: the required parameters to perform the circ transactions
    :param copy_item: an option to perform transaction on a copy of the item

    :return: the item and its loan
    """
    if copy_item:
        item = create_new_item_from_existing_item(item=item)

    # complete missing parameters
    params.setdefault('transaction_date',
                      datetime.now(timezone.utc).isoformat())
    params.setdefault('document_pid', item.document_pid)

    # a parameter to allow in_transit returns
    checkin_transaction_location_pid = \
        params.pop('checkin_transaction_location_pid', None)
    patron = Patron.get_record_by_pid(params.get('patron_pid'))
    # perform circulation actions
    if loan_state in [
            LoanState.PENDING, LoanState.ITEM_AT_DESK, LoanState.ITEM_ON_LOAN,
            LoanState.ITEM_IN_TRANSIT_FOR_PICKUP,
            LoanState.ITEM_IN_TRANSIT_TO_HOUSE
    ]:
        item, actions = item.request(**params)
        loan = Loan.get_record_by_pid(actions[LoanAction.REQUEST].get('pid'))
        assert item.number_of_requests() >= 1
        assert item.is_requested_by_patron(
            patron.get('patron', {}).get('barcode'))
    if loan_state in [
            LoanState.ITEM_AT_DESK, LoanState.ITEM_IN_TRANSIT_FOR_PICKUP,
            LoanState.ITEM_IN_TRANSIT_TO_HOUSE
    ]:
        item, actions = item.validate_request(**params, pid=loan.pid)
        loan = Loan.get_record_by_pid(actions[LoanAction.VALIDATE].get('pid'))
    if loan_state in [
            LoanState.ITEM_ON_LOAN, LoanState.ITEM_IN_TRANSIT_TO_HOUSE
    ]:
        item, actions = item.checkout(**params, pid=loan.pid)
        loan = Loan.get_record_by_pid(actions[LoanAction.CHECKOUT].get('pid'))
    if loan_state == LoanState.ITEM_IN_TRANSIT_TO_HOUSE:
        if checkin_transaction_location_pid:
            params['transaction_location_pid'] = \
                checkin_transaction_location_pid
        item, actions = item.checkin(**params, pid=loan.pid)
        loan = Loan.get_record_by_pid(actions[LoanAction.CHECKIN].get('pid'))

    flush_index(ItemsSearch.Meta.index)
    flush_index(LoansSearch.Meta.index)

    assert loan['state'] == loan_state
    return item, loan
Example #7
0
    def get_notification_context(cls, notifications=None):
        """Get the context to render the notification template."""
        context = {}
        notifications = notifications or []
        if not notifications:
            return context

        context['loans'] = []
        doc_dumper = DocumentGenericDumper()
        item_dumper = ItemNotificationDumper()
        patron_dumper = PatronNotificationDumper()
        for notification in notifications:
            loan = notification.loan
            creation_date = format_date_filter(
                notification.get('creation_date'),
                date_format='medium',
                locale=language_iso639_2to1(
                    notification.get_language_to_use()))
            request_expire_date = format_date_filter(
                loan.get('request_expire_date'),
                date_format='medium',
                locale=language_iso639_2to1(
                    notification.get_language_to_use()))
            # merge doc and item metadata preserving document key
            item_data = notification.item.dumps(dumper=item_dumper)
            doc_data = notification.document.dumps(dumper=doc_dumper)
            doc_data = {**item_data, **doc_data}
            # pickup location name --> !! pickup is on notif.request_loan, not
            # on notif.loan
            request_loan = notification.request_loan
            pickup_location = Location.get_record_by_pid(
                request_loan.get('pickup_location_pid'))
            if not pickup_location:
                pickup_location = Location.get_record_by_pid(
                    request_loan.get('transaction_location_pid'))
            # request_patron
            request_patron = Patron.get_record_by_pid(
                request_loan.get('patron_pid'))

            loan_context = {
                'creation_date':
                creation_date,
                'document':
                doc_data,
                'pickup_name':
                pickup_location.get('pickup_name',
                                    pickup_location.get('name')),
                'request_expire_date':
                request_expire_date,
                'patron':
                request_patron.dumps(dumper=patron_dumper)
            }
            context['loans'].append(loan_context)

        return context
Example #8
0
def test_patrons_dirty_barcode(client, patron_martigny, librarian_martigny):
    """Test patron update with dirty barcode."""
    barcode = patron_martigny.get('patron', {}).get('barcode')[0]
    patron_martigny['patron']['barcode'] = [
        ' {barcode} '.format(barcode=barcode)
    ]
    patron_martigny.update(patron_martigny, dbcommit=True, reindex=True)
    patron = Patron.get_record_by_pid(patron_martigny.pid)
    assert patron.patron.get('barcode') == [barcode]

    # Ensure that users with no patron role will not have a barcode
    librarian_martigny.update(librarian_martigny, dbcommit=True, reindex=True)
    assert not librarian_martigny.get('patron', {}).get('barcode')
Example #9
0
def upgrade():
    """Fix incorrectly set patron communication channels."""
    query = PatronsSearch()\
        .filter('term',
                patron__communication_channel=CommunicationChannel.EMAIL)\
        .filter('bool', must_not=[
            Q('exists', field='patron.additional_communication_email')])\
        .filter('bool', must_not=[Q('exists', field='email')])\
        .source(includes='pid')
    pids = [(hit['pid'], hit.meta.id) for hit in query.scan()]
    errors = 0
    ids = []
    for idx, (pid, id) in enumerate(pids, 1):
        if patron := Patron.get_record_by_pid(pid):
            ids.append(id)
            try:
                patron['patron']['communication_channel'] = \
                    CommunicationChannel.MAIL
                db.session.query(patron.model_cls).filter_by(
                    id=patron.id).update({patron.model_cls.json: patron})
            except Exception as err:
                LOGGER.error(f'{idx} * Update patron: {pid} {err}')
                errors += 1
Example #10
0
 def _process_loan_pending_at_desk_in_transit_for_pickup(
         self, metadata, item_pid):
     """Process for PENDING, ITEM_AT_DESK, ITEM_IN_TRANSIT_FOR_PICKUP."""
     pickup_loc = Location.get_record_by_pid(
         metadata['pickup_location_pid'])
     metadata['pickup_name'] = \
         pickup_loc.get('pickup_name', pickup_loc.get('name'))
     if metadata['state'] == LoanState.ITEM_AT_DESK:
         metadata['rank'] = 0
     if metadata['state'] in [
             LoanState.PENDING, LoanState.ITEM_IN_TRANSIT_FOR_PICKUP
     ]:
         patron = Patron.get_record_by_pid(metadata['patron_pid'])
         item = Item.get_record_by_pid(item_pid)
         metadata['rank'] = item.patron_request_rank(patron)
Example #11
0
def test_patrons_dirty_barcode(client, patron_martigny_no_email,
                               librarian_martigny_no_email):
    """Test patron update with dirty barcode."""
    barcode = patron_martigny_no_email.get('barcode')
    patron_martigny_no_email['barcode'] = ' {barcode} '.format(barcode=barcode)
    patron_martigny_no_email.update(patron_martigny_no_email,
                                    dbcommit=True,
                                    reindex=True)
    patron = Patron.get_record_by_pid(patron_martigny_no_email.pid)
    assert patron.get('barcode') == barcode

    # Ensure that users with no patron role will not have a barcode
    librarian_martigny_no_email.update(librarian_martigny_no_email,
                                       dbcommit=True,
                                       reindex=True)
    assert not librarian_martigny_no_email.get('barcode')
Example #12
0
def _build_notification_email_context(loan, item, location):
    """Build the context used by the send_notification_to_location function.

    :param loan : the loan for which build context
    :param item : the item for which build context
    :param location : the item location
    """
    document_pid = Item.get_document_pid_by_item_pid(loan.item_pid)
    document = Document.get_record_by_pid(document_pid)
    pickup_location = Location.get_record_by_pid(
        loan.get('pickup_location_pid'))
    patron = Patron.get_record_by_pid(loan.patron_pid)

    # inherit holdings call number when possible
    issue_call_number = item.issue_inherited_first_call_number
    if issue_call_number:
        item['call_number'] = issue_call_number

    ctx = {
        'loan': loan.replace_refs().dumps(),
        'item': item.replace_refs().dumps(),
        'document': document.replace_refs().dumps(),
        'pickup_location': pickup_location,
        'item_location': location.dumps(),
        'patron': patron
    }
    library = pickup_location.get_library()
    ctx['pickup_location']['library'] = library
    ctx['item']['item_type'] = \
        ItemType.get_record_by_pid(item.item_type_circulation_category_pid)
    titles = [
        title for title in ctx['document'].get('title', [])
        if title['type'] == 'bf:Title'
    ]
    ctx['document']['title_text'] = \
        next(iter(titles or []), {}).get('_text')
    responsibility_statement = create_title_responsibilites(
        document.get('responsibilityStatement', []))
    ctx['document']['responsibility_statement'] = \
        next(iter(responsibility_statement or []), '')
    trans_date = ciso8601.parse_datetime(loan.get('transaction_date'))
    trans_date = trans_date\
        .replace(tzinfo=timezone.utc)\
        .astimezone(tz=library.get_timezone())
    ctx['loan']['transaction_date'] = \
        trans_date.strftime("%d.%m.%Y - %H:%M:%S")
    return ctx
Example #13
0
def patron_martigny(
        app,
        roles,
        lib_martigny,
        patron_type_children_martigny,
        patron_martigny_data):
    """Create Martigny patron record."""
    ptrn = Patron.get_record_by_pid(patron_martigny_data.get('pid'))
    if ptrn:
        ptrn = ptrn.update(
            data=patron_martigny_data,
            dbcommit=True,
            reindex=True
        )
    else:
        ptrn = Patron.create(
            data=patron_martigny_data,
            delete_pid=False,
            dbcommit=True,
            reindex=True)
    flush_index(PatronsSearch.Meta.index)
    return ptrn
Example #14
0
def librarian_martigny(
        app,
        roles,
        lib_martigny,
        librarian_martigny_data):
    """Create Martigny librarian record."""
    ptrn = Patron.get_record_by_pid(librarian_martigny_data['pid'])
    if ptrn:
        ptrn = ptrn.update(
            data=librarian_martigny_data,
            dbcommit=True,
            reindex=True
        )
    else:
        ptrn = Patron.create(
            data=librarian_martigny_data,
            delete_pid=False,
            dbcommit=True,
            reindex=True
        )
    flush_index(PatronsSearch.Meta.index)
    return ptrn
Example #15
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
Example #16
0
def item(item_barcode):
    """HTTP GET request for requested loans for a library item and patron."""
    item = Item.get_item_by_barcode(item_barcode,
                                    current_librarian.organisation_pid)
    if not item:
        abort(404)
    loan = get_loan_for_item(item_pid_to_object(item.pid))
    if loan:
        loan = Loan.get_record_by_pid(loan.get('pid')).dumps_for_circulation()
    item_dumps = item.dumps_for_circulation()
    patron_pid = flask_request.args.get('patron_pid')

    if patron_pid:
        patron = Patron.get_record_by_pid(patron_pid)
        organisation_pid = item.organisation_pid
        library_pid = item.library_pid
        patron_type_pid = patron.patron_type_pid
        item_type_pid = item.item_type_circulation_category_pid
        circ_policy = CircPolicy.provide_circ_policy(
            organisation_pid=organisation_pid,
            library_pid=library_pid,
            patron_type_pid=patron_type_pid,
            item_type_pid=item_type_pid)
        new_actions = []
        # If circulation policy doesn't allow checkout operation no need to
        # perform special check describe below.
        if circ_policy.can_checkout:
            for action in item_dumps.get('actions', []):
                if action == 'checkout':
                    if (item.number_of_requests() > 0
                            and item.patron_request_rank(patron) == 1
                            or item.number_of_requests() <= 0):
                        new_actions.append(action)
                elif action == 'receive' and item.number_of_requests() == 0:
                    new_actions.append('checkout')
        item_dumps['actions'] = new_actions
    return jsonify({'metadata': {'item': item_dumps, 'loan': loan}})
Example #17
0
def test_user_profile_updates(client, patron_martigny,
                              system_librarian_martigny, json_header, mailbox):
    """Test users profile updates."""
    # login with a patron has only the patron role, this means we are logging
    # into the public interface
    assert patron_martigny.patron['communication_channel'] == \
        CommunicationChannel.MAIL
    login_user_via_session(client, patron_martigny.user)
    # mailbox is empty
    assert not (len(mailbox))
    user_metadata = User.get_by_id(patron_martigny.user.id).dumpsMetadata()
    # changing the email by another does not send any reset_password
    # notification
    user_metadata['email'] = '*****@*****.**'
    res = client.put(url_for('api_users.users_item',
                             id=patron_martigny.user.id),
                     data=json.dumps(user_metadata),
                     headers=json_header)
    assert res.status_code == 200
    assert not (len(mailbox))
    patron_martigny = Patron.get_record_by_pid(patron_martigny.pid)
    # an email was added to patron, communication_channel will change
    # automatically to email
    assert patron_martigny.patron.get('communication_channel') == \
        CommunicationChannel.EMAIL

    # removing the email from profile does not send any reset_password
    # notification
    user_metadata.pop('email', None)
    res = client.put(url_for('api_users.users_item',
                             id=patron_martigny.user.id),
                     data=json.dumps(user_metadata),
                     headers=json_header)
    assert res.status_code == 200
    assert not (len(mailbox))
    # the corresponding patron changes its communication_channel to mail
    # autmoatically if user has no email configured and patron has no
    # additional_communication_email configured
    patron_martigny = Patron.get_record_by_pid(patron_martigny.pid)
    assert patron_martigny.patron.get('communication_channel') == \
        CommunicationChannel.MAIL

    # login as a system_librarian this means we are logging into the
    # professional interface
    login_user_via_session(client, system_librarian_martigny.user)
    # adding an email to a profile does not send any reset_password
    # notification
    user_metadata['email'] = '*****@*****.**'
    res = client.put(url_for('api_users.users_item',
                             id=patron_martigny.user.id),
                     data=json.dumps(user_metadata),
                     headers=json_header)
    assert res.status_code == 200
    assert not (len(mailbox))
    # removing the email from profile does not send any reset_password
    # notification
    user_metadata.pop('email', None)
    res = client.put(url_for('api_users.users_item',
                             id=patron_martigny.user.id),
                     data=json.dumps(user_metadata),
                     headers=json_header)
    assert res.status_code == 200
    assert not (len(mailbox))
    patron_martigny = Patron.get_record_by_pid(patron_martigny.pid)
    assert patron_martigny.patron.get('communication_channel') == \
        CommunicationChannel.MAIL
Example #18
0
def test_patron_create(app, roles, librarian_martigny_data_tmp,
                       patron_type_adults_martigny, mailbox):
    """Test Patron creation."""
    ds = app.extensions['invenio-accounts'].datastore
    email = librarian_martigny_data_tmp.get('email')
    assert not ds.find_user(email=email)
    assert len(mailbox) == 0

    wrong_librarian_martigny_data_tmp = deepcopy(librarian_martigny_data_tmp)
    wrong_librarian_martigny_data_tmp.pop('first_name')
    with pytest.raises(ValidationError):
        ptrn = Patron.create(
            wrong_librarian_martigny_data_tmp,
            dbcommit=True,
            delete_pid=True
        )

    wrong_librarian_martigny_data_tmp = deepcopy(librarian_martigny_data_tmp)
    wrong_librarian_martigny_data_tmp.pop('library')
    with pytest.raises(RecordValidationError):
        ptrn = Patron.create(
            wrong_librarian_martigny_data_tmp,
            dbcommit=True,
            delete_pid=True
        )

    wrong_librarian_martigny_data_tmp = deepcopy(librarian_martigny_data_tmp)
    wrong_librarian_martigny_data_tmp['subscriptions'] = [{
        'start_date': '2000-01-01',
        'end_date': '2001-01-01',
        'patron_type': {'$ref': 'https://ils.rero.ch/api/patron_types/xxx'},
        'patron_transaction': {
            '$ref': 'https://ils.rero.ch/api/patron_transactions/xxx'
        },
    }]
    with pytest.raises(RecordValidationError):
        ptrn = Patron.create(
            wrong_librarian_martigny_data_tmp,
            dbcommit=True,
            delete_pid=True
        )

    ptrn = Patron.create(
        librarian_martigny_data_tmp,
        dbcommit=True,
        delete_pid=False
    )
    user = ds.find_user(email=email)
    assert user
    user_roles = [r.name for r in user.roles]
    assert set(user_roles) == set(ptrn.get('roles'))
    assert len(mailbox) == 1
    assert re.search(r'localhost/lost-password', mailbox[0].body)
    assert ptrn.get('email') in mailbox[0].recipients
    assert ptrn == librarian_martigny_data_tmp
    assert ptrn.get('pid') == 'ptrn2'

    ptrn = Patron.get_record_by_pid('ptrn2')
    assert ptrn == librarian_martigny_data_tmp

    fetched_pid = patron_id_fetcher(ptrn.id, ptrn)
    assert fetched_pid.pid_value == 'ptrn2'
    assert fetched_pid.pid_type == 'ptrn'

    # set librarian
    roles = ['librarian']
    ptrn.update({'roles': roles}, dbcommit=True)
    user_roles = [r.name for r in user.roles]
    assert set(user_roles) == set(roles)
    roles = Patron.available_roles
    data = {
        'roles': Patron.available_roles,
        'patron_type': {'$ref': 'https://ils.rero.ch/api/patron_types/ptty2'}
    }
    ptrn.update(data, dbcommit=True)
    user_roles = [r.name for r in user.roles]
    assert set(user_roles) == set(Patron.available_roles)

    # remove patron
    ptrn.delete()
    # user still exist in the invenio db
    user = ds.find_user(email=email)
    assert user
    # all roles has been removed
    assert not user.roles
    assert len(mailbox) == 1
    # patron does not exists anymore
    ptrn = Patron.get_record_by_pid('ptrn2')
    assert ptrn is None
    ptrn = Patron.get_record_by_pid('ptrn2', with_deleted=True)
    assert ptrn == {}
    assert ptrn.persistent_identifier.pid_value == 'ptrn2'
Example #19
0
 def transaction_user(self):
     """Shortcut for transaction user of the notification."""
     return Patron.get_record_by_pid(self.transaction_user_pid)
Example #20
0
 def patron(self):
     """Shortcut for patron of the notification."""
     return Patron.get_record_by_pid(self.patron_pid)
Example #21
0
def test_patron_create(app, roles, lib_martigny, librarian_martigny_data_tmp,
                       patron_type_adults_martigny, mailbox):
    """Test Patron creation."""
    ds = app.extensions['invenio-accounts'].datastore
    email = librarian_martigny_data_tmp.get('email')

    # sanity checks
    assert len(mailbox) == 0
    assert User.query.count() == 0
    assert UserProfile.query.count() == 0
    l_martigny_data_tmp = librarian_martigny_data_tmp
    librarian_martigny_data_tmp = create_user_from_data(
        librarian_martigny_data_tmp)
    # wrong_librarian_martigny_data_tmp = deepcopy(librarian_martigny_data_tmp)
    # wrong_librarian_martigny_data_tmp.pop('first_name')
    # with pytest.raises(ValidationError):
    #     ptrn = Patron.create(
    #         wrong_librarian_martigny_data_tmp,
    #         dbcommit=True,
    #         delete_pid=True
    #     )

    wrong_librarian_martigny_data_tmp = deepcopy(librarian_martigny_data_tmp)
    wrong_librarian_martigny_data_tmp.pop('libraries')
    with pytest.raises(ValidationError):
        ptrn = Patron.create(wrong_librarian_martigny_data_tmp,
                             dbcommit=True,
                             delete_pid=True)

    wrong_librarian_martigny_data_tmp = deepcopy(librarian_martigny_data_tmp)
    wrong_librarian_martigny_data_tmp.setdefault(
        'patron', {
            'expiration_date': '2023-10-07',
            'barcode': ['2050124311'],
            'type': {
                '$ref': 'https://bib.rero.ch/api/patron_types/ptty2'
            },
            'communication_channel': 'email',
            'communication_language': 'ita'
        })
    wrong_librarian_martigny_data_tmp['patron']['subscriptions'] = [{
        'start_date':
        '2000-01-01',
        'end_date':
        '2001-01-01',
        'patron_type': {
            '$ref': 'https://bib.rero.ch/api/patron_types/xxx'
        },
        'patron_transaction': {
            '$ref': 'https://bib.rero.ch/api/patron_transactions/xxx'
        },
    }]
    with pytest.raises(ValidationError):
        ptrn = Patron.create(wrong_librarian_martigny_data_tmp,
                             dbcommit=True,
                             delete_pid=True)

    # no data has been created
    assert len(mailbox) == 0
    # assert User.query.count() == 0
    # assert UserProfile.query.count() == 0

    ptrn = Patron.create(librarian_martigny_data_tmp,
                         dbcommit=True,
                         delete_pid=False)
    user = User.query.filter_by(id=ptrn.get('user_id')).first()
    user_id = ptrn.get('user_id')
    assert user
    assert user.active
    for field in [
            'first_name', 'last_name', 'street', 'postal_code', 'city',
            'username', 'home_phone'
    ]:
        assert getattr(user.profile, field) == l_martigny_data_tmp.get(field)
    user.profile.birth_date == datetime.strptime(
        l_martigny_data_tmp.get('birth_date'), '%Y-%m-%d')
    user_roles = [r.name for r in user.roles]
    assert set(user_roles) == set(ptrn.get('roles'))
    # TODO: make these checks during the librarian POST creation
    # assert len(mailbox) == 1
    # assert re.search(r'localhost/lost-password', mailbox[0].body)
    # assert re.search(
    #     r'Someone requested that the password' +
    #     ' for your RERO ID account be reset.', mailbox[0].body
    # )
    # assert re.search(
    #     r'Best regards', mailbox[0].body
    # )
    # assert ptrn.get('email') in mailbox[0].recipients
    librarian_martigny_data_tmp['user_id'] = 1
    assert ptrn == librarian_martigny_data_tmp
    assert ptrn.get('pid') == 'ptrn2'

    ptrn = Patron.get_record_by_pid('ptrn2')
    # assert ptrn == librarian_martigny_data_tmp

    fetched_pid = patron_id_fetcher(ptrn.id, ptrn)
    assert fetched_pid.pid_value == 'ptrn2'
    assert fetched_pid.pid_type == 'ptrn'

    # set librarian
    roles = ['librarian']
    ptrn.update({'roles': roles}, dbcommit=True)
    user_roles = [r.name for r in user.roles]
    assert set(user_roles) == set(roles)
    roles = Patron.available_roles
    data = {
        'roles': Patron.available_roles,
        'patron': {
            'expiration_date': '2023-10-07',
            'barcode': ['2050124311'],
            'type': {
                '$ref': 'https://bib.rero.ch/api/patron_types/ptty2'
            },
            'communication_channel': 'email',
            'communication_language': 'ita'
        }
    }
    ptrn.update(data, dbcommit=True)
    user_roles = [r.name for r in user.roles]
    assert set(user_roles) == set(Patron.available_roles)

    # remove patron
    ptrn.delete(False, True, True)
    # user still exist in the invenio db
    user = ds.find_user(email=email)
    assert user
    # all roles has been removed
    assert not user.roles
    # assert len(mailbox) == 1
    # patron does not exists anymore
    ptrn = Patron.get_record_by_pid('ptrn2')
    assert ptrn is None
    ptrn = Patron.get_record_by_pid('ptrn2', with_deleted=True)
    assert ptrn == {}
    assert ptrn.persistent_identifier.pid_value == 'ptrn2'
    # remove patron
    ptrn.delete(True, True, True)
    # clean up the user
    ds.delete_user(user)