118 lines
4.0 KiB
Python
118 lines
4.0 KiB
Python
"""Behavior pins for the shared migration secret ``.env`` writer.
|
|
|
|
Complements ``test_secret_file_perms.py`` (which pins mode/atomicity/error
|
|
propagation through the full migrators): these tests cover the two
|
|
compatibility guarantees the consolidated writer adds — symlinked ``.env``
|
|
files are written through (the link survives), and a writable ``.env``
|
|
inside a non-writable directory falls back to an in-place rewrite instead
|
|
of failing the migration partway through.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import stat
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from opensquilla.migration.env_file import write_secret_env_file
|
|
|
|
|
|
def test_write_secret_env_file_writes_through_symlink(tmp_path: Path) -> None:
|
|
real = tmp_path / "dotfiles" / "env-store"
|
|
real.parent.mkdir()
|
|
real.write_text("EXISTING_KEY=keep-me\n", encoding="utf-8")
|
|
link = tmp_path / ".env"
|
|
link.symlink_to(real)
|
|
|
|
write_secret_env_file(link, ["EXISTING_KEY=keep-me", "NEW_KEY=synthetic"])
|
|
|
|
# The dotfiles-managed link must survive and the target must receive
|
|
# the merged content — replacing the link with a regular file silently
|
|
# disconnects the user's env store.
|
|
assert link.is_symlink()
|
|
content = real.read_text(encoding="utf-8")
|
|
assert "EXISTING_KEY=keep-me" in content
|
|
assert "NEW_KEY=synthetic" in content
|
|
if os.name != "nt":
|
|
assert stat.S_IMODE(os.stat(real).st_mode) == 0o600
|
|
|
|
|
|
@pytest.mark.skipif(os.name == "nt", reason="POSIX permission bits are meaningless on Windows")
|
|
@pytest.mark.skipif(
|
|
hasattr(os, "geteuid") and os.geteuid() == 0, reason="root bypasses file modes"
|
|
)
|
|
def test_write_secret_env_file_falls_back_when_directory_not_writable(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
home = tmp_path / "opensquilla-home"
|
|
home.mkdir()
|
|
env_path = home / ".env"
|
|
env_path.write_text("OLD_KEY=old\n", encoding="utf-8")
|
|
os.chmod(home, 0o555) # directory read/execute only; file stays writable
|
|
try:
|
|
write_secret_env_file(env_path, ["OLD_KEY=old", "NEW_KEY=synthetic"])
|
|
finally:
|
|
os.chmod(home, 0o755)
|
|
|
|
content = env_path.read_text(encoding="utf-8")
|
|
assert "NEW_KEY=synthetic" in content
|
|
assert "OLD_KEY=old" in content
|
|
|
|
|
|
@pytest.mark.skipif(os.name == "nt", reason="POSIX permission bits are meaningless on Windows")
|
|
@pytest.mark.skipif(
|
|
hasattr(os, "geteuid") and os.geteuid() == 0, reason="root bypasses file modes"
|
|
)
|
|
def test_write_secret_env_file_unwritable_dir_without_file_still_raises(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
home = tmp_path / "opensquilla-home"
|
|
home.mkdir()
|
|
os.chmod(home, 0o555)
|
|
try:
|
|
with pytest.raises(PermissionError):
|
|
write_secret_env_file(home / ".env", ["KEY=synthetic"])
|
|
finally:
|
|
os.chmod(home, 0o755)
|
|
|
|
|
|
def test_openclaw_migration_preserves_symlinked_env(
|
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
"""End-to-end: a dotfiles-managed ~/.opensquilla/.env symlink survives a
|
|
secrets migration, with the merged content written through the link."""
|
|
from opensquilla.migration.openclaw import MigrationOptions, OpenClawMigrator
|
|
|
|
source = tmp_path / ".openclaw"
|
|
source.mkdir()
|
|
(source / "openclaw.json").write_text(
|
|
json.dumps({"agents": {"defaults": {}}}), encoding="utf-8"
|
|
)
|
|
(source / ".env").write_text("OPENAI_API_KEY=sk-dummy-openclaw\n", encoding="utf-8")
|
|
|
|
home = tmp_path / "opensquilla-home"
|
|
home.mkdir()
|
|
monkeypatch.setenv("OPENSQUILLA_STATE_DIR", str(home))
|
|
monkeypatch.delenv("OPENSQUILLA_GATEWAY_CONFIG_PATH", raising=False)
|
|
|
|
real = tmp_path / "dotfiles-env"
|
|
real.write_text("EXISTING_KEY=keep-me\n", encoding="utf-8")
|
|
(home / ".env").symlink_to(real)
|
|
|
|
OpenClawMigrator(
|
|
MigrationOptions(
|
|
source=source,
|
|
config_path=tmp_path / "config.toml",
|
|
apply=True,
|
|
migrate_secrets=True,
|
|
)
|
|
).migrate()
|
|
|
|
assert (home / ".env").is_symlink()
|
|
content = real.read_text(encoding="utf-8")
|
|
assert "EXISTING_KEY=keep-me" in content
|
|
assert "OPENAI_API_KEY=sk-dummy-openclaw" in content
|