Commit 13a823a0 authored by Lars Kruse's avatar Lars Kruse

Merge branch 'fix-calendar-with-missing-dates'

Fix a bug in the calendar export code, that breaks for events without an
end time ("until_time").
parents 461e2fa4 c5db94e9
Pipeline #1280 failed with stage
in 1 minute and 42 seconds
...@@ -13,6 +13,7 @@ stadt/settings/local.py ...@@ -13,6 +13,7 @@ stadt/settings/local.py
*.swp *.swp
*.pyc *.pyc
*.log *.log
.coverage
# debian shizzle # debian shizzle
makefilet makefilet
......
...@@ -10,30 +10,36 @@ from features.contributions import models as contributions ...@@ -10,30 +10,36 @@ from features.contributions import models as contributions
from features.memberships import test_mixins as memberships from features.memberships import test_mixins as memberships
def _get_adjusted_event_args(missing_keys=None, **kwargs):
""" assemble a dictionary of arguments for a new event
@param missing_keys: keys to be removed from the dictionary
other keyword arguments: values to be overriden in the default dictionary
"""
event_args = {'title': 'Some Event', 'text': 'Test Text', 'place': 'Test Place',
'time': '3000-01-01 00:00', 'until_time': '3000-01-01 00:00'}
event_args.update(kwargs)
for key in (missing_keys or []):
event_args.pop(key)
return event_args
class Guest(memberships.MemberMixin, core.tests.Test): class Guest(memberships.MemberMixin, core.tests.Test):
def create_event(self, **kwargs): def create_event(self, **kwargs):
self.client.force_login(self.gestalt.user) self.client.force_login(self.gestalt.user)
kwargs.update({ self.client.post(self.get_url('create-event'), _get_adjusted_event_args(**kwargs))
'title': 'Test', 'text': 'Test', 'place': 'Test', 'time': '3000-01-01 00:00',
'until_time': '3000-01-01 00:00'})
self.client.post(self.get_url('create-event'), kwargs)
self.client.logout() self.client.logout()
def get_event_url(self): def get_event_url(self, title):
return associations.Association.objects.get(content__title='Test').get_absolute_url() return associations.Association.objects.get(content__title=title).get_absolute_url()
def create_group_event(self, **kwargs): def create_group_event(self, **kwargs):
self.client.force_login(self.gestalt.user) self.client.force_login(self.gestalt.user)
kwargs.update({ self.client.post(self.get_url('create-group-event', self.group.slug),
'title': 'Group Event', 'text': 'Test', 'place': 'Test', _get_adjusted_event_args(**kwargs))
'time': '3000-01-01 00:00', 'until_time': '3000-01-01 00:00'})
self.client.post(self.get_url('create-group-event', self.group.slug), kwargs)
self.client.logout() self.client.logout()
def get_group_event_url(self):
return associations.Association.objects.get(
content__title='Group Event').get_absolute_url()
def test_guest_event_link(self): def test_guest_event_link(self):
self.assertContainsLink(self.client.get('/'), self.get_url('create-event')) self.assertContainsLink(self.client.get('/'), self.get_url('create-event'))
self.assertNotContainsLink( self.assertNotContainsLink(
...@@ -53,55 +59,47 @@ class Guest(memberships.MemberMixin, core.tests.Test): ...@@ -53,55 +59,47 @@ class Guest(memberships.MemberMixin, core.tests.Test):
url_name='create-group-event', url_args=[self.group.slug], method='post') url_name='create-group-event', url_args=[self.group.slug], method='post')
def test_guest_public_event(self): def test_guest_public_event(self):
self.create_event(public=True) self.create_event(public=True, title='A public Guest Event')
self.assertContainsLink(self.client.get('/'), self.get_event_url()) event_url = self.get_event_url('A public Guest Event')
self.assertContainsLink( self.assertContainsLink(self.client.get('/'), event_url)
self.client.get(self.get_url('events')), self.get_event_url()) self.assertContainsLink(self.client.get(self.get_url('events')), event_url)
self.assertContainsLink( self.assertContainsLink(self.client.get(self.gestalt.get_absolute_url()), event_url)
self.client.get(self.gestalt.get_absolute_url()), self.get_event_url()) self.assertOk(url=event_url)
self.assertOk(url=self.get_event_url())
def test_guest_internal_event(self): def test_guest_internal_event(self):
self.create_event(public=False) self.create_event(public=False, title='An internal Guest Event')
self.assertNotContainsLink(self.client.get('/'), self.get_event_url()) event_url = self.get_event_url('An internal Guest Event')
self.assertNotContainsLink( self.assertNotContainsLink(self.client.get('/'), event_url)
self.client.get(self.get_url('events')), self.get_event_url()) self.assertNotContainsLink(self.client.get(self.get_url('events')), event_url)
self.assertNotContainsLink( self.assertNotContainsLink(self.client.get(self.gestalt.get_absolute_url()), event_url)
self.client.get(self.gestalt.get_absolute_url()), self.get_event_url()) self.assertLogin(url=event_url)
self.assertLogin(url=self.get_event_url())
def test_guest_public_group_event(self): def test_guest_public_group_event(self):
self.create_group_event(public=True) self.create_group_event(public=True, title='A public Group Guest Event')
self.assertContainsLink(obj=self.group, link_url=self.get_group_event_url()) event_url = self.get_event_url('A public Group Guest Event')
self.assertOk(url=self.get_group_event_url()) self.assertContainsLink(obj=self.group, link_url=event_url)
self.assertLogin(url=self.get_group_event_url(), method='post') self.assertOk(url=event_url)
self.assertLogin(url=event_url, method='post')
def test_guest_internal_group_event(self): def test_guest_internal_group_event(self):
self.create_group_event(public=False) self.create_group_event(public=False, title='An internal Group Guest Event')
self.assertNotContainsLink(obj=self.group, link_url=self.get_group_event_url()) event_url = self.get_event_url('An internal Group Guest Event')
self.assertLogin(url=self.get_group_event_url()) self.assertNotContainsLink(obj=self.group, link_url=event_url)
self.assertLogin(url=self.get_group_event_url(), method='post') self.assertLogin(url=event_url)
self.assertLogin(url=event_url, method='post')
class Gestalt(memberships.AuthenticatedMemberMixin, core.tests.Test): class Gestalt(memberships.AuthenticatedMemberMixin, core.tests.Test):
def create_event(self, **kwargs): def create_event(self, **kwargs):
kwargs.update({ return self.client.post(self.get_url('create-event'), _get_adjusted_event_args(**kwargs))
'title': 'Test', 'text': 'Test', 'place': 'Test', 'time': '3000-01-01 00:00',
'until_time': '3000-01-01 00:00'})
return self.client.post(self.get_url('create-event'), kwargs)
def create_group_event(self, **kwargs): def create_group_event(self, **kwargs):
kwargs.update({ return self.client.post(self.get_url('create-group-event', self.group.slug),
'title': 'Group Event', 'text': 'Test', 'place': 'Test', _get_adjusted_event_args(**kwargs))
'time': '3000-01-01 00:00', 'until_time': '3000-01-01 00:00'})
return self.client.post(self.get_url('create-group-event', self.group.slug), kwargs)
def get_event_url(self): def get_event_url(self, title):
return associations.Association.objects.get(content__title='Test').get_absolute_url() return associations.Association.objects.get(content__title=title).get_absolute_url()
def get_group_event_url(self):
return associations.Association.objects.get(
content__title='Group Event').get_absolute_url()
def test_gestalt_event_link(self): def test_gestalt_event_link(self):
self.assertContainsLink(self.client.get('/'), self.get_url('create-event')) self.assertContainsLink(self.client.get('/'), self.get_url('create-event'))
...@@ -114,60 +112,61 @@ class Gestalt(memberships.AuthenticatedMemberMixin, core.tests.Test): ...@@ -114,60 +112,61 @@ class Gestalt(memberships.AuthenticatedMemberMixin, core.tests.Test):
def test_gestalt_create_event(self): def test_gestalt_create_event(self):
self.assertEqual(self.client.get(self.get_url('create-event')).status_code, 200) self.assertEqual(self.client.get(self.get_url('create-event')).status_code, 200)
response = self.create_event() title = 'A new Event'
self.assertRedirects(response, self.get_event_url()) response = self.create_event(title=title)
self.assertExists(associations.Association, content__title='Test') self.assertRedirects(response, self.get_event_url(title))
self.assertExists(associations.Association, content__title=title)
def test_gestalt_create_group_event(self): def test_gestalt_create_group_event(self):
title = 'A new Group Event'
self.assertEqual(self.client.get(self.get_url( self.assertEqual(self.client.get(self.get_url(
'create-group-event', self.group.slug)).status_code, 200) 'create-group-event', self.group.slug)).status_code, 200)
response = self.create_group_event() response = self.create_group_event(title=title)
self.assertRedirects(response, self.get_group_event_url()) self.assertRedirects(response, self.get_event_url(title))
self.assertExists(associations.Association, content__title='Group Event') self.assertExists(associations.Association, content__title=title)
def test_gestalt_public_event(self): def test_gestalt_public_event(self):
self.create_event(public=True) self.create_event(public=True, title='Gestalt Public Event')
self.assertContainsLink(self.client.get('/'), self.get_event_url()) event_url = self.get_event_url('Gestalt Public Event')
self.assertContainsLink( self.assertContainsLink(self.client.get('/'), event_url)
self.client.get(self.get_url('events')), self.get_event_url()) self.assertContainsLink(self.client.get(self.get_url('events')), event_url)
self.assertContainsLink( self.assertContainsLink(self.client.get(self.gestalt.get_absolute_url()), event_url)
self.client.get(self.gestalt.get_absolute_url()), self.get_event_url()) self.assertOk(url=event_url)
self.assertOk(url=self.get_event_url())
def test_gestalt_internal_event(self): def test_gestalt_internal_event(self):
self.create_event(public=False) self.create_event(public=False, title='Gestalt Internal Event')
self.assertContainsLink(self.client.get('/'), self.get_event_url()) event_url = self.get_event_url('Gestalt Internal Event')
self.assertContainsLink( self.assertContainsLink(self.client.get('/'), event_url)
self.client.get(self.get_url('events')), self.get_event_url()) self.assertContainsLink(self.client.get(self.get_url('events')), event_url)
self.assertContainsLink( self.assertContainsLink(self.client.get(self.gestalt.get_absolute_url()), event_url)
self.client.get(self.gestalt.get_absolute_url()), self.get_event_url()) self.assertOk(url=event_url)
self.assertOk(url=self.get_event_url())
def test_gestalt_public_group_event(self): def test_gestalt_public_group_event(self):
self.create_group_event(public=True) title = 'Public Group Event'
self.assertContainsLink(obj=self.group, link_url=self.get_group_event_url()) self.create_group_event(public=True, title=title)
self.assertOk(url=self.get_group_event_url()) self.assertContainsLink(obj=self.group, link_url=self.get_event_url(title))
self.assertOk(url=self.get_event_url(title))
def test_gestalt_internal_group_event(self): def test_gestalt_internal_group_event(self):
self.create_group_event(public=False) self.create_group_event(public=False, title='Gestalt Internal Group Event')
self.assertContainsLink(obj=self.group, link_url=self.get_group_event_url()) event_url = self.get_event_url('Gestalt Internal Group Event')
self.assertOk(url=self.get_group_event_url()) self.assertContainsLink(obj=self.group, link_url=event_url)
self.assertOk(url=event_url)
def test_gestalt_comment_event(self): def test_gestalt_comment_event(self):
self.create_event() self.create_event(title='Gestalt Comment Event')
self.assertRedirect( self.assertRedirect(url=self.get_event_url('Gestalt Comment Event'), method='post',
url=self.get_event_url(), method='post', data={'text': 'Comment'}) data={'text': 'Comment'})
self.assertExists(contributions.Contribution, text__text='Comment') self.assertExists(contributions.Contribution, text__text='Comment')
class TwoGestalten( class TwoGestalten(
memberships.OtherMemberMixin, memberships.AuthenticatedMemberMixin, core.tests.Test): memberships.OtherMemberMixin, memberships.AuthenticatedMemberMixin, core.tests.Test):
def create_event(self, **kwargs): def create_event(self, **kwargs):
kwargs.update({ event_args = _get_adjusted_event_args(**kwargs)
'title': 'Group Event', 'text': 'Test Text', 'place': 'Test Place', self.client.post(self.get_url('create-group-event', self.group.slug), event_args)
'time': '3000-01-01 00:00', 'until_time': '3000-01-01 00:00'}) self.association = associations.Association.objects.get(content__title=event_args['title'])
self.client.post(self.get_url('create-group-event', self.group.slug), kwargs)
self.association = associations.Association.objects.get(content__title='Group Event')
def get_content_url(self): def get_content_url(self):
return self.get_url('content', (self.association.entity.slug, self.association.slug)) return self.get_url('content', (self.association.entity.slug, self.association.slug))
...@@ -176,30 +175,66 @@ class TwoGestalten( ...@@ -176,30 +175,66 @@ class TwoGestalten(
return self.get_url('content-permalink', (self.association.pk)) return self.get_url('content-permalink', (self.association.pk))
def test_event_notified(self): def test_event_notified(self):
self.create_event() self.create_event(text='My Text', place='My Place')
self.assertNotificationsSent(2) self.assertNotificationsSent(2)
self.assertNotificationRecipient(self.gestalt) self.assertNotificationRecipient(self.gestalt)
self.assertNotificationRecipient(self.other_gestalt) self.assertNotificationRecipient(self.other_gestalt)
self.assertNotificationContains(self.get_perma_url()) self.assertNotificationContains(self.get_perma_url())
self.assertNotificationContains('Test Text') self.assertNotificationContains('My Text')
self.assertNotificationContains('Test Place') self.assertNotificationContains('My Place')
class GroupCalendarExportMember(memberships.AuthenticatedMemberMixin, class GroupCalendarExportMember(memberships.AuthenticatedMemberMixin,
features.gestalten.tests.OtherGestaltMixin, features.gestalten.tests.OtherGestaltMixin,
tests.Test): tests.Test):
def test_access_private_calendar(self): def create_group_event(self, **kwargs):
""" test the (in)accessibility of a private calendar of a group for a logged in member """ count_before = associations.Association.objects.count()
self.client.post(self.get_url('create-group-event', self.group.slug),
_get_adjusted_event_args(**kwargs))
count_after = associations.Association.objects.count()
# verify that exactly one item was added
self.assertEqual(count_before + 1, count_after, "Failed to create item: {}".format(kwargs))
def get_calendar_export_url(self, is_public):
data = self.client.get(self.get_url('group-events-export', (self.group.slug, ))) data = self.client.get(self.get_url('group-events-export', (self.group.slug, )))
private_url_regex = re.compile(r'>(?P<url>[^<]+/private.ics[^<]+)<') if is_public:
match = private_url_regex.search(data.content.decode('utf8')) url_regex = re.compile(r'>(?P<url>[^<]+/public.ics[^<]*)<')
else:
url_regex = re.compile(r'>(?P<url>[^<]+/private.ics[^<]+)<')
match = url_regex.search(data.content.decode('utf8'))
self.assertTrue(match) self.assertTrue(match)
private_url = match.groupdict()['url'] calendar_export_url = match.groupdict()['url']
self.assertTrue("token" in private_url) if is_public:
self.assertNotIn("token", calendar_export_url)
else:
self.assertIn("token", calendar_export_url)
return calendar_export_url
def test_private_public_calendar_content(self):
""" verify that private and public events are only available in their calendars """
# create a private and a public event
self.create_group_event(title='Private Event', public=False)
self.create_group_event(title='Public Event', public=True)
# verify the content of the private calendar
calendar_url = self.get_calendar_export_url(False)
data = self.client.get(calendar_url).content.decode('utf8')
self.assertIn('BEGIN:VCALENDAR', data)
self.assertIn('Private Event', data)
self.assertNotIn('Public Event', data)
# verify the content of the public calendar
calendar_url = self.get_calendar_export_url(True)
data = self.client.get(calendar_url).content.decode('utf8')
self.assertIn('BEGIN:VCALENDAR', data)
self.assertNotIn('Private Event', data)
self.assertIn('Public Event', data)
def test_access_private_calendar(self):
""" test the (in)accessibility of a private calendar of a group for a logged in member """
private_url = self.get_calendar_export_url(False)
# verify access via the private URL # verify access via the private URL
data = self.client.get(private_url) data = self.client.get(private_url)
self.assertTrue('BEGIN:VCALENDAR' in data.content.decode('utf8')) self.assertIn('BEGIN:VCALENDAR', data.content.decode('utf8'))
# verify rejected access with a wrong private URL # verify rejected access with a wrong private URL
data = self.client.get(private_url + 'foo') data = self.client.get(private_url + 'foo')
self.assertEqual(data.status_code, 401) self.assertEqual(data.status_code, 401)
...@@ -219,13 +254,21 @@ class GroupCalendarExportMember(memberships.AuthenticatedMemberMixin, ...@@ -219,13 +254,21 @@ class GroupCalendarExportMember(memberships.AuthenticatedMemberMixin,
def test_access_public_calendar(self): def test_access_public_calendar(self):
""" test the accessibility of a public calendar of a group for a logged in member """ """ test the accessibility of a public calendar of a group for a logged in member """
data = self.client.get(self.get_url('group-events-export', (self.group.slug, ))) public_url = self.get_calendar_export_url(True)
public_url_regex = re.compile(r'>(?P<url>[^<]+/public.ics[^<]*)<')
match = public_url_regex.search(data.content.decode('utf8'))
self.assertTrue(match)
public_url = match.groupdict()['url']
data = self.client.get(public_url) data = self.client.get(public_url)
self.assertTrue('BEGIN:VCALENDAR' in data.content.decode('utf8')) self.assertIn('BEGIN:VCALENDAR', data.content.decode('utf8'))
def test_unusual_event_export(self):
""" verify that incomplete and unusual events can be exported """
# "until_time" is the only optional event attribute
self.create_group_event(title='Missing End Time', missing_keys={'until_time'})
# "all_day" is not enabled by default
self.create_group_event(title='All Day Event', all_day=True)
calendar_url = self.get_calendar_export_url(False)
data = self.client.get(calendar_url).content.decode('utf8')
self.assertIn('BEGIN:VCALENDAR', data)
self.assertIn('Missing End Time', data)
self.assertIn('All Day Event', data)
class GroupCalendarExportNonMember(memberships.MemberMixin, class GroupCalendarExportNonMember(memberships.MemberMixin,
......
...@@ -138,8 +138,12 @@ class BaseCalendarFeed(ICalFeed): ...@@ -138,8 +138,12 @@ class BaseCalendarFeed(ICalFeed):
return item.content.first().time.astimezone(tz) return item.content.first().time.astimezone(tz)
def item_end_datetime(self, item): def item_end_datetime(self, item):
tz = django.utils.timezone.get_default_timezone() end_time = item.content.first().until_time
return item.content.first().until_time.astimezone(tz) if end_time is None:
return None
else:
tz = django.utils.timezone.get_default_timezone()
return end_time.astimezone(tz)
class GroupCalendarFeed(BaseCalendarFeed, features.groups.views.Mixin): class GroupCalendarFeed(BaseCalendarFeed, features.groups.views.Mixin):
......
...@@ -43,3 +43,10 @@ test_py: virtualenv_check ...@@ -43,3 +43,10 @@ test_py: virtualenv_check
.PHONY: test_js .PHONY: test_js
test_js: $(DIR_NODE) lint_js test_js: $(DIR_NODE) lint_js
$(RUN_NODE) "$(BIN_NODE_PKG)" run test $(RUN_NODE) "$(BIN_NODE_PKG)" run test
.PHONY: report-python-coverage
coverage_py: virtualenv_check
STADTGESTALTEN_PRESET=test python -m coverage run -m manage test
python -m coverage report
python -m coverage html --directory="$(DIR_BUILD)/coverage-report"
@echo "Coverage Report Location: file://$(realpath $(DIR_BUILD))/coverage-report/index.html"
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment