Files
ThothII/harness/tests/test_corpus_publish.py
T

100 lines
3.8 KiB
Python

import pytest
import os
from tht.corpus.models import CorpusManifest
from tht.corpus.store import CorpusStore, UnsafeCorpusPath
def test_publish_switches_active_atomically_and_resolves_materialized_files(tmp_path):
store = CorpusStore(tmp_path / "corpus")
generation = store.stage(CorpusManifest(), {})
seen = []
store._replace = lambda source, target: (seen.append(source.read_text()), source.replace(target))
published = store.publish(generation)
assert published == generation
assert store.active_generation() == generation
assert seen == [generation + "\n"]
def test_active_manifest_is_a_consistent_reader_snapshot(tmp_path):
store = CorpusStore(tmp_path / "corpus")
first = store.stage(CorpusManifest(metadata={"name": "first"}), {})
second = store.stage(CorpusManifest(metadata={"name": "second"}), {})
store.publish(first)
snapshot = store.active_manifest()
store.publish(second)
assert snapshot.metadata["name"] == "first"
assert store.active_manifest().metadata["name"] == "second"
def test_store_rejects_symlinked_generation_root(tmp_path):
outside = tmp_path / "outside"
outside.mkdir()
root = tmp_path / "corpus"
root.symlink_to(outside, target_is_directory=True)
with pytest.raises(UnsafeCorpusPath):
CorpusStore(root)
def test_active_pointer_cannot_escape_generation_root(tmp_path):
store = CorpusStore(tmp_path / "corpus")
store.root.mkdir(parents=True, exist_ok=True)
store.active_path.write_text("../outside\n")
with pytest.raises(UnsafeCorpusPath):
store.active_manifest()
def test_publish_restores_previous_active_when_directory_fsync_fails_after_replace(tmp_path, monkeypatch):
store = CorpusStore(tmp_path / "corpus")
first = store.stage(CorpusManifest(), {})
second = store.stage(CorpusManifest(), {})
store.publish(first)
def fail_once():
store._fsync_directory = store._sync_root
raise OSError("post replace crash")
store._fsync_directory = fail_once
with pytest.raises(OSError, match="post replace"):
store.publish(second)
assert store.active_generation() == first
def test_read_document_rejects_symlink_hardlink_and_hash_mismatch(tmp_path):
from tht.corpus.models import CanonicalDocument
content = "trusted"
digest = "sha256:" + __import__("hashlib").sha256(content.encode()).hexdigest()
document = CanonicalDocument(
document_id="doc:" + "a" * 64, source_id="fs:one", source_uri="file:///one",
source_fingerprint="sha256:" + "b" * 64, content_hash=digest, content=content,
pipeline_version="evidence-v1",
)
store = CorpusStore(tmp_path / "corpus")
generation = store.stage(CorpusManifest(documents=(document,)), {document.document_id: content})
path = store.resolve_document(document.document_id, generation)
assert store.read_document(document.document_id, generation) == content
path.unlink()
path.symlink_to(tmp_path / "outside")
(tmp_path / "outside").write_text(content)
with pytest.raises(UnsafeCorpusPath):
store.read_document(document.document_id, generation)
path.unlink()
os.link(tmp_path / "outside", path)
with pytest.raises(UnsafeCorpusPath):
store.read_document(document.document_id, generation)
path.unlink()
path.write_text("tampered")
with pytest.raises(UnsafeCorpusPath):
store.read_document(document.document_id, generation)
def test_generation_inventory_is_validated_and_sorted(tmp_path):
store = CorpusStore(tmp_path / "corpus")
first = store.stage(CorpusManifest(), {}, generation="gen:" + "1" * 32)
second = store.stage(CorpusManifest(), {}, generation="gen:" + "2" * 32)
(store.root / "unrelated").mkdir()
assert store.list_generations() == [first, second]