tests.test_workflow_sync module

Tests for workflow sync module.

tests.test_workflow_sync.test_reusable_workflows_sorted()[source]

Verify reusable workflows are sorted.

Return type:

None

tests.test_workflow_sync.test_all_workflow_files_sorted()[source]

Verify all workflow files are sorted.

Return type:

None

tests.test_workflow_sync.test_non_reusable_subset_of_all()[source]

Verify non-reusable workflows are a subset of all workflows.

Return type:

None

tests.test_workflow_sync.test_reusable_subset_of_all()[source]

Verify reusable workflows are a subset of all workflows.

Return type:

None

tests.test_workflow_sync.test_no_overlap()[source]

Verify no overlap between reusable and non-reusable sets.

Return type:

None

tests.test_workflow_sync.test_union_is_all()[source]

Verify union of reusable and non-reusable equals all.

Return type:

None

tests.test_workflow_sync.test_reusable_has_workflow_call(filename)[source]

Verify all reusable workflows have workflow_call trigger.

Return type:

None

tests.test_workflow_sync.test_non_reusable_no_workflow_call(filename)[source]

Verify non-reusable workflows lack workflow_call trigger.

Return type:

None

tests.test_workflow_sync.test_returns_trigger_info(filename)[source]

Verify return type is WorkflowTriggerInfo.

Return type:

None

tests.test_workflow_sync.test_autofix_has_secrets()[source]

Verify autofix.yaml defines secrets.

Return type:

None

tests.test_workflow_sync.test_changelog_has_secrets()[source]

Verify changelog.yaml defines secrets.

Return type:

None

tests.test_workflow_sync.test_release_has_secrets()[source]

Verify release.yaml defines secrets.

PYPI_TOKEN was removed once the publish step migrated to OIDC-based Trusted Publishing via the publish-pypi composite action invoked from the caller workflow. See pypi/warehouse#11096 for context.

Return type:

None

tests.test_workflow_sync.test_unsubscribe_has_secrets()[source]

Verify unsubscribe.yaml defines secrets.

Return type:

None

tests.test_workflow_sync.test_unsubscribe_has_call_inputs()[source]

Verify unsubscribe.yaml exposes its dispatch inputs to callers.

Return type:

None

tests.test_workflow_sync.test_thin_caller_workflow_call_inputs_stay_minimal()[source]

No reusable workflow may grow its forwarded workflow_call inputs unreviewed.

Every input a thin caller forwards widens the blast radius of the cooldown step-back in repomatic.init_project.resolve_default_pin(): that pin can reference a reusable workflow one release older than the caller was generated from, and GitHub rejects a forwarded input the older workflow does not yet declare. Locking the surface keeps a new input a deliberate act, weighed against the step-back, not an accident. To relax it, add the input to EXPECTED_THIN_CALLER_CALL_INPUTS with that trade-off in mind.

Return type:

None

tests.test_workflow_sync.test_unsubscribe_caller_forwards_inputs()[source]

Verify the generated caller forwards each input with the right expression.

Boolean inputs are coerced with == true so non-dispatch events (schedule), where the caller’s inputs context is null, pass an explicit false instead of relying on GitHub’s undocumented handling of a null value for a boolean-typed input.

Return type:

None

tests.test_workflow_sync.test_caller_with_block_matches_call_inputs(filename)[source]

Caller emits a with: forwarding block only for declared call inputs.

Return type:

None

tests.test_workflow_sync.test_no_python_literals_in_yaml(filename)[source]

Verify generated YAML contains no Python dict/list literals.

Regression test for a bug where _render_trigger_value fell through to str() on nested dicts, producing {'key': 'value'} instead of block-style YAML.

Kept alongside the two linter passes below, which do not subsume it. actionlint rejects a leaked literal only where the workflow schema constrains the value it landed in (a flow mapping under schedule:, which takes a list); the same leak inside a free-form value, a paths: entry or an input default, is well-formed YAML that both linters accept. This scans the rendered text wherever it appears, and names the defect directly.

Return type:

None

tests.test_workflow_sync.test_generated_caller_ends_with_one_newline(filename)[source]

Every generated caller ends with exactly one trailing newline.

