Skip to content

Commit 3d5b488

Browse files
committed
tests/cli(refactor[typing]): Tighten config fixture types
why: Make CLI test fixtures and plan helper kwargs type-safe for better mypy coverage. what: - Add explicit config structure aliases in CLI tests - Use a TypedDict for PlanEntry kwargs and a typed subprocess args alias
1 parent 5b39955 commit 3d5b488

6 files changed

Lines changed: 61 additions & 22 deletions

File tree

tests/cli/test_add.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
from syrupy.assertion import SnapshotAssertion
2323

2424

25+
ConfigData: t.TypeAlias = dict[str, dict[str, dict[str, str]]]
26+
27+
2528
class AddRepoFixture(t.NamedTuple):
2629
"""Fixture for add repo test cases."""
2730

@@ -32,8 +35,8 @@ class AddRepoFixture(t.NamedTuple):
3235
path_relative: str | None
3336
dry_run: bool
3437
use_default_config: bool
35-
preexisting_config: dict[str, t.Any] | None
36-
expected_in_config: dict[str, t.Any]
38+
preexisting_config: ConfigData | None
39+
expected_in_config: ConfigData
3740
expected_log_messages: list[str]
3841

3942

@@ -169,8 +172,8 @@ def test_add_repo(
169172
path_relative: str | None,
170173
dry_run: bool,
171174
use_default_config: bool,
172-
preexisting_config: dict[str, t.Any] | None,
173-
expected_in_config: dict[str, t.Any],
175+
preexisting_config: ConfigData | None,
176+
expected_in_config: ConfigData,
174177
expected_log_messages: list[str],
175178
tmp_path: pathlib.Path,
176179
monkeypatch: MonkeyPatch,

tests/cli/test_discover.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class DiscoverFixture(t.NamedTuple):
4141
yes: bool
4242
expected_repo_count: int
4343
config_relpath: str | None
44-
preexisting_config: dict[str, t.Any] | None
44+
preexisting_config: dict[str, object] | None
4545
user_input: str | None
4646
expected_workspace_labels: set[str] | None
4747
merge_duplicates: bool
@@ -352,7 +352,7 @@ def test_discover_repos(
352352
yes: bool,
353353
expected_repo_count: int,
354354
config_relpath: str | None,
355-
preexisting_config: dict[str, t.Any] | None,
355+
preexisting_config: dict[str, object] | None,
356356
user_input: str | None,
357357
expected_workspace_labels: set[str] | None,
358358
merge_duplicates: bool,
@@ -785,9 +785,9 @@ def test_discover_normalization_only_save(
785785
config_file = tmp_path / ".vcspull.yaml"
786786
config_file.write_text(yaml.dump(preexisting_config), encoding="utf-8")
787787

788-
save_calls: list[tuple[pathlib.Path, dict[str, t.Any]]] = []
788+
save_calls: list[tuple[pathlib.Path, dict[str, object]]] = []
789789

790-
def _fake_save(path: pathlib.Path, data: dict[str, t.Any]) -> None:
790+
def _fake_save(path: pathlib.Path, data: dict[str, object]) -> None:
791791
save_calls.append((path, data))
792792

793793
monkeypatch.setattr("vcspull.cli.discover.save_config_yaml", _fake_save)
@@ -978,7 +978,7 @@ def test_discover_skips_non_dict_workspace(
978978
encoding="utf-8",
979979
)
980980

981-
def _fail_save(path: pathlib.Path, data: dict[str, t.Any]) -> None:
981+
def _fail_save(path: pathlib.Path, data: dict[str, object]) -> None:
982982
error_message = "save_config_yaml should not be called when skipping repo"
983983
raise AssertionError(error_message)
984984

tests/cli/test_list.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616
from _pytest.monkeypatch import MonkeyPatch
1717

1818

19-
def create_test_config(config_path: pathlib.Path, repos: dict[str, t.Any]) -> None:
19+
ConfigData: t.TypeAlias = dict[str, dict[str, dict[str, str]]]
20+
21+
22+
def create_test_config(config_path: pathlib.Path, repos: ConfigData) -> None:
2023
"""Create a test config file."""
2124
with config_path.open("w", encoding="utf-8") as f:
2225
yaml.dump(repos, f)
@@ -26,7 +29,7 @@ class ListReposFixture(t.NamedTuple):
2629
"""Fixture for list repos test cases."""
2730

2831
test_id: str
29-
config_data: dict[str, t.Any]
32+
config_data: ConfigData
3033
patterns: list[str]
3134
tree: bool
3235
output_json: bool
@@ -122,7 +125,7 @@ class ListReposFixture(t.NamedTuple):
122125
)
123126
def test_list_repos(
124127
test_id: str,
125-
config_data: dict[str, t.Any],
128+
config_data: ConfigData,
126129
patterns: list[str],
127130
tree: bool,
128131
output_json: bool,

tests/cli/test_plan_output_helpers.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from contextlib import redirect_stdout
99

1010
import pytest
11+
import typing_extensions as te
1112

1213
from vcspull.cli._colors import ColorMode, Colors
1314
from vcspull.cli._output import (
@@ -21,12 +22,32 @@
2122
from vcspull.cli.sync import PlanProgressPrinter
2223

2324

25+
class PlanEntryKwargs(t.TypedDict):
26+
"""Typed kwargs for PlanEntry construction in tests."""
27+
28+
name: str
29+
path: str
30+
workspace_root: str
31+
action: PlanAction
32+
detail: te.NotRequired[str]
33+
url: te.NotRequired[str]
34+
branch: te.NotRequired[str]
35+
remote_branch: te.NotRequired[str]
36+
current_rev: te.NotRequired[str]
37+
target_rev: te.NotRequired[str]
38+
ahead: te.NotRequired[int]
39+
behind: te.NotRequired[int]
40+
dirty: te.NotRequired[bool]
41+
error: te.NotRequired[str]
42+
diagnostics: te.NotRequired[list[str]]
43+
44+
2445
class PlanEntryPayloadFixture(t.NamedTuple):
2546
"""Fixture for PlanEntry payload serialization."""
2647

2748
test_id: str
28-
kwargs: dict[str, t.Any]
29-
expected_keys: dict[str, t.Any]
49+
kwargs: PlanEntryKwargs
50+
expected_keys: dict[str, object]
3051
unexpected_keys: set[str]
3152

3253

@@ -86,14 +107,14 @@ class PlanEntryPayloadFixture(t.NamedTuple):
86107
)
87108
def test_plan_entry_to_payload(
88109
test_id: str,
89-
kwargs: dict[str, t.Any],
90-
expected_keys: dict[str, t.Any],
110+
kwargs: PlanEntryKwargs,
111+
expected_keys: dict[str, object],
91112
unexpected_keys: set[str],
92113
) -> None:
93114
"""Ensure PlanEntry serialises optional fields correctly."""
94115
entry = PlanEntry(**kwargs)
95116
payload = entry.to_payload()
96-
payload_map = t.cast(dict[str, t.Any], payload)
117+
payload_map = t.cast(dict[str, object], payload)
97118

98119
for key, value in expected_keys.items():
99120
assert payload_map[key] == value

tests/cli/test_status.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@
2525
from vcspull.types import ConfigDict
2626

2727

28-
def create_test_config(config_path: pathlib.Path, repos: dict[str, t.Any]) -> None:
28+
ConfigData: t.TypeAlias = dict[str, dict[str, dict[str, str]]]
29+
30+
31+
def create_test_config(config_path: pathlib.Path, repos: ConfigData) -> None:
2932
"""Create a test config file."""
3033
with config_path.open("w", encoding="utf-8") as f:
3134
yaml.dump(repos, f)

tests/cli/test_sync_plan_helpers.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from __future__ import annotations
44

5+
import collections.abc as cabc
6+
import os
57
import subprocess
68
import typing as t
79

@@ -17,6 +19,13 @@
1719

1820

1921
StatusOverride: t.TypeAlias = dict[str, bool | int | None]
22+
CompletedProcessArgs: t.TypeAlias = (
23+
str
24+
| bytes
25+
| os.PathLike[str]
26+
| os.PathLike[bytes]
27+
| cabc.Sequence[str | bytes | os.PathLike[str] | os.PathLike[bytes]]
28+
)
2029

2130

2231
def _build_status(overrides: StatusOverride) -> StatusResult:
@@ -132,8 +141,8 @@ def test_maybe_fetch_behaviour(
132141
if subprocess_behavior:
133142

134143
def _patched_run(
135-
*args: t.Any,
136-
**kwargs: t.Any,
144+
args: CompletedProcessArgs,
145+
**kwargs: object,
137146
) -> subprocess.CompletedProcess[str]:
138147
if subprocess_behavior == "file-not-found":
139148
error_message = "git executable not found"
@@ -143,13 +152,13 @@ def _patched_run(
143152
raise OSError(error_message)
144153
if subprocess_behavior == "non-zero":
145154
return subprocess.CompletedProcess(
146-
args=args[0],
155+
args=args,
147156
returncode=1,
148157
stdout="",
149158
stderr="remote rejected",
150159
)
151160
return subprocess.CompletedProcess(
152-
args=args[0],
161+
args=args,
153162
returncode=0,
154163
stdout="",
155164
stderr="",

0 commit comments

Comments
 (0)