Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,45 @@ def _head(url: str, *args: object, **kwargs: object) -> SimpleNamespace:
monkeypatch.setattr(version_service.httpx, "head", _head)


@pytest.fixture(autouse=True)
def _redirect_global_state_paths(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
"""Keep auto-update bookkeeping out of the developer's real config directory.

Both writers below resolve through ``platformdirs.user_config_dir``, so
neither honours ``--config-dir``: they always address the machine's own
files. That is correct for the shipped CLI -- the version cache and a
pending update are per-machine, not per-project -- and exactly why the
test suite has to redirect them rather than the production code.

``auto_update._get_cache_path`` backs ``version_cache.json``. Any test
reaching the real ``_write_cache`` -- ``TestMaybeAutoUpdate`` has two that
mock ``_read_cache`` but not the write -- stamps the canonical fake latest
version ``1.0.0`` into it. The CLI then believes for a full
``AUTO_UPDATE_CHECK_INTERVAL`` (1 hour) that a release tag which does not
exist is available, prints an update banner on every command, and fails
the reinstall: running ``make test`` broke auto-update for whoever ran it.

``update_runner.state_dir`` backs the deferred-update marker / exit / log
files. No test reaches it today (the deferral tests mock
``request_deferred_update``, and ``should_defer()`` is False off Windows),
but it is the same latent bug: on Windows ``should_defer()`` defaults to
True, and ``report_finished_deferred_update`` -- which runs ahead of every
skip gate -- consumes and unlinks a genuine pending report while reading
it, so a developer's own update outcome would vanish into a test run.

Redirecting the module attributes (not the top-level import in
``test_auto_update.py``, which ``TestGetCachePath`` still exercises
against the real resolver) covers tests written later that forget to mock
the writer. ``test_suite_never_resolves_to_the_real_user_cache`` fails if
this fixture is removed.
"""
from keboola_agent_cli import auto_update, update_runner

cache_file = tmp_path / "version_cache.json"
monkeypatch.setattr(auto_update, "_get_cache_path", lambda: cache_file)
monkeypatch.setattr(update_runner, "state_dir", lambda: tmp_path)


@pytest.fixture
def tmp_config_dir(tmp_path: Path) -> Path:
"""Provide a temporary directory for configuration files."""
Expand Down
32 changes: 32 additions & 0 deletions tests/test_auto_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,38 @@ def test_returns_path_with_filename(self):
assert path.name == "version_cache.json"
assert "keboola-agent-cli" in str(path)

def test_suite_never_resolves_to_the_real_user_cache(self):
"""Guard for the `_redirect_global_state_paths` autouse fixture.

`_get_cache_path` resolves through `platformdirs`, so it is NOT
`--config-dir`-aware and points at the developer's own
`version_cache.json`. Two tests in `TestMaybeAutoUpdate` reach the
real `_write_cache` (they mock `_read_cache`, not the write), which
stamped the canonical fake latest version `1.0.0` into that file --
after which every `kbagent` command on the machine spent an hour
(`AUTO_UPDATE_CHECK_INTERVAL`) trying to install a release tag that
does not exist. The conftest fixture redirects the module attribute
per test; this asserts it is actually in force, so deleting the
fixture fails here instead of silently on someone's laptop.

Uses the module attribute, not the name imported at the top of this
file -- the latter is bound to the original function object and is
deliberately left unpatched for the test above.
"""
import platformdirs

from keboola_agent_cli import update_runner

real_dir = Path(platformdirs.user_config_dir("keboola-agent-cli")).resolve()
assert real_dir != auto_update_module._get_cache_path().resolve().parent
# `update_runner.state_dir` resolves through the very same call and backs
# the deferred-update marker / exit / log files. `should_defer()` is True
# by default on Windows, so a test reaching the scheduler there would
# write real state -- and `report_finished_deferred_update`, which runs
# before every skip gate, unlinks a genuine pending report while reading
# it. Same class of bug, so the fixture covers both.
assert real_dir != update_runner.state_dir().resolve()


# ---------------------------------------------------------------------------
# `kbagent changelog` does not duplicate "What's new" output
Expand Down