diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/videodinges/__init__.py b/tests/videodinges/__init__.py new file mode 100644 index 0000000..8a0591d --- /dev/null +++ b/tests/videodinges/__init__.py @@ -0,0 +1 @@ +from .base import UploadMixin diff --git a/tests/videodinges/base.py b/tests/videodinges/base.py new file mode 100644 index 0000000..31c9d44 --- /dev/null +++ b/tests/videodinges/base.py @@ -0,0 +1,32 @@ +import tempfile +from unittest import TestCase + +from django.test import override_settings + + +class UploadMixin(TestCase): + clean_uploads_after_run = True + base_upload_dir: tempfile.TemporaryDirectory + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_upload_dir = tempfile.TemporaryDirectory(suffix='-videodinges-tests') + + def setUp(self) -> None: + super().setUp() + self.media_root = tempfile.TemporaryDirectory(suffix='-' + self.__class__.__name__, dir=self.base_upload_dir.name) + self.media_root_override_settings = override_settings(MEDIA_ROOT=self.media_root.name) + self.media_root_override_settings.enable() + + def tearDown(self) -> None: + self.media_root_override_settings.disable() + if self.clean_uploads_after_run: + self.media_root.cleanup() + super().tearDown() + + @classmethod + def tearDownClass(cls) -> None: + if cls.clean_uploads_after_run: + cls.base_upload_dir.cleanup() + super().tearDownClass() diff --git a/tests/videodinges/factories.py b/tests/videodinges/factories.py new file mode 100644 index 0000000..0867ab2 --- /dev/null +++ b/tests/videodinges/factories.py @@ -0,0 +1,77 @@ +""" Module generating useful models in 1 place """ +from inspect import signature +from typing import Type, TypeVar + +from django.core.files.uploadedfile import SimpleUploadedFile +import django.db.models + +from videodinges import models + +T = TypeVar('T', bound=django.db.models.Model) + + +def create(model: Type[T], **kwargs) -> T: + if model is models.Video: + return _create_with_defaults(models.Video, kwargs, + title=lambda x: 'Title {}'.format(x), + slug=lambda x: 'slug-{}'.format(x), + description=lambda x: 'Description {}'.format(x), + ) + + if model is models.Transcoding: + def url(): + # only URL if no upload for they are mutually exclusive + if 'upload' not in kwargs: + return 'https://some_url' + + return _create_with_defaults(models.Transcoding, kwargs, + video=lambda: create(models.Video), + quality=models.qualities[0].name, + type=str(models.transcoding_types[0]), + url=url, + ) + + if model is models.Upload: + return _create_with_defaults(models.Upload, kwargs, file=SimpleUploadedFile('some_file.txt', b'some contents')) + + raise NotImplementedError('Factory for %s not implemented' % model) + + +def _create_with_defaults(model: Type[T], kwargs: dict, **defaults) -> T: + """ + Return created django model instance. + When providing lambda as default item, the result of the lambda will be taken. + The lambda will ONLY be executed when not provided in kwargs. + + When a lambda requires an argument, the primary key of the to be created object + will be provided to that argument. This is useful for generating unique fields. + + :param model: django model to create + :param kwargs: keyword arguments to fill the model + :param defaults: default keyword arguments to use when not mentioned in kwargs + """ + + _next_pk = 0 + def next_pk(): + # Queries next pk only one time during creation + nonlocal _next_pk + if _next_pk == 0: + _next_pk = _query_next_pk(model) + return _next_pk + + for k, v in defaults.items(): + if callable(v) and not k in kwargs: + if len(signature(v).parameters) == 1: + result = v(next_pk()) + else: + result = v() + defaults[k] = result + + return model.objects.create(**{**defaults, **kwargs}) + + +def _query_next_pk(model: Type[T]) -> int: + try: + return model.objects.order_by('-pk').first().pk + 1 + except AttributeError: + return 1 diff --git a/tests/videodinges/models/__init__.py b/tests/videodinges/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/videodinges/models/test_transcoding.py b/tests/videodinges/models/test_transcoding.py new file mode 100644 index 0000000..9e0fcbc --- /dev/null +++ b/tests/videodinges/models/test_transcoding.py @@ -0,0 +1,61 @@ +from django.db.utils import IntegrityError + +from django.test import TestCase + +from tests.videodinges import factories, UploadMixin +from videodinges.models import Transcoding, Video, qualities, transcoding_types, Upload + + +class TranscodingTestCase(TestCase): + def setUp(self): + video = Video.objects.create(title='Title', slug='slug', description='Description') + Transcoding.objects.create(video=video, quality=qualities[0].name, type=str(transcoding_types[0]), url='https://some_url') + + def test_model_is_created(self): + transcoding = Transcoding.objects.all()[0] + self.assertEqual(transcoding.video.slug, 'slug') + self.assertEqual(transcoding.quality, '360p') + self.assertEqual(transcoding.type, 'video/webm') + self.assertEqual(transcoding.url, 'https://some_url') + + +class CreateTranscodingTestCase(UploadMixin, TestCase): + + def test_upload_and_url_cannot_both_be_filled(self): + video = factories.create(Video) + with self.assertRaisesMessage(IntegrityError, 'CHECK constraint failed: upload_and_url_cannot_both_be_filled'): + Transcoding.objects.create( + video=video, + quality=qualities[0].name, + type=str(transcoding_types[0]), + url='https://some_url', + upload=factories.create(Upload) + ) + + def test_either_upload_or_url_must_be_filled(self): + video = factories.create(Video) + + with self.assertRaisesMessage(IntegrityError, 'CHECK constraint failed: upload_or_url_is_filled'): + Transcoding.objects.create( + video=video, + quality=qualities[0].name, + type=str(transcoding_types[0]), + ) + + def test_no_duplicate_qualities_for_same_video_and_type_can_be_created(self): + video = factories.create(Video) + + Transcoding.objects.create( + video=video, + quality=qualities[0].name, + type=str(transcoding_types[0]), + url='https://some_url', + ) + + with self.assertRaisesMessage(IntegrityError, 'UNIQUE constraint failed: transcodings.video_id, transcodings.quality, transcodings.type'): + Transcoding.objects.create( + video=video, + quality=qualities[0].name, + type=str(transcoding_types[0]), + url='https://some_url', + ) diff --git a/tests/videodinges/models/test_upload.py b/tests/videodinges/models/test_upload.py new file mode 100644 index 0000000..400a384 --- /dev/null +++ b/tests/videodinges/models/test_upload.py @@ -0,0 +1,15 @@ +from django.test import TestCase + +from tests.videodinges import UploadMixin +from videodinges.models import Upload +from django.core.files.uploadedfile import SimpleUploadedFile + + +class UploadTestCase(UploadMixin, TestCase): + def setUp(self): + super().setUp() + Upload.objects.create(file=SimpleUploadedFile('some_file.txt', b'some contents')) + + def test_model_is_created(self): + upload = Upload.objects.all()[0] + self.assertEqual(upload.file.name, 'some_file.txt') diff --git a/tests/videodinges/models/test_video.py b/tests/videodinges/models/test_video.py new file mode 100644 index 0000000..95b464d --- /dev/null +++ b/tests/videodinges/models/test_video.py @@ -0,0 +1,15 @@ +from django.test import TestCase +from videodinges.models import Video +from datetime import datetime + +class VideoTestCase(TestCase): + def setUp(self): + Video.objects.create(title='Title', slug='slug', description='Description') + + def test_model_is_created(self): + video = Video.objects.get(slug='slug') + self.assertEqual(video.slug, 'slug') + self.assertEqual(video.title, 'Title') + self.assertEqual(video.description, 'Description') + self.assertIsInstance(video.created_at, datetime) + self.assertIsInstance(video.updated_at, datetime) diff --git a/tests/videodinges/test_factories/__init__.py b/tests/videodinges/test_factories/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/videodinges/test_factories/test_create.py b/tests/videodinges/test_factories/test_create.py new file mode 100644 index 0000000..53b9ace --- /dev/null +++ b/tests/videodinges/test_factories/test_create.py @@ -0,0 +1,16 @@ +from django.db import models + +from tests.videodinges import factories +from django.test import TestCase + +class CreateTestCase(TestCase): + + def test_factory_returns_model(self): + + class NotImplementedModel(models.Model): + class Meta: + app_label = 'some_test_label' + + with self.assertRaises(NotImplementedError): + factories.create(NotImplementedModel) + diff --git a/tests/videodinges/test_factories/test_transcoding.py b/tests/videodinges/test_factories/test_transcoding.py new file mode 100644 index 0000000..0f86df4 --- /dev/null +++ b/tests/videodinges/test_factories/test_transcoding.py @@ -0,0 +1,55 @@ +import os + +from django.core.files.uploadedfile import SimpleUploadedFile +from django.test import TestCase +from videodinges.models import Transcoding, Video, Upload +from tests.videodinges import factories, UploadMixin + +class TranscodingTestCase(TestCase): + def test_factory_returns_model(self): + transcoding = factories.create(Transcoding) + self.assertEqual(transcoding.video.slug, 'slug-1') + self.assertEqual(transcoding.quality, '360p') + self.assertEqual(transcoding.type, 'video/webm') + self.assertEqual(transcoding.url, 'https://some_url') + + def test_can_overwrite_kwargs(self): + transcoding = factories.create( + Transcoding, + quality='720p', + type='video/mp4', + url='http://another_url', + video=factories.create(Video, slug='yet-another-video-slug') + ) + + self.assertEqual(transcoding.video.slug, 'yet-another-video-slug') + self.assertEqual(transcoding.quality, '720p') + self.assertEqual(transcoding.type, 'video/mp4') + self.assertEqual(transcoding.url, 'http://another_url') + + def test_does_not_create_video_when_providing_one(self): + transcoding = factories.create( + Transcoding, + quality='720p', + type='video/mp4', + url='http://another_url', + video=factories.create(Video, slug='yet-another-video-slug') + ) + + self.assertEquals(Video.objects.all().count(), 1) + + +class TranscodingWithUploadTestCase(UploadMixin, TestCase): + def test_can_assign_upload(self): + transcoding = factories.create( + Transcoding, + quality='720p', + type='video/mp4', + video=factories.create(Video, slug='yet-another-video-slug'), + upload=factories.create(Upload, file=SimpleUploadedFile('my_upload.txt', b'some_contents')) + ) + + self.assertTrue(os.path.exists(os.path.join(self.media_root.name, 'my_upload.txt'))) + with open(os.path.join(self.media_root.name, 'my_upload.txt'), 'rb') as f: + self.assertEquals(f.read(), b'some_contents') + diff --git a/tests/videodinges/test_factories/test_upload.py b/tests/videodinges/test_factories/test_upload.py new file mode 100644 index 0000000..be67a83 --- /dev/null +++ b/tests/videodinges/test_factories/test_upload.py @@ -0,0 +1,19 @@ +import os + +from django.core.files.uploadedfile import SimpleUploadedFile +from django.test import TestCase + +from tests.videodinges import factories, UploadMixin +from videodinges.models import Upload + +class UploadTestCase(UploadMixin, TestCase): + def test_model_is_created(self): + upload = factories.create(Upload) + self.assertEqual(upload.file.name, 'some_file.txt') + self.assertTrue(os.path.exists(os.path.join(self.media_root.name, 'some_file.txt'))) + + def test_upload_does_not_create_file_when_providing_upload(self): + upload = factories.create(Upload, file=SimpleUploadedFile('my_file.txt', b'some contents')) + self.assertEqual(upload.file.name, 'my_file.txt') + self.assertFalse(os.path.exists(os.path.join(self.media_root.name, 'some_file.txt'))) + self.assertTrue(os.path.exists(os.path.join(self.media_root.name, 'my_file.txt'))) diff --git a/tests/videodinges/test_factories/test_video.py b/tests/videodinges/test_factories/test_video.py new file mode 100644 index 0000000..bc0c19d --- /dev/null +++ b/tests/videodinges/test_factories/test_video.py @@ -0,0 +1,41 @@ +from django.test import TestCase +from videodinges.models import Video +from tests.videodinges import factories +from datetime import datetime + +class VideoTestCase(TestCase): + def test_factory_returns_model(self): + video = factories.create(Video) + self.assertEqual(video.slug, 'slug-1') + self.assertEqual(video.title, 'Title 1') + self.assertEqual(video.description, 'Description 1') + self.assertIsInstance(video.created_at, datetime) + self.assertIsInstance(video.updated_at, datetime) + + def test_factory_can_create_multiple_models(self): + video1 = factories.create(Video) + video2 = factories.create(Video) + video3 = factories.create(Video) + + self.assertEqual(video1.slug, 'slug-1') + self.assertEqual(video1.title, 'Title 1') + self.assertEqual(video1.description, 'Description 1') + self.assertIsInstance(video1.created_at, datetime) + self.assertIsInstance(video1.updated_at, datetime) + + self.assertEqual(video2.slug, 'slug-2') + self.assertEqual(video2.title, 'Title 2') + self.assertEqual(video2.description, 'Description 2') + self.assertIsInstance(video2.created_at, datetime) + self.assertIsInstance(video2.updated_at, datetime) + + self.assertEqual(video3.slug, 'slug-3') + self.assertEqual(video3.title, 'Title 3') + self.assertEqual(video3.description, 'Description 3') + self.assertIsInstance(video3.created_at, datetime) + self.assertIsInstance(video3.updated_at, datetime) + + def test_factory_runs_only_2_queries(self): + """ Factory should only use 2 queries: one for selecting primary key, and one for inserting record """ + with self.assertNumQueries(2): + video = factories.create(Video) diff --git a/tests/videodinges/unit/__init__.py b/tests/videodinges/unit/__init__.py new file mode 100644 index 0000000..63d748f --- /dev/null +++ b/tests/videodinges/unit/__init__.py @@ -0,0 +1,6 @@ +""" + Module for storing 'real' _unit_ tests. + Meaning, they should test single small units e.g. methods or functions in an isolated way. + + Try to use django.test.SimpleTestCase to prevent unnecessary database setup and improve speed. +""" diff --git a/tests/videodinges/unit/models/__init__.py b/tests/videodinges/unit/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/videodinges/unit/models/test_get_quality_by_name.py b/tests/videodinges/unit/models/test_get_quality_by_name.py new file mode 100644 index 0000000..02061fc --- /dev/null +++ b/tests/videodinges/unit/models/test_get_quality_by_name.py @@ -0,0 +1,15 @@ +from django.test import SimpleTestCase +from videodinges.models import get_quality_by_name + +class GetQualityByNameTestCase(SimpleTestCase): + + def test_returns_quality_if_listed(self): + result = get_quality_by_name('480p') + self.assertEqual(result.name, '480p') + self.assertEqual(result.width, 853) + self.assertEqual(result.height, 480) + self.assertEqual(result.priority, 2) + + def test_returns_none_if_not_listed(self): + result = get_quality_by_name('non-existend') + self.assertIsNone(result) diff --git a/tests/videodinges/unit/models/test_get_short_name_of_transcoding_type.py b/tests/videodinges/unit/models/test_get_short_name_of_transcoding_type.py new file mode 100644 index 0000000..fea697d --- /dev/null +++ b/tests/videodinges/unit/models/test_get_short_name_of_transcoding_type.py @@ -0,0 +1,12 @@ +from django.test import SimpleTestCase +from videodinges.models import TranscodingType, get_short_name_of_transcoding_type + +class GetShortNameOfTranscodingTypeTestCase(SimpleTestCase): + + def test_gets_transcoding_by_name(self): + result = get_short_name_of_transcoding_type('video/webm; codecs="vp8, vorbis"') + self.assertEqual(result, 'vp8') + + def test_gets_transcoding_by_transcoding_object(self): + result = get_short_name_of_transcoding_type(TranscodingType(name='Looooong naaaaame', short_name='shrt nm')) + self.assertEqual(result, 'shrt nm') \ No newline at end of file diff --git a/tests/videodinges/views/__init__.py b/tests/videodinges/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/videodinges/views/test_index.py b/tests/videodinges/views/test_index.py new file mode 100644 index 0000000..bf7f6cb --- /dev/null +++ b/tests/videodinges/views/test_index.py @@ -0,0 +1,28 @@ +from unittest.mock import patch, Mock + +from django.http import HttpResponse +from django.test import TestCase, Client +from django.urls import reverse + +from tests.videodinges import factories +from videodinges import models + + +class IndexTestCase(TestCase): + def setUp(self): + self.client = Client() + + #@patch('videodinges.views.render') + def test_index(self): + + #render.return_value = HttpResponse(b'data', status=200) + + video1 = factories.create(models.Video, title='Vid 1', slug='vid-1') + video2 = factories.create(models.Video, title='Vid 2', slug='vid-2') + resp = self.client.get(reverse('index')) + self.assertEqual(resp.status_code, 200) + self.assertContains(resp, 'Vid 1') + self.assertContains(resp, 'vid-1.html') + + self.assertContains(resp, 'Vid 2') + self.assertContains(resp, 'vid-2.html') diff --git a/tests/videodinges/views/test_video.py b/tests/videodinges/views/test_video.py new file mode 100644 index 0000000..aef91b8 --- /dev/null +++ b/tests/videodinges/views/test_video.py @@ -0,0 +1,248 @@ +""" Test video page """ +from django.http import HttpResponse +from django.test import TestCase, Client +from django.urls import reverse + +from tests.videodinges import factories, UploadMixin +from videodinges import models + + +class VideoTestCase(UploadMixin, TestCase): + def setUp(self): + super().setUp() + self.client = Client() + + def test_video_view_renders_properly(self): + + video = factories.create( + models.Video, + title='Vid 1', + slug='vid-1', + default_quality='480p', + ) + transcoding1 = factories.create( + models.Transcoding, + video=video, + quality='480p', + type='video/webm', + url='http://480p.webm', + ) + transcoding2 = factories.create( + models.Transcoding, + video=video, + quality='480p', + type='video/mp4', + url='http://480p.mp4', + ) + transcoding3 = factories.create( + models.Transcoding, + video=video, + quality='720p', + type='video/webm', + url='http://720p.webm', + ) + transcoding4 = factories.create( + models.Transcoding, + video=video, + quality='720p', + type='video/mp4', + url='http://720p.mp4', + ) + + resp:HttpResponse = self.client.get(reverse('video', args=['vid-1'])) + + self.assertEqual(resp.status_code, 200) + + content:str = resp.content.decode(resp.charset) + + srctag = '' + + self.assertInHTML( + """""", + content, + ) + + self.assertInHTML('Vid 1', content) + + self.assertInHTML('

