Пример #1
0
 def test_list_devices_with_search_constraints(self):
     expressions = shared_messages.SearchExpression(
         expression='serial_number')
     expected_response = device_message.ListDevicesResponse(
         devices=[device_message.Device(serial_number='6789')],
         additional_results=False)
     request = device_message.Device(query=shared_messages.SearchRequest(
         query_string='sn:6789',
         expressions=[expressions],
         returned_fields=['serial_number']))
     response = self.service.list_devices(request)
     self.assertEqual(response.devices[0].serial_number,
                      expected_response.devices[0].serial_number)
     self.assertFalse(response.additional_results)
Пример #2
0
 def test_list_devices_with_search_constraints(self):
     expressions = shared_messages.SearchExpression(
         expression='serial_number')
     expected_response = device_messages.ListDevicesResponse(
         devices=[
             device_messages.Device(serial_number='6789',
                                    guest_permitted=True)
         ],
         total_results=1,
         total_pages=1)
     request = device_messages.Device(query=shared_messages.SearchRequest(
         query_string='sn:6789',
         expressions=[expressions],
         returned_fields=['serial_number']))
     response = self.service.list_devices(request)
     self.assertEqual(response, expected_response)
Пример #3
0
 def test_list_shelves_with_search_constraints(self):
     expressions = shared_messages.SearchExpression(expression='location')
     expected_response = shelf_messages.ListShelfResponse(shelves=[
         shelf_messages.Shelf(location=self.shelf.location,
                              shelf_request=shelf_messages.ShelfRequest(
                                  location=self.shelf.location,
                                  urlsafe_key=self.shelf.key.urlsafe()))
     ],
                                                          total_results=1,
                                                          total_pages=1)
     request = shelf_messages.Shelf(
         query=shared_messages.SearchRequest(query_string='location:NYC',
                                             expressions=[expressions],
                                             returned_fields=['location']))
     response = self.service.list_shelves(request)
     self.assertEqual(response, expected_response)
    def testSearchRequest(self):
        search_exp = shared_messages.SearchExpression(
            expression='FAKE-EXPRESSION',
            direction=shared_messages.SortDirection(0))

        search_request = shared_messages.SearchRequest(
            query_string='FAKE-QUERY-STRING',
            expressions=[search_exp],
            returned_fields=['FAKE-RETURN'])

        self.assertEqual(search_request.query_string, 'FAKE-QUERY-STRING')
        self.assertListEqual(search_request.returned_fields, ['FAKE-RETURN'])
        self.assertEqual(search_request.expressions[0].expression,
                         'FAKE-EXPRESSION')
        self.assertEqual(search_request.expressions[0].direction.name,
                         'ASCENDING')
