5

I have a unique_together constraint for email and store. I have used the following code to test the unique constraint. I'm expecting to pass the test with assertRaise but the result shows the opposite. What I'm missing here?

from django.db.backends.sqlite3.base import IntegrityError

class TestUserModel(TestCase):
    def setUp(self):
        self.store1 = Store.objects.create(name='store1')
        self.store2 = Store.objects.create(name='store2')

    def multiple_user_with_same_email_and_store(self):
        data1 = dict(email="abcd@gmail.com", password="a", store=self.store1)
        data2 = dict(email="abcd@gmail.com", password="abc", store=self.store1)
        self.user1 = User.objects.create(**data1)
        user2 = User(**data2)
        self.assertRaises(IntegrityError, user2.save)

Traceback

    Creating test database for alias 'default'...
E
======================================================================
ERROR: multiple_user_with_same_email_and_store (user.tests.test_models.TestUserModel)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/ram/nk/project/helper/helper/user/tests/test_models.py", line 25, in multiple_user_with_same_email_and_store
    self.assertRaises(IntegrityError, user2.save)
  File "/usr/lib/python2.7/unittest/case.py", line 473, in assertRaises
    callableObj(*args, **kwargs)
  File "/home/ram/.virtual/helper/local/lib/python2.7/site-packages/django/db/models/base.py", line 734, in save
    force_update=force_update, update_fields=update_fields)
  File "/home/ram/.virtual/helper/local/lib/python2.7/site-packages/django/db/models/base.py", line 762, in save_base
    updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
  File "/home/ram/.virtual/helper/local/lib/python2.7/site-packages/django/db/models/base.py", line 846, in _save_table
    result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
  File "/home/ram/.virtual/helper/local/lib/python2.7/site-packages/django/db/models/base.py", line 885, in _do_insert
    using=using, raw=raw)
  File "/home/ram/.virtual/helper/local/lib/python2.7/site-packages/django/db/models/manager.py", line 127, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/ram/.virtual/helper/local/lib/python2.7/site-packages/django/db/models/query.py", line 920, in _insert
    return query.get_compiler(using=using).execute_sql(return_id)
  File "/home/ram/.virtual/helper/local/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 974, in execute_sql
    cursor.execute(sql, params)
  File "/home/ram/.virtual/helper/local/lib/python2.7/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
  File "/home/ram/.virtual/helper/local/lib/python2.7/site-packages/django/db/utils.py", line 97, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/home/ram/.virtual/helper/local/lib/python2.7/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
  File "/home/ram/.virtual/helper/local/lib/python2.7/site-packages/django/db/backends/sqlite3/base.py", line 318, in execute
    return Database.Cursor.execute(self, query, params)
IntegrityError: UNIQUE constraint failed: user_user.email, user_user.store_id


    ----------------------------------------------------------------------
    Ran 1 test in 0.003s

    FAILED (errors=1)

User model extends AbstractUser (nothing other added at User), which is as follow

class AbstractUser(auth_models.AbstractBaseUser, PermissionsMixin):

    email = models.EmailField(_('email address'))
    store = models.ForeignKey(Store, verbose_name=_('Store Name'), null=True)
    first_name = models.CharField(_('First name'), max_length=255, blank=True)
    last_name = models.CharField(_('Last name'), max_length=255, blank=True)
    is_staff = models.BooleanField(
            _('Staff status'), default=False,
            help_text=_('Designates whether the user can log into this admin '
                        'site.'))
    is_active = models.BooleanField(
            _('Active'), default=True,
            help_text=_('Designates whether this user should be treated as '
                        'active. Unselect this instead of deleting accounts.'))
    date_joined = models.DateTimeField(_('date joined'),
                                       default=timezone.now)

    objects = UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['store']

    class Meta:
        abstract = True
        verbose_name = _('User')
        verbose_name_plural = _('Users')
        unique_together = ('email', 'store')

and UserManager

class UserManager(auth_models.BaseUserManager):
    def create_user(self, store, email, password=None, **extra_fields):
        """
        Creates and saves a User with the given email and
        password.
        """
        now = timezone.now()
        if not email:
            raise ValueError('The given email must be set')
        email = UserManager.normalize_email(email)
        try:
            with transaction.atomic():
                store, created = Store.objects.get_or_create(name=store)
                user = self.model(email=email, store=store, is_staff=False, is_active=True, is_superuser=False,
                        last_login=now, date_joined=now, **extra_fields)
                user.set_password(password)
                user.save(using=self._db)
        except Error as e:
            logging.ERROR(e)
            raise Error("Internal Error: Unable to create user")
        return user

    def create_superuser(self, store, email, password, **extra_fields):
        u = self.create_user(store, email, password, **extra_fields)
        u.is_staff = True
        u.is_active = True
        u.is_superuser = True
        u.save(using=self._db)
        return u

    def create_staffuser(self, store, email, password, **extra_fields):
        u = self.create_user(store, email, password, **extra_fields)
        u.is_staff = True
        u.is_active = True
        u.is_superuser = False
        u.save(using=self._db)
        return u

Version: 1.8

Lionel
  • 604
  • 9
  • 26
  • could you please show your `User` model's definition (fields and if there is, the __init__ method) as well as the full error stack trace – zsepi Aug 16 '16 at 12:25
  • unique_together should be a tuple of tuples (in this case, it checks that email is unique in itself AND store is unique in itself), see https://docs.djangoproject.com/en/1.10/ref/models/options/#django.db.models.Options.unique_together but it doesn't explain why it isn't failing on the line expected (though here the full error traceback would help) – zsepi Aug 16 '16 at 12:59
  • in the test, where is `IntegrityError` imported from? – zsepi Aug 16 '16 at 13:31
  • I suspect some kind of wrapper (django.db.utils.IntegrityError) or import mixup, and would debug that using `self.assertRaises` as a context manager - `with self.assertRaises(Exception) as raised: user2.save()` followed by `self.assertEqual(IntegrityError, type(raised.exception))` – zsepi Aug 16 '16 at 13:48
  • the type interesting is not `raised` but `raised.exception` and it's `self.assertEqual(In‌​tegrityError, type(raised.exceptio‌​n))` not `self.assertEqual(In‌​tegrityError, raised.exception)` – zsepi Aug 16 '16 at 13:59

1 Answers1

12

So the error is being raised at the right line (in assertRaises), but I suspect there are two different kinds of IntegrityError classes at play here - one that is imported by the test, and one that is raised by the database backend.

e.g.: django.db.backends.postgresql.base.IntegrityError is not the same as django.db.backends.sqlite3.base.IntegrityError. On top of that, there is the Django exception wrapper django.db.utils.IntegrityError

The issue can be debugged and verified by using self.assertRaises as a context manager:

with self.assertRaises(Exception) as raised:  # top level exception as we want to figure out its exact type
    user2.save()
self.assertEqual(IntegrityError, type(raised.exception))  # if it fails, we'll get the correct type to import
zsepi
  • 1,572
  • 10
  • 19