Artifex/tests/test_story_sources.py
2026-08-28 23:51:02 +07:00

122 lines
3.8 KiB
Python

from __future__ import annotations
import hashlib
from pathlib import Path
import pytest
from django.core.management import call_command
from control_plane.authoring.models import (
DocumentAuthority,
DocumentType,
Series,
SourceDocument,
Work,
)
from control_plane.authoring.sources import discover_source_paths, passage_spans, register_source
def source_work() -> Work:
series = Series.objects.create(title="Labyrinth Hero", slug="labyrinth-hero")
return Work.objects.create(series=series, title="The Fortune Below", slug="the-fortune-below")
def test_passage_spans_preserve_exact_source_locations() -> None:
content = "# Heading\n\nFirst line.\nSecond line.\n\nLast.\n"
passages = passage_spans(content)
assert [(item["start_line"], item["end_line"]) for item in passages] == [(1, 1), (3, 4), (6, 6)]
assert all(
content[item["start_char"] : item["end_char"]] == item["content"]
for item in passages
)
def test_source_discovery_supports_explicit_classification_globs(tmp_path: Path) -> None:
canon = tmp_path / "canon"
planning = tmp_path / "planning"
canon.mkdir()
planning.mkdir()
(canon / "facts.md").write_text("Canon", encoding="utf-8")
(planning / "notes.md").write_text("Planning", encoding="utf-8")
discovered = discover_source_paths(tmp_path, ["canon/*.md"])
assert [path.name for path in discovered] == ["facts.md"]
def test_register_source_is_idempotent_and_versions_changed_content(tmp_path: Path) -> None:
work = source_work()
root = tmp_path / "corpus"
root.mkdir()
path = root / "scene.md"
path.write_text("# Scene\n\nFirst version.\n", encoding="utf-8")
first = register_source(
work=work,
path=path,
root=root,
authority=DocumentAuthority.PROVISIONAL,
document_type=DocumentType.SCENE,
)
unchanged = register_source(
work=work,
path=path,
root=root,
authority=DocumentAuthority.PROVISIONAL,
document_type=DocumentType.SCENE,
)
reclassified = register_source(
work=work,
path=path,
root=root,
authority=DocumentAuthority.CANON,
document_type=DocumentType.SCENE,
)
path.write_text("# Scene\n\nSecond version.\n", encoding="utf-8")
second = register_source(
work=work,
path=path,
root=root,
authority=DocumentAuthority.CANON,
document_type=DocumentType.SCENE,
)
document = SourceDocument.objects.get(work=work, logical_key="scene.md")
versions = list(document.versions.order_by("version"))
assert first.status == "created"
assert unchanged.status == "unchanged"
assert second.status == "versioned"
assert reclassified.status == "versioned"
assert len(versions) == 3
assert versions[1].supersedes == versions[0]
assert versions[1].source_sha256 == versions[0].source_sha256
assert versions[1].authority == DocumentAuthority.CANON
assert versions[2].supersedes == versions[1]
assert versions[2].passages.count() == 2
assert versions[2].source_sha256 == hashlib.sha256(path.read_bytes()).hexdigest()
versions[2].authority = DocumentAuthority.REJECTED
with pytest.raises(ValueError, match="immutable"):
versions[2].save()
def test_story_sources_dry_run_does_not_create_database_records(tmp_path: Path) -> None:
path = tmp_path / "canon.md"
path.write_text("# Canon\n\nA fact.\n", encoding="utf-8")
call_command(
"story_sources",
"register",
root=tmp_path,
series_slug="labyrinth-hero",
series_title="Labyrinth Hero",
work_slug="the-fortune-below",
work_title="The Fortune Below",
authority=DocumentAuthority.PROVISIONAL,
dry_run=True,
)
assert not Series.objects.exists()
assert not SourceDocument.objects.exists()