Пример #5
0
class DeviceApiTest(parameterized.TestCase, loanertest.EndpointsTestCase):
    """Tests for the Device API."""
    def setUp(self):
        super(DeviceApiTest, self).setUp()
        self.service = device_api.DeviceApi()
        self.login_admin_endpoints_user()

        self.shelf = shelf_model.Shelf.enroll(user_email=loanertest.USER_EMAIL,
                                              location='NYC',
                                              capacity=10,
                                              friendly_name='GnG',
                                              latitude=40.6892534,
                                              longitude=-74.0466891,
                                              altitude=1.0)

        self.device = device_model.Device()
        self.device.serial_number = '123ABC'
        self.device.asset_tag = '12345'
        self.device.enrolled = True
        self.device.device_model = 'Google Pixelbook'
        self.device.due_date = datetime.datetime(2017, 11, 15)
        self.device.last_known_healthy = datetime.datetime(2017, 11, 1)
        self.device.shelf = self.shelf.key
        self.device.assigned_user = loanertest.USER_EMAIL
        self.device.assignment_date = datetime.datetime(2017, 11, 1)
        self.device.current_ou = '/'
        self.device.ou_changed_date = datetime.datetime(2017, 11, 1)
        self.device.locked = False
        self.device.lost = False
        self.device.chrome_device_id = 'unique_id_1'
        self.device.last_heartbeat = datetime.datetime(2017, 11, 1)
        self.device.damaged = False
        self.device.damaged_reason = None
        self.device.put()
        self.device.set_last_reminder(0)
        self.device.set_next_reminder(1, datetime.timedelta(hours=2))
        device_model.Device(
            serial_number='6789',
            enrolled=True,
            device_model='HP Chromebook 13 G1',
            current_ou='/',
            chrome_device_id='unique_id_2',
            shelf=self.shelf.key,
            damaged=False,
        ).put()
        self.unenrolled_device = device_model.Device(
            serial_number='4567',
            enrolled=False,
            device_model='HP Chromebook 13 G1',
            current_ou='/',
            chrome_device_id='unique_id_3',
            damaged=False,
        )
        self.unenrolled_device.put()
        self.unenrolled_device_directory = {
            'deviceId': 'unique_id',
            'serialNumber': '4567',
            'status': 'ACTIVE',
            'lastSync': datetime.datetime.utcnow(),
            'model': 'HP Chromebook 13 G1',
            'orgUnitPath': constants.ORG_UNIT_DICT['DEFAULT'],
        }

    def tearDown(self):
        super(DeviceApiTest, self).tearDown()
        self.service = None

    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    def test_enroll(self, mock_directoryclass):
        """Tests Enroll with mock methods."""
        mock_directoryclient = mock_directoryclass.return_value
        mock_directoryclient.get_chrome_device.return_value = (
            self.unenrolled_device)
        mock_directoryclient.get_chrome_device_by_serial.return_value = (
            self.unenrolled_device_directory)
        retrieved_device = device_model.Device.get(
            serial_number=self.unenrolled_device.serial_number)
        self.assertFalse(retrieved_device.enrolled)

        request = device_messages.DeviceRequest(
            serial_number=self.unenrolled_device.serial_number)
        with mock.patch.object(self.service, 'check_xsrf_token',
                               autospec=True) as mock_xsrf_token:
            response = self.service.enroll(request)
            self.assertIsInstance(response, message_types.VoidMessage)
            retrieved_device = device_model.Device.get(
                serial_number=self.unenrolled_device.serial_number)
            self.assertTrue(retrieved_device.enrolled)
            self.assertEqual(mock_xsrf_token.call_count, 1)

    @parameterized.parameters((datastore_errors.BadValueError, ),
                              (device_model.DeviceCreationError, ))
    @mock.patch.object(device_model, 'Device', autospec=True)
    def test_enroll_error(self, test_error, mock_device_cls):
        mock_device_cls.enroll.side_effect = test_error
        request = device_messages.DeviceRequest(
            serial_number=self.unenrolled_device.serial_number)
        with self.assertRaises(endpoints.BadRequestException):
            self.service.enroll(request)

    @mock.patch.object(device_model, 'Device', autospec=True)
    def test_unenroll_error(self, mock_device_cls):
        mock_device_cls.get.return_value.unenroll.side_effect = (
            device_model.FailedToUnenrollError())
        request = device_messages.DeviceRequest(
            serial_number=self.unenrolled_device.serial_number)
        with self.assertRaises(endpoints.BadRequestException):
            self.service.unenroll(request)

    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_unenroll(self, mock_xsrf_token, mock_directoryclass):
        mock_directoryclient = mock_directoryclass.return_value
        mock_directoryclient.move_chrome_device_org_unit.return_value = (
            loanertest.TEST_DIR_DEVICE_DEFAULT)
        request = device_messages.DeviceRequest(
            serial_number=self.device.serial_number)
        self.assertTrue(self.device.enrolled)
        response = self.service.unenroll(request)
        self.assertFalse(self.device.enrolled)
        self.assertIsNone(self.device.assigned_user)
        self.assertIsNone(self.device.due_date)
        self.assertIsInstance(response, message_types.VoidMessage)
        assert mock_xsrf_token.call_count == 1

    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_unlock(self, mock_xsrf_token, mock_directoryclass):
        self.device.lost = True
        self.device.locked = True
        self.device.put()
        mock_directoryclient = mock_directoryclass.return_value
        mock_directoryclient.move_chrome_device_org_unit.return_value = (
            loanertest.TEST_DIR_DEVICE_DEFAULT)
        request = device_messages.DeviceRequest(
            serial_number=self.device.serial_number)
        response = self.service.unlock(request)
        self.assertFalse(self.device.locked)
        self.assertFalse(self.device.lost)
        self.assertIsInstance(response, message_types.VoidMessage)
        assert mock_xsrf_token.call_count == 1

    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    def test_unlock_directory_error(self, mock_directory_class):
        mock_directory_client = mock_directory_class.return_value
        mock_directory_client.reenable_chrome_device.side_effect = (
            directory.DirectoryRPCError)
        with self.assertRaises(endpoints.BadRequestException):
            self.service.unlock(
                device_messages.DeviceRequest(asset_tag='12345'))

    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    def test_unlock_move_ou_error(self, mock_directory_class):
        del mock_directory_class  # Unused.
        with mock.patch.object(
                self.device,
                'move_to_default_ou',
                side_effect=device_model.UnableToMoveToDefaultOUError):
            with self.assertRaises(endpoints.BadRequestException):
                self.service.unlock(
                    device_messages.DeviceRequest(asset_tag='12345'))

    @mock.patch('__main__.device_model.Device.device_audit_check')
    def test_device_audit_check(self, mock_device_audit_check):
        request = device_messages.DeviceRequest(unknown_identifier='6765')
        self.assertRaisesRegexp(device_api.endpoints.NotFoundException,
                                device_api._NO_DEVICE_MSG % '6765',
                                self.service.device_audit_check, request)

        device_model.Device(serial_number='12345',
                            enrolled=True,
                            device_model='HP Chromebook 13 G1',
                            current_ou='/',
                            chrome_device_id='unique_id_1',
                            damaged=False).put()
        request = device_messages.DeviceRequest(unknown_identifier='12345')
        response = self.service.device_audit_check(request)
        assert mock_device_audit_check.call_count == 1
        self.assertIsInstance(response, message_types.VoidMessage)

    def test_device_audit_check_device_not_enrolled(self):
        request = device_messages.DeviceRequest(
            unknown_identifier=self.device.serial_number)
        self.device.enrolled = False
        with self.assertRaises(device_api.endpoints.BadRequestException):
            self.service.device_audit_check(request)

    def test_device_audit_check_device_damaged(self):
        request = device_messages.DeviceRequest(
            unknown_identifier=self.device.serial_number)
        self.device.damaged = True
        with self.assertRaises(device_api.endpoints.BadRequestException):
            self.service.device_audit_check(request)

    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    def test_get_device_not_found(self, mock_directory_class):
        mock_directory_client = mock_directory_class.return_value
        mock_directory_client.given_name.return_value = 'given name value'
        request = device_messages.DeviceRequest(unknown_identifier='not-found')
        with self.assertRaises(device_api.endpoints.NotFoundException):
            self.service.get_device(request)

    def test_get_device_unenrolled(self):
        request = device_messages.DeviceRequest(
            unknown_identifier=self.device.serial_number)
        self.device.enrolled = False
        with self.assertRaises(device_api.endpoints.BadRequestException):
            self.service.get_device(request)

    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    def test_get_device(self, mock_directory_class):
        mock_directory_client = mock_directory_class.return_value
        mock_directory_client.given_name.return_value = 'given name value'
        asset_tag_response = self.service.get_device(
            device_messages.DeviceRequest(asset_tag='12345'))
        chrome_device_id_response = self.service.get_device(
            device_messages.DeviceRequest(chrome_device_id='unique_id_1'))
        serial_number_response = self.service.get_device(
            device_messages.DeviceRequest(serial_number='123ABC'))
        urlkey_response = self.service.get_device(
            device_messages.DeviceRequest(urlkey=self.device.key.urlsafe()))
        unknown_identifier_response = self.service.get_device(
            device_messages.DeviceRequest(unknown_identifier='123ABC'))

        self.assertIsInstance(asset_tag_response, device_messages.Device)
        self.assertIsInstance(chrome_device_id_response,
                              device_messages.Device)
        self.assertIsInstance(serial_number_response, device_messages.Device)
        self.assertIsInstance(urlkey_response, device_messages.Device)
        self.assertIsInstance(unknown_identifier_response,
                              device_messages.Device)
        self.assertEqual(self.device.serial_number,
                         asset_tag_response.serial_number)
        self.assertEqual(self.device.device_model,
                         urlkey_response.device_model)

    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    def test_get_device_no_permission(self, mock_directory_class):
        mock_directory_client = mock_directory_class.return_value
        mock_directory_client.given_name.return_value = 'given name value'
        email = 'random@{}'.format(loanertest.USER_DOMAIN)
        self.login_endpoints_user(email=email)
        with self.assertRaises(endpoints.UnauthorizedException):
            self.service.get_device(
                device_messages.DeviceRequest(
                    serial_number=self.device.serial_number))

    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    def test_get_device_has_permission(self, mock_directory_class):
        mock_directory_client = mock_directory_class.return_value
        mock_directory_client.given_name.return_value = 'given name value'
        device = self.service.get_device(
            device_messages.DeviceRequest(
                serial_number=self.device.serial_number))
        self.assertIsInstance(device, device_messages.Device)
        self.assertEqual(device.serial_number, self.device.serial_number)

    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    def test_get_device_assigned_user(self, mock_directory_class):
        mock_directory_client = mock_directory_class.return_value
        mock_directory_client.given_name.return_value = 'given name value'
        email = 'random@{}'.format(loanertest.USER_DOMAIN)
        self.login_endpoints_user(email=email)
        self.device.assigned_user = email
        self.device.put()
        device = self.service.get_device(
            device_messages.DeviceRequest(
                serial_number=self.device.serial_number))
        self.assertIsInstance(device, device_messages.Device)
        self.assertEqual(device.serial_number, self.device.serial_number)

    @parameterized.parameters(directory.DirectoryRPCError,
                              directory.GivenNameDoesNotExistError)
    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    def test_get_device_directory_errors(self, test_error,
                                         mock_directory_class):
        request = device_messages.DeviceRequest(asset_tag='12345')
        mock_directory_client = mock_directory_class.return_value
        mock_directory_client.given_name.side_effect = test_error
        self.assertIsNone(self.service.get_device(request).given_name)

    @parameterized.parameters((
        device_messages.Device(enrolled=True),
        2,
    ), (
        device_messages.Device(current_ou='/'),
        2,
    ), (
        device_messages.Device(enrolled=False),
        1,
    ), (
        device_messages.Device(query=shared_messages.SearchRequest(
            query_string='sn:6789')),
        1,
    ), (
        device_messages.Device(query=shared_messages.SearchRequest(
            query_string='at:12345')),
        1,
    ))
    def test_list_devices(self, request, response_length):
        response = self.service.list_devices(request)
        self.assertEqual(response_length, len(response.devices))

    def test_list_devices_invalid_page_size(self):
        with self.assertRaises(endpoints.BadRequestException):
            request = device_messages.Device(page_size=0)
            self.service.list_devices(request)

    def test_list_devices_with_search_constraints(self):
        expressions = shared_messages.SearchExpression(
            expression='serial_number')
        expected_response = device_messages.ListDevicesResponse(
            devices=[
                device_messages.Device(serial_number='6789',
                                       guest_permitted=True)
            ],
            total_results=1,
            total_pages=1)
        request = device_messages.Device(query=shared_messages.SearchRequest(
            query_string='sn:6789',
            expressions=[expressions],
            returned_fields=['serial_number']))
        response = self.service.list_devices(request)
        self.assertEqual(response, expected_response)

    def test_list_devices_with_filter_message(self):
        message = device_messages.Device(enrolled=True,
                                         device_model='HP Chromebook 13 G1',
                                         current_ou='/')
        filters = api_utils.to_dict(message, device_model.Device)
        request = device_messages.Device(**filters)
        response = self.service.list_devices(request)
        expected_response = device_messages.ListDevicesResponse(
            devices=[
                device_messages.Device(serial_number='6789',
                                       identifier='6789',
                                       enrolled=True,
                                       device_model='HP Chromebook 13 G1',
                                       current_ou='/',
                                       locked=False,
                                       lost=False,
                                       chrome_device_id='unique_id_2',
                                       damaged=False,
                                       guest_permitted=True)
            ],
            total_results=1,
            total_pages=1)
        self.assertEqual(response, expected_response)

    @mock.patch('__main__.device_api.shelf_api.get_shelf')
    def test_list_devices_with_shelf_filter(self, mock_get_shelf):
        # Test for shelf location as filter.
        mock_get_shelf.return_value = self.shelf
        shelf_request_message = shelf_messages.ShelfRequest(
            location=self.shelf.location)
        message = shelf_messages.Shelf(shelf_request=shelf_request_message)
        request = device_messages.Device(shelf=message)
        response = self.service.list_devices(request)
        mock_get_shelf.assert_called_once_with(shelf_request_message)
        self.assertEqual(len(response.devices), 2)

    def test_list_devices_with_offset(self):
        request = device_messages.Device(page_size=1, page_number=1)
        response = self.service.list_devices(request)
        self.assertEqual(1, len(response.devices))
        previouse_response = response

        # Get next page results and make sure it's not the same as last.
        request = device_messages.Device(page_size=1, page_number=2)
        response = self.service.list_devices(request)
        self.assertEqual(1, len(response.devices))
        self.assertNotEqual(response, previouse_response)

    def test_list_devices_inactive_no_shelf(self):
        request = device_messages.Device(enrolled=False, page_size=1)
        response = self.service.list_devices(request)
        expected_response = device_messages.ListDevicesResponse(
            devices=[
                device_messages.Device(
                    serial_number=self.unenrolled_device.serial_number,
                    identifier=self.unenrolled_device.serial_number,
                    enrolled=self.unenrolled_device.enrolled,
                    device_model=self.unenrolled_device.device_model,
                    current_ou=self.unenrolled_device.current_ou,
                    locked=self.unenrolled_device.locked,
                    lost=self.unenrolled_device.lost,
                    chrome_device_id=self.unenrolled_device.chrome_device_id,
                    damaged=self.unenrolled_device.damaged,
                    guest_permitted=True)
            ],
            total_results=1,
            total_pages=1)
        self.assertEqual(expected_response, response)

    @mock.patch('__main__.device_model.Device.list_by_user')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_list_user_devices(self, mock_xsrf_token, mock_list_by_user):
        self.login_endpoints_user()
        device2 = device_model.Device()
        device2.serial_number = '123ABC'
        device2.assigned_user = loanertest.USER_EMAIL
        device2.assignment_date = datetime.datetime(2017, 11, 1)
        device2.due_date = datetime.datetime(2017, 11, 4)
        device2.put()

        request = message_types.VoidMessage()
        mock_list_by_user.return_value = [self.device, device2]
        response = self.service.list_user_devices(request)
        self.assertEqual(response.devices[0].serial_number,
                         self.device.serial_number)
        self.assertEqual(len(response.devices), 2)
        assert mock_xsrf_token.call_count == 1
        mock_list_by_user.assert_called_once_with(loanertest.USER_EMAIL)

    @mock.patch('__main__.device_model.Device.enable_guest_mode')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_enable_guest_mode(self, mock_xsrf_token, mock_enableguest):
        config_model.Config.set('allow_guest_mode', True)
        self.login_endpoints_user()
        self.service.enable_guest_mode(
            device_messages.DeviceRequest(urlkey=self.device.key.urlsafe()))
        assert mock_enableguest.called
        assert mock_xsrf_token.call_count == 1

        mock_xsrf_token.reset_mock()
        mock_enableguest.reset_mock()
        self.service.enable_guest_mode(
            device_messages.DeviceRequest(
                chrome_device_id=self.device.chrome_device_id))
        assert mock_enableguest.called
        assert mock_xsrf_token.call_count == 1

        def guest_disabled_error(*args, **kwargs):
            del args, kwargs  # Unused.
            raise device_model.GuestNotAllowedError(
                device_model._GUEST_MODE_DISABLED_MSG)

        def directory_error(*args, **kwargs):
            del args, kwargs  # Unused.
            raise device_model.EnableGuestError(
                'Directory broke, all your fault.')

        mock_enableguest.side_effect = guest_disabled_error
        with self.assertRaises(endpoints.UnauthorizedException):
            self.service.enable_guest_mode(
                device_messages.DeviceRequest(
                    chrome_device_id=self.device.chrome_device_id))

        mock_enableguest.side_effect = directory_error
        with self.assertRaises(endpoints.InternalServerErrorException):
            self.service.enable_guest_mode(
                device_messages.DeviceRequest(
                    chrome_device_id=self.device.chrome_device_id))

    def test_enable_guest_unassigned(self):
        config_model.Config.set('allow_guest_mode', True)
        self.device.assigned_user = None
        self.device.put()
        with self.assertRaisesRegexp(endpoints.UnauthorizedException,
                                     device_model._UNASSIGNED_DEVICE):
            self.service.enable_guest_mode(
                device_messages.DeviceRequest(
                    urlkey=self.device.key.urlsafe()))

    @mock.patch('__main__.device_model.Device.loan_extend')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_extend_loan(self, mock_xsrf_token, mock_loanextend):
        tomorrow = datetime.datetime.utcnow() + datetime.timedelta(days=1)
        self.login_endpoints_user()
        self.service.extend_loan(
            device_messages.ExtendLoanRequest(
                device=device_messages.DeviceRequest(
                    urlkey=self.device.key.urlsafe()),
                extend_date=tomorrow))
        mock_loanextend.assert_called_once_with(
            user_email=loanertest.USER_EMAIL, extend_date_time=tomorrow)
        assert mock_xsrf_token.call_count == 1

        mock_xsrf_token.reset_mock()
        mock_loanextend.reset_mock()
        self.service.extend_loan(
            device_messages.ExtendLoanRequest(
                device=device_messages.DeviceRequest(
                    chrome_device_id=self.device.chrome_device_id),
                extend_date=tomorrow))
        mock_loanextend.assert_called_once_with(
            user_email=loanertest.USER_EMAIL, extend_date_time=tomorrow)
        assert mock_xsrf_token.call_count == 1

        mock_loanextend.side_effect = device_model.ExtendError
        self.assertRaises(
            device_api.endpoints.BadRequestException, self.service.extend_loan,
            device_messages.ExtendLoanRequest(
                device=device_messages.DeviceRequest(
                    chrome_device_id=self.device.chrome_device_id),
                extend_date=tomorrow))

    def test_extend_loan_unassigned(self):
        self.device.assigned_user = None
        self.device.put()
        with self.assertRaisesRegexp(endpoints.UnauthorizedException,
                                     device_model._UNASSIGNED_DEVICE):
            self.service.extend_loan(
                device_messages.ExtendLoanRequest(
                    device=device_messages.DeviceRequest(
                        chrome_device_id=self.device.chrome_device_id)))

    @mock.patch('__main__.device_model.Device.mark_damaged')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_mark_damaged(self, mock_xsrf_token, mock_markdamaged):
        self.login_endpoints_user()
        self.service.mark_damaged(
            device_messages.DamagedRequest(
                device=device_messages.DeviceRequest(
                    urlkey=self.device.key.urlsafe()),
                damaged_reason='Foo'))
        mock_markdamaged.assert_called_once_with(
            user_email=loanertest.USER_EMAIL, damaged_reason='Foo')
        assert mock_xsrf_token.call_count == 1

        mock_xsrf_token.reset_mock()
        mock_markdamaged.reset_mock()
        self.service.mark_damaged(
            device_messages.DamagedRequest(  # No reason given.
                device=device_messages.DeviceRequest(
                    urlkey=self.device.key.urlsafe())))
        mock_markdamaged.assert_called_once_with(
            user_email=loanertest.USER_EMAIL, damaged_reason=None)
        assert mock_xsrf_token.call_count == 1

    @mock.patch.object(device_model.Device, 'mark_damaged')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_mark_damaged__unauthorized(self, mock_xsrf, mock_markdamaged):
        del mock_xsrf  # unusued.
        self.login_endpoints_user()
        mock_markdamaged.side_effect = device_model.UnauthorizedError()
        with self.assertRaises(endpoints.UnauthorizedException):
            self.service.mark_damaged(
                device_messages.DamagedRequest(
                    device=device_messages.DeviceRequest(
                        urlkey=self.device.key.urlsafe()),
                    damaged_reason='Foo'))

    def test_mark_undamaged(self):
        with mock.patch.object(self.device,
                               'mark_undamaged') as mock_markundamaged:
            self.service.mark_undamaged(
                device_messages.DeviceRequest(
                    urlkey=self.device.key.urlsafe()))
            mock_markundamaged.assert_called_once_with(
                user_email=loanertest.SUPER_ADMIN_EMAIL)

    @mock.patch.object(device_model.Device, 'mark_undamaged')
    def test_mark_undamaged__unauthorized(self, mock_markundamaged):
        self.login_endpoints_user()
        with mock.patch.object(self.service, 'check_xsrf_token') as mock_xsrf:
            mock_markundamaged.side_effect = device_model.UnauthorizedError()
            with self.assertRaises(endpoints.UnauthorizedException):
                self.service.mark_undamaged(
                    device_messages.DeviceRequest(
                        urlkey=self.device.key.urlsafe()))
            assert mock_xsrf.call_count == 1

    @mock.patch('__main__.device_model.Device.mark_lost')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_mark_lost(self, mock_xsrf_token, mock_marklost):
        self.service.mark_lost(
            device_messages.DeviceRequest(urlkey=self.device.key.urlsafe()))
        mock_marklost.assert_called_once_with(
            user_email=loanertest.SUPER_ADMIN_EMAIL)
        assert mock_xsrf_token.call_count == 1

    @mock.patch.object(device_model.Device, 'mark_lost')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_mark_lost__unauthorized(self, mock_xsrf_token, mock_marklost):
        del mock_xsrf_token  # unused.
        mock_marklost.side_effect = device_model.UnauthorizedError()
        with self.assertRaises(endpoints.UnauthorizedException):
            self.service.mark_lost(
                device_messages.DeviceRequest(
                    urlkey=self.device.key.urlsafe()))

    @mock.patch('__main__.device_model.Device.mark_pending_return')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_mark_pending_return(self, mock_xsrf_token, mock_markreturned):
        self.login_endpoints_user()
        self.service.mark_pending_return(
            device_messages.DeviceRequest(urlkey=self.device.key.urlsafe()))
        mock_markreturned.assert_called_once_with(
            user_email=loanertest.USER_EMAIL)
        assert mock_xsrf_token.call_count == 1

    def test_mark_pending_return_unassigned(self):
        self.device.assigned_user = None
        self.device.put()
        with self.assertRaisesRegexp(endpoints.UnauthorizedException,
                                     device_model._UNASSIGNED_DEVICE):
            self.service.mark_pending_return(
                device_messages.DeviceRequest(
                    urlkey=self.device.key.urlsafe()))

    @mock.patch('__main__.device_model.Device.resume_loan')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_resume_loan(self, mock_xsrf_token, mock_resume_loan):
        self.login_endpoints_user()
        self.service.resume_loan(
            device_messages.DeviceRequest(urlkey=self.device.key.urlsafe()))
        assert mock_resume_loan.call_count == 1
        assert mock_xsrf_token.call_count == 1

    @mock.patch.object(device_model.Device, 'resume_loan')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_resume_loan__unauthorized(self, mock_xsrf_token,
                                       mock_resume_loan):
        del mock_xsrf_token  # unused.
        self.login_endpoints_user()
        mock_resume_loan.side_effect = device_model.UnauthorizedError()
        with self.assertRaises(endpoints.UnauthorizedException):
            self.service.resume_loan(
                device_messages.DeviceRequest(
                    urlkey=self.device.key.urlsafe()))

    def test_get_device_errors(self):
        # No identifiers.
        with self.assertRaises(endpoints.BadRequestException):
            device_api._get_device(device_messages.DeviceRequest())

        # URL-safe key that is still URL-safe, but technically not a key.
        with self.assertRaises(device_api.endpoints.BadRequestException):
            device_api._get_device(
                device_messages.DeviceRequest(urlkey='bad-key'))