Vid 1

', content) + + self.assertInHTML('

Description 1

', content) + + self.assertInHTML('480p versie', content) + + self.assertInHTML( + '720p versie', + content + ) + + self.assertInHTML( + '', + content + ) + + def test_video_show_correct_default_quality(self): + + video = factories.create( + models.Video, + title='Vid 1', + slug='vid-1', + default_quality='720p', + ) + transcoding1 = factories.create( + models.Transcoding, + video=video, + quality='480p', + type='video/webm', + url='http://480p.webm', + ) + transcoding2 = factories.create( + models.Transcoding, + video=video, + quality='480p', + type='video/mp4', + url='http://480p.mp4', + ) + transcoding3 = factories.create( + models.Transcoding, + video=video, + quality='720p', + type='video/webm', + url='http://720p.webm', + ) + transcoding4 = factories.create( + models.Transcoding, + video=video, + quality='720p', + type='video/mp4', + url='http://720p.mp4', + ) + + resp:HttpResponse = self.client.get(reverse('video', args=['vid-1'])) + + self.assertEqual(resp.status_code, 200) + + content:str = resp.content.decode(resp.charset) + + self.assertInHTML( + """""", + content, + ) + + + self.assertInHTML( + '480p versie', + content + ) + + self.assertInHTML('720p versie', content) + + + def test_video_shows_correct_quality_for_parameter(self): + + video = factories.create( + models.Video, + title='Vid 1', + slug='vid-1', + ) + transcoding1 = factories.create( + models.Transcoding, + video=video, + quality='480p', + type='video/webm', + url='http://480p.webm', + ) + transcoding2 = factories.create( + models.Transcoding, + video=video, + quality='480p', + type='video/mp4', + url='http://480p.mp4', + ) + transcoding3 = factories.create( + models.Transcoding, + video=video, + quality='720p', + type='video/webm', + url='http://720p.webm', + ) + transcoding4 = factories.create( + models.Transcoding, + video=video, + quality='720p', + type='video/mp4', + url='http://720p.mp4', + ) + + resp:HttpResponse = self.client.get( + reverse('video', args=['vid-1']) + '?quality=720p') + + self.assertEqual(resp.status_code, 200) + + content:str = resp.content.decode(resp.charset) + + self.assertInHTML( + """""", + content, + ) + + + self.assertInHTML( + '480p versie', + content + ) + + self.assertInHTML('720p versie', content) + + def test_video_uploads_shows_correctly(self): + + image = factories.create(models.Upload) + movie = factories.create(models.Upload) + + video = factories.create( + models.Video, + title='Vid 1', + slug='vid-1', + poster=image, + og_image=image + ) + transcoding = factories.create( + models.Transcoding, + video=video, + quality='480p', + type='video/webm', + upload=movie, + ) + + resp:HttpResponse = self.client.get( + reverse('video', args=['vid-1']) + '?quality=720p') + + self.assertEqual(resp.status_code, 200) + + content:str = resp.content.decode(resp.charset) + + self.assertInHTML( + """""".format(url=movie.file.url, image=image.file.url), + content, + ) + + self.assertInHTML( + ''.format(image=image.file.url), + content, + ) + + diff --git a/videodinges/urls.py b/videodinges/urls.py index 7f43c21..f93a469 100644 --- a/videodinges/urls.py +++ b/videodinges/urls.py @@ -23,8 +23,8 @@ from . import views _urlpatterns = [ url(r'^admin/', admin.site.urls), - url(r'^$', views.index), - url(r'^(?P[\w-]+).html', views.video) + url(r'^$', views.index, name='index'), + url(r'^(?P[\w-]+).html', views.video, name='video') ] _urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)