Nothing downstream of the generator collapses trailing whitespace: repomatic.init_project._write_managed writes callers with normalize=False, because their exact bytes carry downstream-owned jobs verbatim. A stray blank line therefore reaches every consuming repository.

release.yaml shipped one for exactly that reason, alone among the callers: its release lane already ends with the blank separator that _generate_release_caller then appended a second time, so the file ended }}\n\n.

Return type:

None

tests.test_workflow_sync.PINNED_CALLER_SHA = '0000000000000000000000000000000000000000'

Placeholder commit SHA for the pinned uses: rendering.

actionlint checks the shape of a uses: reference without resolving it, so a synthetic SHA exercises that branch with no network lookup and no tie to a commit that has to keep existing.

tests.test_workflow_sync.test_generated_callers_pass_yamllint(tmp_path)[source]

Generated thin callers must clear the yamllint rules CI enforces.

A thin caller is the one artifact this repository generates and never materializes into its own tree: .github/workflows/ here holds the canonical workflows, so the lint job’s yamllint step never sees a generated file. The first checkout that does is a downstream repo, a release later, which makes a regression here something a user reports rather than something CI catches. Running the pinned yamllint over the output moves that verdict back to the commit that introduced it.

In practice that is the 120-column cap and little else: a native config replaces yamllint’s defaults rather than layering over them, and the bundled yamllint.yaml sets line-length alone. It is still the rule worth guarding, being the one a renderer breaches by widening a single interpolated value, and the one tests/test_prepare_release.py re-implements in Python to cover the freeze path. Any rule the bundled config grows later is covered here for free.

Marked once: it resolves yamllint through uvx, so one runner suffices.

Return type:

None

tests.test_workflow_sync.test_generated_callers_pass_actionlint(tmp_path)[source]

Generated thin callers must be workflows GitHub will actually accept.

Companion to the yamllint pass, and the stronger half of the pair: it reads the workflow schema, so it sees what neither a YAML parser nor a column cap can. A uses: reference stripped of its @ref, a ${{ }} expression naming a context property that does not exist, a trigger value whose type is wrong for the key it sits under: each parses cleanly, and each fails at startup on the runner. For a generated caller that means failing in someone else’s repository.

Marked once: it downloads the pinned actionlint binary, so one runner suffices.

Return type:

None

tests.test_workflow_sync.test_call_inputs_are_dispatch_passthroughs(filename)[source]

Verify workflow_call inputs only mirror workflow_dispatch ones.

All persistent configuration lives in [tool.repomatic] in pyproject.toml: workflows read config via the repomatic CLI instead of accepting inputs. The only workflow_call inputs allowed are passthroughs of the workflow’s own workflow_dispatch inputs, so generated thin callers can forward a manual dispatch to the reusable workflow (see _generate_thin_caller). Type and default must match the dispatch declaration, so both entry points behave identically.

Return type:

None

tests.test_workflow_sync.test_changelog_has_workflow_run()[source]

Verify changelog.yaml has workflow_run trigger.

Return type:

None

tests.test_workflow_sync.test_autolock_has_schedule()[source]

Verify autolock.yaml has schedule trigger.

Return type:

None

tests.test_workflow_sync.test_nonexistent_file()[source]

Raise FileNotFoundError for missing workflow.

Return type:

None

tests.test_workflow_sync.test_generates_valid_yaml(filename)[source]

Verify generated content is valid YAML.

Return type:

None

tests.test_workflow_sync.test_mirrors_canonical_dispatch(filename)[source]

Caller’s workflow_dispatch presence mirrors canonical, no synthesis.

Return type:

None

tests.test_workflow_sync.test_release_thin_caller_synthesizes_triggers()[source]

The release.yaml caller synthesizes the standard push + dispatch triggers.

_generate_release_caller synthesizes the standard release triggers rather than mirroring the canonical release.yaml’s own (so a dogfooding-only trigger added upstream never leaks into downstream callers). The weekly full-fleet schedule is the one canonical trigger carried over, verbatim.

Return type:

None

tests.test_workflow_sync.test_release_caller_emits_concurrency()[source]

The release.yaml caller carries the entry workflow’s concurrency group.

Unlike a simple thin caller, the multi-lane release caller must declare concurrency on its push-triggered entry: the concurrency-bearing engine lane runs behind needs: build, so a block there joins its group too late to cancel superseded runs. The group protects release commits via github.sha.

