diff --git a/.gitignore b/.gitignore index 58145a6..696a780 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ envs/local.env .envrc - +pytest_result # Created by https://www.gitignore.io/api/pycharm+all,python,django diff --git a/manuels/tests/selenium.py b/manuels/tests/selenium.py new file mode 100644 index 0000000..e77a522 --- /dev/null +++ b/manuels/tests/selenium.py @@ -0,0 +1,61 @@ +from datetime import datetime + +from django.test import LiveServerTestCase +from selenium.webdriver.firefox.options import Options +from selenium.webdriver.firefox.webdriver import WebDriver + +from manuels.models import Teacher + + +class SeleniumTestCase(LiveServerTestCase): + headless = True + + @classmethod + def setUpClass(cls): + super().setUpClass() + options = Options() + if cls.headless: + options.add_argument("-headless") + cls.selenium = WebDriver(options=options) + cls.selenium.implicitly_wait(10) + cls.addClassCleanup(cls._cleanup_selenium) + + @classmethod + def _cleanup_selenium(cls): + try: + cls.selenium.quit() + finally: + # Explicitly ignore errors, we just want to try and quit if it exists. + pass + + def tearDown(self): + for method, error in self._outcome.errors: + if error: + now = datetime.now().strftime("%Y-%m-%dT%H-%M-%S") + name = f"pytest_result/assets/screenshot-{now}-{str(self)}.png" + self.selenium.get_screenshot_as_file(name) + + def get(self, url): + self.selenium.get(self.get_full_url(url)) + + def get_full_url(self, url): + return f"{self.live_server_url}{url}" + + +class TeacherSeleniumTestCase(SeleniumTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + def setUp(self) -> None: + super().setUp() + self.teacher = Teacher.objects.create( + first_name="John", + last_name="Doe", + phone_number="0000000000", + email="teacher@example.com", + ) + + def go_to_teacher_url(self): + url = self.teacher.get_absolute_url() + self.get(url) diff --git a/manuels/tests/test_add_book.py b/manuels/tests/test_add_book.py new file mode 100644 index 0000000..e3b8842 --- /dev/null +++ b/manuels/tests/test_add_book.py @@ -0,0 +1,17 @@ +from manuels.tests.selenium import TeacherSeleniumTestCase + + +class AddBookTestCase(TeacherSeleniumTestCase): + def test_add_book_with_decitre(self): + # empty cache + # use vcrpy to reproduce decitre call on a known book + # Click on add book button + # select level + # fill discipline + # fill ISBN + # click on "fill with decitre" + # check info is filled + # check values, especially price + # finish filling form + # check book is created + assert False diff --git a/manuels/tests/test_inscription.py b/manuels/tests/test_inscription.py index 69d1efe..fdb47ec 100644 --- a/manuels/tests/test_inscription.py +++ b/manuels/tests/test_inscription.py @@ -1,6 +1,45 @@ -from django.test import TestCase +from django.core import mail +from django.urls import reverse + +from manuels.models import Teacher +from manuels.tests.selenium import SeleniumTestCase -class MyTestCase(TestCase): - def test_something(self): - self.assertEqual(True, False) +class InscriptionTestCase(SeleniumTestCase): + headless = False + + def setUp(self) -> None: + super().setUp() + url = reverse("home_page") + assert Teacher.objects.count() == 0 + self.get(url) + self.selenium.find_element_by_id("id_first_name").send_keys("John") + self.selenium.find_element_by_id("id_last_name").send_keys("Doe") + self.selenium.find_element_by_id("id_phone_number").send_keys("0123456789") + self.selenium.find_element_by_id("id_email").send_keys("john@doe.com") + self.selenium.find_element_by_css_selector(".btn[type=submit]").click() + self.teacher = Teacher.objects.first() + expected_url = self.teacher.get_absolute_url() + self.expected_url = self.get_full_url(expected_url) + + def test_teacher_is_created(self): + assert self.teacher.first_name == "John" + assert self.teacher.last_name == "Doe" + assert self.teacher.phone_number == "0123456789" + assert self.teacher.email == "john@doe.com" + + def test_user_is_redirected(self): + assert self.selenium.current_url == self.expected_url + + def test_email_is_sent(self): + assert len(mail.outbox) == 1 + email = mail.outbox[0] + assert email.subject == "Gestion des manuels scolaires" + assert self.expected_url in email.body + + def test_logged_in_user_is_redirected(self): + url = reverse("home_page") + self.get(url) + expected_url = self.teacher.get_absolute_url() + expected_url = self.get_full_url(expected_url) + assert self.selenium.current_url == expected_url diff --git a/manuels/tests/test_teacher_base_view.py b/manuels/tests/test_teacher_base_view.py new file mode 100644 index 0000000..16d74f7 --- /dev/null +++ b/manuels/tests/test_teacher_base_view.py @@ -0,0 +1,38 @@ +from django.test import TestCase + +from manuels.tests.selenium import TeacherSeleniumTestCase + + +class TeacherBaseViewTestCase(TestCase): + def test_view_is_rendered(self): + # Go to url + # check response is 200 + assert False + + def test_view_contains_books(self): + # Create books linked to teacher + # go to url + # check response is 200 + # check books are in context + assert False + + def test_view_contains_supplies(self): + # create supplies requirements linked to teacher + # go to url + # check response is 200 + # check supplies are in context + assert False + + +class TeacherBaseViewSeleniumTestCase(TeacherSeleniumTestCase): + def test_books_are_displayed(self): + # Create books linked to teacher + # go to url + # check book title is displayed + assert False + + def test_supplies_are_displayed(self): + # Create books linked to teacher + # go to url + # check supplies name is displayed + assert False diff --git a/poetry.lock b/poetry.lock index bc41926..9d3413a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -267,6 +267,17 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "model-bakery" +version = "1.3.2" +description = "Smart object creation facility for Django." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +django = ">=2.2" + [[package]] name = "nodeenv" version = "1.6.0" @@ -380,6 +391,44 @@ toml = "*" [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +[[package]] +name = "pytest-django" +version = "4.4.0" +description = "A Django plugin for pytest." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +pytest = ">=5.4.0" + +[package.extras] +docs = ["sphinx", "sphinx-rtd-theme"] +testing = ["django", "django-configurations (>=2.0)"] + +[[package]] +name = "pytest-html" +version = "3.1.1" +description = "pytest plugin for generating HTML reports" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pytest = ">=5.0,<6.0.0 || >6.0.0" +pytest-metadata = "*" + +[[package]] +name = "pytest-metadata" +version = "1.11.0" +description = "pytest plugin for test session metadata" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.dependencies] +pytest = ">=2.9.0" + [[package]] name = "pytz" version = "2021.1" @@ -414,6 +463,17 @@ urllib3 = ">=1.21.1,<1.27" security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +[[package]] +name = "selenium" +version = "3.141.0" +description = "Python bindings for Selenium" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +urllib3 = "*" + [[package]] name = "six" version = "1.16.0" @@ -538,7 +598,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "37ca88e440ff0cd8a3f4c68091868e079548d668d5aa08f0d33188a4db23d9cb" +content-hash = "337fda673a0bc3b5148651917cc641f5d6ce56b39af26d1fcd53624f02feff8c" [metadata.files] appdirs = [ @@ -645,6 +705,10 @@ iniconfig = [ markuppy = [ {file = "MarkupPy-1.14.tar.gz", hash = "sha256:1adee2c0a542af378fe84548ff6f6b0168f3cb7f426b46961038a2bcfaad0d5f"}, ] +model-bakery = [ + {file = "model_bakery-1.3.2-py2.py3-none-any.whl", hash = "sha256:bebde0d4f895f55a8cf26706275a238fecdf0fddd227214675b7351598c99ce5"}, + {file = "model_bakery-1.3.2.tar.gz", hash = "sha256:7e06071c7ba5c58f9c1d7245b4b80cc5240806f30c4561f5c04a652d67626725"}, +] nodeenv = [ {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, @@ -712,6 +776,18 @@ pytest = [ {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, ] +pytest-django = [ + {file = "pytest-django-4.4.0.tar.gz", hash = "sha256:b5171e3798bf7e3fc5ea7072fe87324db67a4dd9f1192b037fed4cc3c1b7f455"}, + {file = "pytest_django-4.4.0-py3-none-any.whl", hash = "sha256:65783e78382456528bd9d79a35843adde9e6a47347b20464eb2c885cb0f1f606"}, +] +pytest-html = [ + {file = "pytest-html-3.1.1.tar.gz", hash = "sha256:3ee1cf319c913d19fe53aeb0bc400e7b0bc2dbeb477553733db1dad12eb75ee3"}, + {file = "pytest_html-3.1.1-py3-none-any.whl", hash = "sha256:b7f82f123936a3f4d2950bc993c2c1ca09ce262c9ae12f9ac763a2401380b455"}, +] +pytest-metadata = [ + {file = "pytest-metadata-1.11.0.tar.gz", hash = "sha256:71b506d49d34e539cc3cfdb7ce2c5f072bea5c953320002c95968e0238f8ecf1"}, + {file = "pytest_metadata-1.11.0-py2.py3-none-any.whl", hash = "sha256:576055b8336dd4a9006dd2a47615f76f2f8c30ab12b1b1c039d99e834583523f"}, +] pytz = [ {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, @@ -751,6 +827,10 @@ requests = [ {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, ] +selenium = [ + {file = "selenium-3.141.0-py2.py3-none-any.whl", hash = "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c"}, + {file = "selenium-3.141.0.tar.gz", hash = "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"}, +] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, diff --git a/pyproject.toml b/pyproject.toml index 7d54401..96297e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,10 @@ django-debug-toolbar = "^3.2.1" django-dotenv = "^1.4.2" pre-commit = "^2.13.0" pytest = "^6.2.4" +pytest-django = "^4.4.0" +pytest-html = "^3.1.1" +model-bakery = "^1.3.2" +selenium = "^3.141.0" [build-system] requires = ["poetry-core>=1.0.0"] @@ -33,3 +37,16 @@ target-version = ['py38'] [tool.isort] profile = "black" + +[tool.pytest.ini_options] +addopts = "--html=pytest_result/pytest.html --color=yes" +minversion = "6.0" +DJANGO_SETTINGS_MODULE = "manuels_collection.settings" +junit_family = "xunit1" +norecursedirs = [ + ".git", +] +python_files = [ + "test_*.py", + "tests.py", +]