feat: Implement initial schema and add various API, service, and management command enhancements across the application.

This commit is contained in:
pacnpal
2026-01-01 15:13:01 -05:00
parent c95f99ca10
commit b243b17af7
413 changed files with 11164 additions and 17433 deletions

View File

@@ -29,40 +29,43 @@ class FilterMetadataContractTests(TestCase):
metadata = smart_park_loader.get_filter_metadata()
# Should have required top-level keys
self.assertIn('categorical', metadata)
self.assertIn('ranges', metadata)
self.assertIn('total_count', metadata)
self.assertIn("categorical", metadata)
self.assertIn("ranges", metadata)
self.assertIn("total_count", metadata)
# Categorical filters should be objects with value/label/count
categorical = metadata['categorical']
categorical = metadata["categorical"]
self.assertIsInstance(categorical, dict)
for filter_name, filter_options in categorical.items():
with self.subTest(filter_name=filter_name):
self.assertIsInstance(filter_options, list,
f"Filter '{filter_name}' should be a list")
self.assertIsInstance(filter_options, list, f"Filter '{filter_name}' should be a list")
for i, option in enumerate(filter_options):
with self.subTest(filter_name=filter_name, option_index=i):
self.assertIsInstance(option, dict,
f"Filter '{filter_name}' option {i} should be an object, not {type(option).__name__}")
self.assertIsInstance(
option,
dict,
f"Filter '{filter_name}' option {i} should be an object, not {type(option).__name__}",
)
# Check required properties
self.assertIn('value', option,
f"Filter '{filter_name}' option {i} missing 'value' property")
self.assertIn('label', option,
f"Filter '{filter_name}' option {i} missing 'label' property")
self.assertIn("value", option, f"Filter '{filter_name}' option {i} missing 'value' property")
self.assertIn("label", option, f"Filter '{filter_name}' option {i} missing 'label' property")
# Check types
self.assertIsInstance(option['value'], str,
f"Filter '{filter_name}' option {i} 'value' should be string")
self.assertIsInstance(option['label'], str,
f"Filter '{filter_name}' option {i} 'label' should be string")
self.assertIsInstance(
option["value"], str, f"Filter '{filter_name}' option {i} 'value' should be string"
)
self.assertIsInstance(
option["label"], str, f"Filter '{filter_name}' option {i} 'label' should be string"
)
# Count is optional but should be int if present
if 'count' in option and option['count'] is not None:
self.assertIsInstance(option['count'], int,
f"Filter '{filter_name}' option {i} 'count' should be int")
if "count" in option and option["count"] is not None:
self.assertIsInstance(
option["count"], int, f"Filter '{filter_name}' option {i} 'count' should be int"
)
def test_rides_filter_metadata_structure(self):
"""Test that rides filter metadata has correct structure."""
@@ -70,16 +73,16 @@ class FilterMetadataContractTests(TestCase):
metadata = loader.get_filter_metadata()
# Should have required top-level keys
self.assertIn('categorical', metadata)
self.assertIn('ranges', metadata)
self.assertIn('total_count', metadata)
self.assertIn("categorical", metadata)
self.assertIn("ranges", metadata)
self.assertIn("total_count", metadata)
# Categorical filters should be objects with value/label/count
categorical = metadata['categorical']
categorical = metadata["categorical"]
self.assertIsInstance(categorical, dict)
# Test specific categorical filters that were problematic
critical_filters = ['categories', 'statuses', 'roller_coaster_types', 'track_materials']
critical_filters = ["categories", "statuses", "roller_coaster_types", "track_materials"]
for filter_name in critical_filters:
if filter_name in categorical:
@@ -89,40 +92,42 @@ class FilterMetadataContractTests(TestCase):
for i, option in enumerate(filter_options):
with self.subTest(filter_name=filter_name, option_index=i):
self.assertIsInstance(option, dict,
f"CRITICAL: Filter '{filter_name}' option {i} is {type(option).__name__} but should be dict")
self.assertIsInstance(
option,
dict,
f"CRITICAL: Filter '{filter_name}' option {i} is {type(option).__name__} but should be dict",
)
self.assertIn('value', option)
self.assertIn('label', option)
self.assertIn('count', option)
self.assertIn("value", option)
self.assertIn("label", option)
self.assertIn("count", option)
def test_range_metadata_structure(self):
"""Test that range metadata has correct structure."""
# Test parks ranges
parks_metadata = smart_park_loader.get_filter_metadata()
ranges = parks_metadata['ranges']
ranges = parks_metadata["ranges"]
for range_name, range_data in ranges.items():
with self.subTest(range_name=range_name):
self.assertIsInstance(range_data, dict,
f"Range '{range_name}' should be an object")
self.assertIsInstance(range_data, dict, f"Range '{range_name}' should be an object")
# Check required properties
self.assertIn('min', range_data)
self.assertIn('max', range_data)
self.assertIn('step', range_data)
self.assertIn('unit', range_data)
self.assertIn("min", range_data)
self.assertIn("max", range_data)
self.assertIn("step", range_data)
self.assertIn("unit", range_data)
# Check types (min/max can be None)
if range_data['min'] is not None:
self.assertIsInstance(range_data['min'], (int, float))
if range_data['max'] is not None:
self.assertIsInstance(range_data['max'], (int, float))
if range_data["min"] is not None:
self.assertIsInstance(range_data["min"], (int, float))
if range_data["max"] is not None:
self.assertIsInstance(range_data["max"], (int, float))
self.assertIsInstance(range_data['step'], (int, float))
self.assertIsInstance(range_data["step"], (int, float))
# Unit can be None or string
if range_data['unit'] is not None:
self.assertIsInstance(range_data['unit'], str)
if range_data["unit"] is not None:
self.assertIsInstance(range_data["unit"], str)
class ContractValidationUtilityTests(TestCase):
@@ -131,38 +136,29 @@ class ContractValidationUtilityTests(TestCase):
def test_validate_filter_metadata_contract_valid(self):
"""Test validation passes for valid filter metadata."""
valid_metadata = {
'categorical': {
'statuses': [
{'value': 'OPERATING', 'label': 'Operating', 'count': 5},
{'value': 'CLOSED_TEMP', 'label': 'Temporarily Closed', 'count': 2}
"categorical": {
"statuses": [
{"value": "OPERATING", "label": "Operating", "count": 5},
{"value": "CLOSED_TEMP", "label": "Temporarily Closed", "count": 2},
]
},
'ranges': {
'rating': {
'min': 1.0,
'max': 10.0,
'step': 0.1,
'unit': 'stars'
}
},
'total_count': 100
"ranges": {"rating": {"min": 1.0, "max": 10.0, "step": 0.1, "unit": "stars"}},
"total_count": 100,
}
# Should not raise an exception
validated = validate_filter_metadata_contract(valid_metadata)
self.assertIsInstance(validated, dict)
self.assertEqual(validated['total_count'], 100)
self.assertEqual(validated["total_count"], 100)
def test_validate_filter_metadata_contract_invalid(self):
"""Test validation fails for invalid filter metadata."""
from rest_framework import serializers
invalid_metadata = {
'categorical': {
'statuses': ['OPERATING', 'CLOSED_TEMP'] # Should be objects, not strings
},
'ranges': {},
'total_count': 100
"categorical": {"statuses": ["OPERATING", "CLOSED_TEMP"]}, # Should be objects, not strings
"ranges": {},
"total_count": 100,
}
# Should raise ValidationError
@@ -171,82 +167,71 @@ class ContractValidationUtilityTests(TestCase):
def test_ensure_filter_option_format_strings(self):
"""Test converting string arrays to proper format."""
string_options = ['OPERATING', 'CLOSED_TEMP', 'UNDER_CONSTRUCTION']
string_options = ["OPERATING", "CLOSED_TEMP", "UNDER_CONSTRUCTION"]
formatted = ensure_filter_option_format(string_options)
self.assertEqual(len(formatted), 3)
for i, option in enumerate(formatted):
self.assertIsInstance(option, dict)
self.assertIn('value', option)
self.assertIn('label', option)
self.assertIn('count', option)
self.assertIn('selected', option)
self.assertIn("value", option)
self.assertIn("label", option)
self.assertIn("count", option)
self.assertIn("selected", option)
self.assertEqual(option['value'], string_options[i])
self.assertEqual(option['label'], string_options[i])
self.assertIsNone(option['count'])
self.assertFalse(option['selected'])
self.assertEqual(option["value"], string_options[i])
self.assertEqual(option["label"], string_options[i])
self.assertIsNone(option["count"])
self.assertFalse(option["selected"])
def test_ensure_filter_option_format_tuples(self):
"""Test converting tuple arrays to proper format."""
tuple_options = [
('OPERATING', 'Operating', 5),
('CLOSED_TEMP', 'Temporarily Closed', 2)
]
tuple_options = [("OPERATING", "Operating", 5), ("CLOSED_TEMP", "Temporarily Closed", 2)]
formatted = ensure_filter_option_format(tuple_options)
self.assertEqual(len(formatted), 2)
self.assertEqual(formatted[0]['value'], 'OPERATING')
self.assertEqual(formatted[0]['label'], 'Operating')
self.assertEqual(formatted[0]['count'], 5)
self.assertEqual(formatted[0]["value"], "OPERATING")
self.assertEqual(formatted[0]["label"], "Operating")
self.assertEqual(formatted[0]["count"], 5)
self.assertEqual(formatted[1]['value'], 'CLOSED_TEMP')
self.assertEqual(formatted[1]['label'], 'Temporarily Closed')
self.assertEqual(formatted[1]['count'], 2)
self.assertEqual(formatted[1]["value"], "CLOSED_TEMP")
self.assertEqual(formatted[1]["label"], "Temporarily Closed")
self.assertEqual(formatted[1]["count"], 2)
def test_ensure_filter_option_format_dicts(self):
"""Test that properly formatted dicts pass through correctly."""
dict_options = [
{'value': 'OPERATING', 'label': 'Operating', 'count': 5},
{'value': 'CLOSED_TEMP', 'label': 'Temporarily Closed', 'count': 2}
{"value": "OPERATING", "label": "Operating", "count": 5},
{"value": "CLOSED_TEMP", "label": "Temporarily Closed", "count": 2},
]
formatted = ensure_filter_option_format(dict_options)
self.assertEqual(len(formatted), 2)
self.assertEqual(formatted[0]['value'], 'OPERATING')
self.assertEqual(formatted[0]['label'], 'Operating')
self.assertEqual(formatted[0]['count'], 5)
self.assertEqual(formatted[0]["value"], "OPERATING")
self.assertEqual(formatted[0]["label"], "Operating")
self.assertEqual(formatted[0]["count"], 5)
def test_ensure_range_format(self):
"""Test range format utility."""
range_data = {
'min': 1.0,
'max': 10.0,
'step': 0.5,
'unit': 'stars'
}
range_data = {"min": 1.0, "max": 10.0, "step": 0.5, "unit": "stars"}
formatted = ensure_range_format(range_data)
self.assertEqual(formatted['min'], 1.0)
self.assertEqual(formatted['max'], 10.0)
self.assertEqual(formatted['step'], 0.5)
self.assertEqual(formatted['unit'], 'stars')
self.assertEqual(formatted["min"], 1.0)
self.assertEqual(formatted["max"], 10.0)
self.assertEqual(formatted["step"], 0.5)
self.assertEqual(formatted["unit"], "stars")
def test_ensure_range_format_missing_step(self):
"""Test range format with missing step defaults to 1.0."""
range_data = {
'min': 1,
'max': 10
}
range_data = {"min": 1, "max": 10}
formatted = ensure_range_format(range_data)
self.assertEqual(formatted['step'], 1.0)
self.assertIsNone(formatted['unit'])
self.assertEqual(formatted["step"], 1.0)
self.assertIsNone(formatted["unit"])
class APIEndpointContractTests(APITestCase):
@@ -278,26 +263,21 @@ class TypeScriptInterfaceComplianceTests(TestCase):
# selected?: boolean;
# }
option = {
'value': 'OPERATING',
'label': 'Operating',
'count': 5,
'selected': False
}
option = {"value": "OPERATING", "label": "Operating", "count": 5, "selected": False}
# All required fields present
self.assertIn('value', option)
self.assertIn('label', option)
self.assertIn("value", option)
self.assertIn("label", option)
# Correct types
self.assertIsInstance(option['value'], str)
self.assertIsInstance(option['label'], str)
self.assertIsInstance(option["value"], str)
self.assertIsInstance(option["label"], str)
# Optional fields have correct types if present
if 'count' in option and option['count'] is not None:
self.assertIsInstance(option['count'], int)
if 'selected' in option:
self.assertIsInstance(option['selected'], bool)
if "count" in option and option["count"] is not None:
self.assertIsInstance(option["count"], int)
if "selected" in option:
self.assertIsInstance(option["selected"], bool)
def test_filter_range_interface_compliance(self):
"""Test FilterRange interface compliance."""
@@ -309,29 +289,24 @@ class TypeScriptInterfaceComplianceTests(TestCase):
# unit?: string;
# }
range_data = {
'min': 1.0,
'max': 10.0,
'step': 0.1,
'unit': 'stars'
}
range_data = {"min": 1.0, "max": 10.0, "step": 0.1, "unit": "stars"}
# All required fields present
self.assertIn('min', range_data)
self.assertIn('max', range_data)
self.assertIn('step', range_data)
self.assertIn("min", range_data)
self.assertIn("max", range_data)
self.assertIn("step", range_data)
# Correct types (min/max can be null)
if range_data['min'] is not None:
self.assertIsInstance(range_data['min'], (int, float))
if range_data['max'] is not None:
self.assertIsInstance(range_data['max'], (int, float))
if range_data["min"] is not None:
self.assertIsInstance(range_data["min"], (int, float))
if range_data["max"] is not None:
self.assertIsInstance(range_data["max"], (int, float))
self.assertIsInstance(range_data['step'], (int, float))
self.assertIsInstance(range_data["step"], (int, float))
# Optional unit field
if 'unit' in range_data and range_data['unit'] is not None:
self.assertIsInstance(range_data['unit'], str)
if "unit" in range_data and range_data["unit"] is not None:
self.assertIsInstance(range_data["unit"], str)
class RegressionTests(TestCase):
@@ -345,7 +320,7 @@ class RegressionTests(TestCase):
# Test parks
parks_metadata = smart_park_loader.get_filter_metadata()
categorical = parks_metadata.get('categorical', {})
categorical = parks_metadata.get("categorical", {})
for filter_name, filter_options in categorical.items():
with self.subTest(filter_name=filter_name):
@@ -353,19 +328,25 @@ class RegressionTests(TestCase):
for i, option in enumerate(filter_options):
with self.subTest(filter_name=filter_name, option_index=i):
self.assertIsInstance(option, dict,
self.assertIsInstance(
option,
dict,
f"REGRESSION: Filter '{filter_name}' option {i} is a {type(option).__name__} "
f"but should be a dict. This causes frontend crashes!")
f"but should be a dict. This causes frontend crashes!",
)
# Must not be a string
self.assertNotIsInstance(option, str,
self.assertNotIsInstance(
option,
str,
f"CRITICAL REGRESSION: Filter '{filter_name}' option {i} is a string '{option}' "
f"but frontend expects object with value/label/count properties!")
f"but frontend expects object with value/label/count properties!",
)
# Test rides
rides_loader = SmartRideLoader()
rides_metadata = rides_loader.get_filter_metadata()
categorical = rides_metadata.get('categorical', {})
categorical = rides_metadata.get("categorical", {})
for filter_name, filter_options in categorical.items():
with self.subTest(filter_name=f"rides_{filter_name}"):
@@ -373,9 +354,12 @@ class RegressionTests(TestCase):
for i, option in enumerate(filter_options):
with self.subTest(filter_name=f"rides_{filter_name}", option_index=i):
self.assertIsInstance(option, dict,
self.assertIsInstance(
option,
dict,
f"REGRESSION: Rides filter '{filter_name}' option {i} is a {type(option).__name__} "
f"but should be a dict. This causes frontend crashes!")
f"but should be a dict. This causes frontend crashes!",
)
def test_ranges_have_step_and_unit(self):
"""Regression test: Ensure ranges have step and unit properties."""
@@ -383,18 +367,15 @@ class RegressionTests(TestCase):
# Backend was sometimes missing step and unit
parks_metadata = smart_park_loader.get_filter_metadata()
ranges = parks_metadata.get('ranges', {})
ranges = parks_metadata.get("ranges", {})
for range_name, range_data in ranges.items():
with self.subTest(range_name=range_name):
self.assertIn('step', range_data,
f"Range '{range_name}' missing 'step' property required by frontend")
self.assertIn('unit', range_data,
f"Range '{range_name}' missing 'unit' property required by frontend")
self.assertIn("step", range_data, f"Range '{range_name}' missing 'step' property required by frontend")
self.assertIn("unit", range_data, f"Range '{range_name}' missing 'unit' property required by frontend")
# Step should be a number
self.assertIsInstance(range_data['step'], (int, float),
f"Range '{range_name}' step should be a number")
self.assertIsInstance(range_data["step"], (int, float), f"Range '{range_name}' step should be a number")
def test_no_undefined_values(self):
"""Regression test: Ensure no undefined values (should be null)."""