Пример #6
0
class SearchTest(loanertest.EndpointsTestCase, parameterized.TestCase):

    _ASSIGNED_DATE = datetime.datetime(year=2017, month=1, day=1)

    @parameterized.parameters((
        shelf_messages.Shelf(location='NY', capacity=50),
        'location:NY capacity:50 enabled:True',
    ), (
        shelf_messages.Shelf(location='NY', capacity=50, enabled=False),
        'location:NY capacity:50 enabled:False',
    ))
    def test_to_query(self, message, expected_query):
        """Tests the creation of a valid search query from ndb properties."""
        query = search_utils.to_query(message, shelf_model.Shelf)
        #  The query is split because ndb properties are unordered when called by
        #  model_class._properties. This test would be flaky otherwise.
        self.assertCountEqual(query.split(' '), expected_query.split(' '))

    @parameterized.named_parameters(
        ('Shelf Message', shelf_messages.Shelf(),
         search.ScoredDocument(
             doc_id='test_doc_id',
             fields=[
                 search.NumberField(name='capacity', value=20.0),
                 search.TextField(name='location', value='US MTV'),
                 search.AtomField(name='location', value='US-MTV'),
                 search.AtomField(name='enabled', value='True'),
                 search.GeoField(name='lat_long',
                                 value=search.GeoPoint(52.37, 4.88)),
                 search.TextField(name='not_present', value='MTV')
             ]),
         shelf_messages.Shelf(enabled=True,
                              location='US-MTV',
                              capacity=20,
                              latitude=52.37,
                              longitude=4.88), 1),
        ('Device Message', device_messages.Device(),
         search.ScoredDocument(
             doc_id='test_doc_id',
             fields=[
                 search.DateField(name='assignment_date',
                                  value=_ASSIGNED_DATE),
                 search.TextField(name='serial_number', value='1234'),
                 search.AtomField(name='enrolled', value='True'),
                 search.TextField(name='assigned_user', value='user')
             ]),
         device_messages.Device(
             enrolled=True,
             serial_number='1234',
             assigned_user='******',
             max_extend_date=_ASSIGNED_DATE + datetime.timedelta(days=14),
             assignment_date=_ASSIGNED_DATE), 0))
    def test_document_to_message(self, message, test_search_document,
                                 expected_message, log_call_count):
        """Tests the creation of a protorpc message from a search document."""
        with mock.patch.object(search_utils, 'logging',
                               autospec=True) as mock_logging:
            response_message = search_utils.document_to_message(
                test_search_document, message)
            self.assertEqual(response_message, expected_message)
            self.assertEqual(mock_logging.error.call_count, log_call_count)

    def test_calculate_page_offset(self):
        """Tests the calculation of page offset."""
        page_size = 10
        page_number = 5
        offset = search_utils.calculate_page_offset(page_size, page_number)
        self.assertEqual(40, offset)

    def test_calculate_total_pages(self):
        """Tests the calculation of total pages."""
        page_size = 6
        total_results = 11
        total_pages = search_utils.calculate_total_pages(
            page_size, total_results)
        self.assertEqual(2, total_pages)

    @parameterized.named_parameters(
        {
            'testcase_name': 'QueryStringOnly',
            'request':
            shared_messages.SearchRequest(query_string='enrolled:True'),
            'expected_values': ('enrolled:True', None, [])
        },
        {
            'testcase_name':
            'QueryStringWithReturnedFields',
            'request':
            shared_messages.SearchRequest(query_string='location:US-NYC',
                                          returned_fields=['location']),
            'expected_values': ('location:US-NYC', None, ['location'])
        },
    )
    def test_set_search_query_options(self, request, expected_values):
        """Tests setting the query options without sort options from message."""
        returned_query, returned_sort_options, returned_returned_fields = (
            search_utils.set_search_query_options(request))
        expected_query, expected_sort_options, expcted_returned_fields = (
            expected_values)
        self.assertEqual(expected_sort_options, returned_sort_options)
        self.assertEqual(expected_query, returned_query)
        self.assertEqual(expcted_returned_fields, returned_returned_fields)

    @parameterized.named_parameters(
        {
            'testcase_name':
            'ExpressionWithDirection',
            'request':
            shared_messages.SearchRequest(
                query_string='enrolled:True',
                expressions=[
                    shared_messages.SearchExpression(
                        expression='enrolled',
                        direction=shared_messages.SortDirection.ASCENDING)
                ]),
            'expected_sort_options_expressions': [
                search.SortExpression(
                    expression='enrolled',
                    direction=search.SortExpression.ASCENDING)
            ]
        },
        {
            'testcase_name':
            'MultipleExpressionsWithDirection',
            'request':
            shared_messages.SearchRequest(
                query_string='enrolled:True',
                expressions=[
                    shared_messages.SearchExpression(
                        expression='enrolled',
                        direction=shared_messages.SortDirection.ASCENDING),
                    shared_messages.SearchExpression(
                        expression='serial_number',
                        direction=shared_messages.SortDirection.DESCENDING)
                ]),
            'expected_sort_options_expressions': [
                search.SortExpression(
                    expression='enrolled',
                    direction=search.SortExpression.ASCENDING),
                search.SortExpression(
                    expression='serial_number',
                    direction=search.SortExpression.DESCENDING)
            ]
        },
        {
            'testcase_name':
            'ExpressionWithoutDirection',
            'request':
            shared_messages.SearchRequest(
                query_string='enrolled:True',
                expressions=[
                    shared_messages.SearchExpression(expression='enrolled')
                ]),
            'expected_sort_options_expressions':
            [search.SortExpression(expression='enrolled')]
        },
        {
            'testcase_name':
            'MultipleExpressionsWithoutDirection',
            'request':
            shared_messages.SearchRequest(
                query_string='enrolled:True',
                expressions=[
                    shared_messages.SearchExpression(expression='enrolled'),
                    shared_messages.SearchExpression(
                        expression='serial_number')
                ]),
            'expected_sort_options_expressions': [
                search.SortExpression(
                    expression='enrolled',
                    direction=search.SortExpression.DESCENDING),
                search.SortExpression(
                    expression='serial_number',
                    direction=search.SortExpression.DESCENDING)
            ]
        },
    )
    def test_set_search_query_options_with_sort_options(
            self, request, expected_sort_options_expressions):
        """Tests setting query options with sort options from message."""
        returned_query, returned_sort_options, returned_returned_fields = (
            search_utils.set_search_query_options(request))
        del returned_query  # Unused.
        del returned_returned_fields  # Unused.
        for i in range(len(returned_sort_options.expressions)):
            self.assertEqual(returned_sort_options.expressions[i].expression,
                             expected_sort_options_expressions[i].expression)
            self.assertEqual(returned_sort_options.expressions[i].direction,
                             expected_sort_options_expressions[i].direction)
