Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions scripts/test_yunxiao_github_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,45 @@ def test_description_source_requires_the_leading_source_line(self):
self.assertIsNone(MODULE.description_source_url(f"Body mentions\nGitHub: {url}"))
self.assertIsNone(MODULE.description_source_url(f"GitHub: {url}#issuecomment-1"))

def test_legacy_source_sections_accept_bom_and_paragraph_whitespace(self):
issue_url = MODULE.source_url("issue", 394, REPOSITORY)
pr_url = MODULE.source_url("pr", 395, REPOSITORY)
cases = (
(f"来源:GitHub Issue #394 \ufeff{issue_url}\ufeff \ufeff 问题现象", issue_url),
(f"来源:GitHub PR #395\n\n{pr_url}\n\n问题\nDetails", pr_url),
(f'<div>来源:GitHub PR #395</div><div><a href="{pr_url}">{pr_url}</a></div><div>Details</div>', pr_url),
)
for description, expected in cases:
with self.subTest(description=description):
self.assertEqual(MODULE.description_source_url(description), expected.casefold())

def test_legacy_source_sections_reject_quoted_or_mismatched_sources(self):
for description in (
"Body first\n来源:GitHub Issue #394\nhttps://github.com/MemTensor/memmy-agent/issues/394",
"来源:GitHub Issue #394\nhttps://github.com/MemTensor/memmy-agent/issues/395",
"来源:GitHub PR #395\nhttps://github.com/MemTensor/memmy-agent/issues/395",
"来源:GitHub Issue #394\nhttps://example.com/MemTensor/memmy-agent/issues/394",
"来源:GitHub Issue #394\nhttps://github.com/MemTensor/memmy-agent/issues/394#issuecomment-1",
"来源:GitHub Issue #394\nUnrelated text\nhttps://github.com/MemTensor/memmy-agent/issues/394",
):
with self.subTest(description=description):
self.assertIsNone(MODULE.description_source_url(description))

def test_title_cleanup_accepts_verified_legacy_source_sections(self):
for kind, number in (("issue", 394), ("pr", 395)):
with self.subTest(kind=kind):
card = source_card(number=number, kind=kind, form="long")
card["description"] = (
f"来源:GitHub {MODULE.SOURCE_LABELS[kind]} #{number}\n\n"
f"{MODULE.source_url(kind, number, REPOSITORY)}\n\nDetails"
)
transport = MemoryTransport([card])
result, stats = self.rename(transport, apply=True)
self.assertEqual((result, stats["renamed"], stats["failed"]), (0, 1, 0))
self.assertEqual(transport.cards["card"]["description"], card["description"])
writes = [body for method, _, body in transport.calls if method == "PUT"]
self.assertEqual(writes, [{"subject": f"[{MODULE.SOURCE_LABELS[kind]} #{number}] Fix scheduler"}])

def test_sync_after_create_and_after_rename_reuses_the_same_card(self):
transport = MemoryTransport()
self.assertEqual(self.sync(transport), "created")
Expand Down
15 changes: 14 additions & 1 deletion scripts/yunxiao_github_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,20 @@ def description_source_url(description: Any) -> str | None:
r"GitHub:\s+(https://github\.com/[^/\s]+/[^/\s]+/(?:issues|pull)/[1-9][0-9]*/?)",
first_line,
)
return match.group(1).rstrip("/").casefold() if match else None
if match:
return match.group(1).rstrip("/").casefold()
# Early manually imported cards used a source section before the URL.
legacy = re.match(
r"\A来源[::]\s*GitHub\s+(Issue|PR)\s+#([1-9][0-9]*)\s+"
r"(https://github\.com/[^/\s]+/[^/\s]+/(issues|pull)/([1-9][0-9]*)/?)(?=\s|$)",
"".join(parser.parts).replace("\ufeff", " ").lstrip(),
)
if not legacy:
return None
label, number, url, path, url_number = legacy.groups()
if number != url_number or {"Issue": "issues", "PR": "pull"}[label] != path:
return None
return url.rstrip("/").casefold()


def iso_after_days(value: str, days: int) -> str:
Expand Down
Loading