def test_remote_authenticate_no_password(self):

        integration = self._external_integration(self._str)
        p = SIP2AuthenticationProvider
        integration.setting(p.PASSWORD_KEYBOARD).value = p.NULL_KEYBOARD
        client = MockSIPClient()
        auth = SIP2AuthenticationProvider(self._default_library,
                                          integration,
                                          client=client)

        # This Evergreen instance doesn't use passwords.
        client.queue_response(self.evergreen_active_user)
        client.queue_response(self.end_session_response)
        patrondata = auth.remote_authenticate("user", None)
        eq_("12345", patrondata.authorization_identifier)
        eq_("863715", patrondata.permanent_id)
        eq_("Booth Active Test", patrondata.personal_name)
        eq_(0, patrondata.fines)
        eq_(datetime(2019, 10, 4), patrondata.authorization_expires)
        eq_("Adult", patrondata.external_type)

        # If a password is specified, it is not sent over the wire.
        client.queue_response(self.evergreen_active_user)
        client.queue_response(self.end_session_response)
        patrondata = auth.remote_authenticate("user2", "some password")
        eq_("12345", patrondata.authorization_identifier)
        request = client.requests[-1]
        assert 'user2' in request
        assert 'some password' not in request
Ejemplo n.º 2
0
    def test_info_to_patrondata_no_validate_password(self):
        integration = self._external_integration(self._str)
        integration.url = 'server.local'
        client = MockSIPClient()
        provider = SIP2AuthenticationProvider(
            self._default_library, integration, client=client
        )

        # Test with valid login, should return PatronData
        info = client.patron_information_parser(TestSIP2AuthenticationProvider.sierra_valid_login)
        patron = provider.info_to_patrondata(info, validate_password=False)
        eq_(patron.__class__, PatronData)
        eq_("12345", patron.authorization_identifier)
        eq_("*****@*****.**", patron.email_address)
        eq_("SHELDON, ALICE", patron.personal_name)
        eq_(0, patron.fines)
        eq_(None, patron.authorization_expires)
        eq_(None, patron.external_type)
        eq_(PatronData.NO_VALUE, patron.block_reason)

        # Test with invalid login, should return PatronData
        info = client.patron_information_parser(TestSIP2AuthenticationProvider.sierra_invalid_login)
        patron = provider.info_to_patrondata(info, validate_password=False)
        eq_(patron.__class__, PatronData)
        eq_("12345", patron.authorization_identifier)
        eq_("*****@*****.**", patron.email_address)
        eq_("SHELDON, ALICE", patron.personal_name)
        eq_(0, patron.fines)
        eq_(None, patron.authorization_expires)
        eq_(None, patron.external_type)
        eq_('no borrowing privileges', patron.block_reason)
Ejemplo n.º 3
0
    def test_login_failure_interrupts_other_request(self):
        sip = MockSIPClient(login_user_id="user_id", login_password="******")
        sip.queue_response("940")

        # We don't even get a chance to make the patron information request
        # because our login attempt fails.
        pytest.raises(IOError, sip.patron_information, "patron_identifier")
Ejemplo n.º 4
0
    def test_login_failure_interrupts_other_request(self):
        sip = MockSIPClient('user_id', 'password')
        sip.queue_response('940')

        # We don't even get a chance to make the patron information request
        # because our login attempt fails.
        assert_raises(IOError,  sip.patron_information, 'patron_identifier')
Ejemplo n.º 5
0
    def test_login_failure_interrupts_other_request(self):
        sip = MockSIPClient('user_id', 'password')
        sip.queue_response('940')

        # We don't even get a chance to make the patron information request
        # because our login attempt fails.
        assert_raises(IOError, sip.patron_information, 'patron_identifier')
Ejemplo n.º 6
0
 def test_different_separator(self):
     """When you create the SIPClient you get to specify which character
     to use as the field separator.
     """
     sip = MockSIPClient(separator='^')
     sip.queue_response("64Y                201610050000114734                        AOnypl ^AA240^AENo Name^BLN^AFYour library card number cannot be located.^AY1AZC9DE")
     response = sip.patron_information('identifier')
     eq_('240', response['patron_identifier'])
Ejemplo n.º 7
0
 def test_different_separator(self):
     """When you create the SIPClient you get to specify which character
     to use as the field separator.
     """
     sip = MockSIPClient(separator="^")
     sip.queue_response(
         "64Y                201610050000114734                        AOnypl ^AA240^AENo Name^BLN^AFYour library card number cannot be located.^AY1AZC9DE"
     )
     response = sip.patron_information("identifier")
     assert "240" == response["patron_identifier"]
Ejemplo n.º 8
0
    def test_login_happens_implicitly_when_user_id_and_password_specified(
            self):
        sip = MockSIPClient('user_id', 'password')
        # We're not logged in, and we must log in before sending a real
        # message.
        eq_(False, sip.logged_in)
        eq_(True, sip.must_log_in)

        sip.queue_response('941')
        sip.queue_response(
            '64Y                201610050000114734                        AOnypl |AA12345|AENo Name|BLN|AFYour library card number cannot be located.  Please see a staff member for assistance.|AY1AZC9DE'
        )
        response = sip.patron_information('patron_identifier')

        # Two requests were made.
        eq_(2, len(sip.requests))
        eq_(2, sip.sequence_number)

        # We're logged in.
        eq_(True, sip.logged_in)

        # We ended up with the right data.
        eq_('12345', response['patron_identifier'])

        # If we reset the connection, we stop being logged in.
        sip.connect()
        eq_(False, sip.logged_in)
        eq_(0, sip.sequence_number)
