class TestVersionsCache(SharedCacheTests, TestCase):

    """Test cache functions when multiple versions are defined."""

    def setUp(self):
        """Setup environment for an enabled cache."""
        self.cache = SampleCache()
        self.cache.versions = ['default', 'v2']
        self.cache.cache.clear()
        self.mock_delete = mock.Mock()
        self.cache.cache.delete = self.mock_delete

    def test_update_instance_unhandled_model(self):
        """An error is raised update a model defined as None in the Cache."""
        self.cache.bar_v2_serializer = None
        self.cache.bar_v2_loader = None
        self.cache.bar_v2_invalidator = None
        super(TestVersionsCache, self).test_update_instance_unhandled_model()

    def test_delete_all_versions_two_versions(self):
        """Delete all cached instances with multiple versions."""
        self.cache.delete_all_versions("Model", 86)
        self.mock_delete.assert_has_calls([
            mock.call("drfc_default_Model_86"),
            mock.call("drfc_v2_Model_86")])
class TestCacheDisabled(SharedCacheTests, TestCase):
    """Test cache functions when the instance cache is disabled."""
    def setUp(self):
        """Setup environment for a disabled cache."""
        self.cache = SampleCache()

    def test_cache_is_none(self):
        """When USE_DRF_INSTANCE_CACHE is False, cache is None."""
        self.assertIsNone(self.cache.cache)

    def test_invalidate_returns_none(self):
        """When cache is disabled, updating is skipped."""
        with self.assertNumQueries(0):
            invalid = self.cache.update_instance('User', 123)
        self.assertEqual([], invalid)

    def test_delete_all_versions(self):
        """No error when requesting to delete all cached instances."""
        self.cache.delete_all_versions("Model", 86)
class TestCacheDisabled(SharedCacheTests, TestCase):
    """Test cache functions when the instance cache is disabled."""

    def setUp(self):
        """Setup environment for a disabled cache."""
        self.cache = SampleCache()

    def test_cache_is_none(self):
        """When USE_DRF_INSTANCE_CACHE is False, cache is None."""
        self.assertIsNone(self.cache.cache)

    def test_invalidate_returns_none(self):
        """When cache is disabled, updating is skipped."""
        with self.assertNumQueries(0):
            invalid = self.cache.update_instance('User', 123)
        self.assertEqual([], invalid)

    def test_delete_all_versions(self):
        """No error when requesting to delete all cached instances."""
        self.cache.delete_all_versions("Model", 86)
