mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-26 01:12:21 +00:00
fix(e2e): skip unpublished npm versions in the Claude Code PR-gate resolver (#43053)
npm keeps an unpublished version's timestamp in the packument's time map but drops it from versions, so the resolver could hand npm install a version it refuses with ETARGET. Only versions still present in versions are candidates now. Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
This commit is contained in:
parent
de8aeff6c6
commit
7faeb15ff3
3 changed files with 66 additions and 1 deletions
0
tests/e2e/claude_code/_pr_gate_unit_tests/__init__.py
Normal file
0
tests/e2e/claude_code/_pr_gate_unit_tests/__init__.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
"""Unit tests for the Claude Code PR-gate version resolver.
|
||||
|
||||
Markerless harness tests: they feed the resolver a hand-built packument and a
|
||||
fixed clock, so they run without a proxy, never reach the npm registry, and
|
||||
carry no `e2e` marker.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from typing import Final, Mapping
|
||||
|
||||
import pytest
|
||||
|
||||
from claude_code.pr_gate_version_resolver import NoEligibleVersionError, resolve_pr_gate_version
|
||||
|
||||
NOW: Final = datetime(2026, 4, 25, 12, 0, tzinfo=timezone.utc)
|
||||
INSIDE_THE_2_1_88_WINDOW: Final = datetime(2026, 4, 3, 12, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def _packument(times: Mapping[str, str], unpublished: frozenset[str] = frozenset()) -> dict[str, object]:
|
||||
return {
|
||||
"name": "@anthropic-ai/claude-code",
|
||||
"time": {"created": "2024-01-01T00:00:00.000Z", "modified": "2026-04-25T00:00:00.000Z", **times},
|
||||
"versions": {version: {"version": version} for version in times if version not in unpublished},
|
||||
}
|
||||
|
||||
|
||||
def test_skips_a_version_npm_has_unpublished() -> None:
|
||||
metadata: Final = _packument(
|
||||
{
|
||||
"2.1.87": "2026-03-28T20:00:00.000Z",
|
||||
"2.1.88": "2026-03-30T22:36:48.424Z",
|
||||
"2.1.89": "2026-03-31T23:32:40.000Z",
|
||||
},
|
||||
unpublished=frozenset({"2.1.88"}),
|
||||
)
|
||||
assert resolve_pr_gate_version(metadata=metadata, as_of=INSIDE_THE_2_1_88_WINDOW) == "2.1.87"
|
||||
|
||||
|
||||
def test_raises_when_the_only_old_enough_version_is_unpublished() -> None:
|
||||
metadata: Final = _packument(
|
||||
{"2.1.88": "2026-03-30T22:36:48.424Z", "2.1.89": "2026-03-31T23:32:40.000Z"},
|
||||
unpublished=frozenset({"2.1.88"}),
|
||||
)
|
||||
with pytest.raises(NoEligibleVersionError):
|
||||
resolve_pr_gate_version(metadata=metadata, as_of=INSIDE_THE_2_1_88_WINDOW)
|
||||
|
||||
|
||||
def test_picks_the_newest_published_version_at_least_min_age_old() -> None:
|
||||
metadata: Final = _packument(
|
||||
{
|
||||
"2.1.118": "2026-04-15T10:00:00.000Z",
|
||||
"2.1.119": "2026-04-21T10:00:00.000Z",
|
||||
"2.2.0-rc.1": "2026-04-22T10:00:00.000Z",
|
||||
"2.1.120": "2026-04-23T10:00:00.000Z",
|
||||
"2.1.121": "2026-04-25T11:00:00.000Z",
|
||||
}
|
||||
)
|
||||
assert resolve_pr_gate_version(metadata=metadata, as_of=NOW) == "2.1.119"
|
||||
|
|
@ -80,7 +80,9 @@ def resolve_pr_gate_version(
|
|||
|
||||
"Newest" means newest by **publish time**, not semver string order —
|
||||
if a patch lands on an older major after a newer release, the
|
||||
patched line is the eligible one.
|
||||
patched line is the eligible one. A version npm has unpublished keeps
|
||||
its ``time`` entry but drops out of ``versions``, so only versions
|
||||
still present in ``versions`` are candidates.
|
||||
|
||||
Args:
|
||||
metadata: Pre-fetched npm packument (skips the HTTP call). Useful
|
||||
|
|
@ -101,6 +103,7 @@ def resolve_pr_gate_version(
|
|||
metadata = fetch(package_name)
|
||||
|
||||
times = metadata.get("time") or {}
|
||||
versions = metadata.get("versions") or {}
|
||||
if as_of is None:
|
||||
as_of = datetime.now(timezone.utc)
|
||||
cutoff = as_of - min_age
|
||||
|
|
@ -109,6 +112,8 @@ def resolve_pr_gate_version(
|
|||
for version, raw_ts in times.items():
|
||||
if version in _TIME_META_KEYS:
|
||||
continue
|
||||
if version not in versions:
|
||||
continue
|
||||
if not isinstance(raw_ts, str):
|
||||
continue
|
||||
if "-" in version:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue