tests.test_lint_repo module

Tests for repository linting module.

tests.test_lint_repo.test_successful_fetch()[source]

Fetch and parse repo metadata.

tests.test_lint_repo.test_empty_fields()[source]

Handle empty fields.

tests.test_lint_repo.test_unreadable_metadata()[source]

Both fields are None when the payload cannot be read.

gh_api_json collapses a failed call and unparsable output into one None, so there is a single outcome to assert on here.

tests.test_lint_repo.test_names_match()[source]

No warning when names match.

tests.test_lint_repo.test_names_differ()[source]

Warning when names differ.

tests.test_lint_repo.test_no_package_name()[source]

Skip when no package name.

tests.test_lint_repo.test_not_sphinx()[source]

Skip when not a Sphinx project.

tests.test_lint_repo.test_sphinx_with_website()[source]

No warning when Sphinx project has website.

tests.test_lint_repo.test_sphinx_without_website()[source]

Warning when Sphinx project has no website.

tests.test_lint_repo.test_sphinx_fetches_metadata()[source]

Fetch metadata when homepage_url not provided.

tests.test_lint_repo.test_documentation_url(project_urls, expected)[source]

Resolve the documentation site a project declares in [project.urls].

tests.test_lint_repo.test_sphinx_website_matches_docs_url(homepage_url, docs_url)[source]

Pass when the website field and the declared docs URL name one address.

tests.test_lint_repo.test_sphinx_website_differs_from_docs_url(homepage_url, docs_url)[source]

Fail when the website field names something other than the docs URL.

tests.test_lint_repo.test_sphinx_website_without_declared_docs_url()[source]

Keep the presence-only check when the project declares no docs URL.

tests.test_lint_repo.test_descriptions_match()[source]

No error when descriptions match.

tests.test_lint_repo.test_descriptions_differ()[source]

Error when descriptions differ.

tests.test_lint_repo.test_no_project_description()[source]

Skip when no project description.

tests.test_lint_repo.test_fetches_metadata()[source]

Fetch metadata when repo_description not provided.

tests.test_lint_repo.test_all_checks_pass()[source]

Return 0 when all checks pass.

tests.test_lint_repo.test_description_mismatch(capsys)[source]

Return 1 when description mismatches.

tests.test_lint_repo.test_package_name_warning(capsys)[source]

Emit warning for package name mismatch but still pass.

tests.test_lint_repo.test_website_warning(capsys)[source]

Emit warning for missing website but still pass.

tests.test_lint_repo.test_website_docs_url_mismatch_warning(capsys)[source]

A website field pointing away from the docs URL warns without failing.

tests.test_lint_repo.test_notifications_pat_check(capsys, unsubscribe_active, has_notifications_pat, expected)[source]

The notifications PAT check only fires when the workflow is opted in.

tests.test_lint_repo.test_cloudflare_secrets_check(capsys, site_deploy, is_sphinx, has_token, expected)[source]

The token is the whole of the credential.

The account is derived from it at run time: a token scoped to nothing but Cloudflare Pages: Edit still enumerates the account it belongs to, so no second identifier is asked for or reported on.

tests.test_lint_repo.test_cloudflare_r2_secrets_follow_the_declared_bucket(bucket, applies)[source]

Without a bucket the deploy drops oversized files by design, reading no key.

tests.test_lint_repo.test_cloudflare_r2_secrets(has_keys, passed, fragment)[source]
tests.test_lint_repo.test_oversized_site_files(tmp_path, big, bucket, passed, fragment)[source]

Warn about a tracked file the Pages deploy would drop, unless R2 takes it.

tests.test_lint_repo.test_pages_deployment_source_follows_the_deploy_target(site_deploy, probed)[source]

The Pages source check is skipped for a project that deploys elsewhere.

GET /repos/{repo}/pages answers 404 for a repository that never enabled Pages, which the check reads as indeterminate. Reported against a Cloudflare-hosted project that is a permanent skip line about a setting nobody will ever change.

tests.test_lint_repo.test_pages_redirect_preserved(pages, history, docs_url, passed, fragment)[source]

The old github.io space must keep redirecting after a move.

Disabling Pages is the unrecoverable mistake: it deletes the redirect and every historical link with it, silently, because nothing inside the repository changes. Leaving the custom domain unset is the quieter one, where the old host serves a copy that stops being rebuilt the moment the deploy job is gated off.

tests.test_lint_repo.test_pages_redirect_check_follows_the_deploy_target()[source]

Only a repository that moved away is asked to keep redirecting.

tests.test_lint_repo.test_manpages_toolchain_reads_the_locked_click_extra(tmp_path, version, passed)[source]

The floor is checked against uv.lock, which the job installs from.

tests.test_lint_repo.test_manpages_toolchain_matches_a_non_canonical_lock_name(tmp_path)[source]

