def test_add_request_on_item_at_desk( client, item_at_desk_martigny_patron_and_loan_at_desk, loc_public_martigny, librarian_martigny_no_email, patron2_martigny_no_email): """Test requests on an at_desk item.""" item, patron, loan = item_at_desk_martigny_patron_and_loan_at_desk # the following tests the circulation action ADD_REQUEST_2_1 # the patron owns the ITEM_AT_DESK loan may not create a new pending loan. params = { 'patron_pid': patron.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid, 'pickup_location_pid': loc_public_martigny.pid } with pytest.raises(RecordCannotBeRequestedError): item, loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) # the following tests the circulation action ADD_REQUEST_2_2 # a patron who doesnt own the ITEM_AT_DESK loan can add a new pending loan. params['patron_pid'] = patron2_martigny_no_email.pid item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) assert requested_loan['state'] == LoanState.PENDING assert loan['state'] == LoanState.ITEM_AT_DESK
def test_operation_logs_serializers(client, rero_json_header, patron_martigny, librarian_martigny, item_lib_martigny, loc_public_martigny, circulation_policies, lib_martigny_data): """Test serializers for operation logs.""" login_user(client, patron_martigny) params = { 'patron_pid': patron_martigny.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny.pid, 'pickup_location_pid': loc_public_martigny.pid, } item_record_to_a_specific_loan_state(item=item_lib_martigny, loan_state=LoanState.ITEM_AT_DESK, params=params, copy_item=True) # Force update ES index flush_index(OperationLogsSearch.Meta.index) list_url = url_for('invenio_records_rest.oplg_list') response = client.get(list_url, headers=rero_json_header) assert response.status_code == 200 data = get_json(response) loan = data.get('hits', {}).get('hits', [])[0].get('metadata', {})\ .get('loan', {}) libary_name = lib_martigny_data['name'] # Check if the library data injected into the section assert libary_name == loan.get('transaction_location', {})\ .get('library').get('name') assert libary_name == loan.get('pickup_location', {})\ .get('library').get('name')
def test_add_request_on_item_on_shelf( item_on_shelf_martigny_patron_and_loan_pending, loc_public_martigny, librarian_martigny_no_email, patron2_martigny_no_email): """Test requests on an on_shelf item.""" item, patron, loan = item_on_shelf_martigny_patron_and_loan_pending # the following tests the circulation action ADD_REQUEST_1_1 # an on_shelf item with no pending requests can have new pending requests. assert loan['state'] == LoanState.PENDING # the following tests the circulation action ADD_REQUEST_1_2_1 # for an item on_shelf with a pending loan, the patron that owns the # pending loan can not add a new pending loan on same item. params = { 'patron_pid': patron.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid, 'pickup_location_pid': loc_public_martigny.pid } with pytest.raises(RecordCannotBeRequestedError): item, loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) # the following tests the circulation action ADD_REQUEST_1_2_2 # for an item on_shelf with a pending loan, a patron that does not own the # pending loan can add a new pending loan on same item. params['patron_pid'] = patron2_martigny_no_email.pid item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) assert requested_loan['state'] == LoanState.PENDING
def test_add_request_on_item_in_transit_for_pickup( item_in_transit_martigny_patron_and_loan_for_pickup, loc_public_martigny, librarian_martigny_no_email, patron2_martigny_no_email, loc_public_fully): """Test requests on an in_transit item for pickup.""" item, patron, loan = item_in_transit_martigny_patron_and_loan_for_pickup # the following tests the circulation action ADD_REQUEST_4_1 # the owner of the IN_TRANSIT_FOR_PICKUP loan can not add a pending loan params = { 'patron_pid': patron.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid, 'pickup_location_pid': loc_public_fully.pid } with pytest.raises(RecordCannotBeRequestedError): item, loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) # the following tests the circulation action ADD_REQUEST_4_2 # a patron who does not own the IN_TRANSIT_FOR_PICKUP loan can add # a new pending loan. params['patron_pid'] = patron2_martigny_no_email.pid item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) assert requested_loan['state'] == LoanState.PENDING assert loan['state'] == LoanState.ITEM_IN_TRANSIT_FOR_PICKUP
def test_checkin_on_item_on_loan_with_requests_externally( item4_on_loan_martigny_patron_and_loan_on_loan, item5_on_loan_martigny_patron_and_loan_on_loan, loc_public_martigny, librarian_martigny, patron2_martigny, loc_public_fully, loc_public_saxon): """Test checkin on an on_loan item with requests at an external library.""" item, patron, loan = item4_on_loan_martigny_patron_and_loan_on_loan # the following tests the circulation action CHECKIN_3_2_2_1 # for an item on_loan, with pending requests. when the pickup library of # the first pending request does not equal to the transaction library, # checkin the item and the loan on_loan is cancelled. # if the pickup location of the first pending equal the item library, # the pending loan becomes ITEM_IN_TRANSIT_FOR_PICKUP params = { 'patron_pid': patron2_martigny.pid, 'transaction_location_pid': loc_public_fully.pid, 'transaction_user_pid': librarian_martigny.pid, 'pickup_location_pid': loc_public_martigny.pid } item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) item, actions = item.checkin(**params) item = Item.get_record_by_pid(item.pid) loan = Loan.get_record_by_pid(loan.pid) requested_loan = Loan.get_record_by_pid(requested_loan.pid) assert item.status == ItemStatus.IN_TRANSIT assert loan['state'] == LoanState.CANCELLED assert requested_loan['state'] == LoanState.ITEM_IN_TRANSIT_FOR_PICKUP item, patron, loan = item5_on_loan_martigny_patron_and_loan_on_loan # the following tests the circulation action CHECKIN_3_2_2_2 # for an item on_loan, with pending requests. when the pickup library of # the first pending request does not equal to the transaction library, # checkin the item and the loan on_loan is cancelled. # if the pickup location of the first pending does not equal the item # library, the pending loan becomes ITEM_IN_TRANSIT_FOR_PICKUP params = { 'patron_pid': patron2_martigny.pid, 'transaction_location_pid': loc_public_saxon.pid, 'transaction_user_pid': librarian_martigny.pid, 'pickup_location_pid': loc_public_fully.pid } item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) item, actions = item.checkin(**params) item = Item.get_record_by_pid(item.pid) loan = Loan.get_record_by_pid(loan.pid) requested_loan = Loan.get_record_by_pid(requested_loan.pid) assert item.status == ItemStatus.IN_TRANSIT assert loan['state'] == LoanState.CANCELLED assert requested_loan['state'] == LoanState.ITEM_IN_TRANSIT_FOR_PICKUP
def test_add_request_on_item_on_loan( item_on_loan_martigny_patron_and_loan_on_loan, loc_public_martigny, librarian_martigny_no_email, patron2_martigny_no_email, patron4_martigny_no_email): """Test requests on an on_loan item.""" item, patron, loan = item_on_loan_martigny_patron_and_loan_on_loan # the following tests the circulation action ADD_REQUEST_3_1 # the patron owns the ITEM_ON_LOAN loan may not create a new pending loan. params = { 'patron_pid': patron.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid, 'pickup_location_pid': loc_public_martigny.pid } with pytest.raises(RecordCannotBeRequestedError): item, loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) # the following tests the circulation action ADD_REQUEST_3_2_1 # any patron who does not own the ITEM_ON_LOAN loan can add a new pending # loan. params['patron_pid'] = patron2_martigny_no_email.pid item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) assert requested_loan['state'] == LoanState.PENDING assert loan['state'] == LoanState.ITEM_ON_LOAN # the following tests the circulation action ADD_REQUEST_3_2_2_1 # when an item on_loan has pending requests, the patron who owns the # pending loan may not add a new pending loan params['patron_pid'] = patron2_martigny_no_email.pid with pytest.raises(RecordCannotBeRequestedError): item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) # the following tests the circulation action ADD_REQUEST_3_2_2_2 # when an item on_loan has pending requests, any patron who does not own # the pending loan may add a new pending loan params['patron_pid'] = patron4_martigny_no_email.pid item, second_requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) assert loan['state'] == LoanState.ITEM_ON_LOAN assert requested_loan['state'] == LoanState.PENDING assert second_requested_loan['state'] == LoanState.PENDING
def test_cancel_request_on_item_at_desk_with_requests_externally( client, item3_at_desk_martigny_patron_and_loan_at_desk, loc_public_martigny, librarian_martigny_no_email, patron2_martigny_no_email, loc_public_fully): """Test cancel requests on an at_desk item with requests at externally.""" item, patron, loan = item3_at_desk_martigny_patron_and_loan_at_desk # the following tests the circulation action CANCEL_REQUEST_2_1_2_1 # an item at_desk with other pending loans. # pickup location of 1st pending loan != pickup location of current loan # cancel the current loan and item is: in_transit, automatic validation of # firt pending loan params = { 'patron_pid': patron2_martigny_no_email.pid, 'transaction_location_pid': loc_public_fully.pid, 'transaction_user_pid': librarian_martigny_no_email.pid, 'pickup_location_pid': loc_public_fully.pid } item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) assert requested_loan['state'] == LoanState.PENDING params = { 'pid': loan.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid } item.cancel_item_request(**params) item = Item.get_record_by_pid(item.pid) loan = Loan.get_record_by_pid(loan.pid) requested_loan = Loan.get_record_by_pid(requested_loan.pid) assert item.status == ItemStatus.IN_TRANSIT assert loan['state'] == LoanState.CANCELLED assert requested_loan['state'] == LoanState.ITEM_IN_TRANSIT_FOR_PICKUP
def item_on_shelf_fully_patron_and_loan_pending( app, librarian_martigny, item_lib_fully, loc_public_fully, patron_martigny, circulation_policies, ): """Creates an item on_shelf requested by a patron. :return item: the created or copied item. :return patron: the patron placed the request. :return loan: the pending loan. """ params = { 'patron_pid': patron_martigny.pid, 'transaction_location_pid': loc_public_fully.pid, 'transaction_user_pid': librarian_martigny.pid, 'pickup_location_pid': loc_public_fully.pid } item, loan = item_record_to_a_specific_loan_state( item=item_lib_fully, loan_state=LoanState.PENDING, params=params, copy_item=True) return item, patron_martigny, loan
def test_checkin_on_item_on_loan_with_requests( item3_on_loan_martigny_patron_and_loan_on_loan, loc_public_martigny, librarian_martigny_no_email, patron2_martigny_no_email): """Test checkin on an on_loan item with requests at local library.""" item, patron, loan = item3_on_loan_martigny_patron_and_loan_on_loan # the following tests the circulation action CHECKIN_3_2_1 # for an item on_loan, with pending requests. when the pickup library of # the first pending request equal to the transaction library, # checkin the item and item becomes at_desk. # the on_loan is returned and validating the first pending loan request. params = { 'patron_pid': patron2_martigny_no_email.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid, 'pickup_location_pid': loc_public_martigny.pid } item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) item, actions = item.checkin(**params) item = Item.get_record_by_pid(item.pid) loan = Loan.get_record_by_pid(loan.pid) requested_loan = Loan.get_record_by_pid(requested_loan.pid) assert item.status == ItemStatus.AT_DESK assert loan['state'] == LoanState.ITEM_RETURNED assert requested_loan['state'] == LoanState.ITEM_AT_DESK
def test_cancel_pending_on_item_in_transit_to_house( client, item3_in_transit_martigny_patron_and_loan_to_house, loc_public_martigny, librarian_martigny_no_email, patron2_martigny_no_email): """Test cancel pending loan on an in_transit to_house item.""" item, patron, loan = item3_in_transit_martigny_patron_and_loan_to_house # the following tests the circulation action CANCEL_REQUEST_5_2 # an item in_transit to house with pending loans. when a # librarian wants to cancel the pending loan. action is permitted. # the loan will be cancelled. and in_transit loan remains in transit. params = { 'patron_pid': patron2_martigny_no_email.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid, 'pickup_location_pid': loc_public_martigny.pid } item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) assert requested_loan['state'] == LoanState.PENDING params = { 'pid': requested_loan.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid } item.cancel_item_request(**params) item = Item.get_record_by_pid(item.pid) loan = Loan.get_record_by_pid(loan.pid) requested_loan = Loan.get_record_by_pid(requested_loan.pid) assert item.status == ItemStatus.IN_TRANSIT assert loan['state'] == LoanState.ITEM_IN_TRANSIT_TO_HOUSE assert requested_loan['state'] == LoanState.CANCELLED
def test_cancel_request_on_item_in_transit_to_house_with_requests( client, item2_in_transit_martigny_patron_and_loan_to_house, loc_public_martigny, librarian_martigny, patron2_martigny): """Test cancel request on an in_transit to_house item with requests.""" item, patron, loan = item2_in_transit_martigny_patron_and_loan_to_house # the following tests the circulation action CANCEL_REQUEST_5_1_2 # an item in_transit to house with pending loans. when a # librarian wants to cancel the in_transit loan. action is permitted. # the loan will be cancelled. and first pending loan will be validated. params = { 'patron_pid': patron2_martigny.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny.pid, 'pickup_location_pid': loc_public_martigny.pid } item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) assert requested_loan['state'] == LoanState.PENDING params = { 'pid': loan.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny.pid } item.cancel_item_request(**params) item = Item.get_record_by_pid(item.pid) loan = Loan.get_record_by_pid(loan.pid) requested_loan = Loan.get_record_by_pid(requested_loan.pid) assert item.status == ItemStatus.AT_DESK assert loan['state'] == LoanState.CANCELLED assert requested_loan['state'] == LoanState.ITEM_AT_DESK
def test_cancel_pending_loan_on_item_in_transit_for_pickup_with_requests( client, item3_in_transit_martigny_patron_and_loan_for_pickup, loc_public_martigny, librarian_martigny, patron2_martigny): """Test cancel pending loan on an in_transit for pickup item.""" item, patron, loan = item3_in_transit_martigny_patron_and_loan_for_pickup # the following tests the circulation action CANCEL_REQUEST_4_2 # an item in_transit for pickup with other pending loans. when a # librarian wants to cancel the pending loan. action is permitted. # item remains in_transit params = { 'patron_pid': patron2_martigny.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny.pid, 'pickup_location_pid': loc_public_martigny.pid } item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) assert requested_loan['state'] == LoanState.PENDING params = { 'pid': requested_loan.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny.pid } item.cancel_item_request(**params) item = Item.get_record_by_pid(item.pid) loan = Loan.get_record_by_pid(loan.pid) requested_loan = Loan.get_record_by_pid(requested_loan.pid) assert item.status == ItemStatus.IN_TRANSIT assert loan['state'] == LoanState.ITEM_IN_TRANSIT_FOR_PICKUP assert requested_loan['state'] == LoanState.CANCELLED
def test_cancel_pending_request_on_item_at_desk( client, item5_at_desk_martigny_patron_and_loan_at_desk, loc_public_martigny, librarian_martigny_no_email, patron2_martigny_no_email): """Test cancel requests on an at_desk item with requests at home.""" item, patron, loan = item5_at_desk_martigny_patron_and_loan_at_desk # the following tests the circulation action CANCEL_REQUEST_2_2 # an item at_desk with other pending loans. when a librarian wants to # cancel one of the pending loans. the item remains at_desk params = { 'patron_pid': patron2_martigny_no_email.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid, 'pickup_location_pid': loc_public_martigny.pid } item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) assert requested_loan['state'] == LoanState.PENDING params = { 'pid': requested_loan.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid } item.cancel_item_request(**params) item = Item.get_record_by_pid(item.pid) loan = Loan.get_record_by_pid(loan.pid) requested_loan = Loan.get_record_by_pid(requested_loan.pid) assert item.status == ItemStatus.AT_DESK assert requested_loan['state'] == LoanState.CANCELLED assert loan['state'] == LoanState.ITEM_AT_DESK
def test_checkin_on_item_on_shelf_with_requests( item_on_shelf_martigny_patron_and_loan_pending, loc_public_martigny, librarian_martigny, patron2_martigny, loc_public_fully, lib_martigny): """Test checkin on an on_shelf item with requests.""" item, patron, loan = item_on_shelf_martigny_patron_and_loan_pending # the following tests the circulation action CHECKIN_1_2_1 # for an item on_shelf with pending loans, the pickup library of the first # pending loan equal to the transaction library, the first pending loan # is validated and item assigned the at_desk # validate_request circulation action will be performed. # create a second pending loan on same item params = { 'patron_pid': patron2_martigny.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny.pid, 'pickup_location_pid': loc_public_fully.pid } item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) assert requested_loan['state'] == LoanState.PENDING params = { 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny.pid } item, actions = item.checkin(**params) assert item.status == ItemStatus.AT_DESK assert Loan.get_record_by_pid(loan.pid)['state'] == LoanState.ITEM_AT_DESK assert Loan.get_record_by_pid(requested_loan.pid)['state'] == \ LoanState.PENDING
def test_validate_on_item_at_desk( item_at_desk_martigny_patron_and_loan_at_desk, loc_public_martigny, librarian_martigny, circulation_policies, patron2_martigny): """Test validate a request on an item at_desk.""" # the following tests the circulation action VALIDATE_2 # on at_desk item, the validation is not possible item, patron, loan = item_at_desk_martigny_patron_and_loan_at_desk params = { 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny.pid, 'pid': loan.pid } with pytest.raises(NoValidTransitionAvailableError): item, actions = item.validate_request(**params) assert item.status == ItemStatus.AT_DESK loan = Loan.get_record_by_pid(loan.pid) assert loan['state'] == LoanState.ITEM_AT_DESK # will not be able to validate any requestes for this item params = { 'patron_pid': patron2_martigny.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny.pid, 'pickup_location_pid': loc_public_martigny.pid } item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) params['pid'] = requested_loan.pid with pytest.raises(NoValidTransitionAvailableError): item, actions = item.validate_request(**params) requested_loan = Loan.get_record_by_pid(requested_loan.pid) assert requested_loan['state'] == LoanState.PENDING loan = Loan.get_record_by_pid(loan.pid) assert loan['state'] == LoanState.ITEM_AT_DESK
def test_loans_serializers(client, rero_json_header, patron_martigny, loc_public_martigny, loc_public_fully, librarian_martigny, item_lib_martigny, item_lib_fully, circulation_policies): """Test serializers for loans.""" login_user(client, patron_martigny) # create somes loans on same item with different state params = { 'patron_pid': patron_martigny.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny.pid, 'pickup_location_pid': loc_public_martigny.pid, } item_record_to_a_specific_loan_state(item=item_lib_martigny, loan_state=LoanState.PENDING, params=params, copy_item=True) item_record_to_a_specific_loan_state(item=item_lib_martigny, loan_state=LoanState.ITEM_AT_DESK, params=params, copy_item=True) item_record_to_a_specific_loan_state(item=item_lib_martigny, loan_state=LoanState.ITEM_ON_LOAN, params=params, copy_item=True) params = { 'patron_pid': patron_martigny.pid, 'transaction_location_pid': loc_public_fully.pid, 'transaction_user_pid': librarian_martigny.pid, 'pickup_location_pid': loc_public_fully.pid, 'checkin_transaction_location_pid': loc_public_martigny.pid } item_record_to_a_specific_loan_state( item=item_lib_fully, loan_state=LoanState.ITEM_IN_TRANSIT_TO_HOUSE, params=params, copy_item=True) list_url = url_for('invenio_records_rest.loanid_list') response = client.get(list_url, headers=rero_json_header) assert response.status_code == 200 data = get_json(response) records = data.get('hits', {}).get('hits', []) for record in records: data = record.get('metadata', {}) if data.get('state') == 'PENDING': assert data.get('pickup_name') elif data.get('state') == 'ITEM_AT_DESK': assert data.get('rank') == 0 elif data.get('state') == 'ITEM_ON_LOAN': assert data.get('overdue') is False elif data.get('state') == 'ITEM_IN_TRANSIT_TO_HOUSE': assert data.get('pickup_library_name') assert data.get('transaction_library_name')
def test_add_request_on_item_in_transit_to_house( item_in_transit_martigny_patron_and_loan_to_house, loc_public_martigny, librarian_martigny_no_email, patron2_martigny_no_email, loc_public_fully, patron4_martigny_no_email): """Test requests on an in_transit item to house.""" item, patron, loan = item_in_transit_martigny_patron_and_loan_to_house # the following tests the circulation action ADD_REQUEST_5_1 # any patron can add a new pending loan on an item with a loan equal to # ITEM_IN_TRANSIT_TO_HOUSE params = { 'patron_pid': patron2_martigny_no_email.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid, 'pickup_location_pid': loc_public_martigny.pid, 'checkin_transaction_location_pid': loc_public_fully.pid, } item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) assert loan['state'] == LoanState.ITEM_IN_TRANSIT_TO_HOUSE assert requested_loan['state'] == LoanState.PENDING # the following tests the circulation action ADD_REQUEST_5_2_1 # when a pending loan exist on an item with loan ITEM_IN_TRANSIT_TO_HOUSE # the patron who owns the pending loan can not add a new pending loan with pytest.raises(RecordCannotBeRequestedError): item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) # the following tests the circulation action ADD_REQUEST_5_2_2 # when a pending loan exist on an item with loan ITEM_IN_TRANSIT_TO_HOUSE, # any patron who does now own the pending loan can add a new pending loan. params['patron_pid'] = patron4_martigny_no_email.pid item, second_requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) assert loan['state'] == LoanState.ITEM_IN_TRANSIT_TO_HOUSE assert second_requested_loan['state'] == LoanState.PENDING assert requested_loan['state'] == LoanState.PENDING
def test_cancel_item_request_on_item_on_loan( client, item_on_loan_martigny_patron_and_loan_on_loan, loc_public_martigny, librarian_martigny_no_email, patron2_martigny_no_email): """Test cancel requests on an on_loan item.""" item, patron, loan = item_on_loan_martigny_patron_and_loan_on_loan # the following tests the circulation action CANCEL_REQUEST_3_1 # an item on_loan with no other pending loans. when a librarian wants to # cancel the on_loan loan. action is not permitted and item remain on_loan. params = { 'pid': loan.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid } with pytest.raises(NoCirculationAction): item.cancel_item_request(**params) item = Item.get_record_by_pid(item.pid) loan = Loan.get_record_by_pid(loan.pid) assert item.status == ItemStatus.ON_LOAN assert loan['state'] == LoanState.ITEM_ON_LOAN # the following tests the circulation action CANCEL_REQUEST_3_2 # an item on_loan with other pending loans. when a librarian wants to # cancel the pending loan. action is permitted and item remains on_loan. params = { 'patron_pid': patron2_martigny_no_email.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid, 'pickup_location_pid': loc_public_martigny.pid } item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) assert requested_loan['state'] == LoanState.PENDING params = { 'pid': requested_loan.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid } item.cancel_item_request(**params) item = Item.get_record_by_pid(item.pid) loan = Loan.get_record_by_pid(loan.pid) requested_loan = Loan.get_record_by_pid(requested_loan.pid) assert item.status == ItemStatus.ON_LOAN assert loan['state'] == LoanState.ITEM_ON_LOAN assert requested_loan['state'] == LoanState.CANCELLED
def item3_in_transit_martigny_patron_and_loan_for_pickup( app, librarian_martigny, loc_public_fully, item_lib_martigny, loc_public_martigny, patron_martigny, circulation_policies): """Creates an item in_transit for pickup. :return item: the created or copied item. :return patron: the patron requested the item. :return loan: the ITEM_IN_TRANSIT_FOR_PICKUP loan. """ params = { 'patron_pid': patron_martigny.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny.pid, 'pickup_location_pid': loc_public_fully.pid } item, loan = item_record_to_a_specific_loan_state( item=item_lib_martigny, loan_state=LoanState.ITEM_IN_TRANSIT_FOR_PICKUP, params=params, copy_item=True) return item, patron_martigny, loan
def item6_in_transit_martigny_patron_and_loan_to_house( app, librarian_martigny, loc_public_fully, item_lib_martigny, loc_public_martigny, patron_martigny, circulation_policies): """Creates an item in_transit to house. :return item: the created or copied item. :return patron: the patron who returned this item. :return loan: the ITEM_IN_TRANSIT_TO_HOUSE loan. """ params = { 'patron_pid': patron_martigny.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny.pid, 'pickup_location_pid': loc_public_martigny.pid, 'checkin_transaction_location_pid': loc_public_fully.pid, } item, loan = item_record_to_a_specific_loan_state( item=item_lib_martigny, loan_state=LoanState.ITEM_IN_TRANSIT_TO_HOUSE, params=params, copy_item=True) return item, patron_martigny, loan
def test_checkin_on_item_in_transit_to_house_with_requests( item3_in_transit_martigny_patron_and_loan_to_house, loc_public_martigny, librarian_martigny_no_email, patron2_martigny_no_email): """Test checkin on an in_transit item to house.""" item, patron, loan = item3_in_transit_martigny_patron_and_loan_to_house # the following tests the circulation action CHECKIN_5_2_1_1 # for an item in_transit (loan=IN_TRANSIT_TO_HOUSE) with pending requests. # when the pickup library of the first pending request does equal to the # transaction library. and pickup library equals to the item library, # will receive the item. # the item becomes at_desk and the loan is terminated. # and will validate the first pending loan params = { 'patron_pid': patron2_martigny_no_email.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid, 'pickup_location_pid': loc_public_martigny.pid } item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) assert requested_loan['state'] == LoanState.PENDING params = { 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid, 'pickup_location_pid': loc_public_martigny.pid } item, actions = item.checkin(**params) item = Item.get_record_by_pid(item.pid) loan = Loan.get_record_by_pid(loan.pid) requested_loan = Loan.get_record_by_pid(requested_loan.pid) assert item.status == ItemStatus.AT_DESK assert loan['state'] == LoanState.ITEM_RETURNED assert requested_loan['state'] == LoanState.ITEM_AT_DESK
def test_cancel_request_on_item_on_shelf( item_lib_martigny, item_on_shelf_martigny_patron_and_loan_pending, loc_public_martigny, librarian_martigny_no_email, patron2_martigny_no_email): """Test cancel request on an on_shelf item.""" # the following tests the circulation action CANCEL_REQUEST_1_1 # on_shelf item with no pending requests, not possible to cancel a request. # a loan pid is a required parameter and not given with pytest.raises(TypeError): item_lib_martigny.cancel_item_request() assert item_lib_martigny.status == ItemStatus.ON_SHELF # the following tests the circulation action CANCEL_REQUEST_1_2 # on_shelf item with pending requests, cancel a pending loan is possible. # the item remains on_shelf item, patron, loan = item_on_shelf_martigny_patron_and_loan_pending # add request for another patron params = { 'patron_pid': patron2_martigny_no_email.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid, 'pickup_location_pid': loc_public_martigny.pid } item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) # cancel request params = { 'pid': loan.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid } item.cancel_item_request(**params) item = Item.get_record_by_pid(item.pid) loan = Loan.get_record_by_pid(loan.pid) assert item.status == ItemStatus.ON_SHELF assert loan['state'] == LoanState.CANCELLED
def test_checkin_on_item_in_transit_to_house_with_external_loans_transit( item6_in_transit_martigny_patron_and_loan_to_house, loc_public_martigny, librarian_martigny_no_email, patron2_martigny_no_email, loc_public_saxon, loc_public_saillon): """Test checkin on an in_transit item to house.""" item, patron, loan = item6_in_transit_martigny_patron_and_loan_to_house # the following tests the circulation action CHECKIN_5_2_2_2 # for an item in_transit (loan=IN_TRANSIT_TO_HOUSE) with pending requests. # pickup location of first PENDING loan does not equal transaction library. # when the pickup library of the first pending request does not equal to # the to the item library, will cancel the current loan and then validate # the first pending request. item becomes in_transit and becomes # ITEM_IN_TRANSIT_FOR_PICKUP params = { 'patron_pid': patron2_martigny_no_email.pid, 'transaction_location_pid': loc_public_saxon.pid, 'transaction_user_pid': librarian_martigny_no_email.pid, 'pickup_location_pid': loc_public_saxon.pid } item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) assert requested_loan['state'] == LoanState.PENDING params = { 'transaction_location_pid': loc_public_saillon.pid, 'transaction_user_pid': librarian_martigny_no_email.pid } item, actions = item.checkin(**params) item = Item.get_record_by_pid(item.pid) loan = Loan.get_record_by_pid(loan.pid) requested_loan = Loan.get_record_by_pid(requested_loan.pid) assert item.status == ItemStatus.IN_TRANSIT assert loan['state'] == LoanState.CANCELLED assert requested_loan['state'] == LoanState.ITEM_IN_TRANSIT_FOR_PICKUP
def test_checkin_on_item_in_transit_to_house_with_external_loans( item5_in_transit_martigny_patron_and_loan_to_house, loc_public_martigny, librarian_martigny_no_email, patron2_martigny_no_email, loc_public_saxon): """Test checkin on an in_transit item to house.""" item, patron, loan = item5_in_transit_martigny_patron_and_loan_to_house # the following tests the circulation action CHECKIN_5_2_2_1 # for an item in_transit (loan=IN_TRANSIT_TO_HOUSE) with pending requests. # pickup location of first PENDING loan does not equal transaction library. # when the pickup library of the first pending request does equal to # the to the item library, no action performed. # the item remains at_desk params = { 'patron_pid': patron2_martigny_no_email.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid, 'pickup_location_pid': loc_public_martigny.pid } item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) assert requested_loan['state'] == LoanState.PENDING params = { 'transaction_location_pid': loc_public_saxon.pid, 'transaction_user_pid': librarian_martigny_no_email.pid } with pytest.raises(NoCirculationAction): item, actions = item.checkin(**params) item = Item.get_record_by_pid(item.pid) loan = Loan.get_record_by_pid(loan.pid) requested_loan = Loan.get_record_by_pid(requested_loan.pid) assert item.status == ItemStatus.IN_TRANSIT assert loan['state'] == LoanState.ITEM_IN_TRANSIT_TO_HOUSE assert requested_loan['state'] == LoanState.PENDING
def test_loan_can_extend(client, patron_martigny, item_lib_martigny, loc_public_martigny, librarian_martigny, circulation_policies, json_header): """Test is loan can extend.""" login_user(client, patron_martigny) params = { 'patron_pid': patron_martigny.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny.pid, 'pickup_location_pid': loc_public_martigny.pid, } item, loan = item_record_to_a_specific_loan_state( item=item_lib_martigny, loan_state=LoanState.ITEM_ON_LOAN, params=params, copy_item=True) list_url = url_for( 'api_loan.can_extend', loan_pid=loan.pid) response = client.get(list_url, headers=json_header) assert response.status_code == 200 assert get_json(response) == { 'can': False, 'reasons': ['Circulation policies disallows the operation.'] }
def item5_on_loan_martigny_patron_and_loan_on_loan(app, librarian_martigny, item_lib_martigny, loc_public_martigny, patron_martigny, circulation_policies): """Creates an item on_loan. :return item: the created or copied item. :return patron: the patron checked-out the item for. :return loan: the ITEM_ON_LOAN loan. """ params = { 'patron_pid': patron_martigny.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny.pid, 'pickup_location_pid': loc_public_martigny.pid } item, loan = item_record_to_a_specific_loan_state( item=item_lib_martigny, loan_state=LoanState.ITEM_ON_LOAN, params=params, copy_item=True) return item, patron_martigny, loan
def item5_at_desk_martigny_patron_and_loan_at_desk(app, librarian_martigny, item_lib_martigny, loc_public_martigny, patron_martigny, circulation_policies): """Creates an item with a validated pending request. :return item: the created or copied item. :return patron: the patron placed the request. :return loan: the validated pending loan. """ params = { 'patron_pid': patron_martigny.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny.pid, 'pickup_location_pid': loc_public_martigny.pid } item, loan = item_record_to_a_specific_loan_state( item=item_lib_martigny, loan_state=LoanState.ITEM_AT_DESK, params=params, copy_item=True) return item, patron_martigny, loan
def test_extend_on_item_on_loan_with_requests( item_on_loan_martigny_patron_and_loan_on_loan, loc_public_martigny, librarian_martigny_no_email, circulation_policies, patron2_martigny_no_email): """Test extend an on_loan item with requests.""" # the following tests the circulation action EXTEND_3_2 # for an on_loan item with requests, the extend action is not possible. item, patron, loan = item_on_loan_martigny_patron_and_loan_on_loan params = { 'patron_pid': patron2_martigny_no_email.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid, 'pickup_location_pid': loc_public_martigny.pid } item, requested_loan = item_record_to_a_specific_loan_state( item=item, loan_state=LoanState.PENDING, params=params, copy_item=False) # test fails if no loan pid is given params = { 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid } with pytest.raises(NoCirculationAction): item, actions = item.extend_loan(**params) assert item.status == ItemStatus.ON_LOAN # test fails if loan pid is given params = { 'pid': loan.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid } with pytest.raises(NoCirculationAction): item, actions = item.extend_loan(**params) assert item.status == ItemStatus.ON_LOAN
def test_reminder_notifications_after_extend( item_lib_martigny, patron_martigny, loc_public_martigny, librarian_martigny, circulation_policies, mailbox, client ): """Test any reminder notification could be resend after loan extension.""" # STEP 1 - CREATE BASIC RESOURCES FOR THE TEST # * Create a loan and update it to be considerate as "due soon". # * Run the `notification-creation` task to create a DUE_SOON # notification params = { 'patron_pid': patron_martigny.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny.pid, 'pickup_location_pid': loc_public_martigny.pid } item, loan = item_record_to_a_specific_loan_state( item=item_lib_martigny, loan_state=LoanState.ITEM_ON_LOAN, params=params, copy_item=True) # get the related cipo and check than an due_soon reminder exists cipo = get_circ_policy(loan) due_soon_reminder = cipo.get_reminder(DUE_SOON_REMINDER_TYPE) assert due_soon_reminder # Update the loan delay = due_soon_reminder.get('days_delay') - 1 due_soon_date = datetime.now() - timedelta(days=delay) end_date = datetime.now() + timedelta(days=1) loan['due_soon_date'] = due_soon_date.astimezone(pytz.utc).isoformat() loan['end_date'] = end_date.astimezone(pytz.utc).isoformat() loan = loan.update(loan, dbcommit=True, reindex=True) assert loan.is_loan_due_soon() # run the create notification task and process notification. mailbox.clear() create_notifications(types=[NotificationType.DUE_SOON]) process_notifications(NotificationType.DUE_SOON) first_notification = get_notification(loan, NotificationType.DUE_SOON) assert first_notification \ and first_notification['status'] == NotificationStatus.DONE assert len(mailbox) == 1 counter = NotificationsSearch()\ .filter('term', context__loan__pid=loan.pid)\ .filter('term', notification_type=NotificationType.DUE_SOON)\ .count() assert counter == 1 # STEP 2 - CHECK NOTIFICATIONS CREATION # Run the `create_notification` task for DUE_SOON notification type. # As a notification already exists, no new DUE_SOON#1 notifications # should be created create_notifications(types=[NotificationType.DUE_SOON]) query = NotificationsSearch() \ .filter('term', context__loan__pid=loan.pid) \ .filter('term', notification_type=NotificationType.DUE_SOON) \ .source('pid').scan() notification_pids = [hit.pid for hit in query] assert len(notification_pids) == 1 assert notification_pids[0] == first_notification.pid # STEP 3 - EXTEND THE LOAN # * User has received the DUE_SOON message and extend the loan. # * Get the new 'due_soon_date' it will be used later to create # notifications login_user_via_session(client, librarian_martigny.user) params = dict( item_pid=item.pid, transaction_user_pid=librarian_martigny.pid, transaction_location_pid=loc_public_martigny.pid ) res, _ = postdata(client, 'api_item.extend_loan', params) assert res.status_code == 200 loan = Loan.get_record_by_pid(loan.pid) due_soon_date = ciso8601.parse_datetime(loan.get('due_soon_date')) # STEP 4 - CHECK NOTIFICATIONS CREATION # Run again the `create_notification` task, again for DUE_SOON # notification type. As the loan is extended, a new DUE_SOON # notification should be created about this loan. # Process the notification, check that this new notification isn't # cancelled and well processed. process_date = due_soon_date + timedelta(days=1) create_notifications( types=[NotificationType.DUE_SOON], tstamp=process_date ) counter = NotificationsSearch() \ .filter('term', context__loan__pid=loan.pid) \ .filter('term', notification_type=NotificationType.DUE_SOON) \ .count() assert counter == 2 process_notifications(NotificationType.DUE_SOON) assert len(mailbox) == 2 second_notification = get_notification(loan, NotificationType.DUE_SOON) assert second_notification \ and second_notification['status'] == NotificationStatus.DONE assert second_notification.pid != first_notification
def test_loan_utils(client, patron_martigny_no_email, patron2_martigny_no_email, circulation_policies, item_lib_martigny, librarian_martigny_no_email, loc_public_martigny): """Test loan utils methods.""" loan_metadata = dict(item_lib_martigny) loan_metadata['item_pid'] = item_pid_to_object(item_lib_martigny.pid) if 'patron_pid' not in loan_metadata: loan_metadata['patron_pid'] = patron_martigny_no_email.pid # Create "virtual" Loan (not registered) loan = Loan(loan_metadata) # test that loan can successfully move to the pending state assert can_be_requested(loan) # test that loan without an item may not move to the pending state del loan['item_pid'] with pytest.raises(Exception): assert can_be_requested(loan) # test a pending loan will be attached at the right organisation and # will not be considered as an active loan params = { 'patron_pid': patron2_martigny_no_email.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid, 'pickup_location_pid': loc_public_martigny.pid } item, loan_pending_martigny = item_record_to_a_specific_loan_state( item=item_lib_martigny, loan_state=LoanState.PENDING, params=params, copy_item=True) assert loan_pending_martigny.patron_pid == patron2_martigny_no_email.pid assert not loan_pending_martigny.is_active assert loan_pending_martigny.organisation_pid # test required parameters for get_loans_by_patron_pid with pytest.raises(TypeError): assert get_loans_by_patron_pid() assert get_loans_by_patron_pid(patron2_martigny_no_email.pid) # test required parameters for get_last_transaction_loc_for_item with pytest.raises(TypeError): assert get_last_transaction_loc_for_item() # test the organisation of the loan is based on the item new_loan = deepcopy(loan_pending_martigny) assert new_loan.organisation_pid del new_loan['item_pid'] with pytest.raises(IlsRecordError.PidDoesNotExist): new_loan.organisation_pid assert not can_be_requested(loan_pending_martigny) # test the allow request at the location level loc_public_martigny['allow_request'] = False loc_public_martigny.update( loc_public_martigny, dbcommit=True, reindex=True ) flush_index(LocationsSearch.Meta.index) new_loan = { 'patron_pid': patron_martigny_no_email.pid, 'transaction_location_pid': loc_public_martigny.pid, 'transaction_user_pid': librarian_martigny_no_email.pid, 'pickup_location_pid': loc_public_martigny.pid } with pytest.raises(RecordCannotBeRequestedError): item, loan_pending_martigny = item_record_to_a_specific_loan_state( item=item_lib_martigny, loan_state=LoanState.PENDING, params=params, copy_item=True) loc_public_martigny['allow_request'] = True loc_public_martigny.update( loc_public_martigny, dbcommit=True, reindex=True ) flush_index(LocationsSearch.Meta.index) item, loan_pending_martigny = item_record_to_a_specific_loan_state( item=item_lib_martigny, loan_state=LoanState.PENDING, params=params, copy_item=True) assert loan_pending_martigny['state'] == LoanState.PENDING