def loans():
    """Load test data fixture."""
    record_uuid = uuid.uuid4()
    new_loan = {}
    pid = loan_pid_minter(record_uuid, data=new_loan)
    Loan.create(data=new_loan, id_=record_uuid)
    db.session.commit()
    click.secho("Loan #{} created.".format(pid.pid_value), fg="green")
Exemple #2
0
def loans():
    """Load test data fixture."""
    record_uuid = uuid.uuid4()
    new_loan = {}
    pid = loan_pid_minter(record_uuid, data=new_loan)
    Loan.create(data=new_loan, id_=record_uuid)
    db.session.commit()
    click.secho("Loan #{} created.".format(pid.pid_value), fg="green")
def test_get_loans(indexed_loans):
    """Test retrive loan list given belonging to an item."""
    loans = list(Loan.get_loans(item_pid='item_pending_1'))
    assert loans
    assert len(loans) == 1
    assert loans[0].get('item_pid') == 'item_pending_1'

    loans = list(
        Loan.get_loans(
            item_pid='item_multiple_pending_on_loan_7',
            exclude_states=['ITEM_ON_LOAN'],
        ))
    assert len(loans) == 2
Exemple #4
0
def test_search_loans_by_pid(indexed_loans):
    """Test retrieve loan list belonging to an item."""
    item_pid = dict(type="itemid", value="item_pending_1")
    loans = list(search_by_pid(item_pid=item_pid).scan())
    assert len(loans) == 1
    loan = Loan.get_record_by_pid(loans[0]["pid"])
    assert loan["item_pid"] == item_pid
Exemple #5
0
def test_loan_patron_resolver_for_anonymous_patron_pid(app, testdata):
    """Test item resolving from loan."""
    loan_pid = testdata["loans"][7]["pid"]
    loan = Loan.get_record_by_pid(loan_pid)
    loan = loan.replace_refs()
    assert loan["patron_pid"] == loan["patron"]["id"]
    assert loan["patron"]["name"] == "anonymous"
Exemple #6
0
def request_loan(document_pid, patron_pid, transaction_location_pid,
                 transaction_user_pid, **kwargs):
    """Create a new loan and trigger the first transition to PENDING."""
    if patron_has_request_on_document(patron_pid=patron_pid,
                                      document_pid=document_pid):
        raise PatronHasRequestOnDocumentError(patron_pid, document_pid)
    _validate_delivery(kwargs.get("delivery"))

    # create a new loan
    record_uuid = uuid.uuid4()
    new_loan = dict(
        patron_pid=patron_pid,
        transaction_location_pid=transaction_location_pid,
        transaction_user_pid=transaction_user_pid,
    )
    pid = loan_pid_minter(record_uuid, data=new_loan)
    loan = Loan.create(data=new_loan, id_=record_uuid)

    params = deepcopy(loan)
    params.update(document_pid=document_pid, **kwargs)

    # trigger the transition to request
    loan = current_circulation.circulation.trigger(
        loan, **dict(params, trigger="request"))

    return pid, loan
Exemple #7
0
def request_loan(params):
    """Create a loan and trigger the first transition to create a request."""
    if "patron_pid" not in params:
        raise MissingRequiredParameterError(
            description="'patron_pid' is required on loan request")
    if "document_pid" not in params:
        raise MissingRequiredParameterError(
            description="'document_pid' is required on loan request")

    if patron_has_active_loan_on_item(patron_pid=params["patron_pid"],
                                      document_pid=params["document_pid"]):
        raise PatronHasLoanOnDocumentError(params["patron_pid"],
                                           params["document_pid"])

    # create a new loan
    record_uuid = uuid.uuid4()
    new_loan = {
        "document_pid": params["document_pid"],
        "patron_pid": params["patron_pid"],
    }

    pid = loan_pid_minter(record_uuid, data=new_loan)
    loan = Loan.create(data=new_loan, id_=record_uuid)

    # trigger the first transition
    loan = current_circulation.circulation.trigger(
        loan, **dict(params, trigger="request"))

    return pid, loan
