test_workspace_unit.py
python
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
1 day ago
| 1 | """Unit tests for multi-repo workspace lanes (§MR.10 unit).""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | from pathlib import Path |
| 6 | |
| 7 | import pytest |
| 8 | import yaml |
| 9 | |
| 10 | from tools.workspace.board_names import ( |
| 11 | board_name_violation, |
| 12 | expected_handover_basename, |
| 13 | is_bare_legacy_basename, |
| 14 | repo_slug, |
| 15 | ) |
| 16 | from tools.workspace.manifest import expand_root, validate_manifest_dict |
| 17 | from tools.workspace.next_extract import ( |
| 18 | extract_next_blocks, |
| 19 | legacy_forbidden_archived_headings, |
| 20 | tip_hash_hex, |
| 21 | ) |
| 22 | from tools.workspace.types import WorkspaceLoadError |
| 23 | from tests.fixtures.workspace import build_two_repo_constellation, primary_fence |
| 24 | |
| 25 | |
| 26 | def test_repo_slug_normalization() -> None: |
| 27 | assert repo_slug("overseer-kit") == "OVERSEER-KIT" |
| 28 | assert repo_slug("scooling") == "SCOOLING" |
| 29 | assert repo_slug("foo__bar!!") == "FOO-BAR" |
| 30 | assert expected_handover_basename("scooling") == "SCOOLING-OVERSEER-HANDOVER.md" |
| 31 | |
| 32 | |
| 33 | def test_bare_vs_prefixed_basename_classifier() -> None: |
| 34 | assert is_bare_legacy_basename("OVERSEER-HANDOVER.md", kind="handover") |
| 35 | assert is_bare_legacy_basename("roadmap.md", kind="roadmap") |
| 36 | assert board_name_violation( |
| 37 | repo_name="knowtation", |
| 38 | handover_basename="OVERSEER-HANDOVER.md", |
| 39 | roadmap_basename="ROADMAP.md", |
| 40 | strict=True, |
| 41 | ) |
| 42 | assert not board_name_violation( |
| 43 | repo_name="knowtation", |
| 44 | handover_basename="KNOWTATION-OVERSEER-HANDOVER.md", |
| 45 | roadmap_basename="KNOWTATION-ROADMAP.md", |
| 46 | strict=True, |
| 47 | ) |
| 48 | |
| 49 | |
| 50 | def test_root_env_and_home_expansion(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: |
| 51 | monkeypatch.setenv("WS_ROOT", str(tmp_path / "app")) |
| 52 | assert expand_root("${WS_ROOT}", environ=dict(**{"WS_ROOT": str(tmp_path / "app")})) == str( |
| 53 | tmp_path / "app" |
| 54 | ) |
| 55 | assert expand_root("${MISSING:-~/fallback}", home=tmp_path).endswith("fallback") |
| 56 | assert expand_root("~/proj", home=tmp_path) == str(tmp_path / "proj") |
| 57 | |
| 58 | |
| 59 | def test_manifest_rejects_bad_cardinality_and_secrets() -> None: |
| 60 | base = { |
| 61 | "overseer_workspace_version": 1, |
| 62 | "id": "x", |
| 63 | "product_order_member": "a", |
| 64 | "members": [ |
| 65 | { |
| 66 | "id": "a", |
| 67 | "role": "product_order", |
| 68 | "root": "/tmp/a", |
| 69 | "regime": "git-only", |
| 70 | "required": True, |
| 71 | "relay": False, |
| 72 | } |
| 73 | ], |
| 74 | "lanes": [{"id": "product", "primary": True}], |
| 75 | } |
| 76 | validate_manifest_dict(base, source_path=Path("/tmp/ws.yaml"), manifest_source="local_workspace") |
| 77 | |
| 78 | bad = dict(base) |
| 79 | bad["members"] = list(base["members"]) + [ |
| 80 | { |
| 81 | "id": "b", |
| 82 | "role": "product_order", |
| 83 | "root": "/tmp/b", |
| 84 | "regime": "git-only", |
| 85 | "required": True, |
| 86 | "relay": False, |
| 87 | } |
| 88 | ] |
| 89 | with pytest.raises(WorkspaceLoadError): |
| 90 | validate_manifest_dict(bad, source_path=Path("/tmp/ws.yaml"), manifest_source="local_workspace") |
| 91 | |
| 92 | secret = dict(base) |
| 93 | secret["token"] = "supersecret" |
| 94 | with pytest.raises(WorkspaceLoadError): |
| 95 | validate_manifest_dict(secret, source_path=Path("/tmp/ws.yaml"), manifest_source="local_workspace") |
| 96 | |
| 97 | identity = dict(base) |
| 98 | identity["X-User-Id"] = "abc" |
| 99 | with pytest.raises(WorkspaceLoadError): |
| 100 | validate_manifest_dict(identity, source_path=Path("/tmp/ws.yaml"), manifest_source="local_workspace") |
| 101 | |
| 102 | |
| 103 | def test_regime_null_only_when_optional() -> None: |
| 104 | raw = { |
| 105 | "overseer_workspace_version": 1, |
| 106 | "id": "x", |
| 107 | "product_order_member": "a", |
| 108 | "members": [ |
| 109 | { |
| 110 | "id": "a", |
| 111 | "role": "product_order", |
| 112 | "root": "/tmp/a", |
| 113 | "regime": "git-only", |
| 114 | "required": True, |
| 115 | "relay": False, |
| 116 | }, |
| 117 | { |
| 118 | "id": "brain", |
| 119 | "role": "edge", |
| 120 | "root": "", |
| 121 | "regime": None, |
| 122 | "required": False, |
| 123 | "relay": False, |
| 124 | }, |
| 125 | ], |
| 126 | "lanes": [{"id": "product", "primary": True}], |
| 127 | } |
| 128 | validate_manifest_dict(raw, source_path=Path("/tmp/ws.yaml"), manifest_source="local_workspace") |
| 129 | raw["members"][1]["required"] = True |
| 130 | with pytest.raises(WorkspaceLoadError): |
| 131 | validate_manifest_dict(raw, source_path=Path("/tmp/ws.yaml"), manifest_source="local_workspace") |
| 132 | |
| 133 | |
| 134 | def test_marker_parse_and_tip_hash_lf_normalize() -> None: |
| 135 | fence = primary_fence(step="K13b", model="Auto", repo="/tmp/r") |
| 136 | digest = tip_hash_hex(fence) |
| 137 | assert digest == tip_hash_hex(fence.replace("\n", "\r\n")) |
| 138 | text = f""" |
| 139 | <!-- overseer:next role=primary lane=product status=live --> |
| 140 | ## NEXT SESSION — K13b (PRIMARY) |
| 141 | |
| 142 | ``` |
| 143 | {fence}``` |
| 144 | |
| 145 | <!-- overseer:next role=archived status=archived --> |
| 146 | ## ARCHIVED SESSION — old |
| 147 | |
| 148 | <!-- overseer:next role=lane_tip lane=security status=live --> |
| 149 | ## LANE TIP — sec (LANE: security) |
| 150 | |
| 151 | ``` |
| 152 | Model: Thinking |
| 153 | Repo: x |
| 154 | Branch: y |
| 155 | Step: SEC |
| 156 | Authority: lane_tip |
| 157 | ``` |
| 158 | """ |
| 159 | blocks = extract_next_blocks(text) |
| 160 | roles = [b.role.value for b in blocks] |
| 161 | assert roles == ["primary", "archived", "lane_tip"] |
| 162 | assert blocks[0].step_id == "K13b" |
| 163 | assert blocks[0].model == "Auto" |
| 164 | |
| 165 | |
| 166 | def test_forbidden_legacy_archived_heading() -> None: |
| 167 | text = "## NEXT SESSION — archived L-SEAM Thinking\n" |
| 168 | hits = legacy_forbidden_archived_headings(text) |
| 169 | assert hits and "archived" in hits[0][1].lower() |
| 170 | |
| 171 | |
| 172 | def test_freshness_true_false(tmp_path: Path) -> None: |
| 173 | from tools.workspace.check_next import check_next |
| 174 | from tools.workspace.manifest import load_manifest_file |
| 175 | |
| 176 | good = build_two_repo_constellation(tmp_path / "good", stale_relay=False) |
| 177 | manifest = load_manifest_file(good["manifest"], manifest_source="local_workspace") |
| 178 | ok = check_next(manifest) |
| 179 | assert ok.ok and ok.exit_code == 0 |
| 180 | |
| 181 | bad = build_two_repo_constellation(tmp_path / "bad", stale_relay=True) |
| 182 | stale = check_next(load_manifest_file(bad["manifest"], manifest_source="local_workspace")) |
| 183 | assert not stale.ok and stale.exit_code == 35 |
| 184 | assert stale.state == "stale_relay" |
| 185 | |
| 186 | |
| 187 | def test_strict_board_names_default_true() -> None: |
| 188 | raw = { |
| 189 | "overseer_workspace_version": 1, |
| 190 | "id": "x", |
| 191 | "product_order_member": "a", |
| 192 | "members": [ |
| 193 | { |
| 194 | "id": "a", |
| 195 | "role": "product_order", |
| 196 | "root": "/tmp/a", |
| 197 | "regime": "git-only", |
| 198 | "required": True, |
| 199 | "relay": False, |
| 200 | } |
| 201 | ], |
| 202 | "lanes": [{"id": "product", "primary": True}], |
| 203 | } |
| 204 | m = validate_manifest_dict(raw, source_path=Path("/tmp/ws.yaml"), manifest_source="local_workspace") |
| 205 | assert m.strict_markers is True |
| 206 | assert m.strict_board_names is True |
| 207 | |
| 208 | |
| 209 | def test_workspace_config_parse(tmp_path: Path) -> None: |
| 210 | from adapters.config import load_config |
| 211 | |
| 212 | cfg_path = tmp_path / "config.yaml" |
| 213 | data = { |
| 214 | "overseer_config_version": 1, |
| 215 | "repo": {"name": "demo", "root_relative_docs": "docs"}, |
| 216 | "vcs": { |
| 217 | "regime": "git-only", |
| 218 | "canonical": "git", |
| 219 | "git": { |
| 220 | "remote": "origin", |
| 221 | "main_branch": "main", |
| 222 | "mirror_branch": None, |
| 223 | "feature_branch_pattern": "feat/{slug}", |
| 224 | }, |
| 225 | "muse": {"staging_remote": None, "main_branch": None}, |
| 226 | }, |
| 227 | "docs": { |
| 228 | "handover": "DEMO-OVERSEER-HANDOVER.md", |
| 229 | "roadmap": "DEMO-ROADMAP.md", |
| 230 | "coordination": None, |
| 231 | "standing_decisions": "DEMO-ROADMAP.md", |
| 232 | "handover_title": "Demo Overseer Handover", |
| 233 | "roadmap_title": "Demo Roadmap", |
| 234 | }, |
| 235 | "thresholds": {"realign_max_commits": 50, "drift_warn_only": True}, |
| 236 | "freeze_contract": { |
| 237 | "enabled": True, |
| 238 | "reviewer": { |
| 239 | "mode": "agent", |
| 240 | "model": "thinking-high", |
| 241 | "provider": "local", |
| 242 | "fallback": "human", |
| 243 | }, |
| 244 | "human_escalation": ["security"], |
| 245 | }, |
| 246 | "workspace": {"constellation_id": "demo-stack", "manifest": None, "product_order_root": None}, |
| 247 | } |
| 248 | cfg_path.write_text(yaml.safe_dump(data), encoding="utf-8") |
| 249 | cfg = load_config(cfg_path) |
| 250 | assert cfg.workspace is not None |
| 251 | assert cfg.workspace.constellation_id == "demo-stack" |
File History
1 commit
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
1 day ago