Пример #7
0
class ShelfApiTest(parameterized.TestCase, loanertest.EndpointsTestCase):
    """Test for the Shelf API."""
    def setUp(self):
        super(ShelfApiTest, self).setUp()
        self.patcher_directory = mock.patch(
            '__main__.device_model.directory.DirectoryApiClient')
        self.mock_directoryclass = self.patcher_directory.start()
        self.addCleanup(self.patcher_directory.stop)
        self.service = shelf_api.ShelfApi()
        self.login_admin_endpoints_user()
        self.patcher_xsrf = mock.patch(
            '__main__.shelf_api.root_api.Service.check_xsrf_token')
        self.shelf = shelf_model.Shelf.enroll(user_email=loanertest.USER_EMAIL,
                                              location='NYC',
                                              capacity=10,
                                              friendly_name='GnG',
                                              latitude=40.6892534,
                                              longitude=-74.0466891,
                                              altitude=1.0)
        shelf1 = shelf_model.Shelf.enroll(user_email=loanertest.USER_EMAIL,
                                          location='MTV',
                                          capacity=20)
        shelf2 = shelf_model.Shelf.enroll(user_email=loanertest.USER_EMAIL,
                                          location='SAO',
                                          capacity=10)
        self.disabled_shelf = shelf_model.Shelf.enroll(
            user_email=loanertest.USER_EMAIL,
            location='SVL',
            capacity=10,
            friendly_name='Bay')
        self.disabled_shelf.disable(loanertest.USER_EMAIL)
        self.shelf_locations = [
            self.shelf.location, shelf1.location, shelf2.location,
            self.disabled_shelf.location
        ]

        self.device1_key = device_model.Device(
            serial_number='12345',
            enrolled=True,
            device_model='HP Chromebook 13 G1',
            current_ou='/',
            chrome_device_id='unique_id_1',
            damaged=False,
        ).put()
        self.device2_key = device_model.Device(
            serial_number='54321',
            enrolled=True,
            device_model='HP Chromebook 13 G1',
            current_ou='/',
            chrome_device_id='unique_id_2',
            damaged=False,
        ).put()
        self.device3_key = device_model.Device(
            serial_number='67890',
            enrolled=True,
            shelf=self.shelf.key,
            device_model='HP Chromebook 13 G1',
            current_ou='/',
            chrome_device_id='unique_id_3',
            damaged=False,
        ).put()
        self.device4_key = device_model.Device(
            serial_number='ABC123',
            enrolled=True,
            shelf=self.shelf.key,
            device_model='HP Chromebook 13 G1',
            current_ou='/',
            chrome_device_id='unique_id_4',
            damaged=False,
        ).put()
        self.device_identifiers = [
            self.device1_key.get().serial_number,
            self.device2_key.get().serial_number,
            self.device3_key.get().serial_number
        ]

    def tearDown(self):
        super(ShelfApiTest, self).tearDown()
        self.service = None

    @mock.patch('__main__.root_api.Service.check_xsrf_token')
    @mock.patch('__main__.shelf_model.Shelf.enroll')
    def test_enroll(self, mock_enroll, mock_xsrf_token):
        """Test Enroll with mock methods."""
        request = shelf_messages.EnrollShelfRequest(
            location='nyc',
            capacity=100,
            friendly_name='test',
            latitude=12.5,
            longitude=12.5,
            altitude=2.0,
            responsible_for_audit='precise',
            audit_interval_override=33,
            audit_notification_enabled=True)
        response = self.service.enroll(request)
        assert mock_xsrf_token.call_count == 1
        self.assertIsInstance(response, message_types.VoidMessage)

    def test_enroll_bad_request(self):
        request = shelf_messages.EnrollShelfRequest(capacity=10)
        with self.assertRaisesRegexp(shelf_api.endpoints.BadRequestException,
                                     'Entity has uninitialized properties'):
            self.service.enroll(request)
        request = shelf_messages.EnrollShelfRequest(location='nyc',
                                                    capacity=10,
                                                    latitude=12.5)
        with self.assertRaisesRegexp(shelf_api.endpoints.BadRequestException,
                                     shelf_model._LAT_LONG_MSG):
            self.service.enroll(request)

    @mock.patch('__main__.root_api.Service.check_xsrf_token')
    def test_get_by_location(self, mock_xsrf_token):
        request = shelf_messages.ShelfRequest(location='NYC')
        response = self.service.get(request)
        assert mock_xsrf_token.call_count == 1
        self.assertEqual(self.shelf.location, response.location)
        self.assertEqual(self.shelf.friendly_name, response.friendly_name)

    def test_disable_by_location(self):
        request = shelf_messages.ShelfRequest(location='NYC')
        self.assertTrue(self.shelf.enabled)
        response = self.service.disable(request)
        self.assertFalse(self.shelf.enabled)
        self.assertIsInstance(response, message_types.VoidMessage)

    @mock.patch('__main__.root_api.Service.check_xsrf_token')
    def test_update_using_location(self, mock_xsrf_token):
        request = shelf_messages.UpdateShelfRequest(
            shelf_request=shelf_messages.ShelfRequest(location='NYC'),
            location='NYC-9th')
        response = self.service.update(request)
        assert mock_xsrf_token.call_count == 1
        self.assertEqual(self.shelf.location, 'NYC-9th')
        shelf = shelf_model.Shelf.get(friendly_name='GnG')
        self.assertEqual(shelf.location, 'NYC-9th')
        self.assertIsInstance(response, message_types.VoidMessage)

    @parameterized.parameters((
        shelf_messages.Shelf(capacity=10),
        2,
    ), (
        shelf_messages.Shelf(enabled=False),
        1,
    ), (
        shelf_messages.Shelf(query=shared_messages.SearchRequest(
            query_string='enabled:True capacity:10')),
        2,
    ), (
        shelf_messages.Shelf(query=shared_messages.SearchRequest(
            query_string='enabled:False')),
        1,
    ))
    @mock.patch('__main__.root_api.Service.check_xsrf_token')
    def test_list_shelves(self, request, response_length, mock_xsrf_token):
        response = self.service.list_shelves(request)
        assert mock_xsrf_token.call_count == 1
        self.assertEqual(response_length, len(response.shelves))

    def test_list_shelves_invalid_page_size(self):
        with self.assertRaises(endpoints.BadRequestException):
            request = shelf_messages.Shelf(page_size=0)
            self.service.list_shelves(request)

    def test_list_shelves_with_search_constraints(self):
        expressions = shared_messages.SearchExpression(expression='location')
        expected_response = shelf_messages.ListShelfResponse(shelves=[
            shelf_messages.Shelf(location=self.shelf.location,
                                 shelf_request=shelf_messages.ShelfRequest(
                                     location=self.shelf.location,
                                     urlsafe_key=self.shelf.key.urlsafe()))
        ],
                                                             total_results=1,
                                                             total_pages=1)
        request = shelf_messages.Shelf(
            query=shared_messages.SearchRequest(query_string='location:NYC',
                                                expressions=[expressions],
                                                returned_fields=['location']))
        response = self.service.list_shelves(request)
        self.assertEqual(response, expected_response)

    def test_list_shelves_with_offset(self):
        previouse_shelf_locations = []
        request = shelf_messages.Shelf(enabled=True,
                                       page_size=1,
                                       page_number=1)
        response = self.service.list_shelves(request)
        self.assertEqual(len(response.shelves), 1)
        previouse_shelf_locations.append(response.shelves[0].location)

        # Get next page results and make sure it's not the same as last.
        request = shelf_messages.Shelf(enabled=True,
                                       page_size=1,
                                       page_number=2)
        response = self.service.list_shelves(request)
        self.assertEqual(len(response.shelves), 1)
        self.assertNotIn(response.shelves[0], previouse_shelf_locations)
        previouse_shelf_locations.append(response.shelves[0].location)

        # Get next page results and make sure it's not the same as last 2.
        request = shelf_messages.Shelf(enabled=True,
                                       page_size=1,
                                       page_number=3)
        response = self.service.list_shelves(request)
        self.assertEqual(len(response.shelves), 1)
        self.assertNotIn(response.shelves[0], previouse_shelf_locations)
        previouse_shelf_locations.append(response.shelves[0].location)

    @mock.patch('__main__.root_api.Service.check_xsrf_token')
    @mock.patch('__main__.shelf_api.logging.info')
    def test_audit_using_shelf_location(self, mock_logging, mock_xsrf_token):
        request = shelf_messages.ShelfAuditRequest(
            shelf_request=shelf_messages.ShelfRequest(location='NYC'),
            device_identifiers=self.device_identifiers)
        response = self.service.audit(request)
        assert mock_xsrf_token.call_count == 1
        mock_logging.assert_called()
        for identifier in self.device_identifiers:
            datastore_device = device_model.Device.get(
                serial_number=identifier)
            self.assertEqual(datastore_device.shelf.get().location, 'NYC')
        self.assertFalse(self.shelf.audit_requested)
        self.assertEqual(self.shelf.last_audit_by,
                         loanertest.SUPER_ADMIN_EMAIL)
        self.assertIsInstance(response, message_types.VoidMessage)

    def test_audit_invalid_device(self):
        request = shelf_messages.ShelfAuditRequest(
            shelf_request=shelf_messages.ShelfRequest(location='NYC'),
            device_identifiers=['Invalid'])
        with self.assertRaisesRegexp(
                endpoints.NotFoundException,
                shelf_api._DEVICE_DOES_NOT_EXIST_MSG % 'Invalid'):
            self.service.audit(request)

    @mock.patch.object(device_model.Device, 'search')
    @mock.patch.object(shelf_api, 'get_shelf', autospec=True)
    def test_audit_remove_devices(self, mock_get_shelf,
                                  mock_model_device_search):
        shelf = self.device2_key.get()
        shelf.shelf = self.shelf.key
        shelf.put()
        mock_model_device_search.return_value = (search.SearchResults(
            results=[
                search.ScoredDocument(doc_id=self.device2_key.urlsafe()),
                search.ScoredDocument(doc_id=self.device3_key.urlsafe()),
                search.ScoredDocument(doc_id=self.device4_key.urlsafe())
            ],
            number_found=3))
        mock_get_shelf.return_value = self.shelf
        request = shelf_messages.ShelfAuditRequest(
            shelf_request=shelf_messages.ShelfRequest(
                location=self.shelf.location),
            device_identifiers=[self.device3_key.get().serial_number])
        self.service.audit(request)
        self.assertEqual(self.device3_key.get().shelf, self.shelf.key)
        self.assertEqual(self.device2_key.get().shelf, None)
        self.assertEqual(self.device4_key.get().shelf, None)

    def test_get_shelf_urlsafe_key(self):
        """Test getting a shelf using the urlsafe key."""
        request = shelf_messages.ShelfRequest(
            urlsafe_key=self.shelf.key.urlsafe())
        shelf = shelf_api.get_shelf(request)
        self.assertEqual(shelf, self.shelf)

    def test_get_shelf_using_location(self):
        """Test getting a shelf using the location."""
        request = shelf_messages.ShelfRequest(location=self.shelf.location)
        shelf = shelf_api.get_shelf(request)
        self.assertEqual(shelf, self.shelf)

    def test_get_shelf_using_location_error(self):
        """Test getting a shelf with an invalid location."""
        request = shelf_messages.ShelfRequest(location='Not_Valid')
        with self.assertRaisesRegexp(
                endpoints.NotFoundException,
                shelf_api._SHELF_DOES_NOT_EXIST_MSG % request.location):
            shelf_api.get_shelf(request)