Exemple #8
0
def create_loan(params):
    """Create a loan for behalf of a user."""
    if "patron_pid" not in params or "item_pid" not in params:
        raise CirculationException(
            "Patron or item not defined on loan request.")

    if patron_has_active_loan_on_item(patron_pid=params["patron_pid"],
                                      item_pid=params["item_pid"]):
        raise CirculationException(
            "Patron has already a request or active loan on this item.")

    if "document_pid" not in params:
        document_pid = circulation_document_retriever(params["item_pid"])
        if document_pid:
            params["document_pid"] = document_pid
    # create a new loan
    record_uuid = uuid.uuid4()
    new_loan = {}
    pid = loan_pid_minter(record_uuid, data=new_loan)
    loan = Loan.create(data=new_loan, id_=record_uuid)
    # trigger the first transition
    loan = current_circulation.circulation.trigger(
        loan, **dict(params, trigger="checkout"))

    return pid, loan
Exemple #9
0
def create_loan(params, should_force_checkout):
    """Create a loan on behalf of a user."""
    if "patron_pid" not in params:
        raise MissingRequiredParameterError(
            description="'patron_pid' is required when creating a loan")
    if "item_pid" not in params:
        raise MissingRequiredParameterError(
            description="'item_pid' is required when creating a loan")

    if patron_has_active_loan_on_item(patron_pid=params["patron_pid"],
                                      item_pid=params["item_pid"]):
        raise PatronHasLoanOnItemError(params["patron_pid"],
                                       params["item_pid"])

    if "document_pid" not in params:
        document_pid = Item.get_document_pid(params["item_pid"])
        if document_pid:
            params["document_pid"] = document_pid

    if should_force_checkout:
        _ensure_item_can_circulate(params['item_pid'])

    # create a new loan
    record_uuid = uuid.uuid4()
    new_loan = {"item_pid": params["item_pid"]}
    pid = loan_pid_minter(record_uuid, data=new_loan)
    loan = Loan.create(data=new_loan, id_=record_uuid)
    # trigger the first transition
    loan = current_circulation.circulation.trigger(
        loan, **dict(params, trigger="checkout"))

    return pid, loan
Exemple #10
0
def test_api_automatic_loan_valid_action(app, db, json_headers, params):
    """Test API valid action on loan."""
    loan = Loan.create({})
    minted_loan = loan_pid_minter(loan.id, loan)
    loan = current_circulation.circulation.trigger(
        loan,
        **dict(params,
               trigger='request',
               pickup_location_pid='pickup_location_pid'))
    db.session.commit()
    assert loan['state'] == 'PENDING'

    app.config[
        'CIRCULATION_ITEM_LOCATION_RETRIEVER'] = lambda x: 'pickup_location_pid'

    loan_pid = loan_pid_fetcher(loan.id, loan)
    assert minted_loan.pid_value == loan_pid.pid_value

    with app.test_client() as client:
        url = url_for('invenio_circulation.loan_pid_actions',
                      pid_value=loan_pid.pid_value,
                      action='next')
        res = client.post(url, headers=json_headers, data=json.dumps(params))

        assert res.status_code == HTTP_CODES['accepted']
        loan_dict = json.loads(res.data.decode('utf-8'))
        assert loan_dict['metadata']['state'] == 'ITEM_AT_DESK'
Exemple #11
0
def checkout_loan(item_pid,
                  patron_pid,
                  transaction_location_pid,
                  transaction_user_pid,
                  force=False,
                  **kwargs):
    """Create a new loan and trigger the first transition to ITEM_ON_LOAN."""
    if patron_has_active_loan_on_item(patron_pid=patron_pid,
                                      item_pid=item_pid):
        raise PatronHasLoanOnItemError(patron_pid, item_pid)
    optional_delivery = kwargs.get("delivery")
    if optional_delivery:
        _validate_delivery(optional_delivery)

    if force:
        _set_item_to_can_circulate(item_pid)

    # create a new loan
    record_uuid = uuid.uuid4()
    new_loan = dict(
        patron_pid=patron_pid,
        transaction_location_pid=transaction_location_pid,
        transaction_user_pid=transaction_user_pid,
    )
    pid = loan_pid_minter(record_uuid, data=new_loan)
    loan = Loan.create(data=new_loan, id_=record_uuid)

    params = deepcopy(loan)
    params.update(item_pid=item_pid, **kwargs)

    # trigger the transition to request
    loan = current_circulation.circulation.trigger(
        loan, **dict(params, trigger="checkout"))

    return pid, loan
