From c2fe2d0f938fcd908eaaa932c073d4ae6e52b766 Mon Sep 17 00:00:00 2001 From: Eric Herrera Date: Sat, 6 Jun 2020 17:55:14 -0500 Subject: [PATCH] Initial changes to support tests. Fix tests clearing cache and updating some methods. Use pytest to run the tests. Bump version --- .travis.yml | 13 ++++ MANIFEST.in | 8 +++ Makefile | 7 ++ requirements/base.txt | 2 +- requirements/django.txt | 1 + requirements/test.in | 8 +++ requirements/test.txt | 31 +++++++++ requirements/tox.in | 4 ++ requirements/tox.txt | 20 ++++++ requirements/travis.in | 4 ++ requirements/travis.txt | 20 ++++++ setup.py | 6 +- testproject/pytest.ini | 3 + tox.ini | 10 +++ wiki/tests.py | 140 ++++++++++++++++++++++++++++++++++++---- 15 files changed, 260 insertions(+), 17 deletions(-) create mode 100644 .travis.yml create mode 100644 MANIFEST.in create mode 100644 requirements/django.txt create mode 100644 requirements/test.in create mode 100644 requirements/test.txt create mode 100644 requirements/tox.in create mode 100644 requirements/tox.txt create mode 100644 requirements/travis.in create mode 100644 requirements/travis.txt create mode 100644 testproject/pytest.ini create mode 100644 tox.ini diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..b8baa020e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: python +python: + - 3.5 + - 3.8 +install: + - pip install -r requirements/travis.txt +matrix: + - python: 3.5 + env: TOXENV=py35-django22 + - python: 3.8 + env: TOXENV=py38-django22 +script: + - tox diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..79de37839 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,8 @@ +include CHANGELOG.rst +include CONTRIBUTING.rst +include LICENSE.txt +include README.rst +recursive-include wiki *.html *.png *.gif *js *.css *jpg *jpeg *svg *py *.txt *.json +recursive-include django_notify *.html *.png *.gif *js *.css *jpg *jpeg *svg *py *.txt *.json +include requirements/base.in + diff --git a/Makefile b/Makefile index 791490d3a..73686e09c 100644 --- a/Makefile +++ b/Makefile @@ -4,3 +4,10 @@ upgrade: pip-compile --upgrade -o requirements/pip_tools.txt requirements/pip_tools.in pip-compile --upgrade -o requirements/base.txt requirements/base.in pip-compile --upgrade -o requirements/docs.txt requirements/docs.in + pip-compile --upgrade -o requirements/test.txt requirements/test.in + pip-compile --upgrade -o requirements/tox.txt requirements/tox.in + pip-compile --upgrade -o requirements/travis.txt requirements/travis.in + # Let tox control the Django version for tests + grep -e "^django==" requirements/base.txt > requirements/django.txt + sed '/^[dD]jango==/d' requirements/test.txt > requirements/test.tmp + mv requirements/test.tmp requirements/test.txt diff --git a/requirements/base.txt b/requirements/base.txt index 638118252..1380df0b9 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -9,7 +9,7 @@ django-classy-tags==1.0.0 # via django-sekizai django-js-asset==1.2.2 # via django-mptt django-mptt==0.11.0 # via -r requirements/base.in django-sekizai==1.1.0 # via -r requirements/base.in -django==2.2.13 # via -c requirements/constraints.txt, -r requirements/base.in, django-classy-tags, django-mptt, django-sekizai +django==2.2.14 # via -c requirements/constraints.txt, -r requirements/base.in, django-classy-tags, django-mptt, django-sekizai markdown==2.6.11 # via -c requirements/constraints.txt, -r requirements/base.in packaging==20.4 # via bleach pyparsing==2.4.7 # via packaging diff --git a/requirements/django.txt b/requirements/django.txt new file mode 100644 index 000000000..3cdbc8d88 --- /dev/null +++ b/requirements/django.txt @@ -0,0 +1 @@ +django==2.2.14 # via -c requirements/constraints.txt, -r requirements/base.in, django-classy-tags, django-mptt, django-sekizai diff --git a/requirements/test.in b/requirements/test.in new file mode 100644 index 000000000..8154ccb2c --- /dev/null +++ b/requirements/test.in @@ -0,0 +1,8 @@ +# Packages required for testing +-c constraints.txt + +-r base.txt + +pytest +pytest-cov +pytest-django diff --git a/requirements/test.txt b/requirements/test.txt new file mode 100644 index 000000000..eb6cdf418 --- /dev/null +++ b/requirements/test.txt @@ -0,0 +1,31 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# make upgrade +# +attrs==19.3.0 # via pytest +bleach==3.1.5 # via -r requirements/base.txt +coverage==5.1 # via pytest-cov +django-classy-tags==1.0.0 # via -r requirements/base.txt, django-sekizai +django-js-asset==1.2.2 # via -r requirements/base.txt, django-mptt +django-mptt==0.11.0 # via -r requirements/base.txt +django-sekizai==1.1.0 # via -r requirements/base.txt +importlib-metadata==1.7.0 # via pluggy, pytest +markdown==2.6.11 # via -c requirements/constraints.txt, -r requirements/base.txt +more-itertools==8.4.0 # via pytest +packaging==20.4 # via -r requirements/base.txt, bleach, pytest +pathlib2==2.3.5 # via pytest +pluggy==0.13.1 # via pytest +py==1.9.0 # via pytest +pyparsing==2.4.7 # via -r requirements/base.txt, packaging +pytest-cov==2.10.0 # via -r requirements/test.in +pytest-django==3.9.0 # via -r requirements/test.in +pytest==5.4.3 # via -r requirements/test.in, pytest-cov, pytest-django +pytz==2020.1 # via -r requirements/base.txt, django +six==1.15.0 # via -r requirements/base.txt, bleach, django-classy-tags, django-sekizai, packaging, pathlib2 +sorl-thumbnail==12.6.3 # via -r requirements/base.txt +sqlparse==0.3.1 # via -r requirements/base.txt, django +wcwidth==0.2.5 # via pytest +webencodings==0.5.1 # via -r requirements/base.txt, bleach +zipp==1.2.0 # via importlib-metadata diff --git a/requirements/tox.in b/requirements/tox.in new file mode 100644 index 000000000..9a2869477 --- /dev/null +++ b/requirements/tox.in @@ -0,0 +1,4 @@ +# Used for tests +-c constraints.txt + +tox diff --git a/requirements/tox.txt b/requirements/tox.txt new file mode 100644 index 000000000..53bf2cd40 --- /dev/null +++ b/requirements/tox.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# make upgrade +# +appdirs==1.4.4 # via virtualenv +distlib==0.3.1 # via virtualenv +filelock==3.0.12 # via tox, virtualenv +importlib-metadata==1.7.0 # via pluggy, tox, virtualenv +importlib-resources==3.0.0 # via virtualenv +packaging==20.4 # via tox +pluggy==0.13.1 # via tox +py==1.9.0 # via tox +pyparsing==2.4.7 # via packaging +six==1.15.0 # via packaging, tox, virtualenv +toml==0.10.1 # via tox +tox==3.16.1 # via -r requirements/tox.in +virtualenv==20.0.25 # via tox +zipp==1.2.0 # via importlib-metadata, importlib-resources diff --git a/requirements/travis.in b/requirements/travis.in new file mode 100644 index 000000000..3dbd7b0c3 --- /dev/null +++ b/requirements/travis.in @@ -0,0 +1,4 @@ +# Requirements for running tests in Travis +-c constraints.txt + +-r tox.txt diff --git a/requirements/travis.txt b/requirements/travis.txt new file mode 100644 index 000000000..498f5d84d --- /dev/null +++ b/requirements/travis.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# make upgrade +# +appdirs==1.4.4 # via -r requirements/tox.txt, virtualenv +distlib==0.3.1 # via -r requirements/tox.txt, virtualenv +filelock==3.0.12 # via -r requirements/tox.txt, tox, virtualenv +importlib-metadata==1.7.0 # via -r requirements/tox.txt, pluggy, tox, virtualenv +importlib-resources==3.0.0 # via -r requirements/tox.txt, virtualenv +packaging==20.4 # via -r requirements/tox.txt, tox +pluggy==0.13.1 # via -r requirements/tox.txt, tox +py==1.9.0 # via -r requirements/tox.txt, tox +pyparsing==2.4.7 # via -r requirements/tox.txt, packaging +six==1.15.0 # via -r requirements/tox.txt, packaging, tox, virtualenv +toml==0.10.1 # via -r requirements/tox.txt, tox +tox==3.16.1 # via -r requirements/tox.txt +virtualenv==20.0.25 # via -r requirements/tox.txt, tox +zipp==1.2.0 # via -r requirements/tox.txt, importlib-metadata, importlib-resources diff --git a/setup.py b/setup.py index 2db1eeec5..4072b9409 100644 --- a/setup.py +++ b/setup.py @@ -62,7 +62,7 @@ def is_requirement(line): setup( name="django-wiki", - version="0.0.27", + version="0.1.0", author="Benjamin Bach", author_email="benjamin@overtag.dk", description=("A wiki system written for the Django framework."), @@ -79,10 +79,8 @@ def is_requirement(line): 'Framework :: Django', 'Intended Audience :: Developers', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.8', 'Framework :: Django', 'Framework :: Django :: 2.2', diff --git a/testproject/pytest.ini b/testproject/pytest.ini new file mode 100644 index 000000000..810e29de6 --- /dev/null +++ b/testproject/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +DJANGO_SETTINGS_MODULE = testproject.settings +python_files = tests.py test_*.py *_tests.py diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..8533b7248 --- /dev/null +++ b/tox.ini @@ -0,0 +1,10 @@ +[tox] +envlist = {py35,py38}-django{22} + +[testenv] +deps = + django22: -r requirements/django.txt + -r{toxinidir}/requirements/test.txt +changedir={toxinidir}/testproject/ +commands = + pytest --cov wiki --cov django_notify diff --git a/wiki/tests.py b/wiki/tests.py index 1ddecbc38..e8574f90d 100644 --- a/wiki/tests.py +++ b/wiki/tests.py @@ -1,18 +1,134 @@ -""" -This file demonstrates writing tests using the unittest module. These will pass -when you run "manage.py test". +from django.contrib.auth.models import User +from django.core.cache import cache +from django.urls import reverse +from django.test import TestCase +from django.test.client import Client +from wiki.models import Article, ArticleRevision, URLPath +import pprint +import re -Replace this with more appropriate tests for your application. -""" -from __future__ import absolute_import +class InitialWebClientTest(TestCase): + """Tests by the dummy web client, with manual creating the root article.""" -from django.test import TestCase + def setUp(self): + User.objects.create_superuser('admin', 'nobody@example.com', 'secret') + self.c = c = Client() + c.login(username='admin', password='secret') + def test_root_article(self): + """Test redirecting to /create-root/, creating the root article and a simple markup.""" + c = self.c + response = c.get(reverse('wiki:root')) # url '/' + self.assertRedirects(response, reverse('wiki:root_create')) # url '/create-root/' + response = c.post(reverse('wiki:root_create'), + {'content': 'test heading h1\n====\n', 'title': 'Wiki Test'}) + self.assertRedirects(response, reverse('wiki:root')) + response = c.get(reverse('wiki:root')) + self.assertContains(response, 'test heading h1') -class SimpleTest(TestCase): - def test_basic_addition(self): - """ - Tests that 1 + 1 always equals 2. + +class WebClientTest(TestCase): + """Tests by the dummy web client.""" + def setUp(self): + User.objects.create_superuser('admin', 'nobody@example.com', 'secret') + self.c = c = Client() + c.login(username='admin', password='secret') + response = self.c.post(reverse('wiki:root_create'), {'content': 'root article content', 'title': 'Root Article'}) + self.example_data = { + 'content': 'The modified text', + 'current_revision': '1', + 'preview': '1', + 'summary': 'why edited', + 'title': 'wiki test'} + + def tearDown(self): + # clear Article cache before the next test + cache.clear() + + def get_by_path(self, path): + """Get the article response for the path. + Example: self.get_by_path("Level1/Slug2/").title """ - self.assertEqual(1 + 1, 2) + return self.c.get(reverse('wiki:get', kwargs={'path': path})) + + def test_preview_save(self): + """Test edit preview, edit save and messages.""" + c = self.c + # test preview + response = c.post(reverse('wiki:preview', kwargs={'path': ''}), self.example_data) # url: '/_preview/' + self.assertContains(response, 'The modified text') + # test save and messages + example2 = self.example_data.copy() + example2['content'] = 'Something 2' + response = c.post(reverse('wiki:edit', kwargs={'path': ''}), example2) + message = c.cookies['messages'].value if 'messages' in c.cookies else None + self.assertRedirects(response, reverse('wiki:root')) + response = c.get(reverse('wiki:root')) + self.assertContains(response, 'Something 2') + self.assertTrue('successfully added' in message) + + def test_redirect_create(self): + """Test that redirects to create if the slug is unknown.""" + response = self.get_by_path('Unknown/') + self.assertRedirects(response, reverse('wiki:create', kwargs={'path': ''}) + '?slug=Unknown') + + def test_cleared_cache(self): + """Test the article cache is cleared after delete.""" + # That bug is tested by one individual test, otherwise it could be + # revealed only by sequence of tests in some particular order + c = self.c + response = c.post(reverse('wiki:create', kwargs={'path': ''}), + {'title': 'Test cache', 'slug': 'TestCache', 'content': 'Content 1'}) + self.assertRedirects(response, reverse('wiki:get', kwargs={'path': 'TestCache/'})) + self.assertContains(self.get_by_path('TestCache/'), 'Content 1') + response = c.post(reverse('wiki:delete', kwargs={'path': 'TestCache/'}), + {'confirm': 'on', 'purge': 'on', 'revision': '2'}) + self.assertRedirects(response, reverse('wiki:get', kwargs={'path': ''})) + response = c.post(reverse('wiki:create', kwargs={'path': ''}), + {'title': 'Test cache', 'slug': 'TestCache', 'content': 'Content 2'}) + self.assertRedirects(response, reverse('wiki:get', kwargs={'path': 'TestCache/'})) + # test the cache + self.assertContains(self.get_by_path('TestCache/'), 'Content 2') + + def test_article_list_update(self): + """Test automatic adding and removing the new article to/from article_list.""" + c = self.c + root_data = {'content': '[article_list depth:2]', 'current_revision': '1', 'preview': '1', 'title': 'Root Article'} + response = c.post(reverse('wiki:edit', kwargs={'path': ''}), root_data) + self.assertRedirects(response, reverse('wiki:root')) + # verify the new article is added to article_list + response = c.post(reverse('wiki:create', kwargs={'path': ''}), + {'title': 'Sub Article 1', 'slug': 'SubArticle1'}) + self.assertRedirects(response, reverse('wiki:get', kwargs={'path': 'SubArticle1/'})) + self.assertContains(self.get_by_path(''), 'Sub Article 1') + self.assertContains(self.get_by_path(''), 'SubArticle1/') + # verify the deleted article is removed from article_list + response = c.post(reverse('wiki:delete', kwargs={'path': 'SubArticle1/'}), + {'confirm': 'on', 'purge': 'on', 'revision': '3'}) + message = c.cookies['messages'].value if 'messages' in c.cookies else None + self.assertRedirects(response, reverse('wiki:get', kwargs={'path': ''})) + self.assertTrue('This article together with all its contents are now completely gone' in message) + self.assertNotContains(self.get_by_path(''), 'Sub Article 1') + + def test_revision_conflict(self): + """Test the warning if the same article is beeing edited concurrently.""" + c = self.c + response = c.post(reverse('wiki:edit', kwargs={'path': ''}), self.example_data) + self.assertRedirects(response, reverse('wiki:root')) + response = c.post(reverse('wiki:edit', kwargs={'path': ''}), self.example_data) + self.assertContains(response, 'While you were editing, someone else changed the revision.') + + def test_nested_create(self): + c = self.c + response = c.post(reverse('wiki:create', kwargs={'path': ''}), + {'title': 'Level 1', 'slug': 'Level1', 'content': 'Content level 1'}) + self.assertRedirects(response, reverse('wiki:get', kwargs={'path': 'Level1/'})) + response = c.post(reverse('wiki:create', kwargs={'path': 'Level1/'}), + {'title': 'test', 'slug': 'Test', 'content': 'Content on level 2'}) + self.assertRedirects(response, reverse('wiki:get', kwargs={'path': 'Level1/Test/'})) + response = c.post(reverse('wiki:create', kwargs={'path': ''}), + {'title': 'test', 'slug': 'Test', 'content': 'Other content on level 1'}) + self.assertRedirects(response, reverse('wiki:get', kwargs={'path': 'Test/'})) + self.assertContains(self.get_by_path('Test/'), 'Other content on level 1') + self.assertContains(self.get_by_path('Level1/Test/'), 'Content') # on level 2')