|
@@ -115,17 +115,16 @@ async def test_get_public_key(tmp_path):
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_detect_branch_from_git_head(tmp_path):
|
|
def test_detect_branch_from_git_head(tmp_path):
|
|
|
- """Read branch directly from .git/HEAD — no subprocess."""
|
|
|
|
|
|
|
+ """Read branch directly from .git/HEAD in the application root — no subprocess."""
|
|
|
git_dir = tmp_path / ".git"
|
|
git_dir = tmp_path / ".git"
|
|
|
git_dir.mkdir()
|
|
git_dir.mkdir()
|
|
|
(git_dir / "HEAD").write_text("ref: refs/heads/dev\n")
|
|
(git_dir / "HEAD").write_text("ref: refs/heads/dev\n")
|
|
|
|
|
|
|
|
with (
|
|
with (
|
|
|
- patch("backend.app.services.spoolbuddy_ssh.settings") as mock_settings,
|
|
|
|
|
|
|
+ patch("backend.app.services.spoolbuddy_ssh._APP_DIR", tmp_path),
|
|
|
patch("asyncio.create_subprocess_exec") as mock_exec,
|
|
patch("asyncio.create_subprocess_exec") as mock_exec,
|
|
|
patch("subprocess.run") as mock_run,
|
|
patch("subprocess.run") as mock_run,
|
|
|
):
|
|
):
|
|
|
- mock_settings.base_dir = tmp_path
|
|
|
|
|
assert detect_current_branch() == "dev"
|
|
assert detect_current_branch() == "dev"
|
|
|
# Regression guard: must not shell out (fails with getpwuid under
|
|
# Regression guard: must not shell out (fails with getpwuid under
|
|
|
# arbitrary Docker PUIDs if ever reintroduced).
|
|
# arbitrary Docker PUIDs if ever reintroduced).
|
|
@@ -133,6 +132,36 @@ def test_detect_branch_from_git_head(tmp_path):
|
|
|
mock_run.assert_not_called()
|
|
mock_run.assert_not_called()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def test_detect_branch_uses_app_dir_not_data_dir(tmp_path):
|
|
|
|
|
+ """Branch detection must look in the application root, not the data dir.
|
|
|
|
|
+
|
|
|
|
|
+ Regression guard for the Docker bug where `.git` was being looked up in
|
|
|
|
|
+ `settings.base_dir` (which is `DATA_DIR=/app/data` in Docker), so it was
|
|
|
|
|
+ never found and the fallback always returned "main" — even when the user
|
|
|
|
|
+ was on a feature branch bind-mounted at `/app`.
|
|
|
|
|
+ """
|
|
|
|
|
+ app_dir = tmp_path / "app"
|
|
|
|
|
+ data_dir = tmp_path / "app" / "data"
|
|
|
|
|
+ app_dir.mkdir()
|
|
|
|
|
+ data_dir.mkdir()
|
|
|
|
|
+
|
|
|
|
|
+ # Real .git lives at the application root (bind-mount style).
|
|
|
|
|
+ (app_dir / ".git").mkdir()
|
|
|
|
|
+ (app_dir / ".git" / "HEAD").write_text("ref: refs/heads/dev\n")
|
|
|
|
|
+
|
|
|
|
|
+ # Decoy .git in the data dir — if the code ever regresses to reading
|
|
|
|
|
+ # from settings.base_dir, this would be returned instead.
|
|
|
|
|
+ (data_dir / ".git").mkdir()
|
|
|
|
|
+ (data_dir / ".git" / "HEAD").write_text("ref: refs/heads/wrong-branch\n")
|
|
|
|
|
+
|
|
|
|
|
+ with (
|
|
|
|
|
+ patch("backend.app.services.spoolbuddy_ssh._APP_DIR", app_dir),
|
|
|
|
|
+ patch("backend.app.services.spoolbuddy_ssh.settings") as mock_settings,
|
|
|
|
|
+ ):
|
|
|
|
|
+ mock_settings.base_dir = data_dir
|
|
|
|
|
+ assert detect_current_branch() == "dev"
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def test_detect_branch_worktree_gitdir_file(tmp_path):
|
|
def test_detect_branch_worktree_gitdir_file(tmp_path):
|
|
|
"""Git worktrees store a `gitdir:` pointer instead of a dir — follow it."""
|
|
"""Git worktrees store a `gitdir:` pointer instead of a dir — follow it."""
|
|
|
real_git_dir = tmp_path / "real-git"
|
|
real_git_dir = tmp_path / "real-git"
|
|
@@ -140,8 +169,7 @@ def test_detect_branch_worktree_gitdir_file(tmp_path):
|
|
|
(real_git_dir / "HEAD").write_text("ref: refs/heads/feature-x\n")
|
|
(real_git_dir / "HEAD").write_text("ref: refs/heads/feature-x\n")
|
|
|
(tmp_path / ".git").write_text(f"gitdir: {real_git_dir}\n")
|
|
(tmp_path / ".git").write_text(f"gitdir: {real_git_dir}\n")
|
|
|
|
|
|
|
|
- with patch("backend.app.services.spoolbuddy_ssh.settings") as mock_settings:
|
|
|
|
|
- mock_settings.base_dir = tmp_path
|
|
|
|
|
|
|
+ with patch("backend.app.services.spoolbuddy_ssh._APP_DIR", tmp_path):
|
|
|
assert detect_current_branch() == "feature-x"
|
|
assert detect_current_branch() == "feature-x"
|
|
|
|
|
|
|
|
|
|
|
|
@@ -152,28 +180,25 @@ def test_detect_branch_detached_head_falls_back(tmp_path):
|
|
|
(git_dir / "HEAD").write_text("deadbeef1234\n")
|
|
(git_dir / "HEAD").write_text("deadbeef1234\n")
|
|
|
|
|
|
|
|
with (
|
|
with (
|
|
|
- patch("backend.app.services.spoolbuddy_ssh.settings") as mock_settings,
|
|
|
|
|
|
|
+ patch("backend.app.services.spoolbuddy_ssh._APP_DIR", tmp_path),
|
|
|
patch.dict(os.environ, {"GIT_BRANCH": "release"}),
|
|
patch.dict(os.environ, {"GIT_BRANCH": "release"}),
|
|
|
):
|
|
):
|
|
|
- mock_settings.base_dir = tmp_path
|
|
|
|
|
assert detect_current_branch() == "release"
|
|
assert detect_current_branch() == "release"
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_detect_branch_env_fallback(tmp_path):
|
|
def test_detect_branch_env_fallback(tmp_path):
|
|
|
with (
|
|
with (
|
|
|
- patch("backend.app.services.spoolbuddy_ssh.settings") as mock_settings,
|
|
|
|
|
|
|
+ patch("backend.app.services.spoolbuddy_ssh._APP_DIR", tmp_path),
|
|
|
patch.dict(os.environ, {"GIT_BRANCH": "staging"}),
|
|
patch.dict(os.environ, {"GIT_BRANCH": "staging"}),
|
|
|
):
|
|
):
|
|
|
- mock_settings.base_dir = tmp_path
|
|
|
|
|
assert detect_current_branch() == "staging"
|
|
assert detect_current_branch() == "staging"
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_detect_branch_default_main(tmp_path):
|
|
def test_detect_branch_default_main(tmp_path):
|
|
|
with (
|
|
with (
|
|
|
- patch("backend.app.services.spoolbuddy_ssh.settings") as mock_settings,
|
|
|
|
|
|
|
+ patch("backend.app.services.spoolbuddy_ssh._APP_DIR", tmp_path),
|
|
|
patch.dict(os.environ, {}, clear=True),
|
|
patch.dict(os.environ, {}, clear=True),
|
|
|
):
|
|
):
|
|
|
- mock_settings.base_dir = tmp_path
|
|
|
|
|
# Remove GIT_BRANCH if present
|
|
# Remove GIT_BRANCH if present
|
|
|
os.environ.pop("GIT_BRANCH", None)
|
|
os.environ.pop("GIT_BRANCH", None)
|
|
|
assert detect_current_branch() == "main"
|
|
assert detect_current_branch() == "main"
|