# -*- coding: utf-8 -*- """ Integration tests for models using CacheManager """ import django from django.forms.models import model_to_dict from django.core.exceptions import ObjectDoesNotExist from django.db import ( connection, reset_queries ) from django.db.models.sql import EmptyResultSet from django.test import TestCase if django.get_version() > '1.7': from django.test import override_settings else: from django.test.utils import override_settings from tests.models import( Car, Driver, Engine, Manufacturer, ) from tests.factories import( CarFactory, DriverFactory, EngineFactory, ManufacturerFactory, PersonFactory, ) class ModelCRUDTests(TestCase): """ Create, read, update and delete tests """ def setUp(self): self.manufacturers = ManufacturerFactory.create_batch(size=5) self.cars = CarFactory.create_batch(size=5, make=self.manufacturers[0]) self.drivers = DriverFactory.create_batch(size=5, cars=[self.cars[0]]) def test_create_new_manufacturer(self): """ Number of manufacturers returned by objects should increment after creating a new manufacturer """ ManufacturerFactory.create() self.assertEqual( len(Manufacturer.objects.all()), len(self.manufacturers) + 1) def test_update_manufacturer(self): """ Cache manager returns most recently updated record even if it has been cached and an update happens after. """ m = self.manufacturers[0] self.assertEqual( model_to_dict(m), model_to_dict(Manufacturer.objects.get(id=m.id))) m.name = 'Toyota' m.save() self.assertEqual( model_to_dict(m), model_to_dict(Manufacturer.objects.get(id=m.id))) def test_delete_manufacturer(self): """ When a cached record is deleted Cache manager raises ObjectDoesNotExist when trying to retrieve """ m = self.manufacturers[0] self.assertEqual( model_to_dict(m), model_to_dict(Manufacturer.objects.get(id=m.id))) m.delete() with self.assertRaises(ObjectDoesNotExist): Manufacturer.objects.get(id=m.id) @override_settings(DEBUG=True) class ModelCacheTests(TestCase): """ Test cache hits and misses """ def setUp(self): self.manufacturer = ManufacturerFactory.create() reset_queries() def test_cache_hit(self): """ The query should be cached when making the same query repeatedly. """ for i in range(5): len(Manufacturer.objects.all()) self.assertEqual(len(connection.queries), 1) def test_dummy_cache(self): """ There should not be any query caching when cache backend is dummy. """ CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', }, 'django_cache_manager.cache_backend': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', } } with override_settings(DEBUG=True, CACHES=CACHES): for i in range(5): len(Manufacturer.objects.all()) self.assertEqual(len(connection.queries), 5) def test_cache_invalidate_with_bulk_create(self): """ Cache should be invalidated when calling 'bulk_create' """ initial_count = len(Manufacturer.objects.all()) Manufacturer.objects.bulk_create( [Manufacturer(name='m1'), Manufacturer(name='m2')] ) reset_queries() new_count = len(Manufacturer.objects.all()) self.assertEqual(len(connection.queries), 1) self.assertEqual(initial_count + 2, new_count) def test_cache_invalidate_with_update(self): """ Cache should be invalidated when calling 'update' """ len(Manufacturer.objects.all()) Manufacturer.objects.all().update(name='new name') reset_queries() len(Manufacturer.objects.all()) self.assertEqual(len(connection.queries), 1) @override_settings(DEBUG=True) class OneToOneModelCacheTests(TestCase): """ Test cache hits and misses on models with one-to-one relationship """ def setUp(self): self.engine = EngineFactory.create(name='test_engine') self.car = CarFactory.create(engine=self.engine, year=2015) reset_queries() def test_one_to_one_mapping_cache(self): """ Queries should be cached when making the same select queries on related objects query repeatedly """ for i in range(5): Car.objects.get(id=self.car.id).engine.name # The above query is actually composed of 2 sql queries: # one for fetching cars and the other engines. self.assertEqual(len(connection.queries), 2) def test_one_to_one_mapping_cache_with_save(self): """ Cache should be invalidated when calling 'save' on related objects """ Car.objects.get(id=self.car.id).engine.name self.engine.name = 'new_engine' self.engine.save() reset_queries() # Both car and engine caches will be invalidated due to car being related to engine Car.objects.get(id=self.car.id).engine.name self.assertEqual(len(connection.queries), 2) def test_one_to_one_mapping_cache_with_delete(self): """ Cache should be invalidated when calling 'delete' related objects """ Car.objects.get(id=self.car.id).engine.name self.engine.delete() reset_queries() with self.assertRaises(ObjectDoesNotExist): Car.objects.get(id=self.car.id).engine.name @override_settings(DEBUG=True) class ManyToOneModelCacheTests(TestCase): """ Test cache hits and misses on models with many-to-one relationship """ def setUp(self): self.manufacturer = ManufacturerFactory.create() self.car = CarFactory.create(make=self.manufacturer, year=2015) reset_queries() def test_many_to_one_mapping_cache(self): """ Queries should be cached when making the same select queries on related objects repeatedly """ for i in range(5): len(Manufacturer.objects.get(id=self.manufacturer.id).cars.all()) # The above query is actually composed of 2 sql queries: # one for fetching the manufacturer and the other cars. self.assertEqual(len(connection.queries), 2) def test_many_to_one_mapping_cache_with_save(self): """ Cache should be invalidated when calling 'save' on related objects """ car2 = CarFactory.create(make=self.manufacturer) len(Manufacturer.objects.get(id=self.manufacturer.id).cars.all()) car2.name = 'gti' car2.save() reset_queries() # Only 1 cache (the one for car selection query) will be invalidated # as we only update data on Car table len(Manufacturer.objects.get(id=self.manufacturer.id).cars.all()) # Because of m2m fix the no. of queries will be 2 instead of 1 self.assertEqual(len(connection.queries), 2) def test_many_to_one_mapping_cache_with_delete(self): """ Cache should be invalidated when calling 'delete' on related objects """ car2 = CarFactory.create(make=self.manufacturer) initial_count = len( Manufacturer.objects.get(id=self.manufacturer.id).cars.all()) car2.delete() reset_queries() # Only 1 cache (the one for car selection query) will be invalidated # as we only delete data on Car table new_count = len( Manufacturer.objects.get(id=self.manufacturer.id).cars.all()) # Because of m2m fix the no. of queries will be 2 instead of 1 self.assertEqual(len(connection.queries), 2) self.assertEqual(initial_count - 1, new_count) @override_settings(DEBUG=True) class SelectRelatedTests(TestCase): """ Tests for select_related """ def setUp(self): self.manufacturer = ManufacturerFactory.create(name='Honda') self.car = CarFactory.create(make=self.manufacturer, year=2015, model='Civic') reset_queries() def test_select_related(self): """ Select related query retrieves updated data when related model changes independently. """ civic = Car.objects.select_related('make').get(model='Civic') self.assertEquals(civic.make.name, 'Honda') self.assertEquals(len(connection.queries), 1) honda = Manufacturer.objects.get(name='Honda') honda.name = 'Honda Inc' honda.save() civic = Car.objects.select_related('make').get(model='Civic') self.assertEquals(civic.make.name, 'Honda Inc') @override_settings(DEBUG=True) class EmptyResultSetTests(TestCase): """ Tests validating behavior when as_sql returns EmptyResultSet. """ def setUp(self): self.manufacturer = ManufacturerFactory.create(name='Honda') self.car = CarFactory.create(make=self.manufacturer, year=2015, model='Civic') reset_queries() def test_empty_list_on_filter_in(self): """ A filter call with __in being passed an empty list should correctly handle the EmptyResultSet exception and return None. """ self.assertEqual([], list(Car.objects.filter(make__in=[])))