Return type:

None

tests.test_workflow_sync.test_correct_uses_ref(filename)[source]

Verify correct uses reference in job.

Return type:

None

tests.test_workflow_sync.test_starts_with_document_marker(filename)[source]

Verify generated YAML starts with — document marker.

Return type:

None

tests.test_workflow_sync.test_quoted_on_key(filename)[source]

Verify the on key is quoted as "on":.

Return type:

None

tests.test_workflow_sync.test_autofix_passes_secrets_explicitly()[source]

Verify autofix.yaml thin caller passes secrets explicitly.

Return type:

None

tests.test_workflow_sync.test_changelog_passes_secrets_explicitly()[source]

Verify changelog.yaml thin caller passes secrets explicitly.

Return type:

None

tests.test_workflow_sync.test_release_passes_secrets_explicitly()[source]

Verify release.yaml thin caller passes secrets explicitly.

PYPI_TOKEN is no longer in the secrets surface: PyPI uploads run via OIDC Trusted Publishing inside the caller-side publish-pypi job.

Return type:

None

tests.test_workflow_sync.test_release_thin_caller_emits_publish_pypi_job()[source]

Verify the release.yaml caller emits the publish-pypi job gated on the build lane.

Return type:

None

tests.test_workflow_sync.test_release_thin_caller_emits_build_and_engine_lanes()[source]

Verify the release.yaml caller calls both reusable lanes with pinned refs.

The build lane (_release-build.yaml) feeds publish-pypi; the engine lane (_release-engine.yaml) runs binaries and finalization. Both are pinned to the requested version and the engine lane is gated on the build lane so it can download the run-scoped wheel.

Return type:

None

tests.test_workflow_sync.test_release_thin_caller_publish_pypi_uses_sha_pin()[source]

Verify SHA-pinned commit form propagates to the publish-pypi action ref.

Return type:

None

tests.test_workflow_sync.test_non_release_thin_caller_omits_publish_pypi_job()[source]

Verify non-release thin callers do not gain a publish-pypi job.

Return type:

None

tests.test_workflow_sync.test_release_thin_caller_publish_pypi_omits_checkout()[source]

The downstream publish-pypi job should not require an explicit checkout.

The cross-repo composite action uses: form is fetched by GitHub automatically: dropping actions/checkout keeps the generator free of third-party action SHA pins (which sync-action-pins would not see, since the generator’s source is .py, not .yaml).

Return type:

None

tests.test_workflow_sync.test_release_thin_caller_publish_pypi_sourced_from_release_yaml()[source]

The publish-pypi job body must be sourced from the bundled release.yaml.

Deriving it from a .yaml file (rather than building it in Python) keeps any future third-party SHA pin in the job body visible to sync-action-pins. The canonical entry carries the job with a local ./ action ref that the generator reshapes for downstream callers.

Return type:

None

tests.test_workflow_sync.test_release_thin_caller_publish_pypi_keeps_canonical_runner()[source]

The generated downstream job inherits the canonical runner verbatim.

The body is carried through from release.yaml unchanged except for the checkout drop and the action-ref pin, so downstream callers run on whichever image repomatic itself uses. The expectation is read from that source rather than written here: pinning the literal made this fail as a fleet migration rather than as the drift it exists to catch.

Return type:

None

tests.test_workflow_sync.test_release_thin_caller_rewrites_local_action_ref(monkeypatch)[source]

Render rewrites the canonical local action ref and drops the checkout.

The bundled release.yaml dogfoods the in-tree action via a local ./ ref (checking itself out first); the downstream caller must instead pin the cross-repo composite action (so its OIDC job_workflow_ref resolves to its own release.yaml) and needs no checkout.

Return type:

None

tests.test_workflow_sync.test_release_thin_caller_drops_self_repository_ignores()[source]

No downstream caller inherits the canonical entry’s zizmor suppressions.

The canonical release.yaml keeps workspace-relative uses: ./… refs and silences zizmor’s self-repository audit inline on each one. Downstream callers get pinned cross-repo refs, which the audit never fires on, so the suppression is scaffolding: carried through it would trail a second # comment past the version pin and push the line over yamllint’s limit.

Return type:

None

tests.test_workflow_sync.test_release_thin_caller_unrecognized_job_raises(monkeypatch)[source]