def test_send_only_to_test_recipients(app, users, testdata, mocker):
    """Tests that send only to test recipients works."""
    class TestMessage(BlockTemplatedMessage):
        def __init__(self, *args, **kwargs):
            template = "tests/subject_body_html.html"
            kwargs.pop("trigger", None)
            kwargs.pop("message_ctx", {})
            kwargs.setdefault("sender", app.config["MAIL_NOTIFY_SENDER"])
            super().__init__(template, **kwargs)

    app.config.update(
        dict(CELERY_TASK_ALWAYS_EAGER=True,
             ILS_MAIL_LOAN_MSG_LOADER=TestMessage,
             ILS_MAIL_ENABLE_TEST_RECIPIENTS=True))
    patron = Patron(users["patron1"].id)
    mocker.patch(
        "invenio_app_ils.records.api.Patron.get_patron",
        return_value=patron,
    )
    loan_data = testdata["loans"][-1]
    loan = Loan.get_record_by_pid(loan_data["pid"])
    fake_recipients = app.config["ILS_MAIL_NOTIFY_TEST_RECIPIENTS"]
    with app.extensions["mail"].record_messages() as outbox:
        assert len(outbox) == 0
        send_loan_mail("trigger", loan, subject="Test", body="Test")
        assert len(outbox) == 1
        assert outbox[0].recipients == fake_recipients
Exemple #13
0
def test_api_loan_invalid_action(app, db, json_headers, params,
                                 mock_is_item_available):
    """Test API invalid action on loan."""
    loan = Loan.create({})
    minted_loan = loan_pid_minter(loan.id, loan)

    loan = current_circulation.circulation.trigger(
        loan,
        **dict(params,
               trigger='request',
               pickup_location_pid='pickup_location_pid'))
    db.session.commit()
    assert loan['state'] == 'PENDING'

    loan_pid = loan_pid_fetcher(loan.id, loan)
    assert minted_loan.pid_value == loan_pid.pid_value

    with app.test_client() as client:
        url = url_for('invenio_circulation.loan_pid_actions',
                      pid_value=loan_pid.pid_value,
                      action='checkout')
        res = client.post(url, headers=json_headers, data=json.dumps(params))
        assert res.status_code == HTTP_CODES['method_not_allowed']
        error_dict = json.loads(res.data.decode('utf-8'))
        assert 'message' in error_dict
Exemple #14
0
def test_loan_extend_permissions(app, client, json_headers, users, testdata,
                                 app_config, loan_params, user,
                                 expected_resp_code):
    """Test loan can be extended."""
    # Create a Loan for patron with pid 1
    login_user(users["librarian"])
    loan_data = testdata["loans"][0]
    loan = Loan.get_record_by_pid(loan_data["pid"])

    current_circulation.circulation.trigger(
        loan, **dict(loan_params, trigger="checkout"))

    user_login(client, "librarian", users)
    resp = client.get(url_for("invenio_records_rest.loanid_item",
                              pid_value=loan["pid"]),
                      headers=json_headers)
    loan = resp.get_json()

    # Remove payload params that break the request
    del loan_params["item_pid"]
    del loan_params["transaction_date"]
    extend_url = loan.get("links").get("actions").get("extend")

    user_login(client, user, users)
    extend_res = client.post(extend_url,
                             headers=json_headers,
                             data=json.dumps(loan_params))
    assert extend_res.status_code == expected_resp_code
