diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..84449d0 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,45 @@ +name: On push + +on: [push] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + # python-version: ['3.9', '3.10', '3.11', '3.12'] + # django-version: ['django>=3.2,<4.0', 'django>=4.0,<5.0', 'django>=5.0'] + python-version: ['3.12'] + django-version: ['django>=5.0'] + exclude: + - python-version: '3.9' + django-version: ['django>=5.0'] + services: + postgres: + image: postgres + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + pip install -r requirements_dev.txt + pip install '${{ matrix.django-version }}' + - name: Run tests + run: | + make test diff --git a/.gitignore b/.gitignore index 8ef313b..9521e7c 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,9 @@ coverage.xml *.log local_settings.py +# pyenv +.python-version + # virtualenv .venv/ venv/ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..315a78b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +version: '3' +services: + postgres: + image: postgres + ports: + - 5432:5432 + restart: unless-stopped + environment: + - POSTGRES_DB=postgres + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres diff --git a/pgcrypto/fields.py b/pgcrypto/fields.py index 71a3ab7..7fc4a5b 100644 --- a/pgcrypto/fields.py +++ b/pgcrypto/fields.py @@ -125,11 +125,13 @@ class BooleanPGPSymmetricKeyField(PGPPublicKeyFieldMixin, models.BooleanField): class DecimalPGPPublicKeyField(DecimalPGPFieldMixin, PGPPublicKeyFieldMixin, models.DecimalField): """Decimal PGP public key encrypted field for postgres.""" + encrypt_sql = PGP_PUB_ENCRYPT_SQL_WITH_NULLIF class DecimalPGPSymmetricKeyField(DecimalPGPFieldMixin, PGPSymmetricKeyFieldMixin, models.DecimalField): """Decimal PGP symmetric key encrypted field for postgres.""" + encrypt_sql = PGP_SYM_ENCRYPT_SQL_WITH_NULLIF class FloatPGPPublicKeyField(PGPPublicKeyFieldMixin, models.FloatField): @@ -154,3 +156,15 @@ class TimePGPSymmetricKeyField(PGPSymmetricKeyFieldMixin, models.TimeField): """Float PGP symmetric key encrypted field for postgres.""" encrypt_sql = PGP_SYM_ENCRYPT_SQL_WITH_NULLIF cast_type = 'TIME' + + +class JSONPGPPublicKeyField(PGPPublicKeyFieldMixin, models.JSONField): + """JSON PGP public key encrypted field for postgres.""" + encrypt_sql = PGP_PUB_ENCRYPT_SQL_WITH_NULLIF + cast_type = 'jsonb' + + +class JSONPGPSymmetricKeyField(PGPSymmetricKeyFieldMixin, models.JSONField): + """Float PGP symmetric key encrypted field for postgres.""" + encrypt_sql = PGP_SYM_ENCRYPT_SQL_WITH_NULLIF + cast_type = 'jsonb' diff --git a/pgcrypto/mixins.py b/pgcrypto/mixins.py index 8d4e897..04f7708 100644 --- a/pgcrypto/mixins.py +++ b/pgcrypto/mixins.py @@ -157,7 +157,7 @@ def get_decrypt_sql(self, connection): class DecimalPGPFieldMixin: """Decimal PGP encrypted field mixin for postgres.""" - cast_type = 'NUMERIC(%(max_digits)s, %(decimal_places)s)' + cast_type = 'DECIMAL(%(max_digits)s, %(decimal_places)s)' def get_cast_sql(self): """Get cast sql.""" diff --git a/requirements.txt b/requirements.txt index 0350699..728ba56 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -e . -django>=1.11,<3.2 +django>=3.2,<=5.0.2 diff --git a/requirements_dev.txt b/requirements_dev.txt index 8b47c60..bd66a85 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,16 +1,15 @@ colour-runner==0.1.1 -coveralls==3.1.0 -coverage==5.5 +coveralls==1.8.0 +coverage==7.4.3 dj-database-url==0.5.0 -factory-boy==3.2.0 -flake8-docstrings==1.6.0 -flake8-import-order==0.18.1 -flake8==3.9.2 +factory-boy==3.3.0 +flake8-docstrings==1.7.0 +flake8-import-order==0.18.2 +flake8==7.0.0 incuna-test-utils==8.0.0 -pip==22.3 -psycopg2-binary==2.8.6 -pyflakes==2.3.1 -pycodestyle==2.7.0 -setuptools==65.5.0 -twine==3.4.1 -wheel==0.36.2 +psycopg2-binary==2.9.9 +pyflakes==3.2.0 +pycodestyle==2.11.1 +setuptools==69.1.1 +twine==5.0.0 +wheel==0.42.0 diff --git a/tests/db_setup.py b/tests/db_setup.py new file mode 100644 index 0000000..d4f69f1 --- /dev/null +++ b/tests/db_setup.py @@ -0,0 +1,13 @@ +import psycopg2 + + +def create_dbs(db_config, databases): + """Set up the databases for test.""" + connection = psycopg2.connect(**db_config) + connection.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) + cursor = connection.cursor() + for database in databases: + cursor.execute(f"DROP DATABASE IF EXISTS {database}") + cursor.execute(f"CREATE DATABASE {database}") + cursor.close() + connection.close() diff --git a/tests/factories.py b/tests/factories.py index 76f2745..b59f58e 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -1,7 +1,9 @@ -from datetime import date, datetime +from datetime import date from decimal import Decimal import factory +from django.utils import timezone + from .models import EncryptedFKModel, EncryptedModel @@ -28,7 +30,7 @@ class EncryptedModelFactory(factory.django.DjangoModelFactory): char_pub_field = factory.Sequence('Text {}'.format) date_pgp_pub_field = date.today() - datetime_pgp_pub_field = datetime.now() + datetime_pgp_pub_field = timezone.now() decimal_pgp_pub_field = Decimal('123456.78') boolean_pgp_pub_field = True @@ -39,9 +41,12 @@ class EncryptedModelFactory(factory.django.DjangoModelFactory): char_sym_field = factory.Sequence('Text {}'.format) date_pgp_sym_field = date.today() - datetime_pgp_sym_field = datetime.now() + datetime_pgp_sym_field = timezone.now() boolean_pgp_sym_field = False + json_pgp_pub_field = {"key": "value"} + json_pgp_sym_field = {"key": "value"} + fk_model = factory.SubFactory(EncryptedFKModelFactory) class Meta: diff --git a/tests/models.py b/tests/models.py index 52f1565..67697f0 100644 --- a/tests/models.py +++ b/tests/models.py @@ -59,6 +59,9 @@ class EncryptedModel(models.Model): float_pgp_sym_field = fields.FloatPGPSymmetricKeyField(blank=True, null=True) boolean_pgp_sym_field = fields.BooleanPGPSymmetricKeyField(blank=True, null=True) + json_pgp_pub_field = fields.JSONPGPPublicKeyField(blank=True, null=True) + json_pgp_sym_field = fields.JSONPGPSymmetricKeyField(blank=True, null=True) + fk_model = models.ForeignKey( EncryptedFKModel, blank=True, null=True, on_delete=models.CASCADE ) diff --git a/tests/run.py b/tests/run.py index 92e8b44..0e86db0 100755 --- a/tests/run.py +++ b/tests/run.py @@ -9,6 +9,23 @@ from django.conf import settings from django.test.runner import DiscoverRunner +print("Current directory:", os.getcwd()) +print("Contents of the directory:", os.listdir(os.getcwd())) +print("Contents of the directory of run.py:", os.listdir(os.chdir(os.path.dirname(os.path.abspath(__file__))))) + +from db_setup import create_dbs + +default_db_config = { + 'dbname': 'postgres', + 'user': 'postgres', + 'password': 'postgres', + 'host': 'localhost', + 'port': 5432, +} + +create_dbs(default_db_config, + databases=('pgcrypto_fields', 'pgcrypto_fields_diff')) + BASEDIR = os.path.dirname(os.path.dirname(__file__)) PUBLIC_PGP_KEY_PATH = os.path.abspath( os.path.join(BASEDIR, 'tests/keys/public.key') @@ -24,7 +41,7 @@ ) diff_keys = dj_database_url.config( - default='postgres://localhost/pgcrypto_fields_diff' + default='postgres://postgres:postgres@localhost/pgcrypto_fields_diff' ) # Cannot chain onto the config() call due to error @@ -37,7 +54,7 @@ settings.configure( DATABASES={ 'default': dj_database_url.config( - default='postgres://localhost/pgcrypto_fields' + default='postgres://postgres:postgres@localhost/pgcrypto_fields' ), 'diff_keys': diff_keys, }, @@ -52,6 +69,8 @@ PRIVATE_PGP_KEY=open(PRIVATE_PGP_KEY_PATH, 'r').read(), PGCRYPTO_KEY='ultrasecret', DEBUG=True, + DEFAULT_AUTO_FIELD='django.db.models.BigAutoField', + USE_TZ=True, ) django.setup() diff --git a/tests/test_fields.py b/tests/test_fields.py index 992a89d..a80d255 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1,4 +1,4 @@ -from datetime import date, datetime +from datetime import date, datetime, timezone from decimal import Decimal from unittest.mock import MagicMock @@ -6,6 +6,7 @@ from django.conf import settings from django.db import connections, models, reset_queries from django.test import TestCase +from django.utils import timezone as tz from incuna_test_utils.utils import field_names from pgcrypto import fields @@ -105,6 +106,8 @@ def test_fields(self): 'float_pgp_sym_field', 'boolean_pgp_pub_field', 'boolean_pgp_sym_field', + 'json_pgp_pub_field', + 'json_pgp_sym_field', 'fk_model', ) self.assertCountEqual(fields, expected) @@ -398,7 +401,7 @@ def test_pgp_symmetric_key_date_form(self): def test_pgp_symmetric_key_datetime_form(self): """Assert form field and widget for `DateTimePGPSymmetricKeyField` field.""" - expected = datetime.now() + expected = tz.now() instance = EncryptedModelFactory.create(datetime_pgp_sym_field=expected) instance.refresh_from_db() # Ensure the PGSQL casting works right @@ -413,12 +416,12 @@ def test_pgp_symmetric_key_datetime_form(self): self.assertTrue( cleaned_data['datetime_pgp_sym_field'], - datetime(2016, 8, 1, 14, 0, 0) + datetime(2016, 8, 1, 14, 0, 0, tzinfo=timezone.utc) ) def test_pgp_symmetric_key_time(self): """Assert date is save with an `TimePGPSymmetricKeyField` field.""" - expected = datetime.now().time() + expected = tz.now().time() instance = EncryptedModelFactory.create(time_pgp_sym_field=expected) instance.refresh_from_db() # Ensure the PGSQL casting works right @@ -430,7 +433,7 @@ def test_pgp_symmetric_key_time(self): def test_pgp_pub_key_time(self): """Assert date is save with an `TimePGPPublicKeyField` field.""" - expected = datetime.now().time() + expected = tz.now().time() instance = EncryptedModelFactory.create(time_pgp_pub_field=expected) instance.refresh_from_db() # Ensure the PGSQL casting works right @@ -442,7 +445,7 @@ def test_pgp_pub_key_time(self): def test_pgp_symmetric_key_time_form(self): """Assert form field and widget for `TimePGPSymmetricKeyField` field.""" - expected = datetime.now().time() + expected = tz.now().time() instance = EncryptedModelFactory.create(time_pgp_sym_field=expected) instance.refresh_from_db() # Ensure the PGSQL casting works right @@ -462,7 +465,7 @@ def test_pgp_symmetric_key_time_form(self): def test_pgp_public_key_time_form(self): """Assert form field and widget for `TimePGPSymmetricKeyField` field.""" - expected = datetime.now().time() + expected = tz.now().time() instance = EncryptedModelFactory.create(time_pgp_pub_field=expected) instance.refresh_from_db() # Ensure the PGSQL casting works right @@ -722,78 +725,104 @@ def test_pgp_pub_key_date_lookups(self): def test_pgp_symmetric_key_datetime_lookups(self): """Assert lookups `DateTimePGPSymmetricKeyField` field.""" - EncryptedModelFactory.create(datetime_pgp_sym_field=datetime(2016, 7, 1, 0, 0, 0)) - EncryptedModelFactory.create(datetime_pgp_sym_field=datetime(2016, 8, 1, 0, 0, 0)) - EncryptedModelFactory.create(datetime_pgp_sym_field=datetime(2016, 9, 1, 0, 0, 0)) + EncryptedModelFactory.create( + datetime_pgp_sym_field=datetime(2016, 7, 1, 0, 0, 0, tzinfo=timezone.utc) + ) + EncryptedModelFactory.create( + datetime_pgp_sym_field=datetime(2016, 8, 1, 0, 0, 0, tzinfo=timezone.utc) + ) + EncryptedModelFactory.create( + datetime_pgp_sym_field=datetime(2016, 9, 1, 0, 0, 0, tzinfo=timezone.utc) + ) # EXACT self.assertEqual( 1, EncryptedModel.objects.filter( - datetime_pgp_sym_field__exact=datetime(2016, 8, 1, 0, 0, 0) - ).count() + datetime_pgp_sym_field__exact=datetime( + 2016, 8, 1, 0, 0, 0, tzinfo=timezone.utc + ) + ).count(), ) self.assertEqual( 0, EncryptedModel.objects.filter( - datetime_pgp_sym_field__exact=datetime(2016, 8, 1, 0, 0, 1) - ).count() + datetime_pgp_sym_field__exact=datetime( + 2016, 8, 1, 0, 0, 1, tzinfo=timezone.utc + ) + ).count(), ) # GT self.assertEqual( 1, EncryptedModel.objects.filter( - datetime_pgp_sym_field__gt=datetime(2016, 8, 1, 0, 0, 0) - ).count() + datetime_pgp_sym_field__gt=datetime( + 2016, 8, 1, 0, 0, 0, tzinfo=timezone.utc + ) + ).count(), ) self.assertEqual( 0, EncryptedModel.objects.filter( - datetime_pgp_sym_field__gt=datetime(2016, 10, 1, 0, 0, 0) - ).count() + datetime_pgp_sym_field__gt=datetime( + 2016, 10, 1, 0, 0, 0, tzinfo=timezone.utc + ) + ).count(), ) # GTE self.assertEqual( 2, EncryptedModel.objects.filter( - datetime_pgp_sym_field__gte=datetime(2016, 8, 1, 0, 0, 0) - ).count() + datetime_pgp_sym_field__gte=datetime( + 2016, 8, 1, 0, 0, 0, tzinfo=timezone.utc + ) + ).count(), ) self.assertEqual( 0, EncryptedModel.objects.filter( - datetime_pgp_sym_field__gte=datetime(2016, 10, 1, 0, 0, 0) - ).count() + datetime_pgp_sym_field__gte=datetime( + 2016, 10, 1, 0, 0, 0, tzinfo=timezone.utc + ) + ).count(), ) # LE self.assertEqual( 1, EncryptedModel.objects.filter( - datetime_pgp_sym_field__lt=datetime(2016, 8, 1, 0, 0, 0) - ).count() + datetime_pgp_sym_field__lt=datetime( + 2016, 8, 1, 0, 0, 0, tzinfo=timezone.utc + ) + ).count(), ) self.assertEqual( 0, EncryptedModel.objects.filter( - datetime_pgp_sym_field__lt=datetime(2016, 6, 1, 0, 0, 0) - ).count() + datetime_pgp_sym_field__lt=datetime( + 2016, 6, 1, 0, 0, 0, tzinfo=timezone.utc + ) + ).count(), ) # LTE self.assertEqual( 2, EncryptedModel.objects.filter( - datetime_pgp_sym_field__lte=datetime(2016, 8, 1, 0, 0, 0) - ).count() + datetime_pgp_sym_field__lte=datetime( + 2016, 8, 1, 0, 0, 0, tzinfo=timezone.utc + ) + ).count(), ) self.assertEqual( 0, EncryptedModel.objects.filter( - datetime_pgp_sym_field__lte=datetime(2016, 6, 1, 0, 0, 0) - ).count() + datetime_pgp_sym_field__lte=datetime( + 2016, 6, 1, 0, 0, 0, tzinfo=timezone.utc + ) + ).count(), ) # RANGE @@ -801,8 +830,8 @@ def test_pgp_symmetric_key_datetime_lookups(self): 3, EncryptedModel.objects.filter( datetime_pgp_sym_field__range=[ - datetime(2016, 6, 1, 0, 0, 0), - datetime(2016, 11, 1, 23, 59, 59) + datetime(2016, 6, 1, 0, 0, 0, tzinfo=timezone.utc), + datetime(2016, 11, 1, 23, 59, 59, tzinfo=timezone.utc) ] ).count() ) @@ -811,8 +840,8 @@ def test_pgp_symmetric_key_datetime_lookups(self): 2, EncryptedModel.objects.filter( datetime_pgp_sym_field__range=[ - datetime(2016, 7, 1, 0, 0, 0), - datetime(2016, 8, 1, 0, 0, 0) + datetime(2016, 7, 1, 0, 0, 0, tzinfo=timezone.utc), + datetime(2016, 8, 1, 0, 0, 0, tzinfo=timezone.utc) ] ).count() ) @@ -821,7 +850,7 @@ def test_pgp_symmetric_key_datetime_lookups(self): 0, EncryptedModel.objects.filter( datetime_pgp_sym_field__range=[ - datetime(2016, 10, 1, 0, 0, 1), + datetime(2016, 10, 1, 0, 0, 1, tzinfo=timezone.utc), None ] ).count() @@ -829,78 +858,104 @@ def test_pgp_symmetric_key_datetime_lookups(self): def test_pgp_public_key_datetime_lookups(self): """Assert lookups `DateTimePGPPublicKeyField` field.""" - EncryptedModelFactory.create(datetime_pgp_pub_field=datetime(2016, 7, 1, 0, 0, 0)) - EncryptedModelFactory.create(datetime_pgp_pub_field=datetime(2016, 8, 1, 0, 0, 0)) - EncryptedModelFactory.create(datetime_pgp_pub_field=datetime(2016, 9, 1, 0, 0, 0)) + EncryptedModelFactory.create( + datetime_pgp_pub_field=datetime(2016, 7, 1, 0, 0, 0, tzinfo=timezone.utc) + ) + EncryptedModelFactory.create( + datetime_pgp_pub_field=datetime(2016, 8, 1, 0, 0, 0, tzinfo=timezone.utc) + ) + EncryptedModelFactory.create( + datetime_pgp_pub_field=datetime(2016, 9, 1, 0, 0, 0, tzinfo=timezone.utc) + ) # EXACT self.assertEqual( 1, EncryptedModel.objects.filter( - datetime_pgp_pub_field__exact=datetime(2016, 8, 1, 0, 0, 0) - ).count() + datetime_pgp_pub_field__exact=datetime( + 2016, 8, 1, 0, 0, 0, tzinfo=timezone.utc + ) + ).count(), ) self.assertEqual( 0, EncryptedModel.objects.filter( - datetime_pgp_pub_field__exact=datetime(2016, 8, 1, 0, 0, 1) - ).count() + datetime_pgp_pub_field__exact=datetime( + 2016, 8, 1, 0, 0, 1, tzinfo=timezone.utc + ) + ).count(), ) # GT self.assertEqual( 1, EncryptedModel.objects.filter( - datetime_pgp_pub_field__gt=datetime(2016, 8, 1, 0, 0, 0) - ).count() + datetime_pgp_pub_field__gt=datetime( + 2016, 8, 1, 0, 0, 0, tzinfo=timezone.utc + ) + ).count(), ) self.assertEqual( 0, EncryptedModel.objects.filter( - datetime_pgp_pub_field__gt=datetime(2016, 10, 1, 0, 0, 0) - ).count() + datetime_pgp_pub_field__gt=datetime( + 2016, 10, 1, 0, 0, 0, tzinfo=timezone.utc + ) + ).count(), ) # GTE self.assertEqual( 2, EncryptedModel.objects.filter( - datetime_pgp_pub_field__gte=datetime(2016, 8, 1, 0, 0, 0) - ).count() + datetime_pgp_pub_field__gte=datetime( + 2016, 8, 1, 0, 0, 0, tzinfo=timezone.utc + ) + ).count(), ) self.assertEqual( 0, EncryptedModel.objects.filter( - datetime_pgp_pub_field__gte=datetime(2016, 10, 1, 0, 0, 0) - ).count() + datetime_pgp_pub_field__gte=datetime( + 2016, 10, 1, 0, 0, 0, tzinfo=timezone.utc + ) + ).count(), ) # LE self.assertEqual( 1, EncryptedModel.objects.filter( - datetime_pgp_pub_field__lt=datetime(2016, 8, 1, 0, 0, 0) - ).count() + datetime_pgp_pub_field__lt=datetime( + 2016, 8, 1, 0, 0, 0, tzinfo=timezone.utc + ) + ).count(), ) self.assertEqual( 0, EncryptedModel.objects.filter( - datetime_pgp_pub_field__lt=datetime(2016, 6, 1, 0, 0, 0) - ).count() + datetime_pgp_pub_field__lt=datetime( + 2016, 6, 1, 0, 0, 0, tzinfo=timezone.utc + ) + ).count(), ) # LTE self.assertEqual( 2, EncryptedModel.objects.filter( - datetime_pgp_pub_field__lte=datetime(2016, 8, 1, 0, 0, 0) - ).count() + datetime_pgp_pub_field__lte=datetime( + 2016, 8, 1, 0, 0, 0, tzinfo=timezone.utc + ) + ).count(), ) self.assertEqual( 0, EncryptedModel.objects.filter( - datetime_pgp_pub_field__lte=datetime(2016, 6, 1, 0, 0, 0) - ).count() + datetime_pgp_pub_field__lte=datetime( + 2016, 6, 1, 0, 0, 0, tzinfo=timezone.utc + ) + ).count(), ) # RANGE @@ -908,8 +963,8 @@ def test_pgp_public_key_datetime_lookups(self): 3, EncryptedModel.objects.filter( datetime_pgp_pub_field__range=[ - datetime(2016, 6, 1, 0, 0, 0), - datetime(2016, 11, 1, 23, 59, 59) + datetime(2016, 6, 1, 0, 0, 0, tzinfo=timezone.utc), + datetime(2016, 11, 1, 23, 59, 59, tzinfo=timezone.utc) ] ).count() ) @@ -918,8 +973,8 @@ def test_pgp_public_key_datetime_lookups(self): 2, EncryptedModel.objects.filter( datetime_pgp_pub_field__range=[ - datetime(2016, 7, 1, 0, 0, 0), - datetime(2016, 8, 1, 0, 0, 0) + datetime(2016, 7, 1, 0, 0, 0, tzinfo=timezone.utc), + datetime(2016, 8, 1, 0, 0, 0, tzinfo=timezone.utc) ] ).count() ) @@ -928,7 +983,7 @@ def test_pgp_public_key_datetime_lookups(self): 0, EncryptedModel.objects.filter( datetime_pgp_pub_field__range=[ - datetime(2016, 10, 1, 0, 0, 1), + datetime(2016, 10, 1, 0, 0, 1, tzinfo=timezone.utc), None ] ).count() @@ -1350,11 +1405,21 @@ def test_update_or_create(self): def test_aggregates(self): """Test aggregate support.""" - EncryptedModelFactory.create(datetime_pgp_sym_field=datetime(2016, 7, 1, 0, 0, 0)) - EncryptedModelFactory.create(datetime_pgp_sym_field=datetime(2016, 7, 2, 0, 0, 0)) - EncryptedModelFactory.create(datetime_pgp_sym_field=datetime(2016, 8, 1, 0, 0, 0)) - EncryptedModelFactory.create(datetime_pgp_sym_field=datetime(2016, 9, 1, 0, 0, 0)) - EncryptedModelFactory.create(datetime_pgp_sym_field=datetime(2016, 9, 2, 0, 0, 0)) + EncryptedModelFactory.create( + datetime_pgp_sym_field=datetime(2016, 7, 1, 0, 0, 0, tzinfo=timezone.utc) + ) + EncryptedModelFactory.create( + datetime_pgp_sym_field=datetime(2016, 7, 2, 0, 0, 0, tzinfo=timezone.utc) + ) + EncryptedModelFactory.create( + datetime_pgp_sym_field=datetime(2016, 8, 1, 0, 0, 0, tzinfo=timezone.utc) + ) + EncryptedModelFactory.create( + datetime_pgp_sym_field=datetime(2016, 9, 1, 0, 0, 0, tzinfo=timezone.utc) + ) + EncryptedModelFactory.create( + datetime_pgp_sym_field=datetime(2016, 9, 2, 0, 0, 0, tzinfo=timezone.utc) + ) total_2016 = self.model.objects.aggregate( count=models.Count('datetime_pgp_sym_field') @@ -1364,8 +1429,8 @@ def test_aggregates(self): total_july = self.model.objects.filter( datetime_pgp_sym_field__range=[ - datetime(2016, 7, 1, 0, 0, 0), - datetime(2016, 7, 30, 23, 59, 59) + datetime(2016, 7, 1, 0, 0, 0, tzinfo=timezone.utc), + datetime(2016, 7, 30, 23, 59, 59, tzinfo=timezone.utc) ] ).aggregate( count=models.Count('datetime_pgp_sym_field') @@ -1380,13 +1445,17 @@ def test_aggregates(self): ) self.assertEqual(5, total_2016['count']) - self.assertEqual(datetime(2016, 7, 1, 0, 0, 0), total_2016['min']) - self.assertEqual(datetime(2016, 9, 2, 0, 0, 0), total_2016['max']) + self.assertEqual( + datetime(2016, 7, 1, 0, 0, 0, tzinfo=timezone.utc), total_2016["min"] + ) + self.assertEqual( + datetime(2016, 9, 2, 0, 0, 0, tzinfo=timezone.utc), total_2016["max"] + ) total_july = self.model.objects.filter( datetime_pgp_sym_field__range=[ - datetime(2016, 7, 1, 0, 0, 0), - datetime(2016, 7, 30, 23, 59, 59) + datetime(2016, 7, 1, 0, 0, 0, tzinfo=timezone.utc), + datetime(2016, 7, 30, 23, 59, 59, tzinfo=timezone.utc) ] ).aggregate( count=models.Count('datetime_pgp_sym_field'), @@ -1395,8 +1464,12 @@ def test_aggregates(self): ) self.assertEqual(2, total_july['count']) - self.assertEqual(datetime(2016, 7, 1, 0, 0, 0), total_july['min']) - self.assertEqual(datetime(2016, 7, 2, 0, 0, 0), total_july['max']) + self.assertEqual( + datetime(2016, 7, 1, 0, 0, 0, tzinfo=timezone.utc), total_july["min"] + ) + self.assertEqual( + datetime(2016, 7, 2, 0, 0, 0, tzinfo=timezone.utc), total_july["max"] + ) def test_distinct(self): """Test distinct support.""" @@ -1467,8 +1540,8 @@ def test_annotate(self): def test_get_col(self): """Test get_col for related alias.""" - related = EncryptedDateTime.objects.create(value=datetime.now()) - related_again = EncryptedDateTime.objects.create(value=datetime.now()) + related = EncryptedDateTime.objects.create(value=tz.now()) + related_again = EncryptedDateTime.objects.create(value=tz.now()) RelatedDateTime.objects.create(related=related, related_again=related_again) @@ -1546,3 +1619,117 @@ def test_write_to_diff_keys(self): instance.hmac_field, expected ) + + def test_json_pgp_pub_field(self): + """Test JSONPGPPublicKeyField.""" + expected = {'a': 1, 'b': '2', 'c': [1, 2, 3]} + EncryptedModelFactory.create(json_pgp_pub_field=expected) + + instance = EncryptedModel.objects.get() + + self.assertIsInstance( + instance.json_pgp_pub_field, + dict + ) + + self.assertEqual( + instance.json_pgp_pub_field, + expected + ) + + items = EncryptedModel.objects.filter(json_pgp_pub_field__a=1) + + self.assertEqual( + 1, + len(items) + ) + + items = EncryptedModel.objects.filter(json_pgp_pub_field__c__contains=2) + + self.assertEqual( + 1, + len(items) + ) + + items = EncryptedModel.objects.filter(json_pgp_pub_field__b=2) + + self.assertEqual( + 0, + len(items) + ) + + def test_json_pgp_sym_field(self): + """Test JsonPGPSymmetricKeyField.""" + expected = {'a': 1, 'b': '2', 'c': [1, 2, 3]} + EncryptedModelFactory.create(json_pgp_sym_field=expected) + + instance = EncryptedModel.objects.get() + + self.assertIsInstance( + instance.json_pgp_sym_field, + dict + ) + + self.assertEqual( + instance.json_pgp_sym_field, + expected + ) + + items = EncryptedModel.objects.filter(json_pgp_sym_field__a=1) + + self.assertEqual( + 1, + len(items) + ) + + items = EncryptedModel.objects.filter(json_pgp_sym_field__c__contains=2) + + self.assertEqual( + 1, + len(items) + ) + + items = EncryptedModel.objects.filter(json_pgp_sym_field__b=2) + + self.assertEqual( + 0, + len(items) + ) + + def test_pgp_public_key_json_form(self): + """Assert form field and widget for `JSONPGPSymmetricKeyField` field.""" + expected = {'a': 1, 'b': '2', 'c': [1, 2, 3]} + instance = EncryptedModelFactory.create(json_pgp_pub_field=expected) + + payload = { + 'json_pgp_pub_field': expected + } + + form = EncryptedForm(payload, instance=instance) + self.assertTrue(form.is_valid()) + + cleaned_data = form.cleaned_data + + self.assertTrue( + cleaned_data['json_pgp_pub_field'], + expected + ) + + def test_pgp_symmetric_key_json_form(self): + """Assert form field and widget for `JSONPGPSymmetricKeyField` field.""" + expected = {'a': 1, 'b': '2', 'c': [1, 2, 3]} + instance = EncryptedModelFactory.create(json_pgp_sym_field=expected) + + payload = { + 'json_pgp_sym_field': expected + } + + form = EncryptedForm(payload, instance=instance) + self.assertTrue(form.is_valid()) + + cleaned_data = form.cleaned_data + + self.assertTrue( + cleaned_data['json_pgp_sym_field'], + expected + )