Render fails loudly when release.yaml’s publish-pypi job lacks the action.

A canonical entry reshaped in a way the renderer was not updated to handle (no local action ref in the job) must surface as an error, not a silent no-op that emits a malformed workflow.

Return type:

None

tests.test_workflow_sync.test_lint_passes_secrets_explicitly()[source]

Verify lint.yaml thin caller passes secrets explicitly.

Return type:

None

tests.test_workflow_sync.test_custom_version()[source]

Verify custom version in uses reference.

Return type:

None

tests.test_workflow_sync.test_custom_repo()[source]

Verify custom repo in uses reference.

Return type:

None

tests.test_workflow_sync.test_non_reusable_raises()[source]

Raise ValueError for non-reusable workflow.

Return type:

None

tests.test_workflow_sync.test_has_jobs_section()[source]

Verify generated YAML has jobs section.

Return type:

None

tests.test_workflow_sync.test_has_name()[source]

Verify generated YAML has name field.

Return type:

None

tests.test_workflow_sync.test_identifies_thin_caller(tmp_path)[source]

Identify a thin caller workflow.

Return type:

None

tests.test_workflow_sync.test_returns_none_for_non_caller(tmp_path)[source]

Return None for non-caller workflow.

Return type:

None

tests.test_workflow_sync.test_returns_none_for_invalid_yaml(tmp_path)[source]

Return None for invalid YAML file.

Return type:

None

tests.test_workflow_sync.test_returns_none_for_missing_file(tmp_path)[source]

Return None for missing file.

Return type:

None

tests.test_workflow_sync.test_identify_custom_repo(tmp_path)[source]

Match with custom repo.

Return type:

None

tests.test_workflow_sync.test_extract_extra_jobs_single()[source]

Preserve a single extra downstream job.

Return type:

None

tests.test_workflow_sync.test_extract_extra_jobs_odd_indent_body_stays_managed()[source]

A hand-edited 3-space body line does not truncate the managed job.

The body walk used to require exactly 4+ spaces, so a 3-space line ended it early and the managed job’s tail leaked into the extras, which the next sync duplicated below the regenerated lanes.

Return type:

None

tests.test_workflow_sync.test_extras_define_jobs_ignores_comment_only_tail()[source]

A trailing comment after the managed lanes is not a downstream job.

bool(extra) used to gate the explicit-permissions contract, so a repository keeping a plain trailing note below its thin caller was silently flipped onto the permissions: {} shape it never asked for.

Return type:

None

tests.test_workflow_sync.test_extract_extra_jobs_treats_publish_pypi_as_managed()[source]

Verify the caller-side publish-pypi job is recognized as managed.

When a thin-caller for release.yaml is regenerated, the publish-pypi job (which uses kdeldycke/repomatic/.github/actions/publish-pypi@...) must be treated as part of the managed surface and not preserved as an extra job. Otherwise re-syncing would duplicate it.

Return type:

None

tests.test_workflow_sync.test_extract_extra_jobs_multiple()[source]

Preserve multiple extra downstream jobs.

Return type:

None

tests.test_workflow_sync.test_extract_extra_jobs_none()[source]

Return empty string when no extra jobs exist.

Return type:

None

tests.test_workflow_sync.test_extract_extra_jobs_not_thin_caller()[source]

Return empty string for a non-thin-caller workflow.

Return type:

None

tests.test_workflow_sync.test_extract_extra_jobs_invalid_yaml()[source]

Return empty string for invalid YAML.

Return type:

None

tests.test_workflow_sync.sync_caller(filename, target, version='v1.2.3', commit_sha=None, paths_spec=None)[source]

Render filename onto target, the way repomatic init syncs one file.

Wraps the single render seam so a test never re-implements the read-render- write dance that _init_workflows performs, which is how the two used to drift apart.

Return type:

None

tests.test_workflow_sync.test_thin_caller_sync_preserves_extra_jobs(tmp_path)[source]

End-to-end: sync overwrites the managed job but preserves extras.

Return type:

None

tests.test_workflow_sync.test_check_has_workflow_dispatch(tmp_path, body, is_issue, needle, level)[source]

The workflow_dispatch check reports presence, absence and unreadability.

Return type:

None

