166 lines
8.3 KiB
Python
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
|
|
|