tests.test_dep_sources module

Tests for the git-to-release dependency source swaps and the release gate.

tests.test_dep_sources.PYPROJECT = '[project]\nname = "basket"\nversion = "1.0.0"\ndependencies = [\n  "cherry>=1.2",\n  "mango[fresh]>=2.1.0.dev0; python_version >= \'3.10\'",\n]\n\n[project.optional-dependencies]\njuice = [\n  "mango[pressed]",\n]\n\n[dependency-groups]\ndocs = [\n  "papaya>=3.0",\n]\ntest = [\n  "mango>=2.1.0.dev0",\n  { include-group = "docs" },\n]\n\n[tool.uv]\nexclude-newer = "1 week"\nexclude-newer-package = { mango = "2026-01-05T00:00:00Z" }\n\n[tool.uv.sources]\nmango = { git = "https://github.com/acme/mango", branch = "main" }\npapaya = { git = "https://github.com/acme/papaya", rev = "1234abcd" }\n'

A project tracking mango’s main branch while awaiting its 2.1.0 release.

papaya is pinned to an exact commit and cherry is a plain registry dependency: both sit outside the managed idiom.

tests.test_dep_sources.test_tracked_git_overrides_selects_branch_tracks(tmp_path)[source]

Only single-source git entries with a branch are considered tracked.

tests.test_dep_sources.test_dev_floor_takes_highest_dev_bound(tmp_path)[source]

The floor is the highest .dev lower bound across every table.

tests.test_dep_sources.test_strip_dev_bounds(requirement, release, expected)[source]

Only matching .dev lower bounds are tightened, byte-for-byte.

tests.test_dep_sources.test_find_ready_swaps_adopts_newest_stable(tmp_path, monkeypatch)[source]

A stable release satisfying the floor makes the swap ready.

tests.test_dep_sources.test_find_ready_swaps_needs_positive_confirmation(tmp_path, monkeypatch, releases)[source]

Anything short of a live stable release keeps the git track in place.

tests.test_dep_sources.test_find_ready_swaps_skips_floorless_overrides(tmp_path, monkeypatch)[source]

A branch track without a dev floor is outside the managed idiom.

tests.test_dep_sources.test_apply_release_swaps_rewrites_pyproject(tmp_path)[source]

The override is dropped and every dev floor tightened, nothing else.

tests.test_dep_sources.test_apply_release_swaps_drops_emptied_sources_table(tmp_path)[source]

Removing the last override removes the [tool.uv.sources] table too.

tests.test_dep_sources.test_format_swap_section()[source]

One row per swap, with branch, adopted release, and ship date.

tests.test_dep_sources.test_floors_inside_cooldown(tmp_path, requirement, floor, locked, age_days, flagged)[source]

Only a floor demanding a release inside the window is reported.

Return type:

None

tests.test_dep_sources.test_dependency_floors_clear_the_cooldown()[source]

No dependency floor requires a release still inside the cooldown.

Such a floor makes the published package uninstallable for anyone resolving it from an index: a downstream repo running a frozen workflow’s uvx 'repomatic==X.Y.Z', or an end user running uvx repomatic. Neither can see uv.lock or [tool.uv] exclude-newer-package, and uv has no environment variable for a per-package exemption, so they have nowhere to record a bypass.

This repository’s own CI cannot catch it, because it installs from uv.lock and resolves straight through the local exemption. That is what makes this a release gate rather than a CI symptom. Wait for a release to clear the window before raising a floor onto it.

Return type:

None

tests.test_dep_sources.SHIPPABLE_PYPROJECT = '[build-system]\nrequires = [ "uv-build>=0.8" ]\n\n[project]\nname = "basket"\nversion = "1.0.0"\ndependencies = [ "cherry>=1.2" ]\n\n[project.optional-dependencies]\njuice = [ "papaya>=3" ]\n\n[dependency-groups]\ntest = [ "mango>=2" ]\n'

A project whose every dependency resolves from PyPI.

Each rule below appends its own offending declaration to this base, so a finding can only come from what the test added.

tests.test_dep_sources.test_clean_project_reports_nothing(tmp_path)[source]

