-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[#2041] Added hard database constraints on User's identifier/login_ty…
…pe fields
- Loading branch information
Showing
3 changed files
with
246 additions
and
27 deletions.
There are no files selected for viewing
88 changes: 88 additions & 0 deletions
88
src/open_inwoner/accounts/migrations/0071_auto_20240125_1227.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# Generated by Django 3.2.23 on 2024-01-25 11:27 | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
("accounts", "0070_auto_20231205_1657"), | ||
] | ||
|
||
operations = [ | ||
migrations.AddConstraint( | ||
model_name="user", | ||
constraint=models.UniqueConstraint( | ||
condition=models.Q(("login_type", "digid")), | ||
fields=("bsn",), | ||
name="unique_bsn_when_login_digid", | ||
), | ||
), | ||
migrations.AddConstraint( | ||
model_name="user", | ||
constraint=models.UniqueConstraint( | ||
condition=models.Q( | ||
("login_type", "eherkenning"), models.Q(("rsin", ""), _negated=True) | ||
), | ||
fields=("rsin",), | ||
name="unique_rsin_when_login_eherkenning", | ||
), | ||
), | ||
migrations.AddConstraint( | ||
model_name="user", | ||
constraint=models.UniqueConstraint( | ||
condition=models.Q( | ||
("login_type", "eherkenning"), models.Q(("kvk", ""), _negated=True) | ||
), | ||
fields=("kvk",), | ||
name="unique_kvk_when_login_eherkenning", | ||
), | ||
), | ||
migrations.AddConstraint( | ||
model_name="user", | ||
constraint=models.UniqueConstraint( | ||
condition=models.Q(("login_type", "oidc")), | ||
fields=("oidc_id",), | ||
name="unique_oidc_id_when_login_oidc", | ||
), | ||
), | ||
migrations.AddConstraint( | ||
model_name="user", | ||
constraint=models.CheckConstraint( | ||
check=models.Q(("bsn", ""), ("login_type", "digid"), _connector="OR"), | ||
name="check_bsn_only_set_when_login_digid", | ||
), | ||
), | ||
migrations.AddConstraint( | ||
model_name="user", | ||
constraint=models.CheckConstraint( | ||
check=models.Q( | ||
("oidc_id", ""), ("login_type", "oidc"), _connector="OR" | ||
), | ||
name="check_oidc_id_only_set_when_login_oidc", | ||
), | ||
), | ||
migrations.AddConstraint( | ||
model_name="user", | ||
constraint=models.CheckConstraint( | ||
check=models.Q( | ||
models.Q(("kvk", ""), ("rsin", "")), | ||
("login_type", "eherkenning"), | ||
_connector="OR", | ||
), | ||
name="check_kvk_or_rsin_only_set_when_login_eherkenning", | ||
), | ||
), | ||
migrations.AddConstraint( | ||
model_name="user", | ||
constraint=models.CheckConstraint( | ||
check=models.Q( | ||
models.Q(("kvk", ""), models.Q(("rsin", ""), _negated=True)), | ||
models.Q(models.Q(("kvk", ""), _negated=True), ("rsin", "")), | ||
models.Q(("login_type", "eherkenning"), _negated=True), | ||
_connector="OR", | ||
), | ||
name="check_kvk_or_rsin_exclusive_when_login_eherkenning", | ||
), | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
119 changes: 119 additions & 0 deletions
119
src/open_inwoner/accounts/tests/test_user_constraints.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
from django.db import IntegrityError, transaction | ||
from django.test import TestCase | ||
|
||
from open_inwoner.accounts.choices import LoginTypeChoices | ||
|
||
from .factories import UserFactory | ||
|
||
|
||
class UserIdentifierConstraintTests(TestCase): | ||
def test_unique_email_when_login_default(self): | ||
UserFactory(email="[email protected]", login_type=LoginTypeChoices.default) | ||
UserFactory(email="[email protected]", login_type=LoginTypeChoices.default) | ||
|
||
with self.assertRaises(IntegrityError): | ||
UserFactory(email="[email protected]", login_type=LoginTypeChoices.default) | ||
|
||
def test_not_unique_email_when_login_not_default(self): | ||
UserFactory(email="[email protected]", login_type=LoginTypeChoices.default) | ||
|
||
UserFactory(email="[email protected]", login_type=LoginTypeChoices.digid) | ||
UserFactory(email="[email protected]", login_type=LoginTypeChoices.eherkenning) | ||
UserFactory(email="[email protected]", login_type=LoginTypeChoices.oidc) | ||
|
||
def test_unique_bsn_when_login_digid(self): | ||
UserFactory(bsn="123456782", login_type=LoginTypeChoices.digid) | ||
UserFactory(bsn="123456783", login_type=LoginTypeChoices.digid) | ||
|
||
with self.assertRaises(IntegrityError): | ||
UserFactory(bsn="123456782", login_type=LoginTypeChoices.digid) | ||
|
||
def test_unique_oidc_id_when_login_oidc(self): | ||
UserFactory(oidc_id="1234", login_type=LoginTypeChoices.oidc) | ||
UserFactory(oidc_id="1235", login_type=LoginTypeChoices.oidc) | ||
|
||
with self.assertRaises(IntegrityError): | ||
UserFactory(oidc_id="1234", login_type=LoginTypeChoices.oidc) | ||
|
||
def test_unique_kvk_when_login_eherkenning(self): | ||
UserFactory(kvk="12345678", login_type=LoginTypeChoices.eherkenning) | ||
UserFactory(kvk="12345679", login_type=LoginTypeChoices.eherkenning) | ||
|
||
with self.assertRaises(IntegrityError): | ||
UserFactory(kvk="12345678", login_type=LoginTypeChoices.eherkenning) | ||
|
||
def test_unique_rsin_when_login_herkenning(self): | ||
UserFactory(rsin="12345678", login_type=LoginTypeChoices.eherkenning) | ||
UserFactory(rsin="12345679", login_type=LoginTypeChoices.eherkenning) | ||
|
||
with self.assertRaises(IntegrityError): | ||
UserFactory(rsin="12345678", login_type=LoginTypeChoices.eherkenning) | ||
|
||
def test_not_both_kvk_and_rsin_when_login_eherkenning(self): | ||
with self.assertRaises(IntegrityError): | ||
UserFactory( | ||
kvk="12345678", rsin="12345678", login_type=LoginTypeChoices.eherkenning | ||
) | ||
|
||
def test_not_bsn_when_login_not_digid(self): | ||
for login_type in [ | ||
LoginTypeChoices.default, | ||
LoginTypeChoices.eherkenning, | ||
LoginTypeChoices.oidc, | ||
]: | ||
with self.subTest(login_type): | ||
# start transaction block because we're testing multiple IntegrityErrors | ||
with transaction.atomic(): | ||
with self.assertRaises(IntegrityError): | ||
UserFactory(bsn="123456782", login_type=login_type) | ||
|
||
def test_not_rsin_when_login_not_eherkenning(self): | ||
for login_type in [ | ||
LoginTypeChoices.default, | ||
LoginTypeChoices.digid, | ||
LoginTypeChoices.oidc, | ||
]: | ||
with self.subTest(login_type): | ||
# start transaction block because we're testing multiple IntegrityErrors | ||
with transaction.atomic(): | ||
with self.assertRaises(IntegrityError): | ||
UserFactory(rsin="12345678", login_type=login_type) | ||
|
||
def test_not_kvk_when_login_not_eherkenning(self): | ||
for login_type in [ | ||
LoginTypeChoices.default, | ||
LoginTypeChoices.digid, | ||
LoginTypeChoices.oidc, | ||
]: | ||
with self.subTest(login_type): | ||
# start transaction block because we're testing multiple IntegrityErrors | ||
with transaction.atomic(): | ||
with self.assertRaises(IntegrityError): | ||
UserFactory(kvk="12345678", login_type=login_type) | ||
|
||
def test_not_oidc_id_when_login_not_oidc(self): | ||
for login_type in [ | ||
LoginTypeChoices.default, | ||
LoginTypeChoices.digid, | ||
LoginTypeChoices.eherkenning, | ||
]: | ||
with self.subTest(login_type): | ||
# start transaction block because we're testing multiple IntegrityErrors | ||
with transaction.atomic(): | ||
with self.assertRaises(IntegrityError): | ||
UserFactory(oidc_id="12345678", login_type=login_type) | ||
|
||
def test_login_types_not_changed(self): | ||
""" | ||
The constraints and tests depend on a known set of login_types, | ||
so make sure this suite fails if we ever change them. | ||
""" | ||
self.assertEqual( | ||
LoginTypeChoices.values, | ||
[ | ||
LoginTypeChoices.default, | ||
LoginTypeChoices.digid, | ||
LoginTypeChoices.eherkenning, | ||
LoginTypeChoices.oidc, | ||
], | ||
) |