"""The gate installer: three pieces in, idempotent, and no comments lost. The point of this module is that sixteen repos get byte-identical copies, so the assertions compare against the templates rather than against a paraphrase of them -- a test that spelled the expected content out by hand would let the template and the installed file drift, which is the failure the installer exists to prevent. """ from __future__ import annotations import runpy from pathlib import Path import pytest from dep_freshness import install as installer from dep_freshness.install_cli import main from dep_freshness.tests.conftest import write TEMPLATES = Path(installer.__file__).resolve().parent / "templates" def _template(name: str) -> str: return (TEMPLATES / name).read_text(encoding="utf-8") def test_a_bare_repo_gets_all_three_pieces(repo): assert installer.install(repo) == [ "scripts/check_dependency_freshness.sh", ".github/workflows/dependency-freshness.yml", ".pre-commit-config.yaml (dependency-freshness hook)", ] delegate = repo / "scripts/check_dependency_freshness.sh" assert delegate.read_text(encoding="utf-8") == _template("delegate.sh") assert delegate.stat().st_mode & 0o111, "the hook execs it; it must be +x" assert (repo / ".github/workflows/dependency-freshness.yml").read_text( encoding="utf-8" ) == _template("workflow.yml") assert "id: dependency-freshness" in (repo / ".pre-commit-config.yaml").read_text( encoding="utf-8" ) def test_a_second_run_writes_nothing(repo): installer.install(repo) assert installer.install(repo) == [] assert installer.plan(repo) == [] def test_plan_names_what_is_missing_without_writing_it(repo): assert installer.plan(repo) == [ "scripts/check_dependency_freshness.sh", ".github/workflows/dependency-freshness.yml", ".pre-commit-config.yaml (dependency-freshness hook)", ] assert not (repo / "scripts").exists() def test_a_drifted_copy_is_rewritten(repo): installer.install(repo) delegate = repo / "scripts/check_dependency_freshness.sh" delegate.write_text("#!/bin/bash\necho lol\n", encoding="utf-8") workflow = repo / ".github/workflows/dependency-freshness.yml" workflow.write_text("name: something else\n", encoding="utf-8") assert installer.install(repo) == [ "scripts/check_dependency_freshness.sh", ".github/workflows/dependency-freshness.yml", ] assert delegate.read_text(encoding="utf-8") == _template("delegate.sh") def test_an_existing_pre_commit_config_keeps_its_hooks_and_comments(repo): """Appending, not re-emitting: a YAML round-trip drops every comment. Each of these configs explains why its hooks exist, and that prose is the only record of it. """ write( repo, ".pre-commit-config.yaml", """\ # The 250-line cap. A file that cannot be read in one piece forces re-reads. repos: - repo: local hooks: - id: file-length-cap name: file length <= 250 lines entry: bash scripts/check_file_length.sh language: system """, ) installer.install(repo) body = (repo / ".pre-commit-config.yaml").read_text(encoding="utf-8") assert "# The 250-line cap." in body assert "id: file-length-cap" in body assert "id: dependency-freshness" in body def test_a_config_that_already_has_the_hook_is_left_alone(repo): write( repo, ".pre-commit-config.yaml", """\ repos: - repo: local hooks: - id: dependency-freshness entry: scripts/check_dependency_freshness.sh language: system """, ) before = (repo / ".pre-commit-config.yaml").read_text(encoding="utf-8") installer.install(repo) assert (repo / ".pre-commit-config.yaml").read_text(encoding="utf-8") == before def test_a_config_without_a_trailing_newline_still_appends_cleanly(repo): write(repo, ".pre-commit-config.yaml", "repos:\n - repo: local\n hooks:") installer.install(repo) body = (repo / ".pre-commit-config.yaml").read_text(encoding="utf-8") assert body.startswith("repos:\n - repo: local\n hooks:\n\n #") assert "id: dependency-freshness" in body def test_fvmrc_is_only_for_repos_that_hold_a_pubspec(repo): assert installer.write_fvmrc(repo, "3.47.2") is False assert not (repo / ".fvmrc").exists() write(repo, "app/pubspec.yaml", "name: demo\n") assert installer.write_fvmrc(repo, "3.47.2") is True assert (repo / ".fvmrc").read_text( encoding="utf-8" ) == '{\n "flutter": "3.47.2"\n}\n' assert installer.write_fvmrc(repo, "3.47.2") is False def test_the_cli_installs_and_reports_each_piece(repo, capsys): assert main([str(repo)]) == 0 out = capsys.readouterr().out assert "wrote: scripts/check_dependency_freshness.sh" in out assert "wrote: .github/workflows/dependency-freshness.yml" in out def test_the_cli_check_mode_writes_nothing(repo, capsys): assert main([str(repo), "--check"]) == 0 assert ( "would write: scripts/check_dependency_freshness.sh" in capsys.readouterr().out ) assert not (repo / "scripts").exists() main([str(repo)]) assert main([str(repo), "--check"]) == 0 assert "gate already current" in capsys.readouterr().out def test_the_cli_pins_the_sdk_when_asked(repo, monkeypatch, capsys): monkeypatch.setattr( "dep_freshness.install_cli.flutter_latest", lambda: ("3.47.2", "3.13.2") ) write(repo, "pubspec.yaml", "name: demo\n") assert main([str(repo), "--fvm"]) == 0 assert "wrote: .fvmrc (flutter 3.47.2)" in capsys.readouterr().out # Same version again: the pin is already right, so nothing is reported. assert main([str(repo), "--fvm"]) == 0 assert ".fvmrc" not in capsys.readouterr().out def test_an_unresolvable_sdk_leaves_the_pin_alone(repo, monkeypatch, capsys): """Offline, the alternative is pinning every repo to a guessed number.""" monkeypatch.setattr( "dep_freshness.install_cli.flutter_latest", lambda: (None, None) ) write(repo, "pubspec.yaml", "name: demo\n") assert main([str(repo), "--fvm"]) == 1 assert "could not resolve latest stable Flutter" in capsys.readouterr().err assert not (repo / ".fvmrc").exists() def test_the_module_entrypoint_runs_the_cli(repo, monkeypatch): monkeypatch.setattr("sys.argv", ["install_main", str(repo), "--check"]) with pytest.raises(SystemExit) as exit_info: runpy.run_module("dep_freshness.install_main", run_name="__main__") assert exit_info.value.code == 0 def test_the_hook_lands_in_the_last_local_block_not_at_end_of_file(repo): """Most of these configs interleave local and remote hook repos. diet-guard, screen-locker and wake-alarm carry nine `- repo:` blocks each, and a hook appended under a remote one is handed to that repo's hook definitions -- which fails with an error naming an unknown id rather than the real problem. """ write( repo, ".pre-commit-config.yaml", """\ repos: - repo: local hooks: - id: file-length-cap entry: bash scripts/check_file_length.sh language: system - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.16.0 hooks: - id: ruff """, ) installer.install(repo) lines = (repo / ".pre-commit-config.yaml").read_text(encoding="utf-8").split("\n") hook = lines.index(" - id: dependency-freshness") remote = lines.index(" - repo: https://github.com/astral-sh/ruff-pre-commit") assert hook < remote, "the hook must sit inside the local block" assert lines[-2] == " - id: ruff", "the remote block keeps its own hooks" def test_a_config_with_no_local_block_refuses_rather_than_guessing(repo): write( repo, ".pre-commit-config.yaml", """\ repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.16.0 hooks: - id: ruff """, ) with pytest.raises(ValueError, match="no ` - repo: local` block"): installer.install(repo)