235 lines
10 KiB
Python
235 lines
10 KiB
Python
from datetime import datetime
|
|
import json
|
|
|
|
from django.contrib.auth.models import User
|
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
from django.test import Client, TestCase
|
|
from django.urls import reverse
|
|
from django.utils import timezone
|
|
|
|
from .models import Event
|
|
|
|
|
|
class EventModelTests(TestCase):
|
|
def test_slug_is_created_on_save(self):
|
|
event = Event.objects.create(
|
|
name='City Market',
|
|
start=timezone.make_aware(datetime(2026, 4, 10, 10, 0)),
|
|
end=timezone.make_aware(datetime(2026, 4, 10, 14, 0)),
|
|
)
|
|
self.assertTrue(event.slug.startswith('city-market-2026-04-10'))
|
|
|
|
|
|
class CalendarViewTests(TestCase):
|
|
def setUp(self):
|
|
Event.objects.create(
|
|
name='Riverfront Market',
|
|
location='Riverfront Pavilion',
|
|
start=timezone.make_aware(datetime(2026, 4, 10, 10, 0)),
|
|
end=timezone.make_aware(datetime(2026, 4, 10, 16, 0)),
|
|
is_published=True,
|
|
)
|
|
|
|
def test_home_page_renders(self):
|
|
response = self.client.get(reverse('home'))
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, 'Show visitors exactly where your business will be on any day.')
|
|
|
|
def test_staff_dashboard_requires_login(self):
|
|
response = self.client.get(reverse('event_dashboard'))
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertIn(reverse('login'), response.url)
|
|
|
|
def test_staff_dashboard_for_staff_user(self):
|
|
staff = User.objects.create_user(username='staffer', password='pass12345', is_staff=True)
|
|
client = Client()
|
|
client.login(username='staffer', password='pass12345')
|
|
response = client.get(reverse('event_dashboard'))
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, 'Manage your public event calendar securely')
|
|
self.assertContains(response, 'Generate a copy-ready widget snippet')
|
|
self.assertContains(response, 'Import a previous backup')
|
|
|
|
def test_embed_page_can_hide_header(self):
|
|
response = self.client.get(reverse('calendar_embed') + '?header=0')
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertNotContains(response, 'Open full page')
|
|
|
|
|
|
class EventDashboardMutationTests(TestCase):
|
|
def setUp(self):
|
|
self.staff = User.objects.create_user(username='staffer', password='pass12345', is_staff=True)
|
|
self.event = Event.objects.create(
|
|
name='Editable Market',
|
|
location='Town Square',
|
|
start=timezone.make_aware(datetime(2026, 4, 12, 9, 0)),
|
|
end=timezone.make_aware(datetime(2026, 4, 12, 12, 0)),
|
|
is_published=True,
|
|
)
|
|
|
|
def _backup_file(self, payload):
|
|
return SimpleUploadedFile(
|
|
'events-backup.json',
|
|
json.dumps(payload).encode('utf-8'),
|
|
content_type='application/json',
|
|
)
|
|
|
|
def test_staff_can_export_event_backup(self):
|
|
self.client.login(username='staffer', password='pass12345')
|
|
response = self.client.get(reverse('event_export'))
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response['Content-Type'], 'application/json')
|
|
self.assertIn('attachment; filename="roadshow-calendar-events-', response['Content-Disposition'])
|
|
payload = json.loads(response.content)
|
|
self.assertEqual(payload['version'], 1)
|
|
self.assertEqual(payload['event_count'], len(payload['events']))
|
|
exported_event = next(item for item in payload['events'] if item['slug'] == self.event.slug)
|
|
self.assertEqual(exported_event['name'], self.event.name)
|
|
|
|
def test_staff_can_preview_event_backup_restore(self):
|
|
self.client.login(username='staffer', password='pass12345')
|
|
payload = {
|
|
'version': 1,
|
|
'project': 'Roadshow Calendar',
|
|
'exported_at': '2026-04-01T10:30:00+00:00',
|
|
'event_count': 1,
|
|
'events': [
|
|
{
|
|
'name': 'Recovered Market',
|
|
'slug': 'recovered-market-2026-04-20',
|
|
'location': 'Harbor Plaza',
|
|
'start': '2026-04-20T10:00:00+00:00',
|
|
'end': '2026-04-20T14:00:00+00:00',
|
|
'event_url': 'https://example.com/recovered',
|
|
'summary': 'Recovered from backup',
|
|
'is_published': False,
|
|
}
|
|
],
|
|
}
|
|
|
|
response = self.client.post(reverse('event_import_preview'), {'backup_file': self._backup_file(payload)})
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, 'Ready to replace the current calendar')
|
|
self.assertContains(response, 'Recovered Market')
|
|
self.assertContains(response, 'Confirm restore and replace events')
|
|
self.assertIn('event_backup_import_payload', self.client.session)
|
|
|
|
def test_staff_can_restore_event_backup(self):
|
|
self.client.login(username='staffer', password='pass12345')
|
|
payload = {
|
|
'version': 1,
|
|
'project': 'Roadshow Calendar',
|
|
'exported_at': '2026-04-01T10:30:00+00:00',
|
|
'event_count': 1,
|
|
'events': [
|
|
{
|
|
'name': 'Recovered Market',
|
|
'slug': 'recovered-market-2026-04-20',
|
|
'location': 'Harbor Plaza',
|
|
'start': '2026-04-20T10:00:00+00:00',
|
|
'end': '2026-04-20T14:00:00+00:00',
|
|
'event_url': 'https://example.com/recovered',
|
|
'summary': 'Recovered from backup',
|
|
'is_published': False,
|
|
}
|
|
],
|
|
}
|
|
|
|
preview_response = self.client.post(reverse('event_import_preview'), {'backup_file': self._backup_file(payload)})
|
|
self.assertEqual(preview_response.status_code, 200)
|
|
|
|
response = self.client.post(reverse('event_import_restore'))
|
|
self.assertRedirects(response, reverse('event_dashboard'))
|
|
self.assertEqual(Event.objects.count(), 1)
|
|
|
|
restored_event = Event.objects.get()
|
|
self.assertEqual(restored_event.name, 'Recovered Market')
|
|
self.assertEqual(restored_event.slug, 'recovered-market-2026-04-20')
|
|
self.assertEqual(restored_event.location, 'Harbor Plaza')
|
|
self.assertFalse(restored_event.is_published)
|
|
self.assertFalse(Event.objects.filter(pk=self.event.pk).exists())
|
|
self.assertNotIn('event_backup_import_payload', self.client.session)
|
|
|
|
def test_staff_can_open_edit_page(self):
|
|
self.client.login(username='staffer', password='pass12345')
|
|
response = self.client.get(reverse('event_edit', args=[self.event.slug]))
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, 'Save changes')
|
|
|
|
def test_staff_can_update_event(self):
|
|
self.client.login(username='staffer', password='pass12345')
|
|
response = self.client.post(
|
|
reverse('event_edit', args=[self.event.slug]),
|
|
{
|
|
'name': 'Updated Market',
|
|
'location': 'Town Square',
|
|
'start': '2026-04-12T09:00',
|
|
'end': '2026-04-12T13:00',
|
|
'event_url': 'https://example.com/event',
|
|
'summary': 'Updated details',
|
|
'is_published': 'on',
|
|
},
|
|
)
|
|
self.assertRedirects(response, reverse('event_dashboard_detail', args=[self.event.slug]))
|
|
self.event.refresh_from_db()
|
|
self.assertEqual(self.event.name, 'Updated Market')
|
|
self.assertEqual(self.event.summary, 'Updated details')
|
|
|
|
def test_staff_can_delete_event(self):
|
|
self.client.login(username='staffer', password='pass12345')
|
|
response = self.client.post(reverse('event_delete', args=[self.event.slug]))
|
|
self.assertRedirects(response, reverse('event_dashboard'))
|
|
self.assertFalse(Event.objects.filter(pk=self.event.pk).exists())
|
|
|
|
|
|
def test_staff_can_create_recurring_events(self):
|
|
self.client.login(username='staffer', password='pass12345')
|
|
response = self.client.post(
|
|
reverse('event_create'),
|
|
{
|
|
'name': 'Weekly Demo',
|
|
'location': 'HQ Lab',
|
|
'start': '2026-05-05T09:30',
|
|
'end': '2026-05-05T11:00',
|
|
'event_url': 'https://example.com/demo',
|
|
'summary': 'Recurring demo day',
|
|
'is_published': 'on',
|
|
'recurrence_start_date': '2026-05-01',
|
|
'recurrence_end_date': '2026-05-31',
|
|
'recurrence_weekday': '2',
|
|
},
|
|
)
|
|
self.assertRedirects(response, reverse('event_dashboard'))
|
|
|
|
recurring_events = Event.objects.filter(name='Weekly Demo').order_by('start')
|
|
self.assertEqual(recurring_events.count(), 4)
|
|
self.assertEqual(
|
|
[event.start.date().isoformat() for event in recurring_events],
|
|
['2026-05-06', '2026-05-13', '2026-05-20', '2026-05-27'],
|
|
)
|
|
self.assertEqual([event.start.hour for event in recurring_events], [9, 9, 9, 9])
|
|
self.assertEqual([event.start.minute for event in recurring_events], [30, 30, 30, 30])
|
|
self.assertTrue(all(event.is_published for event in recurring_events))
|
|
self.assertTrue(all(event.end - event.start == recurring_events[0].end - recurring_events[0].start for event in recurring_events))
|
|
|
|
def test_staff_sees_validation_error_for_invalid_recurrence_range(self):
|
|
self.client.login(username='staffer', password='pass12345')
|
|
response = self.client.post(
|
|
reverse('event_create'),
|
|
{
|
|
'name': 'Broken Weekly Demo',
|
|
'location': 'HQ Lab',
|
|
'start': '2026-05-05T09:30',
|
|
'end': '2026-05-05T11:00',
|
|
'event_url': 'https://example.com/demo',
|
|
'summary': 'Should not save',
|
|
'is_published': 'on',
|
|
'recurrence_start_date': '2026-05-20',
|
|
'recurrence_end_date': '2026-05-10',
|
|
'recurrence_weekday': '2',
|
|
},
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, 'The recurring series must end on or after the start date.')
|
|
self.assertFalse(Event.objects.filter(name='Broken Weekly Demo').exists())
|