def test_notification_email(notification_late_sion, patron_sion, mailbox):
    """Test overdue notification.
        Patron communication channel is email.
    """
    mailbox.clear()
    Dispatcher.dispatch_notifications(notification_late_sion['pid'])
    assert mailbox[0].recipients == [patron_sion.dumps()['email']]
def test_notification_email_availability(notification_availability_sion,
                                         lib_sion, patron_sion, mailbox):
    """Test availability notification.
        Patron communication channel is email.
    """
    mailbox.clear()
    Dispatcher.dispatch_notifications(notification_availability_sion['pid'])
    assert mailbox[0].recipients == [patron_sion.dumps()['email']]
def test_notification_mail(notification_late_martigny, lib_martigny, mailbox):
    """Test notification creation.
        Patron communication channel is mail.
    """
    mailbox.clear()
    Dispatcher.dispatch_notifications(notification_late_martigny['pid'])
    recipient = lib_martigny.get_email(
        notification_late_martigny['notification_type'])
    assert recipient
    assert mailbox[0].recipients == [recipient]
def test_notification_email_aggregated(notification_availability_martigny,
                                       notification2_availability_martigny,
                                       lib_martigny, patron_martigny, mailbox):
    """Test availability notification.
        Patron communication channel is email.
    """
    mailbox.clear()
    Dispatcher.dispatch_notifications([
        notification_availability_martigny['pid'],
        notification2_availability_martigny['pid']
    ],
                                      verbose=True)
    assert len(mailbox) == 1

    recipient = '???'
    for notification_setting in lib_martigny.get('notification_settings'):
        if notification_setting['type'] == NotificationType.AVAILABILITY:
            recipient = notification_setting['email']
    assert mailbox[0].recipients == [recipient]
示例#5
0
def test_notification_dispatch(app, mailbox):
    """Test notification dispatch."""
    class DummyNotification(object):

        data = {
            'pid': 'dummy_notification_pid',
            'notification_type': 'dummy_notification'
        }

        def __init__(self, communication_channel):
            self.communication_channel = communication_channel

        def __getitem__(self, key):
            return self.data[key]
            return self.data[key]

        def replace_pids_and_refs(self):
            return {
                'loan': {
                    'pid': 'dummy_notification_loan_pid',
                    'patron': {
                        'pid': 'dummy_patron_pid',
                        'patron': {
                            'communication_channel': self.communication_channel
                        }
                    }
                }
            }

        def update_process_date(self):
            return self

    notification = DummyNotification('sms')
    Dispatcher().dispatch_notification(notification=notification, verbose=True)

    notification = DummyNotification('whatsapp')
    Dispatcher().dispatch_notification(notification=notification, verbose=True)

    notification = DummyNotification('mail')
    Dispatcher().dispatch_notification(notification=notification, verbose=True)

    notification = DummyNotification('XXXX')
    Dispatcher().dispatch_notification(notification=notification, verbose=True)
