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()