Ejemplo n.º 9
0
    def test_sequence_number_increment(self):
        sip = MockSIPClient(login_user_id="user_id", login_password="******")
        sip.sequence_number = 0
        sip.queue_response("941")
        response = sip.login()
        assert 1 == sip.sequence_number

        # Test wraparound from 9 to 0
        sip.sequence_number = 9
        sip.queue_response("941")
        response = sip.login()
        assert 0 == sip.sequence_number
Ejemplo n.º 10
0
    def test_sequence_number_increment(self):
        sip = MockSIPClient()
        sip.sequence_number = 0
        sip.queue_response('941')
        response = sip.login('user_id', 'password')
        eq_(1, sip.sequence_number)

        # Test wraparound from 9 to 0
        sip.sequence_number = 9
        sip.queue_response('941')
        response = sip.login('user_id', 'password')
        eq_(0, sip.sequence_number)
Ejemplo n.º 11
0
    def test_login_does_not_happen_implicitly_when_user_id_and_password_not_specified(self):
        sip = MockSIPClient()

        # We're implicitly logged in.
        eq_(False, sip.must_log_in)

        sip.queue_response('64Y                201610050000114734                        AOnypl |AA12345|AENo Name|BLN|AFYour library card number cannot be located.  Please see a staff member for assistance.|AY1AZC9DE')
        response = sip.patron_information('patron_identifier')

        # One request was made.
        eq_(1, len(sip.requests))
        eq_(1, sip.sequence_number)

        # We ended up with the right data.
        eq_('12345', response['patron_identifier'])
Ejemplo n.º 12
0
    def test_sequence_number_increment(self):
        sip = MockSIPClient(login_user_id='user_id', login_password='******')
        sip.sequence_number=0
        sip.queue_response('941')
        response = sip.login()
        eq_(1, sip.sequence_number)

        # Test wraparound from 9 to 0
        sip.sequence_number=9
        sip.queue_response('941')
        response = sip.login()
        eq_(0, sip.sequence_number)
Ejemplo n.º 13
0
    def test_login_does_not_happen_implicitly_when_user_id_and_password_not_specified(
            self):
        sip = MockSIPClient()

        # We're implicitly logged in.
        eq_(False, sip.must_log_in)

        sip.queue_response(
            '64Y                201610050000114734                        AOnypl |AA12345|AENo Name|BLN|AFYour library card number cannot be located.  Please see a staff member for assistance.|AY1AZC9DE'
        )
        response = sip.patron_information('patron_identifier')

        # One request was made.
        eq_(1, len(sip.requests))
        eq_(1, sip.sequence_number)

        # We ended up with the right data.
        eq_('12345', response['patron_identifier'])
Ejemplo n.º 14
0
    def test_login_does_not_happen_implicitly_when_user_id_and_password_not_specified(
        self, ):
        sip = MockSIPClient()

        # We're implicitly logged in.
        assert False == sip.must_log_in

        sip.queue_response(
            "64Y                201610050000114734                        AOnypl |AA12345|AENo Name|BLN|AFYour library card number cannot be located.  Please see a staff member for assistance.|AY1AZC9DE"
        )
        response = sip.patron_information("patron_identifier")

        # One request was made.
        assert 1 == len(sip.requests)
        assert 1 == sip.sequence_number

        # We ended up with the right data.
        assert "12345" == response["patron_identifier"]
Ejemplo n.º 15
0
    def test_maximum_resend(self):
        sip = MockSIPClient(login_user_id='user_id', login_password='******')

        # We will keep sending retry messages until we reach the maximum
        sip.queue_response('96')
        sip.queue_response('96')
        sip.queue_response('96')
        sip.queue_response('96')
        sip.queue_response('96')

        # After reaching the maximum the client should give an IOError
        assert_raises(IOError, sip.login)

        # We should send as many requests as we are allowed retries
        eq_(sip.MAXIMUM_RETRIES, len(sip.requests))
Ejemplo n.º 16
0
    def test_maximum_resend(self):
        sip = MockSIPClient(login_user_id="user_id", login_password="******")

        # We will keep sending retry messages until we reach the maximum
        sip.queue_response("96")
        sip.queue_response("96")
        sip.queue_response("96")
        sip.queue_response("96")
        sip.queue_response("96")

        # After reaching the maximum the client should give an IOError
        pytest.raises(IOError, sip.login)

        # We should send as many requests as we are allowed retries
        assert sip.MAXIMUM_RETRIES == len(sip.requests)
    def test_patron_block_setting(self):
        integration_block = self._external_integration(
            self._str,
            settings={SIP2AuthenticationProvider.PATRON_STATUS_BLOCK: "true"})
        integration_noblock = self._external_integration(
            self._str,
            settings={SIP2AuthenticationProvider.PATRON_STATUS_BLOCK: "false"})
        client = MockSIPClient()

        # Test with blocked patron, block should be set
        p = SIP2AuthenticationProvider(self._default_library,
                                       integration_block,
                                       client=client)
        info = client.patron_information_parser(
            TestSIP2AuthenticationProvider.evergreen_expired_card)
        patron = p.info_to_patrondata(info)
        eq_(patron.__class__, PatronData)
        eq_("12345", patron.authorization_identifier)
        eq_("863716", patron.permanent_id)
        eq_("Booth Expired Test", patron.personal_name)
        eq_(0, patron.fines)
        eq_(datetime(2008, 9, 7), patron.authorization_expires)
        eq_(PatronData.NO_BORROWING_PRIVILEGES, patron.block_reason)

        # Test with blocked patron, block should not be set
        p = SIP2AuthenticationProvider(self._default_library,
                                       integration_noblock,
                                       client=client)
        info = client.patron_information_parser(
            TestSIP2AuthenticationProvider.evergreen_expired_card)
        patron = p.info_to_patrondata(info)
        eq_(patron.__class__, PatronData)
        eq_("12345", patron.authorization_identifier)
        eq_("863716", patron.permanent_id)
        eq_("Booth Expired Test", patron.personal_name)
        eq_(0, patron.fines)
        eq_(datetime(2008, 9, 7), patron.authorization_expires)
        eq_(PatronData.NO_VALUE, patron.block_reason)
