Skip to content

Commit

Permalink
Reorganised tests.
Browse files Browse the repository at this point in the history
Separated tests from test app.
Moved wagtail test app inside tests/

Added --makemigrations [appname] argument for runtests.py as wagtail
app requires migration files to work and these may need to be updated.
  • Loading branch information
beatonma committed Nov 19, 2022
1 parent 19291dc commit ba3ece2
Show file tree
Hide file tree
Showing 76 changed files with 494 additions and 398 deletions.
85 changes: 75 additions & 10 deletions runtests.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,80 @@
#!env/bin/python
import os
import sys
from argparse import ArgumentParser

import django
from django.conf import settings
from django.core.management import execute_from_command_line
from django.test.utils import get_runner

SETTINGS_PATH = "tests.config.settings"

APPS = {
"test_app": {
"EXTRA_APPS": [],
},
"test_wagtail_app": {
"EXTRA_APPS": [
"wagtail.users",
"wagtail",
]
},
}

def _get_sys_args() -> list:
args_ = sys.argv
position = None
for index, x in enumerate(args_):
if x.endswith("runtests.py"):
position = index + 1
break

return args_[position:]
def parse_clargs():
parser = ArgumentParser()

parser.add_argument(
"--makemigrations",
nargs=1,
help="Run makemigrations for the given app.",
)

parsed, remaining_ = parser.parse_known_args()

if parsed.makemigrations:
parsed.app_name = parsed.makemigrations[0]

return parsed, remaining_


def _make_migrations(app_name: str):
extra_apps = APPS[app_name]["EXTRA_APPS"]
migrations_settings = {
"DEBUG": False,
"SECRET_KEY": "django-wm-fake-key",
"INSTALLED_APPS": [
"django.contrib.auth",
"django.contrib.contenttypes",
*extra_apps,
f"tests.{app_name}",
"mentions",
],
"DEFAULT_AUTO_FIELD": "django.db.models.BigAutoField",
}

settings.configure(**migrations_settings)
django.setup()

args = sys.argv + ["makemigrations", app_name]
execute_from_command_line(args)

sys.exit()


def _runtests():
def get_sys_args() -> list:
args_ = sys.argv
position = None
for index, x in enumerate(args_):
if x.endswith("runtests.py"):
position = index + 1
break

return args_[position:]

if __name__ == "__main__":
os.environ["DJANGO_SETTINGS_MODULE"] = SETTINGS_PATH

django.setup()
Expand All @@ -29,7 +83,18 @@ def _get_sys_args() -> list:

# Detailed report for any tests that are non-passing (failed, skipped...)
default_args = "-r a".split(" ")
args = _get_sys_args() or default_args
args = get_sys_args() or default_args
failures = test_runner.run_tests(["tests", *args])

sys.exit(bool(failures))


if __name__ == "__main__":
keep_clargs = sys.argv[0:1] # Save the script name.
clargs, remaining = parse_clargs()
sys.argv = keep_clargs + remaining

if clargs.makemigrations:
_make_migrations(clargs.app_name)
else:
_runtests()
198 changes: 0 additions & 198 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,198 +0,0 @@
import logging
from typing import Callable, Optional, Type, TypeVar, Union
from unittest.mock import Mock, patch

import requests
from django.conf import settings
from django.db import models
from django.db.models import QuerySet
from django.test import TestCase
from requests.structures import CaseInsensitiveDict

from mentions import options

log = logging.getLogger(__name__)

M = TypeVar("M", bound=models.Model)


class SimpleTestCase(TestCase):
pass


class WebmentionTestCase(SimpleTestCase):
def tearDown(self) -> None:
super().tearDown()
from mentions.models import (
HCard,
OutgoingWebmentionStatus,
PendingIncomingWebmention,
PendingOutgoingContent,
SimpleMention,
Webmention,
)
from tests.models import (
BadTestModelMissingAllText,
BadTestModelMissingGetAbsoluteUrl,
MentionableTestBlogPost,
MentionableTestModel,
SampleBlog,
)

