Spaces:
Running on Zero
Running on Zero
| 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")) | |