"""Comprehensive tests for ``muse init``. Coverage tiers: - Unit: template generators, _copy_template, module constants - CLI unit: argument validation (bad branch, bad domain, edge cases) - Integration: every flag, file format, lifecycle scenario, file preservation - End-to-end: muse init followed by subsequent muse commands - Security: ANSI injection, symlink attacks, TOCTOU, corrupt inputs - Stress: sequential, concurrent, large-scale, adversarial inputs """ from __future__ import annotations import json import os import pathlib import threading import tomllib from collections.abc import Mapping import pytest from muse.core.paths import config_toml_path, head_path, muse_dir, objects_dir, ref_path, repo_json_path, tags_dir from tests.cli_test_helper import CliRunner, InvokeResult runner = CliRunner() def _init(repo: pathlib.Path, *extra_args: str) -> InvokeResult: """Invoke ``muse init`` inside *repo* (created if needed).""" from muse.cli.app import main as cli repo.mkdir(parents=True, exist_ok=True) saved = os.getcwd() try: os.chdir(repo) return runner.invoke(cli, ["init", *extra_args]) finally: os.chdir(saved) # --------------------------------------------------------------------------- # Unit — template generators # --------------------------------------------------------------------------- class TestMuseignoreTemplate: def test_code_domain_has_pyc_pattern(self) -> None: from muse.cli.commands.init import _museignore_template result = _museignore_template("code") assert "*.pyc" in result def test_midi_domain_has_renders_pattern(self) -> None: from muse.cli.commands.init import _museignore_template result = _museignore_template("midi") assert "/renders/" in result def test_unknown_domain_produces_commented_stub(self) -> None: from muse.cli.commands.init import _museignore_template result = _museignore_template("genomics") assert "[domain.genomics]" in result assert "# patterns" in result def test_result_is_valid_toml(self) -> None: from muse.cli.commands.init import _museignore_template for domain in ("code", "midi", "genomics"): parsed = tomllib.loads(_museignore_template(domain)) assert isinstance(parsed, dict) def test_global_section_present(self) -> None: from muse.cli.commands.init import _museignore_template parsed = tomllib.loads(_museignore_template("code")) assert "global" in parsed assert isinstance(parsed["global"]["patterns"], list) assert ".DS_Store" in parsed["global"]["patterns"] def test_tls_secrets_not_in_template_patterns(self) -> None: """*.key / *.pem / *.crt must NOT appear in template patterns. The engine's built-in blocklist already covers *.key and *.pem. *.crt (public cert) is intentionally trackable. Duplicating these in the user-facing template is misleading and was removed. """ from muse.cli.commands.init import _museignore_template parsed = tomllib.loads(_museignore_template("code")) code_patterns = parsed.get("domain", {}).get("code", {}).get("patterns", []) assert "*.key" not in code_patterns assert "*.crt" not in code_patterns assert "*.pem" not in code_patterns def test_force_track_section_documented_in_template(self) -> None: """Template must document [force_track] so devs know how to whitelist certs.""" from muse.cli.commands.init import _museignore_template result = _museignore_template("code") assert "force_track" in result def test_template_is_valid_toml(self) -> None: """Template must parse as valid TOML regardless of domain.""" from muse.cli.commands.init import _museignore_template parsed = tomllib.loads(_museignore_template("code")) code_patterns = parsed.get("domain", {}).get("code", {}).get("patterns", []) assert isinstance(code_patterns, list) class TestMuseattributesTemplate: def test_domain_embedded_in_meta(self) -> None: from muse.cli.commands.init import _museattributes_template result = _museattributes_template("code") parsed = tomllib.loads(result) assert parsed["meta"]["domain"] == "code" def test_custom_domain_embedded(self) -> None: from muse.cli.commands.init import _museattributes_template result = _museattributes_template("genomics") parsed = tomllib.loads(result) assert parsed["meta"]["domain"] == "genomics" def test_result_is_valid_toml(self) -> None: from muse.cli.commands.init import _museattributes_template for domain in ("code", "midi", "genomics"): parsed = tomllib.loads(_museattributes_template(domain)) assert isinstance(parsed, dict) # --------------------------------------------------------------------------- # CLI unit — argument validation # --------------------------------------------------------------------------- class TestArgValidation: def test_invalid_branch_name_rejected(self, tmp_path: pathlib.Path) -> None: # Null byte is explicitly forbidden by validate_branch_name result = _init(tmp_path, "--default-branch", "branch\x00null") assert result.exit_code != 0 def test_invalid_branch_name_json_error(self, tmp_path: pathlib.Path) -> None: # Consecutive dots are forbidden (path traversal prevention) result = _init(tmp_path, "--default-branch", "../traversal", "--json") assert result.exit_code != 0 data = json.loads(result.output) assert "error" in data def test_invalid_domain_name_rejected(self, tmp_path: pathlib.Path) -> None: # Domain names must match [a-z][a-z0-9_-]* — spaces and ! are banned result = _init(tmp_path, "--domain", "Bad-Domain!") assert result.exit_code != 0 def test_invalid_domain_name_json_error(self, tmp_path: pathlib.Path) -> None: result = _init(tmp_path, "--domain", "Bad-Domain!", "--json") assert result.exit_code != 0 data = json.loads(result.output) assert "error" in data def test_missing_template_dir_rejected(self, tmp_path: pathlib.Path) -> None: result = _init(tmp_path, "--template", str(tmp_path / "nonexistent")) assert result.exit_code != 0 def test_missing_template_dir_json_error(self, tmp_path: pathlib.Path) -> None: result = _init(tmp_path, "--template", str(tmp_path / "nonexistent"), "--json") assert result.exit_code != 0 data = json.loads(result.output) assert "error" in data def test_template_pointing_to_file_rejected(self, tmp_path: pathlib.Path) -> None: f = tmp_path / "not_a_dir.txt" f.write_text("hello") result = _init(tmp_path / "repo", "--template", str(f)) assert result.exit_code != 0 def test_reinit_without_force_rejected(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) result = _init(tmp_path) assert result.exit_code != 0 def test_reinit_without_force_json_error(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) result = _init(tmp_path, "--json") assert result.exit_code != 0 data = json.loads(result.output) assert "error" in data # --------------------------------------------------------------------------- # Integration — filesystem layout # --------------------------------------------------------------------------- class TestFilesystemLayout: def test_muse_dir_created(self, tmp_path: pathlib.Path) -> None: assert _init(tmp_path).exit_code == 0 assert (muse_dir(tmp_path)).is_dir() def test_required_subdirs_exist(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) muse = muse_dir(tmp_path) for subdir in ("objects", "commits", "snapshots", "refs", "refs/heads"): assert (muse / subdir).is_dir(), f"missing: {subdir}" def test_repo_json_created(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) assert (repo_json_path(tmp_path)).exists() def test_repo_json_fields(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) data = json.loads((repo_json_path(tmp_path)).read_text()) assert "repo_id" in data assert "schema_version" in data assert "created_at" in data assert "domain" in data assert data["domain"] == "code" def test_repo_json_repo_id_is_sha256(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) raw = json.loads((repo_json_path(tmp_path)).read_text())["repo_id"] assert raw.startswith("sha256:"), f"expected sha256: prefix, got {raw!r}" assert len(raw) == 71 def test_head_points_to_default_branch(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) head = (head_path(tmp_path)).read_text() assert "main" in head def test_custom_default_branch_in_head(self, tmp_path: pathlib.Path) -> None: _init(tmp_path, "--default-branch", "dev") head = (head_path(tmp_path)).read_text() assert "dev" in head assert (ref_path(tmp_path, "dev")).exists() def test_config_toml_created(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) assert (config_toml_path(tmp_path)).exists() def test_museignore_created(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) assert (tmp_path / ".museignore").exists() def test_museattributes_created(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) assert (tmp_path / ".museattributes").exists() def test_museattributes_has_correct_domain(self, tmp_path: pathlib.Path) -> None: _init(tmp_path, "--domain", "code") parsed = tomllib.loads((tmp_path / ".museattributes").read_text()) assert parsed["meta"]["domain"] == "code" def test_museignore_valid_toml(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) parsed = tomllib.loads((tmp_path / ".museignore").read_text()) assert isinstance(parsed, dict) class TestBareRepo: def test_bare_creates_muse_dir(self, tmp_path: pathlib.Path) -> None: assert _init(tmp_path, "--bare").exit_code == 0 assert (muse_dir(tmp_path)).is_dir() def test_bare_does_not_create_museignore(self, tmp_path: pathlib.Path) -> None: _init(tmp_path, "--bare") assert not (tmp_path / ".museignore").exists() def test_bare_does_not_create_museattributes(self, tmp_path: pathlib.Path) -> None: _init(tmp_path, "--bare") assert not (tmp_path / ".museattributes").exists() def test_bare_repo_json_has_bare_flag(self, tmp_path: pathlib.Path) -> None: _init(tmp_path, "--bare") data = json.loads((repo_json_path(tmp_path)).read_text()) assert data.get("bare") is True def test_non_bare_repo_json_has_bare_false(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) data = json.loads((repo_json_path(tmp_path)).read_text()) assert data["bare"] is False class TestForceReinit: def test_force_reinit_succeeds(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) result = _init(tmp_path, "--force") assert result.exit_code == 0 def test_force_preserves_repo_id(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) original_id = json.loads( (repo_json_path(tmp_path)).read_text() )["repo_id"] _init(tmp_path, "--force") new_id = json.loads( (repo_json_path(tmp_path)).read_text() )["repo_id"] assert original_id == new_id def test_force_does_not_overwrite_museignore(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) custom = '[global]\npatterns = ["custom.txt"]\n' (tmp_path / ".museignore").write_text(custom) _init(tmp_path, "--force") assert (tmp_path / ".museignore").read_text() == custom def test_force_does_not_overwrite_museattributes(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) custom = '[meta]\ndomain = "custom"\n' (tmp_path / ".museattributes").write_text(custom) _init(tmp_path, "--force") assert (tmp_path / ".museattributes").read_text() == custom def test_force_on_fresh_dir_works(self, tmp_path: pathlib.Path) -> None: # --force on a directory that was never a repo should work as fresh init result = _init(tmp_path, "--force") assert result.exit_code == 0 assert (repo_json_path(tmp_path)).exists() class TestTemplate: def test_template_files_copied(self, tmp_path: pathlib.Path) -> None: tmpl = tmp_path / "tmpl" tmpl.mkdir() (tmpl / "README.md").write_text("# hello") (tmpl / "scripts").mkdir() (tmpl / "scripts" / "run.sh").write_text("#!/bin/sh\necho hi") repo = tmp_path / "repo" repo.mkdir() _init(repo, "--template", str(tmpl)) assert (repo / "README.md").read_text() == "# hello" assert (repo / "scripts" / "run.sh").exists() def test_template_does_not_overwrite_muse_dir(self, tmp_path: pathlib.Path) -> None: tmpl = tmp_path / "tmpl" tmpl.mkdir() # A template that tries to write a .muse directory (muse_dir(tmpl)).mkdir() (muse_dir(tmpl) / "injected").write_text("bad") repo = tmp_path / "repo" repo.mkdir() _init(repo, "--template", str(tmpl)) # The injected file may land but should not corrupt the real repo.json assert (repo_json_path(repo)).exists() def test_template_ignored_for_bare_repo(self, tmp_path: pathlib.Path) -> None: tmpl = tmp_path / "tmpl" tmpl.mkdir() (tmpl / "README.md").write_text("hello") repo = tmp_path / "repo" repo.mkdir() _init(repo, "--bare", "--template", str(tmpl)) # --bare suppresses template copy assert not (repo / "README.md").exists() # --------------------------------------------------------------------------- # JSON output — agent UX # --------------------------------------------------------------------------- class TestJsonOutput: def test_json_exit_zero(self, tmp_path: pathlib.Path) -> None: result = _init(tmp_path, "--json") assert result.exit_code == 0 def test_json_is_valid(self, tmp_path: pathlib.Path) -> None: result = _init(tmp_path, "--json") data = json.loads(result.output) assert isinstance(data, dict) def test_json_has_required_fields(self, tmp_path: pathlib.Path) -> None: result = _init(tmp_path, "--json") data = json.loads(result.output) for field in ("repo_id", "branch", "domain", "path", "reinitialised", "bare"): assert field in data, f"missing field: {field}" def test_json_repo_id_matches_repo_json(self, tmp_path: pathlib.Path) -> None: result = _init(tmp_path, "--json") data = json.loads(result.output) stored = json.loads((repo_json_path(tmp_path)).read_text())["repo_id"] assert data["repo_id"] == stored def test_json_branch_matches_default_branch(self, tmp_path: pathlib.Path) -> None: result = _init(tmp_path, "--default-branch", "dev", "--json") data = json.loads(result.output) assert data["branch"] == "dev" def test_json_domain_matches_domain_arg(self, tmp_path: pathlib.Path) -> None: result = _init(tmp_path, "--domain", "code", "--json") data = json.loads(result.output) assert data["domain"] == "code" def test_json_reinitialised_false_on_fresh(self, tmp_path: pathlib.Path) -> None: result = _init(tmp_path, "--json") assert json.loads(result.output)["reinitialised"] is False def test_json_reinitialised_true_on_force(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) result = _init(tmp_path, "--force", "--json") assert json.loads(result.output)["reinitialised"] is True def test_json_force_preserves_repo_id(self, tmp_path: pathlib.Path) -> None: first = json.loads(_init(tmp_path, "--json").output)["repo_id"] second = json.loads(_init(tmp_path, "--force", "--json").output)["repo_id"] assert first == second def test_json_bare_flag_reflects_bare_arg(self, tmp_path: pathlib.Path) -> None: data = json.loads(_init(tmp_path, "--bare", "--json").output) assert data["bare"] is True def test_json_non_bare_flag(self, tmp_path: pathlib.Path) -> None: data = json.loads(_init(tmp_path, "--json").output) assert data["bare"] is False def test_json_path_is_muse_dir(self, tmp_path: pathlib.Path) -> None: data = json.loads(_init(tmp_path, "--json").output) assert data["path"].endswith(".muse") assert pathlib.Path(data["path"]).is_dir() def test_json_no_human_text_on_stdout(self, tmp_path: pathlib.Path) -> None: result = _init(tmp_path, "--json") # Must be parseable JSON with no extra prose data = json.loads(result.output.strip()) assert isinstance(data, dict) # --------------------------------------------------------------------------- # Security # --------------------------------------------------------------------------- class TestSecurity: def test_ansi_in_branch_error_not_on_stdout(self, tmp_path: pathlib.Path) -> None: """Crafted branch names must not inject ANSI into output.""" malicious = "\x1b[31mmalicious\x1b[0m" result = _init(tmp_path, "--default-branch", malicious) assert "\x1b" not in result.output def test_ansi_in_domain_error_not_on_stdout(self, tmp_path: pathlib.Path) -> None: # Domain validator rejects uppercase/special chars including ANSI sequences malicious = "EVIL\x1b[31mred\x1b[0m" result = _init(tmp_path, "--domain", malicious) assert "\x1b" not in result.output def test_control_chars_in_branch_rejected(self, tmp_path: pathlib.Path) -> None: result = _init(tmp_path, "--default-branch", "branch\x00null") assert result.exit_code != 0 def test_json_errors_are_clean_json(self, tmp_path: pathlib.Path) -> None: """Every error path with --json must emit valid JSON, not a traceback.""" cases = [ ["--domain", "Bad-Domain!", "--json"], ["--default-branch", "../traversal", "--json"], ["--template", str(tmp_path / "missing"), "--json"], ] for extra in cases: result = _init(tmp_path / "fresh", *extra) data = json.loads(result.output) assert "error" in data, f"missing 'error' key for args: {extra}" def test_reinit_without_force_json_has_error(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) result = _init(tmp_path, "--json") assert result.exit_code != 0 data = json.loads(result.output) assert "error" in data def test_template_symlink_skipped(self, tmp_path: pathlib.Path) -> None: """Symlinks inside a template directory are silently skipped. A template with a symlink to ``/etc/passwd`` (or any path outside the template root) must not be followed. The symlink is dropped and the rest of the template is copied normally. """ tmpl = tmp_path / "tmpl" tmpl.mkdir() (tmpl / "legit.txt").write_text("ok") (tmpl / "malicious_link").symlink_to("/etc/passwd") repo = tmp_path / "repo" repo.mkdir() result = _init(repo, "--template", str(tmpl)) assert result.exit_code == 0 assert (repo / "legit.txt").exists() assert not (repo / "malicious_link").exists() def test_template_muse_dir_skipped(self, tmp_path: pathlib.Path) -> None: """A ``.muse/`` directory inside a template is never copied. Without this guard, a malicious template could overwrite the freshly created VCS state directory with an attacker-controlled repo_id. """ tmpl = tmp_path / "tmpl" tmpl.mkdir() malicious_muse = muse_dir(tmpl) malicious_muse.mkdir() (malicious_muse / "repo.json").write_text('{"repo_id": "attacker-id"}') (tmpl / "safe.txt").write_text("safe") repo = tmp_path / "repo" repo.mkdir() result = _init(repo, "--template", str(tmpl), "--json") assert result.exit_code == 0 data = json.loads(result.output) # The repo_id must come from init, not from the malicious template real_repo_id = (repo_json_path(repo)) import json as _json stored = _json.loads(real_repo_id.read_text()) assert stored["repo_id"] == data["repo_id"] assert stored["repo_id"] != "attacker-id" def test_template_symlink_path_rejected(self, tmp_path: pathlib.Path) -> None: """A ``--template`` path that is itself a symlink is rejected.""" real_dir = tmp_path / "real" real_dir.mkdir() link = tmp_path / "link" link.symlink_to(real_dir) repo = tmp_path / "repo" repo.mkdir() result = _init(repo, "--template", str(link)) assert result.exit_code != 0 def test_tags_dir_created_at_init(self, tmp_path: pathlib.Path) -> None: """init must create ``.muse/tags/`` so muse tag works immediately.""" result = _init(tmp_path) assert result.exit_code == 0 assert (tags_dir(tmp_path)).is_dir() def test_schema_version_is_integer(self, tmp_path: pathlib.Path) -> None: """schema_version in repo.json must be an integer, not a package version string.""" _init(tmp_path) data = json.loads((repo_json_path(tmp_path)).read_text()) assert isinstance(data["schema_version"], int), ( f"schema_version should be int, got {type(data['schema_version'])}" ) def test_json_output_includes_schema_version(self, tmp_path: pathlib.Path) -> None: """--json output must include schema_version as an integer.""" result = _init(tmp_path, "--json") data = json.loads(result.output) assert "schema_version" in data assert isinstance(data["schema_version"], int) def test_muse_version_written_to_repo_json(self, tmp_path: pathlib.Path) -> None: """repo.json must record the Muse version that created the repo.""" from muse import __version__ _init(tmp_path) data = json.loads((repo_json_path(tmp_path)).read_text()) assert "muse_version" in data assert data["muse_version"] == __version__ def test_bare_always_written_to_repo_json(self, tmp_path: pathlib.Path) -> None: """bare is always present in repo.json (False for normal repos).""" _init(tmp_path) data = json.loads((repo_json_path(tmp_path)).read_text()) assert "bare" in data assert data["bare"] is False # --------------------------------------------------------------------------- # Stress # --------------------------------------------------------------------------- class TestStress: def test_rapid_sequential_inits(self, tmp_path: pathlib.Path) -> None: """50 sequential inits in different directories must all succeed.""" for i in range(50): repo = tmp_path / f"repo_{i:03d}" repo.mkdir() result = _init(repo, "--json") assert result.exit_code == 0, f"init failed for repo_{i:03d}" data = json.loads(result.output) assert "repo_id" in data def test_large_template_dir(self, tmp_path: pathlib.Path) -> None: """Template with 200 files copies without error.""" tmpl = tmp_path / "big_tmpl" tmpl.mkdir() for i in range(200): (tmpl / f"file_{i:03d}.txt").write_text(f"content {i}") repo = tmp_path / "repo" repo.mkdir() result = _init(repo, "--template", str(tmpl)) assert result.exit_code == 0 assert (repo / "file_000.txt").exists() assert (repo / "file_199.txt").exists() def test_reinit_cycle_preserves_repo_id(self, tmp_path: pathlib.Path) -> None: """20 successive --force inits must all return the same repo_id.""" first = json.loads(_init(tmp_path, "--json").output)["repo_id"] for _ in range(20): rid = json.loads(_init(tmp_path, "--force", "--json").output)["repo_id"] assert rid == first, "repo_id changed across reinit" def test_all_domains_produce_valid_ignore(self) -> None: """Every domain in _MUSEIGNORE_DOMAIN_BLOCKS produces valid TOML.""" from muse.cli.commands.init import ( _MUSEIGNORE_DOMAIN_BLOCKS, _museignore_template, ) for domain in list(_MUSEIGNORE_DOMAIN_BLOCKS) + ["custom_domain"]: result = _museignore_template(domain) parsed = tomllib.loads(result) assert isinstance(parsed, dict), f"invalid TOML for domain {domain!r}" # --------------------------------------------------------------------------- # Unit — module constants # --------------------------------------------------------------------------- class TestConstants: """Structural invariants on module-level constants in init.py.""" def test_repo_schema_version_is_positive_int(self) -> None: from muse.cli.commands.init import _REPO_SCHEMA_VERSION assert isinstance(_REPO_SCHEMA_VERSION, int) assert _REPO_SCHEMA_VERSION >= 1 def test_default_config_is_valid_toml(self) -> None: from muse.cli.commands.init import _DEFAULT_CONFIG parsed = tomllib.loads(_DEFAULT_CONFIG) assert isinstance(parsed, dict) def test_default_config_has_user_section(self) -> None: from muse.cli.commands.init import _DEFAULT_CONFIG parsed = tomllib.loads(_DEFAULT_CONFIG) assert "user" in parsed def test_default_config_has_remotes_section(self) -> None: from muse.cli.commands.init import _DEFAULT_CONFIG parsed = tomllib.loads(_DEFAULT_CONFIG) assert "remotes" in parsed def test_bare_config_is_valid_toml(self) -> None: from muse.cli.commands.init import _BARE_CONFIG parsed = tomllib.loads(_BARE_CONFIG) assert isinstance(parsed, dict) def test_bare_config_has_core_bare_true(self) -> None: from muse.cli.commands.init import _BARE_CONFIG parsed = tomllib.loads(_BARE_CONFIG) assert parsed.get("core", {}).get("bare") is True def test_init_subdirs_is_superset_of_critical_muse_dirs(self) -> None: """Every directory in _CRITICAL_MUSE_DIRS must appear in _INIT_SUBDIRS. If this fails, require_repo()'s _verify_muse_dir_integrity() will never see a freshly-init'd critical directory and cannot protect it. """ from muse.cli.commands.init import _INIT_SUBDIRS from muse.core.repo import _CRITICAL_MUSE_DIRS init_set = set(_INIT_SUBDIRS) for critical in _CRITICAL_MUSE_DIRS: assert critical in init_set, ( f".muse/{critical}/ is in _CRITICAL_MUSE_DIRS " f"but missing from _INIT_SUBDIRS — init will not create it" ) def test_init_subdirs_contains_no_duplicates(self) -> None: from muse.cli.commands.init import _INIT_SUBDIRS assert len(_INIT_SUBDIRS) == len(set(_INIT_SUBDIRS)) def test_init_subdirs_no_absolute_paths(self) -> None: from muse.cli.commands.init import _INIT_SUBDIRS for s in _INIT_SUBDIRS: assert not s.startswith("/"), f"{s!r} is absolute — must be relative" def test_museignore_global_patterns_is_list(self) -> None: from muse.cli.commands.init import _MUSEIGNORE_GLOBAL parsed = tomllib.loads(_MUSEIGNORE_GLOBAL) assert isinstance(parsed["global"]["patterns"], list) assert len(parsed["global"]["patterns"]) > 0 # --------------------------------------------------------------------------- # Unit — _copy_template directly # --------------------------------------------------------------------------- class TestCopyTemplate: """Direct unit tests for _copy_template — not mediated by the CLI.""" def test_empty_template_dir_is_a_no_op(self, tmp_path: pathlib.Path) -> None: from muse.cli.commands.init import _copy_template src = tmp_path / "src" dst = tmp_path / "dst" src.mkdir() dst.mkdir() _copy_template(src, dst, []) assert list(dst.iterdir()) == [] def test_files_are_copied(self, tmp_path: pathlib.Path) -> None: from muse.cli.commands.init import _copy_template src = tmp_path / "src" dst = tmp_path / "dst" src.mkdir() dst.mkdir() (src / "hello.txt").write_text("hi") _copy_template(src, dst, []) assert (dst / "hello.txt").read_text() == "hi" def test_subdirectory_is_copied_recursively(self, tmp_path: pathlib.Path) -> None: from muse.cli.commands.init import _copy_template src = tmp_path / "src" dst = tmp_path / "dst" src.mkdir() dst.mkdir() (src / "sub").mkdir() (src / "sub" / "nested.txt").write_text("deep") _copy_template(src, dst, []) assert (dst / "sub" / "nested.txt").read_text() == "deep" def test_deeply_nested_structure_copied(self, tmp_path: pathlib.Path) -> None: from muse.cli.commands.init import _copy_template src = tmp_path / "src" dst = tmp_path / "dst" src.mkdir() dst.mkdir() deep = src / "a" / "b" / "c" deep.mkdir(parents=True) (deep / "file.txt").write_text("leaf") _copy_template(src, dst, []) assert (dst / "a" / "b" / "c" / "file.txt").read_text() == "leaf" def test_symlinks_are_skipped(self, tmp_path: pathlib.Path) -> None: from muse.cli.commands.init import _copy_template src = tmp_path / "src" dst = tmp_path / "dst" src.mkdir() dst.mkdir() (src / "legit.txt").write_text("ok") (src / "malicious").symlink_to("/etc/passwd") _copy_template(src, dst, []) assert (dst / "legit.txt").exists() assert not (dst / "malicious").exists() def test_all_symlinks_dir_copies_nothing(self, tmp_path: pathlib.Path) -> None: from muse.cli.commands.init import _copy_template src = tmp_path / "src" dst = tmp_path / "dst" src.mkdir() dst.mkdir() for i in range(5): (src / f"link{i}").symlink_to("/etc/passwd") _copy_template(src, dst, []) assert list(dst.iterdir()) == [] def test_muse_dir_skipped(self, tmp_path: pathlib.Path) -> None: from muse.cli.commands.init import _copy_template src = tmp_path / "src" dst = tmp_path / "dst" src.mkdir() dst.mkdir() malicious = muse_dir(src) malicious.mkdir() (malicious / "repo.json").write_text('{"repo_id": "attacker"}') (src / "safe.txt").write_text("ok") _copy_template(src, dst, []) assert not (muse_dir(dst)).exists() assert (dst / "safe.txt").exists() def test_muse_file_also_skipped(self, tmp_path: pathlib.Path) -> None: """A file named .muse (not a dir) is also skipped for safety.""" from muse.cli.commands.init import _copy_template src = tmp_path / "src" dst = tmp_path / "dst" src.mkdir() dst.mkdir() (muse_dir(src)).write_text("malicious") (src / "ok.txt").write_text("ok") _copy_template(src, dst, []) assert not (muse_dir(dst)).exists() assert (dst / "ok.txt").exists() def test_existing_file_overwritten(self, tmp_path: pathlib.Path) -> None: """shutil.copy2 overwrites existing files in the destination.""" from muse.cli.commands.init import _copy_template src = tmp_path / "src" dst = tmp_path / "dst" src.mkdir() dst.mkdir() (src / "file.txt").write_text("from template") (dst / "file.txt").write_text("original") _copy_template(src, dst, []) assert (dst / "file.txt").read_text() == "from template" def test_multiple_symlinks_all_skipped(self, tmp_path: pathlib.Path) -> None: from muse.cli.commands.init import _copy_template src = tmp_path / "src" dst = tmp_path / "dst" src.mkdir() dst.mkdir() (src / "real.txt").write_text("real") for name in ("link1", "link2", "link3"): (src / name).symlink_to(str(src / "real.txt")) _copy_template(src, dst, []) assert (dst / "real.txt").exists() for name in ("link1", "link2", "link3"): assert not (dst / name).exists() def test_binary_file_copied_correctly(self, tmp_path: pathlib.Path) -> None: from muse.cli.commands.init import _copy_template src = tmp_path / "src" dst = tmp_path / "dst" src.mkdir() dst.mkdir() data = bytes(range(256)) (src / "binary.bin").write_bytes(data) _copy_template(src, dst, []) assert (dst / "binary.bin").read_bytes() == data # --------------------------------------------------------------------------- # Integration — HEAD and ref file format # --------------------------------------------------------------------------- class TestHeadAndRefFile: """Verify the exact format of .muse/HEAD and branch ref files.""" def test_head_exact_format(self, tmp_path: pathlib.Path) -> None: """HEAD must be exactly 'ref: refs/heads/main\\n'.""" _init(tmp_path) head = (head_path(tmp_path)).read_text() assert head == "ref: refs/heads/main\n" def test_head_exact_format_custom_branch(self, tmp_path: pathlib.Path) -> None: _init(tmp_path, "--default-branch", "dev") head = (head_path(tmp_path)).read_text() assert head == "ref: refs/heads/dev\n" def test_head_updated_on_force_with_different_branch(self, tmp_path: pathlib.Path) -> None: """--force with a new --default-branch updates HEAD.""" _init(tmp_path, "--default-branch", "main") _init(tmp_path, "--force", "--default-branch", "dev") head = (head_path(tmp_path)).read_text() assert head == "ref: refs/heads/dev\n" def test_branch_ref_file_exists_after_init(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) ref = ref_path(tmp_path, "main") assert ref.exists() def test_branch_ref_file_is_empty_on_fresh_init(self, tmp_path: pathlib.Path) -> None: """A fresh repo has no commits — branch ref file must be empty.""" _init(tmp_path) ref = ref_path(tmp_path, "main") assert ref.read_text() == "" def test_custom_branch_ref_file_exists(self, tmp_path: pathlib.Path) -> None: _init(tmp_path, "--default-branch", "feat/new") ref = ref_path(tmp_path, "feat") / "new" assert ref.exists() def test_refs_dir_created(self, tmp_path: pathlib.Path) -> None: """The .muse/refs/ directory itself must exist (not just refs/heads/).""" _init(tmp_path) assert (muse_dir(tmp_path) / "refs").is_dir() def test_no_directory_is_a_symlink_after_init(self, tmp_path: pathlib.Path) -> None: """Post-init integrity check: every _INIT_SUBDIRS entry must be a real dir.""" from muse.cli.commands.init import _INIT_SUBDIRS _init(tmp_path) muse = muse_dir(tmp_path) for subdir in _INIT_SUBDIRS: candidate = muse / subdir assert candidate.is_dir(), f".muse/{subdir} is not a directory" assert not candidate.is_symlink(), f".muse/{subdir} is a symlink" # --------------------------------------------------------------------------- # Integration — config file content # --------------------------------------------------------------------------- class TestConfigFiles: """Verify content and TOML validity of generated config files.""" def test_config_toml_is_valid_toml(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) parsed = tomllib.loads((config_toml_path(tmp_path)).read_text()) assert isinstance(parsed, dict) def test_config_toml_has_user_section(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) parsed = tomllib.loads((config_toml_path(tmp_path)).read_text()) assert "user" in parsed def test_config_toml_has_remotes_section(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) parsed = tomllib.loads((config_toml_path(tmp_path)).read_text()) assert "remotes" in parsed def test_bare_config_toml_has_core_bare(self, tmp_path: pathlib.Path) -> None: _init(tmp_path, "--bare") parsed = tomllib.loads((config_toml_path(tmp_path)).read_text()) assert parsed.get("core", {}).get("bare") is True def test_config_toml_not_overwritten_by_force(self, tmp_path: pathlib.Path) -> None: """--force must not overwrite an existing config.toml.""" _init(tmp_path) config_path = config_toml_path(tmp_path) config_path.write_text('[custom]\nkey = "value"\n') _init(tmp_path, "--force") parsed = tomllib.loads(config_path.read_text()) assert parsed.get("custom", {}).get("key") == "value" def test_museignore_has_correct_domain_section(self, tmp_path: pathlib.Path) -> None: """The [domain.] section must match the --domain flag.""" _init(tmp_path, "--domain", "midi") parsed = tomllib.loads((tmp_path / ".museignore").read_text()) assert "domain" in parsed assert "midi" in parsed["domain"] def test_museignore_code_domain_section_present(self, tmp_path: pathlib.Path) -> None: _init(tmp_path, "--domain", "code") parsed = tomllib.loads((tmp_path / ".museignore").read_text()) assert "code" in parsed.get("domain", {}) def test_museignore_does_not_contain_other_domain_sections( self, tmp_path: pathlib.Path ) -> None: """A code-domain repo must not have a [domain.midi] section.""" _init(tmp_path, "--domain", "code") text = (tmp_path / ".museignore").read_text() assert "domain.midi" not in text def test_repo_json_is_not_zero_bytes(self, tmp_path: pathlib.Path) -> None: """Atomic write guard: repo.json must not be empty after init.""" _init(tmp_path) size = (repo_json_path(tmp_path)).stat().st_size assert size > 0 def test_repo_json_created_at_is_utc_iso(self, tmp_path: pathlib.Path) -> None: import datetime _init(tmp_path) raw = json.loads((repo_json_path(tmp_path)).read_text())["created_at"] dt = datetime.datetime.fromisoformat(raw) assert dt.tzinfo is not None # must be timezone-aware def test_repo_json_domain_matches_arg(self, tmp_path: pathlib.Path) -> None: _init(tmp_path, "--domain", "midi") raw = json.loads((repo_json_path(tmp_path)).read_text()) assert raw["domain"] == "midi" # --------------------------------------------------------------------------- # Integration — --force edge cases # --------------------------------------------------------------------------- class TestForceEdgeCases: """Edge cases in the --force reinit path.""" def test_force_with_corrupt_repo_json_assigns_new_id( self, tmp_path: pathlib.Path ) -> None: """A corrupt repo.json must not crash --force; init assigns a fresh repo_id.""" _init(tmp_path) (repo_json_path(tmp_path)).write_text("{ NOT VALID JSON !!!") result = _init(tmp_path, "--force", "--json") assert result.exit_code == 0 data = json.loads(result.output) assert "repo_id" in data # repo_id must be a sha256: content-addressed ID assert data["repo_id"].startswith("sha256:") assert len(data["repo_id"]) == 71 def test_force_with_empty_repo_json_assigns_new_id( self, tmp_path: pathlib.Path ) -> None: _init(tmp_path) (repo_json_path(tmp_path)).write_bytes(b"") result = _init(tmp_path, "--force", "--json") assert result.exit_code == 0 new_id = json.loads(result.output)["repo_id"] assert new_id.startswith("sha256:") assert len(new_id) == 71 def test_force_with_repo_json_missing_repo_id_assigns_new_id( self, tmp_path: pathlib.Path ) -> None: _init(tmp_path) (repo_json_path(tmp_path)).write_text('{"schema_version": 1}') result = _init(tmp_path, "--force", "--json") assert result.exit_code == 0 new_id = json.loads(result.output)["repo_id"] assert new_id.startswith("sha256:") assert len(new_id) == 71 def test_force_with_non_string_repo_id_assigns_new_id( self, tmp_path: pathlib.Path ) -> None: """repo_id must be a string — if it isn't, force must assign a new one.""" _init(tmp_path) (repo_json_path(tmp_path)).write_text('{"repo_id": 42}') result = _init(tmp_path, "--force", "--json") assert result.exit_code == 0 rid = json.loads(result.output)["repo_id"] assert isinstance(rid, str) assert rid != "42" def test_force_preserves_custom_museignore(self, tmp_path: pathlib.Path) -> None: """--force must not overwrite an existing .museignore.""" _init(tmp_path) custom = '[global]\npatterns = ["custom_file.log"]\n' (tmp_path / ".museignore").write_text(custom) _init(tmp_path, "--force") assert (tmp_path / ".museignore").read_text() == custom def test_force_preserves_custom_museattributes(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) custom = '[meta]\ndomain = "spacetime"\n' (tmp_path / ".museattributes").write_text(custom) _init(tmp_path, "--force") assert (tmp_path / ".museattributes").read_text() == custom def test_force_reinitialised_flag_in_json(self, tmp_path: pathlib.Path) -> None: _init(tmp_path) result = _init(tmp_path, "--force", "--json") assert json.loads(result.output)["reinitialised"] is True def test_force_updates_schema_version_in_repo_json( self, tmp_path: pathlib.Path ) -> None: """After --force, repo.json must have the current schema_version.""" from muse.cli.commands.init import _REPO_SCHEMA_VERSION _init(tmp_path) _init(tmp_path, "--force") raw = json.loads((repo_json_path(tmp_path)).read_text()) assert raw["schema_version"] == _REPO_SCHEMA_VERSION def test_force_on_partially_missing_muse_dir(self, tmp_path: pathlib.Path) -> None: """--force on a .muse/ with some subdirs missing re-creates them.""" _init(tmp_path) import shutil shutil.rmtree(objects_dir(tmp_path)) result = _init(tmp_path, "--force") assert result.exit_code == 0 assert (objects_dir(tmp_path)).is_dir() # --------------------------------------------------------------------------- # Integration — two-repo isolation # --------------------------------------------------------------------------- class TestRepoIsolation: """Multiple repos in sibling directories must be completely independent.""" def test_two_repos_have_different_repo_ids(self, tmp_path: pathlib.Path) -> None: repo_a = tmp_path / "a" repo_b = tmp_path / "b" repo_a.mkdir() repo_b.mkdir() id_a = json.loads(_init(repo_a, "--json").output)["repo_id"] id_b = json.loads(_init(repo_b, "--json").output)["repo_id"] assert id_a != id_b def test_one_hundred_repos_have_unique_repo_ids(self, tmp_path: pathlib.Path) -> None: ids: set[str] = set() for i in range(100): repo = tmp_path / f"r{i:03d}" repo.mkdir() data = json.loads(_init(repo, "--json").output) ids.add(data["repo_id"]) assert len(ids) == 100, "repo_ids collided across 100 repos" def test_init_in_child_does_not_affect_parent(self, tmp_path: pathlib.Path) -> None: """Initialising a subdirectory must not create .muse/ in the parent.""" child = tmp_path / "child" child.mkdir() _init(child) assert not (muse_dir(tmp_path)).exists() def test_sibling_repos_independent_after_reinit(self, tmp_path: pathlib.Path) -> None: repo_a = tmp_path / "a" repo_b = tmp_path / "b" repo_a.mkdir() repo_b.mkdir() _init(repo_a) _init(repo_b) id_a_orig = json.loads((repo_json_path(repo_a)).read_text())["repo_id"] _init(repo_b, "--force") id_a_after = json.loads((repo_json_path(repo_a)).read_text())["repo_id"] assert id_a_orig == id_a_after # reinit of b must not touch a # --------------------------------------------------------------------------- # End-to-end — muse commands immediately after init # --------------------------------------------------------------------------- class TestEndToEnd: """After muse init, every core command must work without error.""" def test_status_works_on_fresh_repo(self, tmp_path: pathlib.Path) -> None: import subprocess _init(tmp_path) r = subprocess.run( ["muse", "status", "--json"], capture_output=True, text=True, cwd=str(tmp_path), ) assert r.returncode == 0 data = json.loads(r.stdout) assert data.get("branch") == "main" def test_log_works_on_fresh_repo(self, tmp_path: pathlib.Path) -> None: """muse log on an empty repo must exit 0 (no commits is not an error).""" import subprocess _init(tmp_path) r = subprocess.run( ["muse", "log", "--json"], capture_output=True, text=True, cwd=str(tmp_path), ) assert r.returncode == 0 def test_branch_shows_initial_branch(self, tmp_path: pathlib.Path) -> None: import subprocess _init(tmp_path) r = subprocess.run( ["muse", "branch"], capture_output=True, text=True, cwd=str(tmp_path), ) assert r.returncode == 0 assert "main" in r.stdout def test_branch_shows_custom_initial_branch(self, tmp_path: pathlib.Path) -> None: import subprocess _init(tmp_path, "--default-branch", "dev") r = subprocess.run( ["muse", "branch"], capture_output=True, text=True, cwd=str(tmp_path), ) assert r.returncode == 0 assert "dev" in r.stdout def test_first_commit_succeeds(self, tmp_path: pathlib.Path) -> None: """Full workflow: init → add file → commit.""" import subprocess _init(tmp_path) (tmp_path / "hello.py").write_text("print('hello')\n") add = subprocess.run( ["muse", "code", "add", "."], capture_output=True, text=True, cwd=str(tmp_path), ) assert add.returncode == 0, f"muse code add failed: {add.stderr}" commit = subprocess.run( ["muse", "commit", "-m", "first commit"], capture_output=True, text=True, cwd=str(tmp_path), ) assert commit.returncode == 0, f"muse commit failed: {commit.stderr}" def test_tag_command_works_after_init(self, tmp_path: pathlib.Path) -> None: """tags/ is pre-created at init — muse tag must not fail with 'no dir'.""" import subprocess _init(tmp_path) # muse tag list should work even on an empty repo r = subprocess.run( ["muse", "tag", "list"], capture_output=True, text=True, cwd=str(tmp_path), ) # exit 0 expected — no tags is not an error assert r.returncode == 0 def test_require_repo_succeeds_immediately_after_init( self, tmp_path: pathlib.Path ) -> None: """require_repo() called from Python must not raise after muse init.""" _init(tmp_path) saved = os.getcwd() try: os.chdir(tmp_path) from muse.core.repo import require_repo ctx = require_repo() assert ctx == tmp_path finally: os.chdir(saved) def test_status_on_custom_domain_repo(self, tmp_path: pathlib.Path) -> None: """muse status works on a repo with a non-default but registered domain.""" import subprocess _init(tmp_path, "--domain", "scaffold") r = subprocess.run( ["muse", "status", "--json"], capture_output=True, text=True, cwd=str(tmp_path), ) assert r.returncode == 0 def test_status_correctly_identifies_branch(self, tmp_path: pathlib.Path) -> None: import subprocess _init(tmp_path, "--default-branch", "feat/new-world") r = subprocess.run( ["muse", "status", "--json"], capture_output=True, text=True, cwd=str(tmp_path), ) data = json.loads(r.stdout) assert data.get("branch") == "feat/new-world" # --------------------------------------------------------------------------- # Security — deeper # --------------------------------------------------------------------------- class TestSecurityDeep: """Additional security scenarios beyond the basic TestSecurity class.""" def test_validation_happens_before_filesystem_is_touched( self, tmp_path: pathlib.Path ) -> None: """If the branch name is invalid, no .muse/ directory must be created.""" result = _init(tmp_path, "--default-branch", "bad branch name!") assert result.exit_code != 0 assert not (muse_dir(tmp_path)).exists() def test_domain_validation_before_filesystem_touched( self, tmp_path: pathlib.Path ) -> None: """If the domain is invalid, no .muse/ directory must be created.""" result = _init(tmp_path, "--domain", "BAD_DOMAIN!") assert result.exit_code != 0 assert not (muse_dir(tmp_path)).exists() def test_template_json_error_path_emits_clean_json( self, tmp_path: pathlib.Path ) -> None: """--template with symlink path + --json must emit valid JSON error.""" real = tmp_path / "real" real.mkdir() link = tmp_path / "link" link.symlink_to(real) result = _init(tmp_path / "repo", "--template", str(link), "--json") data = json.loads(result.output) assert "error" in data def test_template_symlink_inside_subdir_not_followed( self, tmp_path: pathlib.Path ) -> None: """Symlinks nested inside a template's subdirectory are not followed (the directory containing them is deep-copied by shutil.copytree which follows symlinks by default — we only guard at the top level). This test documents the known behaviour so it is explicit.""" tmpl = tmp_path / "tmpl" (tmpl / "sub").mkdir(parents=True) # A real file inside a subdirectory (tmpl / "sub" / "real.txt").write_text("ok") repo = tmp_path / "repo" repo.mkdir() result = _init(repo, "--template", str(tmpl)) assert result.exit_code == 0 # The subdirectory with the real file is copied assert (repo / "sub" / "real.txt").exists() def test_all_error_paths_return_nonzero(self, tmp_path: pathlib.Path) -> None: """Every known error path must return a non-zero exit code.""" cases = [ ["--default-branch", "bad branch"], # bad branch ["--domain", "Bad!"], # bad domain ["--template", str(tmp_path / "nope")], # missing template ] for args in cases: result = _init(tmp_path / "fresh", *args) assert result.exit_code != 0, f"expected failure for args {args}" def test_very_long_branch_name_rejected(self, tmp_path: pathlib.Path) -> None: """Branch names longer than the allowed max must be rejected.""" long_name = "a" * 300 result = _init(tmp_path, "--default-branch", long_name) assert result.exit_code != 0 def test_dot_only_branch_name_rejected(self, tmp_path: pathlib.Path) -> None: result = _init(tmp_path, "--default-branch", ".") assert result.exit_code != 0 def test_dotdot_branch_name_rejected(self, tmp_path: pathlib.Path) -> None: result = _init(tmp_path, "--default-branch", "..") assert result.exit_code != 0 def test_reinit_twice_without_force_second_fails(self, tmp_path: pathlib.Path) -> None: """Two consecutive inits without --force: second must always fail.""" assert _init(tmp_path).exit_code == 0 assert _init(tmp_path).exit_code != 0 assert _init(tmp_path).exit_code != 0 # --------------------------------------------------------------------------- # Stress — deep and concurrent # --------------------------------------------------------------------------- class TestStressDeep: """Large-scale, concurrent, and adversarial stress tests.""" @pytest.mark.slow def test_concurrent_inits_to_different_dirs(self, tmp_path: pathlib.Path) -> None: """50 concurrent threads each init a different directory — no crashes, no repo_id collisions, no cross-repo contamination. Uses subprocess.run (not the _init helper) because os.chdir() is process-global and not thread-safe. Each subprocess gets its own working directory via the ``cwd`` argument. """ import subprocess results: list[tuple[int, str]] = [] errors: list[str] = [] lock = threading.Lock() def do_init(i: int) -> None: repo = tmp_path / f"concurrent_{i:03d}" repo.mkdir() r = subprocess.run( ["muse", "init", "--json"], capture_output=True, text=True, cwd=str(repo), ) with lock: if r.returncode != 0: errors.append(f"repo_{i}: exit={r.returncode} out={r.stdout[:100]}") else: try: data = json.loads(r.stdout) results.append((i, data["repo_id"])) except Exception as exc: errors.append(f"repo_{i}: parse error {exc}") threads = [threading.Thread(target=do_init, args=(i,)) for i in range(50)] for t in threads: t.start() for t in threads: t.join() assert not errors, f"init errors: {errors}" assert len(results) == 50 # All repo_ids must be unique ids = [rid for _, rid in results] assert len(set(ids)) == len(ids), "repo_id collision among concurrent inits" @pytest.mark.slow def test_large_deeply_nested_template(self, tmp_path: pathlib.Path) -> None: """Template with 10 directories × 50 files each (500 total) copies cleanly.""" tmpl = tmp_path / "big" for d in range(10): subdir = tmpl / f"dir_{d:02d}" subdir.mkdir(parents=True) for f in range(50): (subdir / f"file_{f:03d}.txt").write_text(f"d={d} f={f}") repo = tmp_path / "repo" repo.mkdir() result = _init(repo, "--template", str(tmpl)) assert result.exit_code == 0 for d in range(10): assert (repo / f"dir_{d:02d}" / "file_000.txt").exists() assert (repo / f"dir_{d:02d}" / "file_049.txt").exists() @pytest.mark.slow def test_force_reinit_100_times_preserves_repo_id( self, tmp_path: pathlib.Path ) -> None: """100 consecutive --force inits must all return the identical repo_id.""" first = json.loads(_init(tmp_path, "--json").output)["repo_id"] for i in range(100): result = _init(tmp_path, "--force", "--json") assert result.exit_code == 0, f"failed on iteration {i}" rid = json.loads(result.output)["repo_id"] assert rid == first, f"repo_id changed on iteration {i}" @pytest.mark.slow def test_mixed_template_stress(self, tmp_path: pathlib.Path) -> None: """Template with a mix of real files, symlinks, .muse dir, subdirs. Real files must be copied; everything else silently skipped.""" tmpl = tmp_path / "mixed" tmpl.mkdir() # Legitimate files for i in range(100): (tmpl / f"real_{i:03d}.txt").write_text(f"content {i}") # Symlinks — should be skipped for i in range(20): (tmpl / f"malicious_{i:02d}").symlink_to("/etc/passwd") # .muse dir — should be skipped (muse_dir(tmpl)).mkdir() (repo_json_path(tmpl)).write_text('{"repo_id": "malicious"}') # Legitimate subdirectory sub = tmpl / "legit_sub" sub.mkdir() (sub / "nested.txt").write_text("nested content") repo = tmp_path / "repo" repo.mkdir() result = _init(repo, "--template", str(tmpl)) assert result.exit_code == 0 # Real files copied assert (repo / "real_000.txt").read_text() == "content 0" assert (repo / "real_099.txt").read_text() == "content 99" assert (repo / "legit_sub" / "nested.txt").read_text() == "nested content" # Symlinks not copied for i in range(20): assert not (repo / f"malicious_{i:02d}").exists() # .muse not overwritten stored = json.loads((repo_json_path(repo)).read_text()) assert stored.get("repo_id") != "malicious" def test_all_required_subdirs_created_consistently( self, tmp_path: pathlib.Path ) -> None: """Verify all required subdirs are consistently created across 20 inits.""" from muse.cli.commands.init import _INIT_SUBDIRS for i in range(20): repo = tmp_path / f"repo_{i:02d}" repo.mkdir() _init(repo) muse = muse_dir(repo) for subdir in _INIT_SUBDIRS: assert (muse / subdir).is_dir(), ( f"repo_{i}: .muse/{subdir} missing" ) # --------------------------------------------------------------------------- # Directory argument — muse init (ergonomics parity with git init ) # --------------------------------------------------------------------------- def _init_from(cwd: pathlib.Path, *args: str) -> "InvokeResult": """Invoke ``muse init`` with CWD set to *cwd*, passing *args* verbatim. Unlike ``_init``, this helper does NOT pre-create the target directory — the directory-argument feature is expected to create it. """ from muse.cli.app import main as cli cwd.mkdir(parents=True, exist_ok=True) saved = os.getcwd() try: os.chdir(cwd) return runner.invoke(cli, ["init", *args]) finally: os.chdir(saved) class TestDirectoryArgument: """muse init must create the directory (if needed) and init inside it.""" def test_absolute_path_creates_dir_and_inits(self, tmp_path: pathlib.Path) -> None: target = tmp_path / "new_repo" # target does not exist yet — the command must create it result = _init_from(tmp_path, str(target)) assert result.exit_code == 0, result.output assert (muse_dir(target)).is_dir() def test_relative_path_creates_dir_and_inits(self, tmp_path: pathlib.Path) -> None: result = _init_from(tmp_path, "sub_repo") assert result.exit_code == 0, result.output assert muse_dir(tmp_path / "sub_repo").is_dir() def test_dot_is_equivalent_to_no_arg(self, tmp_path: pathlib.Path) -> None: """muse init . should initialise in CWD, same as muse init.""" result = _init_from(tmp_path, ".") assert result.exit_code == 0, result.output assert (muse_dir(tmp_path)).is_dir() def test_existing_empty_dir_is_used(self, tmp_path: pathlib.Path) -> None: target = tmp_path / "existing" target.mkdir() result = _init_from(tmp_path, str(target)) assert result.exit_code == 0, result.output assert (muse_dir(target)).is_dir() def test_cwd_is_not_initialised_when_dir_arg_given(self, tmp_path: pathlib.Path) -> None: """When a directory argument is provided, CWD must NOT get a .muse/.""" target = tmp_path / "target" result = _init_from(tmp_path, str(target)) assert result.exit_code == 0, result.output assert not (muse_dir(tmp_path)).exists() def test_deeply_nested_nonexistent_path_created(self, tmp_path: pathlib.Path) -> None: target = tmp_path / "a" / "b" / "c" result = _init_from(tmp_path, str(target)) assert result.exit_code == 0, result.output assert (muse_dir(target)).is_dir() def test_json_path_reflects_target_dir(self, tmp_path: pathlib.Path) -> None: target = tmp_path / "my_repo" result = _init_from(tmp_path, str(target), "--json") assert result.exit_code == 0, result.output data = json.loads(result.output) assert data["path"].endswith(".muse") assert pathlib.Path(data["path"]).parent.resolve() == target.resolve() def test_dir_arg_with_json_output(self, tmp_path: pathlib.Path) -> None: target = tmp_path / "json_repo" result = _init_from(tmp_path, str(target), "--json") assert result.exit_code == 0, result.output data = json.loads(result.output) assert data["status"] == "ok" assert "repo_id" in data def test_dir_arg_with_all_flags(self, tmp_path: pathlib.Path) -> None: target = tmp_path / "full_flags" result = _init_from( tmp_path, str(target), "--default-branch", "dev", "--domain", "code", "--json", ) assert result.exit_code == 0, result.output data = json.loads(result.output) assert data["branch"] == "dev" assert data["domain"] == "code" def test_dir_arg_human_output_shows_target_muse_dir(self, tmp_path: pathlib.Path) -> None: target = tmp_path / "human_repo" result = _init_from(tmp_path, str(target)) assert result.exit_code == 0, result.output muse_dir_str = str(muse_dir(target)) assert muse_dir_str in result.output or str(muse_dir(target.resolve())) in result.output def test_force_flag_with_dir_arg(self, tmp_path: pathlib.Path) -> None: target = tmp_path / "force_repo" _init_from(tmp_path, str(target)) first_id = json.loads( (repo_json_path(target)).read_text() )["repo_id"] result = _init_from(tmp_path, str(target), "--force") assert result.exit_code == 0, result.output second_id = json.loads( (repo_json_path(target)).read_text() )["repo_id"] assert first_id == second_id def test_bare_flag_with_dir_arg(self, tmp_path: pathlib.Path) -> None: target = tmp_path / "bare_repo" result = _init_from(tmp_path, str(target), "--bare") assert result.exit_code == 0, result.output assert (muse_dir(target)).is_dir() assert not (target / ".museignore").exists() def test_status_works_after_dir_arg_init(self, tmp_path: pathlib.Path) -> None: """Full round-trip: init with dir arg, then muse status inside it.""" import subprocess target = tmp_path / "rountrip" result = _init_from(tmp_path, str(target)) assert result.exit_code == 0, result.output r = subprocess.run( ["muse", "status", "--json"], capture_output=True, text=True, cwd=str(target), ) assert r.returncode == 0, r.stderr data = json.loads(r.stdout) assert data["branch"] == "main" # --------------------------------------------------------------------------- # Default hub remotes # --------------------------------------------------------------------------- _HUB_LOCAL = "https://localhost:1337" _HUB_STAGING = "https://staging.musehub.ai" _HUB_PRODUCTION = "https://musehub.ai" class TestDefaultHubRemotes: """muse init pre-wires local/staging/production remotes when a handle is available in ~/.muse/identity.toml, and sets hub.url to localhost.""" def _read_config(self, repo: pathlib.Path) -> Mapping[str, object]: import tomllib cfg = config_toml_path(repo) with cfg.open("rb") as fh: return tomllib.load(fh) def _init_with_handle( self, tmp_path: pathlib.Path, handle: str, repo_name: str = "myrepo", ) -> pathlib.Path: """Init a repo whose directory is named *repo_name*, with *handle* in identity.""" from unittest.mock import patch repo = tmp_path / repo_name repo.mkdir() with patch( "muse.cli.commands.init.resolve_default_handle", return_value=handle, ): _init(repo) return repo # hub.url defaults --------------------------------------------------- def test_hub_url_defaults_to_localhost(self, tmp_path: pathlib.Path) -> None: """hub.url must be set to localhost:1337 by default.""" repo = self._init_with_handle(tmp_path, "gabriel") cfg = self._read_config(repo) assert cfg["hub"]["url"] == _HUB_LOCAL def test_hub_url_default_is_not_commented_out(self, tmp_path: pathlib.Path) -> None: """hub.url must be an active key, not a comment.""" repo = self._init_with_handle(tmp_path, "gabriel") raw = (config_toml_path(repo)).read_text(encoding="utf-8") assert "# url" not in raw # local remote ------------------------------------------------------- def test_local_remote_url_contains_handle_and_slug(self, tmp_path: pathlib.Path) -> None: """'local' remote URL must embed the handle and directory name.""" repo = self._init_with_handle(tmp_path, "gabriel", repo_name="myrepo") cfg = self._read_config(repo) assert cfg["remotes"]["local"]["url"] == f"{_HUB_LOCAL}/gabriel/myrepo" def test_local_remote_uses_localhost_base(self, tmp_path: pathlib.Path) -> None: repo = self._init_with_handle(tmp_path, "gabriel") cfg = self._read_config(repo) assert cfg["remotes"]["local"]["url"].startswith(_HUB_LOCAL) # staging remote ----------------------------------------------------- def test_staging_remote_url_contains_handle_and_slug(self, tmp_path: pathlib.Path) -> None: repo = self._init_with_handle(tmp_path, "gabriel", repo_name="myrepo") cfg = self._read_config(repo) assert cfg["remotes"]["staging"]["url"] == f"{_HUB_STAGING}/gabriel/myrepo" def test_staging_remote_uses_staging_base(self, tmp_path: pathlib.Path) -> None: repo = self._init_with_handle(tmp_path, "gabriel") cfg = self._read_config(repo) assert cfg["remotes"]["staging"]["url"].startswith(_HUB_STAGING) # production remote -------------------------------------------------- def test_production_remote_url_contains_handle_and_slug(self, tmp_path: pathlib.Path) -> None: repo = self._init_with_handle(tmp_path, "gabriel", repo_name="myrepo") cfg = self._read_config(repo) assert cfg["remotes"]["production"]["url"] == f"{_HUB_PRODUCTION}/gabriel/myrepo" def test_production_remote_uses_production_base(self, tmp_path: pathlib.Path) -> None: repo = self._init_with_handle(tmp_path, "gabriel") cfg = self._read_config(repo) assert cfg["remotes"]["production"]["url"].startswith(_HUB_PRODUCTION) # slug comes from directory name ------------------------------------ def test_slug_uses_directory_name(self, tmp_path: pathlib.Path) -> None: """The repo slug in remote URLs must be the directory name, not a hash.""" repo = self._init_with_handle(tmp_path, "gabriel", repo_name="cool-project") cfg = self._read_config(repo) assert cfg["remotes"]["local"]["url"].endswith("/gabriel/cool-project") assert cfg["remotes"]["staging"]["url"].endswith("/gabriel/cool-project") assert cfg["remotes"]["production"]["url"].endswith("/gabriel/cool-project") def test_different_handles_produce_different_urls(self, tmp_path: pathlib.Path) -> None: from unittest.mock import patch for handle in ("alice", "bob"): repo = tmp_path / handle / "repo" repo.mkdir(parents=True) with patch("muse.cli.commands.init.resolve_default_handle", return_value=handle): _init(repo) cfg = self._read_config(repo) assert f"/{handle}/" in cfg["remotes"]["local"]["url"] # no handle — graceful degradation ---------------------------------- def test_no_handle_hub_url_still_set_to_localhost(self, tmp_path: pathlib.Path) -> None: """Without a handle, hub.url is still wired to localhost.""" from unittest.mock import patch repo = tmp_path / "noidrepo" repo.mkdir() with patch("muse.cli.commands.init.resolve_default_handle", return_value=None): _init(repo) cfg = self._read_config(repo) assert cfg["hub"]["url"] == _HUB_LOCAL def test_no_handle_no_remotes_written(self, tmp_path: pathlib.Path) -> None: """Without a handle we cannot construct remote URLs — remotes stay empty.""" from unittest.mock import patch repo = tmp_path / "noidrepo" repo.mkdir() with patch("muse.cli.commands.init.resolve_default_handle", return_value=None): _init(repo) cfg = self._read_config(repo) assert cfg.get("remotes", {}) == {} # JSON output reflects remotes ------------------------------------- def test_json_output_includes_remotes_key(self, tmp_path: pathlib.Path) -> None: from unittest.mock import patch repo = tmp_path / "jrepo" repo.mkdir() with patch("muse.cli.commands.init.resolve_default_handle", return_value="gabriel"): result = _init(repo, "--json") data = json.loads(result.output) assert "remotes" in data def test_json_remotes_lists_all_three(self, tmp_path: pathlib.Path) -> None: from unittest.mock import patch repo = tmp_path / "jrepo" repo.mkdir() with patch("muse.cli.commands.init.resolve_default_handle", return_value="gabriel"): result = _init(repo, "--json") data = json.loads(result.output) assert set(data["remotes"]) == {"local", "staging", "production"} # reinit preserves existing remotes -------------------------------- def test_reinit_with_force_preserves_existing_config(self, tmp_path: pathlib.Path) -> None: """--force reinit must not overwrite an existing config.toml.""" repo = self._init_with_handle(tmp_path, "gabriel") original_url = self._read_config(repo)["remotes"]["local"]["url"] from unittest.mock import patch with patch("muse.cli.commands.init.resolve_default_handle", return_value="gabriel"): _init(repo, "--force") assert self._read_config(repo)["remotes"]["local"]["url"] == original_url