app_models = [
Webmention,
OutgoingWebmentionStatus,
PendingIncomingWebmention,
PendingOutgoingContent,
HCard,
SimpleMention,
]
test_models = [
MentionableTestModel,
BadTestModelMissingAllText,
BadTestModelMissingGetAbsoluteUrl,
MentionableTestBlogPost,
SampleBlog,
]

all_models = [*app_models, *test_models]
for Model in all_models:
Model.objects.all().delete()

def assert_exists(
self,
model_class: Type[M],
count: int = 1,
**query,
) -> Union[M, QuerySet[M]]:
"""Assert that the expected number of model instances exist and return it/them."""
if count == 1:
try:
return model_class.objects.get(**query)
except (model_class.DoesNotExist, model_class.MultipleObjectsReturned) as e:
raise AssertionError(f"Expected 1 instance of {model_class}: {e}")

qs = model_class.objects.filter(**query)
self.assertEqual(count, qs.count())
return qs

def assert_not_exists(self, model_class: Type[M], **query):
qs = model_class.objects.filter(**query)
self.assertFalse(
qs.exists(),
msg=f"Model {model_class.__name__} with query={query} should not exist: {qs}",
)


class OptionsTestCase(WebmentionTestCase):
"""A TestCase that gracefully handles changes in django-wm options.
Any options that are changed during a test will be reset to the value
defined in global settings once the test is done."""

def setUp(self) -> None:
super().setUp()
self.defaults = options.get_config()

def tearDown(self) -> None:
super().tearDown()
for key, value in self.defaults.items():
setattr(settings, key, value)

if hasattr(settings, options.NAMESPACE):
delattr(settings, options.NAMESPACE)

def enable_celery(self, enable: bool):
setattr(settings, options.SETTING_USE_CELERY, enable)

def set_max_retries(self, n: int):
setattr(settings, options.SETTING_MAX_RETRIES, n)

def set_retry_interval(self, seconds: int):
setattr(settings, options.SETTING_RETRY_INTERVAL, seconds)

def set_dashboard_public(self, public: bool):
setattr(settings, options.SETTING_DASHBOARD_PUBLIC, public)

def set_incoming_target_model_required(self, requires_model: bool):
setattr(
settings,
options.SETTING_INCOMING_TARGET_MODEL_REQUIRED,
requires_model,
)

def set_allow_self_mentions(self, allow: bool):
setattr(settings, options.SETTING_ALLOW_SELF_MENTIONS, allow)


class MockResponse:
"""Mock of requests.Response."""

url: str
headers: Optional[CaseInsensitiveDict]
text: Optional[str]
status_code: Optional[int]

def __init__(
self,
url: str,
headers: Optional[dict] = None,
text: Optional[str] = None,
status_code: Optional[int] = None,
):
self.url = url
self.text = text
self.status_code = status_code
self.headers = CaseInsensitiveDict(headers or {"Content-Type": "text/html"})
log.debug(self)

def __str__(self):
return f"[{self.status_code}] {self.url}"


def patch_http_get(
status_code: int = 200,
text: Optional[str] = None,
headers: Optional[dict] = None,
response: Optional[Callable] = None,
):
headers = headers or {"content-type": "text/html"}

side_effect = (
response
if response
else lambda url, **kw: MockResponse(
url,
headers=headers,
text=text or "",
status_code=status_code,
)
)

return patch.object(
requests,
"get",
Mock(side_effect=side_effect),
)


def patch_http_post(
status_code: int = 200,
headers: Optional[dict] = None,
response: Optional[Callable] = None,
):
side_effect = (
response
if response
else lambda url, **kw: MockResponse(
url,
headers=headers,
status_code=status_code,
)
)

return patch.object(
requests,
"post",
Mock(side_effect=side_effect),
)
Loading

0 comments on commit ba3ece2

Please sign in to comment.