EcoSpecies-Atlas/apps/api/tests/test_repository.py

968 lines
36 KiB
Python

from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from ecospecies_api import repository
SAMPLE_PAYLOAD = [
{
"slug": "test-shad",
"source_file": "Test Shad.txt",
"title": "Test Shad (Alosa testus)",
"common_name": "Test Shad",
"scientific_name": "Alosa testus",
"flelmr_code": "9999",
"summary": "",
"section_count": 2,
"diagnostics": [
{
"level": "warning",
"code": "missing_summary",
"message": "Summary/Abstract section is missing.",
},
{
"level": "warning",
"code": "missing_citations",
"message": "References section not found.",
},
],
"sections": [
{"heading": "HEADER", "content": "Header content"},
{"heading": "HABITAT", "content": "Habitat content"},
],
}
]
UPDATED_PAYLOAD = [
{
"slug": "test-shad",
"source_file": "Test Shad v2.txt",
"title": "Test Shad Revised (Alosa testus)",
"common_name": "Test Shad",
"scientific_name": "Alosa testus revised",
"flelmr_code": "1000",
"summary": "Imported replacement summary.",
"section_count": 2,
"diagnostics": [
{
"level": "warning",
"code": "missing_flelmr_code",
"message": "Replacement diagnostic.",
}
],
"sections": [
{"heading": "HEADER", "content": "Replacement header content"},
{"heading": "HABITAT", "content": "Replacement habitat content"},
],
}
]
DIFFERENT_PAYLOAD = [
{
"slug": "other-fish",
"source_file": "Other Fish.txt",
"title": "Other Fish (Pisces otherus)",
"common_name": "Other Fish",
"scientific_name": "Pisces otherus",
"flelmr_code": "2000",
"summary": "Other fish summary.",
"section_count": 1,
"diagnostics": [],
"sections": [
{"heading": "HEADER", "content": "Other fish header"},
],
}
]
class RepositoryWorkflowTests(unittest.TestCase):
def setUp(self) -> None:
self.tempdir = tempfile.TemporaryDirectory()
db_path = Path(self.tempdir.name) / "test.db"
self.engine = create_engine(f"sqlite:///{db_path}", future=True)
self.session_local = sessionmaker(
bind=self.engine,
autoflush=False,
autocommit=False,
future=True,
)
self.engine_patch = patch.object(repository, "create_db_engine", return_value=self.engine)
self.session_patch = patch.object(repository, "SessionLocal", self.session_local)
self.engine_patch.start()
self.session_patch.start()
repository.import_species_payload(SAMPLE_PAYLOAD)
def tearDown(self) -> None:
self.session_patch.stop()
self.engine_patch.stop()
self.engine.dispose()
self.tempdir.cleanup()
def test_import_filters_missing_summary_diagnostic_from_accepted_dataset(self) -> None:
detail = repository.get_species_by_slug("test-shad")
self.assertIsNotNone(detail)
self.assertEqual(detail["section_count"], 2)
self.assertEqual([section["position"] for section in detail["sections"]], [1, 2])
self.assertEqual([item["code"] for item in detail["diagnostics"]], ["missing_citations"])
self.assertEqual(
detail["legacy_identifiers"],
[
{
"authority": "legacy-ecospecies",
"identifier": "9999",
"label": "FLELMR",
}
],
)
def test_species_detail_includes_structured_document_and_legacy_source(self) -> None:
input_dir = Path(self.tempdir.name) / "input-data" / "InputFiles"
input_dir.mkdir(parents=True, exist_ok=True)
(input_dir / "Test Shad.txt").write_text("HEADER\nLegacy header content\n", encoding="utf-8")
with patch.object(repository, "get_default_data_dir", return_value=str(input_dir)):
detail = repository.get_species_by_slug("test-shad")
self.assertIsNotNone(detail)
assert detail is not None
self.assertEqual(detail["structured_document"]["source_format"], "ecospecies-markdown-v1")
self.assertIn(
"HABITAT",
[node["title"] for node in detail["structured_document"]["ast"]["nodes"]],
)
self.assertEqual(detail["legacy_source"]["source_file"], "Test Shad.txt")
self.assertIn("Legacy header content", detail["legacy_source"]["text"])
self.assertEqual(detail["taxon_identifiers"], [])
def test_editorial_update_changes_publication_visibility_and_creates_audit(self) -> None:
result = repository.update_species_editorial(
slug="test-shad",
publication_status="draft",
summary="Editor-authored summary.",
editor_notes="Needs another review pass.",
is_archived=None,
username="bob",
)
self.assertIsNotNone(result)
self.assertEqual(result["publication_status"], "draft")
self.assertEqual(result["summary"], "Editor-authored summary.")
self.assertEqual(result["last_modified_by"], "bob")
self.assertEqual(repository.get_species_by_slug("test-shad"), None)
editor_detail = repository.get_editor_species_detail("test-shad")
audit = repository.list_species_audit("test-shad")
self.assertIsNotNone(editor_detail)
self.assertEqual(editor_detail["publication_status"], "draft")
self.assertEqual(editor_detail["summary"], "Editor-authored summary.")
self.assertEqual(editor_detail["editor_notes"], "Needs another review pass.")
self.assertIsNotNone(audit)
self.assertEqual(audit[0]["action"], "editorial_update")
self.assertEqual(audit[0]["changed_by"], "bob")
self.assertIn("summary", audit[0]["details"])
self.assertIn("publication_status", audit[0]["details"])
def test_section_update_records_section_audit_metadata(self) -> None:
result = repository.update_species_section(
slug="test-shad",
section_position=2,
content="Updated habitat content.",
username="carol",
)
self.assertIsNotNone(result)
self.assertEqual(result["section"]["position"], 2)
self.assertEqual(result["section"]["content"], "Updated habitat content.")
self.assertEqual(result["last_modified_by"], "carol")
self.assertEqual(sorted(result["changed_fields"].keys()), ["section_content"])
editor_detail = repository.get_editor_species_detail("test-shad")
audit = repository.list_species_audit("test-shad")
self.assertIsNotNone(editor_detail)
self.assertEqual(editor_detail["sections"][1]["content"], "Updated habitat content.")
self.assertIsNotNone(audit)
self.assertEqual(audit[0]["action"], "section_update")
self.assertEqual(audit[0]["changed_by"], "carol")
self.assertEqual(audit[0]["details"]["section_position"], 2)
self.assertEqual(audit[0]["details"]["section_heading"], "HABITAT")
self.assertEqual(
audit[0]["details"]["section_content"],
{"from": "Habitat content", "to": "Updated habitat content."},
)
def test_reimport_preserves_editorial_state_and_audit_history(self) -> None:
repository.update_species_editorial(
slug="test-shad",
publication_status="draft",
summary="Editor-authored summary.",
editor_notes="Needs another review pass.",
is_archived=None,
username="bob",
)
repository.update_species_section(
slug="test-shad",
section_position=2,
content="Updated habitat content.",
username="carol",
)
repository.import_species_payload(UPDATED_PAYLOAD)
editor_detail = repository.get_editor_species_detail("test-shad")
audit = repository.list_species_audit("test-shad")
self.assertIsNotNone(editor_detail)
self.assertEqual(editor_detail["source_file"], "Test Shad v2.txt")
self.assertEqual(editor_detail["title"], "Test Shad Revised (Alosa testus)")
self.assertEqual(editor_detail["scientific_name"], "Alosa testus revised")
self.assertEqual(editor_detail["flelmr_code"], "1000")
self.assertEqual(editor_detail["publication_status"], "draft")
self.assertEqual(editor_detail["summary"], "Editor-authored summary.")
self.assertEqual(editor_detail["editor_notes"], "Needs another review pass.")
self.assertEqual(editor_detail["sections"][0]["content"], "Replacement header content")
self.assertEqual(editor_detail["sections"][1]["content"], "Updated habitat content.")
self.assertEqual([item["code"] for item in editor_detail["diagnostics"]], ["missing_flelmr_code"])
self.assertIsNotNone(audit)
self.assertEqual(len(audit), 2)
self.assertEqual([entry["action"] for entry in audit], ["section_update", "editorial_update"])
def test_reimport_preserves_persisted_taxon_identifiers(self) -> None:
repository.update_species_document_markdown(
slug="test-shad",
markdown="""---
title: Test Shad
common_name: Test Shad
scientific_name: Alosa testus
legacy_identifiers:
- authority: legacy-ecospecies
identifier: 9999
label: FLELMR
taxon_identifiers:
- authority: gbif
identifier: 12345
label: taxonKey
primary: true
primary_taxon_authority: gbif
---
## Summary
Taxon-reviewed summary.
""",
username="edith",
)
repository.import_species_payload(UPDATED_PAYLOAD)
detail = repository.get_editor_species_detail("test-shad")
self.assertIsNotNone(detail)
self.assertEqual(detail["primary_taxon_authority"], "gbif")
self.assertEqual(
detail["primary_taxon_identifier"],
{
"authority": "gbif",
"identifier": "12345",
"label": "taxonKey",
"primary": True,
"source_url": "",
},
)
self.assertEqual(
detail["taxon_identifiers"],
[
{
"authority": "gbif",
"identifier": "12345",
"label": "taxonKey",
"primary": True,
"source_url": "",
}
],
)
def test_reimport_updates_summary_when_no_editorial_override_exists(self) -> None:
repository.import_species_payload(UPDATED_PAYLOAD)
detail = repository.get_species_by_slug("test-shad")
self.assertIsNotNone(detail)
self.assertEqual(detail["summary"], "Imported replacement summary.")
self.assertEqual(detail["sections"][0]["content"], "Replacement header content")
def test_editor_can_archive_species_explicitly(self) -> None:
result = repository.update_species_editorial(
slug="test-shad",
publication_status=None,
summary=None,
editor_notes=None,
is_archived=True,
username="dana",
)
public_detail = repository.get_species_by_slug("test-shad")
editor_detail = repository.get_editor_species_detail("test-shad")
audit = repository.list_species_audit("test-shad")
self.assertIsNotNone(result)
self.assertTrue(result["is_archived"])
self.assertEqual(result["last_modified_by"], "dana")
self.assertIsNone(public_detail)
self.assertIsNotNone(editor_detail)
self.assertTrue(editor_detail["is_archived"])
self.assertIsNotNone(audit)
self.assertEqual(audit[0]["action"], "editorial_update")
self.assertEqual(audit[0]["details"]["is_archived"], {"from": False, "to": True})
def test_editor_can_unarchive_species_explicitly(self) -> None:
repository.update_species_editorial(
slug="test-shad",
publication_status=None,
summary=None,
editor_notes=None,
is_archived=True,
username="dana",
)
result = repository.update_species_editorial(
slug="test-shad",
publication_status=None,
summary=None,
editor_notes=None,
is_archived=False,
username="erin",
)
public_detail = repository.get_species_by_slug("test-shad")
audit = repository.list_species_audit("test-shad")
self.assertIsNotNone(result)
self.assertFalse(result["is_archived"])
self.assertEqual(result["last_modified_by"], "erin")
self.assertIsNotNone(public_detail)
self.assertIsNotNone(audit)
self.assertEqual(audit[0]["details"]["is_archived"], {"from": True, "to": False})
def test_missing_species_is_archived_instead_of_deleted(self) -> None:
repository.import_species_payload(DIFFERENT_PAYLOAD)
public_detail = repository.get_species_by_slug("test-shad")
editor_detail = repository.get_editor_species_detail("test-shad")
editor_items = repository.get_editor_species_list()
audit = repository.list_species_audit("test-shad")
self.assertIsNone(public_detail)
self.assertIsNotNone(editor_detail)
self.assertTrue(editor_detail["is_archived"])
self.assertEqual([item["slug"] for item in repository.list_species()], ["other-fish"])
self.assertEqual([item["slug"] for item in editor_items], ["other-fish", "test-shad"])
self.assertIsNotNone(audit)
self.assertEqual(audit[0]["action"], "import_archive")
self.assertEqual(audit[0]["details"]["is_archived"], {"from": False, "to": True})
def test_archived_species_is_restored_when_it_reappears(self) -> None:
repository.import_species_payload(DIFFERENT_PAYLOAD)
repository.import_species_payload(UPDATED_PAYLOAD)
public_detail = repository.get_species_by_slug("test-shad")
editor_detail = repository.get_editor_species_detail("test-shad")
audit = repository.list_species_audit("test-shad")
self.assertIsNotNone(public_detail)
self.assertIsNotNone(editor_detail)
self.assertFalse(editor_detail["is_archived"])
self.assertEqual(public_detail["summary"], "Imported replacement summary.")
self.assertIsNotNone(audit)
self.assertEqual(audit[0]["action"], "import_restore")
self.assertEqual(audit[0]["details"]["is_archived"], {"from": True, "to": False})
def test_document_markdown_update_refreshes_flat_projection(self) -> None:
result = repository.update_species_document_markdown(
slug="test-shad",
markdown="""---
title: Test Shad Markdown
common_name: Test Shad
scientific_name: Alosa markdownus
species_code: 4242
---
## Summary
Markdown summary.
## Habitat
Open water.
### Type
Pelagic.
""",
username="frank",
)
detail = repository.get_editor_species_detail("test-shad")
document = repository.get_species_document("test-shad")
audit = repository.list_species_audit("test-shad")
self.assertIsNotNone(result)
self.assertIsNotNone(detail)
self.assertIsNotNone(document)
self.assertEqual(detail["title"], "Test Shad Markdown")
self.assertEqual(detail["scientific_name"], "Alosa markdownus")
self.assertEqual(detail["flelmr_code"], "4242")
self.assertEqual(detail["summary"], "Markdown summary.")
self.assertEqual(
[section["heading"] for section in detail["sections"]],
["Habitat", "Habitat / Type"],
)
self.assertEqual(document["updated_by"], "frank")
self.assertIsNotNone(audit)
self.assertEqual(audit[0]["action"], "document_update")
def test_document_markdown_update_extracts_citations(self) -> None:
repository.update_species_document_markdown(
slug="test-shad",
markdown="""---
title: Test Shad Markdown
common_name: Test Shad
scientific_name: Alosa markdownus
legacy_identifiers:
- authority: legacy-ecospecies
identifier: 4242
label: FLELMR
---
## Summary
Markdown summary.
## References
- Smith, J. 2024. Example paper. doi:10.1000/example-doi
- [7] Jones, A. 2022. Fisheries review.
""",
username="frank",
)
detail = repository.get_editor_species_detail("test-shad")
self.assertIsNotNone(detail)
self.assertEqual(detail["citation_count"], 2)
self.assertEqual(detail["citations"][0]["section_heading"], "References")
self.assertEqual(detail["citations"][0]["legacy_reference_number"], "")
self.assertEqual(detail["citations"][0]["doi"], "10.1000/example-doi")
self.assertTrue(detail["citations"][0]["citation_key"])
self.assertIn("@", detail["citations"][0]["draft_bibtex"])
self.assertEqual(detail["citations"][0]["review_status"], "draft")
self.assertEqual(detail["citations"][1]["legacy_reference_number"], "7")
self.assertEqual(detail["citations"][1]["doi"], "")
self.assertIn("ecospecies_reference_number = \\{7\\}", detail["citations"][1]["draft_bibtex"])
def test_editor_can_review_citations_and_reviews_survive_document_save(self) -> None:
repository.update_species_document_markdown(
slug="test-shad",
markdown="""---
title: Test Shad Markdown
common_name: Test Shad
scientific_name: Alosa markdownus
legacy_identifiers:
- authority: legacy-ecospecies
identifier: 4242
label: FLELMR
---
## References
- [7] Jones, A. 2022. Fisheries review.
""",
username="frank",
)
citations = repository.get_editor_species_citations("test-shad")
self.assertIsNotNone(citations)
citation = citations["citations"][0]
result = repository.update_species_citation_review(
slug="test-shad",
citation_id=citation["id"],
review_status="accepted",
normalized_text="Jones, A. (2022). Fisheries review.",
doi="10.1000/review-doi",
citation_key="jones2022review",
entry_type="article",
draft_bibtex="@article{jones2022review,\n doi = {10.1000/review-doi}\n}",
username="edith",
)
self.assertIsNotNone(result)
self.assertEqual(result["citation"]["review_status"], "accepted")
self.assertEqual(result["citation"]["source_type"], "editor_review")
repository.update_species_document_markdown(
slug="test-shad",
markdown="""---
title: Test Shad Markdown
common_name: Test Shad
scientific_name: Alosa markdownus
legacy_identifiers:
- authority: legacy-ecospecies
identifier: 4242
label: FLELMR
---
## References
- [7] Jones, A. 2022. Fisheries review.
""",
username="frank",
)
citations = repository.get_editor_species_citations("test-shad")
audit = repository.list_species_audit("test-shad")
self.assertIsNotNone(citations)
self.assertEqual(citations["citation_count"], 1)
self.assertEqual(citations["citations"][0]["review_status"], "accepted")
self.assertEqual(citations["citations"][0]["doi"], "10.1000/review-doi")
self.assertEqual(citations["citations"][0]["citation_key"], "jones2022review")
self.assertEqual(citations["citations"][0]["entry_type"], "article")
self.assertIn("10.1000/review-doi", citations["citations"][0]["draft_bibtex"])
self.assertIsNotNone(audit)
self.assertEqual(audit[1]["action"], "citation_review_update")
def test_editor_can_run_citation_enrichment(self) -> None:
repository.update_species_document_markdown(
slug="test-shad",
markdown="""---
title: Test Shad Markdown
common_name: Test Shad
scientific_name: Alosa markdownus
legacy_identifiers:
- authority: legacy-ecospecies
identifier: 4242
label: FLELMR
---
## References
- [7] Jones, A. 2022. Fisheries review.
""",
username="frank",
)
citations = repository.get_editor_species_citations("test-shad")
self.assertIsNotNone(citations)
citation = citations["citations"][0]
with patch.object(
repository,
"enrich_citation_payload",
return_value={
"citation_key": "jones2022review",
"entry_type": "article",
"normalized_text": "Jones, A. (2022). Fisheries review. Journal of Tests. DOI:10.1000/review-doi",
"draft_bibtex": "@article{jones2022review,\n doi = {10.1000/review-doi},\n}",
"doi": "10.1000/review-doi",
"source_url": "https://doi.org/10.1000/review-doi",
"openalex_id": "W12345",
"resolver_source_label": "crossref:doi:10.1000/review-doi",
"enrichment_status": "resolved",
"enrichment_error": "",
"conflicts": [],
},
):
result = repository.update_species_citation_enrichment(
slug="test-shad",
citation_id=citation["id"],
username="edith",
)
self.assertIsNotNone(result)
self.assertEqual(result["citation"]["enrichment_status"], "resolved")
self.assertEqual(result["citation"]["doi"], "10.1000/review-doi")
self.assertEqual(result["citation"]["openalex_id"], "W12345")
self.assertEqual(result["citation"]["resolver_source_label"], "crossref:doi:10.1000/review-doi")
self.assertEqual(result["citation"]["source_url"], "https://doi.org/10.1000/review-doi")
citations = repository.get_editor_species_citations("test-shad")
audit = repository.list_species_audit("test-shad")
self.assertIsNotNone(citations)
self.assertEqual(citations["citations"][0]["citation_key"], "jones2022review")
self.assertEqual(citations["citations"][0]["entry_type"], "article")
self.assertEqual(citations["citations"][0]["enrichment_status"], "resolved")
self.assertIsNotNone(audit)
self.assertEqual(audit[0]["action"], "citation_enrichment")
def test_editor_can_run_batch_citation_enrichment(self) -> None:
repository.update_species_document_markdown(
slug="test-shad",
markdown="""---
title: Test Shad Markdown
common_name: Test Shad
scientific_name: Alosa markdownus
legacy_identifiers:
- authority: legacy-ecospecies
identifier: 4242
label: FLELMR
---
## References
- [7] Jones, A. 2022. Fisheries review.
- [8] Smith, B. 2021. Estuarine habitat paper.
""",
username="frank",
)
payloads = [
{
"citation_key": "jones2022review",
"entry_type": "article",
"normalized_text": "Jones, A. (2022). Fisheries review.",
"draft_bibtex": "@article{jones2022review,\n}",
"doi": "10.1000/review-doi",
"source_url": "https://doi.org/10.1000/review-doi",
"openalex_id": "W12345",
"resolver_source_label": "crossref:doi:10.1000/review-doi",
"enrichment_status": "resolved",
"enrichment_error": "",
"conflicts": [],
},
{
"citation_key": "smith2021estuarine",
"entry_type": "misc",
"normalized_text": "",
"draft_bibtex": "",
"doi": "",
"source_url": "",
"openalex_id": "",
"resolver_source_label": "",
"enrichment_status": "unresolved",
"enrichment_error": "No metadata match found from DOI, title, or authority identifiers.",
"conflicts": [],
},
]
with patch.object(repository, "enrich_citation_payload", side_effect=payloads):
result = repository.update_species_citations_enrichment_batch(
slug="test-shad",
username="edith",
)
self.assertIsNotNone(result)
self.assertEqual(result["citation_count"], 2)
self.assertEqual(result["changed_count"], 2)
self.assertEqual(result["resolved_count"], 1)
self.assertEqual(result["unresolved_count"], 1)
self.assertEqual(result["error_count"], 0)
def test_editor_can_review_and_apply_citation_candidates(self) -> None:
repository.update_species_document_markdown(
slug="test-shad",
markdown="""---
title: Test Shad Markdown
common_name: Test Shad
scientific_name: Alosa markdownus
legacy_identifiers:
- authority: legacy-ecospecies
identifier: 4242
label: FLELMR
---
## References
- [7] Daniell, W.C. 1872. Letters referring to experiments of W.C. Daniell, M.D., in introducing shad into the Alabama River. Comm. Rept. U.S. Comm. Fish & Fish. 2: 387-390.
""",
username="frank",
)
citations = repository.get_editor_species_citations("test-shad")
self.assertIsNotNone(citations)
citation = citations["citations"][0]
with patch.object(
repository,
"discover_citation_candidates",
return_value={
"seed": {
"fields": {
"author": "Daniell, W.C.",
"year": "1872",
"title": "Letters referring to experiments of W.C. Daniell, M.D., in introducing shad into the Alabama River",
"journal": "Comm. Rept. U.S. Comm. Fish & Fish.",
"volume": "2",
"pages": "387-390",
}
},
"candidate_count": 1,
"candidates": [
{
"candidate_id": "crossref-search-1-daniell-good",
"source_label": "crossref:search:1:daniell-good",
"entry_type": "article",
"citation_key": "daniell1872lettersreferringexperiments",
"fields": {
"author": "Daniell, W.C.",
"year": "1872",
"title": "Letters referring to experiments of W.C. Daniell, M.D., in introducing shad into the Alabama River",
"journal": "Comm. Rept. U.S. Comm. Fish & Fish.",
"volume": "2",
"pages": "387-390",
},
}
],
},
):
candidates = repository.get_species_citation_candidates("test-shad", citation["id"])
self.assertIsNotNone(candidates)
self.assertEqual(candidates["candidate_count"], 1)
result = repository.apply_species_citation_candidate_selection(
slug="test-shad",
citation_id=citation["id"],
candidate={
"source_label": "crossref:search:1:daniell-good",
"entry_type": "article",
"fields": {
"author": "Daniell, W.C.",
"year": "1872",
"title": "Letters referring to experiments of W.C. Daniell, M.D., in introducing shad into the Alabama River",
"journal": "Comm. Rept. U.S. Comm. Fish & Fish.",
"volume": "2",
"pages": "387-390",
},
},
username="edith",
)
self.assertIsNotNone(result)
self.assertEqual(result["citation"]["resolver_source_label"], "editor:selected:crossref:search:1:daniell-good")
self.assertEqual(result["citation"]["source_type"], "editor_selected_candidate")
self.assertEqual(result["citation"]["review_status"], "accepted")
audit = repository.list_species_audit("test-shad")
self.assertIsNotNone(audit)
self.assertEqual(audit[0]["action"], "citation_candidate_selection")
def test_editor_can_add_candidate_as_additional_citation_and_preserve_it(self) -> None:
repository.update_species_document_markdown(
slug="test-shad",
markdown="""---
title: Test Shad Markdown
common_name: Test Shad
scientific_name: Alosa markdownus
legacy_identifiers:
- authority: legacy-ecospecies
identifier: 4242
label: FLELMR
---
## References
- [7] Daniell, W.C. 1872. Letters referring to experiments of W.C. Daniell, M.D., in introducing shad into the Alabama River. Comm. Rept. U.S. Comm. Fish & Fish. 2: 387-390.
""",
username="frank",
)
citations = repository.get_editor_species_citations("test-shad")
self.assertIsNotNone(citations)
source_citation = citations["citations"][0]
result = repository.add_species_citation_from_candidate(
slug="test-shad",
citation_id=source_citation["id"],
candidate={
"source_label": "crossref:search:1:daniell-related",
"entry_type": "article",
"fields": {
"author": "Jordan, F.",
"year": "2009",
"title": "Habitat use of age 0 Alabama shad in the Pascagoula River drainage, USA",
"journal": "Transactions of the American Fisheries Society",
"volume": "19",
"number": "1",
"pages": "107-115",
"doi": "10.1111/j.1600-0633.2009.00395.x",
"url": "https://doi.org/10.1111/j.1600-0633.2009.00395.x",
},
},
username="edith",
)
self.assertIsNotNone(result)
self.assertEqual(result["citation"]["source_type"], "editor_added_candidate")
self.assertEqual(result["citation"]["review_status"], "accepted")
citations = repository.get_editor_species_citations("test-shad")
self.assertIsNotNone(citations)
self.assertEqual(citations["citation_count"], 2)
self.assertEqual(citations["citations"][1]["section_heading"], "References")
document = repository.get_species_document("test-shad")
self.assertIsNotNone(document)
self.assertIn("Habitat use of age 0 Alabama shad in the Pascagoula River drainage, USA", document["markdown"])
repository.update_species_document_markdown(
slug="test-shad",
markdown=document["markdown"],
username="frank",
)
citations = repository.get_editor_species_citations("test-shad")
self.assertIsNotNone(citations)
self.assertEqual(citations["citation_count"], 2)
self.assertEqual(citations["citations"][1]["source_type"], "editor_added_candidate")
audit = repository.list_species_audit("test-shad")
self.assertIsNotNone(audit)
self.assertEqual(audit[0]["action"], "document_update")
self.assertEqual(audit[1]["action"], "citation_candidate_addition")
def test_contributor_can_view_only_owned_citations(self) -> None:
created = repository.create_contributor_species(
"writer@example.org",
"""---
title: Contributor Draft
common_name: Contributor Fish
scientific_name: Pisces contributoris
species_code:
---
## References
- [12] Example, A. 2025. Draft reference.
""",
)
owned = repository.get_contributor_species_citations(created["slug"], "writer@example.org")
other = repository.get_contributor_species_citations(created["slug"], "other@example.org")
self.assertIsNotNone(owned)
self.assertEqual(owned["citation_count"], 1)
self.assertEqual(owned["citations"][0]["legacy_reference_number"], "12")
self.assertIsNone(other)
def test_public_bibliography_aggregates_species_citations(self) -> None:
repository.update_species_document_markdown(
slug="test-shad",
markdown="""---
title: Test Shad Markdown
common_name: Test Shad
scientific_name: Alosa markdownus
legacy_identifiers:
- authority: legacy-ecospecies
identifier: 4242
label: FLELMR
---
## References
- [7] Jones, A. 2022. Fisheries review.
""",
username="frank",
)
citations = repository.get_editor_species_citations("test-shad")
self.assertIsNotNone(citations)
citation = citations["citations"][0]
repository.update_species_citation_review(
slug="test-shad",
citation_id=citation["id"],
review_status="accepted",
normalized_text="Jones, A. (2022). Fisheries review.",
doi="10.1000/review-doi",
citation_key="jones2022review",
entry_type="article",
draft_bibtex="@article{jones2022review,\n doi = {10.1000/review-doi}\n}",
username="edith",
abstract_text="A short abstract about fisheries review.",
)
bibliography = repository.list_public_bibliography()
self.assertEqual(len(bibliography), 1)
self.assertEqual(bibliography[0]["citation_key"], "jones2022review")
self.assertEqual(bibliography[0]["abstract_text"], "A short abstract about fisheries review.")
self.assertEqual(bibliography[0]["legacy_reference_numbers"], ["7"])
self.assertEqual(bibliography[0]["species_count"], 1)
self.assertEqual(bibliography[0]["species_refs"][0]["slug"], "test-shad")
def test_register_contributor_creates_token_and_enforces_age_gate(self) -> None:
with self.assertRaisesRegex(ValueError, "at least 13 years old"):
repository.register_contributor("person@example.org", False)
result = repository.register_contributor("Person@Example.org", True)
self.assertEqual(result["username"], "person@example.org")
self.assertEqual(result["role"], "contributor")
self.assertEqual(result["minimum_age"], 13)
self.assertTrue(result["token"])
def test_contributor_can_create_and_edit_only_owned_species(self) -> None:
created = repository.create_contributor_species(
"writer@example.org",
"""---
title: Contributor Draft
common_name: Contributor Fish
scientific_name: Pisces contributoris
species_code:
---
## Summary
Draft summary.
## Habitat
Mangroves.
""",
)
detail = repository.get_contributor_species_detail(created["slug"], "writer@example.org")
public_detail = repository.get_species_by_slug(created["slug"])
self.assertIsNotNone(detail)
self.assertIsNone(public_detail)
self.assertEqual(detail["publication_status"], "draft")
self.assertEqual(detail["common_name"], "Contributor Fish")
updated = repository.update_contributor_species_document_markdown(
created["slug"],
"""---
title: Contributor Draft Revised
common_name: Contributor Fish
scientific_name: Pisces contributoris
species_code:
---
## Summary
Revised summary.
## Habitat
Seagrass.
### Depth
Shallow bays.
""",
"writer@example.org",
)
self.assertIsNotNone(updated)
detail = repository.get_contributor_species_detail(created["slug"], "writer@example.org")
other_user_detail = repository.get_contributor_species_detail(created["slug"], "other@example.org")
audit = repository.list_species_audit(created["slug"])
self.assertIsNotNone(detail)
self.assertEqual(detail["summary"], "Revised summary.")
self.assertEqual(
[section["heading"] for section in detail["sections"]],
["Habitat", "Habitat / Depth"],
)
self.assertIsNone(other_user_detail)
self.assertIsNotNone(audit)
self.assertEqual(audit[0]["action"], "contributor_document_update")
if __name__ == "__main__":
unittest.main()