Public Access
DEVX-115: fix: make wiki sync resilient to API timeouts and stale page lists
Post-merge / detect-type (push) Successful in 12s
Post-merge / validate-commit-msg (push) Successful in 9s
Post-merge / vikunja (push) Successful in 16s
Post-merge / release (push) Successful in 39s
Post-merge / configure-repo (push) Successful in 17s
Post-merge / sync-wiki (push) Successful in 45s
Post-merge / badges (push) Successful in 47s
Post-merge / publish (push) Successful in 18s
Post-merge / detect-type (push) Successful in 12s
Post-merge / validate-commit-msg (push) Successful in 9s
Post-merge / vikunja (push) Successful in 16s
Post-merge / release (push) Successful in 39s
Post-merge / configure-repo (push) Successful in 17s
Post-merge / sync-wiki (push) Successful in 45s
Post-merge / badges (push) Successful in 47s
Post-merge / publish (push) Successful in 18s
This commit was merged in pull request #174.
This commit is contained in:
@@ -171,6 +171,35 @@ class TestSyncPage:
|
||||
assert "content" not in payload
|
||||
assert base64.b64decode(payload["content_base64"]).decode("utf-8") == "# Updated"
|
||||
|
||||
def test_create_falls_back_to_update_on_already_exists(self) -> None:
|
||||
"""When create fails with 400 'already exists', re-list and update."""
|
||||
client = MagicMock()
|
||||
# First call: POST /wiki/new → 400 already exists
|
||||
# Second call: PATCH /wiki/page/{sub_url} → success
|
||||
create_error = APIError(400, "wiki page already exists [title: Test-Page]")
|
||||
client._request.side_effect = [create_error, MagicMock()]
|
||||
with patch("devx.ci.sync_wiki._list_wiki_pages_with_retry", return_value={"Test-Page": "Test-Page.-"}):
|
||||
result = sync_page(client, "Test-Page", "# Content", {}, dry_run=False)
|
||||
assert result == "updated"
|
||||
# Verify PATCH was called (second call)
|
||||
patch_call = client._request.call_args_list[1]
|
||||
assert patch_call.args[0] == "PATCH"
|
||||
assert "/wiki/page/Test-Page.-" in patch_call.args[1]
|
||||
|
||||
def test_create_raises_non_400_error(self) -> None:
|
||||
"""Non-400 errors from create should propagate, not trigger fallback."""
|
||||
client = MagicMock()
|
||||
client._request.side_effect = APIError(500, "server error")
|
||||
with pytest.raises(APIError):
|
||||
sync_page(client, "Test-Page", "# Content", {}, dry_run=False)
|
||||
|
||||
def test_create_raises_400_not_already_exists(self) -> None:
|
||||
"""400 errors that don't mention 'already exists' should propagate."""
|
||||
client = MagicMock()
|
||||
client._request.side_effect = APIError(400, "invalid title")
|
||||
with pytest.raises(APIError):
|
||||
sync_page(client, "Test-Page", "# Content", {}, dry_run=False)
|
||||
|
||||
|
||||
class TestVerifyWikiPage:
|
||||
def test_verifies_matching_content(self) -> None:
|
||||
@@ -538,14 +567,14 @@ class TestMain:
|
||||
@patch.dict("os.environ", {"CI_GITEA_TOKEN": "tok"}, clear=True)
|
||||
@patch("devx.ci.sync_wiki.GiteaClient")
|
||||
def test_initial_list_api_error_aborts(self, mock_client_cls: MagicMock) -> None:
|
||||
"""When the initial page list fails, sync aborts to avoid duplicate pages."""
|
||||
"""When the initial page list fails after retries, sync aborts to avoid duplicate pages."""
|
||||
mock_client = MagicMock()
|
||||
mock_client_cls.return_value = mock_client
|
||||
with patch("devx.ci.sync_wiki.MAPPING_FILE") as mock_mapping:
|
||||
mock_mapping.exists.return_value = True
|
||||
with patch("devx.ci.sync_wiki.load_mapping", return_value={"index.md": "Home"}):
|
||||
with patch("devx.ci.sync_wiki.read_doc_content", return_value="# Home"):
|
||||
with patch("devx.ci.sync_wiki.list_wiki_pages", side_effect=APIError(0, "timeout")):
|
||||
with patch("devx.ci.sync_wiki._list_wiki_pages_with_retry", side_effect=APIError(0, "timeout")):
|
||||
with patch("devx.ci.sync_wiki.sync_page", return_value="created"):
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ["--repo", "owner/repo"])
|
||||
@@ -559,17 +588,15 @@ class TestMain:
|
||||
"""When --verify re-fetch fails after retries, verification is skipped gracefully."""
|
||||
mock_client = MagicMock()
|
||||
mock_client_cls.return_value = mock_client
|
||||
# Initial list succeeds, but verify re-fetch fails
|
||||
list_side_effect = [{"Home": "Home"}, APIError(0, "timeout")]
|
||||
with patch("devx.ci.sync_wiki.MAPPING_FILE") as mock_mapping:
|
||||
mock_mapping.exists.return_value = True
|
||||
with patch("devx.ci.sync_wiki.load_mapping", return_value={"index.md": "Home"}):
|
||||
with patch("devx.ci.sync_wiki.read_doc_content", return_value="# Home"):
|
||||
with patch("devx.ci.sync_wiki.list_wiki_pages", return_value={"Home": "Home"}):
|
||||
with patch("devx.ci.sync_wiki._list_wiki_pages_with_retry", side_effect=list_side_effect):
|
||||
with patch("devx.ci.sync_wiki.sync_page", return_value="updated"):
|
||||
with patch(
|
||||
"devx.ci.sync_wiki._list_wiki_pages_with_retry",
|
||||
side_effect=APIError(0, "timeout"),
|
||||
):
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ["--repo", "owner/repo", "--verify"])
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ["--repo", "owner/repo", "--verify"])
|
||||
assert result.exit_code == 0
|
||||
assert "Skipping content verification" in result.output
|
||||
|
||||
Reference in New Issue
Block a user