mirror of
https://github.com/Ar9av/obsidian-wiki.git
synced 2026-09-14 20:36:34 +08:00
ea4508114e
setup.sh had an interactive git-sync flow (git init, .gitignore, remote, a generated sync.sh, optional cron) that `obsidian-wiki setup` — the entry point pip/uv installs actually run — never had, so those installs silently skipped it and .env.example documented a var nothing ever read. Move the git plumbing into obsidian_wiki/sync.py (configure_sync/run_sync/ get_remote) and expose it as `obsidian-wiki sync-setup <url>` and `obsidian-wiki sync`. `obsidian-wiki setup` now offers the same interactive prompt setup.sh always had (or accepts --remote non-interactively), and setup.sh's own prompt delegates into the same module via the CLI instead of re-implementing it. Sync status is read live from the vault's own `git remote` (via `obsidian-wiki info`) rather than a config var that could drift.
138 lines
4.7 KiB
Python
138 lines
4.7 KiB
Python
"""Tests for GitHub vault sync (obsidian_wiki/sync.py).
|
|
|
|
Regression coverage for issue #153: setup.sh had a git-sync flow the pip/uv
|
|
CLI (`obsidian-wiki setup`) never got, so pip/uv installs silently skipped it.
|
|
sync.py is now the single implementation both entrypoints call into.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from obsidian_wiki.sync import configure_sync, get_remote, run_sync
|
|
|
|
|
|
def _git(repo_dir: Path, *args: str) -> subprocess.CompletedProcess:
|
|
return subprocess.run(["git", "-C", str(repo_dir), *args], check=True,
|
|
capture_output=True, text=True)
|
|
|
|
|
|
@pytest.fixture
|
|
def vault(tmp_path):
|
|
v = tmp_path / "vault"
|
|
v.mkdir()
|
|
return v
|
|
|
|
|
|
@pytest.fixture
|
|
def remote_repo(tmp_path):
|
|
"""A bare repo to act as a real push target."""
|
|
r = tmp_path / "remote.git"
|
|
subprocess.run(["git", "init", "-q", "--bare", str(r)], check=True)
|
|
return r
|
|
|
|
|
|
class TestGetRemote:
|
|
def test_not_a_git_repo(self, vault):
|
|
assert get_remote(vault) is None
|
|
|
|
def test_no_origin_set(self, vault):
|
|
_git(vault, "init", "-q")
|
|
assert get_remote(vault) is None
|
|
|
|
def test_returns_configured_remote(self, vault):
|
|
_git(vault, "init", "-q")
|
|
_git(vault, "remote", "add", "origin", "https://example.com/x.git")
|
|
assert get_remote(vault) == "https://example.com/x.git"
|
|
|
|
|
|
class TestConfigureSync:
|
|
def test_missing_vault_raises(self, tmp_path):
|
|
with pytest.raises(FileNotFoundError):
|
|
configure_sync(tmp_path / "nope", "https://example.com/x.git")
|
|
|
|
def test_blank_remote_raises(self, vault):
|
|
with pytest.raises(ValueError):
|
|
configure_sync(vault, " ")
|
|
|
|
def test_inits_git_repo(self, vault):
|
|
configure_sync(vault, "https://example.com/x.git")
|
|
assert (vault / ".git").is_dir()
|
|
|
|
def test_does_not_reinit_existing_repo(self, vault):
|
|
_git(vault, "init", "-q")
|
|
(vault / "existing.md").write_text("keep me")
|
|
_git(vault, "add", "-A")
|
|
_git(vault, "commit", "-q", "-m", "seed")
|
|
configure_sync(vault, "https://example.com/x.git")
|
|
log = _git(vault, "log", "--oneline").stdout
|
|
assert "seed" in log
|
|
|
|
def test_writes_gitignore(self, vault):
|
|
configure_sync(vault, "https://example.com/x.git")
|
|
content = (vault / ".gitignore").read_text()
|
|
assert ".obsidian/workspace.json" in content
|
|
assert ".trash/" in content
|
|
|
|
def test_does_not_overwrite_existing_gitignore(self, vault):
|
|
vault.mkdir(exist_ok=True)
|
|
(vault / ".gitignore").write_text("custom-rule/\n")
|
|
configure_sync(vault, "https://example.com/x.git")
|
|
assert (vault / ".gitignore").read_text() == "custom-rule/\n"
|
|
|
|
def test_sets_remote(self, vault):
|
|
configure_sync(vault, "https://example.com/x.git")
|
|
assert get_remote(vault) == "https://example.com/x.git"
|
|
|
|
def test_updates_existing_remote(self, vault):
|
|
configure_sync(vault, "https://example.com/old.git")
|
|
configure_sync(vault, "https://example.com/new.git")
|
|
assert get_remote(vault) == "https://example.com/new.git"
|
|
|
|
def test_returns_confirmation_messages(self, vault):
|
|
messages = configure_sync(vault, "https://example.com/x.git")
|
|
joined = " ".join(messages)
|
|
assert "Initialized git repo" in joined
|
|
assert "https://example.com/x.git" in joined
|
|
|
|
|
|
class TestRunSync:
|
|
def test_vault_missing(self, tmp_path):
|
|
code, message = run_sync(tmp_path / "nope")
|
|
assert code == 1
|
|
assert "not found" in message
|
|
|
|
def test_not_a_git_repo(self, vault):
|
|
code, message = run_sync(vault)
|
|
assert code == 1
|
|
assert "sync-setup" in message
|
|
|
|
def test_nothing_to_commit(self, vault):
|
|
_git(vault, "init", "-q")
|
|
code, message = run_sync(vault)
|
|
assert code == 0
|
|
assert "nothing to commit" in message
|
|
|
|
def test_commits_and_pushes(self, vault, remote_repo):
|
|
configure_sync(vault, str(remote_repo))
|
|
_git(vault, "config", "user.email", "test@example.com")
|
|
_git(vault, "config", "user.name", "Test")
|
|
(vault / "note.md").write_text("hello")
|
|
code, message = run_sync(vault)
|
|
assert code == 0
|
|
assert "pushed to" in message
|
|
log = _git(vault, "log", "--oneline").stdout
|
|
assert "sync " in log
|
|
|
|
def test_second_run_with_no_changes_is_clean(self, vault, remote_repo):
|
|
configure_sync(vault, str(remote_repo))
|
|
_git(vault, "config", "user.email", "test@example.com")
|
|
_git(vault, "config", "user.name", "Test")
|
|
(vault / "note.md").write_text("hello")
|
|
run_sync(vault)
|
|
code, message = run_sync(vault)
|
|
assert code == 0
|
|
assert "nothing to commit" in message
|