Ejemplo n.º 18
0
    def test_login_happens_when_user_id_and_password_specified(self):
        sip = MockSIPClient(login_user_id="user_id", login_password="******")
        # We're not logged in, and we must log in before sending a real
        # message.
        assert True == sip.must_log_in

        sip.queue_response("941")
        sip.queue_response(
            "64Y                201610050000114734                        AOnypl |AA12345|AENo Name|BLN|AFYour library card number cannot be located.  Please see a staff member for assistance.|AY1AZC9DE"
        )
        sip.login()
        response = sip.patron_information("patron_identifier")

        # Two requests were made.
        assert 2 == len(sip.requests)
        assert 2 == sip.sequence_number

        # We ended up with the right data.
        assert "12345" == response["patron_identifier"]
Ejemplo n.º 19
0
    def test_variant_encoding(self):
        response_unicode = "64              000201610210000142637000000000000000000000000AOnypl |AA12345|AELE CARRÉ, JOHN|BZ0030|CA0050|CB0050|BLY|CQY|BV0|CC15.00|[email protected]|AY1AZD1B7\r"

        # By default, we expect data from a SIP2 server to be encoded
        # as CP850.
        assert "cp850" == self.sip.encoding
        self.sip.queue_response(response_unicode.encode("cp850"))
        response = self.sip.patron_information("identifier")
        assert "LE CARRÉ, JOHN" == response["personal_name"]

        # But a SIP2 server may send some other encoding, such as
        # UTF-8. This can cause odd results if the circulation manager
        # tries to parse the data as CP850.
        self.sip.queue_response(response_unicode.encode("utf-8"))
        response = self.sip.patron_information("identifier")
        assert "LE CARRÉ, JOHN" == response["personal_name"]

        # Giving SIPClient the right encoding means the data is
        # converted correctly.
        sip = MockSIPClient(encoding="utf-8")
        assert "utf-8" == sip.encoding
        sip.queue_response(response_unicode.encode("utf-8"))
        response = sip.patron_information("identifier")
        assert "LE CARRÉ, JOHN" == response["personal_name"]
Ejemplo n.º 20
0
    def test_login_happens_when_user_id_and_password_specified(self):
        sip = MockSIPClient('user_id', 'password')
        # We're not logged in, and we must log in before sending a real
        # message.
        eq_(True, sip.must_log_in)

        sip.queue_response('941')
        sip.queue_response('64Y                201610050000114734                        AOnypl |AA12345|AENo Name|BLN|AFYour library card number cannot be located.  Please see a staff member for assistance.|AY1AZC9DE')
        sip.login()
        response = sip.patron_information('patron_identifier')

        # Two requests were made.
        eq_(2, len(sip.requests))
        eq_(2, sip.sequence_number)

        # We ended up with the right data.
        eq_('12345', response['patron_identifier'])
Ejemplo n.º 21
0
    def test_resend(self):
        sip = MockSIPClient()
        # The first response will be a request to resend the original message.
        sip.queue_response('96')
        # The second response will indicate a successful login.
        sip.queue_response('941')

        response = sip.login('user_id', 'password')

        # We made two requests for a single login command.
        req1, req2 = sip.requests
        # The first request includes a sequence ID field, "AY", with
        # the value "0".
        eq_('9300CNuser_id|COpassword|AY0AZF556\r', req1)

        # The second request does not include a sequence ID field. As
        # a consequence its checksum is different.
        eq_('9300CNuser_id|COpassword|AZF620\r', req2)

        # The login request eventually succeeded.
        eq_({'login_ok': '1', '_status': '94'}, response)
Ejemplo n.º 22
0
    def test_resend(self):
        sip = MockSIPClient(login_user_id="user_id", login_password="******")
        # The first response will be a request to resend the original message.
        sip.queue_response("96")
        # The second response will indicate a successful login.
        sip.queue_response("941")

        response = sip.login()

        # We made two requests for a single login command.
        req1, req2 = sip.requests
        # The first request includes a sequence ID field, "AY", with
        # the value "0".
        assert b"9300CNuser_id|COpassword|AY0AZF556\r" == req1

        # The second request does not include a sequence ID field. As
        # a consequence its checksum is different.
        assert b"9300CNuser_id|COpassword|AZF620\r" == req2

        # The login request eventually succeeded.
        assert {"login_ok": "1", "_status": "94"} == response