示例#6
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
示例#7
0
def test_overdue_limit(
     client, app, librarian_martigny, lib_martigny, item_lib_martigny,
     item2_lib_martigny, patron_type_children_martigny,
     item3_lib_martigny, item_lib_martigny_data, item2_lib_martigny_data,
     item3_lib_martigny_data, loc_public_martigny, patron_martigny,
     circ_policy_short_martigny, lib_saxon, loc_public_saxon):
    """Test overdue limit."""

    item = item_lib_martigny
    item_pid = item.pid
    patron_pid = patron_martigny.pid
    date_format = '%Y/%m/%dT%H:%M:%S.000Z'
    today = datetime.utcnow()
    eod = today.replace(hour=23, minute=59, second=0, microsecond=0,
                        tzinfo=lib_martigny.get_timezone())

    # STEP 0 :: Prepare data for test
    #   * Update the patron_type to set a overdue_items_limit rule.
    #     We define than only 1 overdue items are allowed. Trying a second
    #     checkout is disallowed if patron has an overdue item
    patron_type = patron_type_children_martigny
    patron_type \
        .setdefault('limits', {}) \
        .setdefault('overdue_items_limits', {}) \
        .setdefault('default_value', 1)
    patron_type.update(patron_type, dbcommit=True, reindex=True)
    patron_type = PatronType.get_record_by_pid(patron_type.pid)
    assert patron_type\
        .get('limits', {})\
        .get('overdue_items_limits', {}) \
        .get('default_value') == 1

    # STEP 1 :: Create an checkout with a end_date at the current date
    #   * Create a checkout and set end_date to a fixed_date equals to
    #     current tested date. The loan should not be considered as overdue
    #     and a second checkout should be possible
    login_user_via_session(client, librarian_martigny.user)
    res, data = postdata(
        client,
        'api_item.checkout',
        dict(
            item_pid=item_pid,
            patron_pid=patron_pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
            end_date=eod.strftime(date_format)
        )
    )
    assert res.status_code == 200
    loan_pid = data.get('action_applied')[LoanAction.CHECKOUT].get('pid')
    loan = Loan.get_record_by_pid(loan_pid)
    assert not loan.is_loan_overdue()

    tmp_item_data = deepcopy(item_lib_martigny_data)
    del tmp_item_data['pid']
    tmp_item_data['library']['$ref'] = get_ref_for_pid('lib', lib_saxon.pid)
    tmp_item_data['location']['$ref'] = \
        get_ref_for_pid('loc', loc_public_saxon.pid)
    tmp_item = Item.create(tmp_item_data, dbcommit=True, reindex=True)
    res, data = postdata(
        client,
        'api_item.checkout',
        dict(
            item_pid=tmp_item.pid,
            patron_pid=patron_pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid
        )
    )
    assert res.status_code == 200
    res, _ = postdata(
        client,
        'api_item.checkin',
        dict(
            item_pid=tmp_item.pid,
            pid=data.get('action_applied')[LoanAction.CHECKOUT].get('pid'),
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        )
    )
    assert res.status_code == 200

    # STEP 2 :: Set the loan as overdue and test a new checkout
    #   Now there is one loan in overdue, then the limit is reached and a new
    #   checkout shouldn't be possible
    end_date = eod - timedelta(days=7)
    loan['end_date'] = end_date.isoformat()
    loan.update(loan, dbcommit=True, reindex=True)

    overdue_loans = list(get_overdue_loans(patron_pid=patron_pid))
    assert loan.is_loan_overdue()
    assert loan.end_date == end_date.isoformat()
    assert overdue_loans[0].get('pid') == loan_pid
    assert not get_notification(loan, NotificationType.OVERDUE)

    notification = loan.create_notification(
        _type=NotificationType.OVERDUE).pop()
    Dispatcher.dispatch_notifications([notification.get('pid')])
    flush_index(NotificationsSearch.Meta.index)
    flush_index(LoansSearch.Meta.index)
    assert get_notification(loan, NotificationType.OVERDUE)

    # Try a second checkout - limit should be reached
    res, data = postdata(
        client,
        'api_item.checkout',
        dict(
            item_pid=item2_lib_martigny.pid,
            patron_pid=patron_pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        )
    )
    assert res.status_code == 403
    assert 'Checkout denied' in data['message']
    # Try a request - limit should be reached
    res, data = postdata(
        client,
        'api_item.librarian_request',
        dict(
            item_pid=item2_lib_martigny.pid,
            patron_pid=patron_pid,
            pickup_location_pid=loc_public_martigny.pid,
            transaction_library_pid=lib_martigny.pid,
            transaction_user_pid=librarian_martigny.pid
        )
    )
    assert res.status_code == 403
    assert 'maximal number of overdue items is reached' in data['message']
    # Try to extend - limit should be reached
    res, _ = postdata(
        client,
        'api_item.extend_loan',
        dict(
            item_pid=item_pid,
            transaction_user_pid=librarian_martigny.pid,
            transaction_location_pid=loc_public_martigny.pid
        )
    )
    assert res.status_code == 403
    assert 'maximal number of overdue items is reached' in data['message']

    # reset the patron_type with default value
    del patron_type['limits']

    # [2] test fee amount limit

    # Update the patron_type to set a fee_amount_limit rule
    patron_type \
        .setdefault('limits', {}) \
        .setdefault('fee_amount_limits', {}) \
        .setdefault('default_value', 0.5)
    patron_type.update(patron_type, dbcommit=True, reindex=True)
    patron_type = PatronType.get_record_by_pid(patron_type.pid)
    assert patron_type.get('limits', {}).get('fee_amount_limits', {}) \
        .get('default_value') == 0.5

    # [2.1] test fee amount limit when we try to checkout a second item
    res, data = postdata(
        client,
        'api_item.checkout',
        dict(
            item_pid=item2_lib_martigny.pid,
            patron_pid=patron_pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        )
    )
    assert res.status_code == 403
    assert 'maximal overdue fee amount is reached' in data['message']

    # [2.2] test fee amount limit when we try to request another item
    res, data = postdata(
        client,
        'api_item.librarian_request',
        dict(
            item_pid=item2_lib_martigny.pid,
            patron_pid=patron_pid,
            pickup_location_pid=loc_public_martigny.pid,
            transaction_library_pid=lib_martigny.pid,
            transaction_user_pid=librarian_martigny.pid
        )
    )
    assert res.status_code == 403
    assert 'maximal overdue fee amount is reached' in data['message']

    # [2.3] test fee amount limit when we try to extend loan
    res, _ = postdata(
        client,
        'api_item.extend_loan',
        dict(
            item_pid=item_pid,
            transaction_user_pid=librarian_martigny.pid,
            transaction_location_pid=loc_public_martigny.pid
        )
    )

    assert res.status_code == 403
    assert 'maximal overdue fee amount is reached' in data['message']

    # reset the patron_type with default value
    del patron_type['limits']
    patron_type.update(patron_type, dbcommit=True, reindex=True)
    patron_type = PatronType.get_record_by_pid(patron_type.pid)
    assert patron_type.get('limits') is None

    # # checkin the item to put it back to it's original state
    res, _ = postdata(
        client,
        'api_item.checkin',
        dict(
            item_pid=item_pid,
            pid=loan_pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        )
    )
    assert res.status_code == 200