class TestCache(SharedCacheTests, TestCase):
    """Test cache functions when the instance cache is enabled."""
    def setUp(self):
        """Setup environment for an enabled cache."""
        self.cache = SampleCache()
        self.cache.cache.clear()
        self.mock_delete = mock.Mock()
        self.cache.cache.delete = self.mock_delete

    def test_cache_is_available(self):
        """When USE_DRF_INSTANCE_CACHE is True, cache is available."""
        self.assertTrue(self.cache.cache)

    def test_get_instance_cache_hit(self):
        """When instance is cached, the database is not queried."""
        key = self.cache.key_for('default', 'User', 123)
        self.assertEqual('drfc_default_User_123', key)
        data = {'id': 123, 'foo': 'bar'}
        self.cache.cache.set(key, dumps(data))
        with self.assertNumQueries(0):
            instances = self.cache.get_instances([('User', 123, None)])
        expected = {('User', 123): (data, key, None)}
        self.assertEqual(expected, instances)

    def test_update_instance_invalidator_only(self):
        """A model can have no serializer but a defined invalidator."""
        user = User.objects.create(username='******')
        group = Group.objects.create()
        group.user_set.add(user)
        invalid = self.cache.update_instance('Group', group.pk)
        self.assertEqual([('User', user.pk, 'default')], invalid)

    def test_update_instance_deleted_model(self):
        """A deleted instance can still invalidate related instances."""
        self.assertFalse(User.objects.filter(pk=666).exists())
        invalid = self.cache.update_instance('User', 666)
        self.assertEqual([], invalid)
        self.mock_delete.assert_called_once_with('drfc_default_User_666')

    def test_update_instance_cache_string(self):
        """A invalidator can delete cache entries by name."""
        user = User.objects.create(username='******')
        invalid = self.cache.update_instance('User', user.pk)
        self.assertEqual([], invalid)
        self.mock_delete.assert_called_once_with('drfc_user_count')

    def test_update_instance_no_changes(self):
        """When the representation is unchanged, updates do not cascade."""
        user = User.objects.create(username='******',
                                   date_joined=datetime(
                                       2014, 11, 5, 22, 2, 16, 735772, UTC))
        key = self.cache.key_for('default', 'User', user.pk)
        representation = {
            'id': user.id,
            'username': '******',
            'date_joined:DateTime': '1415224936.735772',
            'votes:PKList': {
                'app': 'sample_poll_app',
                'model': 'choice',
                'pks': [],
            },
        }
        self.cache.cache.set(key, dumps(representation))
        self.mock_delete.side_effect = Exception('Not Called')
        invalid = self.cache.update_instance('User', user.pk, user)
        self.assertEqual([], invalid)

    def test_update_instance_with_raw_instance(self):
        """An invalidator loads related PKs with a raw instance."""
        # Create user, then delete auto-created cache
        User.objects.create(username='******')
        self.cache.cache.clear()
        self.mock_delete.reset_mock()
        user = User.objects.get(username='******')

        with self.assertNumQueries(1):
            invalid = self.cache.update_instance('User', user.pk, user)
        self.assertEqual([], invalid)
        self.mock_delete.assert_called_once_with('drfc_user_count')

    def test_update_instance_with_loaded_instance(self):
        """An invalidator skips the database with a loaded instance."""
        # Create user, then delete auto-created cache
        user_pk = User.objects.create(username='******').pk
        user = self.cache.user_default_loader(user_pk)
        self.cache.cache.clear()
        self.mock_delete.reset_mock()

        with self.assertNumQueries(0):
            invalid = self.cache.update_instance('User', user_pk, user)
        self.assertEqual([], invalid)
        self.mock_delete.assert_called_once_with('drfc_user_count')

    def test_delete_called_on_immediate_invalidate(self):
        """An invalidator can ask for immediate invalidation."""
        user = User.objects.create(username='******')
        question = Question.objects.create(
            question_text='What is your favorite color?',
            pub_date=datetime(2014, 11, 6, 8, 45, 49, 538232, UTC))
        choice = Choice.objects.create(question=question,
                                       choice_text="Blue. No, Green!")
        choice.voters.add(user)
        invalid = self.cache.choice_default_invalidator(choice)
        expected = [
            ('Question', question.pk, True),
            ('User', user.pk, False),
        ]
        self.assertEqual(expected, invalid)
        to_update = self.cache.update_instance('Choice', choice.pk)
        expected_update = [
            ('Question', question.pk, 'default'),
            ('User', user.pk, 'default'),
        ]
        self.assertEqual(expected_update, to_update)
        self.mock_delete.assert_has_calls([
            mock.call('drfc_user_count'),
            mock.call('drfc_default_Question_%s' % question.pk),
            mock.call('drfc_user_count'),
            mock.call('drfc_default_Question_%s' % question.pk)
        ])
        self.assertEqual(4, self.mock_delete.call_count)

    def test_update_instance_cache_miss_update_only(self):
        """With update_only, cache misses don't update or cascade."""
        user = User.objects.create(username='******')
        question = Question.objects.create(
            question_text='What is your favorite color?',
            pub_date=datetime(2014, 11, 6, 8, 45, 49, 538232, UTC))
        choice = Choice.objects.create(question=question,
                                       choice_text="Blue. No, Green!")
        choice.voters.add(user)
        self.cache.cache.clear()
        to_update = self.cache.update_instance('Choice',
                                               choice.pk,
                                               update_only=True)
        self.assertEqual([], to_update)
        self.mock_delete.assert_has_calls([
            mock.call('drfc_user_count'),
            mock.call('drfc_default_Question_%s' % question.pk),
            mock.call('drfc_user_count')
        ])
        self.assertEqual(3, self.mock_delete.call_count)

    def test_delete_all_versions_one_version(self):
        """Delete all cached instances for a model and ID."""
        self.cache.delete_all_versions("Model", 86)
        self.mock_delete.assert_called_once_with("drfc_default_Model_86")
