Publish the wiki through a reusable workflow; this wiki moves to docs/wiki
- publish-wiki-pages.yml: reusable workflow with wiki_dir, prune and tools-ref inputs and the declared secret TOKEN_FOR_GITEA_WIKI - tools/publish_wiki.py: WIKI_PRUNE selects explicit mode without the marker; a missing folder is a configuration error - publish-own-wiki.yml: this repository calls the reusable workflow - wiki/ moves to docs/wiki/; build_wiki.py and its tests follow - the scoped publish-wiki.yml is marked deprecated, not removed - guides, README and the Danish and Bahasa Malaysia wiki pages describe the caller, docs/wiki/ and the new error messages Task: MIL-008#2 Task: MIL-008#4 Task: MIL-008#5 Task: MIL-008#6
This commit is contained in:
@@ -271,7 +271,7 @@ def _shape(text: str) -> dict:
|
||||
@pytest.mark.parametrize("code", [code for code, _label in build_wiki.LANGUAGES])
|
||||
def test_committed_translations_follow_the_english_structure(page, code):
|
||||
"""A translation has the same code blocks, table rows and heading levels."""
|
||||
wiki = REPO_ROOT / "wiki"
|
||||
wiki = REPO_ROOT / "docs" / "wiki"
|
||||
english = _shape((wiki / f"{page}.md").read_text(encoding="utf-8"))
|
||||
translated = _shape((wiki / f"{page}.{code}.md").read_text(encoding="utf-8"))
|
||||
assert translated == english
|
||||
@@ -306,7 +306,7 @@ class TestSidebar:
|
||||
|
||||
def test_home_lists_every_workflow_file_with_a_diagram():
|
||||
"""The Home page names each workflow file of the repository and draws it."""
|
||||
home = (REPO_ROOT / "wiki" / "Home.md").read_text(encoding="utf-8")
|
||||
home = (REPO_ROOT / "docs" / "wiki" / "Home.md").read_text(encoding="utf-8")
|
||||
files = sorted(
|
||||
path.name
|
||||
for folder in (".gitea/scoped_workflows", ".gitea/workflows")
|
||||
|
||||
+131
-7
@@ -1,4 +1,4 @@
|
||||
"""Offline tests for tools/publish_wiki.py and the scoped publish-wiki workflow."""
|
||||
"""Offline tests for tools/publish_wiki.py and the publish-wiki workflows."""
|
||||
|
||||
import base64
|
||||
import io
|
||||
@@ -9,6 +9,8 @@ import yaml
|
||||
|
||||
from workflow_source import PUBLISH_WORKFLOW, REPO_ROOT, load_publish_module
|
||||
|
||||
REUSABLE_WORKFLOW = REPO_ROOT / ".gitea" / "workflows" / "publish-wiki-pages.yml"
|
||||
|
||||
API = "https://git.example.test/api/v1"
|
||||
REPO_URL = f"{API}/repos/TirSystem/repo"
|
||||
WIKI_URL = f"{REPO_URL}/wiki"
|
||||
@@ -450,6 +452,121 @@ class TestRequestJson:
|
||||
assert caught.value.category == "api"
|
||||
|
||||
|
||||
class TestExplicitPrune:
|
||||
"""The reusable workflow sets WIKI_PRUNE: the caller is the opt-in (MIL-008)."""
|
||||
|
||||
@pytest.fixture()
|
||||
def explicit(self, environment, monkeypatch):
|
||||
(environment / ".publish").unlink()
|
||||
monkeypatch.setenv("WIKI_PRUNE", "false")
|
||||
return environment
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("text", "expected"), [("true", True), (" TRUE\n", True), ("false", False)]
|
||||
)
|
||||
def test_reads_true_and_false(self, publish_wiki, text, expected):
|
||||
assert publish_wiki.parse_prune_setting(text) is expected
|
||||
|
||||
@pytest.mark.parametrize("text", ["", "yes", "prune", None])
|
||||
def test_rejects_anything_else(self, publish_wiki, text):
|
||||
with pytest.raises(publish_wiki.WorkflowError) as caught:
|
||||
publish_wiki.parse_prune_setting(text)
|
||||
assert caught.value.category == "configuration"
|
||||
|
||||
def test_publishes_without_a_marker_and_keeps_other_pages(
|
||||
self, publish_wiki, explicit, capsys
|
||||
):
|
||||
gitea = FakeGitea(publish_wiki, wiki={"Old": b64("hand written")})
|
||||
|
||||
assert run_main(publish_wiki, gitea) == 0
|
||||
|
||||
assert set(gitea.wiki) == {"Old", "Home", "Credentials"}
|
||||
assert "set the prune input to true" in capsys.readouterr().out
|
||||
|
||||
def test_prune_true_deletes_pages_outside_the_folder(
|
||||
self, publish_wiki, explicit, monkeypatch
|
||||
):
|
||||
monkeypatch.setenv("WIKI_PRUNE", "true")
|
||||
gitea = FakeGitea(publish_wiki, wiki={"Old": b64("stale")})
|
||||
|
||||
assert run_main(publish_wiki, gitea) == 0
|
||||
|
||||
assert set(gitea.wiki) == {"Home", "Credentials"}
|
||||
|
||||
def test_a_missing_folder_is_a_configuration_error_before_any_request(
|
||||
self, publish_wiki, explicit, monkeypatch, tmp_path
|
||||
):
|
||||
monkeypatch.setenv("WIKI_DIR", str(tmp_path / "docs" / "wiki"))
|
||||
gitea = FakeGitea(publish_wiki)
|
||||
|
||||
with pytest.raises(publish_wiki.WorkflowError, match="wiki_dir") as caught:
|
||||
run_main(publish_wiki, gitea)
|
||||
|
||||
assert caught.value.category == "configuration"
|
||||
assert gitea.calls == []
|
||||
|
||||
def test_an_empty_folder_never_deletes_pages_even_with_prune(
|
||||
self, publish_wiki, explicit, monkeypatch
|
||||
):
|
||||
monkeypatch.setenv("WIKI_PRUNE", "true")
|
||||
for page in explicit.glob("*.md"):
|
||||
page.unlink()
|
||||
gitea = FakeGitea(publish_wiki, wiki={"Home": b64("home")})
|
||||
|
||||
with pytest.raises(publish_wiki.WorkflowError) as caught:
|
||||
run_main(publish_wiki, gitea)
|
||||
|
||||
assert caught.value.category == "configuration"
|
||||
assert gitea.writes == []
|
||||
|
||||
def test_a_missing_secret_fails_before_any_request(
|
||||
self, publish_wiki, explicit, monkeypatch
|
||||
):
|
||||
monkeypatch.setenv("WIKI_CREDENTIALS", "")
|
||||
gitea = FakeGitea(publish_wiki)
|
||||
|
||||
with pytest.raises(publish_wiki.WorkflowError, match="TOKEN_FOR_GITEA_WIKI"):
|
||||
run_main(publish_wiki, gitea)
|
||||
|
||||
assert gitea.calls == []
|
||||
|
||||
|
||||
class TestReusableWorkflow:
|
||||
@pytest.fixture()
|
||||
def document(self):
|
||||
return yaml.safe_load(REUSABLE_WORKFLOW.read_text(encoding="utf-8"))
|
||||
|
||||
def test_is_called_and_has_no_other_trigger(self, document):
|
||||
assert list(document[True]) == ["workflow_call"]
|
||||
|
||||
def test_the_folder_defaults_to_docs_wiki_and_nothing_is_pruned_by_default(self, document):
|
||||
inputs = document[True]["workflow_call"]["inputs"]
|
||||
assert inputs["wiki_dir"]["default"] == "docs/wiki"
|
||||
assert inputs["prune"]["default"] == "false"
|
||||
|
||||
def test_the_token_is_a_declared_secret_without_a_fallback(self, document):
|
||||
text = REUSABLE_WORKFLOW.read_text(encoding="utf-8")
|
||||
assert document[True]["workflow_call"]["secrets"]["TOKEN_FOR_GITEA_WIKI"]["required"]
|
||||
assert "secrets.TOKEN_FOR_GITEA }}" not in text
|
||||
assert "github.token" not in text
|
||||
|
||||
def test_job_has_read_only_permissions(self, document):
|
||||
assert document["jobs"]["publish-wiki"]["permissions"] == {"contents": "read"}
|
||||
|
||||
def test_the_inputs_reach_the_script_through_the_environment(self, document):
|
||||
step = document["jobs"]["publish-wiki"]["steps"][-1]
|
||||
assert step["env"]["WIKI_DIR"] == "${{ inputs.wiki_dir }}"
|
||||
assert step["env"]["WIKI_PRUNE"] == "${{ inputs.prune }}"
|
||||
assert step["env"]["WIKI_CREDENTIALS"] == "${{ secrets.TOKEN_FOR_GITEA_WIKI }}"
|
||||
assert step["run"] == "python3 shared-scripts/tools/publish_wiki.py"
|
||||
assert "inputs." not in step["run"]
|
||||
|
||||
def test_the_script_is_fetched_at_the_tools_ref(self, document):
|
||||
fetch = document["jobs"]["publish-wiki"]["steps"][1]["with"]
|
||||
assert fetch["repository"] == "TirSystem/github-action"
|
||||
assert fetch["ref"] == "${{ inputs.tools-ref }}"
|
||||
|
||||
|
||||
class TestWorkflow:
|
||||
@pytest.fixture()
|
||||
def document(self):
|
||||
@@ -471,8 +588,7 @@ class TestWorkflow:
|
||||
assert publish_step["env"]["WIKI_CREDENTIALS"] == "${{ secrets.TOKEN_FOR_GITEA_WIKI }}"
|
||||
assert "secrets" not in publish_step["run"]
|
||||
|
||||
def test_the_workflow_has_one_source_and_runs_the_script_file(self, document):
|
||||
assert not (REPO_ROOT / ".gitea" / "workflows" / "publish-wiki.yml").exists()
|
||||
def test_the_scoped_workflow_runs_the_script_file(self, document):
|
||||
assert (REPO_ROOT / "tools" / "publish_wiki.py").is_file()
|
||||
steps = document["jobs"]["publish-wiki"]["steps"]
|
||||
assert "python3 - <<" not in PUBLISH_WORKFLOW.read_text(encoding="utf-8")
|
||||
@@ -485,7 +601,15 @@ class TestWorkflow:
|
||||
assert steps[-1]["run"] == "python3 shared-scripts/tools/publish_wiki.py"
|
||||
|
||||
|
||||
def test_this_repository_opts_in_and_prunes():
|
||||
"""The wiki of this repository must equal wiki/ (ADR-0001, ADR-0004)."""
|
||||
marker = REPO_ROOT / "wiki" / ".publish"
|
||||
assert marker.read_text(encoding="utf-8").strip() == "prune"
|
||||
def test_this_repository_publishes_docs_wiki_through_the_reusable_workflow():
|
||||
"""The wiki of this repository equals docs/wiki/ (ADR-0009, MIL-008 task 4)."""
|
||||
assert not (REPO_ROOT / "wiki").exists()
|
||||
assert (REPO_ROOT / "docs" / "wiki" / "Home.md").is_file()
|
||||
caller = yaml.safe_load(
|
||||
(REPO_ROOT / ".gitea" / "workflows" / "publish-own-wiki.yml").read_text(encoding="utf-8")
|
||||
)
|
||||
job = caller["jobs"]["wiki"]
|
||||
assert job["uses"] == "./.gitea/workflows/publish-wiki-pages.yml"
|
||||
assert job["with"]["wiki_dir"] == "docs/wiki"
|
||||
assert job["with"]["prune"] == "true"
|
||||
assert caller[True]["push"]["paths"] == ["docs/wiki/**"]
|
||||
|
||||
@@ -281,7 +281,7 @@ class TestBuildWikiCommandLine:
|
||||
monkeypatch.setattr(build_wiki._module, "check_translations", lambda: [])
|
||||
assert build_wiki["main"](["--check"]) == 1
|
||||
err = capsys.readouterr().err
|
||||
assert "ERROR: wiki/Home.md: missing" in err
|
||||
assert "ERROR: docs/wiki/Home.md: missing" in err
|
||||
assert "python tools/build_wiki.py" in err
|
||||
|
||||
def test_generate_reports_the_changed_pages(self, monkeypatch, capsys):
|
||||
@@ -320,7 +320,7 @@ class TestRunAsAProcess:
|
||||
assert result.stderr.startswith("ERROR: [configuration]")
|
||||
|
||||
def test_publish_wiki_without_a_repository_exits_1(self):
|
||||
result = run_tool("publish_wiki.py", WIKI_DIR=str(REPO_ROOT / "wiki"), GITEA_API_URL="https://x")
|
||||
result = run_tool("publish_wiki.py", WIKI_DIR=str(REPO_ROOT / "docs" / "wiki"), GITEA_API_URL="https://x")
|
||||
assert result.returncode == 1
|
||||
assert "ERROR:" in result.stderr
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ class TestEachWorkflow:
|
||||
def test_every_job_declares_permissions_and_runner(self, path):
|
||||
document = yaml.safe_load(path.read_text(encoding="utf-8"))
|
||||
for name, job in document["jobs"].items():
|
||||
assert "runs-on" in job, f"job {name} has no runs-on"
|
||||
if "uses" not in job: # a called workflow has its own runner
|
||||
assert "runs-on" in job, f"job {name} has no runs-on"
|
||||
assert "permissions" in job, f"job {name} has no explicit permissions"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user