Source code for radioco.apps.schedules.tests.test_schedules

# Radioco - Broadcasting Radio Recording Scheduling system.
# Copyright (C) 2014  Iago Veloso Abalo
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
import datetime
from functools import partial

import mock
import pytz
import recurrence
from django.contrib.admin import AdminSite
from django.core.exceptions import ValidationError, FieldError
from django.core.urlresolvers import reverse
from django.forms import modelform_factory
from django.test import TestCase
from django.test import override_settings
from django.utils import timezone
from pytz import utc

from radioco.apps.programmes.models import Programme, Episode
from radioco.apps.radioco.test_utils import TestDataMixin
from radioco.apps.schedules.admin import CalendarAdmin
from radioco.apps.schedules.models import Calendar, CalendarManager
from radioco.apps.schedules.models import Schedule, Transmission
from radioco.apps.schedules.utils import next_dates


[docs]def mock_now(dt=pytz.utc.localize(datetime.datetime(2014, 1, 1, 13, 30, 0))): return dt
[docs]class ScheduleValidationTests(TestDataMixin, TestCase):
[docs] def test_fields(self): schedule = Schedule() with self.assertRaisesMessage( ValidationError, "{'calendar': [u'This field cannot be null.'], " "'start_dt': [u'This field cannot be null.'], " "'type': [u'This field cannot be blank.'], " "'programme': [u'This field cannot be null.']}"): schedule.clean_fields()
@override_settings(TIME_ZONE='UTC')
[docs]class ScheduleModelTests(TestDataMixin, TestCase):
[docs] def setUp(self): self.calendar = Calendar.objects.create(name='Calendar', is_active=True) self.recurrences = recurrence.Recurrence( rrules=[recurrence.Rule(recurrence.WEEKLY, until=utc.localize(datetime.datetime(2014, 1, 31)))]) programme = Programme.objects.filter(name="Classic hits").get() programme.name = "Classic hits 2" programme.slug = None programme.id = programme.pk = None programme.save() self.programme = programme self.schedule = Schedule.objects.create( programme=self.programme, type='L', recurrences=self.recurrences, start_dt=utc.localize(datetime.datetime(2014, 1, 6, 14, 0, 0)), calendar=self.calendar) self.episode = Episode.objects.create( title='Episode 1', programme=programme, summary='', season=1, number_in_season=1, ) self.programme.rearrange_episodes(pytz.utc.localize(datetime.datetime(1970, 1, 1)), Calendar.get_active()) self.episode.refresh_from_db()
[docs] def test_runtime(self): self.assertEqual(datetime.timedelta(hours=+1), self.schedule.runtime)
[docs] def test_runtime_not_set(self): schedule = Schedule(programme=Programme()) with self.assertRaises(FieldError): schedule.runtime
[docs] def test_start(self): self.assertEqual( self.schedule.start_dt, utc.localize(datetime.datetime(2014, 1, 6, 14, 0, 0)))
[docs] def test_start_lt_calendar(self): self.programme.start_date = datetime.date(2014, 1, 14) self.programme.save() self.schedule.refresh_from_db() self.assertEqual( self.schedule.effective_start_dt, utc.localize(datetime.datetime(2014, 1, 20, 14, 0, 0)))
[docs] def test_end_gt_calendar(self): self.programme.end_date = datetime.date(2014, 1, 14) self.programme.save() self.schedule.refresh_from_db() self.assertEqual( self.schedule.effective_end_dt, utc.localize(datetime.datetime(2014, 1, 13, 15, 0, 0)) # last date including runtime duration )
[docs] def test_recurrence_rules(self): self.assertListEqual( self.schedule.recurrences.rrules, self.recurrences.rrules)
[docs] def test_date_before(self): self.assertEqual( self.schedule.date_before(utc.localize(datetime.datetime(2014, 1, 14))), utc.localize(datetime.datetime(2014, 1, 13, 14, 0)))
[docs] def test_date_after(self): self.assertEqual( self.schedule.date_after(utc.localize(datetime.datetime(2014, 1, 14))), utc.localize(datetime.datetime(2014, 1, 20, 14, 0)))
[docs] def test_date_after_exclude(self): self.assertEqual( self.schedule.date_after(utc.localize(datetime.datetime(2014, 1, 6, 14, 0, 0, 1))), utc.localize(datetime.datetime(2014, 1, 13, 14, 0)))
[docs] def test_dates_between(self): self.assertItemsEqual( self.schedule.dates_between( utc.localize(datetime.datetime(2014, 1, 1)), utc.localize(datetime.datetime(2014, 1, 14))), [utc.localize(datetime.datetime(2014, 1, 6, 14, 0)), utc.localize(datetime.datetime(2014, 1, 13, 14, 0))])
[docs] def test_dates_between_later_start_by_programme(self): self.programme.start_date = datetime.date(2014, 1, 7) self.programme.save() self.schedule.refresh_from_db() self.assertItemsEqual( self.schedule.dates_between( utc.localize(datetime.datetime(2014, 1, 1)), utc.localize(datetime.datetime(2014, 1, 14))), [utc.localize(datetime.datetime(2014, 1, 13, 14, 0))])
[docs] def test_dates_between_earlier_end_by_programme(self): self.programme.end_date = datetime.date(2014, 1, 7) self.programme.save() self.schedule.refresh_from_db() self.assertItemsEqual( self.schedule.dates_between( utc.localize(datetime.datetime(2014, 1, 1)), utc.localize(datetime.datetime(2014, 1, 14))), [utc.localize(datetime.datetime(2014, 1, 6, 14, 0))])
[docs] def test_dates_between_complex_ruleset(self): programme = Programme.objects.create(name="Programme 14:00 - 15:00", current_season=1, runtime=60) schedule = Schedule.objects.create( programme=programme, calendar=self.calendar, start_dt=utc.localize(datetime.datetime(2014, 1, 2, 14, 0, 0)), recurrences=recurrence.Recurrence( rrules=[recurrence.Rule(recurrence.DAILY, interval=2)], exrules=[recurrence.Rule( recurrence.WEEKLY, byday=[recurrence.MO, recurrence.TU])])) self.assertItemsEqual( schedule.dates_between( utc.localize(datetime.datetime(2014, 1, 1)), utc.localize(datetime.datetime(2014, 1, 9))), [utc.localize(datetime.datetime(2014, 1, 2, 14, 0)), utc.localize(datetime.datetime(2014, 1, 4, 14, 0)), utc.localize(datetime.datetime(2014, 1, 8, 14, 0))])
[docs] def test_unicode(self): self.assertEqual(unicode(self.schedule), 'Monday - 14:00:00')
@mock.patch('django.utils.timezone.now', mock_now)
[docs] def test_save_rearrange_episodes(self): self.assertEqual(self.episode.issue_date, utc.localize(datetime.datetime(2014, 1, 6, 14, 0, 0))) self.episode.issue_date = None self.episode.save() self.schedule.save() self.episode.refresh_from_db() self.assertEqual(self.episode.issue_date, utc.localize(datetime.datetime(2014, 1, 6, 14, 0, 0)))
@override_settings(TIME_ZONE='UTC')
[docs]class ScheduleBetweenTests(TestDataMixin, TestCase):
[docs] def setUp(self): self.recurrences = recurrence.Recurrence(rrules=[recurrence.Rule(recurrence.DAILY)]) self.schedule = Schedule.objects.create( programme=self.programme, type='L', recurrences=self.recurrences, start_dt=utc.localize(datetime.datetime(2014, 1, 1, 23, 30, 0)), calendar=self.calendar) self.schedule_without_recurrences = Schedule.objects.create( programme=self.programme, type='L', start_dt=utc.localize(datetime.datetime(2014, 1, 2, 0, 30, 0)), calendar=self.calendar)
[docs] def test_dates_between_includes_started_episode(self): self.assertItemsEqual( self.schedule.dates_between( utc.localize(datetime.datetime(2014, 1, 2, 0, 0, 0)), utc.localize(datetime.datetime(2014, 1, 3, 23, 59, 59)) ), [ utc.localize(datetime.datetime(2014, 1, 1, 23, 30, 0)), # Not finished yet utc.localize(datetime.datetime(2014, 1, 2, 23, 30, 0)), utc.localize(datetime.datetime(2014, 1, 3, 23, 30, 0)), ] )
[docs] def test_between_includes_started_episode(self): between = Transmission.between( utc.localize(datetime.datetime(2014, 1, 2, 0, 0, 0)), utc.localize(datetime.datetime(2014, 1, 3, 23, 59, 59)) ) self.assertListEqual( map(lambda t: (t.slug, t.start), list(between)), [(u'classic-hits', utc.localize(datetime.datetime(2014, 1, 1, 23, 30, 0))), # Not finished yet (u'classic-hits', utc.localize(datetime.datetime(2014, 1, 2, 0, 30, 0))), (u'classic-hits', utc.localize(datetime.datetime(2014, 1, 2, 23, 30, 0))), (u'classic-hits', utc.localize(datetime.datetime(2014, 1, 3, 23, 30, 0)))])
[docs]class CalendarManagerTests(TestDataMixin, TestCase):
[docs] def setUp(self): self.manager = CalendarManager()
[docs] def test_current(self): self.assertIsInstance(self.manager.current(), Calendar)
[docs] def test_current_no_date(self): self.assertIsInstance(self.manager.current(), Calendar)
[docs]class CalendarValidationTests(TestCase):
[docs] def setUp(self): self.CalendarForm = modelform_factory(Calendar, fields=("name",))
[docs] def test_only_one_calendar_active(self): self.assertEquals(len(Calendar.objects.filter(is_active=True)), 1) new_active_calendar = Calendar.objects.create(name="test", is_active=True) self.assertEquals(Calendar.objects.get(is_active=True), new_active_calendar)
# XXX there is something fishy with form validation, check! # def test_name_required(self): # board = Calendar(slug="foo") # form = self.CalendarForm(instance=board) # with self.assertRaisesMessage( # ValidationError, "{'name':[u'This field cannot be blank.']}"): # form.clean() # # def test_slug_required(self): # board = Calendar(name="foo") # with self.assertRaisesMessage( # ValidationError, "{'slug':[u'This field cannot be blank.']}"): # board.full_clean() @override_settings(TIME_ZONE='UTC')
[docs]class CalendarModelTests(TestDataMixin, TestCase):
[docs] def test_str(self): self.assertEqual(str(self.calendar), "Example")
[docs]class CalendarAdminTests(TestDataMixin, TestCase):
[docs] def setUp(self): self.app_admin = CalendarAdmin(Calendar, AdminSite())
[docs] def test_clone_calendar(self): schedule_ids = [_schedule.id for _schedule in self.calendar.schedule_set.all()] num_of_schedules = len(schedule_ids) self.app_admin.clone_calendar(request=None, queryset=Calendar.objects.filter(id=self.calendar.id)) cloned_calendar = Calendar.objects.order_by('-id').first() self.calendar.refresh_from_db() self.assertNotEqual(self.calendar.id, cloned_calendar.id) self.assertEqual(num_of_schedules, self.calendar.schedule_set.count()) self.assertEqual(num_of_schedules, cloned_calendar.schedule_set.count()) self.assertFalse( any( map( lambda x: x in schedule_ids, [_schedule.id for _schedule in cloned_calendar.schedule_set.all()] ) ) ) self.assertNotEquals( frozenset([_schedule.id for _schedule in self.calendar.schedule_set.all()]), frozenset([_schedule.id for _schedule in cloned_calendar.schedule_set.all()]), )
# @mock.patch('django.utils.timezone.now', mock_now) # @mock.patch('radioco.apps.schedules.utils.rearrange_programme_episodes') # def test_delete(self, rearrange_programme_episodes): # def calls(): # for programme in Programme.objects.all(): # yield mock.call(programme, mock_now()) # # post_delete.send(Calendar, instance=self.calendar) # rearrange_programme_episodes.assert_has_calls(calls(), any_order=True) @override_settings(TIME_ZONE='UTC')
[docs]class TransmissionModelTests(TestDataMixin, TestCase):
[docs] def setUp(self): dt = utc.localize(datetime.datetime(2015, 1, 6, 14, 0, 0)) self.episode_in_transmission = Episode.objects.create_episode(date=dt, programme=self.programme) self.transmission = Transmission(self.schedule, dt, self.episode_in_transmission)
[docs] def test_name(self): self.assertEqual( self.transmission.name, self.schedule.programme.name)
[docs] def test_start(self): self.assertEqual( self.transmission.start, utc.localize(datetime.datetime(2015, 1, 6, 14, 0, 0)))
[docs] def test_end(self): self.assertEqual( self.transmission.end, utc.localize(datetime.datetime(2015, 1, 6, 15, 0, 0)))
[docs] def test_slug(self): self.assertEqual( self.transmission.slug, self.schedule.programme.slug)
[docs] def test_programme_url(self): self.assertEqual( self.transmission.programme_url, reverse('programmes:detail', args=[self.schedule.programme.slug]))
[docs] def test_episode__url(self): self.assertEqual( self.transmission.episode_url, self.episode_in_transmission.get_absolute_url())
[docs] def test_at(self): now = Transmission.at(utc.localize(datetime.datetime(2015, 1, 6, 11, 59, 59))) self.assertListEqual( map(lambda t: (t.slug, t.start), list(now)), [(u'the-best-wine', utc.localize(datetime.datetime(2015, 1, 6, 11, 0, 0)))]) now = Transmission.at(utc.localize(datetime.datetime(2015, 1, 6, 12, 0, 0))) self.assertListEqual( map(lambda t: (t.slug, t.start), list(now)), [(u'local-gossips', utc.localize(datetime.datetime(2015, 1, 6, 12, 0, 0)))]) now = Transmission.at(utc.localize(datetime.datetime(2015, 1, 6, 12, 59, 59))) self.assertListEqual( map(lambda t: (t.slug, t.start), list(now)), [(u'local-gossips', utc.localize(datetime.datetime(2015, 1, 6, 12, 0, 0)))]) now = Transmission.at(utc.localize(datetime.datetime(2015, 1, 6, 13, 0, 0))) self.assertListEqual(list(now), [])
[docs] def test_between(self): between = Transmission.between( utc.localize(datetime.datetime(2015, 1, 6, 11, 0, 0)), utc.localize(datetime.datetime(2015, 1, 6, 17, 0, 0))) self.assertListEqual( map(lambda t: (t.slug, t.start), list(between)), [(u'the-best-wine', utc.localize(datetime.datetime(2015, 1, 6, 11, 0))), (u'local-gossips', utc.localize(datetime.datetime(2015, 1, 6, 12, 0))), (u'classic-hits', utc.localize(datetime.datetime(2015, 1, 6, 14, 0)))])
[docs] def test_between_by_queryset(self): between = Transmission.between( utc.localize(datetime.datetime(2015, 1, 6, 12, 0, 0)), utc.localize(datetime.datetime(2015, 1, 6, 17, 0, 0)), schedules=Schedule.objects.filter( calendar=self.another_calendar).all()) self.assertListEqual( map(lambda t: (t.slug, t.start), list(between)), [(u'classic-hits', utc.localize(datetime.datetime(2015, 1, 6, 16, 30, 0)))])
@override_settings(TIME_ZONE='UTC')
[docs]class ScheduleUtilsTests(TestDataMixin, TestCase):
[docs] def setUp(self): self.manager = CalendarManager() programme = Programme.objects.filter(name="Classic hits").get() programme.name = "Classic hits - ScheduleUtilsTests" programme.slug = None programme.id = programme.pk = None programme.save() self.programme = programme Schedule.objects.get_or_create( programme=programme, type='L', calendar=self.calendar, recurrences=recurrence.Recurrence(rrules=[recurrence.Rule(recurrence.DAILY)]), start_dt=pytz.utc.localize(datetime.datetime(2015, 1, 1, 14, 0, 0))) for number in range(1, 11): Episode.objects.create( title='Episode %s' % number, programme=programme, summary='', season=1, number_in_season=number, ) programme.rearrange_episodes(pytz.utc.localize(datetime.datetime(1970, 1, 1)), Calendar.get_active())
[docs] def test_available_dates_after(self): Schedule.objects.create( programme=self.programme, calendar=self.calendar, type="L", start_dt=utc.localize(datetime.datetime(2015, 1, 6, 16, 0, 0)), recurrences=recurrence.Recurrence( rrules=[recurrence.Rule(recurrence.WEEKLY)])) dates = next_dates(self.calendar, self.programme, utc.localize(datetime.datetime(2015, 1, 5))) self.assertEqual(dates.next(), utc.localize(datetime.datetime(2015, 1, 5, 14, 0))) self.assertEqual(dates.next(), utc.localize(datetime.datetime(2015, 1, 6, 14, 0))) self.assertEqual(dates.next(), utc.localize(datetime.datetime(2015, 1, 6, 16, 0)))
[docs] def test_available_dates_none(self): dates = next_dates(self.calendar, Programme(), timezone.now()) with self.assertRaises(StopIteration): dates.next()
[docs] def test_rearrange_episodes(self): self.programme.rearrange_episodes(pytz.utc.localize(datetime.datetime(2015, 1, 1)), Calendar.get_active()) self.assertListEqual( map(lambda e: e.issue_date, self.programme.episode_set.all().order_by('issue_date')[:5]), [ utc.localize(datetime.datetime(2015, 1, 1, 14, 0)), utc.localize(datetime.datetime(2015, 1, 2, 14, 0)), utc.localize(datetime.datetime(2015, 1, 3, 14, 0)), utc.localize(datetime.datetime(2015, 1, 4, 14, 0)), utc.localize(datetime.datetime(2015, 1, 5, 14, 0)) ] )
@mock.patch('django.utils.timezone.now', partial(mock_now, dt=utc.localize(datetime.datetime(2015, 1, 1))))
[docs] def test_rearrange_episodes_new_schedule(self): # Next calendar shouldn't appear due to doesn't belong to the active calendar Schedule.objects.create( programme=self.programme, calendar=Calendar.objects.create(), type="L", start_dt=utc.localize(datetime.datetime(2015, 1, 3, 16, 0, 0)), recurrences=recurrence.Recurrence(rrules=[recurrence.Rule(recurrence.WEEKLY)])) Schedule.objects.create( programme=self.programme, calendar=self.calendar, type="L", start_dt=utc.localize(datetime.datetime(2015, 1, 3, 17, 0, 0)), recurrences=recurrence.Recurrence(rrules=[recurrence.Rule(recurrence.WEEKLY)])) # save should call rearrange # rearrange_programme_episodes(self.programme, pytz.utc.localize(datetime.datetime(2015, 1, 1))) self.assertListEqual( map(lambda e: e.issue_date, self.programme.episode_set.all().order_by('issue_date')[:5]), [ utc.localize(datetime.datetime(2015, 1, 1, 14, 0)), utc.localize(datetime.datetime(2015, 1, 2, 14, 0)), utc.localize(datetime.datetime(2015, 1, 3, 14, 0)), # The next schedule doesn't belong to the active_calendar # utc.localize(datetime.datetime(2015, 1, 3, 16, 0)), utc.localize(datetime.datetime(2015, 1, 3, 17, 0)), utc.localize(datetime.datetime(2015, 1, 4, 14, 0)), ] )
@mock.patch('django.utils.timezone.now', partial(mock_now, dt=utc.localize(datetime.datetime(2015, 1, 3, 16, 0, 1))))
[docs] def test_rearrange_only_non_emited_episodes(self): Schedule.objects.create( programme=self.programme, calendar=Calendar.objects.create(), type="L", start_dt=utc.localize(datetime.datetime(2015, 1, 3, 16, 0, 0)), recurrences=recurrence.Recurrence( rrules=[recurrence.Rule( recurrence.WEEKLY, until=utc.localize(datetime.datetime(2015, 1, 31, 16, 0, 0)))])) # save should call rearrange self.assertListEqual( map(lambda e: e.issue_date, self.programme.episode_set.all().order_by('issue_date')[:5]), [ utc.localize(datetime.datetime(2015, 1, 1, 14, 0)), utc.localize(datetime.datetime(2015, 1, 2, 14, 0)), utc.localize(datetime.datetime(2015, 1, 3, 14, 0)), utc.localize(datetime.datetime(2015, 1, 4, 14, 0)), utc.localize(datetime.datetime(2015, 1, 5, 14, 0)), ] )