Add Docker-executed pytest suite with >90% coverage
This commit is contained in:
0
apps/blog/tests/__init__.py
Normal file
0
apps/blog/tests/__init__.py
Normal file
64
apps/blog/tests/factories.py
Normal file
64
apps/blog/tests/factories.py
Normal file
@@ -0,0 +1,64 @@
|
||||
import factory
|
||||
import wagtail_factories
|
||||
from django.utils import timezone
|
||||
from taggit.models import Tag
|
||||
from wagtail.models import Page
|
||||
|
||||
from apps.authors.models import Author
|
||||
from apps.blog.models import ArticleIndexPage, ArticlePage, HomePage, TagMetadata
|
||||
from apps.legal.models import LegalIndexPage, LegalPage
|
||||
|
||||
|
||||
class AuthorFactory(factory.django.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = Author
|
||||
|
||||
name = factory.Sequence(lambda n: f"Author {n}")
|
||||
slug = factory.Sequence(lambda n: f"author-{n}")
|
||||
|
||||
|
||||
class HomePageFactory(wagtail_factories.PageFactory):
|
||||
class Meta:
|
||||
model = HomePage
|
||||
|
||||
|
||||
class ArticleIndexPageFactory(wagtail_factories.PageFactory):
|
||||
class Meta:
|
||||
model = ArticleIndexPage
|
||||
|
||||
|
||||
class ArticlePageFactory(wagtail_factories.PageFactory):
|
||||
class Meta:
|
||||
model = ArticlePage
|
||||
|
||||
title = factory.Sequence(lambda n: f"Article {n}")
|
||||
slug = factory.Sequence(lambda n: f"article-{n}")
|
||||
author = factory.SubFactory(AuthorFactory)
|
||||
summary = "Summary"
|
||||
body = [("rich_text", "<p>Hello world</p>")]
|
||||
first_published_at = factory.LazyFunction(timezone.now)
|
||||
|
||||
|
||||
class LegalIndexPageFactory(wagtail_factories.PageFactory):
|
||||
class Meta:
|
||||
model = LegalIndexPage
|
||||
|
||||
|
||||
class LegalPageFactory(wagtail_factories.PageFactory):
|
||||
class Meta:
|
||||
model = LegalPage
|
||||
|
||||
title = factory.Sequence(lambda n: f"Legal {n}")
|
||||
slug = factory.Sequence(lambda n: f"legal-{n}")
|
||||
body = "<p>Body</p>"
|
||||
last_updated = factory.Faker("date_object")
|
||||
|
||||
|
||||
def root_page():
|
||||
return Page.get_first_root_node()
|
||||
|
||||
|
||||
def create_tag_with_meta(name: str, colour: str = "neutral"):
|
||||
tag, _ = Tag.objects.get_or_create(name=name, slug=name)
|
||||
TagMetadata.objects.get_or_create(tag=tag, defaults={"colour": colour})
|
||||
return tag
|
||||
8
apps/blog/tests/test_feeds.py
Normal file
8
apps/blog/tests/test_feeds.py
Normal file
@@ -0,0 +1,8 @@
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_feed_endpoint(client):
|
||||
resp = client.get("/feed/")
|
||||
assert resp.status_code == 200
|
||||
assert resp["Content-Type"].startswith("application/rss+xml")
|
||||
18
apps/blog/tests/test_feeds_more.py
Normal file
18
apps/blog/tests/test_feeds_more.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import pytest
|
||||
|
||||
from apps.blog.feeds import AllArticlesFeed
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_all_feed_methods(article_page):
|
||||
feed = AllArticlesFeed()
|
||||
assert feed.item_title(article_page) == article_page.title
|
||||
assert article_page.summary in feed.item_description(article_page)
|
||||
assert article_page.author.name == feed.item_author_name(article_page)
|
||||
assert feed.item_link(article_page).startswith("http")
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_tag_feed_not_found(client):
|
||||
resp = client.get("/feed/tag/does-not-exist/")
|
||||
assert resp.status_code == 404
|
||||
42
apps/blog/tests/test_models.py
Normal file
42
apps/blog/tests/test_models.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import pytest
|
||||
from django.db import IntegrityError
|
||||
from taggit.models import Tag
|
||||
|
||||
from apps.blog.models import ArticleIndexPage, ArticlePage, HomePage, TagMetadata
|
||||
from apps.blog.tests.factories import AuthorFactory
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_home_page_creation(home_page):
|
||||
assert HomePage.objects.count() == 1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_article_index_parent_restriction():
|
||||
assert ArticleIndexPage.parent_page_types == ["blog.HomePage"]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_article_compute_read_time_excludes_code(home_page):
|
||||
index = ArticleIndexPage(title="Articles", slug="articles")
|
||||
home_page.add_child(instance=index)
|
||||
author = AuthorFactory()
|
||||
article = ArticlePage(
|
||||
title="A",
|
||||
slug="a",
|
||||
author=author,
|
||||
summary="s",
|
||||
body=[("rich_text", "<p>one two three</p>"), ("code", {"language": "python", "raw_code": "x y z"})],
|
||||
)
|
||||
index.add_child(instance=article)
|
||||
article.save()
|
||||
assert article.read_time_mins == 1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_tag_metadata_css_and_uniqueness():
|
||||
tag = Tag.objects.create(name="llms", slug="llms")
|
||||
meta = TagMetadata.objects.create(tag=tag, colour="cyan")
|
||||
assert meta.get_css_classes()["bg"].startswith("bg-cyan")
|
||||
with pytest.raises(IntegrityError):
|
||||
TagMetadata.objects.create(tag=tag, colour="pink")
|
||||
27
apps/blog/tests/test_more_models.py
Normal file
27
apps/blog/tests/test_more_models.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import pytest
|
||||
|
||||
from apps.blog.models import TagMetadata
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_home_context_lists_articles(home_page, article_page):
|
||||
ctx = home_page.get_context(type("Req", (), {"GET": {}})())
|
||||
assert "latest_articles" in ctx
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_index_context_handles_page_values(article_index, article_page, rf):
|
||||
request = rf.get("/", {"page": "notanumber"})
|
||||
ctx = article_index.get_context(request)
|
||||
assert ctx["articles"].number == 1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_related_articles_fallback(article_page, article_index):
|
||||
related = article_page.get_related_articles()
|
||||
assert isinstance(related, list)
|
||||
|
||||
|
||||
def test_tag_metadata_fallback_classes():
|
||||
css = TagMetadata.get_fallback_css()
|
||||
assert css["bg"].startswith("bg-")
|
||||
61
apps/blog/tests/test_views.py
Normal file
61
apps/blog/tests/test_views.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import pytest
|
||||
|
||||
from apps.blog.models import ArticleIndexPage, ArticlePage
|
||||
from apps.blog.tests.factories import AuthorFactory
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_homepage_render(client, home_page):
|
||||
resp = client.get("/")
|
||||
assert resp.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_article_index_pagination_and_tag_filter(client, home_page):
|
||||
index = ArticleIndexPage(title="Articles", slug="articles")
|
||||
home_page.add_child(instance=index)
|
||||
author = AuthorFactory()
|
||||
for n in range(14):
|
||||
article = ArticlePage(
|
||||
title=f"A{n}",
|
||||
slug=f"a{n}",
|
||||
author=author,
|
||||
summary="summary",
|
||||
body=[("rich_text", "<p>body</p>")],
|
||||
)
|
||||
index.add_child(instance=article)
|
||||
article.save_revision().publish()
|
||||
|
||||
resp = client.get("/articles/?page=2")
|
||||
assert resp.status_code == 200
|
||||
assert resp.context["articles"].number == 2
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_article_page_related_context(client, home_page):
|
||||
index = ArticleIndexPage(title="Articles", slug="articles")
|
||||
home_page.add_child(instance=index)
|
||||
author = AuthorFactory()
|
||||
main = ArticlePage(
|
||||
title="Main",
|
||||
slug="main",
|
||||
author=author,
|
||||
summary="summary",
|
||||
body=[("rich_text", "<p>body</p>")],
|
||||
)
|
||||
index.add_child(instance=main)
|
||||
main.save_revision().publish()
|
||||
|
||||
related = ArticlePage(
|
||||
title="Related",
|
||||
slug="related",
|
||||
author=author,
|
||||
summary="summary",
|
||||
body=[("rich_text", "<p>body</p>")],
|
||||
)
|
||||
index.add_child(instance=related)
|
||||
related.save_revision().publish()
|
||||
|
||||
resp = client.get("/articles/main/")
|
||||
assert resp.status_code == 200
|
||||
assert "related_articles" in resp.context
|
||||
Reference in New Issue
Block a user