Skip to content

Commit bff187f

Browse files
committed
chore(tests): extending test coverage
1 parent fc32108 commit bff187f

13 files changed

Lines changed: 1339 additions & 3 deletions

CHANGELOG.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Changelog
22

3-
## [Unreleased]
3+
## [2.0.0b3]
44

55
### Licensing
66

@@ -10,8 +10,6 @@
1010

1111
- Ship both `LICENSE` and `LICENSE-docs`, update package metadata, and sync file-level SPDX headers.
1212

13-
## [2.0.0b3]
14-
1513
### MCP server
1614

1715
- Add optional `codeclone[mcp]` extra with `codeclone-mcp` launcher (`stdio` and `streamable-http` transports).

tests/test_baseline.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import json
88
from collections.abc import Callable
99
from pathlib import Path
10+
from typing import Any, cast
1011

1112
import pytest
1213

@@ -225,6 +226,16 @@ def test_baseline_load_legacy_payload(tmp_path: Path) -> None:
225226
assert exc.value.status == "missing_fields"
226227

227228

229+
def test_baseline_load_rejects_non_object_preloaded_payload(tmp_path: Path) -> None:
230+
baseline_path = tmp_path / "baseline.json"
231+
_write_payload(baseline_path, _trusted_payload())
232+
baseline = Baseline(baseline_path)
233+
234+
with pytest.raises(BaselineValidationError, match="must be an object") as exc:
235+
baseline.load(preloaded_payload=cast(Any, []))
236+
assert exc.value.status == "invalid_type"
237+
238+
228239
def test_baseline_load_missing_top_level_key(tmp_path: Path) -> None:
229240
baseline_path = tmp_path / "baseline.json"
230241
_write_payload(baseline_path, {"meta": {}})
@@ -784,6 +795,25 @@ def _boom_stat(self: Path) -> object:
784795
assert exc.value.status == "invalid_type"
785796

786797

798+
def test_baseline_atomic_write_json_cleans_up_temp_file_on_replace_failure(
799+
tmp_path: Path,
800+
monkeypatch: pytest.MonkeyPatch,
801+
) -> None:
802+
path = tmp_path / "baseline.json"
803+
temp_holder: dict[str, Path] = {}
804+
805+
def _boom_replace(src: str | Path, dst: str | Path) -> None:
806+
temp_holder["path"] = Path(src)
807+
raise OSError("replace failed")
808+
809+
monkeypatch.setattr("codeclone.baseline.os.replace", _boom_replace)
810+
811+
with pytest.raises(OSError, match="replace failed"):
812+
baseline_mod._atomic_write_json(path, _trusted_payload())
813+
814+
assert temp_holder["path"].exists() is False
815+
816+
787817
def test_baseline_load_json_read_error(
788818
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
789819
) -> None:
@@ -818,6 +848,22 @@ def test_baseline_optional_str_paths(tmp_path: Path) -> None:
818848
assert exc.value.status == "invalid_type"
819849

820850

851+
def test_baseline_require_utc_iso8601_z_rejects_invalid_calendar_date(
852+
tmp_path: Path,
853+
) -> None:
854+
path = tmp_path / "baseline.json"
855+
with pytest.raises(
856+
BaselineValidationError,
857+
match="'created_at' must be UTC ISO-8601 with Z",
858+
) as exc:
859+
baseline_mod._require_utc_iso8601_z(
860+
{"created_at": "2026-02-31T00:00:00Z"},
861+
"created_at",
862+
path=path,
863+
)
864+
assert exc.value.status == "invalid_type"
865+
866+
821867
def test_baseline_load_legacy_codeclone_version_alias(tmp_path: Path) -> None:
822868
baseline_path = tmp_path / "baseline.json"
823869
payload = _trusted_payload(generator_version="1.4.0")

tests/test_cache.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1277,6 +1277,73 @@ def test_decode_wire_file_entry_optional_source_stats() -> None:
12771277
)
12781278

12791279

1280+
def test_cache_helpers_cover_invalid_analysis_profile_and_source_stats_shapes() -> None:
1281+
assert (
1282+
cache_mod._decode_wire_qualname_span_size(["pkg.mod:fn", 1, 2, "bad"]) is None
1283+
)
1284+
assert cache_mod._decode_wire_qualname_span_size([None, 1, 2, 4]) is None
1285+
assert (
1286+
cache_mod._as_analysis_profile(
1287+
{
1288+
"min_loc": 1,
1289+
"min_stmt": 1,
1290+
"block_min_loc": 2,
1291+
"block_min_stmt": "bad",
1292+
"segment_min_loc": 3,
1293+
"segment_min_stmt": 4,
1294+
}
1295+
)
1296+
is None
1297+
)
1298+
assert (
1299+
cache_mod._decode_optional_wire_source_stats(obj={"ss": [1, 2, "bad", 0]})
1300+
is None
1301+
)
1302+
1303+
1304+
def test_canonicalize_cache_entry_skips_invalid_dead_candidate_suppression_shape() -> (
1305+
None
1306+
):
1307+
normalized = cache_mod._canonicalize_cache_entry(
1308+
cast(
1309+
Any,
1310+
{
1311+
"stat": {"mtime_ns": 1, "size": 2},
1312+
"units": [],
1313+
"blocks": [],
1314+
"segments": [],
1315+
"class_metrics": [],
1316+
"module_deps": [],
1317+
"dead_candidates": [
1318+
{
1319+
"qualname": "pkg.mod:unused",
1320+
"local_name": "unused",
1321+
"filepath": "pkg/mod.py",
1322+
"start_line": 1,
1323+
"end_line": 2,
1324+
"kind": "function",
1325+
"suppressed_rules": "dead-code",
1326+
}
1327+
],
1328+
"referenced_names": [],
1329+
"referenced_qualnames": [],
1330+
"import_names": [],
1331+
"class_names": [],
1332+
},
1333+
)
1334+
)
1335+
assert normalized["dead_candidates"] == [
1336+
{
1337+
"qualname": "pkg.mod:unused",
1338+
"local_name": "unused",
1339+
"filepath": "pkg/mod.py",
1340+
"start_line": 1,
1341+
"end_line": 2,
1342+
"kind": "function",
1343+
}
1344+
]
1345+
1346+
12801347
def test_decode_optional_wire_coupled_classes_rejects_non_string_qualname() -> None:
12811348
assert (
12821349
cache_mod._decode_optional_wire_coupled_classes(
@@ -1562,6 +1629,18 @@ def test_cache_type_predicates_reject_non_dict_variants() -> None:
15621629
assert cache_mod._is_class_metrics_dict([]) is False
15631630
assert cache_mod._is_module_dep_dict([]) is False
15641631
assert cache_mod._is_dead_candidate_dict([]) is False
1632+
assert (
1633+
cache_mod._is_dead_candidate_dict(
1634+
{
1635+
"qualname": "pkg.mod:broken",
1636+
"local_name": "broken",
1637+
"filepath": "pkg/mod.py",
1638+
"start_line": 1,
1639+
"end_line": 2,
1640+
}
1641+
)
1642+
is False
1643+
)
15651644
assert (
15661645
cache_mod._is_dead_candidate_dict(
15671646
{

0 commit comments

Comments
 (0)