tests.test_sync_ops moduleΒΆ

Conformance tests for the dependency-updater registry.

Each test enumerates SYNC_OPERATIONS and asserts a shared invariant, so a new operation that breaks a convention fails by name.

tests.test_sync_ops.test_sync_deps_and_family_are_registered()[source]ΒΆ

sync-deps and every updater it drives are CLI commands.

Return type:

None

tests.test_sync_ops.test_operation_identity_is_coherent(op)[source]ΒΆ

Command, branch, and template names are identical (naming rule 3).

Return type:

None

tests.test_sync_ops.test_operation_has_pr_body_template(op)[source]ΒΆ

Each updater ships a PR-body template under its own name.

Return type:

None

tests.test_sync_ops.test_operation_config_flag_exists(op)[source]ΒΆ

Each config_flag names a real boolean field on the Config schema.

Return type:

None

tests.test_sync_ops.test_operation_job_name_is_descriptive(op)[source]ΒΆ

Each CI job name embeds the operation’s bare noun and an emoji.

Return type:

None

tests.test_sync_ops.test_label_is_shared()[source]ΒΆ

The bumper label and the labeller’s rules name the same label.

The bumpers apply it to the PRs they open, and the labeller applies it to anyone else’s PR touching a lockfile or pyproject.toml. Both spellings are hand-written (workflow YAML cannot import Python), so one renamed without the other silently splits the family across two labels, and the single filter this constant exists for stops finding half of it.

Return type:

None

tests.test_sync_ops.test_write_domains_justify_serial_apply()[source]ΒΆ

Three updaters share the workflow files; the uv pair stays out of them.

This is the invariant behind the serial apply phase: the workflow-file rewriters cannot write concurrently, while sync-uv-lock and sync-dep-sources (uv.lock, pyproject.toml) never touch workflow files and resolve in parallel with them, serialized only against each other through the uv-project mutex.

Return type:

None

tests.test_sync_ops.test_is_enabled_reads_the_config_flag(op)[source]ΒΆ

is_enabled mirrors the operation’s Config boolean.

Return type:

None

tests.test_sync_ops.test_selected_operations_drops_disabled()[source]ΒΆ

A disabled flag removes its operation; here_only=False skips probes.

Return type:

None

tests.test_sync_ops.test_selected_operations_by_name_runs_a_subset_in_registry_order()[source]ΒΆ

Naming operations restricts to them, in registry order, not input order.

Return type:

None

tests.test_sync_ops.test_selected_operations_by_name_still_honors_config_flags()[source]ΒΆ

A named-but-disabled operation is dropped (feature flags are authoritative).

Return type:

None

tests.test_sync_ops.test_run_sync_operations_applies_in_input_order()[source]ΒΆ

Resolves run concurrently; applies run in the order passed in.

Return type:

None

tests.test_sync_ops.test_run_sync_operations_dry_run_skips_apply()[source]ΒΆ

dry_run resolves but never applies.

Return type:

None

tests.test_sync_ops.test_run_sync_operations_isolates_a_failing_resolve()[source]ΒΆ

One operation’s resolve failure yields a None plan, not a crash.

Return type:

None

tests.test_sync_ops.test_operation_order_follows_the_registry()[source]ΒΆ

operation_order restores canonical order regardless of input order.

Return type:

None

tests.test_sync_ops.test_sync_deps_reports_nothing_to_do_in_an_empty_tree(tmp_path, monkeypatch)[source]ΒΆ

With no lockfile, workflows, or source, no updater applies.

Return type:

None

tests.test_sync_ops.test_consolidated_job_covers_every_operation()[source]ΒΆ

Every registered operation has a step group in the job hosting it.

The drift guard: a new SyncOperation added to SYNC_OPERATIONS without wiring its sync command, PR body, and PR into a CI job fails here by name. Each operation is checked against its own workflow and job, so moving one out of the consolidated job keeps the guard rather than dropping it.

Return type:

None

tests.test_sync_ops.test_consolidated_job_passes_ci_flags(op)[source]ΒΆ

Each operation’s ci_flags reach its sync step in the autofix job.