tests.test_workflow_sync.test_check_version_pinned(tmp_path, ref, is_issue, needle)[source]

A caller must pin the upstream workflow to a tag, never to a branch.

Return type:

None

tests.test_workflow_sync.test_matching_triggers(tmp_path)[source]

Pass when triggers match canonical.

Return type:

None

tests.test_workflow_sync.test_missing_trigger(tmp_path)[source]

Fail when a trigger is missing.

Return type:

None

tests.test_workflow_sync.test_extra_trigger(tmp_path)[source]

Fail when a caller declares a trigger absent from canonical.

Return type:

None

tests.test_workflow_sync.test_caller_with_synthetic_triggers_for_call_only_canonical(tmp_path)[source]

Caller with synthesized triggers is clean when canonical has no non-call triggers.

The downstream release.yaml synthesizes push + workflow_dispatch because _release-engine.yaml is call-only. check_triggers_match must not flag those synthesized triggers as “extra” when the canonical defines none.

Return type:

None

tests.test_workflow_sync.ALL_RELEASE_SECRETS = '    secrets:\n      REPOMATIC_PAT: ${{ secrets.REPOMATIC_PAT }}\n      VIRUSTOTAL_API_KEY: ${{ secrets.VIRUSTOTAL_API_KEY }}\n'

Every secret _release-engine.yaml declares, passed explicitly.

tests.test_workflow_sync.test_check_secrets_passed(tmp_path, caller, canonical, secrets_block, is_issue, needle)[source]

A caller must forward every secret its canonical workflow declares.

Return type:

None

tests.test_workflow_sync.CLEAN_WORKFLOW = '---\n"on":\n  workflow_dispatch:\n  push:\njobs:\n  build:\n    runs-on: ubuntu-latest\n'

A workflow the linter finds nothing to say about.

tests.test_workflow_sync.DISPATCHLESS_WORKFLOW = '---\n"on":\n  push:\n'

A workflow missing workflow_dispatch, the linter’s mildest complaint.

tests.test_workflow_sync.test_run_workflow_lint(tmp_path, body, fatal, exit_code)[source]

Lint exit codes across a clean tree, an empty one, and both severities.

Return type:

None

tests.test_workflow_sync.test_run_workflow_lint_missing_directory(tmp_path)[source]

A directory that does not exist is an error, not an empty tree.

Return type:

None

tests.test_workflow_sync.test_thin_caller_exempt_from_workflow_dispatch_check(tmp_path)[source]

Thin callers wrapping a canonical without workflow_dispatch lint clean.

cancel-runs.yaml and release.yaml intentionally lack workflow_dispatch. A thin caller mirroring them must not be flagged by the standalone workflow_dispatch check.

Return type:

None

tests.test_workflow_sync.test_release_thin_caller_lints_clean(tmp_path)[source]

The generated downstream release.yaml lints clean end-to-end.

Regression test: the synthesized push + workflow_dispatch triggers must not be flagged as “extra” by check_triggers_match when the canonical _release-engine.yaml defines no non-call triggers.

Return type:

None

tests.test_workflow_sync.test_every_reusable_workflow_renders(tmp_path, filename)[source]

Each reusable workflow renders into a caller carrying the version ref.

Return type:

None

tests.test_workflow_sync.test_sync_overwrites(tmp_path)[source]

A file that is not a caller at all is replaced wholesale.

Nothing in it parses as a managed lane, so there are no extras to carry over and the render starts from the canonical workflow.

Return type:

None

tests.test_workflow_sync.test_non_reusable_workflow_refuses_to_render(tmp_path)[source]

A workflow with no workflow_call trigger cannot become a caller.

tests.yaml is copied downstream header-first by _init_workflows, never rendered as a caller, so asking for one is a programming error rather than a file to write.

Return type:

None

tests.test_workflow_sync.test_default_level()[source]

Verify default annotation level is WARNING.

Return type:

None

tests.test_workflow_sync.test_custom_level()[source]

Verify custom annotation level.

Return type:

None

tests.test_workflow_sync.BUNDLED_WITH_CONCURRENCY = ('autofix.yaml', 'changelog.yaml', 'debug.yaml', 'docs.yaml', 'labels.yaml', 'lint.yaml', 'metrics.yaml', 'release.yaml', 'tests.yaml')

