"""Tests for markitect.infospace.cli.""" from pathlib import Path import pytest from click.testing import CliRunner from markitect.infospace.cli import infospace_commands @pytest.fixture def runner(): return CliRunner() @pytest.fixture def infospace_dir(tmp_path): """Create a minimal infospace directory with config and entities.""" config_yaml = """\ topic: name: "Test Infospace" domain: "Testing" disciplines: - name: "Test Discipline" viability: coverage_ratio: min: 0.60 redundancy_ratio: max: 0.05 """ (tmp_path / "infospace.yaml").write_text(config_yaml) entities = tmp_path / "output" / "entities" entities.mkdir(parents=True) (entities / "alpha.md").write_text( "# Alpha\n\n## Definition\n\nAlpha is a test entity.\n\n" "## Source Chapter\n\nChapter 1\n\n" "## Domain\n\nProduction\n" ) (entities / "beta.md").write_text( "# Beta\n\n## Definition\n\nBeta is another test entity with more words " "to make it longer.\n\n" "## Source Chapter\n\nChapter 2\n\n" "## Domain\n\nDistribution\n" ) return tmp_path # ── init ───────────────────────────────────────────────────────────── class TestInitCommand: def test_creates_config_file(self, runner, tmp_path): out = tmp_path / "infospace.yaml" result = runner.invoke( infospace_commands, ["init", "--topic", "My Topic", "--domain", "Science", "-o", str(out)], ) assert result.exit_code == 0 assert out.exists() assert "Created" in result.output def test_config_contains_topic(self, runner, tmp_path): out = tmp_path / "infospace.yaml" runner.invoke( infospace_commands, ["init", "--topic", "My Topic", "-o", str(out)], ) text = out.read_text() assert "My Topic" in text def test_refuses_overwrite(self, runner, tmp_path): out = tmp_path / "infospace.yaml" out.write_text("existing") result = runner.invoke( infospace_commands, ["init", "--topic", "X", "-o", str(out)], ) assert result.exit_code != 0 assert "already exists" in result.output def test_with_disciplines(self, runner, tmp_path): out = tmp_path / "infospace.yaml" result = runner.invoke( infospace_commands, [ "init", "--topic", "T", "--discipline", "VSM", "--discipline", "Category Theory", "-o", str(out), ], ) assert result.exit_code == 0 text = out.read_text() assert "VSM" in text assert "Category Theory" in text # ── status ─────────────────────────────────────────────────────────── class TestStatusCommand: def test_shows_topic_and_count(self, runner, infospace_dir): result = runner.invoke( infospace_commands, ["status", "--config", str(infospace_dir / "infospace.yaml")], ) assert result.exit_code == 0 assert "Test Infospace" in result.output assert "2" in result.output # 2 entities def test_shows_domain_field(self, runner, infospace_dir): result = runner.invoke( infospace_commands, ["status", "--config", str(infospace_dir / "infospace.yaml")], ) # Domain from config (topic.domain), not entity domains assert "Testing" in result.output def test_shows_disciplines(self, runner, infospace_dir): result = runner.invoke( infospace_commands, ["status", "--config", str(infospace_dir / "infospace.yaml")], ) assert "Test Discipline" in result.output def test_no_config_exits(self, runner, tmp_path): result = runner.invoke( infospace_commands, ["status", "--config", str(tmp_path / "nonexistent.yaml")], ) assert result.exit_code != 0 # ── entities ───────────────────────────────────────────────────────── class TestEntitiesCommand: def test_lists_entities(self, runner, infospace_dir): result = runner.invoke( infospace_commands, ["entities", "--config", str(infospace_dir / "infospace.yaml")], ) assert result.exit_code == 0 assert "alpha" in result.output assert "beta" in result.output assert "Total: 2" in result.output def test_sort_by_domain(self, runner, infospace_dir): result = runner.invoke( infospace_commands, [ "entities", "--config", str(infospace_dir / "infospace.yaml"), "--sort-by", "domain", ], ) assert result.exit_code == 0 lines = result.output.strip().split("\n") # Distribution comes before Production alphabetically data_lines = [l for l in lines if "alpha" in l or "beta" in l] assert len(data_lines) == 2 def test_no_entities_dir(self, runner, tmp_path): (tmp_path / "infospace.yaml").write_text("topic:\n name: X\n") result = runner.invoke( infospace_commands, ["entities", "--config", str(tmp_path / "infospace.yaml")], ) assert result.exit_code == 0 assert "No entities" in result.output # ── viability ──────────────────────────────────────────────────────── class TestViabilityCommand: def test_no_metrics_shows_thresholds(self, runner, infospace_dir): result = runner.invoke( infospace_commands, ["viability", "--config", str(infospace_dir / "infospace.yaml")], ) assert result.exit_code == 0 assert "coverage_ratio" in result.output def test_with_metrics_file(self, runner, infospace_dir): import yaml metrics_dir = infospace_dir / "output" / "metrics" metrics_dir.mkdir(parents=True, exist_ok=True) metrics = {"coverage_ratio": 0.85, "redundancy_ratio": 0.02} (metrics_dir / "metrics.yaml").write_text(yaml.safe_dump(metrics)) result = runner.invoke( infospace_commands, ["viability", "--config", str(infospace_dir / "infospace.yaml")], ) assert result.exit_code == 0 assert "PASS" in result.output assert "Viable: YES" in result.output def test_failing_threshold(self, runner, infospace_dir): import yaml metrics_dir = infospace_dir / "output" / "metrics" metrics_dir.mkdir(parents=True, exist_ok=True) metrics = {"coverage_ratio": 0.3, "redundancy_ratio": 0.02} (metrics_dir / "metrics.yaml").write_text(yaml.safe_dump(metrics)) result = runner.invoke( infospace_commands, ["viability", "--config", str(infospace_dir / "infospace.yaml")], ) assert result.exit_code == 0 assert "FAIL" in result.output assert "Viable: NO" in result.output def test_no_thresholds_configured(self, runner, tmp_path): (tmp_path / "infospace.yaml").write_text("topic:\n name: X\n") result = runner.invoke( infospace_commands, ["viability", "--config", str(tmp_path / "infospace.yaml")], ) assert result.exit_code == 0 assert "No viability thresholds" in result.output