Ejemplo n.º 23
0
class TestClientDialects(object):
    def setup_method(self):
        self.sip = MockSIPClient()

    def test_generic_dialect(self):
        # Generic ILS should send end_session message
        self.sip.dialect = GenericILS
        self.sip.queue_response(
            "36Y201610210000142637AO3|AA25891000331441|AF|AG")
        self.sip.end_session("username", "password")
        assert self.sip.read_count == 1
        assert self.sip.write_count == 1

    def test_ag_dialect(self):
        # AG VERSO ILS shouldn't end_session message
        self.sip.dialect = AutoGraphicsVerso
        self.sip.end_session("username", "password")
        assert self.sip.read_count == 0
        assert self.sip.write_count == 0
Ejemplo n.º 24
0
    def test_resend(self):
        sip = MockSIPClient(login_user_id='user_id', login_password='******')
        # The first response will be a request to resend the original message.
        sip.queue_response('96')
        # The second response will indicate a successful login.
        sip.queue_response('941')

        response = sip.login()

        # We made two requests for a single login command.
        req1, req2 = sip.requests
        # The first request includes a sequence ID field, "AY", with
        # the value "0".
        eq_('9300CNuser_id|COpassword|AY0AZF556\r', req1)

        # The second request does not include a sequence ID field. As
        # a consequence its checksum is different.
        eq_('9300CNuser_id|COpassword|AZF620\r', req2)

        # The login request eventually succeeded.
        eq_({'login_ok': '1', '_status': '94'}, response)
Ejemplo n.º 25
0
class TestPatronResponse(object):
    def setup_method(self):
        self.sip = MockSIPClient()

    def test_incorrect_card_number(self):
        self.sip.queue_response(
            "64Y                201610050000114734                        AOnypl |AA240|AENo Name|BLN|AFYour library card number cannot be located.|AY1AZC9DE"
        )
        response = self.sip.patron_information("identifier")

        # Test some of the basic fields.
        assert response["institution_id"] == "nypl "
        assert response["personal_name"] == "No Name"
        assert response["screen_message"] == [
            "Your library card number cannot be located."
        ]
        assert response["valid_patron"] == "N"
        assert response["patron_status"] == "Y             "
        parsed = response["patron_status_parsed"]
        assert True == parsed["charge privileges denied"]
        assert False == parsed["too many items charged"]

    def test_hold_items(self):
        "A patron has multiple items on hold."
        self.sip.queue_response(
            "64              000201610050000114837000300020002000000000000AOnypl |AA233|AEBAR, FOO|BZ0030|CA0050|CB0050|BLY|CQY|BV0|CC15.00|AS123|AS456|AS789|[email protected]|AY1AZC848"
        )
        response = self.sip.patron_information("identifier")
        assert "0003" == response["hold_items_count"]
        assert ["123", "456", "789"] == response["hold_items"]

    def test_multiple_screen_messages(self):
        self.sip.queue_response(
            "64Y  YYYYYYYYYYY000201610050000115040000000000000000000000000AOnypl |AA233|AESHELDON, ALICE|BZ0030|CA0050|CB0050|BLY|CQN|BV0|CC15.00|AFInvalid PIN entered.  Please try again or see a staff member for assistance.|AFThere are unresolved issues with your account.  Please see a staff member for assistance.|AY2AZ9B64"
        )
        response = self.sip.patron_information("identifier")
        assert 2 == len(response["screen_message"])

    def test_extension_field_captured(self):
        """This SIP2 message includes an extension field with the code XI."""
        self.sip.queue_response(
            "64  Y           00020161005    122942000000000000000000000000AA240|AEBooth Active Test|BHUSD|BDAdult Circ Desk 1 Newtown, CT USA 06470|AQNEWTWN|BLY|CQN|PA20191004|PCAdult|PIAllowed|XI86371|AOBiblioTest|ZZfoo|AY2AZ0000"
        )
        response = self.sip.patron_information("identifier")

        # The Evergreen XI field is a known extension and is picked up
        # as sipserver_internal_id.
        assert "86371" == response["sipserver_internal_id"]

        # The ZZ field is an unknown extension and is captured under
        # its SIP code.
        assert ["foo"] == response["ZZ"]

    def test_variant_encoding(self):
        response_unicode = "64              000201610210000142637000000000000000000000000AOnypl |AA12345|AELE CARRÉ, JOHN|BZ0030|CA0050|CB0050|BLY|CQY|BV0|CC15.00|[email protected]|AY1AZD1B7\r"

        # By default, we expect data from a SIP2 server to be encoded
        # as CP850.
        assert "cp850" == self.sip.encoding
        self.sip.queue_response(response_unicode.encode("cp850"))
        response = self.sip.patron_information("identifier")
        assert "LE CARRÉ, JOHN" == response["personal_name"]

        # But a SIP2 server may send some other encoding, such as
        # UTF-8. This can cause odd results if the circulation manager
        # tries to parse the data as CP850.
        self.sip.queue_response(response_unicode.encode("utf-8"))
        response = self.sip.patron_information("identifier")
        assert "LE CARRÉ, JOHN" == response["personal_name"]

        # Giving SIPClient the right encoding means the data is
        # converted correctly.
        sip = MockSIPClient(encoding="utf-8")
        assert "utf-8" == sip.encoding
        sip.queue_response(response_unicode.encode("utf-8"))
        response = sip.patron_information("identifier")
        assert "LE CARRÉ, JOHN" == response["personal_name"]

    def test_embedded_pipe(self):
        """In most cases we can handle data even if it contains embedded
        instances of the separator character.
        """
        self.sip.queue_response(
            "64              000201610050000134405000000000000000000000000AOnypl |AA12345|AERICHARDSON, LEONARD|BZ0030|CA0050|CB0050|BLY|CQY|BV0|CC15.00|BEleona|rdr@|bar.com|AY1AZD1BB\r"
        )
        response = self.sip.patron_information("identifier")
        assert "leona|rdr@|bar.com" == response["email_address"]

    def test_different_separator(self):
        """When you create the SIPClient you get to specify which character
        to use as the field separator.
        """
        sip = MockSIPClient(separator="^")
        sip.queue_response(
            "64Y                201610050000114734                        AOnypl ^AA240^AENo Name^BLN^AFYour library card number cannot be located.^AY1AZC9DE"
        )
        response = sip.patron_information("identifier")
        assert "240" == response["patron_identifier"]

    def test_location_code_is_optional(self):
        """You can specify a location_code when logging in, or not."""
        without_code = self.sip.login_message("login_id", "login_password")
        assert without_code.endswith("COlogin_password")
        with_code = self.sip.login_message("login_id", "login_password",
                                           "location_code")
        assert with_code.endswith("COlogin_password|CPlocation_code")

    def test_institution_id_field_is_always_provided(self):
        without_institution_arg = self.sip.patron_information_request(
            "patron_identifier", "patron_password")
        assert without_institution_arg.startswith("AO|", 33)

    def test_institution_id_field_value_provided(self):
        # Fake value retrieved from DB
        sip = MockSIPClient(institution_id="MAIN")
        with_institution_provided = sip.patron_information_request(
            "patron_identifier", "patron_password")
        assert with_institution_provided.startswith("AOMAIN|", 33)

    def test_patron_password_is_optional(self):
        without_password = self.sip.patron_information_request(
            "patron_identifier")
        assert without_password.endswith("AApatron_identifier|AC")
        with_password = self.sip.patron_information_request(
            "patron_identifier", "patron_password")
        assert with_password.endswith(
            "AApatron_identifier|AC|ADpatron_password")

    def test_parse_patron_status(self):
        m = MockSIPClient.parse_patron_status
        pytest.raises(ValueError, m, None)
        pytest.raises(ValueError, m, "")
        pytest.raises(ValueError, m, " " * 20)
        parsed = m("Y Y Y Y Y Y Y ")
        for yes in [
                "charge privileges denied",
                #'renewal privileges denied',
                "recall privileges denied",
                #'hold privileges denied',
                "card reported lost",
                #'too many items charged',
                "too many items overdue",
                #'too many renewals',
                "too many claims of items returned",
                #'too many items lost',
                "excessive outstanding fines",
                #'excessive outstanding fees',
                "recall overdue",
                #'too many items billed',
        ]:
            assert parsed[yes] == True

        for no in [
                #'charge privileges denied',
                "renewal privileges denied",
                #'recall privileges denied',
                "hold privileges denied",
                #'card reported lost',
                "too many items charged",
                #'too many items overdue',
                "too many renewals",
                #'too many claims of items returned',
                "too many items lost",
                #'excessive outstanding fines',
                "excessive outstanding fees",
                #'recall overdue',
                "too many items billed",
        ]:
            assert parsed[no] == False
