GCMD_Keyword_Classifier_MVP / tests /test_persistence.py
igerasimov's picture
MVP Milestone 10
e726170
Raw
History Blame Contribute Delete
3.94 kB
from __future__ import annotations
import json
from pathlib import Path
import pytest
from gcmd_classifier.models import (
ArticleClassificationOutcome,
ArticleProcessingStatus,
ArticleResult,
OutputError,
ProcessingMetadata,
ReviewStatus,
RunSummary,
)
from gcmd_classifier.persistence import JsonResultStore
def _result(doi: str, *, failed: bool = False) -> ArticleResult:
if failed:
return ArticleResult(
DOI=doi,
Title="Failed article",
Year=2025,
Abstract="Text.",
processing_status=ArticleProcessingStatus.FAILED,
classification_outcome=None,
classifications=(),
review_status=ReviewStatus.NOT_REQUIRED,
errors=(OutputError(code="MODEL_FAILED", message="Model failed."),),
processing_metadata=ProcessingMetadata(cache_used=False),
)
return ArticleResult(
DOI=doi,
Title="Completed article",
Year=2025,
Abstract="Text.",
processing_status=ArticleProcessingStatus.COMPLETED,
classification_outcome=ArticleClassificationOutcome.NOT_CLASSIFIED,
classifications=(),
no_classification_reason="No Topic selected.",
review_status=ReviewStatus.NOT_REQUIRED,
processing_metadata=ProcessingMetadata(cache_used=False),
)
def test_article_result_is_saved_after_completion(tmp_path: Path) -> None:
store = JsonResultStore(tmp_path)
result = _result("10.example/done")
store.save_article_result(result)
loaded = store.load_article_result("10.example/done")
assert loaded == result
def test_failed_article_result_is_saved(tmp_path: Path) -> None:
store = JsonResultStore(tmp_path)
result = _result("10.example/failed", failed=True)
store.save_article_result(result)
assert store.load_article_result("10.example/failed") == result
def test_previous_results_survive_simulated_later_failure(tmp_path: Path) -> None:
store = JsonResultStore(tmp_path)
first = _result("10.example/first")
store.save_article_result(first)
with pytest.raises(RuntimeError):
raise RuntimeError("later article failed before save")
assert store.load_article_result("10.example/first") == first
def test_loading_existing_results_works(tmp_path: Path) -> None:
store = JsonResultStore(tmp_path)
first = _result("10.example/first")
second = _result("10.example/second", failed=True)
store.save_article_result(first)
store.save_article_result(second)
loaded = store.load_all_results()
assert {result.DOI for result in loaded} == {"10.example/first", "10.example/second"}
def test_duplicate_output_records_for_same_doi_are_avoided(tmp_path: Path) -> None:
store = JsonResultStore(tmp_path)
first = _result("10.example/same")
second = _result("10.example/same", failed=True)
store.save_article_result(first)
store.save_article_result(second)
loaded = store.load_all_results()
assert len(loaded) == 1
assert loaded[0].processing_status is ArticleProcessingStatus.FAILED
def test_consolidated_output_is_valid_json(tmp_path: Path) -> None:
store = JsonResultStore(tmp_path)
result = _result("10.example/done")
summary = RunSummary(run_id="run-1", articles_received=1, articles_completed=1)
output_path = store.write_consolidated(results=[result], summary=summary)
payload = json.loads(output_path.read_text())
assert payload["summary"]["run_id"] == "run-1"
assert payload["articles"][0]["DOI"] == "10.example/done"
def test_atomic_checkpoint_temp_file_is_removed_after_success(tmp_path: Path) -> None:
store = JsonResultStore(tmp_path)
result = _result("10.example/done")
store.save_article_result(result)
assert store.article_path(result.DOI).exists()
assert not list(store.article_path(result.DOI).parent.glob("*.tmp"))