Пример #8
0
class SearchTest(loanertest.EndpointsTestCase, parameterized.TestCase):
    @parameterized.parameters((
        shelf_messages.Shelf(location='NY', capacity=50),
        'location:NY capacity:50 enabled:True',
    ), (
        shelf_messages.Shelf(location='NY', capacity=50, enabled=False),
        'location:NY capacity:50 enabled:False',
    ))
    def test_to_query(self, message, expected_query):
        """Tests the creation of a valid search query from ndb properties."""
        query = search_utils.to_query(message, shelf_model.Shelf)
        #  The query is split because ndb properties are unordered when called by
        #  model_class._properties. This test would be flaky otherwise.
        self.assertCountEqual(query.split(' '), expected_query.split(' '))

    @mock.patch.object(search_utils, 'logging', autospec=True)
    def test_document_to_message(self, mock_logging):
        """Tests the creation of a protorpc message from a search document."""
        test_search_document = search.ScoredDocument(
            doc_id='test_doc_id',
            fields=[
                search.NumberField(name='capacity', value=20.0),
                search.TextField(name='location', value='US MTV'),
                search.AtomField(name='location', value='US-MTV'),
                search.AtomField(name='enabled', value='True'),
                search.GeoField(name='lat_long',
                                value=search.GeoPoint(52.37, 4.88)),
                search.TextField(name='not_present', value='MTV')
            ])
        expected_message = shelf_messages.Shelf(enabled=True,
                                                location='US-MTV',
                                                capacity=20,
                                                latitude=52.37,
                                                longitude=4.88)

        response_message = search_utils.document_to_message(
            test_search_document, shelf_messages.Shelf())
        self.assertEqual(response_message, expected_message)
        self.assertTrue(response_message.enabled)
        assert mock_logging.error.call_count == 1

    def test_get_search_cursor(self):
        """Tests the creation of a search cursor with a web_safe_string."""
        expected_cursor_web_safe_string = 'False:ODUxODBhNTgyYTQ2ZmI0MDU'
        returned_cursor = (
            search_utils.get_search_cursor(expected_cursor_web_safe_string))
        self.assertEqual(expected_cursor_web_safe_string,
                         returned_cursor.web_safe_string)

    @mock.patch.object(search, 'Cursor', autospec=True)
    def test_get_search_cursor_error(self, mock_cursor):
        """Tests the creation of a search cursor when an error occurs."""
        mock_cursor.side_effect = ValueError
        with self.assertRaisesWithLiteralMatch(endpoints.BadRequestException,
                                               search_utils._CORRUPT_KEY_MSG):
            search_utils.get_search_cursor(None)

    @parameterized.named_parameters(
        {
            'testcase_name': 'QueryStringOnly',
            'request':
            shared_messages.SearchRequest(query_string='enrolled:True'),
            'expected_values': ('enrolled:True', None, [])
        },
        {
            'testcase_name':
            'QueryStringWithReturnedFields',
            'request':
            shared_messages.SearchRequest(query_string='location:US-NYC',
                                          returned_fields=['location']),
            'expected_values': ('location:US-NYC', None, ['location'])
        },
    )
    def test_set_search_query_options(self, request, expected_values):
        """Tests setting the query options without sort options from message."""
        returned_query, returned_sort_options, returned_returned_fields = (
            search_utils.set_search_query_options(request))
        expected_query, expected_sort_options, expcted_returned_fields = (
            expected_values)
        self.assertEqual(expected_sort_options, returned_sort_options)
        self.assertEqual(expected_query, returned_query)
        self.assertEqual(expcted_returned_fields, returned_returned_fields)

    @parameterized.named_parameters(
        {
            'testcase_name':
            'ExpressionWithDirection',
            'request':
            shared_messages.SearchRequest(
                query_string='enrolled:True',
                expressions=[
                    shared_messages.SearchExpression(
                        expression='enrolled',
                        direction=shared_messages.SortDirection.ASCENDING)
                ]),
            'expected_sort_options_expressions': [
                search.SortExpression(
                    expression='enrolled',
                    direction=search.SortExpression.ASCENDING)
            ]
        },
        {
            'testcase_name':
            'MultipleExpressionsWithDirection',
            'request':
            shared_messages.SearchRequest(
                query_string='enrolled:True',
                expressions=[
                    shared_messages.SearchExpression(
                        expression='enrolled',
                        direction=shared_messages.SortDirection.ASCENDING),
                    shared_messages.SearchExpression(
                        expression='serial_number',
                        direction=shared_messages.SortDirection.DESCENDING)
                ]),
            'expected_sort_options_expressions': [
                search.SortExpression(
                    expression='enrolled',
                    direction=search.SortExpression.ASCENDING),
                search.SortExpression(
                    expression='serial_number',
                    direction=search.SortExpression.DESCENDING)
            ]
        },
        {
            'testcase_name':
            'ExpressionWithoutDirection',
            'request':
            shared_messages.SearchRequest(
                query_string='enrolled:True',
                expressions=[
                    shared_messages.SearchExpression(expression='enrolled')
                ]),
            'expected_sort_options_expressions':
            [search.SortExpression(expression='enrolled')]
        },
        {
            'testcase_name':
            'MultipleExpressionsWithoutDirection',
            'request':
            shared_messages.SearchRequest(
                query_string='enrolled:True',
                expressions=[
                    shared_messages.SearchExpression(expression='enrolled'),
                    shared_messages.SearchExpression(
                        expression='serial_number')
                ]),
            'expected_sort_options_expressions': [
                search.SortExpression(
                    expression='enrolled',
                    direction=search.SortExpression.DESCENDING),
                search.SortExpression(
                    expression='serial_number',
                    direction=search.SortExpression.DESCENDING)
            ]
        },
    )
    def test_set_search_query_options_with_sort_options(
            self, request, expected_sort_options_expressions):
        """Tests setting query options with sort options from message."""
        returned_query, returned_sort_options, returned_returned_fields = (
            search_utils.set_search_query_options(request))
        del returned_query  # Unused.
        del returned_returned_fields  # Unused.
        for i in range(len(returned_sort_options.expressions)):
            self.assertEqual(returned_sort_options.expressions[i].expression,
                             expected_sort_options_expressions[i].expression)
            self.assertEqual(returned_sort_options.expressions[i].direction,
                             expected_sort_options_expressions[i].direction)