Ejemplo n.º 26
0
 def setup(self):
     self.sip = MockSIPClient()
Ejemplo n.º 27
0
 def test_login_password_is_optional(self):
     """You can specify a login_id without specifying a login_password."""
     sip = MockSIPClient('user_id')
     sip.queue_response('941')
     response = sip.login()
     eq_({'login_ok': '1', '_status': '94'}, response)
Ejemplo n.º 28
0
 def test_login_success(self):
     sip = MockSIPClient('user_id', 'password')
     sip.queue_response('941')
     response = sip.login()
     eq_({'login_ok': '1', '_status': '94'}, response)
Ejemplo n.º 29
0
 def test_login_failure(self):
     sip = MockSIPClient(login_user_id="user_id", login_password="******")
     sip.queue_response("940")
     pytest.raises(IOError, sip.login)
Ejemplo n.º 30
0
 def test_login_password_is_optional(self):
     """You can specify a login_id without specifying a login_password."""
     sip = MockSIPClient(login_user_id="user_id")
     sip.queue_response("941")
     response = sip.login()
     assert {"login_ok": "1", "_status": "94"} == response
Ejemplo n.º 31
0
 def setup_method(self):
     self.sip = MockSIPClient()
Ejemplo n.º 32
0
 def test_institution_id_field_value_provided(self):
     # Fake value retrieved from DB
     sip = MockSIPClient(institution_id="MAIN")
     with_institution_provided = sip.patron_information_request(
         "patron_identifier", "patron_password")
     assert with_institution_provided.startswith("AOMAIN|", 33)
Ejemplo n.º 33
0
 def test_append_checksum(self):
     sip = MockSIPClient()
     sip.sequence_number = 7
     data = "some data"
     new_data = sip.append_checksum(data)
     assert "some data|AY7AZFAAA" == new_data
