""" This module tests all our quiz models""" import os import shutil import tempfile from django.core.files.uploadedfile import SimpleUploadedFile from django.core.management import call_command from django.utils.translation import get_language, activate from django.utils import timezone from django.urls import reverse from django.test import TestCase, override_settings from rest_framework.test import APITestCase from django.conf import settings from .models import ( Sport, Criterion, CriterionRating, CallToMove, KnowledgeSnack, Question, ) class SportModelTest(TestCase): """Tests the sport model""" def setUp(self): self.name = "HIIT" self.url = ( "https://www.buchsys.de/fu-berlin/angebote/aktueller_zeitraum/_HIIT_" "-_High_Intensity_Interval_Training___HOME.html " ) self.test_sport = Sport( name=self.name, url=self.url, ) self.test_sport.save() def test_sport_can_be_created(self): """New sport is written to the database""" test_sport = Sport.objects.first() self.assertEqual(test_sport.name, self.name) self.assertEqual(test_sport.url, self.url) class CriterionRatingTest(TestCase): """Tests the Relation between Sport and Criterion""" def setUp(self): self.name = "HIIT" self.url = ( "https://www.buchsys.de/fu-berlin/angebote/aktueller_zeitraum/_HIIT_" "-_High_Intensity_Interval_Training___HOME.html " ) self.test_sport = Sport( name=self.name, url=self.url, ) self.test_sport.save() self.criterion = Criterion(name="Einzelsport") self.criterion.save() def test_can_rate_criterion_for_sport(self): """A rating for a specific criterion can be added to a sport""" self.test_sport.rate(self.criterion, 10) self.assertEqual(self.test_sport.criteria_ratings.first(), self.criterion) self.assertEqual(self.test_sport.get_rating(self.criterion), 10) def test_rating_can_be_changed(self): """ If a sport is rated again then the corresponding relation is changed, instead of recreated """ first_rating_object = self.test_sport.rate(self.criterion, 10) second_rating_object = self.test_sport.rate(self.criterion, 8) self.assertEqual(first_rating_object, second_rating_object) self.assertEqual(self.test_sport.get_rating(criterion=self.criterion), 8) FIXTURE_IMAGES = os.path.join(settings.BASE_DIR, "quiz", "fixtures", "images") MEDIA_ROOT = tempfile.mkdtemp( suffix="testing" ) # Create a temp directory for files created during tests @override_settings(MEDIA_ROOT=MEDIA_ROOT) class CallToMoveTest(TestCase): """Tests the Model for Call To Move""" def setUp(self): tempfile.mkdtemp(suffix="testing") # recreate tmp folder before each test self.text = "Kreise deine Arme vor der nächsten Frage 3x nach hinten" self.image_name = "test_image.png" self.image_path = os.path.join(FIXTURE_IMAGES, self.image_name) self.image = SimpleUploadedFile( name=self.image_name, content=open(self.image_path, "rb").read(), content_type="image/png", ) self.call_to_move = CallToMove(text=self.text, image=self.image) self.call_to_move.save() def tearDown(self) -> None: """Delete the temp dir after each test""" shutil.rmtree(MEDIA_ROOT, ignore_errors=True) def test_can_create_call_to_move(self): """A call to move can be correctly created""" self.assertEqual(self.call_to_move.text, self.text) self.assertEqual(self.call_to_move.image.name, self.image.name) def test_can_save_and_load_call_to_move(self): """A saved Call to Move can be loaded""" call_to_move = CallToMove.objects.first() self.assertEqual(call_to_move.text, self.text) self.assertEqual(call_to_move.image.name, self.image.name) @override_settings(MEDIA_ROOT=MEDIA_ROOT) class KnowledgeSnackTest(TestCase): """Tests the Model for Knowledge Snack""" def setUp(self): tempfile.mkdtemp(suffix="testing") # recreate tmp folder before each test self.text = ( "Dass Treppensteigen fast 5x so viele Kalorien verbrennt," "als bei der Nutzung des Aufzuges?" ) self.image_name = "test_image.png" self.image_path = os.path.join(FIXTURE_IMAGES, self.image_name) self.image = SimpleUploadedFile( name=self.image_name, content=open(self.image_path, "rb").read(), content_type="image/png", ) self.knowledge_snack = KnowledgeSnack(text=self.text, image=self.image) self.knowledge_snack.save() def tearDown(self) -> None: """Delete the temp dir after each test""" shutil.rmtree(MEDIA_ROOT, ignore_errors=True) def test_can_create_knowledge_snack(self): """A knowledge snack can be correctly created""" self.assertEqual(self.knowledge_snack.text, self.text) self.assertEqual(self.knowledge_snack.image.name, self.image.name) def test_can_save_and_load_call_to_move(self): """A saved Knowledge Snack can be loaded""" knowledge_snack = KnowledgeSnack.objects.first() self.assertEqual(knowledge_snack.text, self.text) self.assertEqual(knowledge_snack.image.name, self.image.name) class CriterionAndQuestionModelTest(TestCase): """Tests the Criterion and the Question model which have a One to One Relation""" def setUp(self): self.name = "Einzelsport" self.criterion = Criterion(name=self.name) self.criterion.save() def test_criterion_can_be_saved_and_loaded(self): """New criterion can be loaded from the db""" test_criterion = Criterion.objects.first() self.assertEqual(test_criterion.name, self.name) def test_question_can_be_added(self): """ If a question is added to a criterion, then it is accessible through the criterion. """ text = "Ich trainiere gerne mit anderen im Team" question = Question(text=text, criterion=self.criterion) question.save() self.criterion.question = question self.assertEqual(question, self.criterion.question) def test_criterion_stays_if_question_deleted(self): """If assigned question is deleted the foreign key is set None""" text = "Ich trainiere gerne mit anderen im Team" question = Question(text=text, criterion=self.criterion) question.save() question.delete() self.criterion = Criterion.objects.first() with self.assertRaises(Criterion.question.RelatedObjectDoesNotExist): self.criterion.question def test_question_can_be_saved_and_loaded(self): """New Question is saved to the db and can be loaded""" text = "Ich trainiere gerne mit anderen im Team" Question(text=text, criterion=self.criterion).save() test_question = Question.objects.first() self.assertEqual(test_question.text, text) class FixturesTest(TestCase): """ These are the tests for the fixtures in quiz/fixtures. Fixtures can be used to populate the database with test data. They can be used in automated tests, but also in development. """ fixtures = [ "sports.json", "criteria.json", "criterion_ratings.json", "questions.json", "calls_to_move.json", "knowledge_snacks.json", ] def test_sports_created_by_fixture(self): """If the sports fixture is loaded there exists a sport with the given data.""" sport = Sport.objects.get(pk=1) self.assertEqual(sport.name, "Jiu Jitsu") self.assertEqual(sport.url, "http://www.test.de") def test_criterion_created_by_fixture(self): """If the criteria fixture is loaded there exists a criterion with the given data""" criterion = Criterion.objects.get(pk=1) self.assertEqual(criterion.name, "Outdoorsport") def test_criterion_rating_created_by_fixture(self): """If the criterion_ratings fixture is loaded the given sport has a corresponding rating""" criterion = Criterion.objects.get(name="Outdoorsport") sport = Sport.objects.get(name="Jiu Jitsu") self.assertEqual(sport.get_rating(criterion), 1) def test_question_created_by_fixture(self): """If the questions fixture is loaded there exists a question with the given data""" question = Question.objects.get(pk=1) criterion = Criterion.objects.get(name="Outdoorsport") self.assertEqual(question.text, "Ich mache am liebsten draußen Sport") self.assertEqual(question.criterion, criterion) def test_call_to_move_created_by_fixture(self): """If the call to move fixture is loaded there exists a call to move with the given data""" call_to_move = CallToMove.objects.get(pk=1) self.assertEqual( call_to_move.text, "Kreise deine Arme vor der nächsten Frage 3x nach hinten" ) self.assertEqual(call_to_move.image.name, "test_image.png") def test_knowledge_snack_created_by_fixture(self): """ If the knowledge snack fixture is loaded there exists a knowledge snack with the given data """ knowledge_snack = KnowledgeSnack.objects.get(pk=1) self.assertEqual( knowledge_snack.text, "Dass Treppensteigen fast 5x so viele Kalorien verbrennt, " "als die Nutzung des Aufzuges?", ) self.assertEqual(knowledge_snack.image.name, "logo.png") class SeedingTest(TestCase): """Tests the seed_db command in quiz/management/commands""" def test_seed_with_complete_data(self): """If seed_db is called then there exists a certain number of elements per model""" # call the seed command without asking for confirmation call_command("seed_db", ["--yes", "--no-superuser"]) n_sports = 5 n_criteria = 5 self.assertEqual(Sport.objects.count(), n_sports) self.assertEqual(Criterion.objects.count(), n_criteria) self.assertEqual(CriterionRating.objects.count(), n_sports * n_criteria) self.assertEqual(Question.objects.count(), n_criteria) self.assertEqual(CallToMove.objects.count(), 3) self.assertEqual(KnowledgeSnack.objects.count(), 3) class ModeltranslationFallbackTest(TestCase): """ Tests Behaviour of Modeltranslation when no translation is given Also tests Default Language """ def setUp(self): """ Creates a Question and fills the german Translation """ self.criterion = Criterion(name="test") self.question = Question(text="", criterion=self.criterion) self.criterion.save() self.question.save() def test_default_language(self): """ Checks whether the Default Language is German """ cur_language = get_language() self.assertEqual(cur_language, "de") def test_vallback_value(self): """ Checks whether if no Translation is set, the Fallbackvalue is used """ self.assertEqual(self.question.text, ("No Translation for this Field",)) class Modeltranslation_One_Language_Test(TestCase): """ Tests Behaviour when only one language is defined """ def setUp(self): """ Creates a Question and fills the german Translation """ self.criterion = Criterion(name="test") self.question = Question(text="", criterion=self.criterion) self.question.text = "de_text" self.criterion.save() self.question.save() def test_german_translation(self): """ Check whether obj.text returns the German Translation """ self.assertEqual(self.question.text, "de_text") def test_fallback_value_translations(self): """ English Translation is not filled out, check whether german text is returned """ activate("en") self.assertNotEqual(self.question.text, self.question.text_en) self.assertEqual(self.question.text, self.question.text_de) class Modeltranslation_Two_Languages_Test(TestCase): """ Tests Behaviour when two languages are defined """ def setUp(self): """ Creates a Question and fills both the german and english Translations """ self.criterion = Criterion(name="test") self.question = Question(text="", criterion=self.criterion) activate("de") self.question.text = "de_text" activate("en") self.question.text = "en_text" activate("de") self.criterion.save() self.question.save() def test_german_translation(self): """ Tests whether German Translation is returned """ activate("de") self.assertEqual(self.question.text, self.question.text_de) self.assertNotEqual(self.question.text, self.question.text_en) def test_english_translation(self): """ Tests whether English Translation is returned """ activate("en") self.assertEqual(self.question.text, self.question.text_en) self.assertNotEqual(self.question.text, self.question.text_de) class APITest(APITestCase): """Tests the Django API""" fixtures = ["sports.json", "criteria.json", "criterion_ratings.json"] def test_get_sport_returns_correct_data(self): """Test the API endpoint /sport/{id}""" response = self.client.get(reverse("small-sport-list-detail", kwargs={"pk": 1})) sport_data = { "id": 1, "name": "Jiu Jitsu", "url": "http://www.test.de", "currently_active": True, "last_used": timezone.localdate(), "criteria": [{"id": 1, "name": "Outdoorsport", "value": 1}], } self.assertDictEqual(response.data, sport_data) def test_put_sport_makes_correct_changes(self): """ Test the API endpoint /sport/{id} for put requests """ sport_data = { "name": "Karate", "url": "http://www.test2.de", "currently_active": True, "criteria": [{"id": 1, "name": "Outdoorsport", "value": 1}], } response = self.client.put( reverse("small-sport-list-detail", kwargs={"pk": 1}), data=sport_data, format="json", ) response = self.client.get(reverse("small-sport-list-detail", kwargs={"pk": 1})) self.assertEqual(response.data["name"], sport_data["name"]) self.assertEqual(response.data["url"], sport_data["url"]) self.assertEqual(response.data["currently_active"], True) self.assertEqual(len(response.data["criteria"]), Criterion.objects.count()) self.assertDictEqual( response.data["criteria"][0], {"id": 1, "name": "Outdoorsport", "value": 1} ) def test_patch_sport_makes_correct_changes(self): """ Test the API endpoint /sport/{id} for patch requests """ sport_data = { "criteria": [{"id": 1, "value": 3}], } response = self.client.patch( reverse("small-sport-list-detail", kwargs={"pk": 1}), data=sport_data, format="json", ) response = self.client.get(reverse("small-sport-list-detail", kwargs={"pk": 1})) self.assertEqual(response.data["name"], "Jiu Jitsu") self.assertEqual(response.data["url"], "http://www.test.de") self.assertEqual(len(response.data["criteria"]), Criterion.objects.count()) self.assertDictEqual( response.data["criteria"][0], {"id": 1, "name": "Outdoorsport", "value": 3} ) def test_get_sports_returns_correct_data(self): """Test if API endpoint /sport returns correct sports list""" response = self.client.get(reverse("small-sport-list-list")) sport_data = [ { "id": 1, "is_filled": True, "name": "Jiu Jitsu", "url": "http://www.test.de", } ] self.assertListEqual(response.data["results"], sport_data) self.assertEqual(response.data["count"], 1) def test_post_sports_creates_correct_entry(self): """Test if post to /sport creates a correct object""" sport_data_new = { "name": "Karate", "url": "http://www.test2.de", } response = self.client.post( reverse("small-sport-list-list"), data=sport_data_new ) self.assertEqual(response.data["name"], sport_data_new["name"]) self.assertEqual(response.data["url"], sport_data_new["url"]) self.assertEqual(len(response.data["criteria"]), Criterion.objects.count()) self.assertEqual(Sport.objects.count(), 2) def test_delete_sports_creates_correct_entry(self): """Test if delete to /sports deletes an object""" response = self.client.delete( reverse("small-sport-list-detail", kwargs={"pk": 1}) ) self.assertEqual(Sport.objects.count(), 0) def test_get_incomplete_sport_list(self): """ Tests if get to /sport/incomplete/ returns a list of incomplete sports """ # Set Up Incomplete Sport response = self.client.patch( reverse("small-sport-list-detail", kwargs={"pk": 1}), data={"criteria": [{"id": 1, "value": -1}]}, format="json", ) response = self.client.get(reverse("incomplete")) self.assertEqual(len(response.data), 1) self.assertEqual(response.data[0]["name"], "Jiu Jitsu") def test_get_criteria(self): """ Tests if get /critera/ returns a list of all criteria """ # Set Up Values response = self.client.patch( reverse("small-sport-list-detail", kwargs={"pk": 1}), data={"criteria": [{"id": 1, "value": 7}]}, format="json", ) response = self.client.get(reverse("criteria")) self.assertEqual(len(response.data), Criterion.objects.count()) self.assertEqual(response.data[0]["number_of_sports_active"], 1) self.assertEqual(response.data[0]["sum_of_weights"], 7) def test_currently_active(self): """ Tests if PATCHing the "currently_active" value to false correctly changes the sport """ # Set Up Values response = self.client.patch( reverse("small-sport-list-detail", kwargs={"pk": 1}), data={"currently_active": False}, format="json", ) response = self.client.get(reverse("small-sport-list-detail", kwargs={"pk": 1})) self.assertEqual(response.data["currently_active"], False)