Files
mcp-nextcloud/tests/unit/test_config_paths.py
T
Chris CoutinhoandClaude Opus 4.6 512de1f6b0 test: address PR #707 reviewer feedback on config path helpers
- _resolve_settings_files() now raises FileNotFoundError when
  NEXTCLOUD_MCP_SETTINGS_FILE points to a missing file, instead of
  silently falling back to defaults (footgun on typos).
- .secrets.toml is now looked for alongside the explicit settings file
  when NEXTCLOUD_MCP_SETTINGS_FILE is set, matching user expectation for
  /etc-style deployments. Unset behaviour (cwd lookup) is unchanged.
- get_token_db_path() drops the redundant os.environ.get() short-circuit;
  TOKEN_STORAGE_DB is already bound through dynaconf because the key is
  declared in _DEFAULTS.
- is_ephemeral_token_db() docstring documents the "must call
  get_token_db_path() first" precondition.
- alembic.ini comment clarifies the ./tokens.db placeholder is cwd-relative
  by design and points readers at the -x database_url escape hatch.
- New tests/unit/test_config_paths.py (12 tests) covering the ephemeral
  tempfile lifecycle, the TOKEN_STORAGE_DB override path, and all six
  _resolve_settings_files() cases including the two new behaviours.

Full unit suite now at 476 passed (464 + 12 new). Ruff + ty clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 21:14:42 +02:00

159 lines
5.9 KiB
Python

"""Tests for config.py path resolution helpers added in PR #707.
Covers:
- get_token_db_path() / is_ephemeral_token_db() — ephemeral tempfile default
with TOKEN_STORAGE_DB override.
- _resolve_settings_files() — optional external settings file discovery,
including the NEXTCLOUD_MCP_SETTINGS_FILE env var and its colocation
semantics for .secrets.toml.
"""
import os
import tempfile
from pathlib import Path
import pytest
import nextcloud_mcp_server.config as cfg
from nextcloud_mcp_server.config import (
_reload_config,
_resolve_settings_files,
get_token_db_path,
is_ephemeral_token_db,
)
@pytest.fixture(autouse=True)
def _reset_ephemeral_state(monkeypatch):
"""Reset module-global ephemeral tempfile state between tests.
get_token_db_path() memoizes its result in a module-level global and
registers an atexit hook for cleanup. Tests need an isolated slate so
assertions about "already allocated" vs "not yet" are meaningful.
"""
old = cfg._ephemeral_db_path
cfg._ephemeral_db_path = None
monkeypatch.delenv("TOKEN_STORAGE_DB", raising=False)
monkeypatch.delenv("NEXTCLOUD_MCP_SETTINGS_FILE", raising=False)
_reload_config()
yield
if cfg._ephemeral_db_path and os.path.exists(cfg._ephemeral_db_path):
os.unlink(cfg._ephemeral_db_path)
cfg._ephemeral_db_path = old
_reload_config()
class TestGetTokenDbPath:
def test_explicit_env_var_returned(self, monkeypatch, tmp_path):
target = tmp_path / "explicit.db"
monkeypatch.setenv("TOKEN_STORAGE_DB", str(target))
_reload_config()
assert get_token_db_path() == str(target)
# No tempfile should have been allocated since we took the explicit
# branch.
assert cfg._ephemeral_db_path is None
def test_ephemeral_tempfile_when_unset(self):
path = get_token_db_path()
assert path.startswith(tempfile.gettempdir())
assert f"nextcloud-mcp-tokens-{os.getpid()}-" in os.path.basename(path)
assert path.endswith(".db")
assert os.path.exists(path)
def test_ephemeral_tempfile_is_memoized(self):
first = get_token_db_path()
second = get_token_db_path()
assert first == second
# Only the memoized file should exist — no stray siblings.
parent = Path(first).parent
matches = list(parent.glob(f"nextcloud-mcp-tokens-{os.getpid()}-*.db"))
assert matches == [Path(first)]
def test_is_ephemeral_token_db_detects_allocated_path(self):
path = get_token_db_path()
assert is_ephemeral_token_db(path) is True
assert is_ephemeral_token_db("/some/other/path") is False
def test_is_ephemeral_token_db_before_allocation(self):
# The autouse fixture reset _ephemeral_db_path to None; do not call
# get_token_db_path() first. Nothing is allocated yet.
assert cfg._ephemeral_db_path is None
assert is_ephemeral_token_db("/any/path") is False
assert is_ephemeral_token_db("") is False
def test_explicit_path_does_not_trigger_tempfile(self, monkeypatch, tmp_path):
"""Regression guard: the explicit branch must short-circuit cleanly."""
monkeypatch.setenv("TOKEN_STORAGE_DB", str(tmp_path / "pinned.db"))
_reload_config()
get_token_db_path()
# Nothing under the tempfile prefix should have been created.
matches = list(
Path(tempfile.gettempdir()).glob(f"nextcloud-mcp-tokens-{os.getpid()}-*.db")
)
assert matches == []
class TestResolveSettingsFiles:
def test_empty_list_when_nothing_present(self, monkeypatch, tmp_path):
monkeypatch.chdir(tmp_path)
assert _resolve_settings_files() == []
def test_picks_up_cwd_settings(self, monkeypatch, tmp_path):
(tmp_path / "settings.toml").write_text("[default]\n")
monkeypatch.chdir(tmp_path)
result = _resolve_settings_files()
assert str(tmp_path / "settings.toml") in result
def test_picks_up_cwd_secrets(self, monkeypatch, tmp_path):
(tmp_path / ".secrets.toml").write_text("[default]\n")
monkeypatch.chdir(tmp_path)
result = _resolve_settings_files()
assert str(tmp_path / ".secrets.toml") in result
def test_explicit_settings_file_included(self, monkeypatch, tmp_path):
explicit = tmp_path / "nested" / "my-settings.toml"
explicit.parent.mkdir()
explicit.write_text("[default]\n")
monkeypatch.setenv("NEXTCLOUD_MCP_SETTINGS_FILE", str(explicit))
# cwd has no settings.toml / .secrets.toml
monkeypatch.chdir(tmp_path)
result = _resolve_settings_files()
assert result == [str(explicit)]
def test_explicit_settings_file_secrets_colocated(self, monkeypatch, tmp_path):
"""PR #707 reviewer feedback: .secrets.toml should live beside
the explicit settings file, not always in cwd."""
config_dir = tmp_path / "etc"
config_dir.mkdir()
explicit = config_dir / "settings.toml"
explicit.write_text("[default]\n")
secrets = config_dir / ".secrets.toml"
secrets.write_text("[default]\n")
monkeypatch.setenv("NEXTCLOUD_MCP_SETTINGS_FILE", str(explicit))
# cwd is elsewhere and deliberately contains *no* secrets file
monkeypatch.chdir(tmp_path)
result = _resolve_settings_files()
assert str(explicit) in result
assert str(secrets) in result
def test_explicit_missing_raises(self, monkeypatch, tmp_path):
"""PR #707 reviewer feedback: missing explicit path must not be
silently ignored — users will think their config is applied when
it isn't."""
missing = tmp_path / "does-not-exist.toml"
monkeypatch.setenv("NEXTCLOUD_MCP_SETTINGS_FILE", str(missing))
monkeypatch.chdir(tmp_path)
with pytest.raises(FileNotFoundError, match="does-not-exist.toml"):
_resolve_settings_files()