示例#8
0
def test_item_information(client, librarian_martigny,
                          selfcheck_patron_martigny, loc_public_martigny,
                          item_lib_martigny, circulation_policies):
    """Test item information."""
    login_user_via_session(client, librarian_martigny.user)
    # checkout
    res, data = postdata(
        client, 'api_item.checkout',
        dict(item_pid=item_lib_martigny.pid,
             patron_pid=selfcheck_patron_martigny.pid,
             transaction_user_pid=librarian_martigny.pid,
             transaction_location_pid=loc_public_martigny.pid))
    assert res.status_code == 200
    actions = data.get('action_applied')
    loan_pid = actions[LoanAction.CHECKOUT].get('pid')
    loan = Loan.get_record_by_pid(loan_pid)

    assert not loan.is_loan_overdue()
    # set loan on overdue
    end_date = datetime.now(timezone.utc) - timedelta(days=7)
    loan['end_date'] = end_date.isoformat()
    loan.update(loan, dbcommit=True, reindex=True)
    loan = Loan.get_record_by_pid(loan_pid)
    assert loan['state'] == LoanState.ITEM_ON_LOAN
    assert loan.is_loan_overdue()
    notification = loan.create_notification(
        _type=NotificationType.OVERDUE).pop()
    Dispatcher.dispatch_notifications([notification.get('pid')])
    flush_index(NotificationsSearch.Meta.index)
    flush_index(LoansSearch.Meta.index)
    assert number_of_notifications_sent(loan) == 1

    patron_barcode = selfcheck_patron_martigny\
        .get('patron', {}).get('barcode')[0]
    item_barcode = item_lib_martigny.get('barcode')

    # get item information
    response = item_information(
        patron_barcode=patron_barcode,
        item_barcode=item_barcode,
        institution_id=librarian_martigny.organisation_pid)
    assert response
    # check required fields in response
    assert all(key in response for key in (
        'item_id',
        'title_id',
        'circulation_status',
        'fee_type',
        'security_marker',
    ))
    assert response['due_date']
    assert response['fee_amount']

    # checkin
    res, _ = postdata(
        client, 'api_item.checkin',
        dict(item_pid=item_lib_martigny.pid,
             pid=loan_pid,
             transaction_user_pid=librarian_martigny.pid,
             transaction_location_pid=loc_public_martigny.pid))
    assert res.status_code == 200

    # test with wrong item barcode
    response = item_information(
        patron_barcode=patron_barcode,
        item_barcode='wrong_item_barcode',
        institution_id=librarian_martigny.organisation_pid)
    assert 'item not found' in response.get('screen_messages')[0]
