fix: comprehensive default profile export exclusions and import guard

- Add _DEFAULT_EXPORT_EXCLUDE_ROOT constant with 25+ entries to exclude
  from default profile exports: repo checkout (hermes-agent), worktrees,
  databases (state.db), caches, runtime state, logs, binaries
- Add _default_export_ignore() with root-level and universal exclusions
  (__pycache__, *.sock, *.tmp at any depth)
- Remove redundant shutil/tempfile imports from contributor's if-block
- Block import_profile() from accepting 'default' as target name with
  clear guidance to use --name
- Add 7 tests covering: archive creation, inclusion of profile data,
  exclusion of infrastructure, nested __pycache__ exclusion, import
  rejection without --name, import rejection with --name default,
  full export-import roundtrip with a different name

Addresses review feedback on PR #4370.
This commit is contained in:
Teknium 2026-04-01 01:38:19 -07:00 committed by Teknium
parent f04977f45a
commit 68fc4aec21
2 changed files with 210 additions and 6 deletions

View file

@ -488,6 +488,149 @@ class TestExportImport:
with pytest.raises(FileNotFoundError):
export_profile("nonexistent", str(tmp_path / "out.tar.gz"))
# ---------------------------------------------------------------
# Default profile export / import
# ---------------------------------------------------------------
def test_export_default_creates_valid_archive(self, profile_env, tmp_path):
"""Exporting the default profile produces a valid tar.gz."""
default_dir = get_profile_dir("default")
(default_dir / "config.yaml").write_text("model: test")
output = tmp_path / "export" / "default.tar.gz"
output.parent.mkdir(parents=True, exist_ok=True)
result = export_profile("default", str(output))
assert Path(result).exists()
assert tarfile.is_tarfile(str(result))
def test_export_default_includes_profile_data(self, profile_env, tmp_path):
"""Profile data files end up in the archive."""
default_dir = get_profile_dir("default")
(default_dir / "config.yaml").write_text("model: test")
(default_dir / ".env").write_text("KEY=val")
(default_dir / "SOUL.md").write_text("Be nice.")
mem_dir = default_dir / "memories"
mem_dir.mkdir(exist_ok=True)
(mem_dir / "MEMORY.md").write_text("remember this")
output = tmp_path / "export" / "default.tar.gz"
output.parent.mkdir(parents=True, exist_ok=True)
export_profile("default", str(output))
with tarfile.open(str(output), "r:gz") as tf:
names = tf.getnames()
assert "default/config.yaml" in names
assert "default/.env" in names
assert "default/SOUL.md" in names
assert "default/memories/MEMORY.md" in names
def test_export_default_excludes_infrastructure(self, profile_env, tmp_path):
"""Repo checkout, worktrees, profiles, databases are excluded."""
default_dir = get_profile_dir("default")
(default_dir / "config.yaml").write_text("ok")
# Create dirs/files that should be excluded
for d in ("hermes-agent", ".worktrees", "profiles", "bin",
"image_cache", "logs", "sandboxes", "checkpoints"):
sub = default_dir / d
sub.mkdir(exist_ok=True)
(sub / "marker.txt").write_text("excluded")
for f in ("state.db", "gateway.pid", "gateway_state.json",
"processes.json", "errors.log", ".hermes_history",
"active_profile", ".update_check", "auth.lock"):
(default_dir / f).write_text("excluded")
output = tmp_path / "export" / "default.tar.gz"
output.parent.mkdir(parents=True, exist_ok=True)
export_profile("default", str(output))
with tarfile.open(str(output), "r:gz") as tf:
names = tf.getnames()
# Config is present
assert "default/config.yaml" in names
# Infrastructure excluded
excluded_prefixes = [
"default/hermes-agent", "default/.worktrees", "default/profiles",
"default/bin", "default/image_cache", "default/logs",
"default/sandboxes", "default/checkpoints",
]
for prefix in excluded_prefixes:
assert not any(n.startswith(prefix) for n in names), \
f"Expected {prefix} to be excluded but found it in archive"
excluded_files = [
"default/state.db", "default/gateway.pid",
"default/gateway_state.json", "default/processes.json",
"default/errors.log", "default/.hermes_history",
"default/active_profile", "default/.update_check",
"default/auth.lock",
]
for f in excluded_files:
assert f not in names, f"Expected {f} to be excluded"
def test_export_default_excludes_pycache_at_any_depth(self, profile_env, tmp_path):
"""__pycache__ dirs are excluded even inside nested directories."""
default_dir = get_profile_dir("default")
(default_dir / "config.yaml").write_text("ok")
nested = default_dir / "skills" / "my-skill" / "__pycache__"
nested.mkdir(parents=True)
(nested / "cached.pyc").write_text("bytecode")
output = tmp_path / "export" / "default.tar.gz"
output.parent.mkdir(parents=True, exist_ok=True)
export_profile("default", str(output))
with tarfile.open(str(output), "r:gz") as tf:
names = tf.getnames()
assert not any("__pycache__" in n for n in names)
def test_import_default_without_name_raises(self, profile_env, tmp_path):
"""Importing a default export without --name gives clear guidance."""
default_dir = get_profile_dir("default")
(default_dir / "config.yaml").write_text("ok")
archive = tmp_path / "export" / "default.tar.gz"
archive.parent.mkdir(parents=True, exist_ok=True)
export_profile("default", str(archive))
with pytest.raises(ValueError, match="Cannot import as 'default'"):
import_profile(str(archive))
def test_import_default_with_explicit_default_name_raises(self, profile_env, tmp_path):
"""Explicitly importing as 'default' is also rejected."""
default_dir = get_profile_dir("default")
(default_dir / "config.yaml").write_text("ok")
archive = tmp_path / "export" / "default.tar.gz"
archive.parent.mkdir(parents=True, exist_ok=True)
export_profile("default", str(archive))
with pytest.raises(ValueError, match="Cannot import as 'default'"):
import_profile(str(archive), name="default")
def test_import_default_export_with_new_name_roundtrip(self, profile_env, tmp_path):
"""Export default → import under a different name → data preserved."""
default_dir = get_profile_dir("default")
(default_dir / "config.yaml").write_text("model: opus")
mem_dir = default_dir / "memories"
mem_dir.mkdir(exist_ok=True)
(mem_dir / "MEMORY.md").write_text("important fact")
archive = tmp_path / "export" / "default.tar.gz"
archive.parent.mkdir(parents=True, exist_ok=True)
export_profile("default", str(archive))
imported = import_profile(str(archive), name="backup")
assert imported.is_dir()
assert (imported / "config.yaml").read_text() == "model: opus"
assert (imported / "memories" / "MEMORY.md").read_text() == "important fact"
# ===================================================================
# TestProfileIsolation