Allow creators to delete events and restrict private invitations to friends
This commit is contained in:
@@ -163,6 +163,8 @@ class NotificationDatabaseTests(unittest.TestCase):
|
||||
self.assertEqual(result.status_code, 303)
|
||||
|
||||
def invitation(self):
|
||||
with main.get_db_connection() as db:
|
||||
db.execute("INSERT INTO friendships(requester_id,addressee_id,status) VALUES (1,2,'accepted') ON CONFLICT DO NOTHING")
|
||||
result = self.clients[0].post('/concerts', data={'artist':'Private test event', 'start_datetime':'2027-04-01T20:00',
|
||||
'event_type':'other','visibility':'private','invited_user_ids':'2'}, follow_redirects=False)
|
||||
self.assertEqual(result.status_code, 303)
|
||||
@@ -232,6 +234,110 @@ class NotificationDatabaseTests(unittest.TestCase):
|
||||
self.clients[0].post(f'/concerts/{concert}/edit', data=form, follow_redirects=False)
|
||||
self.assertEqual(self.scalar("SELECT count(*) FROM push_notifications WHERE state='pending'"), 1)
|
||||
|
||||
def event(self, owner=1, past=False, kind='other', visibility='public'):
|
||||
start = datetime.now() + timedelta(days=-10 if past else 10)
|
||||
with main.get_db_connection() as db:
|
||||
return db.execute("""INSERT INTO concerts
|
||||
(artist,start_datetime,end_datetime,event_type,visibility,created_by)
|
||||
VALUES (%s,%s,%s,%s,%s,%s) RETURNING id""",
|
||||
('Event access test', start, start + timedelta(days=1) if kind == 'festival' else None,
|
||||
kind, visibility, owner)).fetchone()[0]
|
||||
|
||||
def test_creator_can_delete_own_events_including_past_in_both_languages(self):
|
||||
for language in ('de', 'en'):
|
||||
self.clients[0].get('/language/' + language, follow_redirects=False)
|
||||
for past in (False, True):
|
||||
for kind in ('concert', 'festival', 'other'):
|
||||
with self.subTest(language=language, past=past, kind=kind):
|
||||
event = self.event(past=past, kind=kind)
|
||||
page = self.clients[0].get(f'/concerts/{event}')
|
||||
self.assertEqual(page.status_code, 200)
|
||||
self.assertIn(f'action="/concerts/{event}/delete"', page.text)
|
||||
self.assertIn('Delete event' if language == 'en' else 'Veranstaltung löschen', page.text)
|
||||
if past:
|
||||
self.assertNotIn(f'href="/concerts/{event}/edit"', page.text)
|
||||
result = self.clients[0].post(f'/concerts/{event}/delete', follow_redirects=False)
|
||||
self.assertEqual(result.status_code, 303)
|
||||
self.assertEqual(self.scalar('SELECT count(*) FROM concerts WHERE id=%s', (event,)), 0)
|
||||
|
||||
def test_delete_rejects_foreign_and_anonymous_but_preserves_admin_access(self):
|
||||
for past in (False, True):
|
||||
event = self.event(owner=2, past=past)
|
||||
page = self.clients[0].get(f'/concerts/{event}')
|
||||
self.assertNotIn(f'action="/concerts/{event}/delete"', page.text)
|
||||
self.assertEqual(self.clients[0].post(f'/concerts/{event}/delete', follow_redirects=False).status_code, 403)
|
||||
self.assertEqual(self.scalar('SELECT count(*) FROM concerts WHERE id=%s', (event,)), 1)
|
||||
with TestClient(main.app) as anonymous:
|
||||
self.assertEqual(anonymous.post(f'/concerts/{event}/delete', follow_redirects=False).status_code, 303)
|
||||
self.assertEqual(self.clients[1].post(f'/concerts/{event}/delete',
|
||||
headers={'Origin':'https://evil.invalid'}, follow_redirects=False).status_code, 403)
|
||||
with main.get_db_connection() as db:
|
||||
db.execute('UPDATE users SET is_admin=TRUE WHERE id=3')
|
||||
self.assertIn(f'action="/concerts/{event}/delete"', self.clients[2].get(f'/concerts/{event}').text)
|
||||
self.assertEqual(self.clients[2].post(f'/concerts/{event}/delete', follow_redirects=False).status_code, 303)
|
||||
# Deleting a parent does not delete another user's linked event.
|
||||
parent = self.event(kind='concert')
|
||||
child = self.event(owner=2)
|
||||
with main.get_db_connection() as db:
|
||||
db.execute('UPDATE concerts SET parent_event_id=%s WHERE id=%s', (parent, child))
|
||||
self.assertEqual(self.clients[0].post(f'/concerts/{parent}/delete', follow_redirects=False).status_code, 303)
|
||||
self.assertIsNone(self.scalar('SELECT parent_event_id FROM concerts WHERE id=%s', (child,)))
|
||||
|
||||
def test_private_picker_shows_only_confirmed_unblocked_owner_friends(self):
|
||||
event = self.event(visibility='private')
|
||||
with main.get_db_connection() as db:
|
||||
db.execute("INSERT INTO friendships(requester_id,addressee_id,status) VALUES (2,1,'accepted'),(1,3,'pending')")
|
||||
for language, heading in (('de','Freunde einladen'), ('en','Invite friends')):
|
||||
self.clients[0].get('/language/' + language, follow_redirects=False)
|
||||
for route in ('/concerts/new', f'/concerts/{event}/edit'):
|
||||
page = self.clients[0].get(route)
|
||||
self.assertEqual(page.status_code, 200)
|
||||
field = re.search(r'<fieldset id="event-invitations-field".*?</fieldset>', page.text, re.S).group()
|
||||
self.assertIn(heading, field)
|
||||
self.assertEqual(re.findall(r'name="invited_user_ids" value="(\d+)"', field), ['2'])
|
||||
with main.get_db_connection() as db:
|
||||
db.execute('UPDATE users SET is_admin=TRUE WHERE id=3')
|
||||
admin_page = self.clients[2].get(f'/concerts/{event}/edit')
|
||||
self.assertEqual(re.findall(r'name="invited_user_ids" value="(\d+)"', admin_page.text), ['2'])
|
||||
# Verify the other direction of the friendship and a block overriding it.
|
||||
self.assertEqual([u['id'] for u in main.get_invitable_users(2)], [1])
|
||||
with main.get_db_connection() as db:
|
||||
db.execute('INSERT INTO user_blocks(blocker_id,blocked_id) VALUES (2,1)')
|
||||
self.assertEqual(main.get_invitable_users(1), [])
|
||||
for language, empty in (('de','Noch keine bestätigten Freunde vorhanden.'), ('en','No confirmed friends yet.')):
|
||||
self.clients[0].get('/language/' + language, follow_redirects=False)
|
||||
self.assertIn(empty, self.clients[0].get('/concerts/new').text)
|
||||
|
||||
def test_private_invites_reject_forged_ids_and_preserve_existing_hidden_invites(self):
|
||||
form = {'artist':'Invitation access test', 'start_datetime':'2027-04-01T20:00',
|
||||
'event_type':'other', 'visibility':'private'}
|
||||
for language, error in (('de','Es können nur bestätigte Freunde eingeladen werden.'),
|
||||
('en','Only confirmed friends can be invited.')):
|
||||
self.clients[0].get('/language/' + language, follow_redirects=False)
|
||||
for selected in ('1','2','99999'):
|
||||
result = self.clients[0].post('/concerts', data=dict(form, invited_user_ids=selected), follow_redirects=False)
|
||||
self.assertEqual(result.status_code, 400)
|
||||
self.assertIn(error, result.text)
|
||||
self.assertEqual(self.scalar('SELECT count(*) FROM concerts'), 0)
|
||||
event = self.invitation()
|
||||
result = self.clients[0].post(f'/concerts/{event}/edit',
|
||||
data=dict(form, invited_user_ids=['2','3']), follow_redirects=False)
|
||||
self.assertEqual(result.status_code, 400)
|
||||
self.assertEqual(main.get_event_invitee_ids(event), {2})
|
||||
self.assertEqual(self.scalar('SELECT artist FROM concerts WHERE id=%s', (event,)), 'Private test event')
|
||||
with main.get_db_connection() as db:
|
||||
db.execute('DELETE FROM friendships')
|
||||
# The now hidden legacy invitation survives an unrelated edit, without a new push.
|
||||
result = self.clients[0].post(f'/concerts/{event}/edit', data=form, follow_redirects=False)
|
||||
self.assertEqual(result.status_code, 303)
|
||||
self.assertEqual(main.get_event_invitee_ids(event), {2})
|
||||
self.assertEqual(self.scalar("SELECT count(*) FROM push_notifications WHERE kind='event_invitation'"), 1)
|
||||
# An invited non-owner cannot use the edit route to grant access to someone else.
|
||||
result = self.clients[1].post(f'/concerts/{event}/edit',
|
||||
data=dict(form, invited_user_ids='3'), follow_redirects=False)
|
||||
self.assertEqual(result.status_code, 303)
|
||||
self.assertEqual(main.get_event_invitee_ids(event), {2})
|
||||
|
||||
def test_linkable_events_are_chronological_in_create_and_edit_forms(self):
|
||||
with main.get_db_connection() as db:
|
||||
rows = db.execute("""
|
||||
|
||||
Reference in New Issue
Block a user