Add Docker-executed pytest suite with >90% coverage

This commit is contained in:
Codex_B
2026-02-28 11:53:05 +00:00
parent b5f0f40c4c
commit 8970f4d8de
25 changed files with 587 additions and 0 deletions

View File

View 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

View 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")

View 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

View 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")

View 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-")

View 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