from django.test import TestCase from django.db import IntegrityError, transaction from core.models import ( AppUser, Library, LibraryMember, Genre, ContentRating, MediaSource, Series, MediaItem, Channel, ScheduleTemplate, ScheduleBlock, Airing ) from datetime import time from django.utils import timezone from uuid import uuid4 class PytvCoreTests(TestCase): def setUp(self): self.user1 = AppUser.objects.create_user(username="testuser1", email="test1@example.com", password="password123") self.user2 = AppUser.objects.create_user(username="testuser2", email="test2@example.com", password="password123") def test_user_and_library_creation(self): # Create library lib = Library.objects.create(owner_user=self.user1, name="My Movies") self.assertEqual(lib.visibility, Library.Visibility.PRIVATE) # Test unique constraint: Cannot have two libraries with the same name for the same user with transaction.atomic(): with self.assertRaises(IntegrityError): Library.objects.create(owner_user=self.user1, name="My Movies") # Different user can have the same library name lib2 = Library.objects.create(owner_user=self.user2, name="My Movies") self.assertEqual(lib2.name, "My Movies") def test_media_source_and_item(self): lib = Library.objects.create(owner_user=self.user1, name="My Movies") source = MediaSource.objects.create( library=lib, name="Local NAS", source_type=MediaSource.SourceType.LOCAL_DIRECTORY, uri="/mnt/nas/movies" ) item = MediaItem.objects.create( media_source=source, title="Cool Movie", item_kind=MediaItem.ItemKind.MOVIE, runtime_seconds=7200, file_path="/mnt/nas/movies/cool_movie.mp4" ) self.assertEqual(item.runtime_seconds, 7200) # Test unique constraint (same media source, same path) with transaction.atomic(): with self.assertRaises(IntegrityError): MediaItem.objects.create( media_source=source, title="Another Movie", item_kind=MediaItem.ItemKind.MOVIE, runtime_seconds=5000, file_path="/mnt/nas/movies/cool_movie.mp4" ) # Test M2M Genre works beautifully genre1 = Genre.objects.create(name="Action") genre2 = Genre.objects.create(name="Sci-Fi") item.genres.add(genre1, genre2) self.assertEqual(item.genres.count(), 2) def test_channel_and_scheduling(self): lib = Library.objects.create(owner_user=self.user1, name="TV Network") channel = Channel.objects.create( owner_user=self.user1, library=lib, name="Channel 1", slug="ch-1", channel_number=1 ) template = ScheduleTemplate.objects.create( channel=channel, name="Weekday Prime Time", timezone_name="America/New_York" ) block = ScheduleBlock.objects.create( schedule_template=template, name="8PM Movie", block_type=ScheduleBlock.BlockType.PROGRAMMING, start_local_time=time(20, 0), end_local_time=time(22, 0), day_of_week_mask=62 # Mon-Fri bitmask ) self.assertEqual(block.pad_strategy, ScheduleBlock.PadStrategy.FILL_WITH_INTERSTITIALS) def test_airing_constraints(self): lib = Library.objects.create(owner_user=self.user1, name="TV Network") source = MediaSource.objects.create( library=lib, name="Local NAS", source_type=MediaSource.SourceType.LOCAL_DIRECTORY, uri="/mnt/nas/tv" ) item = MediaItem.objects.create( media_source=source, title="Promo", item_kind=MediaItem.ItemKind.BUMPER, runtime_seconds=15, file_path="/mnt/nas/tv/promo.mp4" ) channel = Channel.objects.create( owner_user=self.user1, library=lib, name="Promo Channel", slug="promo-ch", channel_number=99 ) now = timezone.now() airing = Airing.objects.create( channel=channel, media_item=item, starts_at=now, ends_at=now + timezone.timedelta(seconds=15), slot_kind=Airing.SlotKind.BUMPER, source_reason=Airing.SourceReason.MANUAL, generation_batch_uuid=uuid4() ) self.assertEqual(airing.status, Airing.Status.SCHEDULED)