import pprint from django.contrib.messages import constants from django.contrib.messages import get_messages from django.http import JsonResponse from django.shortcuts import resolve_url from django.test import override_settings from django.utils import translation from django.utils.html import escape from django_functest import FuncBaseMixin from wiki import models from wiki.conf import settings as wiki_settings from wiki.forms import PermissionsForm from wiki.forms import validate_slug_numbers from wiki.models import ArticleRevision from wiki.models import reverse from wiki.models import URLPath from ..base import ArticleWebTestUtils from ..base import DjangoClientTestBase from ..base import NORMALUSER1_PASSWORD from ..base import NORMALUSER1_USERNAME from ..base import RequireRootArticleMixin from ..base import SeleniumBase from ..base import SUPERUSER1_USERNAME from ..base import WebTestBase from tests.testdata.models import CustomGroup class RootArticleViewTestsBase(FuncBaseMixin): """Tests for creating/viewing the root article.""" def test_root_article(self): """ Test redirecting to /create-root/, creating the root article and a simple markup. """ self.get_url("wiki:root") self.assertUrlsEqual(resolve_url("wiki:root_create")) self.fill( { "#id_content": "test heading h1\n====\n", "#id_title": "Wiki Test", } ) self.submit('button[name="save_changes"]') self.assertUrlsEqual("/") self.assertTextPresent("test heading h1") article = URLPath.root().article self.assertIn("test heading h1", article.current_revision.content) class RootArticleViewTestsWebTest(RootArticleViewTestsBase, WebTestBase): pass class RootArticleViewTestsSelenium(RootArticleViewTestsBase, SeleniumBase): pass class ArticleViewViewTests( RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase ): """ Tests for article views, assuming a root article already created. """ def dump_db_status(self, message=""): """Debug printing of the complete important database content.""" print(f"*** db status *** {message}") from wiki.models import Article, ArticleRevision for klass in (Article, ArticleRevision, URLPath): print(f"* {klass.__name__} *") pprint.pprint(list(klass.objects.values()), width=240) def test_redirects_to_create_if_the_slug_is_unknown(self): response = self.get_by_path("unknown/") self.assertRedirects( response, resolve_url("wiki:create", path="") + "?slug=unknown" ) def test_redirects_to_create_with_lowercased_slug(self): response = self.get_by_path("Unknown_Linked_Page/") self.assertRedirects( response, resolve_url("wiki:create", path="") + "?slug=unknown_linked_page", ) def test_article_list_update(self): """ Test automatic adding and removing the new article to/from article_list. """ root_data = { "content": "[article_list depth:2]", "current_revision": str( URLPath.root().article.current_revision.id ), "preview": "1", "title": "Root Article", } response = self.client.post( resolve_url("wiki:edit", path=""), root_data ) self.assertRedirects(response, resolve_url("wiki:root")) # verify the new article is added to article_list response = self.client.post( resolve_url("wiki:create", path=""), {"title": "Sub Article 1", "slug": "SubArticle1"}, ) self.assertRedirects( response, resolve_url("wiki:get", 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 = self.client.post( resolve_url("wiki:delete", path="SubArticle1/"), { "confirm": "on", "purge": "on", "revision": str( URLPath.objects.get( slug="subarticle1" ).article.current_revision.id ), }, ) self.assertRedirects(response, resolve_url("wiki:get", path="")) messages = [m.message for m in get_messages(response.wsgi_request)] self.assertIn( "This article together with all its contents are now completely gone", messages[0], ) self.assertNotContains(self.get_by_path(""), "Sub Article 1") def test_anonymous_root(self): self.client.logout() response = self.client.get( reverse("wiki:get", kwargs={"article_id": self.root_article.pk}) ) self.assertEqual(response.status_code, 200) response = self.client.get(reverse("wiki:get", kwargs={"path": ""})) self.assertEqual(response.status_code, 200) def test_normaluser_root(self): self.client.login( username=NORMALUSER1_USERNAME, password=NORMALUSER1_PASSWORD ) response = self.client.get( reverse("wiki:get", kwargs={"article_id": self.root_article.pk}) ) self.assertEqual(response.status_code, 200) response = self.client.get(reverse("wiki:get", kwargs={"path": ""})) self.assertEqual(response.status_code, 200) def test_show_max_children(self): response = self.client.post( resolve_url("wiki:create", path=""), { "title": "Main", "slug": "WikiRoot", "content": "Content level 1", }, ) self.assertRedirects( response, resolve_url("wiki:get", path="wikiroot/") ) response = self.client.get( reverse("wiki:get", kwargs={"path": "wikiroot/"}) ) self.assertEqual(response.status_code, 200) self.assertIsInstance(response.context["children_slice"], list) self.assertEqual(len(response.context["children_slice"]), 0) for idx in range(1, 40): response = self.client.post( resolve_url("wiki:create", path="wikiroot/"), { "title": f"Sub Article {idx}", "slug": f"SubArticle{idx}", "content": f"Sub Article {idx}", }, ) self.assertRedirects( response, resolve_url("wiki:get", path=f"wikiroot/subarticle{idx}/"), ) response = self.client.get( reverse("wiki:get", kwargs={"path": "wikiroot/"}) ) self.assertEqual(response.status_code, 200) self.assertEqual( len(response.context["children_slice"]), wiki_settings.SHOW_MAX_CHILDREN, ) class CreateViewTest( RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase ): def test_create_nested_article_in_article(self): response = self.client.post( resolve_url("wiki:create", path=""), { "title": "Level 1", "slug": "Level1", "content": "Content level 1", }, ) self.assertRedirects(response, resolve_url("wiki:get", path="level1/")) response = self.client.post( resolve_url("wiki:create", path="Level1/"), {"title": "test", "slug": "Test", "content": "Content on level 2"}, ) self.assertRedirects( response, resolve_url("wiki:get", path="level1/test/") ) response = self.client.post( resolve_url("wiki:create", path=""), { "title": "test", "slug": "Test", "content": "Other content on level 1", }, ) self.assertRedirects(response, resolve_url("wiki:get", 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') def test_illegal_slug(self): # A slug cannot be '123' because it gets confused with an article ID. response = self.client.post( resolve_url("wiki:create", path=""), {"title": "Illegal slug", "slug": "123", "content": "blah"}, ) self.assertContains(response, escape(validate_slug_numbers.message)) class MoveViewTest( RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase ): def test_illegal_slug(self): # A slug cannot be '123' because it gets confused with an article ID. response = self.client.post( resolve_url("wiki:move", path=""), {"destination": "", "slug": "123", "redirect": ""}, ) self.assertContains(response, escape(validate_slug_numbers.message)) def test_move(self): # Create a hierarchy of pages self.client.post( resolve_url("wiki:create", path=""), {"title": "Test", "slug": "test0", "content": "Content .0."}, ) self.client.post( resolve_url("wiki:create", path="test0/"), {"title": "Test00", "slug": "test00", "content": "Content .00."}, ) self.client.post( resolve_url("wiki:create", path=""), {"title": "Test1", "slug": "test1", "content": "Content .1."}, ) self.client.post( resolve_url("wiki:create", path="test1/"), {"title": "Tes10", "slug": "test10", "content": "Content .10."}, ) self.client.post( resolve_url("wiki:create", path="test1/test10/"), { "title": "Test100", "slug": "test100", "content": "Content .100.", }, ) # Move /test1 => /test0 (an already existing destination slug!) response = self.client.post( resolve_url("wiki:move", path="test1/"), { "destination": str(URLPath.root().article.current_revision.id), "slug": "test0", "redirect": "", }, ) self.assertContains(response, "A slug named") self.assertContains(response, "already exists.") # Move /test1 >= /test2 (valid slug), no redirect test0_id = URLPath.objects.get( slug="test0" ).article.current_revision.id response = self.client.post( resolve_url("wiki:move", path="test1/"), {"destination": str(test0_id), "slug": "test2", "redirect": ""}, ) self.assertRedirects( response, resolve_url("wiki:get", path="test0/test2/") ) # Check that there is no article displayed in this path anymore response = self.get_by_path("test1/") self.assertRedirects(response, "/_create/?slug=test1") # Create /test0/test2/test020 response = self.client.post( resolve_url("wiki:create", path="test0/test2/"), { "title": "Test020", "slug": "test020", "content": "Content .020.", }, ) # Move /test0/test2 => /test1new + create redirect response = self.client.post( resolve_url("wiki:move", path="test0/test2/"), { "destination": str(URLPath.root().article.current_revision.id), "slug": "test1new", "redirect": "true", }, ) self.assertRedirects( response, resolve_url("wiki:get", path="test1new/") ) # Check that /test1new is a valid path response = self.get_by_path("test1new/") self.assertContains(response, "Content .1.") # Check that the child article test0/test2/test020 was also moved response = self.get_by_path("test1new/test020/") self.assertContains(response, "Content .020.") response = self.get_by_path("test0/test2/") self.assertContains(response, "Moved: Test1") self.assertRegex( response.rendered_content, r"moved to ]*>wiki:/test1new/" ) response = self.get_by_path("test0/test2/test020/") self.assertContains(response, "Moved: Test020") self.assertRegex( response.rendered_content, r"moved to ]*>wiki:/test1new/test020", ) # Check that moved_to was correctly set urlsrc = URLPath.get_by_path("/test0/test2/") urldst = URLPath.get_by_path("/test1new/") self.assertEqual(urlsrc.moved_to, urldst) # Check that moved_to was correctly set on the child's previous path urlsrc = URLPath.get_by_path("/test0/test2/test020/") urldst = URLPath.get_by_path("/test1new/test020/") self.assertEqual(urlsrc.moved_to, urldst) def test_translation(self): # Test that translation of "Be careful, links to this article" exists. self.client.post( resolve_url("wiki:create", path=""), {"title": "Test", "slug": "test0", "content": "Content"}, ) url = resolve_url("wiki:move", path="test0/") response_en = self.client.get(url) self.assertIn("Move article", response_en.rendered_content) self.assertIn("Be careful", response_en.rendered_content) with translation.override("da-DK"): response_da = self.client.get(url) self.assertNotIn("Move article", response_da.rendered_content) self.assertNotIn("Be careful", response_da.rendered_content) class DeleteViewTest( RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase ): def test_render_delete_view(self): """ Other tests do not render the delete view but just sends a POST """ self.client.post( resolve_url("wiki:create", path=""), { "title": "Test delete", "slug": "testdelete", "content": "To be deleted", }, ) response = self.client.get( resolve_url("wiki:delete", path="testdelete/"), ) # test the cache self.assertContains(response, "Delete article") def test_articles_cache_is_cleared_after_deleting(self): # That bug is tested by one individual test, otherwise it could be # revealed only by sequence of tests in some particular order response = self.client.post( resolve_url("wiki:create", path=""), { "title": "Test cache", "slug": "testcache", "content": "Content 1", }, ) self.assertRedirects( response, resolve_url("wiki:get", path="testcache/") ) response = self.client.post( resolve_url("wiki:delete", path="testcache/"), { "confirm": "on", "purge": "on", "revision": str( URLPath.objects.get( slug="testcache" ).article.current_revision.id ), }, ) self.assertRedirects(response, resolve_url("wiki:get", path="")) response = self.client.post( resolve_url("wiki:create", path=""), { "title": "Test cache", "slug": "TestCache", "content": "Content 2", }, ) self.assertRedirects( response, resolve_url("wiki:get", path="testcache/") ) self.assertContains(self.get_by_path("TestCache/"), "Content 2") def test_deleted_view(self): """ Test that a special page is shown for restoring/purging a deleted article. """ # 1. Create the article self.client.post( resolve_url("wiki:create", path=""), { "title": "Test delete", "slug": "testdelete", "content": "To be deleted", }, ) # 2. Soft delete it self.client.post( resolve_url("wiki:delete", path="testdelete/"), { "confirm": "on", "purge": "", "revision": str( URLPath.objects.get( slug="testdelete" ).article.current_revision.id ), }, ) # 3. Get and test that it redirects to the deleted page response = self.client.get( resolve_url("wiki:get", path="testdelete/"), follow=True, ) # test that it's the Deleted page self.assertContains(response, "Article deleted") # 4. Test that we can purge the page now self.client.post( resolve_url("wiki:deleted", path="testdelete/"), { "confirm": "on", "purge": "on", "revision": str( URLPath.objects.get( slug="testdelete" ).article.current_revision.id ), }, ) # 5. Test that it's not found anymore response = self.client.get( resolve_url("wiki:get", path="testdelete/"), follow=True, ) self.assertContains(response, "Add new article") # def test_delete_article_without_urlpath(self): # """ # We need a test that tests that articles without URLpaths are correctly # deleted. # """ # pass # def test_dont_delete_children(self): # Article.objects.create() class EditViewTest( RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase ): def test_preview_save(self): """Test edit preview, edit save and messages.""" example_data = { "content": "The modified text", "current_revision": str( URLPath.root().article.current_revision.id ), "preview": "1", # 'save': '1', # probably not too important "summary": "why edited", "title": "wiki test", } # test preview response = self.client.post( resolve_url("wiki:preview", path=""), example_data, # url: '/_preview/' ) self.assertContains(response, "The modified text") def test_preview_xframe_options_sameorigin(self): """Ensure that preview response has X-Frame-Options: SAMEORIGIN""" example_data = { "content": "The modified text", "current_revision": str( URLPath.root().article.current_revision.id ), "preview": "1", "summary": "why edited", "title": "wiki test", } response = self.client.post( resolve_url("wiki:preview", path=""), example_data ) self.assertEqual(response.get("X-Frame-Options"), "SAMEORIGIN") def test_revision_conflict(self): """ Test the warning if the same article is being edited concurrently. """ example_data = { "content": "More modifications", "current_revision": str( URLPath.root().article.current_revision.id ), "preview": "0", "save": "1", "summary": "why edited", "title": "wiki test", } response = self.client.post( resolve_url("wiki:edit", path=""), example_data ) self.assertRedirects(response, resolve_url("wiki:root")) response = self.client.post( resolve_url("wiki:edit", path=""), example_data ) self.assertContains( response, "While you were editing, someone else changed the revision.", ) class DiffViewTests(RequireRootArticleMixin, DjangoClientTestBase): def setUp(self): super().setUp() self.root_article.add_revision( ArticleRevision(title="New Revision"), save=True ) self.new_revision = self.root_article.current_revision def test_diff(self): response = self.client.get( reverse("wiki:diff", kwargs={"revision_id": self.root_article.pk}) ) diff = { "diff": ["+ root article content"], "other_changes": [["New title", "Root Article"]], } self.assertJSONEqual(str(response.content, encoding="utf8"), diff) self.assertIsInstance(response, JsonResponse) self.assertEqual(response.status_code, 200) class EditViewTestsBase(RequireRootArticleMixin, FuncBaseMixin): def test_edit_save(self): old_revision = URLPath.root().article.current_revision self.get_url("wiki:edit", path="") self.fill( { "#id_content": "Something 2", "#id_summary": "why edited", "#id_title": "wiki test", } ) self.submit("#id_save") self.assertTextPresent("Something 2") self.assertTextPresent("successfully added") new_revision = URLPath.root().article.current_revision self.assertIn("Something 2", new_revision.content) self.assertEqual( new_revision.revision_number, old_revision.revision_number + 1 ) class EditViewTestsWebTest(EditViewTestsBase, WebTestBase): pass class EditViewTestsSelenium(EditViewTestsBase, SeleniumBase): # Javascript only tests: def test_preview_and_save(self): self.get_url("wiki:edit", path="") self.fill( { "#id_content": "Some changed stuff", "#id_summary": "why edited", "#id_title": "wiki test", } ) self.click("#id_preview") self.submit("#id_preview_save_changes") new_revision = URLPath.root().article.current_revision self.assertIn("Some changed stuff", new_revision.content) class SearchViewTest( RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase ): def test_query_string(self): response = self.client.get( resolve_url("wiki:search"), {"q": "Article"} ) self.assertContains(response, "Root Article") def test_empty_query_string(self): response = self.client.get(resolve_url("wiki:search"), {"q": ""}) self.assertFalse(response.context["articles"]) def test_hierarchy_search(self): c = self.client c.post( resolve_url("wiki:create", path=""), {"title": "Test0", "slug": "test0", "content": "Content test0"}, ) c.post( resolve_url("wiki:create", path=""), {"title": "Test1", "slug": "test1", "content": "Content test1"}, ) c.post( resolve_url("wiki:create", path="test0/"), { "title": "Subtest0", "slug": "subtest0", "content": "Content test2", }, ) response = c.get( resolve_url("wiki:search", path="test0/"), {"q": "Content test"} ) articles = response.context["articles"] def contains_title(articles, title): return any( article.current_revision.title == title for article in articles ) self.assertIs(contains_title(articles, "Test0"), True) self.assertIs(contains_title(articles, "Test1"), False) self.assertIs(contains_title(articles, "Subtest0"), True) def test_hierarchy_search_404(self): c = self.client response = c.get( resolve_url("wiki:search", path="test0/"), {"q": "Content test"} ) self.assertEqual(response.status_code, 404) class DeletedListViewTest( RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase ): def test_deleted_articles_list(self): response = self.client.post( resolve_url("wiki:create", path=""), { "title": "Delete Me", "slug": "deleteme", "content": "delete me please!", }, ) self.assertRedirects( response, resolve_url("wiki:get", path="deleteme/") ) response = self.client.post( resolve_url("wiki:delete", path="deleteme/"), { "confirm": "on", "revision": URLPath.objects.get( slug="deleteme" ).article.current_revision.id, }, ) self.assertRedirects(response, resolve_url("wiki:get", path="")) response = self.client.get(resolve_url("wiki:deleted_list")) self.assertContains(response, "Delete Me") class MergeViewTest( RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase ): def test_merge_preview(self): """Test merge preview""" first_revision = self.root_article.current_revision example_data = { "content": "More modifications\n\nMerge new line", "current_revision": str(first_revision.id), "preview": "0", "save": "1", "summary": "testing merge", "title": "wiki test", } # save a new revision self.client.post(resolve_url("wiki:edit", path=""), example_data) new_revision = models.Article.objects.get( id=self.root_article.id ).current_revision response = self.client.get( resolve_url( "wiki:merge_revision_preview", article_id=self.root_article.id, revision_id=first_revision.id, ), ) self.assertContains(response, "Previewing merge between:") self.assertContains( response, f"#{first_revision.revision_number}", ) self.assertContains( response, f"#{new_revision.revision_number}", ) class SourceViewTests( RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase ): def test_template_used(self): response = self.client.get( reverse("wiki:source", kwargs={"article_id": self.root_article.pk}) ) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, template_name="wiki/source.html") def test_can_read_permission(self): # everybody can see the source of an article self.client.logout() response = self.client.get( reverse("wiki:source", kwargs={"article_id": self.root_article.pk}) ) self.assertEqual(response.status_code, 200) def test_content(self): response = self.client.get( reverse("wiki:source", kwargs={"article_id": self.root_article.pk}) ) self.assertIn("Source of ", str(response.content)) self.assertEqual(response.context["selected_tab"], "source") class HistoryViewTests( RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase ): def test_can_read_permission(self): response = self.client.get( reverse( "wiki:history", kwargs={"article_id": self.root_article.pk} ) ) self.assertEqual(response.status_code, 200) def test_content(self): response = self.client.get( reverse( "wiki:history", kwargs={"article_id": self.root_article.pk} ) ) self.assertContains(response, "History:") self.assertEqual(response.context["selected_tab"], "history") class DirViewTests( RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase ): def test_browse_root(self): response = self.client.get( reverse("wiki:dir", kwargs={"path": ""}), ) self.assertRegex( response.rendered_content, r'Browsing\s+/', ) def test_browse_root_query(self): self.client.post( resolve_url("wiki:create", path=""), {"title": "Test", "slug": "test0", "content": "Content .0."}, ) self.client.post( resolve_url("wiki:create", path="test0/"), {"title": "Test00", "slug": "test00", "content": "Content .00."}, ) response = self.client.get( reverse("wiki:dir", kwargs={"path": ""}), {"query": "Test"}, ) self.assertRegex(response.rendered_content, r"1 article") response = self.client.get( reverse("wiki:dir", kwargs={"path": "test0/"}), {"query": "Test00"}, ) self.assertRegex(response.rendered_content, r"1 article") class SettingsViewTests( RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase ): def test_change_group(self): group = CustomGroup.objects.create() response = self.client.post( resolve_url("wiki:settings", article_id=self.root_article.pk) + "?f=form0", {"group": group.pk, "owner_username": SUPERUSER1_USERNAME}, follow=True, ) self.root_article.refresh_from_db() self.assertEqual(self.root_article.group, group) self.assertEqual(self.root_article.owner, self.superuser1) messages = list(get_messages(response.wsgi_request)) self.assertEqual(len(messages), 1) message = messages[0] self.assertEqual(message.level, constants.SUCCESS) self.assertEqual( message.message, "Permission settings for the article were updated.", ) def test_change_invalid_owner(self): self.assertIsNone(self.root_article.owner) response = self.client.post( resolve_url("wiki:settings", article_id=self.root_article.pk) + "?f=form0", {"owner_username": "invalid"}, follow=True, ) self.assertEqual( response.context["forms"][0].errors["owner_username"], ["No user with that username"], ) def test_unchanged_message(self): # 1. This is not pretty: Constructs a request object to use to construct # the PermissionForm get_response = self.client.get( resolve_url("wiki:settings", article_id=self.root_article.pk) ) # 2. Construct a PermissionForm form = PermissionsForm(self.root_article, get_response.wsgi_request) # 3. ...in order to get the POST form values that will be transmitted form_values = {field.html_name: field.value() or "" for field in form} # 4. Send an unchanged form response = self.client.post( resolve_url("wiki:settings", article_id=self.root_article.pk) + "?f=form0", form_values, follow=True, ) messages = list(get_messages(response.wsgi_request)) self.assertEqual(len(messages), 1) message = messages[0] self.assertEqual(message.level, constants.SUCCESS) self.assertEqual( message.message, "Your permission settings were unchanged, so nothing saved.", ) @override_settings(ACCOUNT_HANDLING=True) def test_login_required(self): self.client.logout() response = self.client.get( reverse( "wiki:settings", kwargs={"article_id": self.root_article.pk} ) ) # it's redirecting self.assertEqual(response.status_code, 302) def test_auth_user(self): response = self.client.get( reverse( "wiki:settings", kwargs={"article_id": self.root_article.pk} ) ) self.assertEqual(response.status_code, 200) def test_normal_user(self): """ Tests that the settings view page renders for a normal user Regression test: https://github.com/django-wiki/django-wiki/issues/1058 """ response = self.client.post( resolve_url("wiki:create", path=""), { "title": "Level 1", "slug": "Level1", "content": "Content level 1", }, ) self.client.login( username=NORMALUSER1_USERNAME, password=NORMALUSER1_PASSWORD ) response = self.client.get( reverse("wiki:settings", kwargs={"path": "level1/"}) ) self.assertEqual(response.status_code, 200) def test_content(self): response = self.client.get( reverse( "wiki:settings", kwargs={"article_id": self.root_article.pk} ) ) self.assertEqual(response.context["selected_tab"], "settings")