A project resolving everything from PyPI has no findings.

Return type:

None

tests.test_dep_sources.test_unshippable_sources_block(tmp_path, extra, package, kind)[source]

Every way of resolving a dependency off-index blocks a release.

Return type:

None

tests.test_dep_sources.test_direct_references_block_in_every_table(tmp_path, table)[source]

A PEP 508 direct reference blocks wherever it is declared.

The v5.0.0 release shipped one in an extra, which PyPI refused at upload after the tag and the GitHub release had already been created.

Return type:

None

tests.test_dep_sources.test_per_platform_source_list_reports_once_per_kind(tmp_path)[source]

A marker-split override is one finding, not one per platform.

Return type:

None

tests.test_dep_sources.test_pypi_element_does_not_mask_a_private_one(tmp_path)[source]

Deduplication must not let a clean list element hide a dirty one.

Both elements classify as an index source, and the PyPI one is skipped without reporting. Recording it as seen anyway would swallow the private index that follows it.

Return type:

None

tests.test_dep_sources.test_pypi_index_source_is_shippable(tmp_path)[source]

An explicit index entry pointing at PyPI is not a finding.

Return type:

None

tests.test_dep_sources.test_allowlist_downgrades_without_hiding(tmp_path)[source]

An allowed package still reports, carrying its reason, but stops blocking.

Return type:

None

tests.test_dep_sources.test_resolution_overrides_warn_without_blocking(tmp_path)[source]

override-dependencies diverges the tested tree without breaking installs.

Return type:

None

tests.test_dep_sources.test_lock_catches_what_pyproject_never_names(tmp_path)[source]

A transitively-pulled git source shows up even with a clean pyproject.

This is why the gate reads the lock as well as the declarations: a source override can name a package no requirement array mentions, and only the resolved tree records it. The project’s own editable = "." entry is not a dependency and is skipped.

Return type:

None

tests.test_dep_sources.test_a_package_is_reported_once(tmp_path)[source]

A source flagged by both halves keeps the actionable pyproject finding.

Return type:

None

tests.test_dep_sources.test_consequence_distinguishes_broken_from_divergent(tmp_path, floor, expected)[source]

The report separates a failing install from a silently different one.

Return type:

None

tests.test_dep_sources.test_release_readiness_flips_the_pr_opening(tmp_path)[source]

The release PR announces readiness only while the project has it.

Return type:

None

Each countdown link points at a job that still opens pull requests.

The link is a search over the job’s pull request branch, and that branch is the template name. Renaming or dropping a template would leave the banner pointing at a query nothing ever matches, which reads as “nothing is coming” rather than as a broken link.

Return type:

None

A dated blocker points at sync-uv-lock, an awaited one at the swap.

Both jobs report the same clearing date from the same two numbers the banner reads: the locked release’s upload time, and the exclude-newer span. The link is what takes the reader from the verdict to that report.

Return type:

None

tests.test_dep_sources.test_release_pr_reserves_caution_for_the_unguarded_step(tmp_path)[source]

The release PR’s two alerts rank by what else would catch the mistake.

Squash and merge is a warning because the release lane catches it anyway: detect-squash-merge in _release-build.yaml fires on the collapsed history and files the recovery. Merging a blocked release has no such backstop on the merge itself, and by the time lint-deps fails in the release lane the freeze commit is on main and the version has to be burned. So the caution is spent on the step nothing else guards, and the template must not spend it anywhere else.

Return type:

None

tests.test_dep_sources.test_project_ships_only_released_dependencies()[source]

This repository can be released as it stands.

The generalization of test_dependency_floors_clear_the_cooldown above: that one covers a floor the cooldown makes unreachable, this one covers every other way a dependency fails to reach the people installing the published artifact. Both are release gates rather than CI symptoms, since this repository’s own workflows install from uv.lock and resolve straight past the problem.

The same check runs in the release lane (_release-build.yaml’s lint-deps job) and in the release PR body, so this is the local copy of a gate that also holds downstream. Its value here is latency: a pytest run says so in milliseconds, where CI takes minutes and the release PR banner needs a push.

Return type:

None