示例#9
0
def test_patron_information(client, librarian_martigny,
                            selfcheck_patron_martigny, loc_public_martigny,
                            item_lib_martigny, item2_lib_martigny,
                            circulation_policies, lib_martigny):
    """Test patron information."""
    login_user_via_session(client, librarian_martigny.user)
    # checkout
    res, data = postdata(
        client, 'api_item.checkout',
        dict(item_pid=item_lib_martigny.pid,
             patron_pid=selfcheck_patron_martigny.pid,
             transaction_user_pid=librarian_martigny.pid,
             transaction_location_pid=loc_public_martigny.pid))
    assert res.status_code == 200
    actions = data.get('action_applied')
    loan_pid = actions[LoanAction.CHECKOUT].get('pid')
    loan = Loan.get_record_by_pid(loan_pid)
    assert not loan.is_loan_overdue()
    # set loan on overdue
    end_date = datetime.now(timezone.utc) - timedelta(days=7)
    loan['end_date'] = end_date.isoformat()
    loan.update(loan, dbcommit=True, reindex=True)
    loan = Loan.get_record_by_pid(loan_pid)
    assert loan.is_loan_overdue()
    notification = loan.create_notification(
        _type=NotificationType.OVERDUE).pop()
    Dispatcher.dispatch_notifications([notification.get('pid')])
    flush_index(NotificationsSearch.Meta.index)
    flush_index(LoansSearch.Meta.index)
    assert number_of_notifications_sent(loan) == 1
    # create request
    res, data = postdata(
        client, 'api_item.librarian_request',
        dict(item_pid=item2_lib_martigny.pid,
             patron_pid=selfcheck_patron_martigny.pid,
             pickup_location_pid=loc_public_martigny.pid,
             transaction_library_pid=lib_martigny.pid,
             transaction_user_pid=librarian_martigny.pid))
    assert res.status_code == 200
    # get patron information
    response = patron_information(
        selfcheck_patron_martigny.get('patron', {}).get('barcode')[0])
    assert response

    # get patron status
    response = patron_status(
        selfcheck_patron_martigny.get('patron', {}).get('barcode')[0])
    assert response

    # checkin
    res, _ = postdata(
        client, 'api_item.checkin',
        dict(item_pid=item_lib_martigny.pid,
             pid=loan_pid,
             transaction_user_pid=librarian_martigny.pid,
             transaction_location_pid=loc_public_martigny.pid))
    assert res.status_code == 200

    # test with wrong patron
    response = patron_information('wrong_patron_barcode')
    assert 'patron not found' in response.get('screen_messages')[0]

    assert 'patron not found' in response.get('screen_messages')[0]
示例#10
0
def test_overdue_loans(client, librarian_martigny, patron_martigny,
                       loc_public_martigny, item_type_standard_martigny,
                       item_lib_martigny, item2_lib_martigny,
                       patron_type_children_martigny,
                       circ_policy_short_martigny, patron3_martigny_blocked):
    """Test overdue loans."""
    login_user_via_session(client, librarian_martigny.user)
    item = item_lib_martigny
    item_pid = item.pid
    patron_pid = patron_martigny.pid

    # checkout
    res, data = postdata(
        client, 'api_item.checkout',
        dict(
            item_pid=item_pid,
            patron_pid=patron_pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        ))
    assert res.status_code == 200, "It probably failed while \
        test_due_soon_loans fail"

    loan_pid = data.get('action_applied')[LoanAction.CHECKOUT].get('pid')
    loan = Loan.get_record_by_pid(loan_pid)
    end_date = datetime.now(timezone.utc) - timedelta(days=7)
    loan['end_date'] = end_date.isoformat()
    loan.update(loan, dbcommit=True, reindex=True)

    overdue_loans = list(get_overdue_loans(patron_pid=patron_pid))
    assert overdue_loans[0].get('pid') == loan_pid
    assert number_of_notifications_sent(loan) == 0

    notification = loan.create_notification(
        _type=NotificationType.OVERDUE).pop()
    Dispatcher.dispatch_notifications([notification.get('pid')])
    flush_index(NotificationsSearch.Meta.index)
    flush_index(LoansSearch.Meta.index)
    assert number_of_notifications_sent(loan) == 1

    # Try a checkout for a blocked user :: It should be blocked
    res, data = postdata(
        client, 'api_item.checkout',
        dict(
            item_pid=item2_lib_martigny.pid,
            patron_pid=patron3_martigny_blocked.pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        ))
    assert res.status_code == 403
    assert 'This patron is currently blocked' in data['message']

    # checkin the item to put it back to it's original state
    res, _ = postdata(
        client, 'api_item.checkin',
        dict(
            item_pid=item_pid,
            pid=loan_pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        ))
    assert res.status_code == 200