from __future__ import annotations import pytest import mdpolish from mdpolish import Pipeline, RunStatus from mdpolish.components import ArxivSubmissionStampComponent STAMP = "arXiv:2104.12345v2 [stat.ME] 31 Dec 2021" OTHER_STAMP = "arXiv:2301.7v1 [cs.AI] 1 Jan 2023" REASON = "删除完整匹配的 arXiv 提交边栏戳" def transform(markdown: str) -> mdpolish.TransformResult: return Pipeline([ArxivSubmissionStampComponent()]).transform(markdown) def test_component_metadata_and_package_export() -> None: result = transform("") assert result.status is RunStatus.SUCCESS assert result.components[0].component_id == "paper.arxiv_submission_stamp" assert result.components[0].version == "1.0.0" assert result.components[0].parameters == () assert result.components[0].applicability assert not hasattr(mdpolish, "ArxivSubmissionStampComponent") @pytest.mark.parametrize("markdown", ["", "普通正文", "中文\nCafe\u0301\n🙂\n"]) def test_no_target_returns_unchanged_success(markdown: str) -> None: result = transform(markdown) assert result.status is RunStatus.SUCCESS assert result.output_markdown == markdown assert result.changes == () @pytest.mark.parametrize( ("markdown", "expected"), [ (f"{STAMP}\n正文", "正文"), (f"正文\n{STAMP}\n后文", "正文\n后文"), (f"正文\n{STAMP}", "正文\n"), (STAMP, ""), (f"正文\r\n{STAMP}\r\n后文", "正文\r\n后文"), (f"正文\r{STAMP}\r后文", "正文\r后文"), ], ) def test_deletes_target_at_approved_line_boundaries(markdown: str, expected: str) -> None: result = transform(markdown) assert result.status is RunStatus.SUCCESS assert result.output_markdown == expected assert len(result.changes) == 1 def test_multiple_targets_are_reported_in_source_order_with_one_atomic_batch() -> None: markdown = f"{STAMP}\n保留\n{OTHER_STAMP}" result = transform(markdown) assert result.status is RunStatus.SUCCESS assert result.output_markdown == "保留\n" assert [change.before for change in result.changes] == [f"{STAMP}\n", OTHER_STAMP] assert [change.proposal_ref.proposal_index for change in result.changes] == [0, 1] assert [change.span.start for change in result.changes] == sorted(change.span.start for change in result.changes) assert {change.before_sha256 for change in result.changes} == {result.input_sha256} assert {change.after_sha256 for change in result.changes} == {result.current_sha256} def test_adjacent_targets_use_non_overlapping_delete_ranges() -> None: result = transform(f"{STAMP}\n{OTHER_STAMP}") assert result.status is RunStatus.SUCCESS assert result.output_markdown == "" assert len(result.changes) == 2 assert result.changes[0].span.end == result.changes[1].span.start @pytest.mark.parametrize( "line", [ f" {STAMP}", f"{STAMP} ", f"- {STAMP}", f"> {STAMP}", "1. Example. arXiv preprint arXiv:2104.12345v2 [stat.ME], 2021.", "See arXiv:2104.12345v2 for details.", "arXiv:2104.12345 [stat.ME] 31 Dec 2021", "arXiv:hep-ph/9901001 [hep-ph] 31 Dec 1999", "arXiv:2104.12345v2 31 Dec 2021", "arXiv:2104.12345v2 [stat.ME] 0 Dec 2021", "arXiv:2104.12345v2 [stat.ME] 32 Dec 2021", "arXiv:2104.12345v2 [stat.ME] 31 December 2021", "arXiv:2104.12345v2 [统计] 31 Dec 2021", ], ) def test_similar_arxiv_text_is_preserved(line: str) -> None: markdown = f"前文\n{line}\n后文" result = transform(markdown) assert result.status is RunStatus.SUCCESS assert result.output_markdown == markdown assert result.changes == () def test_matching_line_inside_fenced_code_is_not_protected() -> None: markdown = f"```text\n{STAMP}\n```\n" result = transform(markdown) assert result.status is RunStatus.SUCCESS assert result.output_markdown == "```text\n```\n" assert len(result.changes) == 1 def test_change_audit_records_identity_reason_source_and_batch_hashes() -> None: result = transform(f"{STAMP}\n正文") assert result.status is RunStatus.SUCCESS change = result.changes[0] assert change.component_id == "paper.arxiv_submission_stamp" assert change.component_version == "1.0.0" assert change.component_position == 0 assert change.proposal_ref.component_position == 0 assert change.proposal_ref.proposal_index == 0 assert change.edit_index == 0 assert change.reason == REASON assert change.before == f"{STAMP}\n" assert change.after == "" assert change.before_sha256 == result.input_sha256 assert change.after_sha256 == result.current_sha256 def test_successful_output_is_stable_and_second_run_has_no_changes() -> None: pipeline = Pipeline([ArxivSubmissionStampComponent()]) first = pipeline.transform(f"{STAMP}\n正文") assert first.status is RunStatus.SUCCESS assert first.output_markdown == "正文" second = pipeline.transform(first.output_markdown) assert second.status is RunStatus.SUCCESS assert second.output_markdown == "正文" assert second.changes == () assert second.residual_proposals == () def test_same_input_produces_same_ordered_result() -> None: pipeline = Pipeline([ArxivSubmissionStampComponent()]) markdown = f"{STAMP}\n正文\n{OTHER_STAMP}\n" assert pipeline.transform(markdown) == pipeline.transform(markdown)