Bundled workflows that define a concurrency block.

tests.test_workflow_sync.BUNDLED_WITHOUT_CONCURRENCY = ('_release-engine.yaml', 'autolock.yaml', 'cancel-runs.yaml', 'unsubscribe.yaml')

Bundled workflows that do not.

_release-engine.yaml is here by design: it delegates concurrency to the push-triggered release.yaml entry that calls it (see _generate_release_caller).

tests.test_workflow_sync.test_concurrency_present(filename)[source]

Verify concurrency is extracted for workflows that define it.

Return type:

None

tests.test_workflow_sync.test_concurrency_absent(filename)[source]

Verify concurrency is None for workflows without it.

Return type:

None

tests.test_workflow_sync.test_concurrency_preserves_expressions()[source]

Verify raw concurrency preserves ${{ }} expressions.

Return type:

None

tests.test_workflow_sync.test_concurrency_preserves_comments()[source]

Verify raw concurrency preserves inline comments.

Return type:

None

tests.test_workflow_sync.test_thin_caller_omits_concurrency(filename)[source]

Verify simple thin callers never include concurrency.

A simple thin caller’s single job joins the reusable workflow’s own concurrency group immediately when called via workflow_call, so duplicating the block in the caller is unnecessary.

Return type:

None

tests.test_workflow_sync.test_thin_caller_drops_upstream_source_paths(filename)[source]

Thin callers drop repomatic/-prefixed paths even without source_paths.

Universal entries (pyproject.toml, workflow self-refs) are preserved so trigger semantics carry over from the canonical workflow.

Return type:

None

tests.test_workflow_sync.test_thin_caller_sha_pinned()[source]

Thin caller with commit_sha produces @sha # version.

Return type:

None

tests.test_workflow_sync.test_thin_caller_sha_none_fallback()[source]

Thin caller without commit_sha produces @version.

Return type:

None

tests.test_workflow_sync.test_thin_caller_sha_pinned_yaml_valid()[source]

SHA-pinned thin caller is valid YAML with comment stripped.

Return type:

None

tests.test_workflow_sync.test_identify_canonical_workflow_sha_pinned(tmp_path)[source]

identify_canonical_workflow recognizes SHA-pinned thin callers.

Return type:

None

tests.test_workflow_sync.test_check_version_pinned_sha(tmp_path)[source]

check_version_pinned passes for SHA-pinned refs.

Return type:

None

tests.test_workflow_sync.test_header_extraction_tests_yaml()[source]

Verify header extraction for tests.yaml.

Return type:

None

tests.test_workflow_sync.test_header_extraction_lint_yaml()[source]

Verify header extraction for lint.yaml.

Return type:

None

tests.test_workflow_sync.test_header_extraction_nonexistent()[source]

Raise FileNotFoundError for missing workflow.

Return type:

None

tests.test_workflow_sync.test_substitute_source_paths(paths, source_paths, expected)[source]

Upstream source globs are retargeted; everything else is kept or dropped.

Return type:

None

tests.test_workflow_sync.test_adapt_trigger_paths(config, filename, spec, expected)[source]

Every PathsSpec knob, applied to one trigger’s config block.

Return type:

None

tests.test_workflow_sync.test_thin_caller_release_with_source_paths()[source]

Verify release.yaml thin caller has no path filters.

release.yaml only has push (no paths:) and workflow_call, so source_paths has no effect.

Return type:

None

tests.test_workflow_sync.test_thin_caller_changelog_with_source_paths()[source]

Verify changelog.yaml thin caller keeps universal paths unchanged.

changelog.yaml has no upstream source glob, so source_paths has no effect beyond keeping all paths intact.

Return type:

None

tests.test_workflow_sync.test_thin_caller_lint_no_paths_with_source_paths()[source]

Verify workflows without paths don’t gain paths from source_paths.

Return type:

None

tests.test_workflow_sync.test_header_with_source_paths_substitutes()[source]

Verify header generation replaces upstream source paths.

Return type:

None

tests.test_workflow_sync.test_tests_yaml_runs_on_bundled_assets_upstream_only()[source]

Upstream Tests run on a skill or agent edit, downstream Tests do not.

