123 lines
4.5 KiB
Python
123 lines
4.5 KiB
Python
"""Tests for config migration on the \"Already up to date\" repair path.
|
|
|
|
Covers ``_check_and_apply_config_migration`` on the ``commit_count == 0``
|
|
retry paths (#91360): a previous update attempt can pull new code onto disk
|
|
and then fail before reaching the config-migration block (e.g. PyPI timeout
|
|
during dependency sync); the retry then enters the ``commit_count == 0``
|
|
branch and returns early, skipping config migration entirely. The fresh
|
|
code (which may require a newer ``_config_version``) keeps running against
|
|
the old config and the next Hermes launch refuses to start.
|
|
|
|
Matrix: config behind / current / ahead of the code's version, plus the
|
|
#86656 contract (quiet-migration warnings must be re-surfaced) and the
|
|
check-failure contract (a config-check failure must not break the repair
|
|
path).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import io
|
|
from unittest.mock import patch
|
|
|
|
import hermes_cli.update_cmd as update_cmd
|
|
|
|
|
|
def _run(current: int, latest: int):
|
|
"""Run _check_and_apply_config_migration with mocked config checks.
|
|
|
|
Returns (stdout, migrate_calls).
|
|
"""
|
|
migrate_calls = []
|
|
|
|
def _fake_migrate(interactive=False, quiet=False):
|
|
migrate_calls.append((interactive, quiet))
|
|
return {"env_added": [], "config_added": [], "warnings": []}
|
|
|
|
with patch.object(update_cmd, "_reload_config_modules"), patch(
|
|
"hermes_cli.config.get_missing_env_vars", return_value=[]
|
|
), patch(
|
|
"hermes_cli.config.get_missing_config_fields", return_value=[]
|
|
), patch.object(
|
|
update_cmd, "_run_config_check_fresh", return_value=(current, latest)
|
|
), patch.object(
|
|
update_cmd, "_run_migrate_config_fresh", side_effect=_fake_migrate
|
|
), patch.object(
|
|
update_cmd, "_migrate_sibling_profile_configs", return_value=[]
|
|
):
|
|
buf = io.StringIO()
|
|
with contextlib.redirect_stdout(buf):
|
|
update_cmd._check_and_apply_config_migration()
|
|
return buf.getvalue(), migrate_calls
|
|
|
|
|
|
def test_migrates_when_config_behind():
|
|
"""Version bump on the repair path must be applied silently."""
|
|
out, calls = _run(current=37, latest=38)
|
|
assert "v37 → v38" in out
|
|
assert "Config format updated" in out
|
|
assert calls == [(False, True)] # non-interactive, quiet
|
|
|
|
|
|
def test_noop_when_config_current():
|
|
"""No migration when the config version is already current."""
|
|
out, calls = _run(current=38, latest=38)
|
|
assert "Configuration is up to date" in out
|
|
assert calls == []
|
|
|
|
|
|
def test_noop_when_config_ahead():
|
|
"""No migration when local config is newer than the code's default."""
|
|
out, calls = _run(current=39, latest=38)
|
|
assert "Configuration is up to date" in out
|
|
assert calls == []
|
|
|
|
|
|
def test_surfaces_migration_warnings():
|
|
"""Warnings from a quiet migration must be re-surfaced (#86656)."""
|
|
|
|
def _fake_migrate(interactive=False, quiet=False):
|
|
return {
|
|
"env_added": [],
|
|
"config_added": [],
|
|
"warnings": ["personality reset: kawaii → default"],
|
|
}
|
|
|
|
with patch.object(update_cmd, "_reload_config_modules"), patch(
|
|
"hermes_cli.config.get_missing_env_vars", return_value=[]
|
|
), patch(
|
|
"hermes_cli.config.get_missing_config_fields", return_value=[]
|
|
), patch.object(
|
|
update_cmd, "_run_config_check_fresh", return_value=(37, 38)
|
|
), patch.object(
|
|
update_cmd, "_run_migrate_config_fresh", side_effect=_fake_migrate
|
|
), patch.object(
|
|
update_cmd, "_migrate_sibling_profile_configs", return_value=[]
|
|
):
|
|
buf = io.StringIO()
|
|
with contextlib.redirect_stdout(buf):
|
|
update_cmd._check_and_apply_config_migration()
|
|
out = buf.getvalue()
|
|
|
|
assert "personality reset" in out
|
|
|
|
|
|
def test_check_failure_does_not_break_repair_path():
|
|
"""A config-check failure must not break the repair path."""
|
|
with patch.object(update_cmd, "_reload_config_modules"), patch(
|
|
"hermes_cli.config.get_missing_env_vars", return_value=[]
|
|
), patch(
|
|
"hermes_cli.config.get_missing_config_fields", return_value=[]
|
|
), patch.object(
|
|
update_cmd, "_run_config_check_fresh", side_effect=RuntimeError("boom")
|
|
), patch.object(
|
|
update_cmd, "_run_migrate_config_fresh", return_value={}
|
|
) as mig:
|
|
buf = io.StringIO()
|
|
with contextlib.redirect_stdout(buf):
|
|
# Should not raise and should not attempt migration.
|
|
update_cmd._check_and_apply_config_migration()
|
|
out = buf.getvalue()
|
|
assert "Could not check config version" in out
|
|
mig.assert_not_called()
|