test_branch_created_meta.py
python
sha256:5eff0848db1d0748f3a195ca871a9275e7455d447690411ec2d83434e11baa65
feat(#216): persist write-once BranchMeta creation provenance
Human
minor
⚠ breaking
3 days ago
| 1 | """Seven-tier tests for BranchMeta created_by/created_at provenance (musehub#216). |
| 2 | |
| 3 | Freeze: ``docs/MUSE-216-BRANCHMETA-CREATED-BY-FREEZE.md`` §5. |
| 4 | |
| 5 | Tiers |
| 6 | ----- |
| 7 | 1 unit — resolve / write-once / load-dump / move / read helper |
| 8 | 2 integration — plain ``muse branch`` stamps TOML; tip ``created_by`` unchanged |
| 9 | 3 e2e — human create then agent tip; copy restamp; rename moves meta |
| 10 | 4 stress — 200 creates; every list entry has both new keys |
| 11 | 5 data-integrity — stamp twice; list stable; corrupt kind; delete removes section |
| 12 | 6 performance — 200-branch list < 5s |
| 13 | 7 security — ESC stripped; no key material; quoted handle round-trips |
| 14 | """ |
| 15 | |
| 16 | from __future__ import annotations |
| 17 | |
| 18 | import json |
| 19 | import pathlib |
| 20 | import time |
| 21 | import tomllib |
| 22 | from collections.abc import Mapping |
| 23 | from unittest import mock |
| 24 | |
| 25 | import pytest |
| 26 | |
| 27 | from tests.cli_test_helper import CliRunner |
| 28 | |
| 29 | cli = None |
| 30 | runner = CliRunner() |
| 31 | |
| 32 | _HUB_URL = "https://localhost:1337" |
| 33 | |
| 34 | |
| 35 | def _env(root: pathlib.Path) -> Mapping[str, str]: |
| 36 | return {"MUSE_REPO_ROOT": str(root)} |
| 37 | |
| 38 | |
| 39 | def _branch_json(root: pathlib.Path) -> list[dict]: |
| 40 | result = runner.invoke(cli, ["branch", "--json"], env=_env(root)) |
| 41 | assert result.exit_code == 0, f"branch --json failed:\n{result.output}" |
| 42 | data = json.loads(result.output.strip()) |
| 43 | assert isinstance(data, list) |
| 44 | return data |
| 45 | |
| 46 | |
| 47 | def _entry(root: pathlib.Path, name: str) -> dict: |
| 48 | return next(b for b in _branch_json(root) if b["name"] == name) |
| 49 | |
| 50 | |
| 51 | def _toml_branch(root: pathlib.Path, name: str) -> Mapping[str, object]: |
| 52 | from muse.core.paths import config_toml_path |
| 53 | |
| 54 | with config_toml_path(root).open("rb") as f: |
| 55 | data = tomllib.load(f) |
| 56 | return data.get("branch", {}).get(name, {}) |
| 57 | |
| 58 | |
| 59 | def _set_user_handle(root: pathlib.Path, handle: str) -> None: |
| 60 | """Wire identity so ``get_config_value("user.handle", root)`` returns *handle*.""" |
| 61 | from muse.cli.config import set_hub_url |
| 62 | from muse.core.identity import save_identity |
| 63 | |
| 64 | set_hub_url(_HUB_URL, root) |
| 65 | save_identity(_HUB_URL, { |
| 66 | "type": "human", |
| 67 | "handle": handle, |
| 68 | "algorithm": "ed25519", |
| 69 | "fingerprint": "0" * 64, |
| 70 | "capabilities": [], |
| 71 | "provisioned_by": "", |
| 72 | "hd_path": "", |
| 73 | "provisioned_by_fingerprint": "", |
| 74 | }) |
| 75 | |
| 76 | |
| 77 | @pytest.fixture() |
| 78 | def repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path: |
| 79 | monkeypatch.chdir(tmp_path) |
| 80 | monkeypatch.delenv("MUSE_AGENT_ID", raising=False) |
| 81 | env = _env(tmp_path) |
| 82 | r = runner.invoke(cli, ["init", "--domain", "code"], env=env) |
| 83 | assert r.exit_code == 0, r.output |
| 84 | (tmp_path / "a.py").write_text("x = 1\n") |
| 85 | runner.invoke(cli, ["code", "add", "a.py"], env=env) |
| 86 | cr = runner.invoke(cli, ["commit", "-m", "initial", "--author", "seed"], env=env) |
| 87 | assert cr.exit_code == 0, cr.output |
| 88 | return tmp_path |
| 89 | |
| 90 | |
| 91 | # --------------------------------------------------------------------------- |
| 92 | # Tier 1 — unit |
| 93 | # --------------------------------------------------------------------------- |
| 94 | |
| 95 | |
| 96 | class TestResolveAndWriteOnceUnit: |
| 97 | """Tier 1: helpers in ``muse.cli.config`` and list read helper.""" |
| 98 | |
| 99 | def test_resolve_agent_env_wins( |
| 100 | self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 101 | ) -> None: |
| 102 | from muse.cli.config import _resolve_branch_creator |
| 103 | |
| 104 | _set_user_handle(repo, "aaronrene") |
| 105 | monkeypatch.setenv("MUSE_AGENT_ID", "claude-code") |
| 106 | assert _resolve_branch_creator(repo) == ("claude-code", "agent") |
| 107 | |
| 108 | def test_resolve_human_from_handle( |
| 109 | self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 110 | ) -> None: |
| 111 | from muse.cli.config import _resolve_branch_creator |
| 112 | |
| 113 | monkeypatch.delenv("MUSE_AGENT_ID", raising=False) |
| 114 | _set_user_handle(repo, "aaronrene") |
| 115 | assert _resolve_branch_creator(repo) == ("aaronrene", "human") |
| 116 | |
| 117 | def test_resolve_both_empty_returns_none( |
| 118 | self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 119 | ) -> None: |
| 120 | from muse.cli.config import _resolve_branch_creator |
| 121 | |
| 122 | monkeypatch.delenv("MUSE_AGENT_ID", raising=False) |
| 123 | with mock.patch( |
| 124 | "muse.cli.config.get_config_value", |
| 125 | return_value=None, |
| 126 | ): |
| 127 | assert _resolve_branch_creator(repo) is None |
| 128 | |
| 129 | def test_resolve_whitespace_env_falls_to_human( |
| 130 | self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 131 | ) -> None: |
| 132 | from muse.cli.config import _resolve_branch_creator |
| 133 | |
| 134 | monkeypatch.setenv("MUSE_AGENT_ID", " ") |
| 135 | _set_user_handle(repo, "aaronrene") |
| 136 | assert _resolve_branch_creator(repo) == ("aaronrene", "human") |
| 137 | |
| 138 | def test_resolve_esc_only_env_returns_none( |
| 139 | self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 140 | ) -> None: |
| 141 | from muse.cli.config import _resolve_branch_creator |
| 142 | |
| 143 | monkeypatch.setenv("MUSE_AGENT_ID", "\x1b\x07\x1f") |
| 144 | with mock.patch( |
| 145 | "muse.cli.config.get_config_value", |
| 146 | return_value=None, |
| 147 | ): |
| 148 | assert _resolve_branch_creator(repo) is None |
| 149 | |
| 150 | def test_write_once_second_stamp_preserves_created_at( |
| 151 | self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 152 | ) -> None: |
| 153 | from muse.cli.config import read_branch_meta, stamp_branch_created, write_branch_meta |
| 154 | |
| 155 | monkeypatch.delenv("MUSE_AGENT_ID", raising=False) |
| 156 | _set_user_handle(repo, "aaronrene") |
| 157 | stamp_branch_created(repo, "feat/once") |
| 158 | first = read_branch_meta(repo, "feat/once")["created_at"] |
| 159 | assert first |
| 160 | write_branch_meta(repo, "feat/once", intent="later") |
| 161 | stamp_branch_created(repo, "feat/once") |
| 162 | second = read_branch_meta(repo, "feat/once") |
| 163 | assert second["created_at"] == first |
| 164 | assert second["created_by"] == "aaronrene" |
| 165 | assert second["intent"] == "later" |
| 166 | |
| 167 | def test_load_dump_round_trip_creation_and_intent( |
| 168 | self, repo: pathlib.Path |
| 169 | ) -> None: |
| 170 | from muse.cli.config import ( |
| 171 | _dump_toml, |
| 172 | _load_config, |
| 173 | write_branch_meta, |
| 174 | ) |
| 175 | from muse.core.paths import config_toml_path |
| 176 | |
| 177 | write_branch_meta( |
| 178 | repo, |
| 179 | "feat/rt", |
| 180 | intent="keep me", |
| 181 | created_by='alice"bob', |
| 182 | created_by_kind="human", |
| 183 | created_at="2026-09-17T10:54:10.123456+00:00", |
| 184 | ) |
| 185 | # Preserve a remote key via a second write that must not drop creation. |
| 186 | write_branch_meta(repo, "feat/rt", resumable=True) |
| 187 | # Inject remote by rewriting through load/dump helpers. |
| 188 | cp = config_toml_path(repo) |
| 189 | cfg = _load_config(cp) |
| 190 | cfg["branch"]["feat/rt"]["remote"] = "origin" |
| 191 | cp.write_text(_dump_toml(cfg), encoding="utf-8") |
| 192 | again = _load_config(cp) |
| 193 | sec = again["branch"]["feat/rt"] |
| 194 | assert sec["intent"] == "keep me" |
| 195 | assert sec["remote"] == "origin" |
| 196 | assert sec["created_by"] == 'alice"bob' |
| 197 | assert sec["created_by_kind"] == "human" |
| 198 | assert sec["created_at"] == "2026-09-17T10:54:10.123456+00:00" |
| 199 | with cp.open("rb") as f: |
| 200 | tomllib.load(f) # must remain parseable after escape |
| 201 | |
| 202 | def test_move_branch_meta_relocates_dict(self, repo: pathlib.Path) -> None: |
| 203 | from muse.cli.config import ( |
| 204 | move_branch_meta, |
| 205 | read_branch_meta, |
| 206 | write_branch_meta, |
| 207 | ) |
| 208 | |
| 209 | write_branch_meta( |
| 210 | repo, |
| 211 | "old/name", |
| 212 | intent="move me", |
| 213 | created_by="aaronrene", |
| 214 | created_by_kind="human", |
| 215 | created_at="2026-09-17T11:00:00+00:00", |
| 216 | ) |
| 217 | move_branch_meta(repo, "old/name", "new/name") |
| 218 | assert read_branch_meta(repo, "old/name") == {} |
| 219 | moved = read_branch_meta(repo, "new/name") |
| 220 | assert moved["intent"] == "move me" |
| 221 | assert moved["created_by"] == "aaronrene" |
| 222 | assert moved["created_at"] == "2026-09-17T11:00:00+00:00" |
| 223 | |
| 224 | def test_branch_created_from_meta_invalid_kind_null(self) -> None: |
| 225 | from muse.cli.commands.branch import _branch_created_from_meta |
| 226 | |
| 227 | by, at = _branch_created_from_meta( |
| 228 | { |
| 229 | "created_by": "aaronrene", |
| 230 | "created_by_kind": "robot", |
| 231 | "created_at": "2026-09-17T11:00:00+00:00", |
| 232 | } |
| 233 | ) |
| 234 | assert by is None |
| 235 | assert at == "2026-09-17T11:00:00+00:00" |
| 236 | |
| 237 | def test_branch_created_from_meta_naive_at_null(self) -> None: |
| 238 | from muse.cli.commands.branch import _branch_created_from_meta |
| 239 | |
| 240 | by, at = _branch_created_from_meta( |
| 241 | { |
| 242 | "created_by": "aaronrene", |
| 243 | "created_by_kind": "human", |
| 244 | "created_at": "2026-09-17T11:00:00", |
| 245 | } |
| 246 | ) |
| 247 | assert by == {"handle": "aaronrene", "kind": "human"} |
| 248 | assert at is None |
| 249 | |
| 250 | |
| 251 | # --------------------------------------------------------------------------- |
| 252 | # Tier 2 — integration |
| 253 | # --------------------------------------------------------------------------- |
| 254 | |
| 255 | |
| 256 | class TestStampIntegration: |
| 257 | def test_plain_branch_stamps_toml_and_json( |
| 258 | self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 259 | ) -> None: |
| 260 | monkeypatch.delenv("MUSE_AGENT_ID", raising=False) |
| 261 | _set_user_handle(repo, "aaronrene") |
| 262 | env = _env(repo) |
| 263 | r = runner.invoke(cli, ["branch", "feat/x"], env=env) |
| 264 | assert r.exit_code == 0, r.output |
| 265 | sec = _toml_branch(repo, "feat/x") |
| 266 | assert "created_at" in sec |
| 267 | assert sec["created_by"] == "aaronrene" |
| 268 | assert sec["created_by_kind"] == "human" |
| 269 | entry = _entry(repo, "feat/x") |
| 270 | assert entry["branch_created_at"] is not None |
| 271 | assert entry["branch_created_by"] == { |
| 272 | "handle": "aaronrene", |
| 273 | "kind": "human", |
| 274 | } |
| 275 | # Tip authorship (#215) stays tip author, not BranchMeta handle object. |
| 276 | assert entry["created_by"] == "seed" |
| 277 | assert isinstance(entry["created_by"], str) |
| 278 | |
| 279 | |
| 280 | # --------------------------------------------------------------------------- |
| 281 | # Tier 3 — e2e |
| 282 | # --------------------------------------------------------------------------- |
| 283 | |
| 284 | |
| 285 | class TestCreateCopyRenameE2E: |
| 286 | def test_human_create_then_agent_tip_preserves_branch_creator( |
| 287 | self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 288 | ) -> None: |
| 289 | monkeypatch.delenv("MUSE_AGENT_ID", raising=False) |
| 290 | _set_user_handle(repo, "aaronrene") |
| 291 | env = _env(repo) |
| 292 | assert runner.invoke(cli, ["branch", "feat/stable"], env=env).exit_code == 0 |
| 293 | assert runner.invoke(cli, ["checkout", "feat/stable"], env=env).exit_code == 0 |
| 294 | (repo / "b.py").write_text("y = 2\n") |
| 295 | runner.invoke(cli, ["code", "add", "b.py"], env=env) |
| 296 | cr = runner.invoke( |
| 297 | cli, |
| 298 | [ |
| 299 | "commit", |
| 300 | "-m", |
| 301 | "agent tip", |
| 302 | "--author", |
| 303 | "aaronrene", |
| 304 | "--agent-id", |
| 305 | "claude-code", |
| 306 | ], |
| 307 | env=env, |
| 308 | ) |
| 309 | assert cr.exit_code == 0, cr.output |
| 310 | entry = _entry(repo, "feat/stable") |
| 311 | assert entry["created_by"] == "claude-code" |
| 312 | assert entry["branch_created_by"] == { |
| 313 | "handle": "aaronrene", |
| 314 | "kind": "human", |
| 315 | } |
| 316 | |
| 317 | def test_copy_dest_gets_new_created_at( |
| 318 | self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 319 | ) -> None: |
| 320 | monkeypatch.delenv("MUSE_AGENT_ID", raising=False) |
| 321 | _set_user_handle(repo, "aaronrene") |
| 322 | env = _env(repo) |
| 323 | assert runner.invoke(cli, ["branch", "feat/src"], env=env).exit_code == 0 |
| 324 | src_at = _entry(repo, "feat/src")["branch_created_at"] |
| 325 | assert src_at is not None |
| 326 | time.sleep(0.01) |
| 327 | assert runner.invoke( |
| 328 | cli, ["branch", "-c", "feat/src", "feat/dst"], env=env |
| 329 | ).exit_code == 0 |
| 330 | dst = _entry(repo, "feat/dst") |
| 331 | src = _entry(repo, "feat/src") |
| 332 | assert dst["branch_created_at"] is not None |
| 333 | assert dst["branch_created_at"] >= src_at |
| 334 | assert src["branch_created_at"] == src_at |
| 335 | assert dst["branch_created_by"] == { |
| 336 | "handle": "aaronrene", |
| 337 | "kind": "human", |
| 338 | } |
| 339 | |
| 340 | def test_force_copy_restamps_dest( |
| 341 | self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 342 | ) -> None: |
| 343 | """F15: force-copy deletes dest meta then stamps a new creation triple.""" |
| 344 | monkeypatch.delenv("MUSE_AGENT_ID", raising=False) |
| 345 | _set_user_handle(repo, "aaronrene") |
| 346 | env = _env(repo) |
| 347 | assert runner.invoke(cli, ["branch", "feat/src2"], env=env).exit_code == 0 |
| 348 | assert runner.invoke(cli, ["branch", "feat/old-dst"], env=env).exit_code == 0 |
| 349 | old_at = _toml_branch(repo, "feat/old-dst")["created_at"] |
| 350 | time.sleep(0.01) |
| 351 | assert runner.invoke( |
| 352 | cli, ["branch", "-C", "feat/src2", "feat/old-dst"], env=env |
| 353 | ).exit_code == 0 |
| 354 | new_at = _toml_branch(repo, "feat/old-dst")["created_at"] |
| 355 | assert new_at != old_at |
| 356 | assert _entry(repo, "feat/old-dst")["branch_created_by"] == { |
| 357 | "handle": "aaronrene", |
| 358 | "kind": "human", |
| 359 | } |
| 360 | |
| 361 | def test_rename_moves_creation_triple( |
| 362 | self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 363 | ) -> None: |
| 364 | monkeypatch.delenv("MUSE_AGENT_ID", raising=False) |
| 365 | _set_user_handle(repo, "aaronrene") |
| 366 | env = _env(repo) |
| 367 | assert runner.invoke(cli, ["branch", "feat/before"], env=env).exit_code == 0 |
| 368 | before = _entry(repo, "feat/before") |
| 369 | triple = ( |
| 370 | before["branch_created_by"], |
| 371 | before["branch_created_at"], |
| 372 | ) |
| 373 | assert runner.invoke( |
| 374 | cli, ["branch", "-m", "feat/before", "feat/after"], env=env |
| 375 | ).exit_code == 0 |
| 376 | names = {b["name"] for b in _branch_json(repo)} |
| 377 | assert "feat/before" not in names |
| 378 | after = _entry(repo, "feat/after") |
| 379 | assert (after["branch_created_by"], after["branch_created_at"]) == triple |
| 380 | assert "feat/before" not in _toml_branch(repo, "feat/before") or True |
| 381 | assert "created_at" not in _toml_branch(repo, "feat/before") |
| 382 | assert _toml_branch(repo, "feat/after")["created_by"] == "aaronrene" |
| 383 | |
| 384 | |
| 385 | # --------------------------------------------------------------------------- |
| 386 | # Tier 4 — stress |
| 387 | # --------------------------------------------------------------------------- |
| 388 | |
| 389 | |
| 390 | class TestStress200: |
| 391 | def test_200_creates_list_keys_present( |
| 392 | self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 393 | ) -> None: |
| 394 | monkeypatch.delenv("MUSE_AGENT_ID", raising=False) |
| 395 | _set_user_handle(repo, "aaronrene") |
| 396 | env = _env(repo) |
| 397 | for i in range(200): |
| 398 | r = runner.invoke(cli, ["branch", f"stress-{i:03d}"], env=env) |
| 399 | assert r.exit_code == 0, r.output |
| 400 | data = _branch_json(repo) |
| 401 | assert len(data) == 201 |
| 402 | for entry in data: |
| 403 | assert "branch_created_by" in entry |
| 404 | assert "branch_created_at" in entry |
| 405 | stamped = [b for b in data if b["name"].startswith("stress-")] |
| 406 | assert all(b["branch_created_at"] is not None for b in stamped) |
| 407 | |
| 408 | |
| 409 | # --------------------------------------------------------------------------- |
| 410 | # Tier 5 — data integrity |
| 411 | # --------------------------------------------------------------------------- |
| 412 | |
| 413 | |
| 414 | class TestDataIntegrity: |
| 415 | def test_intent_update_preserves_created_at_bytes( |
| 416 | self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 417 | ) -> None: |
| 418 | monkeypatch.delenv("MUSE_AGENT_ID", raising=False) |
| 419 | _set_user_handle(repo, "aaronrene") |
| 420 | env = _env(repo) |
| 421 | assert runner.invoke(cli, ["branch", "feat/di"], env=env).exit_code == 0 |
| 422 | first = _toml_branch(repo, "feat/di")["created_at"] |
| 423 | assert runner.invoke( |
| 424 | cli, ["branch", "feat/di", "--intent", "update"], env=env |
| 425 | ).exit_code == 0 |
| 426 | second = _toml_branch(repo, "feat/di")["created_at"] |
| 427 | assert second == first |
| 428 | assert isinstance(second, str) |
| 429 | |
| 430 | def test_list_twice_identical_branch_created( |
| 431 | self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 432 | ) -> None: |
| 433 | monkeypatch.delenv("MUSE_AGENT_ID", raising=False) |
| 434 | _set_user_handle(repo, "aaronrene") |
| 435 | env = _env(repo) |
| 436 | assert runner.invoke(cli, ["branch", "feat/stable-list"], env=env).exit_code == 0 |
| 437 | a = _entry(repo, "feat/stable-list") |
| 438 | b = _entry(repo, "feat/stable-list") |
| 439 | assert a["branch_created_by"] == b["branch_created_by"] |
| 440 | assert a["branch_created_at"] == b["branch_created_at"] |
| 441 | |
| 442 | def test_corrupt_kind_json_null_listing_ok( |
| 443 | self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 444 | ) -> None: |
| 445 | from muse.core.paths import config_toml_path |
| 446 | |
| 447 | monkeypatch.delenv("MUSE_AGENT_ID", raising=False) |
| 448 | env = _env(repo) |
| 449 | assert runner.invoke(cli, ["branch", "feat/corrupt"], env=env).exit_code == 0 |
| 450 | cp = config_toml_path(repo) |
| 451 | text = cp.read_text(encoding="utf-8") |
| 452 | text = text.replace( |
| 453 | 'created_by_kind = "human"', |
| 454 | 'created_by_kind = "robot"', |
| 455 | 1, |
| 456 | ) |
| 457 | # When identity was None, inject a corrupt pair directly. |
| 458 | if 'created_by_kind = "robot"' not in text: |
| 459 | text += ( |
| 460 | '\n[branch."feat/corrupt"]\n' |
| 461 | 'created_by = "aaronrene"\n' |
| 462 | 'created_by_kind = "robot"\n' |
| 463 | 'created_at = "2026-09-17T12:00:00+00:00"\n' |
| 464 | ) |
| 465 | cp.write_text(text, encoding="utf-8") |
| 466 | else: |
| 467 | cp.write_text(text, encoding="utf-8") |
| 468 | result = runner.invoke(cli, ["branch", "--json"], env=env) |
| 469 | assert result.exit_code == 0, result.output |
| 470 | entry = _entry(repo, "feat/corrupt") |
| 471 | assert entry["branch_created_by"] is None |
| 472 | |
| 473 | def test_delete_removes_branch_section( |
| 474 | self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 475 | ) -> None: |
| 476 | monkeypatch.delenv("MUSE_AGENT_ID", raising=False) |
| 477 | _set_user_handle(repo, "aaronrene") |
| 478 | env = _env(repo) |
| 479 | assert runner.invoke(cli, ["branch", "feat/gone"], env=env).exit_code == 0 |
| 480 | assert "created_at" in _toml_branch(repo, "feat/gone") |
| 481 | assert runner.invoke(cli, ["branch", "-D", "feat/gone"], env=env).exit_code == 0 |
| 482 | assert _toml_branch(repo, "feat/gone") == {} |
| 483 | |
| 484 | |
| 485 | # --------------------------------------------------------------------------- |
| 486 | # Tier 6 — performance |
| 487 | # --------------------------------------------------------------------------- |
| 488 | |
| 489 | |
| 490 | class TestPerformance: |
| 491 | def test_200_branch_list_under_5s( |
| 492 | self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 493 | ) -> None: |
| 494 | monkeypatch.delenv("MUSE_AGENT_ID", raising=False) |
| 495 | _set_user_handle(repo, "aaronrene") |
| 496 | env = _env(repo) |
| 497 | for i in range(200): |
| 498 | runner.invoke(cli, ["branch", f"perf-{i:03d}"], env=env) |
| 499 | t0 = time.perf_counter() |
| 500 | data = _branch_json(repo) |
| 501 | elapsed = time.perf_counter() - t0 |
| 502 | assert len(data) == 201 |
| 503 | assert elapsed < 5.0, f"200-branch list took {elapsed:.2f}s (limit 5s)" |
| 504 | |
| 505 | |
| 506 | # --------------------------------------------------------------------------- |
| 507 | # Tier 7 — security |
| 508 | # --------------------------------------------------------------------------- |
| 509 | |
| 510 | |
| 511 | class TestSecurity: |
| 512 | def test_esc_in_agent_id_not_in_toml_or_json( |
| 513 | self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 514 | ) -> None: |
| 515 | monkeypatch.setenv("MUSE_AGENT_ID", "\x1b[31mbad-agent\x1b[0m") |
| 516 | env = {**_env(repo), "MUSE_AGENT_ID": "\x1b[31mbad-agent\x1b[0m"} |
| 517 | r = runner.invoke(cli, ["branch", "feat/sec"], env=env) |
| 518 | assert r.exit_code == 0, r.output |
| 519 | raw = (repo / ".muse" / "config.toml").read_text(encoding="utf-8") |
| 520 | assert "\x1b" not in raw |
| 521 | out = runner.invoke(cli, ["branch", "--json"], env=_env(repo)).output |
| 522 | assert "\x1b" not in out |
| 523 | entry = _entry(repo, "feat/sec") |
| 524 | # ESC bytes stripped; printable remnant remains after sanitize_provenance. |
| 525 | assert entry["branch_created_by"] == { |
| 526 | "handle": "[31mbad-agent[0m", |
| 527 | "kind": "agent", |
| 528 | } |
| 529 | assert "\x1b" not in entry["branch_created_by"]["handle"] |
| 530 | |
| 531 | def test_no_key_material_in_output( |
| 532 | self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 533 | ) -> None: |
| 534 | monkeypatch.setenv("MUSE_AGENT_ID", "safe-agent") |
| 535 | monkeypatch.setenv("MUSE_AGENT_KEY", "super-secret-key-material") |
| 536 | env = { |
| 537 | **_env(repo), |
| 538 | "MUSE_AGENT_ID": "safe-agent", |
| 539 | "MUSE_AGENT_KEY": "super-secret-key-material", |
| 540 | } |
| 541 | assert runner.invoke(cli, ["branch", "feat/nokey"], env=env).exit_code == 0 |
| 542 | out = runner.invoke(cli, ["branch", "--json"], env=env).output |
| 543 | assert "super-secret-key-material" not in out |
| 544 | assert "MUSE_AGENT_KEY" not in out |
| 545 | raw = (repo / ".muse" / "config.toml").read_text(encoding="utf-8") |
| 546 | assert "super-secret-key-material" not in raw |
| 547 | |
| 548 | def test_quote_in_handle_round_trips_toml( |
| 549 | self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 550 | ) -> None: |
| 551 | from muse.core.paths import config_toml_path |
| 552 | |
| 553 | monkeypatch.delenv("MUSE_AGENT_ID", raising=False) |
| 554 | env = _env(repo) |
| 555 | with mock.patch( |
| 556 | "muse.cli.config.get_config_value", |
| 557 | side_effect=lambda key, root=None: ( |
| 558 | 'alice"bob' if key == "user.handle" else None |
| 559 | ), |
| 560 | ): |
| 561 | r = runner.invoke(cli, ["branch", "feat/quote"], env=env) |
| 562 | assert r.exit_code == 0, r.output |
| 563 | with config_toml_path(repo).open("rb") as f: |
| 564 | data = tomllib.load(f) |
| 565 | assert data["branch"]["feat/quote"]["created_by"] == 'alice"bob' |
| 566 | entry = _entry(repo, "feat/quote") |
| 567 | assert entry["branch_created_by"] == { |
| 568 | "handle": 'alice"bob', |
| 569 | "kind": "human", |
| 570 | } |
File History
1 commit
sha256:5eff0848db1d0748f3a195ca871a9275e7455d447690411ec2d83434e11baa65
feat(#216): persist write-once BranchMeta creation provenance
Human
minor
⚠
3 days ago