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]
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)
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
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
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]
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]
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