Exemple #15
0
def create_loan(data):
    """Create a test record."""
    with db.session.begin_nested():
        data = copy.deepcopy(data)
        rec_uuid = uuid.uuid4()
        pid = current_pidstore.minters['loanid'](rec_uuid, data)
        record = Loan.create(data, id_=rec_uuid)
        return pid, record
def create_loan(data):
    """Create a test record."""
    with db.session.begin_nested():
        data = copy.deepcopy(data)
        rec_uuid = uuid.uuid4()
        pid = current_pidstore.minters[CIRCULATION_LOAN_MINTER](rec_uuid, data)
        record = Loan.create(data, id_=rec_uuid)
        return pid, record
Exemple #17
0
def loan_created(app):
    """Minimal Loan object."""
    record_uuid = uuid.uuid4()
    new_loan = {}
    loan_pid_minter(record_uuid, data=new_loan)
    loan = Loan.create(data=new_loan, id_=record_uuid)
    db.session.commit()
    yield loan
def loan_created(app):
    """Minimal Loan object."""
    record_uuid = uuid.uuid4()
    new_loan = {}
    loan_pid_minter(record_uuid, data=new_loan)
    loan = Loan.create(data=new_loan, id_=record_uuid)
    db.session.commit()
    yield loan
Exemple #19
0
def testdata(app, db, es_clear):
    """Create, index and return test data."""
    indexer = RecordIndexer()

    locations = load_json_from_datadir("locations.json")
    for location in locations:
        record = Location.create(location)
        mint_record_pid(LOCATION_PID_TYPE, Location.pid_field, record)
        record.commit()
        db.session.commit()
        indexer.index(record)

    internal_locations = load_json_from_datadir("internal_locations.json")
    iloc_records = []
    for internal_location in internal_locations:
        record = InternalLocation.create(internal_location)
        mint_record_pid(INTERNAL_LOCATION_PID_TYPE, InternalLocation.pid_field,
                        record)
        record.commit()
        iloc_records.append(record)
        db.session.commit()
        indexer.index(record)

    documents = load_json_from_datadir("documents.json")
    for doc in documents:
        record = Document.create(doc)
        mint_record_pid(DOCUMENT_PID_TYPE, Document.pid_field, record)
        record.commit()
        db.session.commit()
        indexer.index(record)

    items = load_json_from_datadir("items.json")
    for item in items:
        record = Item.create(item)
        mint_record_pid(ITEM_PID_TYPE, Item.pid_field, record)
        record.commit()
        db.session.commit()
        indexer.index(record)

    loans = load_json_from_datadir("loans.json")
    for loan in loans:
        record = Loan.create(loan)
        mint_record_pid(CIRCULATION_LOAN_PID_TYPE, Loan.pid_field, record)
        record.commit()
        db.session.commit()
        indexer.index(record)
        # re-index item attached to the loan
        index_record_after_loan_change(app, record)

    # flush all indices after indexing, otherwise ES won't be ready for tests
    current_search.flush_and_refresh(index=None)

    return {
        "locations": locations,
        "documents": documents,
        "items": items,
        "loans": loans,
    }
