Skip to content

Commit 218c557

Browse files
committed
Review comments
1 parent 49c5ba0 commit 218c557

2 files changed

Lines changed: 49 additions & 40 deletions

File tree

dfetch/manifest/manifest.py

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -482,29 +482,36 @@ def append_project_entry(self, project_entry: "ProjectEntry") -> None:
482482
def update_project_version(self, project: ProjectEntry) -> None:
483483
"""Update a project's version in the manifest in-place, preserving layout, comments, and line endings."""
484484
p = self._find_doc_project(project.name)
485-
if p is not None:
486-
mu = p
487-
insert_pos = 1 # right after 'name:' for any newly added key
488-
for key, value in project.version._asdict().items():
489-
if value not in (None, ""):
490-
logger.debug(
491-
f"Updating {project.name} version field '{key}' to '{value}' in manifest"
492-
)
493-
if key in mu:
494-
mu[key] = _yaml_str(value)
495-
else:
496-
mu.insert(insert_pos, key, _yaml_str(value))
497-
insert_pos += 1
485+
if p is None:
486+
raise RequestedProjectNotFoundError(
487+
[project.name],
488+
[
489+
proj["name"]
490+
for proj in self._doc["manifest"]["projects"].as_marked_up()
491+
],
492+
)
493+
mu = p
494+
insert_pos = 1 # right after 'name:' for any newly added key
495+
for key, value in project.version._asdict().items():
496+
if value not in (None, ""):
497+
logger.debug(
498+
f"Updating {project.name} version field '{key}' to '{value}' in manifest"
499+
)
500+
if key in mu:
501+
mu[key] = _yaml_str(value)
498502
else:
499-
# Remove any previously-pinned key that is no longer active
500-
# (e.g. an old 'revision' when the project is now pinned by tag).
501-
mu.pop(key, None)
502-
503-
if project.integrity and project.integrity.hash:
504-
mu["integrity"] = CommentedMap({"hash": project.integrity.hash})
503+
mu.insert(insert_pos, key, _yaml_str(value))
504+
insert_pos += 1
505505
else:
506-
# Remove a stale integrity block if the project no longer carries one.
507-
mu.pop("integrity", None)
506+
# Remove any previously-pinned key that is no longer active
507+
# (e.g. an old 'revision' when the project is now pinned by tag).
508+
mu.pop(key, None)
509+
510+
if project.integrity and project.integrity.hash:
511+
mu["integrity"] = CommentedMap({"hash": project.integrity.hash})
512+
else:
513+
# Remove a stale integrity block if the project no longer carries one.
514+
mu.pop("integrity", None)
508515

509516
def check_name_uniqueness(self, project_name: str) -> None:
510517
"""Raise if *project_name* is already used in the manifest."""

tests/test_remove.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import argparse
77
from pathlib import Path
8-
from unittest.mock import Mock, patch
8+
from unittest.mock import Mock, call, patch
99

1010
import pytest
1111

@@ -105,6 +105,7 @@ def test_remove_nonexistent_project_logs_error() -> None:
105105
"nonexistent", "project 'nonexistent' not found in manifest"
106106
)
107107

108+
108109
def test_remove_with_empty_projects_list_does_nothing() -> None:
109110
"""Remove command should do nothing when no projects are specified."""
110111
remove = Remove()
@@ -158,33 +159,34 @@ def test_remove_multiple_projects_atomically() -> None:
158159
patch("dfetch.commands.remove.safe_rm") as mocked_safe_rm,
159160
patch("dfetch.commands.remove.shutil.copyfile") as mocked_copyfile,
160161
):
162+
161163
def _dump_side_effect():
162164
# Ensure no filesystem deletion happened before persistence
163165
mocked_safe_rm.assert_not_called()
166+
164167
fake_manifest.dump.side_effect = _dump_side_effect
165168

166169
remove(args)
167170

168-
# Verify validation and manifest updates happened before deletions
169-
# selected_projects should be called 3 times (once for each project validation)
170-
assert fake_manifest.selected_projects.call_count == 3
171-
fake_manifest.selected_projects.assert_any_call(["project1"])
172-
fake_manifest.selected_projects.assert_any_call(["project2"])
173-
fake_manifest.selected_projects.assert_any_call(["project3"])
174-
175-
# remove should be called 2 times (once for each existing project)
176-
assert fake_manifest.remove.call_count == 2
177-
fake_manifest.remove.assert_any_call("project1")
178-
fake_manifest.remove.assert_any_call("project3")
179-
180-
# dump should be called once (after all manifest changes)
181-
fake_manifest.dump.assert_called_once()
171+
# Verify exact call order: validate all, then remove found projects, then dump, then delete
172+
fake_manifest.assert_has_calls(
173+
[
174+
call.selected_projects(["project1"]),
175+
call.selected_projects(["project2"]),
176+
call.selected_projects(["project3"]),
177+
call.remove("project1"),
178+
call.remove("project3"),
179+
call.dump(),
180+
],
181+
any_order=False,
182+
)
182183

183-
# safe_rm should be called 2 times (once for each existing destination, after persistence)
184+
# safe_rm called once per existing project destination, after dump
184185
assert mocked_safe_rm.call_count == 2
185-
mocked_safe_rm.assert_any_call(
186-
"some_dest"
187-
) # All destinations are mocked as "some_dest"
186+
mocked_safe_rm.assert_has_calls(
187+
[call("some_dest"), call("some_dest")],
188+
any_order=False,
189+
)
188190

189191
# No backup should be created (VCS superproject)
190192
mocked_copyfile.assert_not_called()

0 commit comments

Comments
 (0)