transport-accessibility/transport_accessibility/pt_map/tests_gtfs_compliance.py
Johannes Randerath 53ab731787 Cookies and tests
- Added cookie banner
- Improved tests and fixed models accordingly
2024-07-03 01:18:17 +02:00

166 lines
8.3 KiB
Python

"""
Test GTFS Compliance
====================
Test suite meant to make sure the models do their best to enforce compliance of the database with the gtfs standard.
In particular we are testing:
1. All of the txt files the reference suggests have a model representation.
2. All of the fields described in each of the files is representable inside the corresponding model.
3. The fields can be set with standard data for the correct data type.
4. None of the optional fields are enforced to be set. (TODO)
5. All of the required fields are enforced to be set with a legal value. (TODO)
6. If set, the optional fields hold only legal values (TODO)
7. All conditional requirements and restrictions are enforced. (TODO)
8. Foreign keys and many to many sets are actually set within the database and are validated. (TODO)
Functions
---------
_get_test_data(f: function):
Decorator, wrapping instance methods to provide an extendable method to get data from the test data set based on the TestCase's requirements.
_test_constructor(f: function):
Decorator. Basic test to validate if the models are instantiable with the given set of fields.
Classes
-------
AllFieldsPresentTestCase(TestCase):
Test case making sure all files are represented with a complete set of fields. (Conditions 1+2 from the above list)
ConstructorAllFieldsTestCase(TestCase):
Test case making sure the models can be instantiated given valid values for all their fields. (3)
ConstructorRequiredFieldsOnlyTestCase(TestCase):
Test case making sure the model creation is fine with just the required fields. (4)
"""
from django.test import TestCase, TransactionTestCase
from pt_map.test_data import *
from pt_map.models import *
import unittest
from django.db import models, transaction
import random
import time
import inspect
import pt_map.models
import datetime
def _get_test_data(f):
"""Get test data for a field type."""
def wrapper(self, field, model, d, i):
f(self, field, model, d, i)
if field["name"] == "service_id":
self.add += 1
return f"{d['pk']}{self.add}"
match field["type"]:
case "fk":
if isinstance(field["references"], list):
field["references"] = random.choice(field["references"])
if field["references"].objects.all():
return field["references"].objects.all()[0]
if not field["references"] == model and field["references"]:
fk = field["references"].objects.create(**{**{f["name"]: self.get_test_data(f, field["references"], d, i) for f in self.gtfs_fields[field["references"]._meta.object_name] if not f["type"] == "mtm"}, "feed_info_id": self.feed_info})
for n, v in {f["name"]: self.get_test_data(f, model, d, i) for f in self.gtfs_fields[field["references"]._meta.object_name] if f["type"] == "mtm"}.items():
getattr(fk, n).set(v)
return fk
return None
case "unique":
return (d['pk'], "unique") #TODO
case "enum":
return random.choice(field["allowed_values"])
case "date":
return datetime.datetime.fromisoformat(d["date"])
case "pk":
return f"{d['pk']}{datetime.datetime.now().timestamp()}"
case "lat":
return d["float"] #TODO
case "lon":
return d["float"] #TODO
case "mtm":
choices = [j for j in range(len(data)-1) if not j == i]
indexes = set([])
while len(indexes) < 3:
indexes.add(random.choice(choices))
indexes = list(indexes)
mtm_test_data = [data[j] for j in indexes if field["references"]._meta.pk.name in data[j].keys() and not field["references"].objects.filter(pk=data[j]['pk'])]
qs = [field["references"].objects.create(**{**{f["name"]: self.get_test_data(f, field["references"], mtm_test_data[j], i) for f in self.gtfs_fields[field["references"]._meta.object_name] if not f["type"] == "mtm"}, "feed_info_id": self.feed_info}) for j in range(len(mtm_test_data))]
for m in qs:
for n, v in {f["name"]: self.get_test_data(f, model, d, i) for f in self.gtfs_fields[field["references"]._meta.object_name] if f["type"] == "mtm"}.items():
getattr(m, n).set(v)
return field["references"].objects.all()
case _:
return d[field["type"]]
return wrapper
def _test_constructor(index):
def decorate(f):
def wrapper(self, *args, **kwargs):
f(self, *args, **kwargs)
for name, model in [tpl for tpl in inspect.getmembers(pt_map.models, inspect.isclass) if tpl[1] not in [FeedInfo,]]:
with self.subTest(name=name):
d = data[index]
values = {**{field["name"]: self.get_test_data(field, model, d, index) for field in self.gtfs_fields[name] if not field["type"] == "mtm"}, "feed_info_id": self.feed_info}
obj = model.objects.create(**values)
for n, v in {field["name"]: self.get_test_data(field, model, d, index) for field in self.gtfs_fields[name] if field["type"] == "mtm"}.items():
getattr(obj, n).set(v)
self.assertIsNotNone(obj)
self.assertIsInstance(obj, models.Model)
return wrapper
return decorate
class AllFieldsPresentTestCase(TestCase):
"""Test for the presence of all the fields of all the models, the GTFS reference describes."""
def setUp(self):
self.model_fields = {name: [*[f.name for f in model._meta.fields], *[f.name for f in model._meta.many_to_many]] for name, model in inspect.getmembers(pt_map.models, inspect.isclass)}
self.gtfs_fields = {name: get_all_fields(name) for name,model in inspect.getmembers(pt_map.models, inspect.isclass)}
def test_all_fields_present(self):
"""Make sure the model has properties for all fields - regardless if required - provided by the GTFS standard."""
for name in self.model_fields.keys():
with self.subTest(name=name):
for f in self.gtfs_fields[name]:
with self.subTest(f=f):
self.assertIn(f["name"], self.model_fields[name])
class ConstructorAllFieldsTestCase(TransactionTestCase):
"""Test all the models can be initialized using appropriate data as specified by the GTFS."""
def setUp(self):
self.model_fields = {name: [f.name for f in model._meta.fields] for name, model in inspect.getmembers(pt_map.models, inspect.isclass) if not model == FeedInfo}
self.model_mtm = {name: [f.name for f in model._meta.many_to_many] for name, model in inspect.getmembers(pt_map.models, inspect.isclass) if not model == FeedInfo}
self.gtfs_fields = {name: get_all_fields(name) for name, model in inspect.getmembers(pt_map.models, inspect.isclass) if not model == FeedInfo}
self.feed_info = FeedInfo.objects.create(**{f["name"]: data[0][f["type"]] for f in get_all_fields("FeedInfo")})
self.add = 0
@_get_test_data
def get_test_data(self, field, model, d, i):
pass
@_test_constructor(0)
def test_constructor_all_fields_fixed(self):
"""Make sure all of the model's fields are initializable with appropriate values. Fixed test data."""
pass
@_test_constructor(random.randint(0, len(data)-1))
def test_constructor_all_fields_other(self):
"""Make sure all of the model's fields are initializable with appropriate values. A second set of test data."""
pass
class ConstructorRequiredFieldsOnlyTestCase(TransactionTestCase):
def setUp(self):
self.gtfs_fields = {name: get_required_fields(name) for name, model in inspect.getmembers(pt_map.models, inspect.isclass) if model not in [FeedInfo]}
self.feed_info = FeedInfo.objects.create(**{f["name"]: data[0][f["type"]] for f in get_required_fields("FeedInfo")})
self.add = 0
@_get_test_data
def get_test_data(self, field, model, d, i):
pass
@_test_constructor(0)
def test_constructor_required_fields_only_fixed(self):
pass
@_test_constructor(random.randint(0, len(data)-1))
def test_constructor_required_fields_only_other(self):
pass