From 4395ae0ca5b1fd358d177a4a6f647b9f5635817f Mon Sep 17 00:00:00 2001 From: Gustavo Silva Date: Fri, 31 Mar 2023 10:37:15 +0100 Subject: [PATCH 1/4] tests: add test to all search and filters To continue ensuring they work as designed. --- surface/surface/tests/__init__.py | 0 .../tests/test_all_search_and_filter.py | 72 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 surface/surface/tests/__init__.py create mode 100644 surface/surface/tests/test_all_search_and_filter.py diff --git a/surface/surface/tests/__init__.py b/surface/surface/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/surface/surface/tests/test_all_search_and_filter.py b/surface/surface/tests/test_all_search_and_filter.py new file mode 100644 index 00000000..cbb986f5 --- /dev/null +++ b/surface/surface/tests/test_all_search_and_filter.py @@ -0,0 +1,72 @@ +from django.contrib.admin.sites import site +from django.core.exceptions import ValidationError +from django.test import TestCase +from django.utils import timezone + + +def _clean_field(fieldname): + # For things like ('time', DateRangeFilter) + if isinstance(fieldname, tuple): + fieldname = fieldname[0] + # Custom field... we will just skip them... + if not isinstance(fieldname, str): + return None + # Remove = from fields + if fieldname[0] == "=": + fieldname = fieldname[1:] + return fieldname + + +def _method_factory(model_class, model_admin): + def method_tester(test_case: TestCase): + # Test the filters and searchfields + for fieldname in list(model_admin.list_filter) + list( + model_admin.search_fields + ): + fieldname = _clean_field(fieldname) + if not fieldname: + continue + try: + try: + # validate that filter can be executed for fieldname, don't care about result + test_case.assertIsInstance( + model_class.objects.filter(**{fieldname: "1"}).count(), int + ) + except ValidationError: + # Datetime fields? + test_case.assertIsInstance( + model_class.objects.filter( + **{fieldname: timezone.now()} + ).count(), + int, + ) + except Exception as e: + test_case.fail(f"{model_admin} search field test failed - {e}") + + return method_tester + + +class AdminMeta(type): + def __init__(cls, *args, **kwargs): + for model_class, model_admin in site._registry.items(): + setattr( + cls, + f'test_{str(model_admin).replace(".", "_")}', + _method_factory(model_class, model_admin), + ) + + +class Test(TestCase, metaclass=AdminMeta): + """ + ## Context + + Metaclass and dynamic test method generation is bad (in principle) for lack of test clarity. + In this case, dynamic was already in place within a single method which prevent proper test result output: + * print would always show up + * if we remove print, when method failed we didn't know which model_admin was bad + + Creating a method per model_admin allows result output to be controlled by the testrunner (verbosity 1 displays only dots, + 2 will display each model_admin as method) and parseable by test result tools + """ + + pass From c788974a4843df21e53cf552212af2877263ea06 Mon Sep 17 00:00:00 2001 From: Gustavo Silva Date: Fri, 31 Mar 2023 10:42:56 +0100 Subject: [PATCH 2/4] fixup: run black on new files --- surface/surface/tests/test_all_search_and_filter.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/surface/surface/tests/test_all_search_and_filter.py b/surface/surface/tests/test_all_search_and_filter.py index cbb986f5..1a7795da 100644 --- a/surface/surface/tests/test_all_search_and_filter.py +++ b/surface/surface/tests/test_all_search_and_filter.py @@ -20,24 +20,18 @@ def _clean_field(fieldname): def _method_factory(model_class, model_admin): def method_tester(test_case: TestCase): # Test the filters and searchfields - for fieldname in list(model_admin.list_filter) + list( - model_admin.search_fields - ): + for fieldname in list(model_admin.list_filter) + list(model_admin.search_fields): fieldname = _clean_field(fieldname) if not fieldname: continue try: try: # validate that filter can be executed for fieldname, don't care about result - test_case.assertIsInstance( - model_class.objects.filter(**{fieldname: "1"}).count(), int - ) + test_case.assertIsInstance(model_class.objects.filter(**{fieldname: "1"}).count(), int) except ValidationError: # Datetime fields? test_case.assertIsInstance( - model_class.objects.filter( - **{fieldname: timezone.now()} - ).count(), + model_class.objects.filter(**{fieldname: timezone.now()}).count(), int, ) except Exception as e: From c6cb66b1696cf8601bfae84e50d9ea6b85257cf9 Mon Sep 17 00:00:00 2001 From: Gustavo Silva Date: Sat, 15 Apr 2023 14:34:36 +0100 Subject: [PATCH 3/4] misc: add script to create psql container --- dev/scripts/create-mysql-container.sh | 0 dev/scripts/create-postgres-container.sh | 9 +++++++++ 2 files changed, 9 insertions(+) mode change 100644 => 100755 dev/scripts/create-mysql-container.sh create mode 100755 dev/scripts/create-postgres-container.sh diff --git a/dev/scripts/create-mysql-container.sh b/dev/scripts/create-mysql-container.sh old mode 100644 new mode 100755 diff --git a/dev/scripts/create-postgres-container.sh b/dev/scripts/create-postgres-container.sh new file mode 100755 index 00000000..b43b262d --- /dev/null +++ b/dev/scripts/create-postgres-container.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + + +docker run -d --name dev-surface-psql \ + --health-cmd='pg_isready -U $POSTGRES_USER' --health-interval='5s' \ + -p 35432:5432 \ + -e POSTGRES_PASSWORD=surfdbpassword \ + -e POSTGRES_USER=surface \ + postgres:15.2-alpine From 075e60f637614cb9c740979b24df74db2ce315cd Mon Sep 17 00:00:00 2001 From: Duarte Duarte Date: Sat, 15 Apr 2023 15:09:05 +0100 Subject: [PATCH 4/4] fix test for IP fields --- surface/surface/tests/test_all_search_and_filter.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/surface/surface/tests/test_all_search_and_filter.py b/surface/surface/tests/test_all_search_and_filter.py index 1a7795da..7b22a568 100644 --- a/surface/surface/tests/test_all_search_and_filter.py +++ b/surface/surface/tests/test_all_search_and_filter.py @@ -2,6 +2,8 @@ from django.core.exceptions import ValidationError from django.test import TestCase from django.utils import timezone +from django.db.utils import DataError +from django.db import transaction def _clean_field(fieldname): @@ -27,13 +29,20 @@ def method_tester(test_case: TestCase): try: try: # validate that filter can be executed for fieldname, don't care about result - test_case.assertIsInstance(model_class.objects.filter(**{fieldname: "1"}).count(), int) + with transaction.atomic(): + test_case.assertIsInstance(model_class.objects.filter(**{fieldname: "1"}).count(), int) except ValidationError: # Datetime fields? test_case.assertIsInstance( model_class.objects.filter(**{fieldname: timezone.now()}).count(), int, ) + except DataError: + # IP fields? + test_case.assertIsInstance( + model_class.objects.filter(**{fieldname: "8.8.8.8"}).count(), + int, + ) except Exception as e: test_case.fail(f"{model_admin} search field test failed - {e}")