class TestCache(SharedCacheTests, TestCase):

    """Test cache functions when the instance cache is enabled."""

    def setUp(self):
        """Setup environment for an enabled cache."""
        self.cache = SampleCache()
        self.cache.cache.clear()
        self.mock_delete = mock.Mock()
        self.cache.cache.delete = self.mock_delete

    def test_cache_is_available(self):
        """When USE_DRF_INSTANCE_CACHE is True, cache is available."""
        self.assertTrue(self.cache.cache)

    def test_get_instance_cache_hit(self):
        """When instance is cached, the database is not queried."""
        key = self.cache.key_for('default', 'User', 123)
        self.assertEqual('drfc_default_User_123', key)
        data = {'id': 123, 'foo': 'bar'}
        self.cache.cache.set(key, dumps(data))
        with self.assertNumQueries(0):
            instances = self.cache.get_instances([('User', 123, None)])
        expected = {('User', 123): (data, key, None)}
        self.assertEqual(expected, instances)

    def test_update_instance_invalidator_only(self):
        """A model can have no serializer but a defined invalidator."""
        user = User.objects.create(username='******')
        group = Group.objects.create()
        group.user_set.add(user)
        invalid = self.cache.update_instance('Group', group.pk)
        self.assertEqual([('User', user.pk, 'default')], invalid)

    def test_update_instance_deleted_model(self):
        """A deleted instance can still invalidate related instances."""
        self.assertFalse(User.objects.filter(pk=666).exists())
        invalid = self.cache.update_instance('User', 666)
        self.assertEqual([], invalid)
        self.mock_delete.assert_called_once_with('drfc_default_User_666')

    def test_update_instance_cache_string(self):
        """A invalidator can delete cache entries by name."""
        user = User.objects.create(username='******')
        invalid = self.cache.update_instance('User', user.pk)
        self.assertEqual([], invalid)
        self.mock_delete.assert_called_once_with('drfc_user_count')

    def test_update_instance_no_changes(self):
        """When the representation is unchanged, updates do not cascade."""
        user = User.objects.create(
            username='******',
            date_joined=datetime(2014, 11, 5, 22, 2, 16, 735772, UTC))
        key = self.cache.key_for('default', 'User', user.pk)
        representation = {
            'id': user.id,
            'username': '******',
            'date_joined:DateTime': '1415224936.735772',
            'votes:PKList': {
                'app': 'sample_poll_app',
                'model': 'choice',
                'pks': [],
            },
        }
        self.cache.cache.set(key, dumps(representation))
        self.mock_delete.side_effect = Exception('Not Called')
        invalid = self.cache.update_instance('User', user.pk, user)
        self.assertEqual([], invalid)

    def test_update_instance_with_raw_instance(self):
        """An invalidator loads related PKs with a raw instance."""
        # Create user, then delete auto-created cache
        User.objects.create(username='******')
        self.cache.cache.clear()
        self.mock_delete.reset_mock()
        user = User.objects.get(username='******')

        with self.assertNumQueries(1):
            invalid = self.cache.update_instance('User', user.pk, user)
        self.assertEqual([], invalid)
        self.mock_delete.assert_called_once_with('drfc_user_count')

    def test_update_instance_with_loaded_instance(self):
        """An invalidator skips the database with a loaded instance."""
        # Create user, then delete auto-created cache
        user_pk = User.objects.create(username='******').pk
        user = self.cache.user_default_loader(user_pk)
        self.cache.cache.clear()
        self.mock_delete.reset_mock()

        with self.assertNumQueries(0):
            invalid = self.cache.update_instance('User', user_pk, user)
        self.assertEqual([], invalid)
        self.mock_delete.assert_called_once_with('drfc_user_count')

    def test_delete_called_on_immediate_invalidate(self):
        """An invalidator can ask for immediate invalidation."""
        user = User.objects.create(username='******')
        question = Question.objects.create(
            question_text='What is your favorite color?',
            pub_date=datetime(2014, 11, 6, 8, 45, 49, 538232, UTC))
        choice = Choice.objects.create(
            question=question, choice_text="Blue. No, Green!")
        choice.voters.add(user)
        invalid = self.cache.choice_default_invalidator(choice)
        expected = [
            ('Question', question.pk, True),
            ('User', user.pk, False),
        ]
        self.assertEqual(expected, invalid)
        to_update = self.cache.update_instance('Choice', choice.pk)
        expected_update = [
            ('Question', question.pk, 'default'),
            ('User', user.pk, 'default'),
        ]
        self.assertEqual(expected_update, to_update)
        self.mock_delete.assertEqual('drf_default_question_%s' % question.pk)

    def test_update_instance_cache_miss_update_only(self):
        """With update_only, cache misses don't update or cascade."""
        user = User.objects.create(username='******')
        question = Question.objects.create(
            question_text='What is your favorite color?',
            pub_date=datetime(2014, 11, 6, 8, 45, 49, 538232, UTC))
        choice = Choice.objects.create(
            question=question, choice_text="Blue. No, Green!")
        choice.voters.add(user)
        self.cache.cache.clear()
        to_update = self.cache.update_instance(
            'Choice', choice.pk, update_only=True)
        self.assertEqual([], to_update)
        self.mock_delete.assertEqual('drf_default_question_%s' % question.pk)

    def test_delete_all_versions_one_version(self):
        """Delete all cached instances for a model and ID."""
        self.cache.delete_all_versions("Model", 86)
        self.mock_delete.assert_called_once_with("drfc_default_Model_86")