"""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) def test_utils_own_checker_is_never_replaced_by_the_delegate(repo): """The one repo whose copy is meant to differ. utils holds the real checker at the delegate's path. Overwriting it with the shim makes the gate exec itself, because the shim resolves the shared gate to exactly the file it just replaced. """ real = "#!/bin/bash\n# the real checker, not a shim\n" write(repo, "dep_freshness/check.py", "# the shared implementation\n") write(repo, "scripts/check_dependency_freshness.sh", real) assert "scripts/check_dependency_freshness.sh" not in installer.plan(repo) installer.install(repo) assert (repo / "scripts/check_dependency_freshness.sh").read_text( encoding="utf-8" ) == real