Ejemplo n.º 34
0
 def test_login_message(self):
     sip = MockSIPClient()
     message = sip.login_message("user_id", "password")
     assert "9300CNuser_id|COpassword" == message
Ejemplo n.º 35
0
 def test_login_success(self):
     sip = MockSIPClient()
     sip.queue_response('941')
     response = sip.login('user_id', 'password')
     eq_({'login_ok': '1', '_status': '94'}, response)
Ejemplo n.º 36
0
 def test_login_failure(self):
     sip = MockSIPClient('user_id', 'password')
     sip.queue_response('940')
     assert_raises(IOError, sip.login)
Ejemplo n.º 37
0
class TestPatronResponse(object):

    def setup(self):
        self.sip = MockSIPClient()

    def test_incorrect_card_number(self):
        self.sip.queue_response("64Y                201610050000114734                        AOnypl |AA240|AENo Name|BLN|AFYour library card number cannot be located.|AY1AZC9DE")
        response = self.sip.patron_information('identifier')

        # Test some of the basic fields.
        eq_(response['institution_id'], 'nypl ')
        eq_(response['personal_name'], 'No Name')
        eq_(response['screen_message'], ['Your library card number cannot be located.'])
        eq_(response['valid_patron'], 'N')
        eq_(response['patron_status'], 'Y             ')
        parsed = response['patron_status_parsed']
        eq_(True, parsed['charge privileges denied'])
        eq_(False, parsed['too many items charged'])

    def test_hold_items(self):
        "A patron has multiple items on hold."
        self.sip.queue_response("64              000201610050000114837000300020002000000000000AOnypl |AA233|AEBAR, FOO|BZ0030|CA0050|CB0050|BLY|CQY|BV0|CC15.00|AS123|AS456|AS789|[email protected]|AY1AZC848")
        response = self.sip.patron_information('identifier')
        eq_('0003', response['hold_items_count'])
        eq_(['123', '456', '789'], response['hold_items'])

    def test_multiple_screen_messages(self):
        self.sip.queue_response("64Y  YYYYYYYYYYY000201610050000115040000000000000000000000000AOnypl |AA233|AESHELDON, ALICE|BZ0030|CA0050|CB0050|BLY|CQN|BV0|CC15.00|AFInvalid PIN entered.  Please try again or see a staff member for assistance.|AFThere are unresolved issues with your account.  Please see a staff member for assistance.|AY2AZ9B64")
        response = self.sip.patron_information('identifier')
        eq_(2, len(response['screen_message']))

    def test_extension_field_captured(self):
        """This SIP2 message includes an extension field with the code XI.
        """
        self.sip.queue_response("64  Y           00020161005    122942000000000000000000000000AA240|AEBooth Active Test|BHUSD|BDAdult Circ Desk 1 Newtown, CT USA 06470|AQNEWTWN|BLY|CQN|PA20191004|PCAdult|PIAllowed|XI86371|AOBiblioTest|ZZfoo|AY2AZ0000")
        response = self.sip.patron_information('identifier')

        # The Evergreen XI field is a known extension and is picked up
        # as sipserver_internal_id.
        eq_("86371", response['sipserver_internal_id'])

        # The ZZ field is an unknown extension and is captured under
        # its SIP code.
        eq_(["foo"], response['ZZ'])

    def test_embedded_pipe(self):
        """In most cases we can handle data even if it contains embedded
        instances of the separator character.
        """
        self.sip.queue_response('64              000201610050000134405000000000000000000000000AOnypl |AA12345|AERICHARDSON, LEONARD|BZ0030|CA0050|CB0050|BLY|CQY|BV0|CC15.00|BEleona|rdr@|bar.com|AY1AZD1BB\r')
        response = self.sip.patron_information('identifier')
        eq_("leona|rdr@|bar.com", response['email_address'])

    def test_different_separator(self):
        """When you create the SIPClient you get to specify which character
        to use as the field separator.
        """
        sip = MockSIPClient(separator='^')
        sip.queue_response("64Y                201610050000114734                        AOnypl ^AA240^AENo Name^BLN^AFYour library card number cannot be located.^AY1AZC9DE")
        response = sip.patron_information('identifier')
        eq_('240', response['patron_identifier'])

    def test_location_code_is_optional(self):
        """You can specify a location_code when logging in, or not."""
        without_code = self.sip.login_message(
            "login_id", "login_password"
        )
        assert without_code.endswith("COlogin_password")
        with_code = self.sip.login_message(
            "login_id", "login_password", "location_code"
        )
        assert with_code.endswith("COlogin_password|CPlocation_code")

    def test_patron_password_is_optional(self):
        without_password = self.sip.patron_information_request(
            "patron_identifier"
        )
        assert without_password.endswith('AApatron_identifier|AC')
        with_password = self.sip.patron_information_request(
            "patron_identifier", "patron_password"
        )
        assert with_password.endswith(
            'AApatron_identifier|AC|ADpatron_password'
        )

    def test_parse_patron_status(self):
        m = MockSIPClient.parse_patron_status
        assert_raises(ValueError, m, None)
        assert_raises(ValueError, m, "")
        assert_raises(ValueError, m, " " * 20)
        parsed = m("Y Y Y Y Y Y Y ")
        for yes in [
                'charge privileges denied',
                #'renewal privileges denied',
                'recall privileges denied',
                #'hold privileges denied',
                'card reported lost',
                #'too many items charged',
                'too many items overdue',
                #'too many renewals',
                'too many claims of items returned',
                #'too many items lost',
                'excessive outstanding fines',
                #'excessive outstanding fees',
                'recall overdue',
                #'too many items billed',
        ]:
            eq_(parsed[yes], True)

        for no in [
                #'charge privileges denied',
                'renewal privileges denied',
                #'recall privileges denied',
                'hold privileges denied',
                #'card reported lost',
                'too many items charged',
                #'too many items overdue',
                'too many renewals',
                #'too many claims of items returned',
                'too many items lost',
                #'excessive outstanding fines',
                'excessive outstanding fees',
                #'recall overdue',
                'too many items billed',
        ]:
            eq_(parsed[no], False)
