Skip to content

Commit ac4c006

Browse files
committed
tests(feat): Add comprehensive tests for new CLI commands
why: New commands (add, discover, list, status) need test coverage to ensure reliability and prevent regressions. Tests follow project's NamedTuple fixture pattern for parameterized testing. what: - Create tests/cli/test_add.py with 5 tests: * Parameterized tests for add with default/custom workspace * Dry-run mode test * Duplicate repository warning test * New config file creation test - Create tests/cli/test_discover.py with 6 tests: * Parameterized tests for single-level and recursive discovery * Dry-run mode test * Skip repos without remote URL test * Show existing repos test * Workspace override test - Create tests/cli/test_list.py with 6 tests: * Parameterized tests for listing all/filtered repos * JSON output test * Tree mode test * Empty config test * Pattern no-match test - Create tests/cli/test_status.py with 7 tests: * Parameterized tests for repo status (exists/git/missing) * Status all repos test * JSON output test * Detailed mode test * Pattern filter test Testing patterns: - Use caplog.set_level(logging.INFO) to capture log output - Use tmp_path and monkeypatch for isolated test environments - Follow project's NamedTuple fixture pattern for parameterization - Test both human and machine-readable output modes refs: All 133 tests pass (109 original + 24 new), mypy clean, ruff clean
1 parent 936488a commit ac4c006

4 files changed

Lines changed: 984 additions & 0 deletions

File tree

