Merge pull request 'Match wiki pages by address so names with spaces are the same page' (#64) from mil-006-wiki-title-identity into main
Reviewed-on: #64
This commit was merged in pull request #64.
This commit is contained in:
@@ -142,9 +142,27 @@ jobs:
|
|||||||
return owner, repo
|
return owner, repo
|
||||||
|
|
||||||
|
|
||||||
|
def page_key(title):
|
||||||
|
"""Return the identity of a page: Gitea stores a space as a hyphen.
|
||||||
|
|
||||||
|
The page "Home da" is the file Home-da.md and its address is Home-da, so
|
||||||
|
both spellings must name the same page.
|
||||||
|
"""
|
||||||
|
return str(title).replace(" ", "-")
|
||||||
|
|
||||||
|
|
||||||
def read_pages(wiki_dir):
|
def read_pages(wiki_dir):
|
||||||
"""Return {page title: content} for every Markdown file in wiki_dir."""
|
"""Return {page key: content} for every Markdown file in wiki_dir."""
|
||||||
pages = {path.stem: path.read_bytes() for path in sorted(wiki_dir.glob("*.md"))}
|
pages = {}
|
||||||
|
for path in sorted(wiki_dir.glob("*.md")):
|
||||||
|
key = page_key(path.stem)
|
||||||
|
if key in pages:
|
||||||
|
raise WorkflowError(
|
||||||
|
"configuration",
|
||||||
|
f"Two files in {wiki_dir} name the wiki page {key}; a space and "
|
||||||
|
"a hyphen are the same in a page name.",
|
||||||
|
)
|
||||||
|
pages[key] = path.read_bytes()
|
||||||
if not pages:
|
if not pages:
|
||||||
# Publishing an empty directory would delete the whole wiki.
|
# Publishing an empty directory would delete the whole wiki.
|
||||||
raise WorkflowError(
|
raise WorkflowError(
|
||||||
@@ -154,7 +172,10 @@ jobs:
|
|||||||
|
|
||||||
|
|
||||||
def list_wiki_titles(wiki_url, headers, send):
|
def list_wiki_titles(wiki_url, headers, send):
|
||||||
"""Return the titles of the pages that exist in the wiki.
|
"""Return the keys of the pages that exist in the wiki.
|
||||||
|
|
||||||
|
The key is the address Gitea reports (sub_url), or the title with its
|
||||||
|
spaces replaced by hyphens.
|
||||||
|
|
||||||
Gitea answers 404 for a wiki that has never been written to; that is an
|
Gitea answers 404 for a wiki that has never been written to; that is an
|
||||||
empty wiki, not a failure.
|
empty wiki, not a failure.
|
||||||
@@ -171,7 +192,11 @@ jobs:
|
|||||||
raise
|
raise
|
||||||
if not isinstance(batch, list):
|
if not isinstance(batch, list):
|
||||||
raise WorkflowError("api", "Gitea returned an invalid wiki page list.")
|
raise WorkflowError("api", "Gitea returned an invalid wiki page list.")
|
||||||
titles.update(str(item.get("title", "")) for item in batch)
|
titles.update(
|
||||||
|
urllib.parse.unquote(str(item.get("sub_url") or ""))
|
||||||
|
or page_key(item.get("title", ""))
|
||||||
|
for item in batch
|
||||||
|
)
|
||||||
if len(batch) < PAGE_LIMIT:
|
if len(batch) < PAGE_LIMIT:
|
||||||
return titles
|
return titles
|
||||||
page += 1
|
page += 1
|
||||||
|
|||||||
@@ -67,6 +67,27 @@ class TestReadPages:
|
|||||||
publish_wiki.read_pages(tmp_path)
|
publish_wiki.read_pages(tmp_path)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPageKey:
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("name", "key"),
|
||||||
|
[("Home", "Home"), ("Home da", "Home-da"), ("Home-da", "Home-da"), ("Home.da", "Home.da"),
|
||||||
|
("AI Prompt Template ms", "AI-Prompt-Template-ms"), ("_Sidebar", "_Sidebar")],
|
||||||
|
)
|
||||||
|
def test_a_space_and_a_hyphen_are_the_same_page(self, publish_wiki, name, key):
|
||||||
|
assert publish_wiki.page_key(name) == key
|
||||||
|
|
||||||
|
def test_file_names_with_spaces_and_hyphens_are_keyed_the_same(self, publish_wiki, tmp_path):
|
||||||
|
(tmp_path / "Home da.md").write_bytes(b"hjem")
|
||||||
|
(tmp_path / "_Sidebar.md").write_bytes(b"menu")
|
||||||
|
assert publish_wiki.read_pages(tmp_path) == {"Home-da": b"hjem", "_Sidebar": b"menu"}
|
||||||
|
|
||||||
|
def test_two_files_for_one_page_are_refused(self, publish_wiki, tmp_path):
|
||||||
|
(tmp_path / "Home da.md").write_bytes(b"one")
|
||||||
|
(tmp_path / "Home-da.md").write_bytes(b"two")
|
||||||
|
with pytest.raises(publish_wiki.WorkflowError, match="name the wiki page Home-da"):
|
||||||
|
publish_wiki.read_pages(tmp_path)
|
||||||
|
|
||||||
|
|
||||||
class FakeGitea:
|
class FakeGitea:
|
||||||
"""Answers like the Gitea repository and wiki endpoints."""
|
"""Answers like the Gitea repository and wiki endpoints."""
|
||||||
|
|
||||||
@@ -84,7 +105,7 @@ class FakeGitea:
|
|||||||
if url.startswith(f"{WIKI_URL}/pages"):
|
if url.startswith(f"{WIKI_URL}/pages"):
|
||||||
if not self.wiki_initialised:
|
if not self.wiki_initialised:
|
||||||
raise self.error("configuration", "404")
|
raise self.error("configuration", "404")
|
||||||
return [{"title": title} for title in self.wiki]
|
return [{"title": key.replace("-", " "), "sub_url": key} for key in self.wiki]
|
||||||
if method == "POST" and url == f"{WIKI_URL}/new":
|
if method == "POST" and url == f"{WIKI_URL}/new":
|
||||||
self.wiki[body["title"]] = body["content_base64"]
|
self.wiki[body["title"]] = body["content_base64"]
|
||||||
return None
|
return None
|
||||||
@@ -265,6 +286,64 @@ class TestPublish:
|
|||||||
assert gitea.wiki == {"Home": b64("keep me")}
|
assert gitea.wiki == {"Home": b64("keep me")}
|
||||||
|
|
||||||
|
|
||||||
|
class TestPageIdentity:
|
||||||
|
"""A wiki that already has pages named with spaces, as Gitea stores them."""
|
||||||
|
|
||||||
|
def test_a_file_named_with_a_space_updates_the_existing_page(
|
||||||
|
self, publish_wiki, environment
|
||||||
|
):
|
||||||
|
(environment / "Home.md").unlink()
|
||||||
|
(environment / "Credentials.md").unlink()
|
||||||
|
(environment / ".publish").write_text("", encoding="utf-8")
|
||||||
|
(environment / "Home da.md").write_text("hjem v2", encoding="utf-8")
|
||||||
|
gitea = FakeGitea(publish_wiki, wiki={"Home-da": b64("hjem v1")})
|
||||||
|
|
||||||
|
assert run_main(publish_wiki, gitea) == 0
|
||||||
|
|
||||||
|
assert gitea.wiki == {"Home-da": b64("hjem v2")}
|
||||||
|
assert [call[0] for call in gitea.calls].count("POST") == 0
|
||||||
|
assert ("PATCH", f"{WIKI_URL}/page/Home-da") in [(c[0], c[1]) for c in gitea.calls]
|
||||||
|
|
||||||
|
def test_identical_pages_with_spaces_are_a_no_op(self, publish_wiki, environment):
|
||||||
|
(environment / "Home.md").unlink()
|
||||||
|
(environment / "Credentials.md").unlink()
|
||||||
|
(environment / "Home da.md").write_text("hjem", encoding="utf-8")
|
||||||
|
(environment / "_Sidebar.md").write_text("menu", encoding="utf-8")
|
||||||
|
gitea = FakeGitea(
|
||||||
|
publish_wiki, wiki={"Home-da": b64("hjem"), "_Sidebar": b64("menu")}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert run_main(publish_wiki, gitea) == 0
|
||||||
|
|
||||||
|
assert gitea.writes == []
|
||||||
|
|
||||||
|
def test_prune_does_not_delete_a_page_that_is_in_the_folder_under_another_spelling(
|
||||||
|
self, publish_wiki, environment
|
||||||
|
):
|
||||||
|
(environment / "Home.md").unlink()
|
||||||
|
(environment / "Credentials.md").unlink()
|
||||||
|
(environment / "Install ms.md").write_text("pasang", encoding="utf-8")
|
||||||
|
gitea = FakeGitea(
|
||||||
|
publish_wiki, wiki={"Install-ms": b64("pasang"), "Old-page": b64("x")}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert run_main(publish_wiki, gitea) == 0
|
||||||
|
|
||||||
|
assert gitea.wiki == {"Install-ms": b64("pasang")}
|
||||||
|
|
||||||
|
def test_the_address_wins_over_the_title_and_is_decoded(self, publish_wiki):
|
||||||
|
def listing(url, headers=None, **kwargs):
|
||||||
|
return [
|
||||||
|
{"title": "Whatever", "sub_url": "Real-Address"},
|
||||||
|
{"title": "Only title"},
|
||||||
|
{"title": "Encoded", "sub_url": "Caf%C3%A9"},
|
||||||
|
]
|
||||||
|
|
||||||
|
keys = publish_wiki.list_wiki_titles(WIKI_URL, {}, listing)
|
||||||
|
|
||||||
|
assert keys == {"Real-Address", "Only-title", "Café"}
|
||||||
|
|
||||||
|
|
||||||
class TestDescribeToken:
|
class TestDescribeToken:
|
||||||
def test_recognises_a_gitea_token_shape(self, publish_wiki):
|
def test_recognises_a_gitea_token_shape(self, publish_wiki):
|
||||||
description = publish_wiki.describe_token("a1b2c3d4e5" * 4)
|
description = publish_wiki.describe_token("a1b2c3d4e5" * 4)
|
||||||
|
|||||||
Reference in New Issue
Block a user