A lock spelling the package Click_Extra names the same project.

tests.test_lint_repo.test_manpages_toolchain_is_indeterminate_without_click_extra(tmp_path, packages)[source]

A lock that never mentions click-extra answers neither pass nor fail.

tests.test_lint_repo.test_manpages_toolchain_is_indeterminate_without_a_lock(tmp_path)[source]

A project with no lockfile is reported as unknown, not broken.

tests.test_lint_repo.test_manpages_toolchain_only_applies_to_an_opted_in_project()[source]

The roster entry stays silent until manpages.script is set.

tests.test_lint_repo.BOOTSTRAP_CONFIGS = (ToolConfigComponent(name='ruff', description='Ruff linter/formatter configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='ruff.toml', tool_section='tool.ruff', sync_mode=<SyncMode.BOOTSTRAP: 1>, customizable_entries=(), legacy_table='', preserved_keys=(), graft_identity_keys=(), overlay=False), ToolConfigComponent(name='pytest', description='Pytest test configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='pytest.toml', tool_section='tool.pytest', sync_mode=<SyncMode.BOOTSTRAP: 1>, customizable_entries=(('addopts', '--cov=.'),), legacy_table='ini_options', preserved_keys=(), graft_identity_keys=(), overlay=False), ToolConfigComponent(name='coverage', description='Coverage.py measurement and reporting configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='coverage.toml', tool_section='tool.coverage', sync_mode=<SyncMode.BOOTSTRAP: 1>, customizable_entries=(), legacy_table='', preserved_keys=(), graft_identity_keys=(), overlay=False), ToolConfigComponent(name='mypy', description='Mypy type checking configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='mypy.toml', tool_section='tool.mypy', sync_mode=<SyncMode.BOOTSTRAP: 1>, customizable_entries=(), legacy_table='', preserved_keys=(), graft_identity_keys=(), overlay=False), ToolConfigComponent(name='mdformat', description='mdformat Markdown formatter configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='mdformat.toml', tool_section='tool.mdformat', sync_mode=<SyncMode.BOOTSTRAP: 1>, customizable_entries=(), legacy_table='', preserved_keys=(), graft_identity_keys=(), overlay=False))

Every tool config init seeds once and never revisits.

tests.test_lint_repo.test_missing_template_entries(template, local, expected)[source]

Only absence is drift: a tuned value and a local addition are not.

tests.test_lint_repo.test_template_member_name(member, expected)[source]

A scalar is its own name, and a table is named by its inline TOML.

tests.test_lint_repo.test_bootstrap_config_drift_passes_on_its_own_template(comp)[source]

A section holding the bundle verbatim is missing nothing.

tests.test_lint_repo.test_bootstrap_config_drift_reports_a_dropped_entry(comp)[source]

A knob the template gained after seeding reaches nobody on its own.

kdeldycke/dotfiles carried a [tool.ruff] seeded before the bundle added D301 to lint.ignore. init never revisits a BOOTSTRAP section, so the rule stayed absent with nothing reporting it.

tests.test_lint_repo.test_bootstrap_config_drift_keeps_a_customized_placeholder()[source]

--cov=. names the project’s own package, so its absence is not drift.

tests.test_lint_repo.test_bootstrap_config_drift_reads_the_pytest_legacy_table()[source]

A section kept under ini_options is measured there, not reported empty.

pytest refuses a file carrying both [tool.pytest] keys and [tool.pytest.ini_options], so reporting the template’s keys as missing from [tool.pytest] would prescribe a configuration pytest rejects.

tests.test_lint_repo.test_template_table_member_names_write_the_member()[source]

The name of a table must be TOML that writes that same table.

The name is what a maintainer pastes into the section. A name that parsed to anything else would have them copy an entry the template does not hold, and the warning would stay.

tests.test_lint_repo.test_bootstrap_config_drift_declines_any_reportable_entry(comp, keys, member)[source]

Every entry the check reports can be declined, at its path in the section.

A missing key and a missing list member are named the way the tool’s own section names them, so no entry needs a syntax of its own.

tests.test_lint_repo.test_bootstrap_config_drift_keeps_reporting_the_siblings_of_a_declined_member()[source]

Declining one member of a list says nothing about the others.

tests.test_lint_repo.test_bootstrap_config_drift_declines_an_entry_of_the_pytest_legacy_table()[source]

An entry is named below the tool, wherever the section keeps its keys.

tests.test_lint_repo.test_bootstrap_config_drift_wants_a_reason(reason)[source]

An entry mapped to no reason declines nothing: the reason is the point.

tests.test_lint_repo.test_bootstrap_config_drift_reports_an_entry_that_declines_nothing(leaf, verdict)[source]

A record that outlived its subject is reported, like the gap it hid.

The section took the entry back, the template dropped it, or the path never named one: in each case the record now states a decision nobody is making, and the next real gap at that path would arrive already declined.

tests.test_lint_repo.test_bootstrap_config_drift_roster_entry_reads_the_declined_table()[source]

The roster hands the check the table, not only the section it measures.

tests.test_lint_repo.test_bootstrap_config_drift_is_settled_in_this_repository()[source]

This repository carries or declines every entry of its seeded templates.

A declined table is keyed by its inline TOML, so the record stops applying the day that rendering changes. The check then only warns, which nobody reads on a green run.

tests.test_lint_repo.test_lint_context_carries_the_lint_repo_table(tmp_path, monkeypatch)[source]

from_project hands over what the repository told the checks.

tests.test_lint_repo.test_customizable_entries_exist_in_their_template(comp)[source]

A placeholder declaration goes stale silently, and then hides real drift.

tests.test_lint_repo.test_customizable_entries_are_bootstrap_only(comp)[source]

An ongoing sync re-derives the section, so it has no placeholder to keep.

tests.test_lint_repo.BUNDLED_LYCHEE_EXCLUDES = ('^https://github.com/.+/issues/[0-9]+#issuecomment-.*$', '^https://github.com/.+/releases/.+/download/.*$', '^https://doi.org/.+/zenodo.*$', 'archive\\.ph', 'sci-hub\\.st', 'ycombinator\\.com', '^https://(www\\.)?x\\.com(/.*)?$', '^https://www\\.virustotal\\.com/gui/.*$', 'bitdefender\\.com', '^https://(www\\.)?medium\\.com(/.*)?$', '^https://www\\.npmjs\\.com/package/.*$', '^https://star-history\\.com/.*$', 'githubstatus\\.com', '^https://claude\\.ai/.*$')

The canonical [tool.lychee] exclude list every deployed repository starts from.

tests.test_lint_repo.test_anchored_exclude_core(pattern, expected)[source]

Only an anchored host-only pattern replaced a bare form.

tests.test_lint_repo.test_superseded_local_excludes_passes_on_the_bundle()[source]

A section holding the bundle verbatim superseded nothing.

tests.test_lint_repo.test_superseded_local_excludes_reports_a_bare_entry_the_bundle_anchored()[source]

The graft appends a local-only item and never retires a replaced one.

Every awesome list carried a bare x\.com beside the anchored form the bundle replaced it with, so lychee went on excluding every domain ending in x.com in all four of them.

tests.test_lint_repo.test_superseded_local_excludes_replaces_a_bare_entry_left_alone()[source]

A section no sync reaches lacks the anchored form, so a drop loses the host.

A repository outside the awesome-only scope keeps its own [tool.lychee] copy, carrying the bare x\.com where the bundle now has the anchored form. Dropping it there would stop excluding x.com itself.

tests.test_lint_repo.test_superseded_local_excludes_keeps_a_local_bare_entry_with_no_twin()[source]

A repository’s own bare pattern is a customization, not a leftover.

tests.test_lint_repo.test_superseded_local_excludes_keeps_a_local_anchored_entry()[source]

An anchored local pattern narrows rather than broadens, so it is kept.

tests.test_lint_repo.test_superseded_local_excludes_ignores_a_tool_with_no_patterns()[source]

Only a synced array of URL patterns has an anchored/bare distinction.

tests.test_lint_repo.test_bundled_excludes_never_carry_both_forms()[source]

Guard the bundle itself against the pair this check reports downstream.

Not retroactive coverage: the bundle anchored x\.com without ever shipping both forms, so the stale copies lived downstream alone. What this catches is the next time an entry is anchored here, since a bare pattern beside its anchored replacement is canonical in both and no downstream check can report it as drift.

tests.test_lint_repo.test_every_host_only_anchored_bundle_entry_is_readable()[source]

An anchored entry the core extractor cannot read silently narrows the check.

anchored_exclude_core() recognises one shape. A host-only entry written in another answers None, so a bare downstream copy of it would go unreported. A path-shaped entry is expected to answer None: no bare form of one was ever bundled on its own.

tests.test_lint_repo.test_pages_redirects_clean_file_passes(tmp_path, monkeypatch)[source]

A file the engine keeps whole reports one green line.

tests.test_lint_repo.test_pages_redirects_reports_dropped_rules(tmp_path, monkeypatch)[source]

Every line the engine drops surfaces with its file position.

These are the failures wrangler pages deploy swallows: a duplicate source, a bad status, an infinite loop. Each dropped line is a redirect that looks committed and is dead in production.

tests.test_lint_repo.test_pages_redirects_reports_budget_abort_and_misordering(tmp_path, monkeypatch)[source]

The silent kill switch: statics after a dynamic burn the 100 budget.

The file below opens with a dynamic rule, so every following static is charged against the dynamic budget of 100. The 101st charged rule (line 101, however static it looks) aborts the parse, and everything below it is discarded without a word from the deploy pipeline.

tests.test_lint_repo.test_pages_redirects_names_a_url_a_surviving_pattern_captured(tmp_path, monkeypatch)[source]

An abandoned exact rule whose URL a broader pattern now answers.

The reader can find a dead redirect by following it. One that still redirects, to somewhere its author never wrote, looks healthy from the outside, so naming the new destination is the only way it surfaces.

tests.test_lint_repo.test_pages_redirects_caps_the_urls_it_names(tmp_path, monkeypatch)[source]

A tail of hundreds is summarized, not dumped into one lint message.

tests.test_lint_repo.test_pages_redirects_names_the_exact_rules_a_pattern_shadows(tmp_path, monkeypatch)[source]

An exact rule below a pattern that matches it is dead, and the lint says so.

Moving it above the patterns is the fix, and it is also a change: the rule starts to fire. The failure names each such rule with the pattern that answers in its place, so the reader knows which answers the reorder moves.

tests.test_lint_repo.test_pages_redirects_reports_rules_that_cannot_work(tmp_path, monkeypatch, line, fragment)[source]

Rules the parser keeps and production still breaks, each with its line.

The engine reports none of them: it drops a source it cannot compile at run time, and a destination token it does not replace reaches the visitor as literal text.

tests.test_lint_repo.test_pages_redirects_accepts_a_literal_colon_in_an_exact_rule(tmp_path, monkeypatch)[source]

An exact source captures nothing, so a colon in its destination is text.

tests.test_lint_repo.test_pages_redirects_check_is_fatal_and_gated_on_the_file()[source]

The roster entry must fail the run: dropped rules are already broken.

tests.test_lint_repo.test_pages_redirects_honours_gitignore(tmp_path, monkeypatch)[source]

A generated copy of the file must not be audited, only the source.

A static site build copies _redirects verbatim into its output tree, so without the gitignore filter every finding would be reported twice, and a stale build artifact could fail the lint after the source was fixed.

tests.test_lint_repo.test_wrangler_config_check(tmp_path, monkeypatch, wrangler_toml, context_kwargs, expected_failures)[source]

wrangler.toml must agree with the declared project name and date.

The file only feeds local wrangler commands on a Direct Upload project, which is exactly why nothing else ever catches it lying: one project’s compatibility date sat three years behind the live value this way.

tests.test_lint_repo.test_minimal_run()[source]

Run with no checks enabled.

tests.test_lint_repo.test_topics_no_keywords()[source]

Skip when no keywords provided.

tests.test_lint_repo.test_topics_all_in_keywords()[source]

No warning when all topics are in keywords.

tests.test_lint_repo.test_topics_extra_not_in_keywords()[source]

Warning when topics exist that are not in keywords.

tests.test_lint_repo.test_topics_match_keywords_case_insensitively()[source]

GitHub lowercases topics, so a capitalized keyword still counts.

tests.test_lint_repo.test_topics_match_multi_word_keywords(keyword)[source]

A topic holds no whitespace, so a phrase counts as its hyphenated form.

tests.test_lint_repo.test_topics_phrase_keyword_does_not_declare_its_words()[source]

A phrase declares its hyphenated topic, not one topic per word.

tests.test_lint_repo.test_topics_api_failure()[source]

Skip gracefully when API call fails.

tests.test_lint_repo.test_topics_empty_response()[source]

Skip when no topics are set on the repo.

tests.test_lint_repo.test_stale_gh_pages_branch_absent_passes()[source]

A 404 is the desired state: the branch is gone.

tests.test_lint_repo.test_stale_gh_pages_branch_present_fails()[source]

A readable branch is the stale leftover the check exists to flag.

tests.test_lint_repo.test_stale_gh_pages_branch_api_failure_skips()[source]

An unreadable API is a skip, never a pass.

Every other failure used to report green, so an auth flap or a rate limit silently vouched for a branch nobody read.

tests.test_lint_repo.test_funding_file_exists(tmp_path, monkeypatch)[source]

No warning when funding file already exists.

tests.test_lint_repo.test_funding_file_exists_lowercase(tmp_path, monkeypatch)[source]

Detect funding file regardless of case.

tests.test_lint_repo.test_funding_missing_with_sponsors(tmp_path, monkeypatch)[source]

Warning when owner has sponsors but no funding file.

tests.test_lint_repo.test_funding_skipped_for_fork(tmp_path, monkeypatch)[source]

Skip funding check for forked repositories.

tests.test_lint_repo.test_funding_skipped_no_sponsors(tmp_path, monkeypatch)[source]

Skip when owner has no GitHub Sponsors listing.

tests.test_lint_repo.test_workflow_permissions(tmp_path, monkeypatch, workflow, expect_fail, needle)[source]

Flag starved reusable calls and custom-step workflows missing the key.

tests.test_lint_repo.test_funding_unreadable_payload(tmp_path, monkeypatch)[source]

Skip gracefully when the GraphQL payload cannot be read.

tests.test_lint_repo.CANONICAL_TEMPLATE_PATH = '.github/pr-templates/sync-fruit-basket.md'

Where a repository-local PR body template is expected to live.

tests.test_lint_repo.CONFORMING_TEMPLATE = '---\ntitle: Sync fruit basket\nfooter: false\n---\n\nBody.\n'

A template passing every frontmatter rule.

tests.test_lint_repo.test_pr_templates(tmp_path, monkeypatch, arg_path, file_path, body, needle)[source]

Flag misplaced, missing, and malformed repository-local PR templates.

tests.test_lint_repo.test_pr_templates_equals_form(tmp_path, monkeypatch)[source]

--template-file=path is matched the same as the space-separated form.

tests.test_lint_repo.test_pr_templates_referenced_and_on_disk_reported_once(tmp_path, monkeypatch)[source]

A template both referenced and on disk is one candidate, not two.

A workflow always spells the path with /, while the directory glob yields the native separator, so a naive union of the two reports the same template twice on Windows, the second time without its referencing workflow.

tests.test_lint_repo.test_pr_templates_unreferenced_still_checked(tmp_path, monkeypatch)[source]

A template no workflow names is still validated, so drift stays visible.

tests.test_lint_repo.test_pr_templates_skipped(tmp_path, monkeypatch, make_workflows_dir, needle)[source]

Skip cleanly when there is no workflow directory, or nothing to check.

tests.test_lint_repo.test_stale_drafts_detected()[source]

Warn about draft releases that are not dev pre-releases.

tests.test_lint_repo.test_stale_drafts_none()[source]

No warning when only dev pre-release drafts exist.

tests.test_lint_repo.test_stale_drafts_unreadable_payload()[source]

Skip gracefully when the release list cannot be read.

tests.test_lint_repo.test_stale_drafts_multiple()[source]

List all stale draft tags in the warning.

tests.test_lint_repo.test_install_guide_downloads_all_present(tmp_path, monkeypatch)[source]

Pass when every referenced file is attached to its release.

tests.test_lint_repo.test_install_guide_downloads_latest_alias_present(tmp_path, monkeypatch)[source]

Check a versionless alias against the latest release, with no tag.

tests.test_lint_repo.test_install_guide_downloads_latest_alias_renamed(tmp_path, monkeypatch)[source]

Warn on an alias naming a file the latest release renamed away.

This is the meta-package-manager shape: binaries went from mpm-* to meta-package-manager-* in 7.0.0, and nothing rewrites an alias URL, so the guide kept advertising a 404 across six releases.

tests.test_lint_repo.test_install_guide_downloads_missing_asset(tmp_path, monkeypatch)[source]

Warn naming each referenced file the release does not carry.

tests.test_lint_repo.test_install_guide_downloads_release_without_assets(tmp_path, monkeypatch)[source]

A release carrying no assets at all reports every referenced file.

This is the 7.7.0 shape: the release published, so the API answers, but the binary upload never ran.

tests.test_lint_repo.test_install_guide_downloads_skipped(tmp_path, monkeypatch, body, needle)[source]

Skip when there is no guide, or no download URL to verify.

tests.test_lint_repo.test_install_guide_downloads_unreadable_release(tmp_path, monkeypatch)[source]

Skip rather than warn when the release cannot be read.

A transient API failure must not be reported as a broken install guide.

tests.test_lint_repo.test_sha_pinning_required_enabled()[source]

Pass when the repository requires SHA pinning for Actions.

tests.test_lint_repo.test_sha_pinning_required_disabled()[source]

Warn with a fix link when SHA pinning is not required.

tests.test_lint_repo.test_sha_pinning_required_unreadable_payload()[source]

Skip gracefully when the permissions payload cannot be read.

tests.test_lint_repo.test_pat_permission_probe_pass(probe)[source]

Pass with the probe’s success message when the API call succeeds.

tests.test_lint_repo.test_pat_permission_probe_fail_403(probe)[source]

Fail with the missing-permission message when the API call 403s.

tests.test_lint_repo.test_pat_vulnerability_alerts_permission_404()[source]

A 404 is reported as ‘alerts not enabled’, not as a missing permission.

tests.test_lint_repo.test_pat_check_non_403_surfaces_raw_error()[source]

A 401 from an upstream incident surfaces the raw stderr, not a scope hint.

tests.test_lint_repo.test_pat_check_annotates_with_github_status_incident()[source]

An active githubstatus.com incident is appended to the failure message.

tests.test_lint_repo.REFUSED = 'Resource not accessible by personal access token (HTTP 403)'

What gh reports when a token asks a repository it does not cover.

tests.test_lint_repo.test_pat_repository_scope_probes_an_endpoint_only_its_token_reads()[source]

The probe is the Administration row, and no other row can replace it.

A public repository answers the contents, issues, pull requests and workflows endpoints to any caller, so a 2xx from one of them on another repository says nothing about the token.

tests.test_lint_repo.test_pat_repository_scope_probes_other_repositories(answers, passed, fragment)[source]

A PAT is refused the installation endpoint, so the probes decide.

Only a repository that answers fails the check: a refusal passes it, and anything else leaves it undecided. The repository under check is never one of the others, however the listing spells its name.

tests.test_lint_repo.test_pat_repository_scope_probes_the_most_recently_pushed_repositories()[source]

The listing is sorted by push date, and the probes stop at the limit.

tests.test_lint_repo.test_pat_repository_scope_skips_an_unreadable_listing()[source]

A listing that fails leaves the check undecided.

tests.test_lint_repo.test_pat_stale_statuses_permission_present()[source]

Warn when a 422 proves the token still grants Commit statuses.

tests.test_lint_repo.test_pat_stale_statuses_permission_absent_or_indeterminate(stderr)[source]

Stay silent when the token lacks the scope (403) or the probe is inconclusive.

tests.test_lint_repo.test_pat_stale_statuses_permission_unexpected_success()[source]

An impossible 2xx from the null-SHA probe stays silent rather than warning.

tests.test_lint_repo.test_pat_checks_skipped_without_pat(capsys)[source]

PAT checks are skipped when has_pat is False.

tests.test_lint_repo.test_pat_checks_all_pass(capsys)[source]

Return 0 when all PAT capability checks pass.

tests.test_lint_repo.test_pat_checks_fail_on_missing_permission(capsys)[source]

Return 1 when a PAT capability check fails.

tests.test_lint_repo.test_pypi_trusted_publisher_no_package_name()[source]

Skip when package name is not provided.

tests.test_lint_repo.test_pypi_trusted_publisher_no_release()[source]

Skip when the package has no published version.

tests.test_lint_repo.test_pypi_trusted_publisher_no_provenance()[source]

Indeterminate when provenance fetch fails (pre-OIDC release).

tests.test_lint_repo.test_pypi_trusted_publisher_empty_bundles()[source]

Indeterminate when provenance contains no publisher bundles.

tests.test_lint_repo.test_pypi_trusted_publisher_match()[source]

Pass when a publisher bundle names this repo and release.yaml.

tests.test_lint_repo.test_pypi_trusted_publisher_workflow_mismatch()[source]

Fail when provenance names a different workflow.

tests.test_lint_repo.test_pypi_trusted_publisher_repository_mismatch()[source]

Fail when provenance names a different repository (e.g., upstream).

tests.test_lint_repo.test_check_test_matrix_excludes(tmp_path, monkeypatch, runner, stale)[source]

An exclude naming a runner the matrix no longer has is a warning.

tests.test_lint_repo.test_check_test_matrix_excludes_names_only_the_stale_half(tmp_path, monkeypatch)[source]

A partially stale exclude is reported by its stale values alone.

Under full-include the emitted matrix is a flat job list whose all_variations() is empty, and the check used to read its axes from there: every key of the entry then counted as absent, so the message blamed the live half along with the stale one.

tests.test_lint_repo.test_python_floor_disagreeing_with_classifiers(tmp_path, monkeypatch)[source]

A floor below the lowest classifier misreports what installs.

tests.test_lint_repo.test_python_matrix_missing_an_advertised_boundary(tmp_path, monkeypatch)[source]

Advertising a version the matrix never reaches is untested support.

tests.test_lint_repo.test_python_matrix_may_skip_intermediate_versions(tmp_path, monkeypatch)[source]

Only the ends of the range are mandatory, to keep CI load a free choice.

tests.test_lint_repo.test_python_matrix_testing_an_unadvertised_version(tmp_path, monkeypatch)[source]

A released version tested but not advertised is support left unclaimed.

tests.test_lint_repo.test_python_matrix_tolerates_unreleased_versions(tmp_path, monkeypatch)[source]

An in-development version cannot be advertised, so it is exempt.

tests.test_lint_repo.test_python_matrix_built_at_runtime_is_skipped(tmp_path, monkeypatch)[source]

A fromJSON matrix is opaque, and its axes are canonical already.

tests.test_lint_repo.test_runner_images(tmp_path, monkeypatch, runner, expect_fail, needle)[source]

Floating aliases and off-axis images are flagged; expressions are not.

tests.test_lint_repo.test_runner_images_ignores_thin_callers(tmp_path, monkeypatch)[source]

A job with no steps delegates, and its runner is the callee’s business.

tests.test_lint_repo.test_literal_runners(tmp_path, monkeypatch, runner, expected)[source]

Every literal runs-on: is reported, axis membership notwithstanding.

tests.test_lint_repo.test_literal_runners_skips_thin_callers(tmp_path, monkeypatch)[source]

A delegating job contributes no runner: it names none of its own.

tests.test_lint_repo.test_literal_runners_groups_every_location(tmp_path, monkeypatch)[source]

One image named by several jobs reports all of them, sorted by file.

tests.test_lint_repo.test_inline_pins_match_upstream_flags_lagging_pin(tmp_path)[source]

An inline pin behind the uses: ref version is a fatal error.

tests.test_lint_repo.test_inline_pins_match_upstream_accepts_synced_pin(tmp_path)[source]

An inline pin equal to the uses: ref version passes.

tests.test_lint_repo.test_inline_pins_match_upstream_skips_without_refs(tmp_path)[source]

The canonical repo (local uv run, no upstream refs) has nothing to check.

tests.test_lint_repo.test_self_pin_exemption_flags_unexempt_pin(tmp_path)[source]

A pin resolving under a cooldown without the bypass is a fatal error.

tests.test_lint_repo.test_self_pin_exemption_accepts_exempt_pin(tmp_path)[source]

The spelling both writers emit passes.

tests.test_lint_repo.test_self_pin_exemption_ignores_workflow_without_cooldown(tmp_path)[source]

No cooldown means nothing to exempt, so a bare pin resolves fine.

tests.test_lint_repo.test_setup_uv_version_pin_flags_unpinned_step(tmp_path)[source]

A step with no version: input installs whatever uv is newest.

tests.test_lint_repo.test_setup_uv_version_pin_accepts_pinned_steps(tmp_path)[source]

Every step naming the same version passes.

tests.test_lint_repo.test_setup_uv_version_pin_flags_unpinned_step_next_to_pinned_one(tmp_path)[source]

A pinned step does not vouch for an unpinned one beside it.

tests.test_lint_repo.test_setup_uv_version_pin_flags_split_versions(tmp_path)[source]

Steps pinning two versions silently test two uv releases.

tests.test_lint_repo.test_requested_metadata_keys(command, expected)[source]

Positional keys are read off the command line the way Click reads them.

tests.test_lint_repo.test_metadata_keys_flags_a_retired_key(tmp_path, monkeypatch)[source]

A key removed upstream fails the lint instead of the next workflow run.

tests.test_lint_repo.test_metadata_keys_accepts_current_keys(tmp_path, monkeypatch)[source]

Keys the command still answers raise nothing.

tests.test_lint_repo.test_metadata_keys_skips_a_repo_that_never_calls_it(tmp_path, monkeypatch)[source]

No invocation to read is a skip, not a pass.

tests.test_lint_repo.test_metadata_value_options_match_the_command()[source]

The hand-listed value options are the ones the command really declares.

check_metadata_keys cannot import the CLI to ask (see METADATA_VALUE_OPTIONS), so the list is pinned here instead: an option gaining a value would otherwise make the lint read that value as a key.

tests.test_lint_repo.test_metadata_keys_covers_every_workflow_of_this_repo()[source]

This repository’s own workflows only ask for keys that exist.

tests.test_lint_repo.test_check_branch_ruleset_found()[source]

Active branch ruleset is detected.

tests.test_lint_repo.test_check_branch_ruleset_none()[source]

No branch rulesets returns failure.

tests.test_lint_repo.test_check_branch_ruleset_api_error()[source]

An unreadable rulesets API is indeterminate, not a failure.

Matches check_tag_protection_rules, which reads the same payload. The setup guide still treats the None as incomplete and keeps the step open.

tests.test_lint_repo.test_check_classic_branch_protection_none()[source]

A repository holding only rulesets passes.

tests.test_lint_repo.test_check_classic_branch_protection_found(patterns, expected)[source]

Every surviving rule is named, and both settings pages are linked.

tests.test_lint_repo.test_check_classic_branch_protection_api_error()[source]

Reading the rules needs admin, so a refusal skips instead of passing.

A token without admin gets the same RuntimeError as a network failure. Answering True there would report a clean repository on the strength of a question nobody managed to ask.

tests.test_lint_repo.test_check_classic_branch_protection_partial_payload(payload)[source]

A partial GraphQL response reads as no rules, never as a crash.

GraphQL answers 200 with a null branch when a field resolves to nothing, so every level of the envelope can arrive empty.

tests.test_lint_repo.test_check_immutable_releases_enabled()[source]

Immutable releases enabled is detected.

tests.test_lint_repo.test_check_immutable_releases_disabled()[source]

Immutable releases disabled returns failure.

tests.test_lint_repo.test_check_immutable_releases_api_error()[source]

API failure returns None (indeterminate), not False.

tests.test_lint_repo.test_every_check_is_reachable_from_the_roster()[source]

Every check_* this module defines is wired into REPO_CHECKS.

check_branch_ruleset_on_default and check_immutable_releases shipped defined here but reached only from setup_guide, so lint-repo silently skipped two of its own checks. A check nobody calls is a check nobody benefits from: adding one now means adding it to the roster, or the omission fails here.

tests.test_lint_repo.test_repo_check_names_are_unique()[source]

Two checks sharing a name would make the roster ambiguous to read.

tests.test_lint_repo.test_help_lists_every_check()[source]

lint-repo --help names every check the command runs, with its severity.

The list renders from REPO_CHECKS, so this guards the wiring: a placeholder left unfilled, or a check the rendering drops, fails here.

tests.test_lint_repo.test_check_description_opens_on_its_severity(check)[source]

A check’s docs bullet says whether it fails the job or only warns.

A fatal check’s description opens on Fails, any other on Warns, or on Reports for one that reports before it warns. The docs page renders these bullets under the lint-repo job, so a wrong verb misstates the severity.

tests.test_lint_repo.test_workflows_page_renders_every_check()[source]

The lint-repo job docs render the roster rather than a hand-kept list.

tests.test_lint_repo.test_setup_uv_checksum_coverage_accepts_a_covered_pin(tmp_path)[source]

A pinned uv the pinned action can hash is the state worth having.

tests.test_lint_repo.test_setup_uv_checksum_coverage_flags_an_uncovered_pin(tmp_path)[source]

Two good pins can still verify nothing between them.

setup-uv skips validation for a version absent from the table its own release bundles, on a debug line no CI log shows, so nothing else reports this.

tests.test_lint_repo.test_setup_uv_checksum_coverage_skips_on_an_unreadable_table(tmp_path)[source]

An unreachable API is indeterminate, never a red run.

tests.test_lint_repo.test_setup_uv_checksum_coverage_skips_without_a_pinned_version(tmp_path)[source]

An unpinned step is the other check’s finding, not this one’s.

tests.test_lint_repo.test_setup_uv_checksum_coverage_skips_without_workflows(tmp_path)[source]

A repository with no workflow has nothing to check.

tests.test_lint_repo.test_undeclared_labels_detected()[source]

Report a label the repository carries that no source declares.

tests.test_lint_repo.test_undeclared_labels_warns_before_deleting()[source]

The finding carries the usage count step, not just the names.

Deleting a label detaches every item carrying it, so a check that only named the orphans would invite exactly the loss it exists to prevent.

tests.test_lint_repo.test_undeclared_labels_clean()[source]

No finding when every label on the repository is declared.

tests.test_lint_repo.test_undeclared_labels_skips_on_an_unreadable_api()[source]

An unreachable API is indeterminate, never a red run.

tests.test_lint_repo.test_undeclared_labels_is_advisory()[source]

The check never fails the command.

A repository may legitimately carry a hand-made label, and deleting one is a judgement call. Both make a red run the wrong answer.

tests.test_lint_repo.test_declared_label_names_reads_the_bundled_profiles()[source]

The default profile is always read, the awesome one only for a list.

tests.test_lint_repo.test_declared_label_names_excludes_rename_sources()[source]

A rename-from value is not a declaration.

It names a label expected to stop existing, so counting it as declared would hide the orphan left behind when the rename does not fire. That is how meta-package-manager carried 📦 dependencies and its 864 items unreported for six months.

tests.test_lint_repo.LEMON_CONFIG = {'profiles': {'default': {'labels': [{'color': 'fff44f', 'name': '🍋 lemon'}]}}}

One label definition, serialized below into each format labelmaker reads.

tests.test_lint_repo.test_names_in_labelmaker_config_reads_every_labelmaker_format(filename, text)[source]

Each format labelmaker reads declares its labels.

sync-labels applies an extra label file in any of them, so a TOML-only reader would call every label of a JSON or YAML file an orphan.

tests.test_lint_repo.test_names_in_labelmaker_config_rejects_an_unreadable_file(filename, text)[source]

A definition this module cannot read raises instead of declaring nothing.

An empty answer would call every label of that file an orphan.

tests.test_lint_repo.test_declared_label_names_reads_a_yaml_extra_labels_file(tmp_path)[source]

A committed extra-labels/ file counts whatever its format.

tests.test_lint_repo.REMOTE_LABELS_URL = 'https://example.com/orchard/citrus.toml'

A labels.extra-files entry, served through a mocked get_bytes.

tests.test_lint_repo.test_declared_label_names_fetches_remote_extra_files()[source]

A labels.extra-files definition counts without a staged export.

lint-repo stages nothing, so the declared set reads the file from its URL. Otherwise every label only that file declares would read as an orphan.

tests.test_lint_repo.test_undeclared_labels_skips_on_an_unusable_extra_file(url, fetch, error)[source]

A definition that cannot be fetched or read leaves the check skipped.

A short declared set would call every label of that file an orphan. The set resolves to unknown instead, and the check reports skipped without reading the repository’s labels.