initial
This commit is contained in:
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
158
tests/base.py
Normal file
158
tests/base.py
Normal file
@@ -0,0 +1,158 @@
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import django_functest
|
||||
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
|
||||
from django.template import Context
|
||||
from django.template import Template
|
||||
from django.test import override_settings
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from wiki.models import URLPath
|
||||
|
||||
SUPERUSER1_USERNAME = "admin"
|
||||
SUPERUSER1_PASSWORD = "secret"
|
||||
|
||||
NORMALUSER1_USERNAME = "normaluser"
|
||||
NORMALUSER1_PASSWORD = "secret"
|
||||
|
||||
|
||||
class RequireSuperuserMixin:
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
self.superuser1 = User.objects.create_superuser(
|
||||
SUPERUSER1_USERNAME, "nobody@example.com", SUPERUSER1_PASSWORD
|
||||
)
|
||||
|
||||
|
||||
class RequireBasicData(RequireSuperuserMixin):
|
||||
"""
|
||||
Mixin that creates common data required for all tests.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
self.normaluser1 = User.objects.create_user(
|
||||
NORMALUSER1_USERNAME, "nobody@example.com", NORMALUSER1_PASSWORD
|
||||
)
|
||||
|
||||
|
||||
class TestBase(RequireBasicData, TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class RequireRootArticleMixin:
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.root = URLPath.create_root()
|
||||
self.root_article = URLPath.root().article
|
||||
rev = self.root_article.current_revision
|
||||
rev.title = "Root Article"
|
||||
rev.content = "root article content"
|
||||
rev.save()
|
||||
|
||||
|
||||
class ArticleTestBase(RequireRootArticleMixin, TestBase):
|
||||
"""
|
||||
Sets up basic data for testing with an article and some revisions
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DjangoClientTestBase(TestBase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.client.login(
|
||||
username=SUPERUSER1_USERNAME, password=SUPERUSER1_PASSWORD
|
||||
)
|
||||
|
||||
|
||||
class WebTestCommonMixin(RequireBasicData, django_functest.ShortcutLoginMixin):
|
||||
"""
|
||||
Common setup required for WebTest and Selenium tests
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.shortcut_login(
|
||||
username=SUPERUSER1_USERNAME, password=SUPERUSER1_PASSWORD
|
||||
)
|
||||
|
||||
|
||||
class WebTestBase(
|
||||
WebTestCommonMixin, django_functest.FuncWebTestMixin, TestCase
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
INCLUDE_SELENIUM_TESTS = os.environ.get("INCLUDE_SELENIUM_TESTS", "0") == "1"
|
||||
|
||||
|
||||
@unittest.skipUnless(INCLUDE_SELENIUM_TESTS, "Skipping Selenium tests")
|
||||
class SeleniumBase(
|
||||
WebTestCommonMixin,
|
||||
django_functest.FuncSeleniumMixin,
|
||||
StaticLiveServerTestCase,
|
||||
):
|
||||
driver_name = "Chrome"
|
||||
display = os.environ.get("SELENIUM_SHOW_BROWSER", "0") == "1"
|
||||
|
||||
if not INCLUDE_SELENIUM_TESTS:
|
||||
# Don't call super() in setUpClass(), it will attempt to instantiate
|
||||
# a browser instance which is slow and might fail
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
pass
|
||||
|
||||
|
||||
class ArticleWebTestUtils:
|
||||
def get_by_path(self, path):
|
||||
"""
|
||||
Get the article response for the path.
|
||||
Example: self.get_by_path("Level1/Slug2/").title
|
||||
"""
|
||||
return self.client.get(reverse("wiki:get", kwargs={"path": path}))
|
||||
|
||||
|
||||
class TemplateTestCase(TestCase):
|
||||
@property
|
||||
def template(self):
|
||||
raise NotImplementedError("Subclasses must implement this")
|
||||
|
||||
def render(self, context):
|
||||
return Template(self.template).render(Context(context))
|
||||
|
||||
|
||||
# See
|
||||
# https://github.com/django-wiki/django-wiki/pull/382
|
||||
class wiki_override_settings(override_settings):
|
||||
def enable(self):
|
||||
super().enable()
|
||||
self.reload_wiki_settings()
|
||||
|
||||
def disable(self):
|
||||
super().disable()
|
||||
self.reload_wiki_settings()
|
||||
|
||||
def reload_wiki_settings(self):
|
||||
from importlib import reload
|
||||
from wiki.conf import settings
|
||||
|
||||
reload(settings)
|
||||
0
tests/core/__init__.py
Normal file
0
tests/core/__init__.py
Normal file
153
tests/core/test_accounts.py
Normal file
153
tests/core/test_accounts.py
Normal file
@@ -0,0 +1,153 @@
|
||||
from django.conf import settings as django_settings
|
||||
from django.contrib import auth
|
||||
from django.contrib.auth import authenticate
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.shortcuts import resolve_url
|
||||
from wiki.conf import settings as wiki_settings
|
||||
from wiki.models import reverse
|
||||
|
||||
from ..base import ArticleWebTestUtils
|
||||
from ..base import DjangoClientTestBase
|
||||
from ..base import RequireRootArticleMixin
|
||||
from ..base import SUPERUSER1_PASSWORD
|
||||
from ..base import SUPERUSER1_USERNAME
|
||||
from ..base import TestBase
|
||||
from ..base import wiki_override_settings
|
||||
from ..testdata.models import CustomUser
|
||||
|
||||
|
||||
SIGNUP_TEST_USERNAME = "wiki"
|
||||
SIGNUP_TEST_PASSWORD = "wiki1234567"
|
||||
|
||||
|
||||
class AccountUpdateTest(
|
||||
RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase
|
||||
):
|
||||
def test_password_change(self):
|
||||
"""
|
||||
Test that we can make a successful password change via the update form
|
||||
"""
|
||||
# Check out that it works as expected, notice that there is no referrer
|
||||
# on this GET request.
|
||||
self.client.get(
|
||||
resolve_url(
|
||||
"wiki:profile_update",
|
||||
)
|
||||
)
|
||||
|
||||
# Now check that we don't succeed with unmatching passwords
|
||||
example_data = {
|
||||
"password1": "abcdef",
|
||||
"password2": "abcdef123",
|
||||
"email": self.superuser1.email,
|
||||
}
|
||||
|
||||
# save a new revision
|
||||
response = self.client.post(
|
||||
resolve_url("wiki:profile_update"), example_data
|
||||
)
|
||||
self.assertContains(
|
||||
response, "Passwords don", status_code=200
|
||||
) # Django 2/3 output different escaped versions of single quote in don't
|
||||
|
||||
# Now check that we don't succeed with unmatching passwords
|
||||
example_data = {
|
||||
"password1": "abcdef",
|
||||
"password2": "abcdef",
|
||||
"email": self.superuser1.email,
|
||||
}
|
||||
|
||||
# save a new revision
|
||||
response = self.client.post(
|
||||
resolve_url("wiki:profile_update"), example_data
|
||||
)
|
||||
|
||||
# Need to force str() because of:
|
||||
# TypeError: coercing to Unicode: need string or buffer, __proxy__
|
||||
# found
|
||||
self.assertRedirects(response, str(django_settings.LOGIN_REDIRECT_URL))
|
||||
|
||||
self.assertEqual(
|
||||
self.superuser1,
|
||||
authenticate(
|
||||
username=self.superuser1.username,
|
||||
password=example_data["password1"],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class UpdateProfileViewTest(
|
||||
RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase
|
||||
):
|
||||
def test_update_profile(self):
|
||||
self.client.post(
|
||||
resolve_url("wiki:profile_update"),
|
||||
{
|
||||
"email": "test@test.com",
|
||||
"password1": "newPass",
|
||||
"password2": "newPass",
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
|
||||
test_auth = authenticate(username="admin", password="newPass")
|
||||
|
||||
self.assertIsNotNone(test_auth)
|
||||
self.assertEqual(test_auth.email, "test@test.com")
|
||||
|
||||
|
||||
@wiki_override_settings(ACCOUNT_HANDLING=True)
|
||||
class LogoutViewTests(RequireRootArticleMixin, DjangoClientTestBase):
|
||||
def test_logout_account_handling(self):
|
||||
self.client.get(wiki_settings.LOGOUT_URL)
|
||||
user = auth.get_user(self.client)
|
||||
self.assertIs(auth.get_user(self.client).is_authenticated, False)
|
||||
self.assertIsInstance(user, AnonymousUser)
|
||||
|
||||
|
||||
@wiki_override_settings(ACCOUNT_HANDLING=True)
|
||||
class LoginTestViews(RequireRootArticleMixin, TestBase):
|
||||
def test_already_signed_in(self):
|
||||
self.client.force_login(self.superuser1)
|
||||
response = self.client.get(wiki_settings.LOGIN_URL)
|
||||
self.assertRedirects(response, reverse("wiki:root"))
|
||||
|
||||
def test_log_in(self):
|
||||
self.client.post(
|
||||
wiki_settings.LOGIN_URL,
|
||||
{"username": SUPERUSER1_USERNAME, "password": SUPERUSER1_PASSWORD},
|
||||
)
|
||||
self.assertIs(self.superuser1.is_authenticated, True)
|
||||
self.assertEqual(auth.get_user(self.client), self.superuser1)
|
||||
|
||||
|
||||
class SignupViewTests(RequireRootArticleMixin, TestBase):
|
||||
@wiki_override_settings(ACCOUNT_HANDLING=True, ACCOUNT_SIGNUP_ALLOWED=True)
|
||||
def test_signup(self):
|
||||
response = self.client.post(
|
||||
wiki_settings.SIGNUP_URL,
|
||||
data={
|
||||
"password1": SIGNUP_TEST_PASSWORD,
|
||||
"password2": SIGNUP_TEST_PASSWORD,
|
||||
"username": SIGNUP_TEST_USERNAME,
|
||||
"email": "wiki@wiki.com",
|
||||
},
|
||||
)
|
||||
self.assertIs(
|
||||
CustomUser.objects.filter(email="wiki@wiki.com").exists(), True
|
||||
)
|
||||
self.assertRedirects(response, reverse("wiki:login"))
|
||||
|
||||
# Test that signing up the same user again gives a validation error
|
||||
# Regression test: https://github.com/django-wiki/django-wiki/issues/1152
|
||||
response = self.client.post(
|
||||
wiki_settings.SIGNUP_URL,
|
||||
data={
|
||||
"password1": SIGNUP_TEST_PASSWORD,
|
||||
"password2": SIGNUP_TEST_PASSWORD,
|
||||
"username": SIGNUP_TEST_USERNAME,
|
||||
"email": "wiki@wiki.com",
|
||||
},
|
||||
)
|
||||
self.assertIs(response.status_code, 200)
|
||||
self.assertContains(response, "username already exists")
|
||||
64
tests/core/test_basic.py
Normal file
64
tests/core/test_basic.py
Normal file
@@ -0,0 +1,64 @@
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
|
||||
from django.test import TestCase
|
||||
from wiki.conf import settings as wiki_settings
|
||||
from wiki.core.http import send_file
|
||||
from wiki.forms import Group
|
||||
from wiki.models import Article
|
||||
from wiki.models import ArticleRevision
|
||||
from wiki.models import URLPath
|
||||
|
||||
from ..base import wiki_override_settings
|
||||
from ..testdata.models import CustomGroup
|
||||
|
||||
|
||||
class URLPathTests(TestCase):
|
||||
def test_manager(self):
|
||||
root = URLPath.create_root()
|
||||
child = URLPath.create_urlpath(root, "child")
|
||||
|
||||
self.assertIsNone(root.parent)
|
||||
self.assertEqual(list(root.children.all().active()), [child])
|
||||
|
||||
|
||||
class CustomGroupTests(TestCase):
|
||||
@wiki_override_settings(WIKI_GROUP_MODEL="auth.Group")
|
||||
def test_setting(self):
|
||||
self.assertEqual(wiki_settings.GROUP_MODEL, "auth.Group")
|
||||
|
||||
def test_custom(self):
|
||||
self.assertEqual(Group, CustomGroup)
|
||||
self.assertEqual(wiki_settings.GROUP_MODEL, "testdata.CustomGroup")
|
||||
|
||||
|
||||
class LineEndingsTests(TestCase):
|
||||
def test_manager(self):
|
||||
article = Article()
|
||||
article.add_revision(
|
||||
ArticleRevision(title="Root", content="Hello\nworld"), save=True
|
||||
)
|
||||
self.assertEqual("Hello\r\nworld", article.current_revision.content)
|
||||
|
||||
|
||||
class HttpTests(TestCase):
|
||||
def test_send_file(self):
|
||||
fabricate_request = self.client.get("/").wsgi_request
|
||||
fobject = tempfile.NamedTemporaryFile("r")
|
||||
response = send_file(
|
||||
fabricate_request, fobject.name, filename="test.pdf"
|
||||
)
|
||||
assert response.has_header("Content-Disposition")
|
||||
assert "inline" in response.get("Content-Disposition")
|
||||
response = send_file(
|
||||
fabricate_request, fobject.name, filename="test.jpeg"
|
||||
)
|
||||
assert response.has_header("Content-Disposition")
|
||||
response = send_file(
|
||||
fabricate_request,
|
||||
fobject.name,
|
||||
filename="test.jpeg",
|
||||
last_modified=datetime.now(),
|
||||
)
|
||||
assert response.has_header("Content-Disposition")
|
||||
fobject.close()
|
||||
115
tests/core/test_checks.py
Normal file
115
tests/core/test_checks.py
Normal file
@@ -0,0 +1,115 @@
|
||||
import copy
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.checks import Error
|
||||
from django.core.checks import registry
|
||||
from django.test import TestCase
|
||||
from wiki.checks import FIELDS_IN_CUSTOM_USER_MODEL
|
||||
from wiki.checks import REQUIRED_CONTEXT_PROCESSORS
|
||||
from wiki.checks import REQUIRED_INSTALLED_APPS
|
||||
from wiki.checks import Tags
|
||||
|
||||
from ..base import wiki_override_settings
|
||||
|
||||
|
||||
def _remove(settings, arg):
|
||||
return [setting for setting in settings if not setting.startswith(arg)]
|
||||
|
||||
|
||||
class CheckTests(TestCase):
|
||||
def test_required_installed_apps(self):
|
||||
for app in REQUIRED_INSTALLED_APPS:
|
||||
with self.settings(
|
||||
INSTALLED_APPS=_remove(settings.INSTALLED_APPS, app[0])
|
||||
):
|
||||
errors = registry.run_checks(
|
||||
tags=[Tags.required_installed_apps]
|
||||
)
|
||||
expected_errors = [
|
||||
Error(
|
||||
"needs %s in INSTALLED_APPS" % app[1],
|
||||
id="wiki.%s" % app[2],
|
||||
)
|
||||
]
|
||||
self.assertEqual(errors, expected_errors)
|
||||
|
||||
def test_required_context_processors(self):
|
||||
for context_processor in REQUIRED_CONTEXT_PROCESSORS:
|
||||
TEMPLATES = copy.deepcopy(settings.TEMPLATES)
|
||||
TEMPLATES[0]["OPTIONS"]["context_processors"] = [
|
||||
cp
|
||||
for cp in TEMPLATES[0]["OPTIONS"]["context_processors"]
|
||||
if cp != context_processor[0]
|
||||
]
|
||||
with self.settings(TEMPLATES=TEMPLATES):
|
||||
errors = registry.run_checks(tags=[Tags.context_processors])
|
||||
expected_errors = [
|
||||
Error(
|
||||
"needs %s in TEMPLATES[*]['OPTIONS']['context_processors']"
|
||||
% context_processor[0],
|
||||
id="wiki.%s" % context_processor[1],
|
||||
)
|
||||
]
|
||||
self.assertEqual(errors, expected_errors)
|
||||
|
||||
def test_custom_user_model_mitigation_required(self):
|
||||
"""
|
||||
Django check django.forms.ModelForm.Meta on definition, and raises an error if Meta.fields don't exist in Meta.model.
|
||||
This causes problems in wiki.forms.UserCreationForm and wiki.forms.UserUpdateForm when a custom user model doesn't have fields django-wiki assumes.
|
||||
There is some code in wiki.forms that detects this situation.
|
||||
This check asserts that Django are still raising an exception on definition, and asserts the mitigation code in wiki.forms,
|
||||
and that test_check_for_fields_in_custom_user_model below are required.
|
||||
"""
|
||||
from django.core.exceptions import FieldError
|
||||
from django import forms
|
||||
from ..testdata.models import VeryCustomUser
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
FieldError,
|
||||
"Unknown field\\(s\\) \\((email|username|, )+\\) specified for VeryCustomUser",
|
||||
):
|
||||
|
||||
class UserUpdateForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = VeryCustomUser
|
||||
fields = ["username", "email"]
|
||||
|
||||
def test_check_for_fields_in_custom_user_model(self):
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
with wiki_override_settings(
|
||||
WIKI_ACCOUNT_HANDLING=False,
|
||||
AUTH_USER_MODEL="testdata.VeryCustomUser",
|
||||
):
|
||||
errors = registry.run_checks(
|
||||
tags=[Tags.fields_in_custom_user_model]
|
||||
)
|
||||
self.assertEqual(errors, [])
|
||||
with wiki_override_settings(
|
||||
WIKI_ACCOUNT_HANDLING=True,
|
||||
AUTH_USER_MODEL="testdata.VeryCustomUser",
|
||||
):
|
||||
errors = registry.run_checks(
|
||||
tags=[Tags.fields_in_custom_user_model]
|
||||
)
|
||||
expected_errors = [
|
||||
Error(
|
||||
"%s.%s.%s refers to a field that is not of type %s"
|
||||
% (
|
||||
get_user_model().__module__,
|
||||
get_user_model().__name__,
|
||||
field_fetcher,
|
||||
required_field_type,
|
||||
),
|
||||
hint="If you have your own login/logout views, turn off settings.WIKI_ACCOUNT_HANDLING",
|
||||
obj=get_user_model(),
|
||||
id="wiki.%s" % error_code,
|
||||
)
|
||||
for check_function_name, field_fetcher, required_field_type, error_code in FIELDS_IN_CUSTOM_USER_MODEL
|
||||
]
|
||||
self.assertEqual(errors, expected_errors)
|
||||
with wiki_override_settings(WIKI_ACCOUNT_HANDLING=True):
|
||||
errors = registry.run_checks(
|
||||
tags=[Tags.fields_in_custom_user_model]
|
||||
)
|
||||
self.assertEqual(errors, [])
|
||||
29
tests/core/test_commands.py
Normal file
29
tests/core/test_commands.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from django.core.management import call_command
|
||||
|
||||
from ..base import ArticleTestBase
|
||||
|
||||
|
||||
class TestManagementCommands(ArticleTestBase):
|
||||
"""
|
||||
This clever test case can be inherited in other plugins
|
||||
|
||||
Some data is created with ArticleTestBase, use that.
|
||||
"""
|
||||
|
||||
def test_dumpdata_loaddata(self):
|
||||
sysout = sys.stdout
|
||||
fixtures_file = tempfile.NamedTemporaryFile(
|
||||
"w", delete=False, suffix=".json"
|
||||
)
|
||||
sys.stdout = fixtures_file
|
||||
call_command("dumpdata", "wiki")
|
||||
fixtures_file.file.flush()
|
||||
fixtures_file.file.close()
|
||||
sys.stdout = open(os.devnull, "w")
|
||||
call_command("loaddata", fixtures_file.name)
|
||||
sys.stdout = sysout
|
||||
os.unlink(fixtures_file.name)
|
||||
81
tests/core/test_editor.py
Normal file
81
tests/core/test_editor.py
Normal file
@@ -0,0 +1,81 @@
|
||||
import wiki.editors
|
||||
from django.forms import Textarea
|
||||
from wiki.editors.base import BaseEditor
|
||||
|
||||
from ..base import RequireRootArticleMixin
|
||||
from ..base import WebTestBase
|
||||
from ..base import wiki_override_settings
|
||||
|
||||
|
||||
class CustomEditor(BaseEditor):
|
||||
def get_widget(self, revision=None):
|
||||
return Textarea(attrs={"data-revision": revision.pk})
|
||||
|
||||
def get_admin_widget(self, revision=None):
|
||||
return Textarea(attrs={"data-revision": revision.pk})
|
||||
|
||||
|
||||
class EditorTest(RequireRootArticleMixin, WebTestBase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# reset the cached editor class and instance
|
||||
wiki.editors._editor, wiki.editors._EditorClass = None, None
|
||||
|
||||
def test_editor_widget_markitup(self):
|
||||
response = self.get_url("wiki:edit", path="")
|
||||
self.assertContains(
|
||||
response,
|
||||
'<textarea name="content" class="markItUp" rows="10" cols="40" id="id_content">',
|
||||
)
|
||||
|
||||
def test_admin_widget_markitup(self):
|
||||
response = self.get_url(
|
||||
"admin:wiki_articlerevision_change",
|
||||
object_id=self.root_article.current_revision.id,
|
||||
)
|
||||
self.assertContains(
|
||||
response,
|
||||
'<textarea name="content" class="markItUp" rows="10" cols="40" id="id_content">',
|
||||
)
|
||||
|
||||
@wiki_override_settings(WIKI_EDITOR="wiki.editors.base.BaseEditor")
|
||||
def test_editor_widget_base(self):
|
||||
response = self.get_url("wiki:edit", path="")
|
||||
self.assertContains(
|
||||
response,
|
||||
'<textarea name="content" cols="40" rows="10" id="id_content">',
|
||||
)
|
||||
|
||||
@wiki_override_settings(WIKI_EDITOR="wiki.editors.base.BaseEditor")
|
||||
def test_admin_widget_base(self):
|
||||
response = self.get_url(
|
||||
"admin:wiki_articlerevision_change",
|
||||
object_id=self.root_article.current_revision.id,
|
||||
)
|
||||
self.assertContains(
|
||||
response,
|
||||
'<textarea name="content" cols="40" rows="10" id="id_content">',
|
||||
)
|
||||
|
||||
@wiki_override_settings(WIKI_EDITOR="tests.core.test_editor.CustomEditor")
|
||||
def test_editor_widget_custom(self):
|
||||
response = self.get_url("wiki:edit", path="")
|
||||
self.assertContains(
|
||||
response,
|
||||
'<textarea name="content" cols="40" rows="10" data-revision="{}" id="id_content">'.format(
|
||||
self.root_article.current_revision.id
|
||||
),
|
||||
)
|
||||
|
||||
@wiki_override_settings(WIKI_EDITOR="tests.core.test_editor.CustomEditor")
|
||||
def test_admin_widget_custom(self):
|
||||
response = self.get_url(
|
||||
"admin:wiki_articlerevision_change",
|
||||
object_id=self.root_article.current_revision.id,
|
||||
)
|
||||
self.assertContains(
|
||||
response,
|
||||
'<textarea name="content" cols="40" rows="10" data-revision="{}" id="id_content">'.format(
|
||||
self.root_article.current_revision.id
|
||||
),
|
||||
)
|
||||
39
tests/core/test_forms.py
Normal file
39
tests/core/test_forms.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from django.test import TestCase
|
||||
from django.utils.translation import gettext
|
||||
from wiki.forms import DeleteForm
|
||||
from wiki.forms import UserCreationForm
|
||||
|
||||
from tests.base import DjangoClientTestBase
|
||||
from tests.base import RequireRootArticleMixin
|
||||
|
||||
|
||||
class DeleteFormTests(RequireRootArticleMixin, DjangoClientTestBase):
|
||||
def test_not_sure(self):
|
||||
data = {"purge": True, "confirm": False}
|
||||
form = DeleteForm(
|
||||
article=self.root_article, has_children=True, data=data
|
||||
)
|
||||
self.assertIs(form.is_valid(), False)
|
||||
self.assertEqual(
|
||||
form.errors["__all__"], [gettext("You are not sure enough!")]
|
||||
)
|
||||
|
||||
|
||||
class UserCreationFormTests(TestCase):
|
||||
def test_honeypot(self):
|
||||
data = {
|
||||
"address": "Wiki Road 123",
|
||||
"phone": "12345678",
|
||||
"email": "wiki@wiki.com",
|
||||
"username": "WikiMan",
|
||||
"password1": "R@ndomString",
|
||||
"password2": "R@ndomString",
|
||||
}
|
||||
form = UserCreationForm(data=data)
|
||||
self.assertIs(form.is_valid(), False)
|
||||
self.assertEqual(
|
||||
form.errors["__all__"],
|
||||
[
|
||||
"Thank you, non-human visitor. Please keep trying to fill in the form."
|
||||
],
|
||||
)
|
||||
81
tests/core/test_managers.py
Normal file
81
tests/core/test_managers.py
Normal file
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
Tests that the custom queryset methods work, this is important
|
||||
because the pattern of building them is different from Django
|
||||
1.5 to 1.6 to 1.7 so there will be 3 patterns in play at the
|
||||
same time.
|
||||
"""
|
||||
from wiki.models import Article
|
||||
from wiki.models import URLPath
|
||||
from wiki.plugins.attachments.models import Attachment
|
||||
|
||||
from ..base import ArticleTestBase
|
||||
|
||||
|
||||
class ArticleManagerTests(ArticleTestBase):
|
||||
def test_queryset_methods_directly_on_manager(self):
|
||||
self.assertEqual(Article.objects.can_read(self.superuser1).count(), 1)
|
||||
self.assertEqual(Article.objects.can_write(self.superuser1).count(), 1)
|
||||
self.assertEqual(Article.objects.active().count(), 1)
|
||||
|
||||
def test_mass_deletion(self):
|
||||
"""
|
||||
https://github.com/django-wiki/django-wiki/issues/857
|
||||
"""
|
||||
Article.objects.all().delete()
|
||||
self.assertEqual(Article.objects.all().count(), 0)
|
||||
|
||||
def test_queryset_methods_on_querysets(self):
|
||||
self.assertEqual(
|
||||
Article.objects.all().can_read(self.superuser1).count(), 1
|
||||
)
|
||||
self.assertEqual(
|
||||
Article.objects.all().can_write(self.superuser1).count(), 1
|
||||
)
|
||||
self.assertEqual(Article.objects.all().active().count(), 1)
|
||||
|
||||
# See: https://code.djangoproject.com/ticket/22817
|
||||
def test_queryset_empty_querysets(self):
|
||||
self.assertEqual(
|
||||
Article.objects.none().can_read(self.superuser1).count(), 0
|
||||
)
|
||||
self.assertEqual(
|
||||
Article.objects.none().can_write(self.superuser1).count(), 0
|
||||
)
|
||||
self.assertEqual(Article.objects.none().active().count(), 0)
|
||||
|
||||
|
||||
class AttachmentManagerTests(ArticleTestBase):
|
||||
def test_queryset_methods_directly_on_manager(self):
|
||||
# Do the same for Attachment which uses ArtickeFkManager
|
||||
self.assertEqual(
|
||||
Attachment.objects.can_read(self.superuser1).count(), 0
|
||||
)
|
||||
self.assertEqual(
|
||||
Attachment.objects.can_write(self.superuser1).count(), 0
|
||||
)
|
||||
self.assertEqual(Attachment.objects.active().count(), 0)
|
||||
|
||||
def test_queryset_methods_on_querysets(self):
|
||||
self.assertEqual(
|
||||
Attachment.objects.all().can_read(self.superuser1).count(), 0
|
||||
)
|
||||
self.assertEqual(
|
||||
Attachment.objects.all().can_write(self.superuser1).count(), 0
|
||||
)
|
||||
self.assertEqual(Attachment.objects.all().active().count(), 0)
|
||||
|
||||
# See: https://code.djangoproject.com/ticket/22817
|
||||
def test_queryset_empty_query_sets(self):
|
||||
self.assertEqual(
|
||||
Attachment.objects.none().can_read(self.superuser1).count(), 0
|
||||
)
|
||||
self.assertEqual(
|
||||
Attachment.objects.none().can_write(self.superuser1).count(), 0
|
||||
)
|
||||
self.assertEqual(Attachment.objects.none().active().count(), 0)
|
||||
|
||||
|
||||
class URLPathManagerTests(ArticleTestBase):
|
||||
def test_related_manager_works_with_filters(self):
|
||||
root = URLPath.root()
|
||||
self.assertNotIn(root.id, [p.id for p in root.children.active()])
|
||||
127
tests/core/test_markdown.py
Normal file
127
tests/core/test_markdown.py
Normal file
@@ -0,0 +1,127 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
import markdown
|
||||
from django.test import TestCase
|
||||
from wiki.core.markdown import ArticleMarkdown
|
||||
from wiki.core.markdown.mdx.codehilite import WikiCodeHiliteExtension
|
||||
from wiki.core.markdown.mdx.responsivetable import ResponsiveTableExtension
|
||||
from wiki.models import URLPath
|
||||
|
||||
from ..base import ArticleTestBase
|
||||
|
||||
try:
|
||||
import pygments
|
||||
|
||||
pygments = True # NOQA
|
||||
except ImportError:
|
||||
pygments = False
|
||||
|
||||
|
||||
class ArticleMarkdownTests(ArticleTestBase):
|
||||
@patch("wiki.core.markdown.settings")
|
||||
def test_do_not_modify_extensions(self, settings):
|
||||
extensions = ["footnotes", "attr_list", "sane_lists"]
|
||||
settings.MARKDOWN_KWARGS = {"extensions": extensions}
|
||||
number_of_extensions = len(extensions)
|
||||
ArticleMarkdown(None)
|
||||
self.assertEqual(len(extensions), number_of_extensions)
|
||||
|
||||
def test_html_removal(self):
|
||||
urlpath = URLPath.create_urlpath(
|
||||
self.root,
|
||||
"html_removal",
|
||||
title="Test 1",
|
||||
content="</html>only_this",
|
||||
)
|
||||
|
||||
self.assertEqual(urlpath.article.render(), "<p>only_this</p>")
|
||||
|
||||
|
||||
class ResponsiveTableExtensionTests(TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.md = markdown.Markdown(
|
||||
extensions=["extra", ResponsiveTableExtension()]
|
||||
)
|
||||
self.md_without = markdown.Markdown(extensions=["extra"])
|
||||
|
||||
def test_wrapping(self):
|
||||
text = "|th|th|\n|--|--|\n|td|td|"
|
||||
expected = (
|
||||
'<div class="table-responsive">\n'
|
||||
+ self.md_without.convert(text)
|
||||
+ "\n</div>"
|
||||
)
|
||||
self.assertEqual(self.md.convert(text), expected)
|
||||
|
||||
|
||||
class CodehiliteTests(TestCase):
|
||||
def test_fenced_code(self):
|
||||
md = markdown.Markdown(extensions=["extra", WikiCodeHiliteExtension()])
|
||||
text = (
|
||||
"Code:\n"
|
||||
"\n"
|
||||
"```python\n"
|
||||
"echo 'line 1'\n"
|
||||
"echo 'line 2'\n"
|
||||
"```\n"
|
||||
)
|
||||
result = (
|
||||
(
|
||||
"""<p>Code:</p>\n"""
|
||||
"""<div class="codehilite-wrap"><div class="codehilite"><pre><span></span><span class="n">echo</span> <span class="s1">'line 1'</span>\n"""
|
||||
"""<span class="n">echo</span> <span class="s1">'line 2'</span>\n"""
|
||||
"""</pre></div>\n"""
|
||||
"""</div>"""
|
||||
)
|
||||
if pygments
|
||||
else (
|
||||
"""<p>Code:</p>\n"""
|
||||
"""<div class="codehilite-wrap"><pre class="codehilite"><code class="language-python">echo 'line 1'\n"""
|
||||
"""echo 'line 2'\n</code></pre>\n"""
|
||||
"""</div>"""
|
||||
)
|
||||
)
|
||||
self.assertEqual(
|
||||
md.convert(text),
|
||||
result,
|
||||
)
|
||||
|
||||
def test_indented_code(self):
|
||||
md = markdown.Markdown(extensions=["extra", WikiCodeHiliteExtension()])
|
||||
text = (
|
||||
"Code:\n"
|
||||
"\n"
|
||||
" #!/usr/bin/python\n"
|
||||
" print('line 1')\n"
|
||||
" print('line 2')\n"
|
||||
" print('æøå')\n"
|
||||
"\n"
|
||||
)
|
||||
result = (
|
||||
(
|
||||
"""<p>Code:</p>\n"""
|
||||
"""<div class="codehilite-wrap"><table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1\n"""
|
||||
"""2\n"""
|
||||
"""3\n"""
|
||||
"""4</pre></div></td><td class="code"><div class="codehilite"><pre><span></span><span class="ch">#!/usr/bin/python</span>\n"""
|
||||
"""<span class="k">print</span><span class="p">(</span><span class="s1">'line 1'</span><span class="p">)</span>\n"""
|
||||
"""<span class="k">print</span><span class="p">(</span><span class="s1">'line 2'</span><span class="p">)</span>\n"""
|
||||
"""<span class="k">print</span><span class="p">(</span><span class="s1">'æøå'</span><span class="p">)</span>\n"""
|
||||
"""</pre></div>\n"""
|
||||
"""</td></tr></table></div>"""
|
||||
)
|
||||
if pygments
|
||||
else (
|
||||
"""<p>Code:</p>\n"""
|
||||
"""<div class="codehilite-wrap"><pre class="codehilite"><code class="language-python linenums">#!/usr/bin/python\n"""
|
||||
"""print('line 1')\n"""
|
||||
"""print('line 2')\n"""
|
||||
"""print('æøå')\n</code></pre>\n"""
|
||||
"""</div>"""
|
||||
)
|
||||
)
|
||||
self.assertEqual(
|
||||
md.convert(text),
|
||||
result,
|
||||
)
|
||||
147
tests/core/test_models.py
Normal file
147
tests/core/test_models.py
Normal file
@@ -0,0 +1,147 @@
|
||||
from django.apps import apps
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.sites.models import Site
|
||||
from django.test.testcases import TestCase
|
||||
from django.urls import re_path
|
||||
from wiki.conf import settings
|
||||
from wiki.managers import ArticleManager
|
||||
from wiki.models import Article
|
||||
from wiki.models import ArticleRevision
|
||||
from wiki.models import URLPath
|
||||
from wiki.urls import WikiURLPatterns
|
||||
|
||||
User = get_user_model()
|
||||
Group = apps.get_model(settings.GROUP_MODEL)
|
||||
|
||||
|
||||
class WikiCustomUrlPatterns(WikiURLPatterns):
|
||||
def get_article_urls(self):
|
||||
urlpatterns = [
|
||||
re_path(
|
||||
"^my-wiki/(?P<article_id>[0-9]+)/$",
|
||||
self.article_view_class.as_view(),
|
||||
name="get",
|
||||
),
|
||||
]
|
||||
return urlpatterns
|
||||
|
||||
def get_article_path_urls(self):
|
||||
urlpatterns = [
|
||||
re_path(
|
||||
"^my-wiki/(?P<path>.+/|)$",
|
||||
self.article_view_class.as_view(),
|
||||
name="get",
|
||||
),
|
||||
]
|
||||
return urlpatterns
|
||||
|
||||
|
||||
class ArticleModelTest(TestCase):
|
||||
def test_default_fields_of_empty_article(self):
|
||||
a = Article.objects.create()
|
||||
|
||||
self.assertIsNone(a.current_revision)
|
||||
self.assertIsNone(a.owner)
|
||||
self.assertIsNone(a.group)
|
||||
|
||||
self.assertIsNotNone(a.created)
|
||||
self.assertIsNotNone(a.modified)
|
||||
|
||||
self.assertIsNotNone(a.group_read)
|
||||
self.assertIsNotNone(a.group_write)
|
||||
self.assertIsNotNone(a.other_read)
|
||||
self.assertIsNotNone(a.other_write)
|
||||
|
||||
# XXX maybe redundant test
|
||||
def test_model_manager_class(self):
|
||||
self.assertIsInstance(Article.objects, ArticleManager)
|
||||
|
||||
def test_str_method_if_have_current_revision(self):
|
||||
title = "Test title"
|
||||
|
||||
a = Article.objects.create()
|
||||
ArticleRevision.objects.create(article=a, title=title)
|
||||
|
||||
self.assertEqual(str(a), title)
|
||||
|
||||
def test_str_method_if_dont_have_current_revision(self):
|
||||
a = Article.objects.create()
|
||||
|
||||
expected = "Article without content (1)"
|
||||
|
||||
self.assertEqual(str(a), expected)
|
||||
|
||||
def test_get_absolute_url_if_urlpath_set_is_exists(self):
|
||||
a1 = Article.objects.create()
|
||||
s1 = Site.objects.create(domain="something.com", name="something.com")
|
||||
u1 = URLPath.objects.create(article=a1, site=s1)
|
||||
|
||||
a2 = Article.objects.create()
|
||||
s2 = Site.objects.create(
|
||||
domain="somethingelse.com", name="somethingelse.com"
|
||||
)
|
||||
URLPath.objects.create(
|
||||
article=a2, site=s2, parent=u1, slug="test_slug"
|
||||
)
|
||||
|
||||
url = a2.get_absolute_url()
|
||||
|
||||
expected = "/test_slug/"
|
||||
|
||||
self.assertEqual(url, expected)
|
||||
|
||||
def test_get_absolute_url_if_urlpath_set_is_not_exists(self):
|
||||
a = Article.objects.create()
|
||||
|
||||
url = a.get_absolute_url()
|
||||
|
||||
expected = "/1/"
|
||||
|
||||
self.assertEqual(url, expected)
|
||||
|
||||
def test_article_is_related_to_articlerevision(self):
|
||||
title = "Test title"
|
||||
|
||||
a = Article.objects.create()
|
||||
r = ArticleRevision.objects.create(article=a, title=title)
|
||||
|
||||
self.assertEqual(r.article, a)
|
||||
self.assertIn(r, a.articlerevision_set.all())
|
||||
|
||||
def test_article_is_related_to_owner(self):
|
||||
u = User.objects.create(username="Noman", password="pass")
|
||||
a = Article.objects.create(owner=u)
|
||||
|
||||
self.assertEqual(a.owner, u)
|
||||
self.assertIn(a, u.owned_articles.all())
|
||||
|
||||
def test_article_is_related_to_group(self):
|
||||
g = Group.objects.create()
|
||||
a = Article.objects.create(group=g)
|
||||
|
||||
self.assertEqual(a.group, g)
|
||||
self.assertIn(a, g.article_set.all())
|
||||
|
||||
def test_cache(self):
|
||||
a = Article.objects.create()
|
||||
ArticleRevision.objects.create(
|
||||
article=a, title="test", content="# header"
|
||||
)
|
||||
expected = """<h1 id="wiki-toc-header">header""" """.*</h1>"""
|
||||
# cached content does not exist yet. this will create it
|
||||
self.assertRegex(a.get_cached_content(), expected)
|
||||
# actual cached content test
|
||||
self.assertRegex(a.get_cached_content(), expected)
|
||||
|
||||
def test_articlerevision_presave_signals(self):
|
||||
a = Article.objects.create()
|
||||
ar1 = ArticleRevision(article=a, title="revision1")
|
||||
|
||||
a.add_revision(ar1)
|
||||
self.assertEqual(ar1, a.current_revision)
|
||||
|
||||
ar2 = ArticleRevision(article=a, title="revision2")
|
||||
|
||||
ar2.save()
|
||||
|
||||
self.assertEqual(ar2.previous_revision, ar1)
|
||||
39
tests/core/test_paginator.py
Normal file
39
tests/core/test_paginator.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from django.test import TestCase
|
||||
from wiki.core.paginator import WikiPaginator
|
||||
|
||||
|
||||
class PaginatorTest(TestCase):
|
||||
"""
|
||||
Test the WikiPaginator and it's page_range() function
|
||||
"""
|
||||
|
||||
def test_paginator(self):
|
||||
objects = [1]
|
||||
p = WikiPaginator(objects, 2, side_pages=2)
|
||||
self.assertEqual(p.num_pages, 1)
|
||||
|
||||
p.page(1)
|
||||
self.assertEqual(p.page_range, [1])
|
||||
|
||||
objects = [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
p = WikiPaginator(objects, 2, side_pages=2)
|
||||
self.assertEqual(p.num_pages, 5)
|
||||
|
||||
p.page(1)
|
||||
self.assertEqual(p.page_range, [1, 2, 3, 0, 5])
|
||||
|
||||
p.page(3)
|
||||
self.assertEqual(p.page_range, [1, 2, 3, 4, 5])
|
||||
|
||||
objects = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
|
||||
p = WikiPaginator(objects, 2, side_pages=2)
|
||||
self.assertEqual(p.num_pages, 9)
|
||||
|
||||
p.page(1)
|
||||
self.assertEqual(p.page_range, [1, 2, 3, 0, 9])
|
||||
|
||||
p.page(5)
|
||||
self.assertEqual(p.page_range, [1, 0, 3, 4, 5, 6, 7, 0, 9])
|
||||
|
||||
p.page(8)
|
||||
self.assertEqual(p.page_range, [1, 0, 6, 7, 8, 9])
|
||||
106
tests/core/test_sites.py
Normal file
106
tests/core/test_sites.py
Normal file
@@ -0,0 +1,106 @@
|
||||
from importlib import reload
|
||||
|
||||
from django.contrib.sites.models import Site
|
||||
from django.test.testcases import TestCase
|
||||
from django.urls import include
|
||||
from django.urls import re_path
|
||||
from wiki import sites
|
||||
from wiki import urls
|
||||
from wiki.apps import WikiConfig
|
||||
from wiki.models import Article
|
||||
from wiki.models import URLPath
|
||||
|
||||
from ..base import wiki_override_settings
|
||||
|
||||
|
||||
class WikiCustomSite(sites.WikiSite):
|
||||
def get_article_urls(self):
|
||||
urlpatterns = [
|
||||
re_path(
|
||||
"^some-prefix/(?P<article_id>[0-9]+)/$",
|
||||
self.article_view,
|
||||
name="get",
|
||||
),
|
||||
]
|
||||
return urlpatterns
|
||||
|
||||
def get_article_path_urls(self):
|
||||
urlpatterns = [
|
||||
re_path(
|
||||
"^some-other-prefix/(?P<path>.+/|)$",
|
||||
self.article_view,
|
||||
name="get",
|
||||
),
|
||||
]
|
||||
return urlpatterns
|
||||
|
||||
|
||||
class WikiCustomConfig(WikiConfig):
|
||||
default_site = "tests.core.test_sites.WikiCustomSite"
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r"^notify/", include("django_nyt.urls")),
|
||||
re_path(r"", include("wiki.urls")),
|
||||
]
|
||||
|
||||
|
||||
@wiki_override_settings(
|
||||
INSTALLED_APPS=[
|
||||
"tests.testdata",
|
||||
"django.contrib.auth.apps.AuthConfig",
|
||||
"django.contrib.contenttypes.apps.ContentTypesConfig",
|
||||
"django.contrib.sessions.apps.SessionsConfig",
|
||||
"django.contrib.admin.apps.AdminConfig",
|
||||
"django.contrib.humanize.apps.HumanizeConfig",
|
||||
"django.contrib.sites.apps.SitesConfig",
|
||||
"django_nyt.apps.DjangoNytConfig",
|
||||
"mptt",
|
||||
"sekizai",
|
||||
"sorl.thumbnail",
|
||||
"tests.core.test_sites.WikiCustomConfig",
|
||||
"wiki.plugins.attachments.apps.AttachmentsConfig",
|
||||
"wiki.plugins.notifications.apps.NotificationsConfig",
|
||||
"wiki.plugins.images.apps.ImagesConfig",
|
||||
"wiki.plugins.macros.apps.MacrosConfig",
|
||||
"wiki.plugins.globalhistory.apps.GlobalHistoryConfig",
|
||||
],
|
||||
ROOT_URLCONF="tests.core.test_sites",
|
||||
)
|
||||
class CustomWikiSiteTest(TestCase):
|
||||
def setUp(self):
|
||||
# Reload wiki.urls since it may have already been instantiated by another test app.
|
||||
self._old_site = sites.site
|
||||
sites.site = sites.DefaultWikiSite()
|
||||
reload(urls)
|
||||
|
||||
def tearDown(self):
|
||||
sites.site = self._old_site
|
||||
reload(urls)
|
||||
|
||||
def test_use_custom_wiki_site(self):
|
||||
self.assertEqual(sites.site.__class__.__name__, "WikiCustomSite")
|
||||
|
||||
def test_get_absolute_url_if_urlpath_set_is_not_exists__no_root_urlconf(
|
||||
self
|
||||
):
|
||||
a = Article.objects.create()
|
||||
|
||||
self.assertEqual(a.get_absolute_url(), "/some-prefix/1/")
|
||||
|
||||
def test_get_absolute_url_if_urlpath_set_is_exists__no_root_urlconf(self):
|
||||
a1 = Article.objects.create()
|
||||
s1 = Site.objects.create(domain="something.com", name="something.com")
|
||||
u1 = URLPath.objects.create(article=a1, site=s1)
|
||||
|
||||
a2 = Article.objects.create()
|
||||
s2 = Site.objects.create(
|
||||
domain="somethingelse.com", name="somethingelse.com"
|
||||
)
|
||||
URLPath.objects.create(
|
||||
article=a2, site=s2, parent=u1, slug="test_slug"
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
a2.get_absolute_url(), "/some-other-prefix/test_slug/"
|
||||
)
|
||||
368
tests/core/test_template_filters.py
Normal file
368
tests/core/test_template_filters.py
Normal file
@@ -0,0 +1,368 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from wiki.models import Article
|
||||
from wiki.models import ArticleRevision
|
||||
from wiki.templatetags.wiki_tags import can_delete
|
||||
from wiki.templatetags.wiki_tags import can_moderate
|
||||
from wiki.templatetags.wiki_tags import can_read
|
||||
from wiki.templatetags.wiki_tags import can_write
|
||||
from wiki.templatetags.wiki_tags import get_content_snippet
|
||||
from wiki.templatetags.wiki_tags import is_locked
|
||||
|
||||
from ..base import TemplateTestCase
|
||||
from ..base import wiki_override_settings
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class GetContentSnippet(TemplateTestCase):
|
||||
template = """
|
||||
{% load wiki_tags %}
|
||||
{{ some_content|get_content_snippet:"keyword, max_words" }}
|
||||
"""
|
||||
|
||||
def test_keyword_at_the_end_of_the_content(self):
|
||||
text = "lorem " * 80
|
||||
content = text + " list"
|
||||
expected = (
|
||||
"lorem lorem lorem lorem lorem lorem lorem lorem lorem "
|
||||
"lorem lorem lorem lorem lorem lorem <strong>list</strong>"
|
||||
)
|
||||
|
||||
output = get_content_snippet(content, "list")
|
||||
|
||||
self.assertEqual(output, expected)
|
||||
|
||||
def test_keyword_at_the_beginning_of_the_content(self):
|
||||
text = "lorem " * 80
|
||||
content = "list " + text
|
||||
expected = (
|
||||
"<strong>list</strong> lorem lorem lorem lorem lorem "
|
||||
"lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem "
|
||||
"lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem "
|
||||
"lorem lorem lorem"
|
||||
)
|
||||
|
||||
output = get_content_snippet(content, "list")
|
||||
|
||||
self.assertEqual(output, expected)
|
||||
|
||||
def test_whole_content_consists_of_keywords(self):
|
||||
content = "lorem " * 80
|
||||
expected = "<strong>lorem</strong>" + 30 * " <strong>lorem</strong>"
|
||||
|
||||
output = get_content_snippet(content, "lorem")
|
||||
|
||||
self.assertEqual(output, expected)
|
||||
|
||||
def test_keyword_is_not_in_a_content(self):
|
||||
content = "lorem " * 80
|
||||
expected = (
|
||||
"lorem lorem lorem lorem lorem lorem lorem lorem lorem "
|
||||
"lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem "
|
||||
"lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem lorem"
|
||||
)
|
||||
|
||||
output = get_content_snippet(content, "list")
|
||||
|
||||
self.assertEqual(output, expected)
|
||||
|
||||
def test_a_few_keywords_in_content(self):
|
||||
text = "lorem " * 80
|
||||
content = "list " + text
|
||||
|
||||
text = "ipsum " * 80
|
||||
content += text + " list "
|
||||
|
||||
text = "dolorum " * 80
|
||||
content += text + " list"
|
||||
|
||||
expected = "<strong>list</strong>" + 30 * " lorem"
|
||||
|
||||
output = get_content_snippet(content, "list")
|
||||
|
||||
self.assertEqual(output, expected)
|
||||
|
||||
# XXX bug or feature?
|
||||
def test_keyword_is_in_content_and_max_words_is_zero(self):
|
||||
text = "spam " * 800
|
||||
content = text + " list"
|
||||
|
||||
output = get_content_snippet(content, "list", 0)
|
||||
expected = "spam " * 800 + "<strong>list</strong>"
|
||||
|
||||
self.assertEqual(output, expected)
|
||||
|
||||
# XXX bug or feature?
|
||||
def test_keyword_is_in_content_and_max_words_is_negative(self):
|
||||
text = "spam " * 80
|
||||
content = text + " list"
|
||||
|
||||
output = get_content_snippet(content, "list", -10)
|
||||
expected = "spam " * 75 + "<strong>list</strong>"
|
||||
|
||||
self.assertEqual(output, expected)
|
||||
|
||||
# XXX bug or feature?
|
||||
def test_keyword_is_not_in_content_and_max_words_is_zero(self):
|
||||
content = "spam " * 15
|
||||
|
||||
output = get_content_snippet(content, "list", 0)
|
||||
expected = ""
|
||||
|
||||
self.assertEqual(output, expected)
|
||||
|
||||
# XXX bug or feature?
|
||||
def test_keyword_is_not_in_content_and_max_words_is_negative(self):
|
||||
content = "spam " * 15
|
||||
|
||||
output = get_content_snippet(content, "list", -10)
|
||||
expected = "spam spam spam spam spam"
|
||||
|
||||
self.assertEqual(output, expected)
|
||||
|
||||
def test_no_content(self):
|
||||
content = ""
|
||||
|
||||
output = get_content_snippet(content, "list")
|
||||
|
||||
self.assertEqual(output, "")
|
||||
|
||||
content = " "
|
||||
|
||||
output = get_content_snippet(content, "list")
|
||||
|
||||
self.assertEqual(output, "")
|
||||
|
||||
def test_strip_tags(self):
|
||||
keyword = "maybe"
|
||||
|
||||
content = """
|
||||
I should citate Shakespeare or Byron.
|
||||
Or <a>maybe</a> copy paste from <a href="http://python.org">python</a>
|
||||
or django documentation. Maybe.
|
||||
"""
|
||||
|
||||
expected = (
|
||||
"I should citate Shakespeare or Byron. "
|
||||
"Or <strong>maybe</strong> copy paste from python "
|
||||
"or django documentation. <strong>Maybe.</strong>"
|
||||
)
|
||||
|
||||
output = get_content_snippet(content, keyword, 30)
|
||||
|
||||
self.assertEqual(output, expected)
|
||||
|
||||
def test_max_words_arg(self):
|
||||
keyword = "eggs"
|
||||
|
||||
content = """
|
||||
knight eggs spam ham eggs guido python eggs circus
|
||||
"""
|
||||
expected = (
|
||||
"knight <strong>eggs</strong> spam ham "
|
||||
"<strong>eggs</strong> guido"
|
||||
)
|
||||
|
||||
output = get_content_snippet(content, keyword, 5)
|
||||
|
||||
self.assertEqual(output, expected)
|
||||
|
||||
output = get_content_snippet(content, keyword, 0)
|
||||
|
||||
expected = (
|
||||
"knight <strong>eggs</strong> spam ham "
|
||||
"<strong>eggs</strong> guido python <strong>eggs</strong>"
|
||||
)
|
||||
self.assertEqual(output, expected)
|
||||
|
||||
def test_content_case_preserved(self):
|
||||
keyword = "DOlOr"
|
||||
match = "DoLoR"
|
||||
content = "lorem ipsum %s sit amet" % match
|
||||
output = get_content_snippet(content, keyword)
|
||||
self.assertIn(match, output)
|
||||
self.assertNotIn(keyword, output)
|
||||
|
||||
|
||||
class CanRead(TemplateTestCase):
|
||||
template = """
|
||||
{% load wiki_tags %}
|
||||
{{ article|can_read:user }}
|
||||
"""
|
||||
|
||||
@wiki_override_settings(WIKI_CAN_READ=lambda *args: True)
|
||||
def test_user_have_permission(self):
|
||||
a = Article.objects.create()
|
||||
|
||||
u = User.objects.create(username="Nobody", password="pass")
|
||||
|
||||
output = can_read(a, u)
|
||||
self.assertIs(output, True)
|
||||
|
||||
output = self.render({"article": a, "user": u})
|
||||
self.assertIn("True", output)
|
||||
|
||||
@wiki_override_settings(WIKI_CAN_READ=lambda *args: False)
|
||||
def test_user_dont_have_permission(self):
|
||||
a = Article.objects.create()
|
||||
u = User.objects.create(username="Noman", password="pass")
|
||||
|
||||
output = can_read(a, u)
|
||||
self.assertIs(output, False)
|
||||
|
||||
output = self.render({"article": a, "user": u})
|
||||
self.assertIn("False", output)
|
||||
|
||||
|
||||
class CanWrite(TemplateTestCase):
|
||||
template = """
|
||||
{% load wiki_tags %}
|
||||
{{ article|can_write:user }}
|
||||
"""
|
||||
|
||||
@wiki_override_settings(WIKI_CAN_DELETE=lambda *args: True)
|
||||
def test_user_have_permission(self):
|
||||
a = Article.objects.create()
|
||||
|
||||
u = User.objects.create(username="Nobody", password="pass")
|
||||
|
||||
output = can_write(a, u)
|
||||
self.assertIs(output, True)
|
||||
|
||||
output = self.render({"article": a, "user": u})
|
||||
self.assertIn("True", output)
|
||||
|
||||
@wiki_override_settings(WIKI_CAN_WRITE=lambda *args: False)
|
||||
def test_user_dont_have_permission(self):
|
||||
a = Article.objects.create()
|
||||
u = User.objects.create(username="Noman", password="pass")
|
||||
|
||||
output = can_write(a, u)
|
||||
self.assertIs(output, False)
|
||||
|
||||
output = self.render({"article": a, "user": u})
|
||||
self.assertIn("False", output)
|
||||
|
||||
|
||||
class CanDelete(TemplateTestCase):
|
||||
template = """
|
||||
{% load wiki_tags %}
|
||||
{{ article|can_delete:user }}
|
||||
"""
|
||||
|
||||
@wiki_override_settings(WIKI_CAN_DELETE=lambda *args: True)
|
||||
def test_user_have_permission(self):
|
||||
a = Article.objects.create()
|
||||
|
||||
u = User.objects.create(username="Nobody", password="pass")
|
||||
|
||||
output = can_delete(a, u)
|
||||
self.assertIs(output, True)
|
||||
|
||||
output = self.render({"article": a, "user": u})
|
||||
self.assertIn("True", output)
|
||||
|
||||
@wiki_override_settings(WIKI_CAN_WRITE=lambda *args: False)
|
||||
def test_user_dont_have_permission(self):
|
||||
a = Article.objects.create()
|
||||
u = User.objects.create(username="Noman", password="pass")
|
||||
|
||||
output = can_delete(a, u)
|
||||
self.assertIs(output, False)
|
||||
|
||||
output = self.render({"article": a, "user": u})
|
||||
self.assertIn("False", output)
|
||||
|
||||
|
||||
class CanModerate(TemplateTestCase):
|
||||
template = """
|
||||
{% load wiki_tags %}
|
||||
{{ article|can_moderate:user }}
|
||||
"""
|
||||
|
||||
@wiki_override_settings(WIKI_CAN_MODERATE=lambda *args: True)
|
||||
def test_user_have_permission(self):
|
||||
a = Article.objects.create()
|
||||
|
||||
u = User.objects.create(username="Nobody", password="pass")
|
||||
|
||||
output = can_moderate(a, u)
|
||||
self.assertIs(output, True)
|
||||
|
||||
output = self.render({"article": a, "user": u})
|
||||
self.assertIn("True", output)
|
||||
|
||||
def test_user_dont_have_permission(self):
|
||||
a = Article.objects.create()
|
||||
u = User.objects.create(username="Noman", password="pass")
|
||||
|
||||
output = can_moderate(a, u)
|
||||
self.assertIs(output, False)
|
||||
|
||||
output = self.render({"article": a, "user": u})
|
||||
self.assertIn("False", output)
|
||||
|
||||
|
||||
class IsLocked(TemplateTestCase):
|
||||
template = """
|
||||
{% load wiki_tags %}
|
||||
{{ article|is_locked }}
|
||||
"""
|
||||
|
||||
def test_no_current_revision(self):
|
||||
a = Article.objects.create()
|
||||
|
||||
output = is_locked(a)
|
||||
self.assertIsNone(output)
|
||||
|
||||
output = self.render({"article": a})
|
||||
self.assertIn("None", output)
|
||||
|
||||
def test_have_current_revision_and_not_locked(self):
|
||||
a = Article.objects.create()
|
||||
ArticleRevision.objects.create(article=a, locked=False)
|
||||
|
||||
b = Article.objects.create()
|
||||
ArticleRevision.objects.create(article=b)
|
||||
|
||||
output = is_locked(a)
|
||||
self.assertIs(output, False)
|
||||
|
||||
output = is_locked(b)
|
||||
self.assertIs(output, False)
|
||||
|
||||
output = self.render({"article": a})
|
||||
self.assertIn("False", output)
|
||||
|
||||
def test_have_current_revision_and_locked(self):
|
||||
a = Article.objects.create()
|
||||
ArticleRevision.objects.create(article=a, locked=True)
|
||||
|
||||
output = is_locked(a)
|
||||
self.assertIs(output, True)
|
||||
|
||||
output = self.render({"article": a})
|
||||
self.assertIn("True", output)
|
||||
|
||||
|
||||
class PluginEnabled(TemplateTestCase):
|
||||
template = """
|
||||
{% load wiki_tags %}
|
||||
{% if "wiki.plugins.attachments"|plugin_enabled %}It is enabled{% endif %}
|
||||
"""
|
||||
|
||||
def test_true(self):
|
||||
output = self.render({})
|
||||
self.assertIn("It is enabled", output)
|
||||
|
||||
|
||||
class WikiSettings(TemplateTestCase):
|
||||
template = """
|
||||
{% load wiki_tags %}
|
||||
{% if "ACCOUNT_HANDLING"|wiki_settings %}It is enabled{% endif %}
|
||||
"""
|
||||
|
||||
@wiki_override_settings(WIKI_ACCOUNT_HANDLING=lambda *args: True)
|
||||
def test_setting(self):
|
||||
output = self.render({})
|
||||
self.assertIn("It is enabled", output)
|
||||
353
tests/core/test_template_tags.py
Normal file
353
tests/core/test_template_tags.py
Normal file
@@ -0,0 +1,353 @@
|
||||
"""
|
||||
Almost all test cases covers both tag calling and template using.
|
||||
"""
|
||||
from django.conf import settings as django_settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.http import HttpRequest
|
||||
from wiki.conf import settings
|
||||
from wiki.forms import CreateRootForm
|
||||
from wiki.models import Article
|
||||
from wiki.models import ArticleForObject
|
||||
from wiki.models import ArticleRevision
|
||||
from wiki.templatetags.wiki_tags import article_for_object
|
||||
from wiki.templatetags.wiki_tags import login_url
|
||||
from wiki.templatetags.wiki_tags import wiki_form
|
||||
from wiki.templatetags.wiki_tags import wiki_render
|
||||
|
||||
from ..base import TemplateTestCase
|
||||
|
||||
if not django_settings.configured:
|
||||
django_settings.configure()
|
||||
|
||||
|
||||
# XXX article_for_object accepts context, but not using it
|
||||
class ArticleForObjectTemplatetagTest(TemplateTestCase):
|
||||
template = """
|
||||
{% load wiki_tags %}
|
||||
{% article_for_object obj as anything %}
|
||||
{{ anything }}
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
from wiki.templatetags import wiki_tags
|
||||
|
||||
wiki_tags._cache = {}
|
||||
|
||||
def test_obj_arg_is_not_a_django_model(self):
|
||||
from wiki.templatetags import wiki_tags
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
article_for_object({}, "")
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
article_for_object({"request": 100500}, {})
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
self.render({"obj": "tiger!"})
|
||||
|
||||
self.assertEqual(len(wiki_tags._cache), 0)
|
||||
|
||||
def test_obj_is_not_in__cache_and_articleforobject_is_not_exist(self):
|
||||
from wiki.templatetags.wiki_tags import _cache as cache
|
||||
|
||||
obj = Article.objects.create()
|
||||
|
||||
article_for_object({}, obj)
|
||||
|
||||
self.assertIn(obj, cache)
|
||||
self.assertIsNone(cache[obj])
|
||||
self.assertEqual(len(cache), 1)
|
||||
|
||||
self.render({"obj": obj})
|
||||
|
||||
self.assertIn(obj, cache)
|
||||
self.assertIsNone(cache[obj])
|
||||
self.assertEqual(len(cache), 1)
|
||||
|
||||
def test_obj_is_not_in__cache_and_articleforobjec_is_exist(self):
|
||||
from wiki.templatetags.wiki_tags import _cache as cache
|
||||
|
||||
a = Article.objects.create()
|
||||
content_type = ContentType.objects.get_for_model(a)
|
||||
ArticleForObject.objects.create(
|
||||
article=a, content_type=content_type, object_id=1
|
||||
)
|
||||
|
||||
output = article_for_object({}, a)
|
||||
|
||||
self.assertEqual(output, a)
|
||||
self.assertIn(a, cache)
|
||||
self.assertEqual(cache[a], a)
|
||||
self.assertEqual(len(cache), 1)
|
||||
|
||||
self.render({"obj": a})
|
||||
|
||||
self.assertIn(a, cache)
|
||||
self.assertEqual(cache[a], a)
|
||||
self.assertEqual(len(cache), 1)
|
||||
|
||||
def test_obj_in__cache_and_articleforobject_is_not_exist(self):
|
||||
model = Article.objects.create()
|
||||
|
||||
from wiki.templatetags import wiki_tags
|
||||
|
||||
wiki_tags._cache = {model: "spam"}
|
||||
|
||||
article_for_object({}, model)
|
||||
|
||||
self.assertIn(model, wiki_tags._cache)
|
||||
self.assertIsNone(wiki_tags._cache[model])
|
||||
self.assertEqual(len(wiki_tags._cache), 1)
|
||||
|
||||
self.render({"obj": model})
|
||||
|
||||
self.assertIn(model, wiki_tags._cache)
|
||||
self.assertIsNone(wiki_tags._cache[model])
|
||||
self.assertEqual(len(wiki_tags._cache), 1)
|
||||
|
||||
self.assertNotIn("spam", wiki_tags._cache.values())
|
||||
|
||||
def test_obj_in__cache_and_articleforobjec_is_exist(self):
|
||||
article = Article.objects.create()
|
||||
content_type = ContentType.objects.get_for_model(article)
|
||||
ArticleForObject.objects.create(
|
||||
article=article, content_type=content_type, object_id=1
|
||||
)
|
||||
|
||||
from wiki.templatetags import wiki_tags
|
||||
|
||||
wiki_tags._cache = {article: "spam"}
|
||||
|
||||
output = article_for_object({}, article)
|
||||
|
||||
self.assertEqual(output, article)
|
||||
self.assertIn(article, wiki_tags._cache)
|
||||
self.assertEqual(wiki_tags._cache[article], article)
|
||||
|
||||
output = self.render({"obj": article})
|
||||
|
||||
self.assertIn(article, wiki_tags._cache)
|
||||
self.assertEqual(wiki_tags._cache[article], article)
|
||||
|
||||
expected = "Article without content (1)"
|
||||
|
||||
self.assertIn(expected, output)
|
||||
|
||||
|
||||
# TODO manage plugins in template
|
||||
class WikiRenderTest(TemplateTestCase):
|
||||
template = """
|
||||
{% load wiki_tags %}
|
||||
{% wiki_render article pc %}
|
||||
"""
|
||||
|
||||
def tearDown(self):
|
||||
from wiki.core.plugins import registry
|
||||
|
||||
registry._cache = {}
|
||||
super().tearDown()
|
||||
|
||||
keys = [
|
||||
"article",
|
||||
"content",
|
||||
"preview",
|
||||
"plugins",
|
||||
"STATIC_URL",
|
||||
"CACHE_TIMEOUT",
|
||||
]
|
||||
|
||||
def test_if_preview_content_is_none(self):
|
||||
# monkey patch
|
||||
from wiki.core.plugins import registry
|
||||
|
||||
registry._cache = {"ham": "spam"}
|
||||
|
||||
article = Article.objects.create()
|
||||
|
||||
output = wiki_render({}, article)
|
||||
|
||||
self.assertCountEqual(self.keys, output)
|
||||
|
||||
self.assertEqual(output["article"], article)
|
||||
self.assertIsNone(output["content"])
|
||||
self.assertIs(output["preview"], False)
|
||||
|
||||
self.assertEqual(output["plugins"], {"ham": "spam"})
|
||||
self.assertEqual(output["STATIC_URL"], django_settings.STATIC_URL)
|
||||
self.assertEqual(output["CACHE_TIMEOUT"], settings.CACHE_TIMEOUT)
|
||||
|
||||
# Additional check
|
||||
self.render({"article": article, "pc": None})
|
||||
|
||||
def test_called_with_preview_content_and_article_have_current_revision(
|
||||
self
|
||||
):
|
||||
article = Article.objects.create()
|
||||
ArticleRevision.objects.create(
|
||||
article=article,
|
||||
title="Test title",
|
||||
content="Some beauty test text",
|
||||
)
|
||||
|
||||
content = (
|
||||
"""This is a normal paragraph\n"""
|
||||
"""\n"""
|
||||
"""Headline\n"""
|
||||
"""========\n"""
|
||||
)
|
||||
|
||||
expected = (
|
||||
"""(?s).*<p>This is a normal paragraph</p>\n"""
|
||||
"""<h1 id="wiki-toc-headline">Headline"""
|
||||
""".*</h1>.*"""
|
||||
)
|
||||
|
||||
# monkey patch
|
||||
from wiki.core.plugins import registry
|
||||
|
||||
registry._cache = {"spam": "eggs"}
|
||||
|
||||
output = wiki_render({}, article, preview_content=content)
|
||||
self.assertCountEqual(self.keys, output)
|
||||
self.assertEqual(output["article"], article)
|
||||
self.assertRegex(output["content"], expected)
|
||||
self.assertIs(output["preview"], True)
|
||||
self.assertEqual(output["plugins"], {"spam": "eggs"})
|
||||
self.assertEqual(output["STATIC_URL"], django_settings.STATIC_URL)
|
||||
self.assertEqual(output["CACHE_TIMEOUT"], settings.CACHE_TIMEOUT)
|
||||
|
||||
output = self.render({"article": article, "pc": content})
|
||||
self.assertRegex(output, expected)
|
||||
|
||||
def test_called_with_preview_content_and_article_dont_have_current_revision(
|
||||
self
|
||||
):
|
||||
article = Article.objects.create()
|
||||
|
||||
content = (
|
||||
"""This is a normal paragraph\n"""
|
||||
"""\n"""
|
||||
"""Headline\n"""
|
||||
"""========\n"""
|
||||
)
|
||||
|
||||
# monkey patch
|
||||
from wiki.core.plugins import registry
|
||||
|
||||
registry._cache = {"spam": "eggs"}
|
||||
|
||||
output = wiki_render({}, article, preview_content=content)
|
||||
|
||||
self.assertCountEqual(self.keys, output)
|
||||
|
||||
self.assertEqual(output["article"], article)
|
||||
|
||||
self.assertMultiLineEqual(output["content"], "")
|
||||
self.assertIs(output["preview"], True)
|
||||
|
||||
self.assertEqual(output["plugins"], {"spam": "eggs"})
|
||||
self.assertEqual(output["STATIC_URL"], django_settings.STATIC_URL)
|
||||
self.assertEqual(output["CACHE_TIMEOUT"], settings.CACHE_TIMEOUT)
|
||||
|
||||
self.render({"article": article, "pc": content})
|
||||
|
||||
|
||||
class WikiFormTest(TemplateTestCase):
|
||||
template = """
|
||||
{% load wiki_tags %}
|
||||
{% wiki_form form_obj %}
|
||||
"""
|
||||
|
||||
def test_form_obj_is_not_baseform_instance(self):
|
||||
context = {"test_key": "test_value"}
|
||||
form_obj = "ham"
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
wiki_form(context, form_obj)
|
||||
|
||||
self.assertEqual(context, {"test_key": "test_value"})
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
self.render({"test_key": 100500})
|
||||
|
||||
self.assertEqual(context, {"test_key": "test_value"})
|
||||
|
||||
def test_form_obj_is_baseform_instance(self):
|
||||
context = {"test_key": "test_value"}
|
||||
|
||||
# not by any special reasons, just a form
|
||||
form_obj = CreateRootForm()
|
||||
|
||||
wiki_form(context, form_obj)
|
||||
|
||||
self.assertEqual(context, {"test_key": "test_value", "form": form_obj})
|
||||
|
||||
self.render({"form_obj": form_obj})
|
||||
|
||||
self.assertEqual(context, {"test_key": "test_value", "form": form_obj})
|
||||
|
||||
|
||||
class LoginUrlTest(TemplateTestCase):
|
||||
template = """
|
||||
{% load wiki_tags %}
|
||||
{% login_url as some_url %}
|
||||
{{ some_url }}
|
||||
"""
|
||||
|
||||
def test_no_request_in_context(self):
|
||||
with self.assertRaises(KeyError):
|
||||
login_url({})
|
||||
|
||||
with self.assertRaises(KeyError):
|
||||
self.render({})
|
||||
|
||||
def test_login_url_if_no_query_string_in_request(self):
|
||||
r = HttpRequest()
|
||||
r.META = {}
|
||||
r.path = "best/test/page/ever/"
|
||||
|
||||
output = login_url({"request": r})
|
||||
|
||||
expected = "/_accounts/login/?next=best/test/page/ever/"
|
||||
|
||||
self.assertEqual(output, expected)
|
||||
|
||||
output = self.render({"request": r})
|
||||
|
||||
self.assertIn(expected, output)
|
||||
|
||||
def test_login_url_if_query_string_is_empty(self):
|
||||
r = HttpRequest()
|
||||
r.META = {"QUERY_STRING": ""}
|
||||
r.path = "best/test/page/ever/"
|
||||
|
||||
output = login_url({"request": r})
|
||||
|
||||
expected = "/_accounts/login/?next=best/test/page/ever/"
|
||||
|
||||
self.assertEqual(output, expected)
|
||||
|
||||
output = self.render({"request": r})
|
||||
|
||||
self.assertIn(expected, output)
|
||||
|
||||
def test_login_url_if_query_string_is_not_empty(self):
|
||||
r = HttpRequest()
|
||||
r.META = {"QUERY_STRING": "title=Main_page&action=raw"}
|
||||
r.path = "best/test/page/ever/"
|
||||
|
||||
context = {"request": r}
|
||||
|
||||
output = login_url(context)
|
||||
|
||||
expected = (
|
||||
"/_accounts/login/"
|
||||
"?next=best/test/page/ever/%3Ftitle%3DMain_page%26action%3Draw"
|
||||
)
|
||||
|
||||
self.assertEqual(output, expected)
|
||||
|
||||
output = self.render({"request": r})
|
||||
|
||||
self.assertIn(expected, output)
|
||||
78
tests/core/test_urls.py
Normal file
78
tests/core/test_urls.py
Normal file
@@ -0,0 +1,78 @@
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib.sites.models import Site
|
||||
from django.test.testcases import TestCase
|
||||
from django.urls import include
|
||||
from django.urls import re_path
|
||||
from wiki.models import Article
|
||||
from wiki.models import URLPath
|
||||
from wiki.urls import get_pattern as get_wiki_pattern
|
||||
from wiki.urls import WikiURLPatterns
|
||||
|
||||
from ..base import wiki_override_settings
|
||||
|
||||
|
||||
class WikiCustomUrlPatterns(WikiURLPatterns):
|
||||
def get_article_urls(self):
|
||||
urlpatterns = [
|
||||
re_path(
|
||||
"^some-prefix/(?P<article_id>[0-9]+)/$",
|
||||
self.article_view_class.as_view(),
|
||||
name="get",
|
||||
),
|
||||
]
|
||||
return urlpatterns
|
||||
|
||||
def get_article_path_urls(self):
|
||||
urlpatterns = [
|
||||
re_path(
|
||||
"^some-other-prefix/(?P<path>.+/|)$",
|
||||
self.article_view_class.as_view(),
|
||||
name="get",
|
||||
),
|
||||
]
|
||||
return urlpatterns
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r"^notify/", include("django_nyt.urls")),
|
||||
re_path(
|
||||
r"^elsewhere/",
|
||||
get_wiki_pattern(url_config_class=WikiCustomUrlPatterns),
|
||||
),
|
||||
] + static("/static/", document_root="./")
|
||||
|
||||
|
||||
@wiki_override_settings(
|
||||
WIKI_URL_CONFIG_CLASS="tests.core.test_models.WikiCustomUrlPatterns",
|
||||
ROOT_URLCONF="tests.core.test_urls",
|
||||
)
|
||||
class ArticleModelReverseMethodTest(TestCase):
|
||||
def test_get_absolute_url_if_urlpath_set_is_not_exists__no_root_urlconf(
|
||||
self
|
||||
):
|
||||
a = Article.objects.create()
|
||||
|
||||
url = a.get_absolute_url()
|
||||
|
||||
expected = "/elsewhere/some-prefix/1/"
|
||||
|
||||
self.assertEqual(url, expected)
|
||||
|
||||
def test_get_absolute_url_if_urlpath_set_is_exists__no_root_urlconf(self):
|
||||
a1 = Article.objects.create()
|
||||
s1 = Site.objects.create(domain="something.com", name="something.com")
|
||||
u1 = URLPath.objects.create(article=a1, site=s1)
|
||||
|
||||
a2 = Article.objects.create()
|
||||
s2 = Site.objects.create(
|
||||
domain="somethingelse.com", name="somethingelse.com"
|
||||
)
|
||||
URLPath.objects.create(
|
||||
article=a2, site=s2, parent=u1, slug="test_slug"
|
||||
)
|
||||
|
||||
url = a2.get_absolute_url()
|
||||
|
||||
expected = "/elsewhere/some-other-prefix/test_slug/"
|
||||
|
||||
self.assertEqual(url, expected)
|
||||
14
tests/core/test_utils.py
Normal file
14
tests/core/test_utils.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from django.test import TestCase
|
||||
from wiki.core.utils import object_to_json_response
|
||||
|
||||
|
||||
class TestUtils(TestCase):
|
||||
def test_object_to_json(self):
|
||||
"""
|
||||
Simple test, the actual serialization happens in json.dumps and we
|
||||
don't wanna test this core module in depth.
|
||||
"""
|
||||
obj = []
|
||||
response = object_to_json_response(obj)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.content, b"[]")
|
||||
982
tests/core/test_views.py
Normal file
982
tests/core/test_views.py
Normal file
@@ -0,0 +1,982 @@
|
||||
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 <a[^>]*>wiki:/test1new/"
|
||||
)
|
||||
|
||||
response = self.get_by_path("test0/test2/test020/")
|
||||
self.assertContains(response, "Moved: Test020")
|
||||
self.assertRegex(
|
||||
response.rendered_content,
|
||||
r"moved to <a[^>]*>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+<strong><a href=".+">/</a></strong>',
|
||||
)
|
||||
|
||||
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")
|
||||
0
tests/plugins/__init__.py
Normal file
0
tests/plugins/__init__.py
Normal file
0
tests/plugins/attachments/__init__.py
Normal file
0
tests/plugins/attachments/__init__.py
Normal file
38
tests/plugins/attachments/test_commands.py
Normal file
38
tests/plugins/attachments/test_commands.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from wiki.models import URLPath
|
||||
from wiki.plugins.attachments import models
|
||||
|
||||
from tests.core.test_commands import TestManagementCommands
|
||||
|
||||
|
||||
class TestAttachmentManagementCommands(TestManagementCommands):
|
||||
"""
|
||||
Add some more data
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.test_file = tempfile.NamedTemporaryFile(
|
||||
"w", delete=False, suffix=".txt"
|
||||
)
|
||||
self.test_file.write("test")
|
||||
|
||||
self.child1 = URLPath.create_urlpath(
|
||||
self.root, "test-slug", title="Test 1"
|
||||
)
|
||||
|
||||
self.attachment1 = models.Attachment.objects.create(
|
||||
article=self.child1.article
|
||||
)
|
||||
|
||||
self.attachment1_revision1 = models.AttachmentRevision.objects.create(
|
||||
attachment=self.attachment1,
|
||||
file=self.test_file.name,
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
os.unlink(self.test_file.name)
|
||||
super().tearDown()
|
||||
42
tests/plugins/attachments/test_models.py
Normal file
42
tests/plugins/attachments/test_models.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from wiki.plugins.attachments.models import Attachment
|
||||
from wiki.plugins.attachments.models import AttachmentRevision
|
||||
|
||||
from tests.base import RequireRootArticleMixin
|
||||
from tests.base import TestBase
|
||||
|
||||
|
||||
class AttachmentRevisionTests(RequireRootArticleMixin, TestBase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.attachment = Attachment.objects.create(
|
||||
article=self.root_article,
|
||||
original_filename="blah.txt",
|
||||
)
|
||||
self.revision = AttachmentRevision.objects.create(
|
||||
attachment=self.attachment,
|
||||
file=None,
|
||||
description="muh",
|
||||
revision_number=1,
|
||||
)
|
||||
|
||||
def test_revision_no_file(self):
|
||||
# Intentionally, there are no asserts, as the test just needs to
|
||||
# target an if-branch in the pre-delete signal for AttachmentRevision
|
||||
self.revision.delete()
|
||||
|
||||
def test_revision_file_size(self):
|
||||
self.assertIsNone(self.revision.get_size())
|
||||
|
||||
def test_get_filename_no_file(self):
|
||||
self.assertIsNone(self.revision.get_filename())
|
||||
|
||||
def test_str(self):
|
||||
self.assertEqual(
|
||||
str(self.revision),
|
||||
"%s: %s (r%d)"
|
||||
% (
|
||||
"Root Article",
|
||||
"blah.txt",
|
||||
1,
|
||||
),
|
||||
)
|
||||
183
tests/plugins/attachments/test_views.py
Normal file
183
tests/plugins/attachments/test_views.py
Normal file
@@ -0,0 +1,183 @@
|
||||
from io import BytesIO
|
||||
|
||||
from django.core.files.uploadedfile import InMemoryUploadedFile
|
||||
from django.urls import reverse
|
||||
from wiki.models import URLPath
|
||||
|
||||
from ...base import ArticleWebTestUtils
|
||||
from ...base import DjangoClientTestBase
|
||||
from ...base import RequireRootArticleMixin
|
||||
|
||||
|
||||
class AttachmentTests(
|
||||
RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase
|
||||
):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.article = self.root_article
|
||||
self.test_data = "This is a plain text file"
|
||||
self.test_description = "My file"
|
||||
|
||||
def _createTxtFilestream(self, strData, **kwargs):
|
||||
"""
|
||||
Helper function to create filestream for upload.
|
||||
|
||||
Parameters :
|
||||
strData : str, test string data
|
||||
|
||||
Optional Arguments :
|
||||
filename : str, Defaults to 'test.txt'
|
||||
"""
|
||||
filename = kwargs.get("filename", "test.txt")
|
||||
data = strData.encode("utf-8")
|
||||
filedata = BytesIO(data)
|
||||
filestream = InMemoryUploadedFile(
|
||||
filedata, None, filename, "text", len(data), None
|
||||
)
|
||||
return filestream
|
||||
|
||||
def _create_test_attachment(self, path):
|
||||
url = reverse("wiki:attachments_index", kwargs={"path": path})
|
||||
filestream = self._createTxtFilestream(self.test_data)
|
||||
response = self.client.post(
|
||||
url,
|
||||
{
|
||||
"description": self.test_description,
|
||||
"file": filestream,
|
||||
"save": "1",
|
||||
},
|
||||
)
|
||||
self.assertRedirects(response, url)
|
||||
|
||||
def test_upload(self):
|
||||
"""
|
||||
Tests that simple file upload uploads correctly
|
||||
Uploading a file should preserve the original filename.
|
||||
Uploading should not modify file in any way.
|
||||
"""
|
||||
self._create_test_attachment("")
|
||||
# Check the object was created.
|
||||
attachment = self.article.shared_plugins_set.all()[0].attachment
|
||||
self.assertEqual(attachment.original_filename, "test.txt")
|
||||
self.assertEqual(
|
||||
attachment.current_revision.file.file.read(),
|
||||
self.test_data.encode("utf-8"),
|
||||
)
|
||||
|
||||
def test_replace(self):
|
||||
"""
|
||||
Tests that previous revisions are not deleted
|
||||
Tests that only the most recent revision is deleted when
|
||||
"replace" is checked.
|
||||
"""
|
||||
# Upload initial file
|
||||
url = reverse("wiki:attachments_index", kwargs={"path": ""})
|
||||
data = "This is a plain text file"
|
||||
filestream = self._createTxtFilestream(data)
|
||||
self.client.post(
|
||||
url, {"description": "My file", "file": filestream, "save": "1"}
|
||||
)
|
||||
attachment = self.article.shared_plugins_set.all()[0].attachment
|
||||
|
||||
# uploading for the first time should mean that there is only one revision.
|
||||
self.assertEqual(attachment.attachmentrevision_set.count(), 1)
|
||||
|
||||
# Change url to replacement page.
|
||||
url = reverse(
|
||||
"wiki:attachments_replace",
|
||||
kwargs={
|
||||
"attachment_id": attachment.id,
|
||||
"article_id": self.article.id,
|
||||
},
|
||||
)
|
||||
|
||||
# Upload replacement without removing revisions
|
||||
replacement_data = data + " And this is my edit"
|
||||
replacement_filestream = self._createTxtFilestream(replacement_data)
|
||||
self.client.post(
|
||||
url,
|
||||
{
|
||||
"description": "Replacement upload",
|
||||
"file": replacement_filestream,
|
||||
},
|
||||
)
|
||||
attachment = self.article.shared_plugins_set.all()[0].attachment
|
||||
# Revision count should be two
|
||||
self.assertEqual(attachment.attachmentrevision_set.count(), 2)
|
||||
# Original filenames should not be modified
|
||||
self.assertEqual(attachment.original_filename, "test.txt")
|
||||
# Latest revision should equal replacment_data
|
||||
self.assertEqual(
|
||||
attachment.current_revision.file.file.read(),
|
||||
replacement_data.encode("utf-8"),
|
||||
)
|
||||
first_replacement = attachment.current_revision
|
||||
|
||||
# Upload another replacement, this time removing most recent revision
|
||||
replacement_data2 = data + " And this is a different edit"
|
||||
replacement_filestream2 = self._createTxtFilestream(replacement_data2)
|
||||
self.client.post(
|
||||
url,
|
||||
{
|
||||
"description": "Replacement upload",
|
||||
"file": replacement_filestream2,
|
||||
"replace": "on",
|
||||
},
|
||||
)
|
||||
attachment = self.article.shared_plugins_set.all()[0].attachment
|
||||
# Revision count should still be two
|
||||
self.assertEqual(attachment.attachmentrevision_set.count(), 2)
|
||||
# Latest revision should equal replacment_data2
|
||||
self.assertEqual(
|
||||
attachment.current_revision.file.file.read(),
|
||||
replacement_data2.encode("utf-8"),
|
||||
)
|
||||
# The first replacement should no longer be in the filehistory
|
||||
self.assertNotIn(
|
||||
first_replacement, attachment.attachmentrevision_set.all()
|
||||
)
|
||||
|
||||
def test_search(self):
|
||||
"""
|
||||
Call the search view
|
||||
"""
|
||||
self._create_test_attachment("")
|
||||
url = reverse("wiki:attachments_search", kwargs={"path": ""})
|
||||
response = self.client.get(url, {"query": self.test_description})
|
||||
self.assertContains(response, self.test_description)
|
||||
|
||||
def get_article(self, cont):
|
||||
urlpath = URLPath.create_urlpath(
|
||||
URLPath.root(), "html_attach", title="TestAttach", content=cont
|
||||
)
|
||||
self._create_test_attachment(urlpath.path)
|
||||
return urlpath.article.render()
|
||||
|
||||
def test_render(self):
|
||||
output = self.get_article("[attachment:1]")
|
||||
expected = (
|
||||
r'<span class="attachment"><a href=".*attachments/download/1/"'
|
||||
r' title="Click to download test\.txt">\s*test\.txt\s*</a>'
|
||||
)
|
||||
self.assertRegex(output, expected)
|
||||
|
||||
def test_render_missing(self):
|
||||
output = self.get_article("[attachment:2]")
|
||||
expected = r'<span class="attachment attachment-deleted">\s*Attachment with ID #2 is deleted.\s*</span>'
|
||||
self.assertRegex(output, expected)
|
||||
|
||||
def test_render_title(self):
|
||||
output = self.get_article('[attachment:1 title:"Test title"]')
|
||||
expected = (
|
||||
r'<span class="attachment"><a href=".*attachments/download/1/"'
|
||||
r' title="Click to download test\.txt">\s*Test title\s*</a>'
|
||||
)
|
||||
self.assertRegex(output, expected)
|
||||
|
||||
def test_render_title_size(self):
|
||||
output = self.get_article('[attachment:1 title:"Test title 2" size]')
|
||||
expected = (
|
||||
r'<span class="attachment"><a href=".*attachments/download/1/"'
|
||||
r' title="Click to download test\.txt">\s*Test title 2 \[25[^b]bytes\]\s*</a>'
|
||||
)
|
||||
self.assertRegex(output, expected)
|
||||
0
tests/plugins/editsection/__init__.py
Normal file
0
tests/plugins/editsection/__init__.py
Normal file
207
tests/plugins/editsection/test_editsection.py
Normal file
207
tests/plugins/editsection/test_editsection.py
Normal file
@@ -0,0 +1,207 @@
|
||||
import re
|
||||
|
||||
from django.urls import reverse
|
||||
from django_functest import FuncBaseMixin
|
||||
from wiki.models import URLPath
|
||||
|
||||
from ...base import DjangoClientTestBase
|
||||
from ...base import RequireRootArticleMixin
|
||||
from ...base import WebTestBase
|
||||
|
||||
TEST_CONTENT = (
|
||||
"Title 1\n"
|
||||
"=======\n"
|
||||
"## Title 2\n"
|
||||
"Title 3\n"
|
||||
"-------\n"
|
||||
"a\n"
|
||||
"Paragraph\n"
|
||||
"-------\n"
|
||||
"### Title 4\n"
|
||||
"## Title 5\n"
|
||||
"# Title 6\n"
|
||||
)
|
||||
|
||||
|
||||
TEST_CONTENT_SRC_COMMENT = """
|
||||
# Section 1
|
||||
Section 1 Lorem ipsum dolor sit amet
|
||||
|
||||
```python
|
||||
# hello world
|
||||
print("hello world")
|
||||
```
|
||||
|
||||
# Section 2
|
||||
Section 2 Lorem ipsum dolor sit amet
|
||||
"""
|
||||
|
||||
|
||||
class EditSectionTests(RequireRootArticleMixin, DjangoClientTestBase):
|
||||
def test_editsection(self):
|
||||
# Test creating links to allow editing all sections individually
|
||||
urlpath = URLPath.create_urlpath(
|
||||
URLPath.root(), "testedit", title="TestEdit", content=TEST_CONTENT
|
||||
)
|
||||
output = urlpath.article.render()
|
||||
expected = (
|
||||
r"(?s)"
|
||||
r'Title 1<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-title-1/">\[edit\]</a>.*'
|
||||
r'Title 2<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-title-2/">\[edit\]</a>.*'
|
||||
r'Title 3<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-title-3/">\[edit\]</a>.*'
|
||||
r'Title 4<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-title-4/">\[edit\]</a>.*'
|
||||
r'Title 5<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-title-5/">\[edit\]</a>.*'
|
||||
r'Title 6<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-title-6/">\[edit\]</a>.*'
|
||||
)
|
||||
self.assertRegex(output, expected)
|
||||
|
||||
# Test wrong header text. Editing should fail with a redirect.
|
||||
url = reverse(
|
||||
"wiki:editsection",
|
||||
kwargs={"path": "testedit/", "header": "does-not-exist"},
|
||||
)
|
||||
response = self.client.get(url)
|
||||
self.assertRedirects(
|
||||
response, reverse("wiki:get", kwargs={"path": "testedit/"})
|
||||
)
|
||||
|
||||
# Test extracting sections for editing
|
||||
url = reverse(
|
||||
"wiki:editsection",
|
||||
kwargs={"path": "testedit/", "header": "wiki-toc-title-4"},
|
||||
)
|
||||
response = self.client.get(url)
|
||||
expected = ">### Title 4[\r\n]*" "<"
|
||||
self.assertRegex(response.rendered_content, expected)
|
||||
|
||||
url = reverse(
|
||||
"wiki:editsection",
|
||||
kwargs={"path": "testedit/", "header": "wiki-toc-title-3"},
|
||||
)
|
||||
response = self.client.get(url)
|
||||
expected = (
|
||||
">Title 3[\r\n]*"
|
||||
"-------[\r\n]*"
|
||||
"a[\r\n]*"
|
||||
"Paragraph[\r\n]*"
|
||||
"-------[\r\n]*"
|
||||
"### Title 4[\r\n]*"
|
||||
"<"
|
||||
)
|
||||
self.assertRegex(response.rendered_content, expected)
|
||||
|
||||
def test_broken_content(self):
|
||||
# Regression test for https://github.com/django-wiki/django-wiki/issues/1094
|
||||
TEST_CONTENT = "### [Here we go](#anchor)"
|
||||
urlpath = URLPath.create_urlpath(
|
||||
URLPath.root(), "testedit", title="TestEdit", content=TEST_CONTENT
|
||||
)
|
||||
output = urlpath.article.render()
|
||||
print(output)
|
||||
|
||||
def get_section_content(self, response):
|
||||
# extract actual section content from response (editor)
|
||||
m = re.search(
|
||||
r"<textarea[^>]+>(?P<content>[^<]+)</textarea>",
|
||||
response.rendered_content,
|
||||
re.DOTALL,
|
||||
)
|
||||
if m:
|
||||
return m.group("content")
|
||||
else:
|
||||
return ""
|
||||
|
||||
def test_sourceblock_with_comment(self):
|
||||
# https://github.com/django-wiki/django-wiki/issues/1246
|
||||
URLPath.create_urlpath(
|
||||
URLPath.root(),
|
||||
"testedit_src",
|
||||
title="TestEditSourceComment",
|
||||
content=TEST_CONTENT_SRC_COMMENT,
|
||||
)
|
||||
url = reverse(
|
||||
"wiki:editsection",
|
||||
kwargs={"path": "testedit_src/", "header": "wiki-toc-section-2"},
|
||||
)
|
||||
response = self.client.get(url)
|
||||
actual = self.get_section_content(response)
|
||||
expected = "# Section 2\r\nSection 2 Lorem ipsum dolor sit amet\r\n"
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_nonunique_headers(self):
|
||||
"""test whether non-unique headers will be handled properly"""
|
||||
source = """# Investigation 1\n\n## Date\n2023-01-01\n\n# Investigation 2\n\n## Date\n2023-01-02"""
|
||||
URLPath.create_urlpath(
|
||||
URLPath.root(),
|
||||
"testedit_src",
|
||||
title="TestEditSourceComment",
|
||||
content=source,
|
||||
)
|
||||
url = reverse(
|
||||
"wiki:editsection",
|
||||
kwargs={"path": "testedit_src/", "header": "wiki-toc-date"},
|
||||
)
|
||||
response = self.client.get(url)
|
||||
actual = self.get_section_content(response)
|
||||
expected = "## Date\r\n2023-01-01\r\n\r\n"
|
||||
|
||||
self.assertEqual(actual, expected)
|
||||
url = reverse(
|
||||
"wiki:editsection",
|
||||
kwargs={"path": "testedit_src/", "header": "wiki-toc-date_1"},
|
||||
)
|
||||
response = self.client.get(url)
|
||||
actual = self.get_section_content(response)
|
||||
expected = "## Date\r\n2023-01-02"
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_underscore_and_dot(self):
|
||||
"""test whether we can handle non-slug characters like dots in header IDs"""
|
||||
# Explanation: While autogenerated ids are slugified, Markdown allows to manually
|
||||
# specify the ID using the {#custom_id_value} syntax. As HTML5 only requires ID
|
||||
# values not to contain whitespace, we should be able to handle any valid HTML5 ID, too.
|
||||
source = """# Title 1 {#some_id_with.dot}\n\n"""
|
||||
urlpath = URLPath.create_urlpath(
|
||||
URLPath.root(), "testedit", title="TestEdit", content=source
|
||||
)
|
||||
# rendering causes NoReverseMatch without the fix
|
||||
actual = urlpath.article.render()
|
||||
expected = '<h1 id="some_id_with.dot">Title 1<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/some_id_with.dot/">[edit]</a></h1>'
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
|
||||
class EditSectionEditBase(RequireRootArticleMixin, FuncBaseMixin):
|
||||
pass
|
||||
|
||||
|
||||
class EditSectionEditTests(EditSectionEditBase, WebTestBase):
|
||||
# Test editing a section
|
||||
def test_editsection_edit(self):
|
||||
urlpath = URLPath.create_urlpath(
|
||||
URLPath.root(), "testedit", title="TestEdit", content=TEST_CONTENT
|
||||
)
|
||||
old_number = urlpath.article.current_revision.revision_number
|
||||
|
||||
self.get_literal_url(
|
||||
reverse(
|
||||
"wiki:editsection",
|
||||
kwargs={"path": "testedit/", "header": "wiki-toc-title-3"},
|
||||
)
|
||||
)
|
||||
self.fill({"#id_content": "# Header 1\nContent of the new section"})
|
||||
self.submit("#id_save")
|
||||
expected = (
|
||||
r"(?s)"
|
||||
r'Title 1<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-title-1/">\[edit\]</a>.*'
|
||||
r'Title 2<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-title-2/">\[edit\]</a>.*'
|
||||
r'Header 1<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-header-1/">\[edit\]</a>.*'
|
||||
r"Content of the new section.*"
|
||||
r'Title 5<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-title-5/">\[edit\]</a>.*'
|
||||
r'Title 6<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-title-6/">\[edit\]</a>.*'
|
||||
)
|
||||
self.assertRegex(self.last_response.content.decode("utf-8"), expected)
|
||||
|
||||
new_number = URLPath.objects.get(
|
||||
slug="testedit"
|
||||
).article.current_revision.revision_number
|
||||
self.assertEqual(new_number, old_number + 1)
|
||||
0
tests/plugins/globalhistory/__init__.py
Normal file
0
tests/plugins/globalhistory/__init__.py
Normal file
99
tests/plugins/globalhistory/test_globalhistory.py
Normal file
99
tests/plugins/globalhistory/test_globalhistory.py
Normal file
@@ -0,0 +1,99 @@
|
||||
from django.urls import reverse
|
||||
from django.utils import translation
|
||||
from wiki.models import URLPath
|
||||
|
||||
from ...base import ArticleWebTestUtils
|
||||
from ...base import DjangoClientTestBase
|
||||
from ...base import RequireRootArticleMixin
|
||||
|
||||
|
||||
class GlobalhistoryTests(
|
||||
RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase
|
||||
):
|
||||
def test_history(self):
|
||||
url = reverse("wiki:globalhistory")
|
||||
url0 = reverse("wiki:globalhistory", kwargs={"only_last": "0"})
|
||||
url1 = reverse("wiki:globalhistory", kwargs={"only_last": "1"})
|
||||
|
||||
response = self.client.get(url)
|
||||
expected = "(?s).*Root Article.*no log message.*"
|
||||
self.assertRegex(response.rendered_content, expected)
|
||||
|
||||
URLPath.create_urlpath(
|
||||
URLPath.root(),
|
||||
"testhistory1",
|
||||
title="TestHistory1",
|
||||
content="a page",
|
||||
user_message="Comment 1",
|
||||
)
|
||||
response = self.client.get(url)
|
||||
expected = (
|
||||
"(?s).*TestHistory1.*Comment 1.*" "Root Article.*no log message.*"
|
||||
)
|
||||
self.assertRegex(response.rendered_content, expected)
|
||||
|
||||
urlpath = URLPath.create_urlpath(
|
||||
URLPath.root(),
|
||||
"testhistory2",
|
||||
title="TestHistory2",
|
||||
content="a page",
|
||||
user_message="Comment 2",
|
||||
)
|
||||
expected = (
|
||||
"(?s).*TestHistory2.*Comment 2.*"
|
||||
"TestHistory1.*Comment 1.*"
|
||||
"Root Article.*no log message.*"
|
||||
)
|
||||
response = self.client.get(url)
|
||||
self.assertRegex(response.rendered_content, expected)
|
||||
|
||||
response = self.client.get(url0)
|
||||
self.assertRegex(response.rendered_content, expected)
|
||||
|
||||
response = self.client.get(url1)
|
||||
self.assertRegex(response.rendered_content, expected)
|
||||
|
||||
response = self.client.post(
|
||||
reverse("wiki:edit", kwargs={"path": "testhistory2/"}),
|
||||
{
|
||||
"content": "a page modified",
|
||||
"current_revision": str(urlpath.article.current_revision.id),
|
||||
"preview": "0",
|
||||
"save": "1",
|
||||
"summary": "Testing Revision",
|
||||
"title": "TestHistory2Mod",
|
||||
},
|
||||
)
|
||||
|
||||
expected = (
|
||||
"(?s).*TestHistory2Mod.*Testing Revision.*"
|
||||
"TestHistory2.*Comment 2.*"
|
||||
"TestHistory1.*Comment 1.*"
|
||||
"Root Article.*no log message.*"
|
||||
)
|
||||
response = self.client.get(url)
|
||||
self.assertRegex(response.rendered_content, expected)
|
||||
|
||||
response = self.client.get(url0)
|
||||
self.assertRegex(response.rendered_content, expected)
|
||||
|
||||
expected = (
|
||||
"(?s).*TestHistory2Mod.*Testing Revision.*"
|
||||
"TestHistory1.*Comment 1.*"
|
||||
"Root Article.*no log message.*"
|
||||
)
|
||||
response = self.client.get(url1)
|
||||
self.assertRegex(response.rendered_content, expected)
|
||||
|
||||
def test_translation(self):
|
||||
# Test that translation of "List of %s changes in the wiki." exists.
|
||||
url = reverse("wiki:globalhistory")
|
||||
response_en = self.client.get(url)
|
||||
self.assertIn("Global history", response_en.rendered_content)
|
||||
self.assertIn("in the wiki", response_en.rendered_content)
|
||||
|
||||
with translation.override("da-DK"):
|
||||
response_da = self.client.get(url)
|
||||
|
||||
self.assertNotIn("Global history", response_da.rendered_content)
|
||||
self.assertNotIn("in the wiki", response_da.rendered_content)
|
||||
0
tests/plugins/images/__init__.py
Normal file
0
tests/plugins/images/__init__.py
Normal file
16
tests/plugins/images/test_forms.py
Normal file
16
tests/plugins/images/test_forms.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from django.test import TestCase
|
||||
from django.utils.translation import gettext
|
||||
from wiki.plugins.images.forms import PurgeForm
|
||||
|
||||
|
||||
class PurgeFormTest(TestCase):
|
||||
def test_not_sure(self):
|
||||
form = PurgeForm(data={"confirm": False})
|
||||
self.assertIs(form.is_valid(), False)
|
||||
self.assertEqual(
|
||||
form.errors["confirm"], [gettext("You are not sure enough!")]
|
||||
)
|
||||
|
||||
def test_sure(self):
|
||||
form = PurgeForm(data={"confirm": True})
|
||||
self.assertIs(form.is_valid(), True)
|
||||
93
tests/plugins/images/test_markdown.py
Normal file
93
tests/plugins/images/test_markdown.py
Normal file
@@ -0,0 +1,93 @@
|
||||
import base64
|
||||
from io import BytesIO
|
||||
|
||||
from django.core.files.uploadedfile import InMemoryUploadedFile
|
||||
from wiki.core import markdown
|
||||
from wiki.plugins.images import models
|
||||
|
||||
from tests.base import RequireRootArticleMixin
|
||||
from tests.base import TestBase
|
||||
|
||||
|
||||
class ImageMarkdownTests(RequireRootArticleMixin, TestBase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.image_revision = models.ImageRevision(
|
||||
image=self._create_test_gif_file(), width=1, height=1
|
||||
)
|
||||
self.image = models.Image(article=self.root_article)
|
||||
self.image.add_revision(self.image_revision)
|
||||
self.assertEqual(1, self.image.id)
|
||||
|
||||
def _create_test_gif_file(self):
|
||||
# A black 1x1 gif
|
||||
str_base64 = "R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="
|
||||
filename = "test.gif"
|
||||
data = base64.b64decode(str_base64)
|
||||
filedata = BytesIO(data)
|
||||
return InMemoryUploadedFile(
|
||||
filedata, None, filename, "image", len(data), None
|
||||
)
|
||||
|
||||
def test_before_and_after(self):
|
||||
md = markdown.ArticleMarkdown(article=self.root_article)
|
||||
md_text = md.convert(
|
||||
"before [image:%s align:left] after" % self.image.id
|
||||
)
|
||||
before_pos = md_text.index("before")
|
||||
figure_pos = md_text.index("<figure")
|
||||
after_pos = md_text.index("after")
|
||||
self.assertTrue(before_pos < figure_pos < after_pos)
|
||||
|
||||
def test_markdown(self):
|
||||
md = markdown.ArticleMarkdown(article=self.root_article)
|
||||
md_text = md.convert("[image:%s align:left]" % self.image.id)
|
||||
self.assertIn("<figure", md_text)
|
||||
self.assertNotIn("[image:%s align:left]" % self.image.id, md_text)
|
||||
md_text = md.convert(
|
||||
"image: [image:%s align:left]\nadasd" % self.image.id
|
||||
)
|
||||
self.assertIn("<figure", md_text)
|
||||
self.assertIn("<figcaption", md_text)
|
||||
md_text = md.convert(
|
||||
"image: [image:%s align:right size:medium]\nadasd" % self.image.id
|
||||
)
|
||||
self.assertIn("<figure", md_text)
|
||||
self.assertIn("<figcaption", md_text)
|
||||
md_text = md.convert(
|
||||
"image: [image:123 align:left size:medium]\nadasd"
|
||||
)
|
||||
self.assertIn("Image not found", md_text)
|
||||
self.assertIn("<figcaption", md_text)
|
||||
|
||||
def test_caption(self):
|
||||
md = markdown.ArticleMarkdown(article=self.root_article)
|
||||
md_text = md.convert(
|
||||
"[image:%s align:left]\n this is visual" % self.image.id
|
||||
)
|
||||
self.assertIn("<figure", md_text)
|
||||
self.assertRegex(
|
||||
md_text,
|
||||
r'<figcaption class="caption">\s*this is visual\s*</figcaption>',
|
||||
)
|
||||
md = markdown.ArticleMarkdown(article=self.root_article)
|
||||
md_text = md.convert(
|
||||
"[image:%s align:left]\n this is visual\n second line"
|
||||
% self.image.id
|
||||
)
|
||||
self.assertIn("<figure", md_text)
|
||||
self.assertRegex(
|
||||
md_text,
|
||||
r'<figcaption class="caption">\s*this is visual\s*second line\s*</figcaption>',
|
||||
)
|
||||
|
||||
def check_escape(self, text_to_escape):
|
||||
md = markdown.ArticleMarkdown(article=self.root_article)
|
||||
md_text = md.convert("`%s`" % text_to_escape)
|
||||
self.assertNotIn("<figure", md_text)
|
||||
self.assertIn(text_to_escape, md_text)
|
||||
|
||||
def test_escape(self):
|
||||
self.check_escape("[image:%s align:left]" % self.image.id)
|
||||
self.check_escape("image tag: [image:%s]" % self.image.id)
|
||||
303
tests/plugins/images/test_views.py
Normal file
303
tests/plugins/images/test_views.py
Normal file
@@ -0,0 +1,303 @@
|
||||
import base64
|
||||
import os
|
||||
import re
|
||||
from io import BytesIO
|
||||
|
||||
from django.core.files.uploadedfile import InMemoryUploadedFile
|
||||
from django.urls import reverse
|
||||
from PIL import Image
|
||||
from wiki.core.plugins import registry as plugin_registry
|
||||
from wiki.models import URLPath
|
||||
from wiki.plugins.images import models
|
||||
from wiki.plugins.images.wiki_plugin import ImagePlugin
|
||||
|
||||
from ...base import ArticleWebTestUtils
|
||||
from ...base import DjangoClientTestBase
|
||||
from ...base import RequireRootArticleMixin
|
||||
from ...base import wiki_override_settings
|
||||
|
||||
|
||||
class ImageTests(
|
||||
RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase
|
||||
):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.article = self.root_article
|
||||
# A black 1x1 gif
|
||||
self.test_data = "R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="
|
||||
|
||||
def _create_gif_filestream_from_base64(self, str_base64, **kwargs):
|
||||
"""
|
||||
Helper function to create filestream for upload.
|
||||
|
||||
Parameters :
|
||||
strData : str, test string data
|
||||
|
||||
Optional Arguments :
|
||||
filename : str, Defaults to 'test.txt'
|
||||
"""
|
||||
filename = kwargs.get("filename", "test.gif")
|
||||
data = base64.b64decode(str_base64)
|
||||
filedata = BytesIO(data)
|
||||
filestream = InMemoryUploadedFile(
|
||||
filedata, None, filename, "image", len(data), None
|
||||
)
|
||||
return filestream
|
||||
|
||||
def _create_test_image(self, path):
|
||||
# Get the form index
|
||||
plugin_index = -1
|
||||
for cnt, plugin_instance in enumerate(plugin_registry.get_sidebar()):
|
||||
if isinstance(plugin_instance, ImagePlugin):
|
||||
plugin_index = cnt
|
||||
break
|
||||
self.assertGreaterEqual(
|
||||
plugin_index, 0, msg="Image plugin not activated"
|
||||
)
|
||||
base_edit_url = reverse("wiki:edit", kwargs={"path": path})
|
||||
url = base_edit_url + f"?f=form{plugin_index:d}"
|
||||
filestream = self._create_gif_filestream_from_base64(self.test_data)
|
||||
response = self.client.post(
|
||||
url,
|
||||
{
|
||||
"unsaved_article_title": self.article.current_revision.title,
|
||||
"unsaved_article_content": self.article.current_revision.content,
|
||||
"image": filestream,
|
||||
"images_save": "1",
|
||||
},
|
||||
)
|
||||
self.assertRedirects(response, base_edit_url)
|
||||
|
||||
def test_index(self):
|
||||
url = reverse("wiki:images_index", kwargs={"path": ""})
|
||||
response = self.client.get(
|
||||
url,
|
||||
)
|
||||
self.assertContains(response, "Images")
|
||||
|
||||
def test_upload(self):
|
||||
"""
|
||||
Tests that simple file upload uploads correctly
|
||||
Uploading a file should preserve the original filename.
|
||||
Uploading should not modify file in any way.
|
||||
"""
|
||||
self._create_test_image("")
|
||||
# Check the object was created.
|
||||
image = models.Image.objects.get()
|
||||
image_revision = image.current_revision.imagerevision
|
||||
self.assertEqual(image_revision.get_filename(), "test.gif")
|
||||
self.assertEqual(
|
||||
image_revision.image.file.read(), base64.b64decode(self.test_data)
|
||||
)
|
||||
|
||||
def get_article(self, cont, image):
|
||||
urlpath = URLPath.create_urlpath(
|
||||
URLPath.root(), "html_image", title="TestImage", content=cont
|
||||
)
|
||||
if image:
|
||||
self._create_test_image(urlpath.path)
|
||||
return urlpath.article.render()
|
||||
|
||||
def test_image_missing(self):
|
||||
output = self.get_article("[image:1]", False)
|
||||
expected = (
|
||||
'<figure class="thumbnail"><a href="">'
|
||||
'<div class="caption"><em>Image not found</em></div>'
|
||||
'</a><figcaption class="caption"></figcaption></figure>'
|
||||
)
|
||||
self.assertEqual(output, expected)
|
||||
|
||||
def test_image_default(self):
|
||||
output = self.get_article("[image:1]", True)
|
||||
image_rev = models.Image.objects.get().current_revision.imagerevision
|
||||
expected = re.compile(
|
||||
r'<figure class="thumbnail">'
|
||||
r'<a href="' + re.escape(image_rev.image.url) + '">'
|
||||
r'<img src="/?cache/.*\.jpg" alt="test\.gif">'
|
||||
r'</a><figcaption class="caption"></figcaption></figure>'
|
||||
)
|
||||
self.assertRegex(output, expected)
|
||||
|
||||
def test_image_large_right(self):
|
||||
output = self.get_article("[image:1 align:right size:large]", True)
|
||||
image_rev = models.Image.objects.get().current_revision.imagerevision
|
||||
expected = re.compile(
|
||||
r'<figure class="thumbnail float-right">'
|
||||
r'<a href="' + re.escape(image_rev.image.url) + '">'
|
||||
r'<img src="/?cache/.*\.jpg" alt="test\.gif"></a>'
|
||||
r'<figcaption class="caption"></figcaption></figure>'
|
||||
)
|
||||
self.assertRegex(output, expected)
|
||||
|
||||
def test_image_orig(self):
|
||||
output = self.get_article("[image:1 size:orig]", True)
|
||||
image_rev = models.Image.objects.get().current_revision.imagerevision
|
||||
expected = (
|
||||
'<figure class="thumbnail">'
|
||||
'<a href="' + image_rev.image.url + '">'
|
||||
'<img src="' + image_rev.image.url + '" alt="test.gif"></a>'
|
||||
'<figcaption class="caption"></figcaption></figure>'
|
||||
)
|
||||
self.assertEqual(output, expected)
|
||||
|
||||
# https://gist.github.com/guillaumepiot/817a70706587da3bd862835c59ef584e
|
||||
def generate_photo_file(self):
|
||||
file = BytesIO()
|
||||
image = Image.new("RGBA", size=(100, 100), color=(155, 0, 0))
|
||||
image.save(file, "gif")
|
||||
file.name = "test.gif"
|
||||
file.seek(0)
|
||||
return file
|
||||
|
||||
def test_add_revision(self):
|
||||
self._create_test_image(path="")
|
||||
image = models.Image.objects.get()
|
||||
before_edit_rev = image.current_revision.revision_number
|
||||
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"wiki:images_add_revision",
|
||||
kwargs={
|
||||
"article_id": self.root_article,
|
||||
"image_id": image.pk,
|
||||
"path": "",
|
||||
},
|
||||
),
|
||||
data={"image": self.generate_photo_file()},
|
||||
)
|
||||
self.assertRedirects(
|
||||
response, reverse("wiki:edit", kwargs={"path": ""})
|
||||
)
|
||||
image = models.Image.objects.get()
|
||||
self.assertEqual(models.Image.objects.count(), 1)
|
||||
self.assertEqual(
|
||||
image.current_revision.previous_revision.revision_number,
|
||||
before_edit_rev,
|
||||
)
|
||||
|
||||
def test_delete_restore_revision(self):
|
||||
self._create_test_image(path="")
|
||||
image = models.Image.objects.get()
|
||||
before_edit_rev = image.current_revision.revision_number
|
||||
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"wiki:images_delete",
|
||||
kwargs={
|
||||
"article_id": self.root_article,
|
||||
"image_id": image.pk,
|
||||
"path": "",
|
||||
},
|
||||
),
|
||||
)
|
||||
self.assertRedirects(
|
||||
response, reverse("wiki:images_index", kwargs={"path": ""})
|
||||
)
|
||||
image = models.Image.objects.get()
|
||||
self.assertEqual(models.Image.objects.count(), 1)
|
||||
self.assertEqual(
|
||||
image.current_revision.previous_revision.revision_number,
|
||||
before_edit_rev,
|
||||
)
|
||||
self.assertIs(image.current_revision.deleted, True)
|
||||
|
||||
# RESTORE
|
||||
before_edit_rev = image.current_revision.revision_number
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"wiki:images_restore",
|
||||
kwargs={
|
||||
"article_id": self.root_article,
|
||||
"image_id": image.pk,
|
||||
"path": "",
|
||||
},
|
||||
),
|
||||
)
|
||||
self.assertRedirects(
|
||||
response, reverse("wiki:images_index", kwargs={"path": ""})
|
||||
)
|
||||
image = models.Image.objects.get()
|
||||
self.assertEqual(models.Image.objects.count(), 1)
|
||||
self.assertEqual(
|
||||
image.current_revision.previous_revision.revision_number,
|
||||
before_edit_rev,
|
||||
)
|
||||
self.assertFalse(image.current_revision.deleted)
|
||||
|
||||
def test_purge(self):
|
||||
"""
|
||||
Tests that an image is really purged
|
||||
"""
|
||||
self._create_test_image(path="")
|
||||
image = models.Image.objects.get()
|
||||
image_revision = image.current_revision.imagerevision
|
||||
f_path = image_revision.image.file.name
|
||||
|
||||
self.assertIs(os.path.exists(f_path), True)
|
||||
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"wiki:images_purge",
|
||||
kwargs={
|
||||
"article_id": self.root_article,
|
||||
"image_id": image.pk,
|
||||
"path": "",
|
||||
},
|
||||
),
|
||||
data={"confirm": True},
|
||||
)
|
||||
self.assertRedirects(
|
||||
response, reverse("wiki:images_index", kwargs={"path": ""})
|
||||
)
|
||||
self.assertEqual(models.Image.objects.count(), 0)
|
||||
self.assertIs(os.path.exists(f_path), False)
|
||||
|
||||
def test_add_revision_purge_image(self):
|
||||
"""
|
||||
Tests that an image with more than one revision is really purged
|
||||
"""
|
||||
# use another test to stage this one
|
||||
self.test_add_revision()
|
||||
|
||||
image = models.Image.objects.get()
|
||||
image_revision = image.current_revision.imagerevision
|
||||
f_path = image_revision.image.file.name
|
||||
|
||||
self.assertIs(os.path.exists(f_path), True)
|
||||
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"wiki:images_purge",
|
||||
kwargs={
|
||||
"article_id": self.root_article,
|
||||
"image_id": image.pk,
|
||||
"path": "",
|
||||
},
|
||||
),
|
||||
data={"confirm": True},
|
||||
)
|
||||
self.assertRedirects(
|
||||
response, reverse("wiki:images_index", kwargs={"path": ""})
|
||||
)
|
||||
self.assertEqual(models.Image.objects.count(), 0)
|
||||
self.assertIs(os.path.exists(f_path), False)
|
||||
|
||||
@wiki_override_settings(ACCOUNT_HANDLING=True)
|
||||
def test_login_on_revision_add(self):
|
||||
self._create_test_image(path="")
|
||||
self.client.logout()
|
||||
image = models.Image.objects.get()
|
||||
url = reverse(
|
||||
"wiki:images_add_revision",
|
||||
kwargs={
|
||||
"article_id": self.root_article,
|
||||
"image_id": image.pk,
|
||||
"path": "",
|
||||
},
|
||||
)
|
||||
response = self.client.post(
|
||||
url, data={"image": self.generate_photo_file()}
|
||||
)
|
||||
self.assertRedirects(
|
||||
response, "{}?next={}".format(reverse("wiki:login"), url)
|
||||
)
|
||||
0
tests/plugins/links/__init__.py
Normal file
0
tests/plugins/links/__init__.py
Normal file
124
tests/plugins/links/test_links.py
Normal file
124
tests/plugins/links/test_links.py
Normal file
@@ -0,0 +1,124 @@
|
||||
import markdown
|
||||
from ddt import data
|
||||
from ddt import ddt
|
||||
from ddt import unpack
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse_lazy
|
||||
from wiki.models import URLPath
|
||||
from wiki.plugins.links.mdx.djangowikilinks import WikiPathExtension
|
||||
|
||||
from tests.base import wiki_override_settings
|
||||
|
||||
FIXTURE_POSITIVE_MATCHES_TRAILING_SLASH = [
|
||||
(
|
||||
"[Français](wiki:/fr)",
|
||||
'<p><a class="wikipath linknotfound" href="/fr/">Français</a></p>',
|
||||
),
|
||||
(
|
||||
# Link to an existing page
|
||||
"[Test link](wiki:/linktest)",
|
||||
'<p><a class="wikipath" href="/linktest/">Test link</a></p>',
|
||||
),
|
||||
(
|
||||
# Link with an empty fragment
|
||||
"[Test link](wiki:/linktest#)",
|
||||
'<p><a class="wikipath" href="/linktest/#/">Test link</a></p>',
|
||||
),
|
||||
(
|
||||
# Link to a header in an existing page
|
||||
"[Test head](wiki:/linktest#wiki-toc-a-section)",
|
||||
'<p><a class="wikipath" href="/linktest/#wiki-toc-a-section/">Test head</a></p>',
|
||||
),
|
||||
(
|
||||
# Link to a header in a non existing page
|
||||
"[Test head nonExist](wiki:/linktesterr#wiki-toc-a-section)",
|
||||
'<p><a class="wikipath linknotfound" href="/linktesterr#wiki-toc-a-section/">Test head nonExist</a></p>',
|
||||
),
|
||||
(
|
||||
# Invalid Wiki link: The default markdown link parser takes over
|
||||
"[Test head err](wiki:/linktest#wiki-toc-a-section#err)",
|
||||
'<p><a href="wiki:/linktest#wiki-toc-a-section#err">Test head err</a></p>',
|
||||
),
|
||||
]
|
||||
FIXTURE_POSITIVE_MATCHES_NO_TRAILING_SLASH = [
|
||||
(
|
||||
"[Français](wiki:/fr)",
|
||||
'<p><a class="wikipath linknotfound" href="/fr">Français</a></p>',
|
||||
),
|
||||
(
|
||||
# Link to an existing page
|
||||
"[Test link](wiki:/linktest)",
|
||||
'<p><a class="wikipath" href="/linktest">Test link</a></p>',
|
||||
),
|
||||
(
|
||||
# Relative path
|
||||
"[Test link](wiki:linktest)",
|
||||
'<p><a class="wikipath" href="/linktest">Test link</a></p>',
|
||||
),
|
||||
(
|
||||
# Link with an empty fragment
|
||||
"[Test link](wiki:/linktest#)",
|
||||
'<p><a class="wikipath" href="/linktest/#">Test link</a></p>',
|
||||
),
|
||||
(
|
||||
# Link to a header in an existing page
|
||||
"[Test head](wiki:/linktest#wiki-toc-a-section)",
|
||||
'<p><a class="wikipath" href="/linktest/#wiki-toc-a-section">Test head</a></p>',
|
||||
),
|
||||
(
|
||||
# Link to a header in a non existing page
|
||||
"[Test head nonExist](wiki:/linktesterr#wiki-toc-a-section)",
|
||||
'<p><a class="wikipath linknotfound" href="/linktesterr#wiki-toc-a-section">Test head nonExist</a></p>',
|
||||
),
|
||||
(
|
||||
# Invalid Wiki link: The default markdown link parser takes over
|
||||
"[Test head err](wiki:/linktest#wiki-toc-a-section#err)",
|
||||
'<p><a href="wiki:/linktest#wiki-toc-a-section#err">Test head err</a></p>',
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@ddt
|
||||
class WikiPathExtensionTests(TestCase):
|
||||
"""
|
||||
Test the wikilinks markdown plugin.
|
||||
I could not get it to work with `@pytest.mark.parametrize` so using `ddt` instead
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
config = (("base_url", reverse_lazy("wiki:get", kwargs={"path": ""})),)
|
||||
URLPath.create_root()
|
||||
urlpath = URLPath.create_urlpath(
|
||||
URLPath.root(),
|
||||
"linktest",
|
||||
title="LinkTest",
|
||||
content="A page\n#A section\nA line",
|
||||
user_message="Comment1",
|
||||
)
|
||||
# TODO: Use wiki.core.markdown.article_markdown
|
||||
self.md = markdown.Markdown(
|
||||
extensions=["extra", WikiPathExtension(config)]
|
||||
)
|
||||
self.md.article = urlpath.article
|
||||
|
||||
@wiki_override_settings(WIKI_WIKILINKS_TRAILING_SLASH=True)
|
||||
@data(*FIXTURE_POSITIVE_MATCHES_TRAILING_SLASH)
|
||||
@unpack
|
||||
def test_works_with_lazy_functions_slashes(
|
||||
self, markdown_input, expected_output
|
||||
):
|
||||
self.assertEqual(
|
||||
self.md.convert(markdown_input),
|
||||
expected_output,
|
||||
)
|
||||
|
||||
@wiki_override_settings(WIKI_WIKILINKS_TRAILING_SLASH=False)
|
||||
@data(*FIXTURE_POSITIVE_MATCHES_NO_TRAILING_SLASH)
|
||||
@unpack
|
||||
def test_works_with_lazy_functions_no_slashes(
|
||||
self, markdown_input, expected_output
|
||||
):
|
||||
self.assertEqual(
|
||||
self.md.convert(markdown_input),
|
||||
expected_output,
|
||||
)
|
||||
262
tests/plugins/links/test_urlize.py
Normal file
262
tests/plugins/links/test_urlize.py
Normal file
@@ -0,0 +1,262 @@
|
||||
import html
|
||||
|
||||
import markdown
|
||||
import pytest
|
||||
from wiki.plugins.links.mdx.urlize import makeExtension
|
||||
from wiki.plugins.links.mdx.urlize import UrlizeExtension
|
||||
|
||||
# Template accepts two strings - href value and link text value.
|
||||
EXPECTED_LINK_TEMPLATE = (
|
||||
'<a href="%s" rel="nofollow" target="_blank">'
|
||||
'<span class="fa fa-external-link-alt">'
|
||||
"</span>"
|
||||
"<span>"
|
||||
" %s"
|
||||
"</span>"
|
||||
"</a>"
|
||||
)
|
||||
|
||||
# Template accepts two strings - href value and link text value.
|
||||
EXPECTED_PARAGRAPH_TEMPLATE = "<p>%s</p>" % EXPECTED_LINK_TEMPLATE
|
||||
|
||||
|
||||
FIXTURE_POSITIVE_MATCHES = [
|
||||
# Test surrounding begin/end characters.
|
||||
(
|
||||
"(example.com)",
|
||||
"<p>("
|
||||
+ EXPECTED_LINK_TEMPLATE % ("http://example.com", "example.com")
|
||||
+ ")</p>",
|
||||
),
|
||||
(
|
||||
"<example.com>",
|
||||
"<p><"
|
||||
+ EXPECTED_LINK_TEMPLATE % ("http://example.com", "example.com")
|
||||
+ "></p>",
|
||||
),
|
||||
# Test protocol specification.
|
||||
(
|
||||
"http://example.com",
|
||||
EXPECTED_PARAGRAPH_TEMPLATE
|
||||
% ("http://example.com", "http://example.com"),
|
||||
),
|
||||
(
|
||||
"https://example.com",
|
||||
EXPECTED_PARAGRAPH_TEMPLATE
|
||||
% ("https://example.com", "https://example.com"),
|
||||
),
|
||||
(
|
||||
"ftp://example.com",
|
||||
EXPECTED_PARAGRAPH_TEMPLATE
|
||||
% ("ftp://example.com", "ftp://example.com"),
|
||||
),
|
||||
(
|
||||
"ftps://example.com",
|
||||
EXPECTED_PARAGRAPH_TEMPLATE
|
||||
% ("ftps://example.com", "ftps://example.com"),
|
||||
),
|
||||
(
|
||||
"example.com",
|
||||
EXPECTED_PARAGRAPH_TEMPLATE % ("http://example.com", "example.com"),
|
||||
),
|
||||
(
|
||||
"onion://example.com",
|
||||
EXPECTED_PARAGRAPH_TEMPLATE
|
||||
% ("onion://example.com", "onion://example.com"),
|
||||
),
|
||||
(
|
||||
"onion9+.-://example.com",
|
||||
EXPECTED_PARAGRAPH_TEMPLATE
|
||||
% ("onion9+.-://example.com", "onion9+.-://example.com"),
|
||||
),
|
||||
# Test various supported host variations.
|
||||
(
|
||||
"10.10.1.1",
|
||||
EXPECTED_PARAGRAPH_TEMPLATE % ("http://10.10.1.1", "10.10.1.1"),
|
||||
),
|
||||
(
|
||||
"1122:3344:5566:7788:9900:aabb:ccdd:eeff",
|
||||
EXPECTED_PARAGRAPH_TEMPLATE
|
||||
% (
|
||||
"http://1122:3344:5566:7788:9900:aabb:ccdd:eeff",
|
||||
"1122:3344:5566:7788:9900:aabb:ccdd:eeff",
|
||||
),
|
||||
),
|
||||
(
|
||||
"1122:3344:5566:7788:9900:AaBb:cCdD:EeFf",
|
||||
EXPECTED_PARAGRAPH_TEMPLATE
|
||||
% (
|
||||
"http://1122:3344:5566:7788:9900:AaBb:cCdD:EeFf",
|
||||
"1122:3344:5566:7788:9900:AaBb:cCdD:EeFf",
|
||||
),
|
||||
),
|
||||
("::1", EXPECTED_PARAGRAPH_TEMPLATE % ("http://::1", "::1")),
|
||||
("1::2:3", EXPECTED_PARAGRAPH_TEMPLATE % ("http://1::2:3", "1::2:3")),
|
||||
("1::", EXPECTED_PARAGRAPH_TEMPLATE % ("http://1::", "1::")),
|
||||
("::", EXPECTED_PARAGRAPH_TEMPLATE % ("http://::", "::")),
|
||||
(
|
||||
"example.com",
|
||||
EXPECTED_PARAGRAPH_TEMPLATE % ("http://example.com", "example.com"),
|
||||
),
|
||||
(
|
||||
"example.horse",
|
||||
EXPECTED_PARAGRAPH_TEMPLATE
|
||||
% ("http://example.horse", "example.horse"),
|
||||
),
|
||||
(
|
||||
"my.long.domain.example.com",
|
||||
EXPECTED_PARAGRAPH_TEMPLATE
|
||||
% ("http://my.long.domain.example.com", "my.long.domain.example.com"),
|
||||
),
|
||||
# Test port section.
|
||||
(
|
||||
"10.1.1.1:8000",
|
||||
EXPECTED_PARAGRAPH_TEMPLATE
|
||||
% ("http://10.1.1.1:8000", "10.1.1.1:8000"),
|
||||
),
|
||||
# Test trailing path specification.
|
||||
(
|
||||
"http://example.com/",
|
||||
EXPECTED_PARAGRAPH_TEMPLATE
|
||||
% ("http://example.com/", "http://example.com/"),
|
||||
),
|
||||
(
|
||||
"http://example.com/my/path",
|
||||
EXPECTED_PARAGRAPH_TEMPLATE
|
||||
% ("http://example.com/my/path", "http://example.com/my/path"),
|
||||
),
|
||||
(
|
||||
"http://example.com/my/path?param1=value1¶m2=value2",
|
||||
EXPECTED_PARAGRAPH_TEMPLATE
|
||||
% (
|
||||
"http://example.com/my/path?param1=value1&param2=value2",
|
||||
"http://example.com/my/path?param1=value1&param2=value2",
|
||||
),
|
||||
),
|
||||
# Link positioned somewhere within the text, but around whitespace boundary.
|
||||
(
|
||||
"This is link myhost.example.com",
|
||||
"<p>This is link "
|
||||
+ EXPECTED_LINK_TEMPLATE
|
||||
% ("http://myhost.example.com", "myhost.example.com")
|
||||
+ "</p>",
|
||||
),
|
||||
(
|
||||
"myhost.example.com is the link",
|
||||
"<p>"
|
||||
+ EXPECTED_LINK_TEMPLATE
|
||||
% ("http://myhost.example.com", "myhost.example.com")
|
||||
+ " is the link</p>",
|
||||
),
|
||||
(
|
||||
"I have best myhost.example.com link ever",
|
||||
"<p>I have best "
|
||||
+ EXPECTED_LINK_TEMPLATE
|
||||
% ("http://myhost.example.com", "myhost.example.com")
|
||||
+ " link ever</p>",
|
||||
),
|
||||
(
|
||||
"I have best\nmyhost.example.com link ever",
|
||||
"<p>I have best\n"
|
||||
+ EXPECTED_LINK_TEMPLATE
|
||||
% ("http://myhost.example.com", "myhost.example.com")
|
||||
+ " link ever</p>",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
FIXTURE_NEGATIVE_MATCHES = [
|
||||
# localhost as part of another word.
|
||||
("localhosts", "<p>localhosts</p>"),
|
||||
("localhost", "<p>localhost</p>"),
|
||||
("localhost:8000", "<p>localhost:8000</p>"),
|
||||
# Incomplete FQDNs.
|
||||
("example.", "<p>example.</p>"),
|
||||
(".example .com", "<p>.example .com</p>"),
|
||||
# Invalid FQDNs.
|
||||
("example-.com", "<p>example-.com</p>"),
|
||||
("-example.com", "<p>-example.com</p>"),
|
||||
("my.-example.com", "<p>my.-example.com</p>"),
|
||||
# Invalid IPv6 patterns.
|
||||
(
|
||||
"1:2:3:4:5:6:7:8:a", # Use :a, because using a number would match as optional port
|
||||
"<p>1:2:3:4:5:6:7:8:a</p>",
|
||||
),
|
||||
(
|
||||
"1::2::3",
|
||||
"<p>1::2::3</p>",
|
||||
),
|
||||
(
|
||||
"::::1",
|
||||
"<p>::::1</p>",
|
||||
),
|
||||
(
|
||||
"1::::",
|
||||
"<p>1::::</p>",
|
||||
),
|
||||
# Invalid IPv4 patterns.
|
||||
(
|
||||
"1.2.3.4.5",
|
||||
"<p>1.2.3.4.5</p>",
|
||||
),
|
||||
# Invalid protocols.
|
||||
(
|
||||
"9onion://example.com",
|
||||
"<p>9onion://example.com</p>",
|
||||
),
|
||||
(
|
||||
"-onion://example.com",
|
||||
"<p>-onion://example.com</p>",
|
||||
),
|
||||
(
|
||||
"+onion://example.com",
|
||||
"<p>+onion://example.com</p>",
|
||||
),
|
||||
(
|
||||
".onion://example.com",
|
||||
"<p>.onion://example.com</p>",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class TestUrlizeExtension:
|
||||
def setup_method(self):
|
||||
self.md = markdown.Markdown(extensions=[UrlizeExtension()])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"markdown_text, expected_output", FIXTURE_POSITIVE_MATCHES
|
||||
)
|
||||
def test_positive_matches(self, markdown_text, expected_output):
|
||||
assert self.md.convert(markdown_text) == expected_output
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"markdown_text, expected_output", FIXTURE_NEGATIVE_MATCHES
|
||||
)
|
||||
def test_negative_matches(self, markdown_text, expected_output):
|
||||
assert self.md.convert(markdown_text) == expected_output
|
||||
|
||||
def test_url_with_non_matching_begin_and_end_ignored(self):
|
||||
assert self.md.convert("(example.com>") == "<p>%s</p>" % html.escape(
|
||||
"(example.com>"
|
||||
)
|
||||
assert self.md.convert("<example.com)") == "<p>%s</p>" % html.escape(
|
||||
"<example.com)"
|
||||
)
|
||||
assert self.md.convert("(example.com") == "<p>%s</p>" % html.escape(
|
||||
"(example.com"
|
||||
)
|
||||
assert self.md.convert("example.com)") == "<p>%s</p>" % html.escape(
|
||||
"example.com)"
|
||||
)
|
||||
assert self.md.convert("<example.com") == "<p>%s</p>" % html.escape(
|
||||
"<example.com"
|
||||
)
|
||||
assert self.md.convert("example.com>") == "<p>%s</p>" % html.escape(
|
||||
"example.com>"
|
||||
)
|
||||
|
||||
|
||||
def test_makeExtension_return_value():
|
||||
extension = makeExtension()
|
||||
|
||||
assert isinstance(extension, UrlizeExtension)
|
||||
0
tests/plugins/macros/__init__.py
Normal file
0
tests/plugins/macros/__init__.py
Normal file
14
tests/plugins/macros/test_links.py
Normal file
14
tests/plugins/macros/test_links.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from wiki.core import markdown
|
||||
|
||||
from tests.base import RequireRootArticleMixin
|
||||
from tests.base import TestBase
|
||||
|
||||
|
||||
class WikiLinksTests(RequireRootArticleMixin, TestBase):
|
||||
def test_wikilink(self):
|
||||
md = markdown.ArticleMarkdown(article=self.root_article)
|
||||
md_text = md.convert("[[Root Article]]")
|
||||
self.assertEqual(
|
||||
md_text,
|
||||
'<p><a class="wiki_wikilink wiki-broken" href="/Root_Article/">Root Article</a></p>',
|
||||
)
|
||||
18
tests/plugins/macros/test_macro.py
Normal file
18
tests/plugins/macros/test_macro.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from wiki.core import markdown
|
||||
|
||||
from tests.base import RequireRootArticleMixin
|
||||
from tests.base import TestBase
|
||||
|
||||
|
||||
class MacroTests(RequireRootArticleMixin, TestBase):
|
||||
def test_article_list(self):
|
||||
md = markdown.ArticleMarkdown(article=self.root_article)
|
||||
md_text = md.convert("[article_list depth:2]")
|
||||
self.assertIn("Nothing below this level", md_text)
|
||||
self.assertNotIn("[article_list depth:2]", md_text)
|
||||
|
||||
def test_escape(self):
|
||||
md = markdown.ArticleMarkdown(article=self.root_article)
|
||||
md_text = md.convert("`[article_list depth:2]`")
|
||||
self.assertNotIn("Nothing below this level", md_text)
|
||||
self.assertIn("[article_list depth:2]", md_text)
|
||||
345
tests/plugins/macros/test_toc.py
Normal file
345
tests/plugins/macros/test_toc.py
Normal file
@@ -0,0 +1,345 @@
|
||||
from django.test import TestCase
|
||||
from markdown import Markdown
|
||||
from wiki.core import markdown
|
||||
from wiki.plugins.macros.mdx.toc import WikiTocExtension
|
||||
|
||||
from tests.base import RequireRootArticleMixin
|
||||
from tests.base import TestBase
|
||||
|
||||
|
||||
class TocMacroTests(TestCase):
|
||||
maxDiff = None
|
||||
|
||||
def test_toc_renders_table_of_content(self):
|
||||
"""Verifies that the [TOC] wiki code renders a Table of Content"""
|
||||
md = Markdown(extensions=["extra", WikiTocExtension()])
|
||||
text = (
|
||||
"[TOC]\n"
|
||||
"\n"
|
||||
"# First title.\n"
|
||||
"\n"
|
||||
"Paragraph 1\n"
|
||||
"\n"
|
||||
"## Subsection\n"
|
||||
"\n"
|
||||
"Paragraph 2"
|
||||
)
|
||||
expected_output = (
|
||||
'<div class="toc">\n'
|
||||
"<ul>\n"
|
||||
'<li><a href="#wiki-toc-first-title">First title.</a><ul>\n'
|
||||
'<li><a href="#wiki-toc-subsection">Subsection</a></li>\n'
|
||||
"</ul>\n"
|
||||
"</li>\n"
|
||||
"</ul>\n"
|
||||
"</div>\n"
|
||||
'<h1 id="wiki-toc-first-title">First title.</h1>\n'
|
||||
"<p>Paragraph 1</p>\n"
|
||||
'<h2 id="wiki-toc-subsection">Subsection</h2>\n'
|
||||
"<p>Paragraph 2</p>"
|
||||
)
|
||||
self.assertEqual(md.convert(text), expected_output)
|
||||
|
||||
def test_toc_renders_table_of_content_with_kwargs(self):
|
||||
"""Verifies that the [TOC] wiki code renders a Table of Content"""
|
||||
md = Markdown(extensions=["extra", WikiTocExtension(title="test")])
|
||||
text = (
|
||||
"[TOC]\n"
|
||||
"\n"
|
||||
"# First title.\n"
|
||||
"\n"
|
||||
"Paragraph 1\n"
|
||||
"\n"
|
||||
"## Subsection\n"
|
||||
"\n"
|
||||
"Paragraph 2"
|
||||
)
|
||||
expected_output = (
|
||||
'<div class="toc"><span class="toctitle">test</span><ul>\n'
|
||||
'<li><a href="#wiki-toc-first-title">First title.</a><ul>\n'
|
||||
'<li><a href="#wiki-toc-subsection">Subsection</a></li>\n'
|
||||
"</ul>\n"
|
||||
"</li>\n"
|
||||
"</ul>\n"
|
||||
"</div>\n"
|
||||
'<h1 id="wiki-toc-first-title">First title.</h1>\n'
|
||||
"<p>Paragraph 1</p>\n"
|
||||
'<h2 id="wiki-toc-subsection">Subsection</h2>\n'
|
||||
"<p>Paragraph 2</p>"
|
||||
)
|
||||
self.assertEqual(md.convert(text), expected_output)
|
||||
|
||||
|
||||
class TocMacroTestsInWiki(RequireRootArticleMixin, TestBase):
|
||||
def test_toc_renders_table_of_content_in_wiki(self):
|
||||
md = markdown.ArticleMarkdown(article=self.root_article)
|
||||
text = (
|
||||
"[TOC]\n"
|
||||
"\n"
|
||||
"# First title.\n"
|
||||
"\n"
|
||||
"Paragraph 1\n"
|
||||
"\n"
|
||||
"## Subsection\n"
|
||||
"\n"
|
||||
"Paragraph 2"
|
||||
)
|
||||
expected_output = (
|
||||
'<div class="toc"><span class="toctitle">Contents</span><ul>\n'
|
||||
'<li><a href="#wiki-toc-first-title">First title.</a><ul>\n'
|
||||
'<li><a href="#wiki-toc-subsection">Subsection</a></li>\n'
|
||||
"</ul>\n"
|
||||
"</li>\n"
|
||||
"</ul>\n"
|
||||
"</div>\n"
|
||||
'<h1 id="wiki-toc-first-title">First title.<a class="article-edit-title-link" '
|
||||
'href="/_plugin/editsection/header/wiki-toc-first-title/">[edit]</a></h1>\n'
|
||||
"<p>Paragraph 1</p>\n"
|
||||
'<h2 id="wiki-toc-subsection">Subsection<a class="article-edit-title-link" '
|
||||
'href="/_plugin/editsection/header/wiki-toc-subsection/">[edit]</a></h2>\n'
|
||||
"<p>Paragraph 2</p>"
|
||||
)
|
||||
self.assertEqual(md.convert(text), expected_output)
|
||||
|
||||
def test_toc_renders_table_of_content_with_toc_class(self):
|
||||
md = markdown.ArticleMarkdown(article=self.root_article)
|
||||
text = (
|
||||
"[TOC toc_class:'nontoc test']\n"
|
||||
"\n"
|
||||
"# First title.\n"
|
||||
"\n"
|
||||
"Paragraph 1\n"
|
||||
"\n"
|
||||
"## Subsection\n"
|
||||
"\n"
|
||||
"Paragraph 2"
|
||||
)
|
||||
expected_output = (
|
||||
'<div class="nontoc test"><span class="toctitle">Contents</span><ul>\n'
|
||||
'<li><a href="#wiki-toc-first-title">First title.</a><ul>\n'
|
||||
'<li><a href="#wiki-toc-subsection">Subsection</a></li>\n'
|
||||
"</ul>\n"
|
||||
"</li>\n"
|
||||
"</ul>\n"
|
||||
"</div>\n"
|
||||
'<h1 id="wiki-toc-first-title">First title.<a class="article-edit-title-link" '
|
||||
'href="/_plugin/editsection/header/wiki-toc-first-title/">[edit]</a></h1>\n'
|
||||
"<p>Paragraph 1</p>\n"
|
||||
'<h2 id="wiki-toc-subsection">Subsection<a class="article-edit-title-link" '
|
||||
'href="/_plugin/editsection/header/wiki-toc-subsection/">[edit]</a></h2>\n'
|
||||
"<p>Paragraph 2</p>"
|
||||
)
|
||||
self.assertEqual(md.convert(text), expected_output)
|
||||
|
||||
def test_toc_renders_table_of_content_in_wiki_with_kwargs(self):
|
||||
md = markdown.ArticleMarkdown(article=self.root_article)
|
||||
text = (
|
||||
"[TOC title:test]\n"
|
||||
"\n"
|
||||
"# First title.\n"
|
||||
"\n"
|
||||
"Paragraph 1\n"
|
||||
"\n"
|
||||
"## Subsection\n"
|
||||
"\n"
|
||||
"Paragraph 2"
|
||||
)
|
||||
expected_output = (
|
||||
'<div class="toc"><span class="toctitle">test</span><ul>\n'
|
||||
'<li><a href="#wiki-toc-first-title">First title.</a><ul>\n'
|
||||
'<li><a href="#wiki-toc-subsection">Subsection</a></li>\n'
|
||||
"</ul>\n"
|
||||
"</li>\n"
|
||||
"</ul>\n"
|
||||
"</div>\n"
|
||||
'<h1 id="wiki-toc-first-title">First title.<a class="article-edit-title-link" '
|
||||
'href="/_plugin/editsection/header/wiki-toc-first-title/">[edit]</a></h1>\n'
|
||||
"<p>Paragraph 1</p>\n"
|
||||
'<h2 id="wiki-toc-subsection">Subsection<a class="article-edit-title-link" '
|
||||
'href="/_plugin/editsection/header/wiki-toc-subsection/">[edit]</a></h2>\n'
|
||||
"<p>Paragraph 2</p>"
|
||||
)
|
||||
self.assertEqual(md.convert(text), expected_output)
|
||||
|
||||
def test_toc_renders_table_of_content_in_wiki_with_depth(self):
|
||||
md = markdown.ArticleMarkdown(article=self.root_article)
|
||||
text = (
|
||||
"[TOC toc_depth:1]\n"
|
||||
"\n"
|
||||
"# First title.\n"
|
||||
"\n"
|
||||
"Paragraph 1\n"
|
||||
"\n"
|
||||
"## Subsection\n"
|
||||
"\n"
|
||||
"Paragraph 2"
|
||||
)
|
||||
expected_output = (
|
||||
'<div class="toc"><span class="toctitle">Contents</span><ul>\n'
|
||||
'<li><a href="#wiki-toc-first-title">First title.</a></li>\n'
|
||||
"</ul>\n"
|
||||
"</div>\n"
|
||||
'<h1 id="wiki-toc-first-title">First title.<a class="article-edit-title-link" '
|
||||
'href="/_plugin/editsection/header/wiki-toc-first-title/">[edit]</a></h1>\n'
|
||||
"<p>Paragraph 1</p>\n"
|
||||
'<h2 id="wiki-toc-subsection">Subsection<a class="article-edit-title-link" '
|
||||
'href="/_plugin/editsection/header/wiki-toc-subsection/">[edit]</a></h2>\n'
|
||||
"<p>Paragraph 2</p>"
|
||||
)
|
||||
self.assertEqual(md.convert(text), expected_output)
|
||||
|
||||
def test_toc_renders_table_of_content_in_wiki_with_multi_kwargs(self):
|
||||
md = markdown.ArticleMarkdown(article=self.root_article)
|
||||
text = (
|
||||
"[TOC title:'test' toc_depth:'1' anchorlink:'True']\n"
|
||||
"\n"
|
||||
"# First title.\n"
|
||||
"\n"
|
||||
"Paragraph 1\n"
|
||||
"\n"
|
||||
"## Subsection\n"
|
||||
"\n"
|
||||
"Paragraph 2"
|
||||
)
|
||||
expected_output = (
|
||||
'<div class="toc"><span class="toctitle">test</span><ul>\n'
|
||||
'<li><a href="#wiki-toc-first-title">First title.</a></li>\n'
|
||||
"</ul>\n"
|
||||
"</div>\n"
|
||||
'<h1 id="wiki-toc-first-title"><a class="toclink" '
|
||||
'href="#wiki-toc-first-title">First title.</a><a '
|
||||
'class="article-edit-title-link" '
|
||||
'href="/_plugin/editsection/header/wiki-toc-first-title/">[edit]</a></h1>\n'
|
||||
"<p>Paragraph 1</p>\n"
|
||||
'<h2 id="wiki-toc-subsection"><a class="toclink" '
|
||||
'href="#wiki-toc-subsection">Subsection</a><a class="article-edit-title-link" '
|
||||
'href="/_plugin/editsection/header/wiki-toc-subsection/">[edit]</a></h2>\n'
|
||||
"<p>Paragraph 2</p>"
|
||||
)
|
||||
self.assertEqual(md.convert(text), expected_output)
|
||||
|
||||
def test_toc_renders_table_of_content_in_wiki_wrong_type(self):
|
||||
md = markdown.ArticleMarkdown(article=self.root_article)
|
||||
text = (
|
||||
"[TOC anchorlink:Yes]\n"
|
||||
"\n"
|
||||
"# First title.\n"
|
||||
"\n"
|
||||
"Paragraph 1\n"
|
||||
"\n"
|
||||
"## Subsection\n"
|
||||
"\n"
|
||||
"Paragraph 2"
|
||||
)
|
||||
expected_output = (
|
||||
'<div class="toc"><span class="toctitle">Contents</span><ul>\n'
|
||||
'<li><a href="#wiki-toc-first-title">First title.</a><ul>\n'
|
||||
'<li><a href="#wiki-toc-subsection">Subsection</a></li>\n'
|
||||
"</ul>\n"
|
||||
"</li>\n"
|
||||
"</ul>\n"
|
||||
"</div>\n"
|
||||
'<h1 id="wiki-toc-first-title">First title.<a class="article-edit-title-link" '
|
||||
'href="/_plugin/editsection/header/wiki-toc-first-title/">[edit]</a></h1>\n'
|
||||
"<p>Paragraph 1</p>\n"
|
||||
'<h2 id="wiki-toc-subsection">Subsection<a class="article-edit-title-link" '
|
||||
'href="/_plugin/editsection/header/wiki-toc-subsection/">[edit]</a></h2>\n'
|
||||
"<p>Paragraph 2</p>"
|
||||
)
|
||||
self.assertEqual(md.convert(text), expected_output)
|
||||
|
||||
def test_toc_renders_table_of_content_in_wiki_test_bool_one(self):
|
||||
# Test if the integer is 1 and should be True
|
||||
md = markdown.ArticleMarkdown(article=self.root_article)
|
||||
text = (
|
||||
"[TOC anchorlink:1]\n"
|
||||
"\n"
|
||||
"# First title.\n"
|
||||
"\n"
|
||||
"Paragraph 1\n"
|
||||
"\n"
|
||||
"## Subsection\n"
|
||||
"\n"
|
||||
"Paragraph 2"
|
||||
)
|
||||
expected_output = (
|
||||
'<div class="toc"><span class="toctitle">Contents</span><ul>\n'
|
||||
'<li><a href="#wiki-toc-first-title">First title.</a><ul>\n'
|
||||
'<li><a href="#wiki-toc-subsection">Subsection</a></li>\n'
|
||||
"</ul>\n"
|
||||
"</li>\n"
|
||||
"</ul>\n"
|
||||
"</div>\n"
|
||||
'<h1 id="wiki-toc-first-title"><a class="toclink" '
|
||||
'href="#wiki-toc-first-title">First title.</a><a '
|
||||
'class="article-edit-title-link" '
|
||||
'href="/_plugin/editsection/header/wiki-toc-first-title/">[edit]</a></h1>\n'
|
||||
"<p>Paragraph 1</p>\n"
|
||||
'<h2 id="wiki-toc-subsection"><a class="toclink" '
|
||||
'href="#wiki-toc-subsection">Subsection</a><a class="article-edit-title-link" '
|
||||
'href="/_plugin/editsection/header/wiki-toc-subsection/">[edit]</a></h2>\n'
|
||||
"<p>Paragraph 2</p>"
|
||||
)
|
||||
self.assertEqual(md.convert(text), expected_output)
|
||||
|
||||
def test_toc_renders_table_of_content_in_wiki_test_bool_zero(self):
|
||||
# Test if the integer is zero and should be false
|
||||
md = markdown.ArticleMarkdown(article=self.root_article)
|
||||
text = (
|
||||
"[TOC anchorlink:0]\n"
|
||||
"\n"
|
||||
"# First title.\n"
|
||||
"\n"
|
||||
"Paragraph 1\n"
|
||||
"\n"
|
||||
"## Subsection\n"
|
||||
"\n"
|
||||
"Paragraph 2"
|
||||
)
|
||||
expected_output = (
|
||||
'<div class="toc"><span class="toctitle">Contents</span><ul>\n'
|
||||
'<li><a href="#wiki-toc-first-title">First title.</a><ul>\n'
|
||||
'<li><a href="#wiki-toc-subsection">Subsection</a></li>\n'
|
||||
"</ul>\n"
|
||||
"</li>\n"
|
||||
"</ul>\n"
|
||||
"</div>\n"
|
||||
'<h1 id="wiki-toc-first-title">First title.<a class="article-edit-title-link" '
|
||||
'href="/_plugin/editsection/header/wiki-toc-first-title/">[edit]</a></h1>\n'
|
||||
"<p>Paragraph 1</p>\n"
|
||||
'<h2 id="wiki-toc-subsection">Subsection<a class="article-edit-title-link" '
|
||||
'href="/_plugin/editsection/header/wiki-toc-subsection/">[edit]</a></h2>\n'
|
||||
"<p>Paragraph 2</p>"
|
||||
)
|
||||
self.assertEqual(md.convert(text), expected_output)
|
||||
|
||||
def test_toc_renders_table_of_content_in_wiki_test_bool_wrong(self):
|
||||
# Test if the integer is wrong value
|
||||
md = markdown.ArticleMarkdown(article=self.root_article)
|
||||
text = (
|
||||
"[TOC anchorlink:5]\n"
|
||||
"\n"
|
||||
"# First title.\n"
|
||||
"\n"
|
||||
"Paragraph 1\n"
|
||||
"\n"
|
||||
"## Subsection\n"
|
||||
"\n"
|
||||
"Paragraph 2"
|
||||
)
|
||||
expected_output = (
|
||||
'<div class="toc"><span class="toctitle">Contents</span><ul>\n'
|
||||
'<li><a href="#wiki-toc-first-title">First title.</a><ul>\n'
|
||||
'<li><a href="#wiki-toc-subsection">Subsection</a></li>\n'
|
||||
"</ul>\n"
|
||||
"</li>\n"
|
||||
"</ul>\n"
|
||||
"</div>\n"
|
||||
'<h1 id="wiki-toc-first-title">First title.<a class="article-edit-title-link" '
|
||||
'href="/_plugin/editsection/header/wiki-toc-first-title/">[edit]</a></h1>\n'
|
||||
"<p>Paragraph 1</p>\n"
|
||||
'<h2 id="wiki-toc-subsection">Subsection<a class="article-edit-title-link" '
|
||||
'href="/_plugin/editsection/header/wiki-toc-subsection/">[edit]</a></h2>\n'
|
||||
"<p>Paragraph 2</p>"
|
||||
)
|
||||
self.assertEqual(md.convert(text), expected_output)
|
||||
0
tests/plugins/notifications/__init__.py
Normal file
0
tests/plugins/notifications/__init__.py
Normal file
9
tests/plugins/notifications/test_forms.py
Normal file
9
tests/plugins/notifications/test_forms.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from django.test import TestCase
|
||||
from wiki.plugins.notifications.forms import SettingsFormSet
|
||||
|
||||
from tests.base import RequireSuperuserMixin
|
||||
|
||||
|
||||
class SettingsFormTests(RequireSuperuserMixin, TestCase):
|
||||
def test_formset(self):
|
||||
SettingsFormSet(user=self.superuser1)
|
||||
76
tests/plugins/notifications/test_views.py
Normal file
76
tests/plugins/notifications/test_views.py
Normal file
@@ -0,0 +1,76 @@
|
||||
from django.shortcuts import resolve_url
|
||||
from django_nyt.models import Settings
|
||||
|
||||
from tests.base import ArticleWebTestUtils
|
||||
from tests.base import DjangoClientTestBase
|
||||
from tests.base import RequireRootArticleMixin
|
||||
|
||||
|
||||
class NotificationSettingsTests(
|
||||
RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase
|
||||
):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
def test_login_required(self):
|
||||
self.client.logout()
|
||||
response = self.client.get(resolve_url("wiki:notification_settings"))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_when_logged_in(self):
|
||||
response = self.client.get(resolve_url("wiki:notification_settings"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(
|
||||
response, "wiki/plugins/notifications/settings.html"
|
||||
)
|
||||
|
||||
def test_change_settings(self):
|
||||
self.settings, __ = Settings.objects.get_or_create(
|
||||
user=self.superuser1, is_default=True
|
||||
)
|
||||
|
||||
url = resolve_url("wiki:notification_settings")
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = {"csrf_token": response.context["csrf_token"]}
|
||||
|
||||
# management form information, needed because of the formset
|
||||
management_form = response.context["form"].management_form
|
||||
|
||||
for i in (
|
||||
"TOTAL_FORMS",
|
||||
"INITIAL_FORMS",
|
||||
"MIN_NUM_FORMS",
|
||||
"MAX_NUM_FORMS",
|
||||
):
|
||||
data[f"{management_form.prefix}-{i}"] = management_form[i].value()
|
||||
|
||||
for i in range(response.context["form"].total_form_count()):
|
||||
# get form index 'i'
|
||||
current_form = response.context["form"].forms[i]
|
||||
|
||||
# retrieve all the fields
|
||||
for field_name in current_form.fields:
|
||||
value = current_form[field_name].value()
|
||||
data[f"{current_form.prefix}-{field_name}"] = (
|
||||
value if value is not None else ""
|
||||
)
|
||||
|
||||
data["form-TOTAL_FORMS"] = 1
|
||||
data["form-0-email"] = 2
|
||||
data["form-0-interval"] = 0
|
||||
# post the request without any change
|
||||
|
||||
response = self.client.post(url, data, follow=True)
|
||||
|
||||
self.assertEqual(len(response.context.get("messages")), 1)
|
||||
|
||||
message = response.context.get("messages")._loaded_messages[0]
|
||||
self.assertIn(
|
||||
message.message,
|
||||
"You will receive notifications instantly for 0 articles",
|
||||
)
|
||||
|
||||
# Ensure we didn't create redundant Settings objects
|
||||
assert self.superuser1.nyt_settings.all().count() == 1
|
||||
0
tests/plugins/pymdown/__init__.py
Normal file
0
tests/plugins/pymdown/__init__.py
Normal file
339
tests/plugins/pymdown/test_pymdown.py
Normal file
339
tests/plugins/pymdown/test_pymdown.py
Normal file
@@ -0,0 +1,339 @@
|
||||
from django.test import TestCase
|
||||
from markdown import Markdown
|
||||
from wiki.core import markdown
|
||||
from wiki.plugins.pymdown import wiki_plugin
|
||||
|
||||
from tests.base import RequireRootArticleMixin
|
||||
from tests.base import TestBase
|
||||
|
||||
|
||||
class TocMacroTests(TestCase):
|
||||
"""
|
||||
This is used to test the PyMdown extensions module independently of Django Wiki. If this fails
|
||||
it should because something has changed on with PyMdown or Markdown itself.
|
||||
"""
|
||||
|
||||
def test_pymdown_renders_block_details(self):
|
||||
extensions = ["extra"]
|
||||
extensions.extend(wiki_plugin.PymdownPlugin.markdown_extensions)
|
||||
md = Markdown(extensions=extensions)
|
||||
text = "/// details | Some summary\n" "\n" "Some content\b" "///\n"
|
||||
expected_output = (
|
||||
"<details>\n"
|
||||
"<summary>Some summary</summary>\n"
|
||||
"<p>Some content\x08///</p>\n"
|
||||
"</details>"
|
||||
)
|
||||
self.assertEqual(expected_output, md.convert(text))
|
||||
|
||||
def test_pymdown_renders_block_details_with_type(self):
|
||||
extensions = ["extra"]
|
||||
extensions.extend(wiki_plugin.PymdownPlugin.markdown_extensions)
|
||||
md = Markdown(extensions=extensions)
|
||||
text = (
|
||||
"/// details | Some summary\n"
|
||||
" type: warning\n"
|
||||
"\n"
|
||||
"Some content\b"
|
||||
"///\n"
|
||||
)
|
||||
expected_output = (
|
||||
'<details class="warning">\n'
|
||||
"<summary>Some summary</summary>\n"
|
||||
"<p>Some content\x08///</p>\n"
|
||||
"</details>"
|
||||
)
|
||||
self.assertEqual(expected_output, md.convert(text))
|
||||
|
||||
def test_pymdown_renders_block_admonition(self):
|
||||
extensions = ["extra"]
|
||||
extensions.extend(wiki_plugin.PymdownPlugin.markdown_extensions)
|
||||
md = Markdown(extensions=extensions)
|
||||
text = "/// admonition | Some summary\n" "Some content\b" "///\n"
|
||||
expected_output = (
|
||||
'<div class="admonition">\n'
|
||||
'<p class="admonition-title">Some summary</p>\n'
|
||||
"<p>Some content\x08///</p>\n"
|
||||
"</div>"
|
||||
)
|
||||
self.assertEqual(expected_output, md.convert(text))
|
||||
|
||||
def test_pymdown_renders_block_admonition_with_type(self):
|
||||
extensions = ["extra"]
|
||||
extensions.extend(wiki_plugin.PymdownPlugin.markdown_extensions)
|
||||
md = Markdown(extensions=extensions)
|
||||
text = (
|
||||
"/// admonition | Some summary\n"
|
||||
" type: warning\n"
|
||||
"Some content\b"
|
||||
"///\n"
|
||||
)
|
||||
expected_output = (
|
||||
'<div class="admonition warning">\n'
|
||||
'<p class="admonition-title">Some summary</p>\n'
|
||||
"<p>Some content\x08///</p>\n"
|
||||
"</div>"
|
||||
)
|
||||
self.assertEqual(expected_output, md.convert(text))
|
||||
|
||||
def test_pymdown_renders_block_definition(self):
|
||||
extensions = ["extra"]
|
||||
extensions.extend(wiki_plugin.PymdownPlugin.markdown_extensions)
|
||||
md = Markdown(extensions=extensions)
|
||||
text = (
|
||||
"/// define\n"
|
||||
"Apple\n"
|
||||
"\n"
|
||||
"- Pomaceous fruit of plants of the genus Malu in the family Rosaceae.\n"
|
||||
"///\n"
|
||||
)
|
||||
expected_output = (
|
||||
"<dl>\n"
|
||||
"<dt>Apple</dt>\n"
|
||||
"<dd>Pomaceous fruit of plants of the genus Malu in the family "
|
||||
"Rosaceae.</dd>\n"
|
||||
"</dl>"
|
||||
)
|
||||
self.assertEqual(expected_output, md.convert(text))
|
||||
|
||||
def test_pymdown_renders_block_definition_multiples(self):
|
||||
extensions = ["extra"]
|
||||
extensions.extend(wiki_plugin.PymdownPlugin.markdown_extensions)
|
||||
md = Markdown(extensions=extensions)
|
||||
text = (
|
||||
"/// define\n"
|
||||
"Apple\n"
|
||||
"\n"
|
||||
"- Pomaceous fruit of plants of the genus Malu in the family Rosaceae.\n"
|
||||
"\n"
|
||||
"Orange\n"
|
||||
"\n"
|
||||
"- The fruit of an evergreen tree of hte genus Citrus.\n"
|
||||
"///\n"
|
||||
)
|
||||
expected_output = (
|
||||
"<dl>\n"
|
||||
"<dt>Apple</dt>\n"
|
||||
"<dd>Pomaceous fruit of plants of the genus Malu in the family "
|
||||
"Rosaceae.</dd>\n"
|
||||
"<dt>Orange</dt>\n"
|
||||
"<dd>The fruit of an evergreen tree of hte genus Citrus.</dd>\n"
|
||||
"</dl>"
|
||||
)
|
||||
self.assertEqual(expected_output, md.convert(text))
|
||||
|
||||
def test_pymdown_renders_block_definition_multiple_terms(self):
|
||||
extensions = ["extra"]
|
||||
extensions.extend(wiki_plugin.PymdownPlugin.markdown_extensions)
|
||||
md = Markdown(extensions=extensions)
|
||||
text = (
|
||||
"/// define\n"
|
||||
"Term 1\n"
|
||||
"\n"
|
||||
"Term 2\n"
|
||||
"- Definition a\n"
|
||||
"\n"
|
||||
"Term 3\n"
|
||||
"\n"
|
||||
"- Definition b\n"
|
||||
"///\n"
|
||||
)
|
||||
expected_output = (
|
||||
"<dl>\n"
|
||||
"<dt>Term 1</dt>\n"
|
||||
"<dt>Term 2\n"
|
||||
"- Definition a</dt>\n"
|
||||
"<dt>Term 3</dt>\n"
|
||||
"<dd>Definition b</dd>\n"
|
||||
"</dl>"
|
||||
)
|
||||
self.assertEqual(expected_output, md.convert(text))
|
||||
|
||||
def test_pymdown_renders_block_html_wrap(self):
|
||||
extensions = ["extra"]
|
||||
extensions.extend(wiki_plugin.PymdownPlugin.markdown_extensions)
|
||||
md = Markdown(extensions=extensions)
|
||||
text = (
|
||||
"/// html | div[stype='border: 1px solid red;']\n"
|
||||
"some *markdown* content\n"
|
||||
"///\n"
|
||||
)
|
||||
expected_output = (
|
||||
'<div stype="border: 1px solid red;">\n'
|
||||
"<p>some <em>markdown</em> content</p>\n"
|
||||
"</div>"
|
||||
)
|
||||
self.assertEqual(expected_output, md.convert(text))
|
||||
|
||||
|
||||
class TocMacroTestsInWiki(RequireRootArticleMixin, TestBase):
|
||||
def test_pymdown_in_wiki_renders_block_details(self):
|
||||
wiki_plugin.settings.update_whitelist() # Fixes testing bug
|
||||
md = markdown.ArticleMarkdown(
|
||||
article=self.root_article,
|
||||
extensions=wiki_plugin.PymdownPlugin.markdown_extensions,
|
||||
)
|
||||
text = "/// details | Some summary\n" "\n" "Some content\n" "///\n"
|
||||
expected_output = (
|
||||
"<details>\n"
|
||||
"<summary>Some summary</summary>\n"
|
||||
"<p>Some content</p>\n"
|
||||
"</details>"
|
||||
)
|
||||
self.assertEqual(expected_output, md.convert(text))
|
||||
|
||||
def test_pymdown_in_wiki_renders_block_details_with_type(self):
|
||||
wiki_plugin.settings.update_whitelist() # Fixes testing bug
|
||||
md = markdown.ArticleMarkdown(
|
||||
article=self.root_article,
|
||||
extensions=wiki_plugin.PymdownPlugin.markdown_extensions,
|
||||
)
|
||||
text = (
|
||||
"/// details | Some summary\n"
|
||||
" type: warning\n"
|
||||
"Some content\n"
|
||||
"///\n"
|
||||
)
|
||||
expected_output = (
|
||||
'<details class="warning">\n'
|
||||
"<summary>Some summary</summary>\n"
|
||||
"<p>Some content</p>\n"
|
||||
"</details>"
|
||||
)
|
||||
self.assertEqual(expected_output, md.convert(text))
|
||||
|
||||
def test_pymdown_in_wiki_renders_block_admonition(self):
|
||||
md = markdown.ArticleMarkdown(
|
||||
article=self.root_article,
|
||||
extensions=wiki_plugin.PymdownPlugin.markdown_extensions,
|
||||
)
|
||||
text = "/// admonition | Some summary\n" "Some content.\n" "///\n"
|
||||
expected_output = (
|
||||
'<div class="admonition">\n'
|
||||
'<p class="admonition-title">Some summary</p>\n'
|
||||
"<p>Some content.</p>\n"
|
||||
"</div>"
|
||||
)
|
||||
self.assertEqual(expected_output, md.convert(text))
|
||||
|
||||
def test_pymdown_in_wiki_renders_block_admonition_with_type(self):
|
||||
md = markdown.ArticleMarkdown(
|
||||
article=self.root_article,
|
||||
extensions=wiki_plugin.PymdownPlugin.markdown_extensions,
|
||||
)
|
||||
text = (
|
||||
"/// admonition | Some summary\n"
|
||||
" type: warning\n"
|
||||
"Some content.\n"
|
||||
"///\n"
|
||||
)
|
||||
expected_output = (
|
||||
'<div class="admonition warning">\n'
|
||||
'<p class="admonition-title">Some summary</p>\n'
|
||||
"<p>Some content.</p>\n"
|
||||
"</div>"
|
||||
)
|
||||
self.assertEqual(expected_output, md.convert(text))
|
||||
|
||||
def test_pymdown_in_wiki_renders_block_definition(self):
|
||||
md = markdown.ArticleMarkdown(
|
||||
article=self.root_article,
|
||||
extensions=wiki_plugin.PymdownPlugin.markdown_extensions,
|
||||
)
|
||||
text = (
|
||||
"/// define\n"
|
||||
"Apple\n"
|
||||
"\n"
|
||||
"- Pomaceous fruit of plants of the genus Malu in the family Rosaceae.\n"
|
||||
"///\n"
|
||||
)
|
||||
expected_output = (
|
||||
"<dl>\n"
|
||||
"<dt>Apple</dt>\n"
|
||||
"<dd>Pomaceous fruit of plants of the genus Malu in the family "
|
||||
"Rosaceae.</dd>\n"
|
||||
"</dl>"
|
||||
)
|
||||
self.assertEqual(expected_output, md.convert(text))
|
||||
|
||||
def test_pymdown_in_wiki_renders_block_definition_multiples(self):
|
||||
md = markdown.ArticleMarkdown(
|
||||
article=self.root_article,
|
||||
extensions=wiki_plugin.PymdownPlugin.markdown_extensions,
|
||||
)
|
||||
text = (
|
||||
"/// define\n"
|
||||
"Apple\n"
|
||||
"\n"
|
||||
"- Pomaceous fruit of plants of the genus Malu in the family Rosaceae.\n"
|
||||
"\n"
|
||||
"Orange\n"
|
||||
"\n"
|
||||
"- The fruit of an evergreen tree of hte genus Citrus.\n"
|
||||
"///\n"
|
||||
)
|
||||
expected_output = (
|
||||
"<dl>\n"
|
||||
"<dt>Apple</dt>\n"
|
||||
"<dd>Pomaceous fruit of plants of the genus Malu in the family "
|
||||
"Rosaceae.</dd>\n"
|
||||
"<dt>Orange</dt>\n"
|
||||
"<dd>The fruit of an evergreen tree of hte genus Citrus.</dd>\n"
|
||||
"</dl>"
|
||||
)
|
||||
self.assertEqual(expected_output, md.convert(text))
|
||||
|
||||
def test_pymdown_in_wiki_renders_block_definition_multiple_terms(self):
|
||||
md = markdown.ArticleMarkdown(
|
||||
article=self.root_article,
|
||||
extensions=wiki_plugin.PymdownPlugin.markdown_extensions,
|
||||
)
|
||||
text = (
|
||||
"/// define\n"
|
||||
"Term 1\n"
|
||||
"\n"
|
||||
"Term 2\n"
|
||||
"- Definition a\n"
|
||||
"\n"
|
||||
"Term 3\n"
|
||||
"\n"
|
||||
"- Definition b\n"
|
||||
"///\n"
|
||||
)
|
||||
expected_output = (
|
||||
"<dl>\n"
|
||||
"<dt>Term 1</dt>\n"
|
||||
"<dt>Term 2\n"
|
||||
"- Definition a</dt>\n"
|
||||
"<dt>Term 3</dt>\n"
|
||||
"<dd>Definition b</dd>\n"
|
||||
"</dl>"
|
||||
)
|
||||
self.assertEqual(expected_output, md.convert(text))
|
||||
|
||||
def test_pymdown_in_wiki_renders_block_html_wrap(self):
|
||||
md = markdown.ArticleMarkdown(
|
||||
article=self.root_article,
|
||||
extensions=wiki_plugin.PymdownPlugin.markdown_extensions,
|
||||
)
|
||||
text = "/// html | div.my-class\n" "some *markdown* content\n" "///\n"
|
||||
expected_output = '<div class="my-class">\n<p>some <em>markdown</em> content</p>\n</div>'
|
||||
self.assertEqual(expected_output, md.convert(text))
|
||||
|
||||
def test_pymdown_in_wiki_renders_block_html_wrap_test_bleach(self):
|
||||
"""
|
||||
The tags get bleached and thus this doesn't work.
|
||||
"""
|
||||
md = markdown.ArticleMarkdown(
|
||||
article=self.root_article,
|
||||
extensions=wiki_plugin.PymdownPlugin.markdown_extensions,
|
||||
)
|
||||
text = (
|
||||
"/// html | div[stype='border: 1px solid red;']\n"
|
||||
"some *markdown* content\n"
|
||||
"///\n"
|
||||
)
|
||||
expected_output = (
|
||||
"<div>\n<p>some <em>markdown</em> content</p>\n</div>"
|
||||
)
|
||||
self.assertEqual(expected_output, md.convert(text))
|
||||
0
tests/plugins/redlinks/__init__.py
Normal file
0
tests/plugins/redlinks/__init__.py
Normal file
97
tests/plugins/redlinks/test_redlinks.py
Normal file
97
tests/plugins/redlinks/test_redlinks.py
Normal file
@@ -0,0 +1,97 @@
|
||||
from django.urls import reverse
|
||||
from wiki.core import markdown
|
||||
from wiki.models import URLPath
|
||||
|
||||
from ...base import wiki_override_settings
|
||||
from tests.base import RequireRootArticleMixin
|
||||
from tests.base import TestBase
|
||||
|
||||
|
||||
class RedlinksTests(RequireRootArticleMixin, TestBase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.child = URLPath.create_urlpath(self.root, "child")
|
||||
|
||||
def test_root_to_self(self):
|
||||
self.assert_internal(self.root, "[Internal](./)")
|
||||
|
||||
def test_root_to_child(self):
|
||||
self.assert_internal(self.root, "[Child](child/)")
|
||||
|
||||
def test_child_to_self(self):
|
||||
self.assert_internal(self.child, "[Child](../child/)")
|
||||
|
||||
def test_child_to_self_no_slash(self):
|
||||
self.assert_internal(self.child, "[Child](../child)")
|
||||
|
||||
def test_root_to_outside(self):
|
||||
self.assert_external(
|
||||
self.root, "[Outside](http://outside.example.org/)"
|
||||
)
|
||||
|
||||
def test_absolute_external(self):
|
||||
if reverse("wiki:get", kwargs={"path": ""}) == "/":
|
||||
# All absolute paths could be wiki-internal, and the server root is
|
||||
# the the wiki root, which is bound to exist.
|
||||
self.assert_internal(self.root, "[Server Root](/)")
|
||||
else:
|
||||
# The wiki root is below the server root, so the server root is an
|
||||
# external link.
|
||||
self.assert_external(self.root, "[Server Root](/)")
|
||||
self.assert_external(self.root, "[Static File](/static/)")
|
||||
|
||||
def test_absolute_internal(self):
|
||||
wiki_root = reverse("wiki:get", kwargs={"path": ""})
|
||||
self.assert_internal(self.root, f"[Server Root]({wiki_root})")
|
||||
|
||||
def test_child_to_broken(self):
|
||||
self.assert_broken(self.child, "[Broken](../broken/)")
|
||||
|
||||
def test_root_to_broken(self):
|
||||
self.assert_broken(self.root, "[Broken](broken/)")
|
||||
|
||||
def test_not_a_link(self):
|
||||
self.assert_none(self.root, '<a id="anchor">old-style anchor</a>')
|
||||
|
||||
def test_invalid_url(self):
|
||||
self.assert_none(self.root, "[Invalid](http://127[.500.20.1/)")
|
||||
|
||||
def test_mailto(self):
|
||||
self.assert_none(self.root, "<foo@example.com>")
|
||||
|
||||
def assert_none(self, urlpath, md_text):
|
||||
md = markdown.ArticleMarkdown(article=urlpath.article)
|
||||
html = md.convert(md_text)
|
||||
self.assertNotIn("wiki-internal", html)
|
||||
self.assertNotIn("wiki-external", html)
|
||||
self.assertNotIn("wiki-broken", html)
|
||||
self.assertIn("<a", html)
|
||||
|
||||
def assert_internal(self, urlpath, md_text):
|
||||
md = markdown.ArticleMarkdown(article=urlpath.article)
|
||||
html = md.convert(md_text)
|
||||
self.assertIn("wiki-internal", html)
|
||||
self.assertNotIn("wiki-external", html)
|
||||
self.assertNotIn("wiki-broken", html)
|
||||
|
||||
def assert_external(self, urlpath, md_text):
|
||||
md = markdown.ArticleMarkdown(article=urlpath.article)
|
||||
html = md.convert(md_text)
|
||||
self.assertNotIn("wiki-internal", html)
|
||||
self.assertIn("wiki-external", html)
|
||||
self.assertNotIn("wiki-broken", html)
|
||||
|
||||
def assert_broken(self, urlpath, md_text):
|
||||
md = markdown.ArticleMarkdown(article=urlpath.article)
|
||||
html = md.convert(md_text)
|
||||
self.assertNotIn("wiki-internal", html)
|
||||
self.assertNotIn("wiki-external", html)
|
||||
self.assertIn("wiki-broken", html)
|
||||
|
||||
|
||||
@wiki_override_settings(
|
||||
WIKI_URL_CONFIG_CLASS="tests.core.test_models.WikiCustomUrlPatterns",
|
||||
ROOT_URLCONF="tests.core.test_urls",
|
||||
)
|
||||
class RedLinksWithChangedBaseURL(RedlinksTests):
|
||||
pass
|
||||
69
tests/settings.py
Normal file
69
tests/settings.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import os
|
||||
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
TESTS_DATA_ROOT = os.path.dirname(__file__)
|
||||
|
||||
MEDIA_ROOT = os.path.join(TESTS_DATA_ROOT, "media")
|
||||
|
||||
DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3"}}
|
||||
|
||||
DEBUG = True
|
||||
AUTH_USER_MODEL = "testdata.CustomUser"
|
||||
WIKI_GROUP_MODEL = "testdata.CustomGroup"
|
||||
SITE_ID = 1
|
||||
ROOT_URLCONF = "tests.testdata.urls"
|
||||
INSTALLED_APPS = [
|
||||
"tests.testdata",
|
||||
"django.contrib.auth.apps.AuthConfig",
|
||||
"django.contrib.contenttypes.apps.ContentTypesConfig",
|
||||
"django.contrib.sessions.apps.SessionsConfig",
|
||||
"django.contrib.admin.apps.AdminConfig",
|
||||
"django.contrib.humanize.apps.HumanizeConfig",
|
||||
"django.contrib.sites.apps.SitesConfig",
|
||||
"django.contrib.messages",
|
||||
"django_nyt.apps.DjangoNytConfig",
|
||||
"mptt",
|
||||
"sekizai",
|
||||
"sorl.thumbnail",
|
||||
"wiki.apps.WikiConfig",
|
||||
"wiki.plugins.attachments.apps.AttachmentsConfig",
|
||||
"wiki.plugins.editsection.apps.EditSectionConfig",
|
||||
"wiki.plugins.notifications.apps.NotificationsConfig",
|
||||
"wiki.plugins.images.apps.ImagesConfig",
|
||||
"wiki.plugins.macros.apps.MacrosConfig",
|
||||
"wiki.plugins.pymdown.apps.PyMdownConfig",
|
||||
"wiki.plugins.globalhistory.apps.GlobalHistoryConfig",
|
||||
"wiki.plugins.redlinks.apps.RedlinksConfig",
|
||||
]
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
]
|
||||
USE_TZ = True
|
||||
SECRET_KEY = "b^fv_)t39h%9p40)fnkfblo##jkr!$0)lkp6bpy!fi*f$4*92!"
|
||||
STATIC_URL = "/static/"
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.i18n",
|
||||
"django.template.context_processors.media",
|
||||
"django.template.context_processors.request",
|
||||
"django.template.context_processors.static",
|
||||
"django.template.context_processors.tz",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"sekizai.context_processors.sekizai",
|
||||
]
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
LOGIN_REDIRECT_URL = reverse_lazy("wiki:get", kwargs={"path": ""})
|
||||
0
tests/testdata/__init__.py
vendored
Normal file
0
tests/testdata/__init__.py
vendored
Normal file
172
tests/testdata/migrations/0001_initial.py
vendored
Normal file
172
tests/testdata/migrations/0001_initial.py
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
# Generated by Django 2.2.1 on 2019-07-03 19:03
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
import django.utils.timezone
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("auth", "0008_alter_user_username_max_length"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="CustomGroup",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="VeryCustomUser",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("password", models.CharField(max_length=128, verbose_name="password")),
|
||||
(
|
||||
"last_login",
|
||||
models.DateTimeField(
|
||||
blank=True, null=True, verbose_name="last login"
|
||||
),
|
||||
),
|
||||
("identifier", models.IntegerField()),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="CustomUser",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("password", models.CharField(max_length=128, verbose_name="password")),
|
||||
(
|
||||
"last_login",
|
||||
models.DateTimeField(
|
||||
blank=True, null=True, verbose_name="last login"
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_superuser",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Designates that this user has all permissions without explicitly assigning them.",
|
||||
verbose_name="superuser status",
|
||||
),
|
||||
),
|
||||
(
|
||||
"username",
|
||||
models.CharField(
|
||||
error_messages={
|
||||
"unique": "A user with that username already exists."
|
||||
},
|
||||
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
|
||||
max_length=150,
|
||||
unique=True,
|
||||
validators=[
|
||||
django.contrib.auth.validators.UnicodeUsernameValidator()
|
||||
],
|
||||
verbose_name="username",
|
||||
),
|
||||
),
|
||||
(
|
||||
"first_name",
|
||||
models.CharField(
|
||||
blank=True, max_length=30, verbose_name="first name"
|
||||
),
|
||||
),
|
||||
(
|
||||
"last_name",
|
||||
models.CharField(
|
||||
blank=True, max_length=150, verbose_name="last name"
|
||||
),
|
||||
),
|
||||
(
|
||||
"email",
|
||||
models.EmailField(
|
||||
blank=True, max_length=254, verbose_name="email address"
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_staff",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Designates whether the user can log into this admin site.",
|
||||
verbose_name="staff status",
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_active",
|
||||
models.BooleanField(
|
||||
default=True,
|
||||
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
|
||||
verbose_name="active",
|
||||
),
|
||||
),
|
||||
(
|
||||
"date_joined",
|
||||
models.DateTimeField(
|
||||
default=django.utils.timezone.now, verbose_name="date joined"
|
||||
),
|
||||
),
|
||||
("some_field", models.IntegerField(default=0)),
|
||||
(
|
||||
"groups",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.Group",
|
||||
verbose_name="groups",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_permissions",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="Specific permissions for this user.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.Permission",
|
||||
verbose_name="user permissions",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "user",
|
||||
"verbose_name_plural": "users",
|
||||
"abstract": False,
|
||||
},
|
||||
managers=[
|
||||
("objects", django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
]
|
||||
0
tests/testdata/migrations/__init__.py
vendored
Normal file
0
tests/testdata/migrations/__init__.py
vendored
Normal file
29
tests/testdata/models.py
vendored
Normal file
29
tests/testdata/models.py
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
from django.contrib.auth.base_user import AbstractBaseUser
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.db import models
|
||||
|
||||
|
||||
class CustomUser(AbstractUser):
|
||||
some_field = models.IntegerField(default=0)
|
||||
|
||||
custom_groups = models.ManyToManyField(
|
||||
"CustomGroup",
|
||||
verbose_name="groups",
|
||||
blank=True,
|
||||
help_text=(
|
||||
"The groups this user belongs to. A user will get all permissions "
|
||||
"granted to each of their groups."
|
||||
),
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
)
|
||||
|
||||
|
||||
class CustomGroup(models.Model):
|
||||
pass
|
||||
|
||||
|
||||
# user with invalid renamed identifier, and no email field
|
||||
class VeryCustomUser(AbstractBaseUser):
|
||||
identifier = models.IntegerField()
|
||||
USERNAME_FIELD = "identifier"
|
||||
26
tests/testdata/urls.py
vendored
Normal file
26
tests/testdata/urls.py
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
from django.conf import settings
|
||||
from django.contrib import admin
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
from django.urls import include
|
||||
from django.urls import re_path
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r"^admin/doc/", include("django.contrib.admindocs.urls")),
|
||||
re_path(r"^admin/", admin.site.urls),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += staticfiles_urlpatterns()
|
||||
urlpatterns += [
|
||||
re_path(
|
||||
r"^media/(?P<path>.*)$",
|
||||
"django.views.static.serve",
|
||||
{"document_root": settings.MEDIA_ROOT},
|
||||
),
|
||||
]
|
||||
|
||||
urlpatterns += [
|
||||
re_path(r"^django_functest/", include("django_functest.urls")),
|
||||
re_path(r"^notify/", include("django_nyt.urls")),
|
||||
re_path(r"", include("wiki.urls")),
|
||||
]
|
||||
Reference in New Issue
Block a user