- Replace dict.get() with eager default in TagMetadata.get_css_classes() with explicit if/else to avoid unnecessary MD5 hash + DB access - Fix test_article_save_auto_generates_slug_from_title to actually test auto-generation by passing slug="" instead of the expected result Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
208 lines
7.0 KiB
Python
208 lines
7.0 KiB
Python
import pytest
|
|
from django.db import IntegrityError
|
|
from taggit.models import Tag
|
|
|
|
from apps.blog.models import (
|
|
TAG_COLOUR_PALETTE,
|
|
ArticleIndexPage,
|
|
ArticlePage,
|
|
Category,
|
|
HomePage,
|
|
TagMetadata,
|
|
get_auto_tag_colour_css,
|
|
)
|
|
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"] == "bg-brand-cyan/10"
|
|
with pytest.raises(IntegrityError):
|
|
TagMetadata.objects.create(tag=tag, colour="pink")
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_article_default_category_is_assigned(home_page):
|
|
index = ArticleIndexPage(title="Articles", slug="articles")
|
|
home_page.add_child(instance=index)
|
|
author = AuthorFactory()
|
|
article = ArticlePage(
|
|
title="Categorised",
|
|
slug="categorised",
|
|
author=author,
|
|
summary="s",
|
|
body=[("rich_text", "<p>body</p>")],
|
|
)
|
|
index.add_child(instance=article)
|
|
article.save()
|
|
assert article.category.slug == "general"
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_article_read_time_is_not_recomputed_when_body_text_is_unchanged(home_page, monkeypatch):
|
|
index = ArticleIndexPage(title="Articles", slug="articles")
|
|
home_page.add_child(instance=index)
|
|
author = AuthorFactory()
|
|
article = ArticlePage(
|
|
title="Stable read time",
|
|
slug="stable-read-time",
|
|
author=author,
|
|
summary="s",
|
|
body=[("rich_text", "<p>body words</p>")],
|
|
)
|
|
index.add_child(instance=article)
|
|
article.save()
|
|
|
|
def fail_compute():
|
|
raise AssertionError("read time should not be recomputed when body text is unchanged")
|
|
|
|
monkeypatch.setattr(article, "_compute_read_time", fail_compute)
|
|
article.title = "Retitled"
|
|
article.save()
|
|
article.refresh_from_db()
|
|
|
|
assert article.read_time_mins == 1
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_category_ordering():
|
|
Category.objects.get_or_create(name="General", slug="general")
|
|
Category.objects.create(name="Z", slug="z", sort_order=2)
|
|
Category.objects.create(name="A", slug="a", sort_order=1)
|
|
names = list(Category.objects.values_list("name", flat=True))
|
|
assert names == ["General", "A", "Z"]
|
|
|
|
|
|
# ── Auto tag colour tests ────────────────────────────────────────────────────
|
|
|
|
|
|
def test_auto_tag_colour_is_deterministic():
|
|
"""Same tag name always produces the same colour."""
|
|
css1 = get_auto_tag_colour_css("python")
|
|
css2 = get_auto_tag_colour_css("python")
|
|
assert css1 == css2
|
|
|
|
|
|
def test_auto_tag_colour_is_case_insensitive():
|
|
"""Tag colour assignment is case-insensitive."""
|
|
assert get_auto_tag_colour_css("Python") == get_auto_tag_colour_css("python")
|
|
|
|
|
|
def test_auto_tag_colour_returns_valid_palette_entry():
|
|
"""Returned CSS dict must be from the palette."""
|
|
css = get_auto_tag_colour_css("llms")
|
|
assert css in TAG_COLOUR_PALETTE
|
|
|
|
|
|
def test_auto_tag_colour_distributes_across_palette():
|
|
"""Different tag names should map to multiple palette entries."""
|
|
sample_tags = ["python", "javascript", "rust", "go", "ruby", "java",
|
|
"typescript", "css", "html", "sql", "llms", "mlops"]
|
|
colours = {get_auto_tag_colour_css(t)["text"] for t in sample_tags}
|
|
assert len(colours) >= 3, "Tags should spread across at least 3 palette colours"
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_tag_without_metadata_uses_auto_colour():
|
|
"""Tags without TagMetadata should get auto-assigned colour, not neutral."""
|
|
tag = Tag.objects.create(name="fastapi", slug="fastapi")
|
|
expected = get_auto_tag_colour_css("fastapi")
|
|
# Verify no metadata exists
|
|
assert not TagMetadata.objects.filter(tag=tag).exists()
|
|
# The template tag helper should fall back to auto colour
|
|
from apps.core.templatetags.core_tags import _resolve_tag_css
|
|
assert _resolve_tag_css(tag) == expected
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_tag_with_metadata_overrides_auto_colour():
|
|
"""Tags with explicit TagMetadata should use that colour."""
|
|
tag = Tag.objects.create(name="django", slug="django")
|
|
TagMetadata.objects.create(tag=tag, colour="pink")
|
|
from apps.core.templatetags.core_tags import _resolve_tag_css
|
|
css = _resolve_tag_css(tag)
|
|
assert css["text"] == "text-brand-pink"
|
|
|
|
|
|
# ── Auto slug tests ──────────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_article_save_auto_generates_slug_from_title(home_page):
|
|
"""Model save should auto-generate slug from title when slug is empty."""
|
|
index = ArticleIndexPage(title="Articles", slug="articles")
|
|
home_page.add_child(instance=index)
|
|
author = AuthorFactory()
|
|
article = ArticlePage(
|
|
title="My Great Article",
|
|
slug="",
|
|
author=author,
|
|
summary="summary",
|
|
body=[("rich_text", "<p>body</p>")],
|
|
)
|
|
index.add_child(instance=article)
|
|
article.refresh_from_db()
|
|
assert article.slug == "my-great-article"
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_article_save_auto_generates_search_description(article_index):
|
|
"""Model save should populate search_description from summary."""
|
|
author = AuthorFactory()
|
|
article = ArticlePage(
|
|
title="SEO Auto",
|
|
slug="seo-auto",
|
|
author=author,
|
|
summary="This is the article summary.",
|
|
body=[("rich_text", "<p>body</p>")],
|
|
)
|
|
article_index.add_child(instance=article)
|
|
article.save()
|
|
assert article.search_description == "This is the article summary."
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_article_save_preserves_explicit_search_description(article_index):
|
|
"""Explicit search_description should not be overwritten."""
|
|
author = AuthorFactory()
|
|
article = ArticlePage(
|
|
title="SEO Explicit",
|
|
slug="seo-explicit",
|
|
author=author,
|
|
summary="Generated summary.",
|
|
search_description="Custom SEO description.",
|
|
body=[("rich_text", "<p>body</p>")],
|
|
)
|
|
article_index.add_child(instance=article)
|
|
article.save()
|
|
assert article.search_description == "Custom SEO description."
|