Add blog and article narration import
Deploy production / deploy (push) Successful in 4s

This commit is contained in:
2026-08-13 15:12:33 -07:00
parent a2e405cb96
commit 2fe6b689b0
8 changed files with 2672 additions and 0 deletions
+138
View File
@@ -0,0 +1,138 @@
from __future__ import annotations
from pathlib import Path
import shutil
import sys
import unittest
import uuid
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
from blog_editor import BlogEditorError, BlogRepository, suggest_slug # noqa: E402
class BlogEditorTests(unittest.TestCase):
def setUp(self) -> None:
cache_root = Path(__file__).resolve().parents[1] / ".cache"
cache_root.mkdir(exist_ok=True)
self.site_root = cache_root / f"blog-editor-test-{uuid.uuid4().hex}"
(self.site_root / "content" / "blog").mkdir(parents=True)
(self.site_root / "content" / "tags" / "writing").mkdir(parents=True)
(self.site_root / "models").mkdir()
(self.site_root / "Test.lektorproject").write_text(
"[project]\nname = Test\n", encoding="utf-8"
)
(self.site_root / "content" / "blog" / "contents.lr").write_text(
"_model: section\n---\ntitle: Blog\n", encoding="utf-8"
)
(self.site_root / "content" / "tags" / "writing" / "contents.lr").write_text(
"_model: tag\n---\ntitle: Writing\n---\nsummary: Writing.\n",
encoding="utf-8",
)
source_model = Path(__file__).resolve().parents[1] / "models" / "entry.ini"
shutil.copy2(source_model, self.site_root / "models" / "entry.ini")
self.repository = BlogRepository(self.site_root)
def tearDown(self) -> None:
shutil.rmtree(self.site_root)
@staticmethod
def values(**overrides: str) -> dict[str, str]:
values = {
"title": "Test Entry",
"date": "2026-08-13",
"updated": "",
"author": "Test Author",
"tags": "writing",
"kicker": "Blog",
"summary": "A test summary.",
"body": "A **Markdown** body.",
"external_url": "",
"published_urls": "https://example.test/post",
}
values.update(overrides)
return values
def test_current_site_blog_records_and_controlled_tags_load(self) -> None:
repository = BlogRepository(Path(__file__).resolve().parents[1])
entries = repository.list_entries()
self.assertGreaterEqual(len(entries), 7)
self.assertGreaterEqual(len(repository.tags), 45)
self.assertTrue(all(entry.document.get("kicker") == "Blog" for entry in entries))
for entry in entries:
with self.subTest(slug=entry.slug):
self.assertEqual(
entry.document.render().encode("utf-8"), entry.original_bytes
)
def test_create_uses_existing_record_conventions_and_refuses_collision(self) -> None:
entry = self.repository.create_entry(
"test-entry", self.values(body=" preserved indentation\n\nBody text.")
)
rendered = entry.path.read_text(encoding="utf-8")
self.assertIn("_model: entry\n---\ntitle: Test Entry\n", rendered)
self.assertIn("tags: writing\n", rendered)
self.assertIn(
"published_urls:\n\nhttps://example.test/post\n---\nbody:\n",
rendered,
)
with self.assertRaises(BlogEditorError):
self.repository.create_entry("test-entry", self.values())
def test_edit_preserves_unmanaged_and_unchanged_blocks_exactly(self) -> None:
entry = self.repository.create_entry(
"test-entry", self.values(body=" preserved indentation\n\nBody text.")
)
original = entry.path.read_text(encoding="utf-8")
marker = "---\neditor_note:\nKeep this unknown field exactly. \n"
original = original.replace("---\nbody:\n", marker + "---\nbody:\n")
entry.path.write_text(original, encoding="utf-8")
loaded = self.repository.load_entry("test-entry")
values = self.repository.values_for(loaded.document)
values["summary"] = "An edited summary."
saved = self.repository.save_entry(loaded, values)
rendered = saved.path.read_text(encoding="utf-8")
self.assertIn(marker, rendered)
self.assertIn("summary: An edited summary.\n", rendered)
self.assertIn("body:\n preserved indentation\n\nBody text.\n", rendered)
self.assertIn("published_urls:\n\nhttps://example.test/post\n", rendered)
def test_save_detects_an_external_change(self) -> None:
entry = self.repository.create_entry("test-entry", self.values())
with entry.path.open("a", encoding="utf-8") as output:
output.write("\n")
with self.assertRaisesRegex(BlogEditorError, "changed on disk"):
self.repository.save_entry(entry, self.values(summary="Changed"))
def test_delete_removes_only_the_loaded_entry_directory(self) -> None:
entry = self.repository.create_entry("test-entry", self.values())
attachment = entry.path.parent / "narration.mp3"
attachment.write_bytes(b"disposable attachment")
neighbor = self.repository.create_entry("neighbor-entry", self.values())
self.repository.delete_entry(entry)
self.assertFalse(entry.path.parent.exists())
self.assertTrue(neighbor.path.is_file())
def test_delete_detects_an_external_change(self) -> None:
entry = self.repository.create_entry("test-entry", self.values())
with entry.path.open("a", encoding="utf-8") as output:
output.write("\n")
with self.assertRaisesRegex(BlogEditorError, "changed on disk"):
self.repository.delete_entry(entry)
self.assertTrue(entry.path.is_file())
def test_unapproved_tag_is_rejected(self) -> None:
with self.assertRaisesRegex(BlogEditorError, "unapproved tag"):
self.repository.create_entry("test-entry", self.values(tags="invented"))
def test_slug_suggestion_matches_repository_style(self) -> None:
self.assertEqual(suggest_slug("Were Testing: A GUI!"), "were-testing-a-gui")
if __name__ == "__main__":
unittest.main()