Ejemplo n.º 38
0
 def test_login_success(self):
     sip = MockSIPClient(login_user_id="user_id", login_password="******")
     sip.queue_response("941")
     response = sip.login()
     assert {"login_ok": "1", "_status": "94"} == response
Ejemplo n.º 39
0
 def test_append_checksum(self):
     sip = MockSIPClient()
     sip.sequence_number=7
     data = "some data"
     new_data = sip.append_checksum(data)
     eq_("some data|AY7AZFAAA", new_data)
Ejemplo n.º 40
0
class TestPatronResponse(object):
    def setup(self):
        self.sip = MockSIPClient()

    def test_incorrect_card_number(self):
        self.sip.queue_response(
            "64Y                201610050000114734                        AOnypl |AA240|AENo Name|BLN|AFYour library card number cannot be located.|AY1AZC9DE"
        )
        response = self.sip.patron_information('identifier')

        # Test some of the basic fields.
        response['institution_id'] = 'nypl '
        response['peronal_name'] = 'No Name'
        response['screen_message'] = [
            'Your library card number cannot be located.'
        ]
        response['valid_patron'] = 'N'
        response['patron_status'] = 'Y             '

    def test_hold_items(self):
        "A patron has multiple items on hold."
        self.sip.queue_response(
            "64              000201610050000114837000300020002000000000000AOnypl |AA233|AEBAR, FOO|BZ0030|CA0050|CB0050|BLY|CQY|BV0|CC15.00|AS123|AS456|AS789|[email protected]|AY1AZC848"
        )
        response = self.sip.patron_information('identifier')
        eq_('0003', response['hold_items_count'])
        eq_(['123', '456', '789'], response['hold_items'])

    def test_multiple_screen_messages(self):
        self.sip.queue_response(
            "64Y  YYYYYYYYYYY000201610050000115040000000000000000000000000AOnypl |AA233|AESHELDON, ALICE|BZ0030|CA0050|CB0050|BLY|CQN|BV0|CC15.00|AFInvalid PIN entered.  Please try again or see a staff member for assistance.|AFThere are unresolved issues with your account.  Please see a staff member for assistance.|AY2AZ9B64"
        )
        response = self.sip.patron_information('identifier')
        eq_(2, len(response['screen_message']))

    def test_extension_field_captured(self):
        """This SIP2 message includes an extension field with the code XI.
        """
        self.sip.queue_response(
            "64  Y           00020161005    122942000000000000000000000000AA240|AEBooth Active Test|BHUSD|BDAdult Circ Desk 1 Newtown, CT USA 06470|AQNEWTWN|BLY|CQN|PA20191004|PCAdult|PIAllowed|XI86371|AOBiblioTest|ZZfoo|AY2AZ0000"
        )
        response = self.sip.patron_information('identifier')

        # The Evergreen XI field is a known extension and is picked up
        # as sipserver_internal_id.
        eq_("86371", response['sipserver_internal_id'])

        # The ZZ field is an unknown extension and is captured under
        # its SIP code.
        eq_(["foo"], response['ZZ'])

    def test_embedded_pipe(self):
        """In most cases we can handle data even if it contains embedded
        instances of the separator character.
        """
        self.sip.queue_response(
            '64              000201610050000134405000000000000000000000000AOnypl |AA12345|AERICHARDSON, LEONARD|BZ0030|CA0050|CB0050|BLY|CQY|BV0|CC15.00|BEleona|rdr@|bar.com|AY1AZD1BB\r'
        )
        response = self.sip.patron_information('identifier')
        eq_("leona|rdr@|bar.com", response['email_address'])

    def test_different_separator(self):
        """When you create the SIPClient you get to specify which character
        to use as the field separator.
        """
        sip = MockSIPClient(separator='^')
        sip.queue_response(
            "64Y                201610050000114734                        AOnypl ^AA240^AENo Name^BLN^AFYour library card number cannot be located.^AY1AZC9DE"
        )
        response = sip.patron_information('identifier')
        eq_('240', response['patron_identifier'])

    def test_location_code_is_optional(self):
        """You can specify a location_code when logging in, or not."""
        without_code = self.sip.login_message("login_id", "login_password")
        assert without_code.endswith("COlogin_password")
        with_code = self.sip.login_message("login_id", "login_password",
                                           "location_code")
        assert with_code.endswith("COlogin_password|CPlocation_code")

    def test_patron_password_is_optional(self):
        without_password = self.sip.patron_information_request(
            "patron_identifier")
        assert without_password.endswith('AApatron_identifier|AC')
        with_password = self.sip.patron_information_request(
            "patron_identifier", "patron_password")
        assert with_password.endswith(
            'AApatron_identifier|AC|ADpatron_password')