Пример #9
0
class DeviceApiTest(parameterized.TestCase, loanertest.EndpointsTestCase):
    """Tests for the Device API."""
    def setUp(self):
        super(DeviceApiTest, self).setUp()
        self.service = device_api.DeviceApi()
        self.login_admin_endpoints_user()

        # Set bootstrap to completed so that maintenance mode will not be invoked.
        config_model.Config.set('bootstrap_completed', True)
        self.shelf = shelf_model.Shelf.enroll(user_email=loanertest.USER_EMAIL,
                                              location='NYC',
                                              capacity=10,
                                              friendly_name='GnG',
                                              latitude=40.6892534,
                                              longitude=-74.0466891,
                                              altitude=1.0)

        self.device = device_model.Device()
        self.device.serial_number = '123ABC'
        self.device.asset_tag = '12345'
        self.device.enrolled = True
        self.device.device_model = 'Google Pixelbook'
        self.device.due_date = datetime.datetime(2017, 11, 15)
        self.device.last_known_healthy = datetime.datetime(2017, 11, 1)
        self.device.shelf = self.shelf.key
        self.device.assigned_user = loanertest.USER_EMAIL
        self.device.assignment_date = datetime.datetime(2017, 11, 1)
        self.device.current_ou = '/'
        self.device.ou_changed_date = datetime.datetime(2017, 11, 1)
        self.device.locked = False
        self.device.lost = False
        self.device.chrome_device_id = 'unique_id_1'
        self.device.last_heartbeat = datetime.datetime(2017, 11, 1)
        self.device.damaged = False
        self.device.damaged_reason = None
        self.device.put()
        self.device.set_last_reminder(0)
        self.device.set_next_reminder(1, datetime.timedelta(hours=2))
        device_model.Device(
            serial_number='6789',
            enrolled=True,
            device_model='HP Chromebook 13 G1',
            current_ou='/',
            chrome_device_id='unique_id_2',
            shelf=self.shelf.key,
            damaged=False,
        ).put()
        self.unenrolled_device = device_model.Device(
            serial_number='4567',
            enrolled=False,
            device_model='HP Chromebook 13 G1',
            current_ou='/',
            chrome_device_id='unique_id_3',
            damaged=False,
        )
        self.unenrolled_device.put()
        self.unenrolled_device_directory = {
            'deviceId': 'unique_id',
            'serialNumber': '4567',
            'status': 'ACTIVE',
            'lastSync': datetime.datetime.utcnow(),
            'model': 'HP Chromebook 13 G1',
            'orgUnitPath': constants.ORG_UNIT_DICT['DEFAULT'],
        }

    def tearDown(self):
        super(DeviceApiTest, self).tearDown()
        self.service = None

    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    def test_enroll(self, mock_directoryclass):
        """Tests Enroll with mock methods."""
        mock_directoryclient = mock_directoryclass.return_value
        mock_directoryclient.get_chrome_device.return_value = (
            self.unenrolled_device)
        mock_directoryclient.get_chrome_device_by_serial.return_value = (
            self.unenrolled_device_directory)
        retrieved_device = device_model.Device.get(
            serial_number=self.unenrolled_device.serial_number)
        self.assertFalse(retrieved_device.enrolled)

        request = device_messages.DeviceRequest(
            serial_number=self.unenrolled_device.serial_number)
        with mock.patch.object(self.service, 'check_xsrf_token',
                               autospec=True) as mock_xsrf_token:
            response = self.service.enroll(request)
            self.assertIsInstance(response, message_types.VoidMessage)
            retrieved_device = device_model.Device.get(
                serial_number=self.unenrolled_device.serial_number)
            self.assertTrue(retrieved_device.enrolled)
            self.assertEqual(mock_xsrf_token.call_count, 1)

    @parameterized.parameters((datastore_errors.BadValueError, ),
                              (device_model.DeviceCreationError, ))
    @mock.patch.object(device_model, 'Device', autospec=True)
    def test_enroll_error(self, test_error, mock_device_cls):
        mock_device_cls.enroll.side_effect = test_error
        request = device_messages.DeviceRequest(
            serial_number=self.unenrolled_device.serial_number)
        with self.assertRaises(endpoints.BadRequestException):
            self.service.enroll(request)

    @mock.patch.object(device_model, 'Device', autospec=True)
    def test_unenroll_error(self, mock_device_cls):
        mock_device_cls.get.return_value.unenroll.side_effect = (
            device_model.FailedToUnenrollError())
        request = device_messages.DeviceRequest(
            serial_number=self.unenrolled_device.serial_number)
        with self.assertRaises(endpoints.BadRequestException):
            self.service.unenroll(request)

    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_unenroll(self, mock_xsrf_token, mock_directoryclass):
        mock_directoryclient = mock_directoryclass.return_value
        mock_directoryclient.move_chrome_device_org_unit.return_value = (
            loanertest.TEST_DIR_DEVICE_DEFAULT)
        request = device_messages.DeviceRequest(
            serial_number=self.device.serial_number)
        self.assertTrue(self.device.enrolled)
        response = self.service.unenroll(request)
        self.assertFalse(self.device.enrolled)
        self.assertIsNone(self.device.assigned_user)
        self.assertIsNone(self.device.due_date)
        self.assertIsInstance(response, message_types.VoidMessage)
        self.assertEqual(mock_xsrf_token.call_count, 1)

    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_unlock(self, mock_xsrf_token, mock_directoryclass):
        self.device.lost = True
        self.device.locked = True
        self.device.put()
        mock_directoryclient = mock_directoryclass.return_value
        mock_directoryclient.move_chrome_device_org_unit.return_value = (
            loanertest.TEST_DIR_DEVICE_DEFAULT)
        request = device_messages.DeviceRequest(
            serial_number=self.device.serial_number)
        response = self.service.unlock(request)
        self.assertFalse(self.device.locked)
        self.assertFalse(self.device.lost)
        self.assertIsInstance(response, message_types.VoidMessage)
        self.assertEqual(mock_xsrf_token.call_count, 1)

    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    def test_unlock_directory_error(self, mock_directory_class):
        mock_directory_client = mock_directory_class.return_value
        mock_directory_client.reenable_chrome_device.side_effect = (
            directory.DirectoryRPCError)
        with self.assertRaises(endpoints.BadRequestException):
            self.service.unlock(
                device_messages.DeviceRequest(asset_tag='12345'))

    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    def test_unlock_move_ou_error(self, mock_directory_class):
        del mock_directory_class  # Unused.
        with mock.patch.object(
                self.device,
                'move_to_default_ou',
                side_effect=device_model.UnableToMoveToDefaultOUError):
            with self.assertRaises(endpoints.BadRequestException):
                self.service.unlock(
                    device_messages.DeviceRequest(asset_tag='12345'))

    @mock.patch('__main__.device_model.Device.device_audit_check')
    def test_device_audit_check(self, mock_device_audit_check):
        request = device_messages.DeviceRequest(identifier='6765')
        self.assertRaisesRegexp(device_api.endpoints.NotFoundException,
                                device_api._NO_DEVICE_MSG % '6765',
                                self.service.device_audit_check, request)

        device_model.Device(serial_number='12345',
                            enrolled=True,
                            device_model='HP Chromebook 13 G1',
                            current_ou='/',
                            chrome_device_id='unique_id_1',
                            damaged=False).put()
        request = device_messages.DeviceRequest(identifier='12345')
        response = self.service.device_audit_check(request)
        self.assertEqual(mock_device_audit_check.call_count, 1)
        self.assertIsInstance(response, message_types.VoidMessage)

    def test_device_audit_check_device_not_enrolled(self):
        request = device_messages.DeviceRequest(
            identifier=self.device.serial_number)
        self.device.enrolled = False
        with self.assertRaises(device_api.endpoints.BadRequestException):
            self.service.device_audit_check(request)

    def test_device_audit_check_device_damaged(self):
        request = device_messages.DeviceRequest(
            identifier=self.device.serial_number)
        self.device.damaged = True
        with self.assertRaises(device_api.endpoints.BadRequestException):
            self.service.device_audit_check(request)

    def test_device_audit_check_audit_error(self):
        request = device_messages.DeviceRequest(
            identifier=self.device.serial_number)
        self.testbed.mock_raiseevent.side_effect = device_model.DeviceAuditEventError
        with self.assertRaises(device_api.endpoints.BadRequestException):
            self.service.device_audit_check(request)

    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    def test_get_device_not_found(self, mock_directory_class):
        mock_directory_client = mock_directory_class.return_value
        mock_directory_client.given_name.return_value = 'given name value'
        request = device_messages.DeviceRequest(identifier='not-found')
        with self.assertRaises(device_api.endpoints.NotFoundException):
            self.service.get_device(request)

    def test_get_device_unenrolled(self):
        request = device_messages.DeviceRequest(
            identifier=self.device.serial_number)
        self.device.enrolled = False
        with self.assertRaises(device_api.endpoints.BadRequestException):
            self.service.get_device(request)

    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    def test_get_device(self, mock_directory_class):
        mock_directory_client = mock_directory_class.return_value
        mock_directory_client.given_name.return_value = 'given name value'
        asset_tag_response = self.service.get_device(
            device_messages.DeviceRequest(asset_tag='12345'))
        chrome_device_id_response = self.service.get_device(
            device_messages.DeviceRequest(chrome_device_id='unique_id_1'))
        serial_number_response = self.service.get_device(
            device_messages.DeviceRequest(serial_number='123ABC'))
        urlkey_response = self.service.get_device(
            device_messages.DeviceRequest(urlkey=self.device.key.urlsafe()))
        identifier_response = self.service.get_device(
            device_messages.DeviceRequest(identifier='123ABC'))

        self.assertIsInstance(asset_tag_response, device_messages.Device)
        self.assertIsInstance(chrome_device_id_response,
                              device_messages.Device)
        self.assertIsInstance(serial_number_response, device_messages.Device)
        self.assertIsInstance(urlkey_response, device_messages.Device)
        self.assertIsInstance(identifier_response, device_messages.Device)
        self.assertEqual(self.device.serial_number,
                         asset_tag_response.serial_number)
        self.assertEqual(self.device.device_model,
                         urlkey_response.device_model)

    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    def test_get_device_no_permission(self, mock_directory_class):
        mock_directory_client = mock_directory_class.return_value
        mock_directory_client.given_name.return_value = 'given name value'
        email = 'random@{}'.format(loanertest.USER_DOMAIN)
        self.login_endpoints_user(email=email)
        with self.assertRaises(endpoints.UnauthorizedException):
            self.service.get_device(
                device_messages.DeviceRequest(
                    serial_number=self.device.serial_number))

    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    def test_get_device_has_permission(self, mock_directory_class):
        mock_directory_client = mock_directory_class.return_value
        mock_directory_client.given_name.return_value = 'given name value'
        device = self.service.get_device(
            device_messages.DeviceRequest(
                serial_number=self.device.serial_number))
        self.assertIsInstance(device, device_messages.Device)
        self.assertEqual(device.serial_number, self.device.serial_number)

    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    def test_get_device_assigned_user(self, mock_directory_class):
        mock_directory_client = mock_directory_class.return_value
        mock_directory_client.given_name.return_value = 'given name value'
        email = 'random@{}'.format(loanertest.USER_DOMAIN)
        self.login_endpoints_user(email=email)
        self.device.assigned_user = email
        self.device.put()
        device = self.service.get_device(
            device_messages.DeviceRequest(
                serial_number=self.device.serial_number))
        self.assertIsInstance(device, device_messages.Device)
        self.assertEqual(device.serial_number, self.device.serial_number)

    @parameterized.parameters(directory.DirectoryRPCError,
                              directory.GivenNameDoesNotExistError)
    @mock.patch.object(directory, 'DirectoryApiClient', autospec=True)
    def test_get_device_directory_errors(self, test_error,
                                         mock_directory_class):
        request = device_messages.DeviceRequest(asset_tag='12345')
        mock_directory_client = mock_directory_class.return_value
        mock_directory_client.given_name.side_effect = test_error
        self.assertIsNone(self.service.get_device(request).given_name)

    @parameterized.parameters((
        device_messages.Device(enrolled=True),
        2,
    ), (
        device_messages.Device(current_ou='/'),
        2,
    ), (
        device_messages.Device(enrolled=False),
        1,
    ), (
        device_messages.Device(query=shared_messages.SearchRequest(
            query_string='sn:6789')),
        1,
    ), (
        device_messages.Device(query=shared_messages.SearchRequest(
            query_string='at:12345')),
        1,
    ))
    def test_list_devices(self, request, response_length):
        response = self.service.list_devices(request)
        self.assertLen(response.devices, response_length)

    def test_list_devices_with_search_constraints(self):
        expressions = shared_messages.SearchExpression(
            expression='serial_number')
        expected_response = device_messages.ListDevicesResponse(
            devices=[device_messages.Device(serial_number='6789')],
            has_additional_results=False)
        request = device_messages.Device(query=shared_messages.SearchRequest(
            query_string='sn:6789',
            expressions=[expressions],
            returned_fields=['serial_number']))
        response = self.service.list_devices(request)
        self.assertEqual(response.devices[0].serial_number,
                         expected_response.devices[0].serial_number)
        self.assertFalse(response.has_additional_results)

    def test_list_devices_with_filter_message(self):
        message = device_messages.Device(enrolled=True,
                                         device_model='HP Chromebook 13 G1',
                                         current_ou='/')
        filters = api_utils.to_dict(message, device_model.Device)
        request = device_messages.Device(**filters)
        response = self.service.list_devices(request)
        expected_response = device_messages.ListDevicesResponse(
            devices=[
                device_messages.Device(serial_number='6789',
                                       identifier='6789',
                                       enrolled=True,
                                       device_model='HP Chromebook 13 G1',
                                       current_ou='/',
                                       locked=False,
                                       lost=False,
                                       chrome_device_id='unique_id_2',
                                       damaged=False,
                                       guest_permitted=True,
                                       onboarded=False)
            ],
            has_additional_results=False)
        self.assertEqual(response, expected_response)

    @mock.patch('__main__.device_api.shelf_api.get_shelf')
    def test_list_devices_with_shelf_filter(self, mock_get_shelf):
        # Test for shelf location as filter.
        mock_get_shelf.return_value = self.shelf
        shelf_request_message = shelf_messages.ShelfRequest(
            location=self.shelf.location)
        message = shelf_messages.Shelf(shelf_request=shelf_request_message)
        request = device_messages.Device(shelf=message)
        response = self.service.list_devices(request)
        mock_get_shelf.assert_called_once_with(shelf_request_message)
        self.assertLen(response.devices, 2)

    def test_list_devices_with_page_token(self):
        request = device_messages.Device(enrolled=True, page_size=1)
        response_devices = []
        while True:
            response = self.service.list_devices(request)
            for device in response.devices:
                response_devices.append(device)
            request = device_messages.Device(enrolled=True,
                                             page_size=1,
                                             page_token=response.page_token)
            if not response.has_additional_results:
                break
        self.assertLen(response_devices, 2)

    @mock.patch.object(search_utils,
                       'to_query',
                       return_value='enrolled:enrolled',
                       autospec=True)
    def test_list_devices_with_malformed_page_token(self, mock_to_query):
        """Test list devices with a fake token, raises BadRequestException."""
        request = device_messages.Device(page_token='malformedtoken')
        with self.assertRaises(endpoints.BadRequestException):
            self.service.list_devices(request)

    def test_list_devices_inactive_no_shelf(self):
        request = device_messages.Device(enrolled=False, page_size=1)
        response = self.service.list_devices(request)
        expected_response = device_messages.ListDevicesResponse(
            devices=[
                device_messages.Device(
                    serial_number=self.unenrolled_device.serial_number,
                    identifier=self.unenrolled_device.serial_number,
                    enrolled=self.unenrolled_device.enrolled,
                    device_model=self.unenrolled_device.device_model,
                    current_ou=self.unenrolled_device.current_ou,
                    locked=self.unenrolled_device.locked,
                    lost=self.unenrolled_device.lost,
                    chrome_device_id=self.unenrolled_device.chrome_device_id,
                    damaged=self.unenrolled_device.damaged,
                    guest_permitted=True,
                    onboarded=False)
            ],
            has_additional_results=False)
        self.assertEqual(expected_response, response)

    @mock.patch('__main__.device_model.Device.list_by_user')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_list_user_devices(self, mock_xsrf_token, mock_list_by_user):
        self.login_endpoints_user()
        device2 = device_model.Device()
        device2.serial_number = '123ABC'
        device2.assigned_user = loanertest.USER_EMAIL
        device2.assignment_date = datetime.datetime(2017, 11, 1)
        device2.due_date = datetime.datetime(2017, 11, 4)
        device2.put()

        request = message_types.VoidMessage()
        mock_list_by_user.return_value = [self.device, device2]
        response = self.service.list_user_devices(request)
        self.assertEqual(response.devices[0].serial_number,
                         self.device.serial_number)
        self.assertLen(response.devices, 2)
        self.assertEqual(mock_xsrf_token.call_count, 1)
        mock_list_by_user.assert_called_once_with(loanertest.USER_EMAIL)

    @mock.patch('__main__.device_model.Device.enable_guest_mode')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_enable_guest_mode(self, mock_xsrf_token, mock_enableguest):
        config_model.Config.set('allow_guest_mode', True)
        self.login_endpoints_user()
        self.service.enable_guest_mode(
            device_messages.DeviceRequest(urlkey=self.device.key.urlsafe()))
        self.assertTrue(mock_enableguest.called)
        self.assertEqual(mock_xsrf_token.call_count, 1)

        mock_xsrf_token.reset_mock()
        mock_enableguest.reset_mock()
        self.service.enable_guest_mode(
            device_messages.DeviceRequest(
                chrome_device_id=self.device.chrome_device_id))
        self.assertTrue(mock_enableguest.called)
        self.assertEqual(mock_xsrf_token.call_count, 1)

        def guest_disabled_error(*args, **kwargs):
            del args, kwargs  # Unused.
            raise device_model.GuestNotAllowedError(
                device_model._GUEST_MODE_DISABLED_MSG)

        def directory_error(*args, **kwargs):
            del args, kwargs  # Unused.
            raise device_model.EnableGuestError(
                'Directory broke, all your fault.')

        mock_enableguest.side_effect = guest_disabled_error
        with self.assertRaises(endpoints.UnauthorizedException):
            self.service.enable_guest_mode(
                device_messages.DeviceRequest(
                    chrome_device_id=self.device.chrome_device_id))

        mock_enableguest.side_effect = directory_error
        with self.assertRaises(endpoints.InternalServerErrorException):
            self.service.enable_guest_mode(
                device_messages.DeviceRequest(
                    chrome_device_id=self.device.chrome_device_id))

    def test_enable_guest_unassigned(self):
        config_model.Config.set('allow_guest_mode', True)
        self.device.assigned_user = None
        self.device.put()
        with self.assertRaisesRegexp(
                endpoints.UnauthorizedException,
                device_model._UNASSIGNED_DEVICE % self.device.identifier):
            self.service.enable_guest_mode(
                device_messages.DeviceRequest(
                    urlkey=self.device.key.urlsafe()))

    @mock.patch('__main__.device_model.Device.loan_extend')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_extend_loan(self, mock_xsrf_token, mock_loanextend):
        tomorrow = datetime.datetime.utcnow() + datetime.timedelta(days=1)
        self.login_endpoints_user()
        self.service.extend_loan(
            device_messages.ExtendLoanRequest(
                device=device_messages.DeviceRequest(
                    urlkey=self.device.key.urlsafe()),
                extend_date=tomorrow))
        mock_loanextend.assert_called_once_with(
            user_email=loanertest.USER_EMAIL, extend_date_time=tomorrow)
        self.assertEqual(mock_xsrf_token.call_count, 1)

        mock_xsrf_token.reset_mock()
        mock_loanextend.reset_mock()
        self.service.extend_loan(
            device_messages.ExtendLoanRequest(
                device=device_messages.DeviceRequest(
                    chrome_device_id=self.device.chrome_device_id),
                extend_date=tomorrow))
        mock_loanextend.assert_called_once_with(
            user_email=loanertest.USER_EMAIL, extend_date_time=tomorrow)
        self.assertEqual(mock_xsrf_token.call_count, 1)

        mock_loanextend.side_effect = device_model.ExtendError
        self.assertRaises(
            device_api.endpoints.BadRequestException, self.service.extend_loan,
            device_messages.ExtendLoanRequest(
                device=device_messages.DeviceRequest(
                    chrome_device_id=self.device.chrome_device_id),
                extend_date=tomorrow))

    def test_extend_loan_unassigned(self):
        self.device.assigned_user = None
        self.device.put()
        with self.assertRaisesRegexp(
                endpoints.UnauthorizedException,
                device_model._UNASSIGNED_DEVICE % self.device.identifier):
            self.service.extend_loan(
                device_messages.ExtendLoanRequest(
                    device=device_messages.DeviceRequest(
                        chrome_device_id=self.device.chrome_device_id)))

    @mock.patch('__main__.device_model.Device.mark_damaged')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_mark_damaged(self, mock_xsrf_token, mock_markdamaged):
        self.login_endpoints_user()
        self.service.mark_damaged(
            device_messages.DamagedRequest(
                device=device_messages.DeviceRequest(
                    urlkey=self.device.key.urlsafe()),
                damaged_reason='Foo'))
        mock_markdamaged.assert_called_once_with(
            user_email=loanertest.USER_EMAIL, damaged_reason='Foo')
        self.assertEqual(mock_xsrf_token.call_count, 1)

        mock_xsrf_token.reset_mock()
        mock_markdamaged.reset_mock()
        self.service.mark_damaged(
            device_messages.DamagedRequest(  # No reason given.
                device=device_messages.DeviceRequest(
                    urlkey=self.device.key.urlsafe())))
        mock_markdamaged.assert_called_once_with(
            user_email=loanertest.USER_EMAIL, damaged_reason=None)
        self.assertEqual(mock_xsrf_token.call_count, 1)

    @mock.patch.object(device_model.Device, 'mark_damaged')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_mark_damaged__unauthorized(self, mock_xsrf, mock_markdamaged):
        del mock_xsrf  # Unused.
        self.login_endpoints_user()
        mock_markdamaged.side_effect = device_model.UnauthorizedError()
        with self.assertRaises(endpoints.UnauthorizedException):
            self.service.mark_damaged(
                device_messages.DamagedRequest(
                    device=device_messages.DeviceRequest(
                        urlkey=self.device.key.urlsafe()),
                    damaged_reason='Foo'))

    def test_mark_undamaged(self):
        with mock.patch.object(self.device,
                               'mark_undamaged') as mock_markundamaged:
            self.service.mark_undamaged(
                device_messages.DeviceRequest(
                    urlkey=self.device.key.urlsafe()))
            mock_markundamaged.assert_called_once_with(
                user_email=loanertest.SUPER_ADMIN_EMAIL)

    @mock.patch.object(device_model.Device, 'mark_undamaged')
    def test_mark_undamaged__unauthorized(self, mock_markundamaged):
        self.login_endpoints_user()
        with mock.patch.object(self.service,
                               'check_xsrf_token') as mock_xsrf_token:
            mock_markundamaged.side_effect = device_model.UnauthorizedError()
            with self.assertRaises(endpoints.UnauthorizedException):
                self.service.mark_undamaged(
                    device_messages.DeviceRequest(
                        urlkey=self.device.key.urlsafe()))
            self.assertEqual(mock_xsrf_token.call_count, 1)

    @mock.patch('__main__.device_model.Device.mark_lost')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_mark_lost(self, mock_xsrf_token, mock_marklost):
        self.service.mark_lost(
            device_messages.DeviceRequest(urlkey=self.device.key.urlsafe()))
        mock_marklost.assert_called_once_with(
            user_email=loanertest.SUPER_ADMIN_EMAIL)
        self.assertEqual(mock_xsrf_token.call_count, 1)

    @mock.patch.object(device_model.Device, 'mark_lost')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_mark_lost__unauthorized(self, mock_xsrf_token, mock_marklost):
        del mock_xsrf_token  # Unused.
        mock_marklost.side_effect = device_model.UnauthorizedError()
        with self.assertRaises(endpoints.UnauthorizedException):
            self.service.mark_lost(
                device_messages.DeviceRequest(
                    urlkey=self.device.key.urlsafe()))

    @mock.patch('__main__.device_model.Device.mark_pending_return')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_mark_pending_return(self, mock_xsrf_token, mock_markreturned):
        self.login_endpoints_user()
        self.service.mark_pending_return(
            device_messages.DeviceRequest(urlkey=self.device.key.urlsafe()))
        mock_markreturned.assert_called_once_with(
            user_email=loanertest.USER_EMAIL)
        self.assertEqual(mock_xsrf_token.call_count, 1)

    def test_mark_pending_return_unassigned(self):
        self.device.assigned_user = None
        self.device.put()
        with self.assertRaisesRegexp(
                endpoints.UnauthorizedException,
                device_model._UNASSIGNED_DEVICE % self.device.identifier):
            self.service.mark_pending_return(
                device_messages.DeviceRequest(
                    urlkey=self.device.key.urlsafe()))

    @mock.patch.object(device_model.Device, 'complete_onboard')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_complete_onboard(self, mock_xsrf_token, mock_completeonboard):
        self.login_endpoints_user()
        self.service.complete_onboard(
            device_messages.DeviceRequest(urlkey=self.device.key.urlsafe()))
        mock_completeonboard.assert_called_once_with(
            user_email=loanertest.USER_EMAIL)
        self.assertEqual(mock_xsrf_token.call_count, 1)

    @mock.patch('__main__.device_model.Device.resume_loan')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_resume_loan(self, mock_xsrf_token, mock_resume_loan):
        self.login_endpoints_user()
        self.service.resume_loan(
            device_messages.DeviceRequest(urlkey=self.device.key.urlsafe()))
        self.assertEqual(mock_resume_loan.call_count, 1)
        self.assertEqual(mock_xsrf_token.call_count, 1)

    @mock.patch.object(device_model.Device, 'resume_loan')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_resume_loan__unauthorized(self, mock_xsrf_token,
                                       mock_resume_loan):
        del mock_xsrf_token  # Unused.
        self.login_endpoints_user()
        mock_resume_loan.side_effect = device_model.UnauthorizedError()
        with self.assertRaises(endpoints.UnauthorizedException):
            self.service.resume_loan(
                device_messages.DeviceRequest(
                    urlkey=self.device.key.urlsafe()))

    @mock.patch.object(bigquery, 'BigQueryClient')
    @mock.patch.object(root_api.Service, 'check_xsrf_token', autospec=True)
    def test_get_history(self, mock_xsrf_token, mock_bigquery):
        device_request = device_messages.DeviceRequest()
        device_request.asset_tag = '12345'
        request = device_messages.HistoryRequest(device=device_request)

        device_response = device_messages.Device()
        device_response.asset_tag = '12345'

        expected_response = device_messages.HistoryResponse()
        for _ in range(2):
            expected_response.devices.append(device_response)
            expected_response.timestamp.append(
                datetime.datetime(2019, 10, 22, 20, 43, 37))
            expected_response.actor.append('*****@*****.**')
            expected_response.summary.append(
                'Beginning new loan for user [email protected] with device 12345.'
            )

        bigquery_response = [
            (u"Key('Device', 5158133238333440)",
             datetime.datetime(2019, 10, 22, 20, 43, 37),
             u'*****@*****.**', u'enable_guest_mode',
             u'Beginning new loan for user [email protected] with device 12345.',
             {
                 u'ou_changed_date': datetime.datetime(2019, 10, 22, 20, 43,
                                                       37),
                 u'current_ou': u'/',
                 u'shelf': None,
                 u'due_date': datetime.datetime(2019, 10, 22, 20, 43, 37),
                 u'chrome_device_id': u'unique_id_1',
                 u'mark_pending_return_date': None,
                 u'asset_tag': u'12345',
                 u'last_known_healthy':
                 datetime.datetime(2019, 10, 22, 20, 43, 37),
                 u'locked': False,
                 u'last_reminder': {
                     u'count': 1,
                     u'time': datetime.datetime(2019, 10, 22, 20, 43, 37),
                     u'level': 1
                 },
                 u'next_reminder': None,
                 u'device_model': u'Chromebook',
                 u'enrolled': True,
                 u'serial_number': u'123ABC',
                 u'damaged': False,
                 u'onboarded': True,
                 u'assignment_date': datetime.datetime(2019, 10, 22, 20, 43,
                                                       37),
                 u'damaged_reason': None,
                 u'assigned_user': u'*****@*****.**',
                 u'lost': False,
                 u'last_heartbeat': datetime.datetime(2019, 10, 22, 20, 43, 37)
             }),
            (u"Key('Device', 5158133238333440)",
             datetime.datetime(2019, 10, 22, 20, 43, 37),
             u'*****@*****.**', u'enable_guest_mode',
             u'Beginning new loan for user [email protected] with device 12345.',
             {
                 u'ou_changed_date': datetime.datetime(2019, 10, 22, 20, 43,
                                                       37),
                 u'current_ou': u'/',
                 u'shelf': None,
                 u'due_date': datetime.datetime(2019, 10, 22, 20, 43, 37),
                 u'chrome_device_id': u'unique_id_1',
                 u'mark_pending_return_date': None,
                 u'asset_tag': u'12345',
                 u'last_known_healthy':
                 datetime.datetime(2019, 10, 22, 20, 43, 37),
                 u'locked': False,
                 u'last_reminder': {
                     u'count': 1,
                     u'time': datetime.datetime(2019, 10, 22, 20, 43, 37),
                     u'level': 1
                 },
                 u'next_reminder': None,
                 u'device_model': u'Chromebook',
                 u'enrolled': True,
                 u'serial_number': u'123ABC',
                 u'damaged': False,
                 u'onboarded': True,
                 u'assignment_date': datetime.datetime(2019, 10, 22, 20, 43,
                                                       37),
                 u'damaged_reason': None,
                 u'assigned_user': u'*****@*****.**',
                 u'lost': False,
                 u'last_heartbeat': datetime.datetime(2019, 10, 22, 20, 43, 37)
             }),
        ]
        mock_bigquery_client = mock.Mock()
        mock_bigquery_client.get_device_info.return_value = bigquery_response
        mock_bigquery.return_value = mock_bigquery_client

        actual_response = self.service.get_history(request)

        self.assertEqual(actual_response, expected_response)

    def test_get_device_errors(self):
        # No identifiers.
        with self.assertRaises(endpoints.BadRequestException):
            device_api._get_device(device_messages.DeviceRequest())

        # URL-safe key that is still URL-safe, but technically not a key.
        with self.assertRaises(device_api.endpoints.BadRequestException):
            device_api._get_device(
                device_messages.DeviceRequest(urlkey='bad-key'))