fix(tests): repair three mock-wiring bugs in doc_sync_executor tests

test_executor_successful_sync_entire_repo: _get_git_commit is patched
separately and never calls the real _run_command, so it does not consume a
slot in mock_run.side_effect. The list reserved one anyway (labelled "git
rev-parse HEAD"), which shifted "M  README.md\n" one call late — git status
--porcelain saw "" (no changes) instead, so execute() took the no-changes
branch and the test asserted "Successfully synced" against "already up to
date". Removed the phantom slot. Also gave the iterdir() mock items real
string .name attributes: MagicMock(name="X") sets the mock's repr, not the
.name attribute read by execute()'s `item.name != '.git'` check, so every
item was being treated as non-.git regardless of the intended value.

test_executor_sync_specific_paths, test_executor_handles_no_changes:
mock_upstream_dir/mock_gitea_dir were built but never wired to
mock_path.return_value, so `work_dir = Path(...)` and `upstream_dir =
work_dir / "upstream"` resolved to a different, unconfigured auto-generated
MagicMock. `source.name` on that mock is itself a MagicMock, not a string,
so `', '.join(copied_paths)` raised TypeError. Wired mock_path.return_value
to a work_dir mock whose __truediv__ yields the intended upstream/gitea
mocks, matching the pattern the first test already used correctly.

All three are test-side: doc_sync_executor.py is unchanged. Confirmed via
git log -p that this file and its test have exactly one commit in this
repo's history (the initial extraction), so there is no prior passing
version to regress from — these tests appear to have never passed.
This commit is contained in:
2026-08-18 15:49:20 +02:00
parent 5ccfb83f2f
commit 874f9f9711
+50 -13
View File
@@ -60,23 +60,36 @@ class TestDocSyncExecutor:
mock_upstream_dir = MagicMock()
mock_gitea_dir = MagicMock()
# Setup directory mocking
mock_upstream_dir.iterdir.return_value = [
MagicMock(name=".git", is_dir=lambda: True),
MagicMock(name="README.md", is_dir=lambda: False),
MagicMock(name="docs", is_dir=lambda: True),
]
mock_gitea_dir.iterdir.return_value = [
MagicMock(name=".git", is_dir=lambda: True)
]
# Setup directory mocking. MagicMock(name=...) sets the mock's
# repr, not its .name attribute (classic gotcha — see
# test_executor_sync_specific_paths) — set .name explicitly so
# execute()'s `item.name != '.git'` check actually excludes it.
git_item = MagicMock(is_dir=lambda: True)
git_item.name = ".git"
readme_item = MagicMock(is_dir=lambda: False)
readme_item.name = "README.md"
docs_item = MagicMock(is_dir=lambda: True)
docs_item.name = "docs"
mock_upstream_dir.iterdir.return_value = [git_item, readme_item, docs_item]
gitea_git_item = MagicMock(is_dir=lambda: True)
gitea_git_item.name = ".git"
mock_gitea_dir.iterdir.return_value = [gitea_git_item]
mock_path.return_value = mock_work_dir
mock_work_dir.__truediv__.side_effect = [mock_upstream_dir, mock_gitea_dir]
# Mock git status to show changes
# Mock git status to show changes. _get_git_commit is patched
# separately above and never calls the real _run_command, so it
# does not consume a slot in this side_effect list — the actual
# call order for this (clone-succeeds, entire-repo) path is:
# clone upstream, clone gitea, add, status, commit, tag, push
# branch, push tag. The list previously reserved a slot for
# "git rev-parse HEAD" that _run_command is never asked for,
# which shifted "M README.md\n" one call late and made
# `git status --porcelain` see "" (no changes) instead.
mock_run.side_effect = [
"", # git clone upstream
"", # git rev-parse HEAD
"", # git clone gitea
"", # git add
"M README.md\n", # git status --porcelain (has changes)
@@ -108,7 +121,15 @@ class TestDocSyncExecutor:
mock_get_commit.return_value = "abc123"
# Mock path operations
# Mock path operations. work_dir = Path(...) resolves to
# mock_path.return_value — mock_upstream_dir/mock_gitea_dir have
# to be reachable from there via __truediv__, the same way
# test_executor_successful_sync_entire_repo wires it, or
# `upstream_dir / doc_path` never reaches these mocks at all and
# falls through to an unconfigured auto-generated MagicMock
# instead (observed failure: TypeError joining a MagicMock into
# ', '.join(copied_paths)).
mock_work_dir = MagicMock()
mock_upstream_dir = MagicMock()
mock_gitea_dir = MagicMock()
mock_docs = MagicMock(name="docs")
@@ -121,6 +142,8 @@ class TestDocSyncExecutor:
mock_examples.is_dir.return_value = True
mock_examples.name = "examples"
mock_path.return_value = mock_work_dir
mock_work_dir.__truediv__.side_effect = [mock_upstream_dir, mock_gitea_dir]
mock_upstream_dir.__truediv__.side_effect = [mock_docs, mock_examples]
mock_gitea_dir.iterdir.return_value = []
@@ -159,13 +182,27 @@ class TestDocSyncExecutor:
mock_run.side_effect = run_command_side_effect
# Setup minimal mocking
# Setup minimal mocking. sample_doc_sync_config's docs_paths is
# ["/docs"] (tests/conftest.py), so execute() takes the
# specific-paths branch and needs `upstream_dir / "docs"` wired
# to something with a real string .name — see
# test_executor_sync_specific_paths for the same wiring gap and
# the TypeError it produces unwired.
mock_work_dir = MagicMock()
mock_upstream_dir = MagicMock()
mock_gitea_dir = MagicMock()
mock_gitea_dir.iterdir.return_value = []
mock_upstream_dir.iterdir.return_value = []
mock_docs = MagicMock()
mock_docs.exists.return_value = True
mock_docs.is_dir.return_value = True
mock_docs.name = "docs"
mock_path.return_value = mock_work_dir
mock_work_dir.__truediv__.side_effect = [mock_upstream_dir, mock_gitea_dir]
mock_upstream_dir.__truediv__.side_effect = [mock_docs]
result = await doc_sync_executor.execute(sample_doc_sync_config, test_settings)
assert "already up to date" in result.lower() or "no changes" in result.lower()