# Copyright 2018 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Tests for backend.api.shelf_api."""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

from absl.testing import parameterized
import mock

from protorpc import message_types

from google.appengine.api import search

import endpoints

from loaner.web_app.backend.api import root_api  # pylint: disable=unused-import
from loaner.web_app.backend.api import shelf_api
from loaner.web_app.backend.api.messages import shared_messages
from loaner.web_app.backend.api.messages import shelf_messages
from loaner.web_app.backend.models import device_model
from loaner.web_app.backend.models import shelf_model  # pylint: disable=unused-import
from loaner.web_app.backend.testing import loanertest


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)
    self.assertEqual(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)
    self.assertEqual(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)
    self.assertEqual(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)
    self.assertEqual(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)
    self.assertEqual(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.assertIsNone(self.device2_key.get().shelf)
    self.assertIsNone(self.device4_key.get().shelf)

  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)


if __name__ == '__main__':
  loanertest.main()

[((111, 3, 111, 59), 'mock.patch', 'mock.patch', ({(111, 14, 111, 58): '"""__main__.root_api.Service.check_xsrf_token"""'}, {}), "('__main__.root_api.Service.check_xsrf_token')", False, 'import mock\n'), ((112, 3, 112, 50), 'mock.patch', 'mock.patch', ({(112, 14, 112, 49): '"""__main__.shelf_model.Shelf.enroll"""'}, {}), "('__main__.shelf_model.Shelf.enroll')", False, 'import mock\n'), ((136, 3, 136, 59), 'mock.patch', 'mock.patch', ({(136, 14, 136, 58): '"""__main__.root_api.Service.check_xsrf_token"""'}, {}), "('__main__.root_api.Service.check_xsrf_token')", False, 'import mock\n'), ((151, 3, 151, 59), 'mock.patch', 'mock.patch', ({(151, 14, 151, 58): '"""__main__.root_api.Service.check_xsrf_token"""'}, {}), "('__main__.root_api.Service.check_xsrf_token')", False, 'import mock\n'), ((172, 3, 172, 59), 'mock.patch', 'mock.patch', ({(172, 14, 172, 58): '"""__main__.root_api.Service.check_xsrf_token"""'}, {}), "('__main__.root_api.Service.check_xsrf_token')", False, 'import mock\n'), ((221, 3, 221, 59), 'mock.patch', 'mock.patch', ({(221, 14, 221, 58): '"""__main__.root_api.Service.check_xsrf_token"""'}, {}), "('__main__.root_api.Service.check_xsrf_token')", False, 'import mock\n'), ((222, 3, 222, 48), 'mock.patch', 'mock.patch', ({(222, 14, 222, 47): '"""__main__.shelf_api.logging.info"""'}, {}), "('__main__.shelf_api.logging.info')", False, 'import mock\n'), ((246, 3, 246, 51), 'mock.patch.object', 'mock.patch.object', ({(246, 21, 246, 40): 'device_model.Device', (246, 42, 246, 50): '"""search"""'}, {}), "(device_model.Device, 'search')", False, 'import mock\n'), ((247, 3, 247, 59), 'mock.patch.object', 'mock.patch.object', (), '', False, 'import mock\n'), ((294, 2, 294, 19), 'loaner.web_app.backend.testing.loanertest.main', 'loanertest.main', ({}, {}), '()', False, 'from loaner.web_app.backend.testing import loanertest\n'), ((44, 29, 45, 61), 'mock.patch', 'mock.patch', ({(45, 8, 45, 60): '"""__main__.device_model.directory.DirectoryApiClient"""'}, {}), "('__main__.device_model.directory.DirectoryApiClient')", False, 'import mock\n'), ((48, 19, 48, 39), 'loaner.web_app.backend.api.shelf_api.ShelfApi', 'shelf_api.ShelfApi', ({}, {}), '()', False, 'from loaner.web_app.backend.api import shelf_api\n'), ((50, 24, 51, 63), 'mock.patch', 'mock.patch', ({(51, 8, 51, 62): '"""__main__.shelf_api.root_api.Service.check_xsrf_token"""'}, {}), "('__main__.shelf_api.root_api.Service.check_xsrf_token')", False, 'import mock\n'), ((52, 17, 55, 21), 'loaner.web_app.backend.models.shelf_model.Shelf.enroll', 'shelf_model.Shelf.enroll', (), '', False, 'from loaner.web_app.backend.models import shelf_model\n'), ((56, 13, 57, 70), 'loaner.web_app.backend.models.shelf_model.Shelf.enroll', 'shelf_model.Shelf.enroll', (), '', False, 'from loaner.web_app.backend.models import shelf_model\n'), ((58, 13, 59, 70), 'loaner.web_app.backend.models.shelf_model.Shelf.enroll', 'shelf_model.Shelf.enroll', (), '', False, 'from loaner.web_app.backend.models import shelf_model\n'), ((60, 26, 62, 28), 'loaner.web_app.backend.models.shelf_model.Shelf.enroll', 'shelf_model.Shelf.enroll', (), '', False, 'from loaner.web_app.backend.models import shelf_model\n'), ((115, 14, 118, 68), 'loaner.web_app.backend.api.messages.shelf_messages.EnrollShelfRequest', 'shelf_messages.EnrollShelfRequest', (), '', False, 'from loaner.web_app.backend.api.messages import shelf_messages\n'), ((124, 14, 124, 60), 'loaner.web_app.backend.api.messages.shelf_messages.EnrollShelfRequest', 'shelf_messages.EnrollShelfRequest', (), '', False, 'from loaner.web_app.backend.api.messages import shelf_messages\n'), ((129, 14, 130, 51), 'loaner.web_app.backend.api.messages.shelf_messages.EnrollShelfRequest', 'shelf_messages.EnrollShelfRequest', (), '', False, 'from loaner.web_app.backend.api.messages import shelf_messages\n'), ((138, 14, 138, 57), 'loaner.web_app.backend.api.messages.shelf_messages.ShelfRequest', 'shelf_messages.ShelfRequest', (), '', False, 'from loaner.web_app.backend.api.messages import shelf_messages\n'), ((145, 14, 145, 57), 'loaner.web_app.backend.api.messages.shelf_messages.ShelfRequest', 'shelf_messages.ShelfRequest', (), '', False, 'from loaner.web_app.backend.api.messages import shelf_messages\n'), ((159, 12, 159, 54), 'loaner.web_app.backend.models.shelf_model.Shelf.get', 'shelf_model.Shelf.get', (), '', False, 'from loaner.web_app.backend.models import shelf_model\n'), ((184, 18, 184, 73), 'loaner.web_app.backend.api.messages.shared_messages.SearchExpression', 'shared_messages.SearchExpression', (), '', False, 'from loaner.web_app.backend.api.messages import shared_messages\n'), ((202, 14, 202, 76), 'loaner.web_app.backend.api.messages.shelf_messages.Shelf', 'shelf_messages.Shelf', (), '', False, 'from loaner.web_app.backend.api.messages import shelf_messages\n'), ((208, 14, 208, 76), 'loaner.web_app.backend.api.messages.shelf_messages.Shelf', 'shelf_messages.Shelf', (), '', False, 'from loaner.web_app.backend.api.messages import shelf_messages\n'), ((215, 14, 215, 76), 'loaner.web_app.backend.api.messages.shelf_messages.Shelf', 'shelf_messages.Shelf', (), '', False, 'from loaner.web_app.backend.api.messages import shelf_messages\n'), ((275, 12, 275, 40), 'loaner.web_app.backend.api.shelf_api.get_shelf', 'shelf_api.get_shelf', ({(275, 32, 275, 39): 'request'}, {}), '(request)', False, 'from loaner.web_app.backend.api import shelf_api\n'), ((280, 14, 280, 71), 'loaner.web_app.backend.api.messages.shelf_messages.ShelfRequest', 'shelf_messages.ShelfRequest', (), '', False, 'from loaner.web_app.backend.api.messages import shelf_messages\n'), ((281, 12, 281, 40), 'loaner.web_app.backend.api.shelf_api.get_shelf', 'shelf_api.get_shelf', ({(281, 32, 281, 39): 'request'}, {}), '(request)', False, 'from loaner.web_app.backend.api import shelf_api\n'), ((286, 14, 286, 63), 'loaner.web_app.backend.api.messages.shelf_messages.ShelfRequest', 'shelf_messages.ShelfRequest', (), '', False, 'from loaner.web_app.backend.api.messages import shelf_messages\n'), ((164, 7, 164, 40), 'loaner.web_app.backend.api.messages.shelf_messages.Shelf', 'shelf_messages.Shelf', (), '', False, 'from loaner.web_app.backend.api.messages import shelf_messages\n'), ((165, 7, 165, 42), 'loaner.web_app.backend.api.messages.shelf_messages.Shelf', 'shelf_messages.Shelf', (), '', False, 'from loaner.web_app.backend.api.messages import shelf_messages\n'), ((180, 16, 180, 49), 'loaner.web_app.backend.api.messages.shelf_messages.Shelf', 'shelf_messages.Shelf', (), '', False, 'from loaner.web_app.backend.api.messages import shelf_messages\n'), ((231, 25, 231, 74), 'loaner.web_app.backend.models.device_model.Device.get', 'device_model.Device.get', (), '', False, 'from loaner.web_app.backend.models import device_model\n'), ((290, 6, 290, 34), 'loaner.web_app.backend.api.shelf_api.get_shelf', 'shelf_api.get_shelf', ({(290, 26, 290, 33): 'request'}, {}), '(request)', False, 'from loaner.web_app.backend.api import shelf_api\n'), ((68, 23, 75, 5), 'loaner.web_app.backend.models.device_model.Device', 'device_model.Device', (), '', False, 'from loaner.web_app.backend.models import device_model\n'), ((76, 23, 83, 5), 'loaner.web_app.backend.models.device_model.Device', 'device_model.Device', (), '', False, 'from loaner.web_app.backend.models import device_model\n'), ((84, 23, 92, 5), 'loaner.web_app.backend.models.device_model.Device', 'device_model.Device', (), '', False, 'from loaner.web_app.backend.models import device_model\n'), ((93, 23, 101, 5), 'loaner.web_app.backend.models.device_model.Device', 'device_model.Device', (), '', False, 'from loaner.web_app.backend.models import device_model\n'), ((154, 22, 154, 65), 'loaner.web_app.backend.api.messages.shelf_messages.ShelfRequest', 'shelf_messages.ShelfRequest', (), '', False, 'from loaner.web_app.backend.api.messages import shelf_messages\n'), ((193, 14, 196, 41), 'loaner.web_app.backend.api.messages.shared_messages.SearchRequest', 'shared_messages.SearchRequest', (), '', False, 'from loaner.web_app.backend.api.messages import shared_messages\n'), ((225, 22, 225, 65), 'loaner.web_app.backend.api.messages.shelf_messages.ShelfRequest', 'shelf_messages.ShelfRequest', (), '', False, 'from loaner.web_app.backend.api.messages import shelf_messages\n'), ((239, 22, 239, 65), 'loaner.web_app.backend.api.messages.shelf_messages.ShelfRequest', 'shelf_messages.ShelfRequest', (), '', False, 'from loaner.web_app.backend.api.messages import shelf_messages\n'), ((265, 22, 265, 79), 'loaner.web_app.backend.api.messages.shelf_messages.ShelfRequest', 'shelf_messages.ShelfRequest', (), '', False, 'from loaner.web_app.backend.api.messages import shelf_messages\n'), ((167, 16, 168, 54), 'loaner.web_app.backend.api.messages.shared_messages.SearchRequest', 'shared_messages.SearchRequest', (), '', False, 'from loaner.web_app.backend.api.messages import shared_messages\n'), ((170, 16, 171, 43), 'loaner.web_app.backend.api.messages.shared_messages.SearchRequest', 'shared_messages.SearchRequest', (), '', False, 'from loaner.web_app.backend.api.messages import shared_messages\n')]