实现 arXiv 提交边栏戳清洗组件
冻结 0004,新增严格整行删除组件和测试,并记录 5 份论文的只读验证结果。同步 ClinDB 清洗范围,并忽略本地 reference 调研副本。
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user