diff --git a/src/.coveragerc b/src/.coveragerc index 0d8c77cc01..555a84e1d6 100644 --- a/src/.coveragerc +++ b/src/.coveragerc @@ -7,3 +7,4 @@ exclude_lines = pragma: no cover def __str__ if settings.DEBUG + NOQA diff --git a/src/tixl/settings.py b/src/tixl/settings.py index 81b1eb16b0..591d8922eb 100644 --- a/src/tixl/settings.py +++ b/src/tixl/settings.py @@ -44,6 +44,7 @@ INSTALLED_APPS = ( 'bootstrap3', 'debug_toolbar.apps.DebugToolbarConfig', 'djangoformsetjs', + 'tixlplugins.testdummy', 'tixlplugins.timerestriction', ) diff --git a/src/tixlbase/cache.py b/src/tixlbase/cache.py index e1f81f220a..d9f635f90c 100644 --- a/src/tixlbase/cache.py +++ b/src/tixlbase/cache.py @@ -38,6 +38,9 @@ class EventRelatedCache: key = hashlib.sha256(key.encode("UTF-8")).hexdigest() return key + def _strip_prefix(self, key): + return key.split(":", maxsplit=3)[-1] if 'event:' in key else key + def clear(self): try: prefix = self.cache.incr(self.prefixkey, 1) @@ -52,25 +55,29 @@ class EventRelatedCache: return self.cache.get(self._prefix_key(key)) def get_many(self, keys): - return self.cache.get_many([self._prefix_key(key) for key in keys]) + values = self.cache.get_many([self._prefix_key(key) for key in keys]) + newvalues = {} + for k, v in values.items(): + newvalues[self._strip_prefix(k)] = v + return newvalues def set_many(self, values, timeout=3600): newvalues = {} - for i in values.items(): - newvalues[self._prefix_key(i[0])] = i[1] - return self.cache.set_many([newvalues], timeout) + for k, v in values.items(): + newvalues[self._prefix_key(k)] = v + return self.cache.set_many(newvalues, timeout) - def delete(self, key): + def delete(self, key): # NOQA return self.cache.delete(self._prefix_key(key)) - def delete_many(self, keys): + def delete_many(self, keys): # NOQA return self.cache.delete_many([self._prefix_key(key) for key in keys]) - def incr(self, key, by=1): + def incr(self, key, by=1): # NOQA return self.cache.incr(self._prefix_key(key), by) - def decr(self, key, by=1): + def decr(self, key, by=1): # NOQA return self.cache.decr(self._prefix_key(key), by) - def close(self): + def close(self): # NOQA pass diff --git a/src/tixlbase/plugins.py b/src/tixlbase/plugins.py index 1989694e30..23a0ce33d4 100644 --- a/src/tixlbase/plugins.py +++ b/src/tixlbase/plugins.py @@ -1,6 +1,6 @@ -try: +try: # NOQA from enum import Enum -except ImportError: +except ImportError: # NOQA from flufl.enum import Enum from django.apps import apps diff --git a/src/tixlbase/tests/test_cache.py b/src/tixlbase/tests/test_cache.py new file mode 100644 index 0000000000..078ace9229 --- /dev/null +++ b/src/tixlbase/tests/test_cache.py @@ -0,0 +1,47 @@ +import random + +from django.test import TestCase +from django.core.cache import cache as django_cache +from django.utils.timezone import now + +from tixlbase.models import Event, Organizer + + +class CacheTest(TestCase): + """ + This test case tests the invalidation of the event related + cache. + """ + + def setUp(self): + o = Organizer.objects.create(name='Dummy', slug='dummy') + self.event = Event.objects.create( + organizer=o, name='Dummy', slug='dummy', + date_from=now(), + ) + self.cache = self.event.get_cache() + randint = random.random() + self.testkey = "test" + str(randint) + + def test_interference(self): + django_cache.clear() + self.cache.set(self.testkey, "foo") + self.assertIsNone(django_cache.get(self.testkey)) + self.assertIn(self.cache.get(self.testkey), (None, "foo")) + + def test_longkey(self): + self.cache.set(self.testkey * 100, "foo") + self.assertEquals(self.cache.get(self.testkey * 100), "foo") + + def test_invalidation(self): + self.cache.set(self.testkey, "foo") + self.cache.clear() + self.assertIsNone(self.cache.get(self.testkey)) + + def test_many(self): + inp = { + 'a': 'foo', + 'b': 'bar', + } + self.cache.set_many(inp) + self.assertEquals(inp, self.cache.get_many(inp.keys())) diff --git a/src/tixlbase/tests/test_models.py b/src/tixlbase/tests/test_models.py index 1217476bba..433510768c 100644 --- a/src/tixlbase/tests/test_models.py +++ b/src/tixlbase/tests/test_models.py @@ -16,21 +16,63 @@ class ItemVariationsTest(TestCase): def setUp(self): o = Organizer.objects.create(name='Dummy', slug='dummy') - e = Event.objects.create( + self.event = Event.objects.create( organizer=o, name='Dummy', slug='dummy', date_from=now(), ) - p = Property.objects.create(event=e, name='Size') + p = Property.objects.create(event=self.event, name='Size') PropertyValue.objects.create(prop=p, value='S') PropertyValue.objects.create(prop=p, value='M') PropertyValue.objects.create(prop=p, value='L') - p = Property.objects.create(event=e, name='Color') + p = Property.objects.create(event=self.event, name='Color') PropertyValue.objects.create(prop=p, value='black') PropertyValue.objects.create(prop=p, value='blue') + def test_variationdict(self): + i = Item.objects.create(event=self.event, name='Dummy') + p = Property.objects.get(event=self.event, name='Size') + i.properties.add(p) + iv = ItemVariation.objects.create(item=i) + pv = PropertyValue.objects.get(prop=p, value='S') + iv.values.add(pv) + + variations = i.get_all_variations() + + for vd in variations: + for i, v in vd.relevant_items(): + self.assertIs(type(i), int) + self.assertIs(type(v), PropertyValue) + + for v in vd.relevant_values(): + self.assertIs(type(v), PropertyValue) + + if vd[p.pk] == pv: + vd1 = vd + + vd2 = VariationDict() + vd2[p.pk] = pv + + self.assertEqual(vd2.identify(), vd1.identify()) + self.assertEqual(vd2, vd1) + + vd2[p.pk] = PropertyValue.objects.get(prop=p, value='M') + + self.assertNotEqual(vd2.identify(), vd.identify()) + self.assertNotEqual(vd2, vd1) + + vd3 = vd2.copy() + self.assertEqual(vd3, vd2) + + vd2[p.pk] = pv + self.assertNotEqual(vd3, vd2) + + vd4 = VariationDict() + vd4[4] = 'b' + vd4[2] = 'a' + self.assertEqual(vd4.ordered_values(), ['a', 'b']) + def test_get_all_variations(self): - e = Event.objects.get(name='Dummy', organizer__name='Dummy') - i = Item.objects.create(event=e, name='Dummy') + i = Item.objects.create(event=self.event, name='Dummy') # No properties available v = i.get_all_variations() @@ -38,7 +80,7 @@ class ItemVariationsTest(TestCase): self.assertEqual(v[0], {}) # One property, no variations - p = Property.objects.get(event=e, name='Size') + p = Property.objects.get(event=self.event, name='Size') i.properties.add(p) v = i.get_all_variations() self.assertIs(type(v), list) @@ -72,7 +114,7 @@ class ItemVariationsTest(TestCase): self.assertEqual(num_variations, 1) # Two properties, one variation - p2 = Property.objects.get(event=e, name='Color') + p2 = Property.objects.get(event=self.event, name='Color') i.properties.add(p2) iv.values.add(PropertyValue.objects.get(prop=p2, value='black')) v = i.get_all_variations() diff --git a/src/tixlbase/tests/test_plugins.py b/src/tixlbase/tests/test_plugins.py new file mode 100644 index 0000000000..defff24ab7 --- /dev/null +++ b/src/tixlbase/tests/test_plugins.py @@ -0,0 +1,57 @@ +from django.test import TestCase +from django.utils.timezone import now +from django.conf import settings + +from tixlbase.models import Event, Organizer +from tixlbase.plugins import get_all_plugins +from tixlbase.signals import determine_availability + + +class PluginRegistryTest(TestCase): + """ + This test case performs tests for the plugin registry. + """ + + def setUp(self): + o = Organizer.objects.create(name='Dummy', slug='dummy') + self.event = Event.objects.create( + organizer=o, name='Dummy', slug='dummy', + date_from=now(), + ) + + def test_plugin_names(self): + for mod in get_all_plugins(): + self.assertIn(mod.module, settings.INSTALLED_APPS) + + def test_metadata(self): + for mod in get_all_plugins(): + self.assertTrue(hasattr(mod, 'name')) + self.assertTrue(hasattr(mod, 'version')) + self.assertTrue(hasattr(mod, 'type')) + + +class PluginSignalTest(TestCase): + """ + This test case tests the EventPluginSignal handler + """ + + def setUp(self): + o = Organizer.objects.create(name='Dummy', slug='dummy') + self.event = Event.objects.create( + organizer=o, name='Dummy', slug='dummy', + date_from=now(), + ) + + def test_no_plugins_active(self): + self.event.plugins = '' + self.event.save() + responses = determine_availability.send(self.event) + self.assertEqual(len(responses), 0) + + def test_one_plugin_active(self): + self.event.plugins = 'tixlplugins.testdummy' + self.event.save() + payload = {'foo': 'bar'} + responses = determine_availability.send(self.event, **payload) + self.assertEqual(len(responses), 1) + self.assertIn('tixlplugins.testdummy.signals', [r[0].__module__ for r in responses]) diff --git a/src/tixlcontrol/views/event.py b/src/tixlcontrol/views/event.py index af84c9b4dc..c3617ff76d 100644 --- a/src/tixlcontrol/views/event.py +++ b/src/tixlcontrol/views/event.py @@ -75,7 +75,7 @@ class EventPlugins(EventPermissionRequiredMixin, TemplateView, SingleObjectMixin def get_context_data(self, *args, **kwargs): from tixlbase.plugins import get_all_plugins context = super().get_context_data(*args, **kwargs) - context['plugins'] = get_all_plugins() + context['plugins'] = [p for p in get_all_plugins() if not p.name.startswith('.')] context['plugins_active'] = self.object.get_plugins() return context diff --git a/src/tixlplugins/testdummy/__init__.py b/src/tixlplugins/testdummy/__init__.py new file mode 100644 index 0000000000..1d2574a3ff --- /dev/null +++ b/src/tixlplugins/testdummy/__init__.py @@ -0,0 +1,17 @@ +from django.apps import AppConfig +from tixlbase.plugins import PluginType + + +class TestDummyApp(AppConfig): + name = 'tixlplugins.testdummy' + verbose_name = '.testdummy' + + class TixlPluginMeta: + type = PluginType.RESTRICTION + name = '.testdummy' + version = '1.0.0' + + def ready(self): + from . import signals # NOQA + +default_app_config = 'tixlplugins.testdummy.TestDummyApp' diff --git a/src/tixlplugins/testdummy/models.py b/src/tixlplugins/testdummy/models.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/tixlplugins/testdummy/signals.py b/src/tixlplugins/testdummy/signals.py new file mode 100644 index 0000000000..8fbcde2a7f --- /dev/null +++ b/src/tixlplugins/testdummy/signals.py @@ -0,0 +1,9 @@ +from django.dispatch import receiver + +from tixlbase.signals import determine_availability + + +@receiver(determine_availability) +def availability_handler(sender, **kwargs): + kwargs['sender'] = sender + return kwargs diff --git a/src/tixlplugins/timerestriction/tests.py b/src/tixlplugins/timerestriction/tests.py index 50c14f6a95..98b5e90742 100644 --- a/src/tixlplugins/timerestriction/tests.py +++ b/src/tixlplugins/timerestriction/tests.py @@ -57,6 +57,33 @@ class TimeRestrictionTest(TestCase): self.assertTrue(result[0]['available']) self.assertEqual(result[0]['price'], 12) + def test_cached_result(self): + r = TimeRestriction.objects.create( + timeframe_from=now() - timedelta(days=3), + timeframe_to=now() + timedelta(days=3), + event=self.event, + price=12 + ) + r.items.add(self.item) + result = signals.availability_handler( + self.event, item=self.item, + variations=self.item.get_all_variations(), + context=None, cache=self.event.get_cache() + ) + self.assertEqual(len(result), 1) + self.assertIn('available', result[0]) + self.assertTrue(result[0]['available']) + self.assertEqual(result[0]['price'], 12) + result = signals.availability_handler( + self.event, item=self.item, + variations=self.item.get_all_variations(), + context=None, cache=self.event.get_cache() + ) + self.assertEqual(len(result), 1) + self.assertIn('available', result[0]) + self.assertTrue(result[0]['available']) + self.assertEqual(result[0]['price'], 12) + def test_simple_case_unavailable(self): r = TimeRestriction.objects.create( timeframe_from=now() - timedelta(days=5),