def check_user_activity(app, user_pid, client, json_headers):
    """ Check if there are records related to the user. """
    # wait ES refresh
    current_search.flush_and_refresh(index="*")

    AnonymousPatron = app.config["ILS_PATRON_ANONYMOUS_CLASS"]
    anonymous_patron_fields = AnonymousPatron().dumps_loader()

    loans = get_loans_by_patron_pid(user_pid).scan()
    for hit in loans:
        # test ES
        assert hit["patron"] == anonymous_patron_fields
        # test DB
        loan = Loan.get_record_by_pid(hit.pid)
        assert loan["patron"] == anonymous_patron_fields
        # test REST
        url = url_for("invenio_records_rest.loanid_item", pid_value=hit.pid)
        res = client.get(url, headers=json_headers)
        assert res.get_json()["metadata"]["patron"] == anonymous_patron_fields

    borrowing_requests = (
        BorrowingRequestsSearch().search_by_patron_pid(user_pid).scan())
    for hit in borrowing_requests:
        # test ES
        assert hit["patron"] == anonymous_patron_fields
        # test DB
        borrowing_request = BorrowingRequest.get_record_by_pid(hit.pid)
        assert borrowing_request["patron"] == anonymous_patron_fields
        # test REST
        url = url_for("invenio_records_rest.illbid_item", pid_value=hit.pid)
        res = client.get(url, headers=json_headers)
        assert res.get_json()["metadata"]["patron"] == anonymous_patron_fields

    document_requests = (
        DocumentRequestSearch().search_by_patron_pid(user_pid).scan())
    for hit in document_requests:
        # test ES
        assert hit["patron"] == anonymous_patron_fields
        # test DB
        document_request = DocumentRequest.get_record_by_pid(hit.pid)
        assert document_request["patron"] == anonymous_patron_fields
        # test REST
        url = url_for("invenio_records_rest.dreqid_item", pid_value=hit.pid)
        res = client.get(url, headers=json_headers)
        assert res.get_json()["metadata"]["patron"] == anonymous_patron_fields

    acquisitions = OrderSearch().search_by_patron_pid(user_pid).scan()
    for hit in acquisitions:
        # test ES
        assert hit["patron"] == anonymous_patron_fields
        # test DB
        acquisition = Order.get_record_by_pid(hit.pid)
        assert acquisition["patron"] == anonymous_patron_fields
        # test REST
        url = url_for("invenio_records_rest.acqoid_item", pid_value=hit.pid)
        res = client.get(url, headers=json_headers)
        assert res.get_json()["metadata"]["patron"] == anonymous_patron_fields
Exemple #21
0
def test_loan_patron_resolver_for_non_existing_patron_pid(app, testdata):
    """Test item resolving from loan."""
    AnonymousPatron = app.config["ILS_PATRON_ANONYMOUS_CLASS"]
    loan_pid = testdata["loans"][6]["pid"]
    loan = Loan.get_record_by_pid(loan_pid)
    loan = loan.replace_refs()
    assert loan["patron_pid"] == str(20)
    assert loan["patron"]["id"] == str(AnonymousPatron.id)
    assert loan["patron"]["name"] == "anonymous"
Exemple #22
0
 def persist(self):
     """Persist."""
     recs = []
     for obj in self.holder.loans["objs"]:
         rec = self._persist(CIRCULATION_LOAN_PID_TYPE, "pid",
                             Loan.create(obj))
         recs.append(rec)
     db.session.commit()
     return recs
Exemple #23
0
def test_document_requests_on_item_returned(mock_available_item,
                                            mock_pending_loans_for_document,
                                            mock_is_item_available,
                                            loan_created, db, params):
    """Test loan request action."""

    # return item is not available
    mock_available_item.return_value = False

    with SwappedConfig('CIRCULATION_DOCUMENT_RETRIEVER_FROM_ITEM',
                       lambda x: 'document_pid'):
        same_location = params['transaction_location_pid']
        with SwappedConfig('CIRCULATION_ITEM_LOCATION_RETRIEVER',
                           lambda x: same_location):
            # start a loan on item with pid 'item_pid'
            new_loan = current_circulation.circulation.trigger(
                loan_created, **dict(params,
                                     trigger='checkout',
                                     item_pid='item_pid',
                                     pickup_location_pid='pickup_location_pid')
            )
            db.session.commit()
            assert new_loan['state'] == 'ITEM_ON_LOAN'

            # create a new loan request on document_pid without items available
            new_loan_created = Loan.create({})
            # remove item_pid
            params.pop('item_pid')
            pending_loan = current_circulation.circulation.trigger(
                new_loan_created,
                **dict(params, trigger='request',
                       document_pid='document_pid',
                       pickup_location_pid='pickup_location_pid')
            )
            db.session.commit()
            assert pending_loan['state'] == 'PENDING'
            # no item available found. Request is created with no item attached
            assert 'item_pid' not in pending_loan
            assert pending_loan['document_pid'] == 'document_pid'

            # resolve pending document requests to `document_pid`
            mock_pending_loans_for_document.return_value = [pending_loan]

            returned_loan = current_circulation.circulation.trigger(
                new_loan, **dict(params,
                                 item_pid='item_pid',
                                 pickup_location_pid='pickup_location_pid')
            )
            db.session.commit()
            assert returned_loan['state'] == 'ITEM_RETURNED'

            # item `item_pid` has been attached to pending loan request on
            # `document_pid` automatically
            assert pending_loan['state'] == 'PENDING'
            assert pending_loan['item_pid'] == 'item_pid'
            assert pending_loan['document_pid'] == 'document_pid'
