Files
ar9av__obsidian-wiki/tests/test_write_config_preserves_user_keys.py
Arnav 77d74e0261 feat(config): XDG-style config location, with legacy fallback (#172) (#173)
* feat(config): resolve global config from an XDG-style location (#172)

The global config lived at ~/.obsidian-wiki, which isn't XDG-compliant.
It now resolves to $XDG_CONFIG_HOME/obsidian-wiki (default
~/.config/obsidian-wiki).

Existing installs are unaffected: if ~/.obsidian-wiki is present and the
XDG path is not, the legacy directory is used as-is. No migration is
required, and nothing moves on upgrade. Once the XDG path exists it wins,
so `mv ~/.obsidian-wiki ~/.config/obsidian-wiki` is all a migration takes.

Resolution is implemented identically in Python (cli.py), bash (setup.sh,
scripts/daily-update.sh, scripts/wiki-notify.sh), and the Config
Resolution Protocol that every skill defers to, so vault-scoped state and
named `config.<name>` profiles stay in one place.

* fix(config): stop setup from wiping user-added config keys

`write_config` rewrote the global config with only the three setup-managed
keys, so every `obsidian-wiki setup` — including the one a pip upgrade
prompts for — silently destroyed anything else in the file:
OBSIDIAN_LINK_FORMAT, QMD_WIKI_COLLECTION, WIKI_SKIP_PROJECTS, sync
settings, and comments.

Setup now owns only OBSIDIAN_VAULT_PATH, OBSIDIAN_WIKI_REPO and
OBSIDIAN_WIKI_VERSION, updating those in place and carrying every other
line over untouched. Duplicate definitions of a managed key collapse to
one.

Predates the XDG change (same behavior on main) but is fixed here because
this branch already touches config writing, and the XDG work makes
re-running setup more likely.

Also pins PYTHONPATH in both new test files so they exercise this
checkout rather than whatever obsidian_wiki happens to be installed.
2026-08-16 20:40:09 -07:00

131 lines
4.1 KiB
Python

"""`setup` must not destroy user-added config keys when it re-writes the file."""
from __future__ import annotations
import os
import subprocess
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
def _setup(home: Path, vault: Path) -> subprocess.CompletedProcess[str]:
env = os.environ.copy()
env["HOME"] = str(home)
env.pop("XDG_CONFIG_HOME", None)
# Pin the import to this checkout — a separately installed obsidian_wiki
# would otherwise shadow it depending on the working directory.
env["PYTHONPATH"] = str(REPO_ROOT)
return subprocess.run(
[sys.executable, "-m", "obsidian_wiki.cli", "setup", "--vault", str(vault)],
capture_output=True,
text=True,
env=env,
cwd=str(home),
)
def _config_dir(home: Path) -> Path:
legacy = home / ".obsidian-wiki"
return legacy if legacy.is_dir() else home / ".config" / "obsidian-wiki"
def _values(path: Path) -> dict[str, str]:
values: dict[str, str] = {}
for raw in path.read_text().splitlines():
line = raw.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
values[key.strip()] = value.strip().strip('"')
return values
def test_rerunning_setup_keeps_user_added_keys(tmp_path: Path) -> None:
home = tmp_path / "home"
config_dir = home / ".config" / "obsidian-wiki"
config_dir.mkdir(parents=True)
vault = tmp_path / "vault"
vault.mkdir()
config = config_dir / "config"
config.write_text(
"# my notes config\n"
'OBSIDIAN_VAULT_PATH="/old/path"\n'
'OBSIDIAN_LINK_FORMAT="markdown"\n'
'QMD_WIKI_COLLECTION="mybrain"\n'
'WIKI_SKIP_PROJECTS="secret-thing"\n',
encoding="utf-8",
)
proc = _setup(home, vault)
assert proc.returncode == 0, proc.stderr
values = _values(config)
# User keys survive.
assert values["OBSIDIAN_LINK_FORMAT"] == "markdown"
assert values["QMD_WIKI_COLLECTION"] == "mybrain"
assert values["WIKI_SKIP_PROJECTS"] == "secret-thing"
# Managed key is updated, not duplicated.
assert values["OBSIDIAN_VAULT_PATH"] == str(vault)
body = config.read_text()
assert body.count("OBSIDIAN_VAULT_PATH=") == 1
# Comments are kept.
assert "# my notes config" in body
def test_setup_on_legacy_config_preserves_keys(tmp_path: Path) -> None:
"""The same guarantee must hold for the pre-XDG config location."""
home = tmp_path / "home"
legacy = home / ".obsidian-wiki"
legacy.mkdir(parents=True)
vault = tmp_path / "vault"
vault.mkdir()
config = legacy / "config"
config.write_text('OBSIDIAN_VAULT_PATH="/old"\nOBSIDIAN_LINK_FORMAT="markdown"\n', encoding="utf-8")
proc = _setup(home, vault)
assert proc.returncode == 0, proc.stderr
assert _config_dir(home) == legacy
values = _values(config)
assert values["OBSIDIAN_LINK_FORMAT"] == "markdown"
assert values["OBSIDIAN_VAULT_PATH"] == str(vault)
def test_setup_writes_managed_keys_on_a_fresh_config(tmp_path: Path) -> None:
home = tmp_path / "home"
home.mkdir()
vault = tmp_path / "vault"
vault.mkdir()
proc = _setup(home, vault)
assert proc.returncode == 0, proc.stderr
values = _values(_config_dir(home) / "config")
assert values["OBSIDIAN_VAULT_PATH"] == str(vault)
assert values["OBSIDIAN_WIKI_REPO"]
assert values["OBSIDIAN_WIKI_VERSION"]
def test_setup_collapses_duplicate_managed_keys(tmp_path: Path) -> None:
home = tmp_path / "home"
config_dir = home / ".config" / "obsidian-wiki"
config_dir.mkdir(parents=True)
vault = tmp_path / "vault"
vault.mkdir()
config = config_dir / "config"
config.write_text(
'OBSIDIAN_VAULT_PATH="/one"\nOBSIDIAN_LINK_FORMAT="markdown"\nOBSIDIAN_VAULT_PATH="/two"\n',
encoding="utf-8",
)
proc = _setup(home, vault)
assert proc.returncode == 0, proc.stderr
body = config.read_text()
assert body.count("OBSIDIAN_VAULT_PATH=") == 1
assert _values(config)["OBSIDIAN_VAULT_PATH"] == str(vault)
assert _values(config)["OBSIDIAN_LINK_FORMAT"] == "markdown"