From 7f1fedbb1dea08d3e894c08fafa7bbea1b05530c Mon Sep 17 00:00:00 2001 From: Hugo Rodger-Brown Date: Mon, 18 Sep 2023 22:12:30 +0100 Subject: [PATCH] Refactor tests --- anonymiser/models.py | 9 ++--- tests/{anon.py => anonymisers.py} | 24 ++++++------ tests/apps.py | 2 +- tests/conftest.py | 7 +++- tests/test_models.py | 64 +++++++++++++++---------------- tests/test_registry.py | 4 +- 6 files changed, 54 insertions(+), 56 deletions(-) rename tests/{anon.py => anonymisers.py} (69%) diff --git a/anonymiser/models.py b/anonymiser/models.py index 3679f55..b70e28c 100644 --- a/anonymiser/models.py +++ b/anonymiser/models.py @@ -66,11 +66,8 @@ class BaseAnonymiser: # Override with the model to be anonymised model: type[models.Model] - # override with a list of fields to exclude from anonymisation report - exclude_rules = (lambda f: f.is_relation or isinstance(f, models.AutoField),) - - # field_name: redaction_value. redaction_value can be a static value or a - # callable, such as a function (e.g. F expression) or a class (e.g. Func). + # field_name: redaction_value. redaction_value can be a static value + # or a db function, e.g. F("field_name") or Value("static value"). field_redactions: dict[str, Any] = {} def __setattr__(self, __name: str, __value: Any) -> None: @@ -189,7 +186,7 @@ def _max_length(f: models.Field) -> int: def redact_queryset( self, queryset: models.QuerySet[models.Model], - auto_redact: bool = True, + auto_redact: bool = False, **field_overrides: Any, ) -> int: """ diff --git a/tests/anon.py b/tests/anonymisers.py similarity index 69% rename from tests/anon.py rename to tests/anonymisers.py index a1e325a..0501741 100644 --- a/tests/anon.py +++ b/tests/anonymisers.py @@ -1,7 +1,6 @@ -from django.db import models +from django.db.models import F, Value from django.db.models.functions import Concat -from anonymiser.db.expressions import GenerateUuid4 from anonymiser.decorators import register_anonymiser from anonymiser.models import BaseAnonymiser @@ -11,17 +10,6 @@ @register_anonymiser class UserAnonymiser(BaseAnonymiser): model = User - field_redactions = { - "first_name": "FIRST_NAME", - "last_name": "LAST_NAME", - "uuid": GenerateUuid4(), - "email": Concat( - models.F("first_name"), - models.Value("."), - models.F("last_name"), - models.Value("@example.com"), - ), - } def anonymise_first_name(self, obj: User) -> None: obj.first_name = "Anonymous" @@ -33,3 +21,13 @@ class BadUserAnonymiser(BaseAnonymiser): def anonymise_first_name(self, obj: User) -> None: # this is not allowed - should be obj.first_name. self.first_name = "Anonymous" + + +class UserRedacter(BaseAnonymiser): + model = User + + field_redactions = { + "first_name": "FIRST_NAME", + "last_name": "LAST_NAME", + "email": Concat(Value("user_"), F("id"), Value("@example.com")), + } diff --git a/tests/apps.py b/tests/apps.py index 9170794..d38bf9c 100644 --- a/tests/apps.py +++ b/tests/apps.py @@ -12,4 +12,4 @@ class TestsConfig(AppConfig): def ready(self) -> None: super().ready() logger.debug("Adding tests app anonymisers") - from . import anon # noqa F401 + from . import anonymisers # noqa F401 diff --git a/tests/conftest.py b/tests/conftest.py index 17c5082..1460678 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,7 @@ import pytest from django.conf import settings -from tests.anon import UserAnonymiser +from tests.anonymisers import UserAnonymiser, UserRedacter from tests.models import User IS_POSTGRES = ( @@ -35,3 +35,8 @@ def user2() -> User: @pytest.fixture def user_anonymiser() -> UserAnonymiser: return UserAnonymiser() + + +@pytest.fixture +def user_redacter() -> UserRedacter: + return UserRedacter() diff --git a/tests/test_models.py b/tests/test_models.py index 200e85b..49d1891 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -8,7 +8,7 @@ from anonymiser.db.expressions import GenerateUuid4 from anonymiser.models import FieldSummaryData -from .anon import BadUserAnonymiser, UserAnonymiser +from .anonymisers import BadUserAnonymiser, UserAnonymiser, UserRedacter from .models import User @@ -125,18 +125,7 @@ def test_bad_anonymiser() -> None: @pytest.mark.django_db -@mock.patch.object(CursorWrapper, "execute") -@skipUnless(settings.IS_POSTGRES, "Test requires Postgres.") -def test_generate_uuid4(mock_execute: mock.MagicMock) -> None: - User.objects.update(uuid=GenerateUuid4()) - assert ( - mock_execute.call_args[0][0] - == 'UPDATE "tests_user" SET "uuid" = uuid_generate_v4()' - ) - - -@pytest.mark.django_db -class TestPostgresRedaction: +class TestRedaction: @pytest.fixture(autouse=settings.IS_POSTGRES) def activate_postgresql_uuid(self) -> None: """Activate the uuid-ossp extension in the test database.""" @@ -144,36 +133,38 @@ def activate_postgresql_uuid(self) -> None: cursor.execute('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";') @skipUnless(settings.IS_POSTGRES, "Test requires Postgres.") + @mock.patch.object(CursorWrapper, "execute") + def test_generate_uuid4(self, mock_execute: mock.MagicMock) -> None: + User.objects.update(uuid=GenerateUuid4()) + assert ( + mock_execute.call_args[0][0] + == 'UPDATE "tests_user" SET "uuid" = uuid_generate_v4()' + ) + def test_redact_queryset_none( - self, user: User, user_anonymiser: UserAnonymiser + self, user: User, user_redacter: UserRedacter ) -> None: - assert user_anonymiser.redact_queryset(User.objects.none()) == 0 + assert user_redacter.redact_queryset(User.objects.none()) == 0 - @skipUnless(settings.IS_POSTGRES, "Test requires Postgres.") - def test_redact_queryset_one( - self, user: User, user_anonymiser: UserAnonymiser - ) -> None: - uuid = user.uuid - assert user_anonymiser.redact_queryset(User.objects.all()) == 1 + def test_redact_queryset_one(self, user: User, user_redacter: UserRedacter) -> None: + assert user_redacter.redact_queryset(User.objects.all()) == 1 user.refresh_from_db() assert user.first_name == "FIRST_NAME" assert user.last_name == "LAST_NAME" - assert user.uuid != uuid + assert user.email == f"user_{user.id}@example.com" - @skipUnless(settings.IS_POSTGRES, "Test requires Postgres.") def test_redact_queryset_two( self, user: User, user2: User, - user_anonymiser: UserAnonymiser, + user_redacter: UserRedacter, ) -> None: - assert user_anonymiser.redact_queryset(User.objects.all()) == 2 + assert user_redacter.redact_queryset(User.objects.all()) == 2 user.refresh_from_db() user2.refresh_from_db() # confirm that we haven't reused the same uuid for all objects assert user.uuid != user2.uuid - @skipUnless(settings.IS_POSTGRES, "Test requires Postgres.") @pytest.mark.parametrize( "auto_redact,location,biography", [ @@ -184,24 +175,33 @@ def test_redact_queryset_two( def test_redact_queryset__auto_redact( self, user: User, - user_anonymiser: UserAnonymiser, + user_redacter: UserRedacter, auto_redact: bool, location: str, biography: str, ) -> None: - user_anonymiser.redact_queryset(User.objects.all(), auto_redact=auto_redact) + user_redacter.redact_queryset(User.objects.all(), auto_redact=auto_redact) user.refresh_from_db() # auto-redacted fields assert user.location == location assert user.biography == biography - @skipUnless(settings.IS_POSTGRES, "Test requires Postgres.") def test_redact_queryset__field_overrides( self, user: User, - user_anonymiser: UserAnonymiser, + user_redacter: UserRedacter, ) -> None: - user_anonymiser.redact_queryset(User.objects.all(), location="Area 51") + user_redacter.redact_queryset(User.objects.all(), location="Area 51") user.refresh_from_db() - # auto-redacted fields assert user.location == "Area 51" + + @skipUnless(settings.IS_POSTGRES, "Test requires Postgres.") + def test_redact_queryset__field_overrides__postgres( + self, + user: User, + user_redacter: UserRedacter, + ) -> None: + uuid = user.uuid + user_redacter.redact_queryset(User.objects.all(), uuid=GenerateUuid4()) + user.refresh_from_db() + assert user.uuid != uuid diff --git a/tests/test_registry.py b/tests/test_registry.py index 94b8a0e..0546693 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -1,13 +1,11 @@ from anonymiser.decorators import register_anonymiser from anonymiser.registry import _registry, anonymisable_models -from .anon import UserAnonymiser +from .anonymisers import UserAnonymiser from .models import User def test_registry() -> None: - # assert anonymisable_models() == [] - # register(UserAnonymiser) assert anonymisable_models() == [User]