Exemple #24
0
def index_loans_after_item_indexed(item_pid):
    """Index loan to refresh item reference."""
    loan_search = search_by_pid(item_pid=item_pid)
    loan_ids = []
    for loan in loan_search.scan():
        record = Loan.get_record_by_pid(loan[Loan.pid_field])
        if record:
            loan_ids.append(record.id)

    RecordIndexer().bulk_index(loan_ids)
Exemple #25
0
def test_state_checkout_with_loan_pid(db, params, loan_schema):
    """."""
    data = {}
    data.update({'loan_pid': 'loan_pid'})
    data.update(loan_schema)
    loan_created = Loan.create(data)

    new_params = deepcopy(params)
    new_params['trigger'] = 'checkout'
    loan = current_circulation.circulation.trigger(loan_created, **new_params)
    loan.validate()
Exemple #26
0
def test_email_on_loan_checkout(client, app_with_mail, users, testdata,
                                loan_params):
    """Test that an email is sent on loan checkout."""
    loan_data = testdata["loans"][1]
    loan = Loan.get_record_by_pid(loan_data["pid"])
    with app_with_mail.extensions["mail"].record_messages() as outbox:
        user_login(client, "admin", users)

        assert len(outbox) == 0
        current_circulation.circulation.trigger(
            loan, **dict(loan_params, trigger="checkout"))
        assert len(outbox) == 1
        msg = outbox[0]

    doc = Document.get_record_by_pid(loan_data["document_pid"])
    expected_subject = (
        """InvenioILS: your loan for "{0}" has started.""".format(
            doc["title"]))
    assert msg.subject == expected_subject

    edition_year = " ({edition} - {year})".format(edition=doc["edition"],
                                                  year=doc["publication_year"])
    full_title = "{title}, {author}{edition_year}".format(
        title=doc["title"],
        author=doc["authors"][0]["full_name"],
        edition_year=edition_year,
    )

    literature_url = "{host}{path}".format(
        host=current_app.config["SPA_HOST"],
        path=current_app.config["SPA_PATHS"]["literature"] %
        {"pid": doc["pid"]},
    )
    profile_url = "{host}{path}".format(
        host=current_app.config["SPA_HOST"],
        path=current_app.config["SPA_PATHS"]["profile"],
    )
    expected_body_plain = """Dear Patron One,

your loan for "{doc_full_title}" <{literature_url}> has started.

The due date is {loan_end_date}.

You can see your ongoing and past loans in your profile page <{profile_url}>.

Kind regards,
InvenioILS""".format(
        doc_full_title=full_title,
        literature_url=literature_url,
        loan_end_date=loan_data["end_date"],
        profile_url=profile_url,
    )
    assert msg.body == expected_body_plain
Exemple #27
0
def test_email_on_loan_checkout(client, app_with_mail, users, testdata,
                                loan_params):
    """Test that an email is sent when an admin performs a loan checkout."""
    loan_data = testdata["loans"][1]
    loan = Loan.get_record_by_pid(loan_data["pid"])
    with app_with_mail.extensions["mail"].record_messages() as outbox:
        user_login(client, "admin", users)

        assert len(outbox) == 0
        current_circulation.circulation.trigger(
            loan, **dict(loan_params, trigger="checkout"))
        assert len(outbox) == 1