The drift guard: an updater that declares a CI capability (like --release-notes) whose consolidated-job step never passes it silently drops the feature from the PR body. Fails by name when the registry and the YAML disagree.

Return type:

None

tests.test_sync_ops.test_consolidated_job_uses_full_history()[source]ΒΆ

The job checks out full history so repeated create-pull-request calls in one checkout do not trip git’s β€œshallow file has changed” race.

Return type:

None

tests.test_sync_ops.test_consolidated_job_labels_every_pr_consistently()[source]ΒΆ

Every PR the consolidated job opens carries the shared dependency label.

Return type:

None

tests.test_sync_ops.test_consolidated_job_resets_tree_before_each_operation()[source]ΒΆ

Each shared-job operation resets the tree first, so PR diffs never bleed.

Only the consolidated operations need this: one with a job to itself starts from a clean checkout, so a reset there would be dead weight.

Return type:

None

tests.test_sync_ops.test_pinned_with_packages_covers_every_registry_pin()[source]ΒΆ

Every package==version in the registry reaches the bumper.

The drift guard: a plugin set added to a tool’s with_packages but never scanned ages out silently, which is how mdformat’s own ruff== pin sat four minor versions behind the top-level ruff.

Return type:

None

tests.test_sync_ops.test_pinned_with_packages_keeps_the_lowest_of_two_pins(monkeypatch)[source]ΒΆ

Two tools pinning one package resolve against the older of the two.

Return type:

None

tests.test_sync_ops.test_no_standalone_dependency_jobs_remain()[source]ΒΆ

The four bumpers live only in the consolidated job, not as separate jobs.

Return type:

None

tests.test_sync_ops.test_sync_tool_versions_routes_npm_tools_to_the_npm_registry(monkeypatch)[source]ΒΆ

npm-backed tools resolve versions through the npm registry datasource.

awesome-lint is the only npm tool, so recording the argument npm_candidates receives proves the elif spec.npm branch routes it away from PyPI/GitHub. Empty candidate lists mean nothing is bumped, so tool_registry.py stays put.

Return type:

None

tests.test_sync_ops.test_widest_changes(changes, expected)[source]ΒΆ

A name repeated across files collapses onto its lowest starting version.

Return type:

None

tests.test_sync_ops.test_bypass_only_plan_counts_as_changed_and_renders()[source]ΒΆ

A run that only edits cooldown bypasses still produces a PR report.

Pruning or freezing an exclude-newer-package entry rewrites pyproject.toml even when no package version moves; without this the sync PR would carry that hunk with an empty body.

Return type:

None

tests.test_sync_ops.test_render_plan_markdown_closes_on_the_held_back_section()[source]ΒΆ

What the run did comes first; what it left alone closes the body.

The held-back section is the only forward-looking one, so it sits below the bypass table. A bypass-only run moves no version, and the old order opened its PR on releases it had not adopted.

Return type:

None

tests.test_sync_ops.test_render_plan_markdown_places_the_upgrade_invite_after_the_notes()[source]ΒΆ

The invitation follows the notes it asks the maintainer to work through.

Return type:

None

tests.test_sync_ops.SWAP_PYPROJECT = '[project]\nname = "basket"\nversion = "1.0.0"\ndependencies = [\n  "cherry>=1.2",\n  "mango>=2.1.0.dev0",\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" }\n'ΒΆ

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

tests.test_sync_ops.SWAP_PRE_LOCK = 'version = 1\nrequires-python = ">=3.10"\n\n[options]\nexclude-newer = "0001-01-01T00:00:00Z"\nexclude-newer-span = "P1W"\n\n[[package]]\nname = "cherry"\nversion = "1.2.0"\nsource = { registry = "https://pypi.org/simple" }\nsdist = { url = "https://example.test/cherry.tar.gz", upload-time = "2026-06-01T10:00:00Z" }\n\n[[package]]\nname = "mango"\nversion = "2.1.0.dev0"\nsource = { git = "https://github.com/acme/mango?branch=main#abcdef12" }\n'ΒΆ

The lock before the swap: mango resolved from git, no upload time.

