Skip to content

Commit 14e23a2

Browse files
gsilvaptDDuarte
andauthored
tests: add test to all search and filters (#103)
* tests: add test to all search and filters To continue ensuring they work as designed. * fixup: run black on new files * misc: add script to create psql container * fix test for IP fields --------- Co-authored-by: Duarte Duarte <[email protected]>
1 parent 825e832 commit 14e23a2

File tree

4 files changed

+84
-0
lines changed

4 files changed

+84
-0
lines changed

dev/scripts/create-mysql-container.sh

100644100755
File mode changed.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env bash
2+
3+
4+
docker run -d --name dev-surface-psql \
5+
--health-cmd='pg_isready -U $POSTGRES_USER' --health-interval='5s' \
6+
-p 35432:5432 \
7+
-e POSTGRES_PASSWORD=surfdbpassword \
8+
-e POSTGRES_USER=surface \
9+
postgres:15.2-alpine

surface/surface/tests/__init__.py

Whitespace-only changes.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
from django.contrib.admin.sites import site
2+
from django.core.exceptions import ValidationError
3+
from django.test import TestCase
4+
from django.utils import timezone
5+
from django.db.utils import DataError
6+
from django.db import transaction
7+
8+
9+
def _clean_field(fieldname):
10+
# For things like ('time', DateRangeFilter)
11+
if isinstance(fieldname, tuple):
12+
fieldname = fieldname[0]
13+
# Custom field... we will just skip them...
14+
if not isinstance(fieldname, str):
15+
return None
16+
# Remove = from fields
17+
if fieldname[0] == "=":
18+
fieldname = fieldname[1:]
19+
return fieldname
20+
21+
22+
def _method_factory(model_class, model_admin):
23+
def method_tester(test_case: TestCase):
24+
# Test the filters and searchfields
25+
for fieldname in list(model_admin.list_filter) + list(model_admin.search_fields):
26+
fieldname = _clean_field(fieldname)
27+
if not fieldname:
28+
continue
29+
try:
30+
try:
31+
# validate that filter can be executed for fieldname, don't care about result
32+
with transaction.atomic():
33+
test_case.assertIsInstance(model_class.objects.filter(**{fieldname: "1"}).count(), int)
34+
except ValidationError:
35+
# Datetime fields?
36+
test_case.assertIsInstance(
37+
model_class.objects.filter(**{fieldname: timezone.now()}).count(),
38+
int,
39+
)
40+
except DataError:
41+
# IP fields?
42+
test_case.assertIsInstance(
43+
model_class.objects.filter(**{fieldname: "8.8.8.8"}).count(),
44+
int,
45+
)
46+
except Exception as e:
47+
test_case.fail(f"{model_admin} search field test failed - {e}")
48+
49+
return method_tester
50+
51+
52+
class AdminMeta(type):
53+
def __init__(cls, *args, **kwargs):
54+
for model_class, model_admin in site._registry.items():
55+
setattr(
56+
cls,
57+
f'test_{str(model_admin).replace(".", "_")}',
58+
_method_factory(model_class, model_admin),
59+
)
60+
61+
62+
class Test(TestCase, metaclass=AdminMeta):
63+
"""
64+
## Context
65+
66+
Metaclass and dynamic test method generation is bad (in principle) for lack of test clarity.
67+
In this case, dynamic was already in place within a single method which prevent proper test result output:
68+
* print would always show up
69+
* if we remove print, when method failed we didn't know which model_admin was bad
70+
71+
Creating a method per model_admin allows result output to be controlled by the testrunner (verbosity 1 displays only dots,
72+
2 will display each model_admin as method) and parseable by test result tools
73+
"""
74+
75+
pass

0 commit comments

Comments
 (0)