def test_loan_request(db, params):
    """Test loan request action."""
    loan = Loan.create({})
    assert loan['state'] == 'CREATED'

    loan = current_circulation.circulation.trigger(
        loan,
        **dict(params,
               trigger='request',
               pickup_location_pid='pickup_location_pid'))
    db.session.commit()
    assert loan['state'] == 'PENDING'
def checkout_loan(
    item_pid,
    patron_pid,
    transaction_location_pid,
    transaction_user_pid=None,
    force=False,
    **kwargs
):
    """Create a new loan and trigger the first transition to ITEM_ON_LOAN.

    :param item_pid: a dict containing `value` and `type` fields to
        uniquely identify the item.
    :param patron_pid: the PID value of the patron
    :param transaction_location_pid: the PID value of the location where the
        checkout is performed
    :param transaction_user_pid: the PID value of the user that performed the
        checkout
    :param force: if True, ignore the current status of the item and do perform
        the checkout. If False, the checkout will fail when the item cannot
        circulate.
    """
    if patron_has_active_loan_on_item(
        patron_pid=patron_pid, item_pid=item_pid
    ):
        raise PatronHasLoanOnItemError(patron_pid, item_pid)
    optional_delivery = kwargs.get("delivery")
    if optional_delivery:
        _validate_delivery(optional_delivery)

    if force:
        _set_item_to_can_circulate(item_pid)

    transaction_user_pid = transaction_user_pid or str(current_user.id)

    # create a new loan
    record_uuid = uuid.uuid4()
    new_loan = dict(
        patron_pid=patron_pid,
        transaction_location_pid=transaction_location_pid,
        transaction_user_pid=transaction_user_pid,
    )
    pid = ils_circulation_loan_pid_minter(record_uuid, data=new_loan)
    loan = Loan.create(data=new_loan, id_=record_uuid)

    params = deepcopy(loan)
    params.update(item_pid=item_pid, **kwargs)

    # trigger the transition to request
    loan = current_circulation.circulation.trigger(
        loan, **dict(params, trigger="checkout")
    )

    return pid, loan
Exemple #30
0
def testdata(app, db, es_clear, system_user):
    """Create, index and return test data."""
    indexer = RecordIndexer()

    locations = load_json_from_datadir("locations.json")
    for location in locations:
        record = Location.create(location)
        mint_record_pid(LOCATION_PID_TYPE, "pid", record)
        record.commit()
        db.session.commit()
        indexer.index(record)

    internal_locations = load_json_from_datadir("internal_locations.json")
    for internal_location in internal_locations:
        record = InternalLocation.create(internal_location)
        mint_record_pid(INTERNAL_LOCATION_PID_TYPE, "pid", record)
        record.commit()
        db.session.commit()
        indexer.index(record)

    documents = load_json_from_datadir("documents.json")
    for doc in documents:
        record = Document.create(doc)
        mint_record_pid(DOCUMENT_PID_TYPE, "pid", record)
        record.commit()
        db.session.commit()
        indexer.index(record)

    items = load_json_from_datadir("items.json")
    for item in items:
        record = Item.create(item)
        mint_record_pid(ITEM_PID_TYPE, "pid", record)
        record.commit()
        db.session.commit()
        indexer.index(record)

    loans = load_json_from_datadir("loans.json")
    for loan in loans:
        record = Loan.create(loan)
        mint_record_pid(CIRCULATION_LOAN_PID_TYPE, "pid", record)
        record.commit()
        db.session.commit()
        indexer.index(record)

    # flush all indices after indexing, otherwise ES won't be ready for tests
    current_search.flush_and_refresh(index='*')
    return {
        "locations": locations,
        "documents": documents,
        "items": items,
        "loans": loans,
    }
def test_email_on_overdue_loan(app, users, testdata, mocker):
    """Test that an email is sent when an admin performs a loan checkout."""
    app.config.update(CELERY_TASK_ALWAYS_EAGER=True)
    mocker.patch(
        "invenio_app_ils.records.api.Patron.get_patron",
        return_value=Patron(users["patron1"].id),
    )
    loan_data = testdata["loans"][-1]
    loan = Loan.get_record_by_pid(loan_data["pid"])
    with app.extensions["mail"].record_messages() as outbox:
        assert len(outbox) == 0
        send_loan_overdue_reminder_mail(loan)
        assert len(outbox) == 1