The bundled skills and agents reach the package through symlinks, so an edit to one changes no repomatic/** path, while the suite reads them all.

Return type:

None

tests.test_workflow_sync.test_header_without_source_paths_drops_upstream_glob()[source]

Without source_paths, the upstream source glob is dropped from the header.

Return type:

None

tests.test_workflow_sync.test_header_with_extra_paths_appends()[source]

extra_paths are appended to every paths block in the header.

Return type:

None

tests.test_workflow_sync.test_header_with_ignore_paths_strips_canonical()[source]

ignore_paths removes matching entries from every paths block.

Return type:

None

tests.test_workflow_sync.test_header_per_workflow_override_replaces_paths_blocks()[source]

Per-workflow paths override replaces every block in the workflow.

Return type:

None

tests.test_workflow_sync.test_header_per_workflow_override_does_not_apply_to_other_files()[source]

Override scoped to one filename does not affect another workflow’s header.

Return type:

None

tests.test_workflow_sync.test_header_override_quotes_entries_needing_it(entry)[source]

Override entries that cannot stand as plain scalars are quoted.

tests.yaml ships a paths: block whose entries are all plain, so there is no quote style to inherit. Emitting one of these bare produced a header that stopped parsing, which GitHub answers by ignoring the workflow.

Return type:

None

tests.test_workflow_sync.test_needs_yaml_quote(value, expected)[source]

Only entries that would not round-trip as plain scalars are flagged.

Return type:

None

tests.test_workflow_sync.test_derive_source_paths_from_name()[source]

Derive source paths from [project.name].

Return type:

None

tests.test_workflow_sync.test_derive_source_paths_underscore_name()[source]

Derive source paths when name already uses underscores.

Return type:

None

tests.test_workflow_sync.test_derive_source_paths_simple_name()[source]

Derive source paths from a simple name without hyphens.

Return type:

None

tests.test_workflow_sync.test_derive_source_paths_no_name()[source]

Return empty list when no project name defined.

Return type:

None

tests.test_workflow_sync.test_derive_source_paths_empty_pyproject()[source]

Return empty list for empty pyproject data.

Return type:

None

tests.test_workflow_sync.test_resolve_source_paths_explicit_config()[source]

Use explicitly configured source paths.

Return type:

None

tests.test_workflow_sync.test_resolve_source_paths_none_derives()[source]

Auto-derive when config is None.

Return type:

None

tests.test_workflow_sync.test_resolve_source_paths_empty_list_returns_none()[source]

Return None when explicitly set to empty list.

Return type:

None

tests.test_workflow_sync.test_resolve_source_paths_no_name_returns_none()[source]

Return None when no project name and no config.

Return type:

None

tests.test_workflow_sync.test_split_yaml_quote(scalar, expected)[source]

Strip outer matching quotes; pass through plain scalars.

Return type:

None

tests.test_workflow_sync.test_thin_caller_paths_spec_extra_paths_appends()[source]

Thin caller picks up extra_paths in every paths-bearing trigger.

Return type:

None

tests.test_workflow_sync.test_thin_caller_paths_spec_ignore_strips_canonical()[source]

Thin caller strips ignore_paths entries from every paths block.

Return type:

None

tests.test_workflow_sync.test_thin_caller_paths_spec_per_workflow_override_replaces_wholesale()[source]

Per-workflow override replaces the thin caller’s paths list verbatim.

Return type:

None

tests.test_workflow_sync.test_header_ignore_drops_block_when_empty()[source]

When ignore_paths empties a block, the paths: key is removed.

Return type:

None

tests.test_workflow_sync.test_header_ignore_applies_before_extras()[source]

ignore_paths runs before extra_paths: an entry stripped then re-added stays.

A canonical entry listed in ignore_paths and extra_paths simultaneously is stripped first and then appended at the tail (not preserved in place).

Return type:

None

tests.test_workflow_sync.test_header_paths_ignore_block_untouched_by_knobs()[source]

extra_paths/ignore_paths/per-workflow override only target paths:.

paths-ignore: blocks are written by the canonical workflow as exclusion filters; they are not the trigger gate the knobs are designed for. The header rewriter must leave them alone.

Return type:

None

tests.test_workflow_sync.test_header_preserves_canonical_quote_style()[source]

Header rewriter preserves quote style for unmodified entries.

Return type:

None

tests.test_workflow_sync.test_canonical_caller_permissions(filename, expected)[source]

Union the job-level scopes a caller has to forward.

Return type:

None

tests.test_workflow_sync.test_canonical_caller_permissions_keeps_most_permissive(monkeypatch, levels, expected)[source]

Resolve a scope granted at odds across jobs to its strongest level.

Order of appearance must not matter: a narrower grant later in the file cannot walk back a wider one already collected.

Return type:

None

tests.test_workflow_sync.test_generate_thin_caller_omits_permissions_by_default()[source]

Leave a caller with no extra jobs exactly as before.

Its single managed job inherits the repository default, which the reusable workflow’s own permissions: blocks then cap. Pinning {} here would buy nothing and starve that call.

Return type:

None

tests.test_workflow_sync.test_generate_thin_caller_emits_both_permission_halves()[source]

Pin least privilege at the top and forward the scopes the call needs.

Return type:

None

tests.test_workflow_sync.test_generate_thin_caller_spells_out_an_empty_forward()[source]

Emit permissions: {} on the job when no upstream job asks for a scope.

Return type:

None

tests.test_workflow_sync.test_thin_caller_permissions_track_extra_jobs(tmp_path)[source]

Add the contract when extra jobs appear, drop it when they go away.

Return type:

None

tests.test_workflow_sync.test_release_caller_always_denies_permissions(tmp_path)[source]

The canonical entry’s deny-by-default reaches every downstream copy.

Unlike a plain thin caller, whose contract only appears once extra jobs do, release.yaml is copied from an entry that always carries the key.

Return type:

None

tests.test_workflow_sync.test_release_caller_preserves_downstream_needs(tmp_path, downstream_needs, expected)[source]

Carry the consumer’s own needs: edges across a sync.

A repo building a release-assets file gates the engine on the job packing it. That edge sits on a lane this renderer regenerates, so without carrying it over every repomatic init would silently drop the gate. Edges naming a managed lane or a vanished job are dropped instead of echoed back.

Return type:

None

tests.test_workflow_sync.test_release_caller_drops_repomatic_local_needs()[source]

A canonical needs: edge on a repomatic-only job never reaches downstream.

The canonical entry gates its engine lane on pack-plugin, a job that exists only upstream: the renderer copies the three managed lanes and leaves it behind. Echoing the edge would emit a needs: on a job the generated file does not define, which GitHub rejects at startup, breaking the release workflow of every consumer at once.

Return type:

None

tests.test_workflow_sync.test_generated_caller_with_extra_jobs_satisfies_lint(tmp_path, monkeypatch)[source]

Close the loop the warning opened.

check_workflow_permissions is what flagged these callers in the first place, and it fails both on a missing top-level key and on a managed call starved by an empty one. A generated caller must clear both at once.

Return type:

None

tests.test_workflow_sync.test_thin_caller_rendering_has_a_single_seam()[source]

Only render_thin_caller_for_target may render a caller for a file.

That helper is where the downstream state a sync must carry over (extra jobs, the consumer’s own needs: edges) is read and passed on. A second call site is a second place to forget one, which is exactly how the needs: edge came to be dropped on the repomatic init path while the suite driving the other copy stayed green.

Reach the renderer through the helper. If a caller genuinely needs raw output with no file behind it, it is not a sync and should say so here.

Return type:

None

tests.test_workflow_sync.test_configured_input_reaches_the_generated_caller()[source]

A [tool.repomatic] value lands in the caller, behind the dispatch input.

repomatic init is the only moment it can: the reusable workflow runs with no checkout of the calling repository, so nothing downstream can read pyproject.toml at run time. The dispatch input stays in front of the literal, so a manual run still overrides the configured default.

Return type:

None

tests.test_workflow_sync.test_default_config_leaves_the_caller_untouched()[source]

Configuring nothing churns no caller: the two renders are identical.

Return type:

None

tests.test_workflow_sync.test_canonical_input_defaults_match_the_config_field(input_name, field_name)[source]

The canonical workflow and the config field declare the same default.

The literal is spelled three times in unsubscribe.yaml: the workflow_call input, the workflow_dispatch input, and the || '...' fallback the step applies when a non-dispatch event leaves inputs null. A default that moves in the config and not in the YAML would leave a repository configuring nothing on a different value than the one the schema documents.

Return type:

None