Skip to content

Commit fe36fbd

Browse files
committed
test(import[prune]) Add collision and cross-source prune test coverage
why: Prune logic lacked tests for edge cases where the same repo name exists in multiple workspaces or from different import sources. what: - Add test_import_sync_prune_same_name_different_sources: pruning source A leaves source B's same-name entry in a different workspace intact - Add test_import_prune_cross_workspace_same_name: standalone --prune removes stale entry from one workspace without affecting another - Add test_import_sync_same_name_from_remote_not_pruned: repo matching fetched_repo_names goes through UPDATE_URL, not prune Fixes #528
1 parent d352bb4 commit fe36fbd

1 file changed

Lines changed: 202 additions & 0 deletions

File tree

tests/cli/test_import_repos.py

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3783,6 +3783,208 @@ def test_import_dry_run_shows_summary_counts(
37833783
assert "Dry run complete" in caplog.text
37843784

37853785

3786+
# ---------------------------------------------------------------------------
3787+
# Collision / cross-source prune tests
3788+
# ---------------------------------------------------------------------------
3789+
3790+
3791+
def test_import_sync_prune_same_name_different_sources(
3792+
tmp_path: pathlib.Path,
3793+
monkeypatch: MonkeyPatch,
3794+
) -> None:
3795+
"""Pruning source A leaves source B entry with the same repo name intact."""
3796+
monkeypatch.setenv("HOME", str(tmp_path))
3797+
workspace_a = tmp_path / "code"
3798+
workspace_a.mkdir()
3799+
workspace_b = tmp_path / "work"
3800+
workspace_b.mkdir()
3801+
config_file = tmp_path / ".vcspull.yaml"
3802+
3803+
# Two workspaces, each with "shared-name" from different sources
3804+
save_config_yaml(
3805+
config_file,
3806+
{
3807+
"~/code/": {
3808+
"shared-name": {
3809+
"repo": "git+git@github.com:org-a/shared-name.git",
3810+
"metadata": {"imported_from": "github:org-a"},
3811+
},
3812+
},
3813+
"~/work/": {
3814+
"shared-name": {
3815+
"repo": "git+git@github.com:org-b/shared-name.git",
3816+
"metadata": {"imported_from": "github:org-b"},
3817+
},
3818+
},
3819+
},
3820+
)
3821+
3822+
# Sync with org-a, but org-a no longer has "shared-name" → prune from org-a
3823+
importer = MockImporter(repos=[_make_repo("other-repo", owner="org-a")])
3824+
_run_import(
3825+
importer,
3826+
service_name="github",
3827+
target="org-a",
3828+
workspace=str(workspace_a),
3829+
mode="user",
3830+
language=None,
3831+
topics=None,
3832+
min_stars=0,
3833+
include_archived=False,
3834+
include_forks=False,
3835+
limit=100,
3836+
config_path_str=str(config_file),
3837+
dry_run=False,
3838+
yes=True,
3839+
output_json=False,
3840+
output_ndjson=False,
3841+
color="never",
3842+
sync=True,
3843+
import_source="github:org-a",
3844+
)
3845+
3846+
from vcspull._internal.config_reader import ConfigReader
3847+
3848+
final_config = ConfigReader._from_file(config_file)
3849+
assert final_config is not None
3850+
3851+
# org-a's shared-name should be pruned
3852+
assert "shared-name" not in final_config.get("~/code/", {})
3853+
# org-b's shared-name should be untouched
3854+
assert "shared-name" in final_config["~/work/"]
3855+
entry_b = final_config["~/work/"]["shared-name"]
3856+
assert entry_b["metadata"]["imported_from"] == "github:org-b"
3857+
3858+
3859+
def test_import_prune_cross_workspace_same_name(
3860+
tmp_path: pathlib.Path,
3861+
monkeypatch: MonkeyPatch,
3862+
) -> None:
3863+
"""Pruning one workspace leaves the other workspace's same-name entry intact."""
3864+
monkeypatch.setenv("HOME", str(tmp_path))
3865+
workspace_code = tmp_path / "code"
3866+
workspace_code.mkdir()
3867+
workspace_work = tmp_path / "work"
3868+
workspace_work.mkdir()
3869+
config_file = tmp_path / ".vcspull.yaml"
3870+
3871+
# Both workspaces have "myrepo" with different import tags
3872+
save_config_yaml(
3873+
config_file,
3874+
{
3875+
"~/code/": {
3876+
"myrepo": {
3877+
"repo": "git+git@github.com:user-a/myrepo.git",
3878+
"metadata": {"imported_from": "github:user-a"},
3879+
},
3880+
},
3881+
"~/work/": {
3882+
"myrepo": {
3883+
"repo": "git+git@github.com:user-b/myrepo.git",
3884+
"metadata": {"imported_from": "github:user-b"},
3885+
},
3886+
},
3887+
},
3888+
)
3889+
3890+
# Prune user-a: remote has other-repo but not myrepo → myrepo stale
3891+
importer = MockImporter(repos=[_make_repo("other-repo", owner="user-a")])
3892+
_run_import(
3893+
importer,
3894+
service_name="github",
3895+
target="user-a",
3896+
workspace=str(workspace_code),
3897+
mode="user",
3898+
language=None,
3899+
topics=None,
3900+
min_stars=0,
3901+
include_archived=False,
3902+
include_forks=False,
3903+
limit=100,
3904+
config_path_str=str(config_file),
3905+
dry_run=False,
3906+
yes=True,
3907+
output_json=False,
3908+
output_ndjson=False,
3909+
color="never",
3910+
prune=True,
3911+
import_source="github:user-a",
3912+
)
3913+
3914+
from vcspull._internal.config_reader import ConfigReader
3915+
3916+
final_config = ConfigReader._from_file(config_file)
3917+
assert final_config is not None
3918+
3919+
# user-a's entry should be pruned
3920+
assert "myrepo" not in final_config.get("~/code/", {})
3921+
# user-b's entry should be untouched
3922+
assert "myrepo" in final_config["~/work/"]
3923+
assert (
3924+
final_config["~/work/"]["myrepo"]["metadata"]["imported_from"]
3925+
== "github:user-b"
3926+
)
3927+
3928+
3929+
def test_import_sync_same_name_from_remote_not_pruned(
3930+
tmp_path: pathlib.Path,
3931+
monkeypatch: MonkeyPatch,
3932+
) -> None:
3933+
"""A repo matching fetched_repo_names is NOT pruned even if URL changed."""
3934+
monkeypatch.setenv("HOME", str(tmp_path))
3935+
workspace = tmp_path / "repos"
3936+
workspace.mkdir()
3937+
config_file = tmp_path / ".vcspull.yaml"
3938+
3939+
# Existing repo with old URL, tagged from same source
3940+
save_config_yaml(
3941+
config_file,
3942+
{
3943+
"~/repos/": {
3944+
"repo-x": {
3945+
"repo": "git+git@github.com:testuser/repo-x-OLD.git",
3946+
"metadata": {"imported_from": "github:testuser"},
3947+
},
3948+
},
3949+
},
3950+
)
3951+
3952+
# Remote still has "repo-x" (new URL) → UPDATE_URL, NOT prune
3953+
importer = MockImporter(repos=[_make_repo("repo-x")])
3954+
_run_import(
3955+
importer,
3956+
service_name="github",
3957+
target="testuser",
3958+
workspace=str(workspace),
3959+
mode="user",
3960+
language=None,
3961+
topics=None,
3962+
min_stars=0,
3963+
include_archived=False,
3964+
include_forks=False,
3965+
limit=100,
3966+
config_path_str=str(config_file),
3967+
dry_run=False,
3968+
yes=True,
3969+
output_json=False,
3970+
output_ndjson=False,
3971+
color="never",
3972+
sync=True,
3973+
import_source="github:testuser",
3974+
)
3975+
3976+
from vcspull._internal.config_reader import ConfigReader
3977+
3978+
final_config = ConfigReader._from_file(config_file)
3979+
assert final_config is not None
3980+
3981+
# repo-x should still exist (not pruned) with updated URL
3982+
assert "repo-x" in final_config["~/repos/"]
3983+
entry = final_config["~/repos/"]["repo-x"]
3984+
assert entry["repo"] == _SSH.replace("repo1", "repo-x")
3985+
assert entry["metadata"]["imported_from"] == "github:testuser"
3986+
3987+
37863988
def test_import_parser_has_prune_flag() -> None:
37873989
"""The shared parent parser must expose --prune."""
37883990
from vcspull.cli.import_cmd._common import _create_shared_parent

0 commit comments

Comments
 (0)