def test_email_on_loan_checkout(app, users, testdata, loan_params, mocker):
    """Test that an email is sent when an admin performs a loan checkout."""
    app.config.update(CELERY_TASK_ALWAYS_EAGER=True)

    loan_data = testdata["loans"][1]
    loan = Loan.get_record_by_pid(loan_data["pid"])
    with app.extensions["mail"].record_messages() as outbox:
        admin = users["admin"]
        login_user(admin)

        assert len(outbox) == 0
        current_circulation.circulation.trigger(
            loan, **dict(loan_params, trigger="checkout"))
        assert len(outbox) == 1
def test_search_loans_by_pid(indexed_loans):
    """Test retrieve loan list given belonging to an item."""
    loans = list(LoansSearch.search_loans_by_pid(item_pid='item_pending_1'))
    assert loans
    assert len(loans) == 1
    loan = Loan.get_record_by_pid(loans[0]['loan_pid'])
    assert loan.get('item_pid') == 'item_pending_1'

    loans = list(
        LoansSearch.search_loans_by_pid(
            item_pid='item_multiple_pending_on_loan_7',
            exclude_states=['ITEM_ON_LOAN'],
        ))
    assert len(loans) == 2
def test_document_requests_on_item_returned(
    mock_available_item, mock_pending_loans_for_document,
    mock_is_item_available_for_checkout, loan_created, db, params
):
    """Test loan request action."""
    # return item is not available
    mock_available_item.return_value = False

    with SwappedConfig(
        "CIRCULATION_DOCUMENT_RETRIEVER_FROM_ITEM", lambda x: "document_pid"
    ):
        same_location = params["transaction_location_pid"]
        with SwappedConfig(
            "CIRCULATION_ITEM_LOCATION_RETRIEVER", lambda x: same_location
        ):
            # start a loan on item with pid 'item_pid'
            new_loan = current_circulation.circulation.trigger(
                loan_created,
                **dict(
                    params,
                    trigger="checkout",
                    item_pid="item_pid",
                    pickup_location_pid="pickup_location_pid",
                )
            )
            db.session.commit()
            assert new_loan["state"] == "ITEM_ON_LOAN"

            # create a new loan request on document_pid without items available
            new_loan_created = Loan.create({
                Loan.pid_field: "2"
            })
            # remove item_pid
            params.pop("item_pid")
            pending_loan = current_circulation.circulation.trigger(
                new_loan_created,
                **dict(
                    params,
                    trigger="request",
                    document_pid="document_pid",
                    pickup_location_pid="pickup_location_pid",
                )
            )
            db.session.commit()
            assert pending_loan["state"] == "PENDING"
            # no item available found. Request is created with no item attached
            assert "item_pid" not in pending_loan
            assert pending_loan["document_pid"] == "document_pid"

            # resolve pending document requests to `document_pid`
            mock_pending_loans_for_document.return_value = [pending_loan]

            returned_loan = current_circulation.circulation.trigger(
                new_loan,
                **dict(
                    params,
                    item_pid="item_pid",
                    pickup_location_pid="pickup_location_pid",
                )
            )
            db.session.commit()
            assert returned_loan["state"] == "ITEM_RETURNED"

            # item `item_pid` has been attached to pending loan request on
            # `document_pid` automatically
            assert pending_loan["state"] == "PENDING"
            assert pending_loan["item_pid"] == "item_pid"
            assert pending_loan["document_pid"] == "document_pid"
def test_search_loans_by_pid(indexed_loans):
    """Test retrieve loan list belonging to an item."""
    loans = list(search_by_pid(item_pid="item_pending_1").scan())
    assert len(loans) == 1
    loan = Loan.get_record_by_pid(loans[0][Loan.pid_field])
    assert loan.get("item_pid") == "item_pending_1"