diff --git a/aegis/core/manual_updater.py b/aegis/core/manual_updater.py index 66398bec..d31087e7 100644 --- a/aegis/core/manual_updater.py +++ b/aegis/core/manual_updater.py @@ -986,9 +986,12 @@ def _regenerate_shared_files( verbose_print(f" • {var_name}={var_value}") elif policy.get("warn"): - # Only warn if file actually has changes that need manual merge - existing_content = output_path.read_text() - if content != existing_content: + if self._shared_file_is_pristine(output_path, template_file): + self._write_rendered(output_path, content) + verbose_print(f" Updated: {shared_file}") + shared_files_updated.append(shared_file) + # Only warn if a diverged file has changes that need manual merge. + elif content != output_path.read_text(): print(f" Manual merge required: {shared_file}") shared_files_need_manual_merge.append(shared_file) diff --git a/tests/cli/test_issue_870_warn_only_regeneration.py b/tests/cli/test_issue_870_warn_only_regeneration.py new file mode 100644 index 00000000..94b10b28 --- /dev/null +++ b/tests/cli/test_issue_870_warn_only_regeneration.py @@ -0,0 +1,81 @@ +"""Regression tests for issue #870 — stale warn-only Dockerfiles.""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from aegis.core import manual_updater +from aegis.core.manual_updater import ManualUpdater + + +@pytest.mark.parametrize( + ("before", "after", "expected"), + [(False, True, "css-build"), (True, False, "FROM python")], +) +def test_htmx_change_regenerates_pristine_warn_only_dockerfile( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + before: bool, + after: bool, + expected: str, +) -> None: + dockerfile = tmp_path / "Dockerfile" + renders = { + False: "FROM python:3.13\n", + True: "FROM node:22-alpine AS css-build\nFROM python:3.13\n", + } + dockerfile.write_text(renders[before]) + updater = ManualUpdater.__new__(ManualUpdater) + updater.project_path = tmp_path + updater.answers = {"include_htmx": before} + monkeypatch.setattr( + updater, + "_render_template_file", + lambda _template, answers: renders[answers["include_htmx"]], + ) + monkeypatch.setattr( + manual_updater, + "SHARED_TEMPLATE_FILES", + {"Dockerfile": {"overwrite": False, "backup": False, "warn": True}}, + ) + + updated, _, need_merge = updater._regenerate_shared_files({"include_htmx": after}) + + assert expected in dockerfile.read_text() + assert dockerfile.read_text() == renders[after] + assert "Dockerfile" in updated + assert "Dockerfile" not in need_merge + + +def test_htmx_change_preserves_custom_dockerfile( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + dockerfile = tmp_path / "Dockerfile" + dockerfile.write_text("FROM python:3.13\nRUN custom-build-step\n") + before = dockerfile.read_text() + updater = ManualUpdater.__new__(ManualUpdater) + updater.project_path = tmp_path + updater.answers = {"include_htmx": False} + monkeypatch.setattr( + updater, + "_render_template_file", + lambda _template, answers: ( + "FROM node:22-alpine AS css-build\nFROM python:3.13\n" + if answers["include_htmx"] + else "FROM python:3.13\n" + ), + ) + monkeypatch.setattr( + manual_updater, + "SHARED_TEMPLATE_FILES", + {"Dockerfile": {"overwrite": False, "backup": False, "warn": True}}, + ) + + updated, _, need_merge = updater._regenerate_shared_files({"include_htmx": True}) + + assert dockerfile.read_text() == before + assert "Dockerfile" not in updated + assert "Dockerfile" in need_merge