Ejemplo n.º 41
0
 def setup(self):
     self.sip = MockSIPClient()
    def test_remote_authenticate(self):
        integration = self._external_integration(self._str)
        client = MockSIPClient()
        auth = SIP2AuthenticationProvider(self._default_library,
                                          integration,
                                          client=client)

        # Some examples taken from a Sierra SIP API.
        client.queue_response(self.sierra_valid_login)
        patrondata = auth.remote_authenticate("user", "pass")
        eq_("12345", patrondata.authorization_identifier)
        eq_("*****@*****.**", patrondata.email_address)
        eq_("SHELDON, ALICE", patrondata.personal_name)
        eq_(0, patrondata.fines)
        eq_(None, patrondata.authorization_expires)
        eq_(None, patrondata.external_type)
        eq_(PatronData.NO_VALUE, patrondata.block_reason)

        client.queue_response(self.sierra_invalid_login)
        eq_(None, auth.remote_authenticate("user", "pass"))

        # Since Sierra provides both the patron's fine amount and the
        # maximum allowable amount, we can determine just by looking
        # at the SIP message that this patron is blocked for excessive
        # fines.
        client.queue_response(self.sierra_excessive_fines)
        patrondata = auth.remote_authenticate("user", "pass")
        eq_(PatronData.EXCESSIVE_FINES, patrondata.block_reason)

        # Some examples taken from an Evergreen instance that doesn't
        # use passwords.
        client.queue_response(self.evergreen_active_user)
        patrondata = auth.remote_authenticate("user", "pass")
        eq_("12345", patrondata.authorization_identifier)
        eq_("863715", patrondata.permanent_id)
        eq_("Booth Active Test", patrondata.personal_name)
        eq_(0, patrondata.fines)
        eq_(datetime(2019, 10, 4), patrondata.authorization_expires)
        eq_("Adult", patrondata.external_type)

        # A patron with an expired card.
        client.queue_response(self.evergreen_expired_card)
        patrondata = auth.remote_authenticate("user", "pass")
        eq_("12345", patrondata.authorization_identifier)
        # SIP extension field XI becomes sipserver_internal_id which
        # becomes PatronData.permanent_id.
        eq_("863716", patrondata.permanent_id)
        eq_("Booth Expired Test", patrondata.personal_name)
        eq_(0, patrondata.fines)
        eq_(datetime(2008, 9, 7), patrondata.authorization_expires)
        eq_(PatronData.NO_BORROWING_PRIVILEGES, patrondata.block_reason)

        # A patron with excessive fines
        client.queue_response(self.evergreen_excessive_fines)
        patrondata = auth.remote_authenticate("user", "pass")
        eq_("12345", patrondata.authorization_identifier)
        eq_("863718", patrondata.permanent_id)
        eq_("Booth Excessive Fines Test", patrondata.personal_name)
        eq_(100, patrondata.fines)
        eq_(datetime(2019, 10, 04), patrondata.authorization_expires)

        # We happen to know that this patron can't borrow books due to
        # excessive fines, but that information doesn't show up as a
        # block, because Evergreen doesn't also provide the
        # fine limit. This isn't a big deal -- we'll pick it up later
        # when we apply the site policy.
        #
        # This patron also has "Recall privileges denied" set, but
        # that's not a reason to block them.
        eq_(PatronData.NO_VALUE, patrondata.block_reason)

        # "Hold privileges denied" is not a block because you can
        # still borrow books.
        client.queue_response(self.evergreen_hold_privileges_denied)
        patrondata = auth.remote_authenticate("user", "pass")
        eq_(PatronData.NO_VALUE, patrondata.block_reason)

        client.queue_response(self.evergreen_card_reported_lost)
        patrondata = auth.remote_authenticate("user", "pass")
        eq_(PatronData.CARD_REPORTED_LOST, patrondata.block_reason)

        # Some examples taken from a Polaris instance.
        client.queue_response(self.polaris_valid_pin)
        patrondata = auth.remote_authenticate("user", "pass")
        eq_("25891000331441", patrondata.authorization_identifier)
        eq_("*****@*****.**", patrondata.email_address)
        eq_(9.25, patrondata.fines)
        eq_("Falk, Jen", patrondata.personal_name)
        eq_(datetime(2018, 6, 9, 23, 59, 59), patrondata.authorization_expires)

        client.queue_response(self.polaris_wrong_pin)
        patrondata = auth.remote_authenticate("user", "pass")
        eq_(None, patrondata)

        client.queue_response(self.polaris_no_such_patron)
        patrondata = auth.remote_authenticate("user", "pass")
        eq_(None, patrondata)

        client.queue_response(self.polaris_expired_card)
        patrondata = auth.remote_authenticate("user", "pass")
        eq_(datetime(2016, 10, 25, 23, 59, 59),
            patrondata.authorization_expires)

        client.queue_response(self.polaris_excess_fines)
        patrondata = auth.remote_authenticate("user", "pass")
        eq_(11.50, patrondata.fines)
Ejemplo n.º 43
0
 def test_login_message(self):
     sip = MockSIPClient()
     message = sip.login_message('user_id', 'password')
     eq_('9300CNuser_id|COpassword', message)