tests.test_sync_ops.SWAP_POST_LOCK = 'version = 1\nrequires-python = ">=3.10"\n\n[options]\nexclude-newer = "0001-01-01T00:00:00Z"\nexclude-newer-span = "P1W"\n\n[[package]]\nname = "cherry"\nversion = "1.2.0"\nsource = { registry = "https://pypi.org/simple" }\nsdist = { url = "https://example.test/cherry.tar.gz", upload-time = "2026-06-01T10:00:00Z" }\n\n[[package]]\nname = "mango"\nversion = "2.1.0"\nsource = { registry = "https://pypi.org/simple" }\nsdist = { url = "https://example.test/mango.tar.gz", upload-time = "2026-07-10T09:00:00Z" }\n'ΒΆ

The lock a successful uv lock produces after the swap.

tests.test_sync_ops.test_resolve_dep_sources_swaps_and_freezes(tmp_path, monkeypatch)[source]ΒΆ

The happy path: override dropped, floor tightened, release frozen.

Return type:

None

tests.test_sync_ops.test_resolve_dep_sources_dry_run_restores(tmp_path, monkeypatch)[source]ΒΆ

A dry run reports the full swap but leaves the project untouched.

Return type:

None

tests.test_sync_ops.test_resolve_dep_sources_is_all_or_nothing(tmp_path, monkeypatch, lock_outcome)[source]ΒΆ

A failed or wrong-version lock restores the project untouched.

Return type:

None

tests.test_sync_ops.test_resolve_dep_sources_noop_before_release(tmp_path, monkeypatch)[source]ΒΆ

With no qualifying release, the resolve never touches the project.

Return type:

None

tests.test_sync_ops.SELF_PIN_WORKFLOW = 'env:\n  UV_EXCLUDE_NEWER: "1 week"\n          uvx --no-progress \'repomatic==7.11.0\' metadata\n'ΒΆ

A downstream workflow whose self-pin is current but carries no exemption.

tests.test_sync_ops.test_plan_file_rewrites_backfills_exemption_without_a_version_bump(tmp_path)[source]ΒΆ

A splice-only rewrite is still a change, and still gets written.

The exemption backfill reports no (name, old, new) triple, since it names a package rather than moving a version. Gating the write on that list threw the rewrite away, so a downstream repo already pinned at the newest release never got the flag and its whole test workflow stayed unresolvable.

tests.test_sync_ops.test_plan_file_rewrites_reports_nothing_when_already_exempt(tmp_path)[source]ΒΆ

An exemption already in place leaves the plan empty, not endlessly dirty.

tests.test_sync_ops.test_plan_file_rewrites_keeps_version_bumps_out_of_the_exemption_list(tmp_path)[source]ΒΆ

A file that also moved a version reports through changes, not twice.

tests.test_sync_ops.test_resolve_action_pins_and_init_owned_files(monkeypatch, tmp_path, in_source_repo)[source]ΒΆ

sync-action-pins skips init-owned files downstream, bumps them upstream.

The publish-pypi composite action is copied byte-for-byte from a bundled template, so downstream its setup-uv pin is owned by repomatic init and a bump would ping-pong with the next sync-repomatic. In the source repo the bundled template is a symlink to this very file, so the pin is a normal source-of-truth ref and stays bumpable. A downstream-authored workflow pinning the same action always bumps.

Return type:

None

tests.test_sync_ops.test_bundled_pin_bearing_files_are_excluded_from_pin_scan()[source]ΒΆ

Every init-owned file carrying a uses: SHA pin is excluded from bumping.

Generic guard for the ping-pong fix: any BundledComponent .github/ target that ships a pinned uses: ref (today only the publish-pypi action) must be listed in BUNDLED_VERBATIM_TARGETS, so _pinnable_files drops it and neither sync-action-pins nor sync-workflow-pins fights the verbatim init deploy.

Return type:

None

tests.test_sync_ops.test_apply_file_writes_rebases_on_current_text(tmp_path)[source]ΒΆ

A sibling operation’s apply landing between plan and write survives.

Both .github/ pin updaters plan whole-file rewrites from the same pre-apply snapshot; writing the planned text verbatim would revert whichever sibling applied first.

tests.test_sync_ops.test_apply_file_writes_verbatim_without_rebase(tmp_path)[source]ΒΆ

A plan carrying no rebase closure still writes its planned text.