tests/cli/test_add.py

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
"""Tests for vcspull add command."""
2+
3+
from __future__ import annotations
4+
5+
import pathlib
6+
import typing as t
7+
8+
import pytest
9+
10+
from vcspull.cli.add import add_repo
11+
12+
if t.TYPE_CHECKING:
13+
from _pytest.monkeypatch import MonkeyPatch
14+
15+
16+
class AddRepoFixture(t.NamedTuple):
17+
"""Fixture for add repo test cases."""
18+
19+
test_id: str
20+
name: str
21+
url: str
22+
workspace_root: str | None
23+
path: str | None
24+
dry_run: bool
25+
expected_in_config: dict[str, t.Any]
26+
expected_log_messages: list[str]
27+
28+
29+
ADD_REPO_FIXTURES: list[AddRepoFixture] = [
30+
AddRepoFixture(
31+
test_id="simple-add-default-workspace",
32+
name="myproject",
33+
url="git+https://github.com/user/myproject.git",
34+
workspace_root=None,
35+
path=None,
36+
dry_run=False,
37+
expected_in_config={
38+
"./": {
39+
"myproject": {"repo": "git+https://github.com/user/myproject.git"},
40+
},
41+
},
42+
expected_log_messages=["Successfully added 'myproject'"],
43+
),
44+
AddRepoFixture(
45+
test_id="add-with-custom-workspace",
46+
name="flask",
47+
url="git+https://github.com/pallets/flask.git",
48+
workspace_root="~/code/",
49+
path=None,
50+
dry_run=False,
51+
expected_in_config={
52+
"~/code/": {
53+
"flask": {"repo": "git+https://github.com/pallets/flask.git"},
54+
},
55+
},
56+
expected_log_messages=["Successfully added 'flask'"],
57+
),
58+
AddRepoFixture(
59+
test_id="dry-run-no-write",
60+
name="django",
61+
url="git+https://github.com/django/django.git",
62+
workspace_root=None,
63+
path=None,
64+
dry_run=True,
65+
expected_in_config={}, # Nothing written in dry-run
66+
expected_log_messages=["Would add 'django'"],
67+
),
68+
]
69+
70+
71+
@pytest.mark.parametrize(
72+
list(AddRepoFixture._fields),
73+
ADD_REPO_FIXTURES,
74+
ids=[fixture.test_id for fixture in ADD_REPO_FIXTURES],
75+
)
76+
def test_add_repo(
77+
test_id: str,
78+
name: str,
79+
url: str,
80+
workspace_root: str | None,
81+
path: str | None,
82+
dry_run: bool,
83+
expected_in_config: dict[str, t.Any],
84+
expected_log_messages: list[str],
85+
tmp_path: pathlib.Path,
86+
monkeypatch: MonkeyPatch,
87+
caplog: t.Any,
88+
) -> None:
89+
"""Test adding a repository to the config."""
90+
# Set logging level to capture INFO messages
91+
import logging
92+
93+
caplog.set_level(logging.INFO)
94+
95+
# Set up temp directory as home
96+
monkeypatch.setenv("HOME", str(tmp_path))
97+
monkeypatch.chdir(tmp_path)
98+
99+
config_file = tmp_path / ".vcspull.yaml"
100+
101+
# Run add_repo
102+
add_repo(
103+
name=name,
104+
url=url,
105+
config_file_path_str=str(config_file),
106+
path=path,
107+
workspace_root_path=workspace_root,
108+
dry_run=dry_run,
109+
)
110+
111+
# Check log messages
112+
log_output = caplog.text
113+
for expected_msg in expected_log_messages:
114+
assert expected_msg in log_output, (
115+
f"Expected '{expected_msg}' in log output, got: {log_output}"
116+
)
117+
118+
# Check config file
119+
if dry_run:
120+
# In dry-run mode, config file should not be created
121+
if len(expected_in_config) == 0:
122+
assert not config_file.exists(), (
123+
"Config file should not be created in dry-run mode"
124+
)
125+
else:
126+
# In normal mode, check the config was written correctly
127+
if len(expected_in_config) > 0:
128+
assert config_file.exists(), "Config file should be created"
129+
130+
import yaml
131+
132+
with config_file.open() as f:
133+
actual_config = yaml.safe_load(f)
134+
135+
for workspace, repos in expected_in_config.items():
136+
assert workspace in actual_config, (
137+
f"Workspace '{workspace}' should be in config"
138+
)
139+
for repo_name, repo_data in repos.items():
140+
assert repo_name in actual_config[workspace], (
141+
f"Repo '{repo_name}' should be in workspace '{workspace}'"
142+
)
143+
assert actual_config[workspace][repo_name] == repo_data
144+
145+
146+
def test_add_repo_duplicate_warning(
147+
tmp_path: pathlib.Path,
148+
monkeypatch: MonkeyPatch,
149+
caplog: t.Any,
150+
) -> None:
151+
"""Test that adding a duplicate repository shows a warning."""
152+
import logging
153+
154+
caplog.set_level(logging.INFO)
155+
156+
monkeypatch.setenv("HOME", str(tmp_path))
157+
monkeypatch.chdir(tmp_path)
158+
159+
config_file = tmp_path / ".vcspull.yaml"
160+
161+
# Add repo first time
162+
add_repo(
163+
name="myproject",
164+
url="git+https://github.com/user/myproject.git",
165+
config_file_path_str=str(config_file),
166+
path=None,
167+
workspace_root_path=None,
168+
dry_run=False,
169+
)
170+
171+
# Clear logs
172+
caplog.clear()
173+
174+
# Try to add again
175+
add_repo(
176+
name="myproject",
177+
url="git+https://github.com/user/myproject-v2.git",
178+
config_file_path_str=str(config_file),
179+
path=None,
180+
workspace_root_path=None,
181+
dry_run=False,
182+
)
183+
184+
# Should have warning
185+
assert "already exists" in caplog.text
186+
187+
188+
def test_add_repo_creates_new_file(
189+
tmp_path: pathlib.Path,
190+
monkeypatch: MonkeyPatch,
191+
) -> None:
192+
"""Test that add_repo creates a new config file if it doesn't exist."""
193+
monkeypatch.setenv("HOME", str(tmp_path))
194+
monkeypatch.chdir(tmp_path)
195+
196+
config_file = tmp_path / ".vcspull.yaml"
197+
assert not config_file.exists()
198+
199+
add_repo(
200+
name="newrepo",
201+
url="git+https://github.com/user/newrepo.git",
202+
config_file_path_str=str(config_file),
203+
path=None,
204+
workspace_root_path=None,
205+
dry_run=False,
206+
)
207+
208+
assert config_file.exists()
209+
210+
import yaml
211+
212+
with config_file.open() as f:
213+
config = yaml.safe_load(f)
214+
215+
assert "./" in config
216+
assert "newrepo" in config["./"]

0 commit comments

Comments
 (0)