tests.test_sync_ops.UV_CANDIDATES = [('0.12.2', '2026-08-05', '0.12.2'), ('0.12.3', '2026-08-07', '0.12.3'), ('0.12.4', '2026-08-13', '0.12.4')]ΒΆ

Three uv releases, all clear of the cooldown on GATE_TODAY.

tests.test_sync_ops.SETUP_UV_PIN = {PosixPath('lint.yaml'): 'jobs:\n  lint:\n    steps:\n      - uses: astral-sh/setup-uv@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa # v10.0.0\n        with:\n          version: "0.12.3"\n'}ΒΆ

A workflow pinning both halves: the action commit and the uv it installs.

tests.test_sync_ops.test_uv_gate_drops_unverifiable_releases()[source]ΒΆ

A uv the pinned action cannot checksum is not a candidate.

Without this, sync-workflow-pins walks uv past the table on its own schedule and CI installs it without a pinned hash.

tests.test_sync_ops.test_uv_gate_reads_the_action_pin_out_of_the_scanned_files()[source]ΒΆ

The commit the table is fetched at comes from the files being synced.

The gate’s two halves are wired through the scanned text: miss the pin and it silently degrades to ungated, which is the failure it exists to prevent.

tests.test_sync_ops.test_uv_gate_holds_the_pin_when_the_table_is_unknown(caplog)[source]ΒΆ

An unreadable table keeps out every release newer than the pin.

A blind bump can overshoot the table, and the next run that reads it steps the pin back again.

tests.test_sync_ops.test_uv_gate_passes_everything_through_without_a_setup_uv_pin()[source]ΒΆ

With no action pinned there is no table to wait for, so nothing holds.

tests.test_sync_ops.test_uv_gate_reports_a_pin_above_the_table()[source]ΒΆ

A pin past the table is signalled back, so the caller can step it down.

Reported rather than silently gated, because the sync only walks a version backwards on this one condition.

tests.test_sync_ops.test_uv_gate_warns_when_the_pinned_uv_is_unverified(caplog)[source]ΒΆ

The pin on disk is audited too, not just the one about to replace it.

tests.test_sync_ops.test_uv_gate_stays_quiet_when_the_pinned_uv_is_verified(caplog)[source]ΒΆ

A covered pin is the normal state and earns no warning.

tests.test_sync_ops.test_uv_gate_reports_a_release_it_withheld(caplog)[source]ΒΆ

A newer uv the action cannot verify is announced, at info level.

uv ships weekly against setup-uv’s monthly cadence, so this is the ordinary state between action bumps: a warning every run would train the reader to ignore the ones that mean something.

tests.test_sync_ops.test_workflow_pins_step_a_uv_pin_back_onto_the_checksum_table(caplog)[source]ΒΆ

The repair is the pin move itself, not the warning beside it.

A repository whose uv pin overtook the table has no forward move left when the newest setup-uv is the one it already pins, so the sync walks uv down to the newest release that action can verify.

tests.test_sync_ops.test_workflow_pins_label_a_uv_step_back_in_the_table()[source]ΒΆ

A pin moving down says so in its own row, and links to why.

The arrow alone renders a step back like any bump, so without the label the repair reads as an update to an older release.

tests.test_sync_ops.test_workflow_pins_label_nothing_on_a_forward_uv_bump()[source]ΒΆ

An ordinary uv bump inside the table carries no label.

tests.test_sync_ops.test_workflow_pins_hold_the_uv_pin_on_an_unreadable_table()[source]ΒΆ

An unreadable table moves the uv pin neither forward nor back.

0.12.4 cleared the cooldown, so only the hold keeps it out.

tests.test_sync_ops.test_workflow_pins_invite_the_upgrade_review_on_a_repomatic_bump()[source]ΒΆ

A lockstep bump of the upstream pin carries the review launcher.

The uses: refs already moved by hand, so realigning the inline pin is the pull request where a maintainer learns the repository now runs a newer release, and the one where both versions are known.

Return type:

None

tests.test_sync_ops.test_workflow_pins_invite_nothing_without_a_repomatic_bump()[source]ΒΆ

An inline pin already aligned with the refs leaves the section out.

Return type:

None