generated from coulomb/repo-seed
Initial implementation
This commit is contained in:
59
tests/test_cli.py
Normal file
59
tests/test_cli.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def run_cli(*args: str) -> subprocess.CompletedProcess[str]:
|
||||
env = os.environ.copy()
|
||||
env["PYTHONPATH"] = "src"
|
||||
return subprocess.run(
|
||||
[sys.executable, "-m", "infospace_bench", *args],
|
||||
check=False,
|
||||
env=env,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
|
||||
|
||||
def test_cli_create_inspect_and_add_artifact(tmp_path: Path) -> None:
|
||||
create = run_cli(
|
||||
"create",
|
||||
str(tmp_path),
|
||||
"pilot",
|
||||
"--name",
|
||||
"Pilot Infospace",
|
||||
"--topic-domain",
|
||||
"Test Domain",
|
||||
)
|
||||
assert create.returncode == 0, create.stderr
|
||||
assert json.loads(create.stdout)["slug"] == "pilot"
|
||||
|
||||
source = tmp_path / "source.md"
|
||||
source.write_text("# Source\n", encoding="utf-8")
|
||||
add = run_cli(
|
||||
"add-artifact",
|
||||
str(tmp_path / "infospaces" / "pilot"),
|
||||
str(source),
|
||||
"--kind",
|
||||
"source",
|
||||
"--title",
|
||||
"Source",
|
||||
)
|
||||
assert add.returncode == 0, add.stderr
|
||||
assert json.loads(add.stdout)["artifact"]["id"] == "source/source.md"
|
||||
|
||||
inspect = run_cli("inspect", str(tmp_path / "infospaces" / "pilot"))
|
||||
assert inspect.returncode == 0, inspect.stderr
|
||||
payload = json.loads(inspect.stdout)
|
||||
assert payload["config"]["topic"]["domain"] == "Test Domain"
|
||||
assert payload["artifacts"][0]["title"] == "Source"
|
||||
|
||||
|
||||
def test_cli_returns_structured_error(tmp_path: Path) -> None:
|
||||
result = run_cli("inspect", str(tmp_path / "missing"))
|
||||
|
||||
assert result.returncode == 2
|
||||
payload = json.loads(result.stderr)
|
||||
assert payload["error"]["code"] == "missing_infospace"
|
||||
78
tests/test_evaluation.py
Normal file
78
tests/test_evaluation.py
Normal file
@@ -0,0 +1,78 @@
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from infospace_bench.evaluation import (
|
||||
EntityEvaluation,
|
||||
EvaluationSnapshot,
|
||||
MetricValue,
|
||||
ScoreEntry,
|
||||
diff_snapshots,
|
||||
)
|
||||
|
||||
|
||||
def test_entity_evaluation_round_trips_and_computes_overall_score() -> None:
|
||||
evaluated_at = datetime(2026, 5, 14, tzinfo=timezone.utc)
|
||||
evaluation = EntityEvaluation(
|
||||
artifact_id="source/chapter.md",
|
||||
evaluator="test",
|
||||
scores=[
|
||||
ScoreEntry("definition_precision", 4),
|
||||
ScoreEntry("provenance_quality", 5),
|
||||
],
|
||||
evaluated_at=evaluated_at,
|
||||
notes=["clear enough"],
|
||||
)
|
||||
|
||||
payload = evaluation.to_dict()
|
||||
loaded = EntityEvaluation.from_dict(payload)
|
||||
|
||||
assert payload["overall_score"] == 4.5
|
||||
assert loaded == evaluation
|
||||
|
||||
|
||||
def test_snapshot_diff_reports_added_removed_score_and_metric_changes() -> None:
|
||||
now = datetime(2026, 5, 14, tzinfo=timezone.utc)
|
||||
before = EvaluationSnapshot(
|
||||
snapshot_id="before",
|
||||
created_at=now,
|
||||
schema_name="baseline",
|
||||
artifact_count=1,
|
||||
artifact_evaluations=[
|
||||
EntityEvaluation(
|
||||
artifact_id="source/a.md",
|
||||
evaluator="test",
|
||||
scores=[ScoreEntry("quality", 3)],
|
||||
evaluated_at=now,
|
||||
)
|
||||
],
|
||||
collection_metrics=[MetricValue("coverage_ratio", 0.5)],
|
||||
)
|
||||
after = EvaluationSnapshot(
|
||||
snapshot_id="after",
|
||||
created_at=now,
|
||||
schema_name="baseline",
|
||||
artifact_count=1,
|
||||
artifact_evaluations=[
|
||||
EntityEvaluation(
|
||||
artifact_id="source/a.md",
|
||||
evaluator="test",
|
||||
scores=[ScoreEntry("quality", 4)],
|
||||
evaluated_at=now,
|
||||
),
|
||||
EntityEvaluation(
|
||||
artifact_id="source/b.md",
|
||||
evaluator="test",
|
||||
scores=[ScoreEntry("quality", 5)],
|
||||
evaluated_at=now,
|
||||
),
|
||||
],
|
||||
collection_metrics=[MetricValue("coverage_ratio", 0.75)],
|
||||
)
|
||||
|
||||
diff = diff_snapshots(before, after)
|
||||
|
||||
assert diff.added_artifacts == ["source/b.md"]
|
||||
assert diff.removed_artifacts == []
|
||||
assert diff.score_changes[0].artifact_id == "source/a.md"
|
||||
assert diff.score_changes[0].delta == 1
|
||||
assert diff.metric_changes[0].name == "coverage_ratio"
|
||||
assert diff.metric_changes[0].delta == 0.25
|
||||
59
tests/test_inspection.py
Normal file
59
tests/test_inspection.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from infospace_bench.checks import run_collection_checks
|
||||
from infospace_bench.inspection import export_mermaid, relationship_summary
|
||||
from infospace_bench.models import KnowledgeArtifact, ViabilityThreshold
|
||||
from infospace_bench.viability import evaluate_viability
|
||||
|
||||
|
||||
def artifacts() -> list[KnowledgeArtifact]:
|
||||
return [
|
||||
KnowledgeArtifact(
|
||||
id="source/a.md",
|
||||
path="artifacts/sources/a.md",
|
||||
kind="source",
|
||||
title="A",
|
||||
relationships=[{"type": "supports", "target": "generated/b.md"}],
|
||||
),
|
||||
KnowledgeArtifact(
|
||||
id="generated/b.md",
|
||||
path="artifacts/generated/b.md",
|
||||
kind="generated",
|
||||
title="B",
|
||||
relationships=[{"type": "refines", "target": "source/a.md"}],
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def test_collection_checks_produce_viability_metrics() -> None:
|
||||
report = run_collection_checks(artifacts())
|
||||
|
||||
assert report.metrics["redundancy_ratio"] == 0
|
||||
assert report.metrics["coverage_ratio"] == 1
|
||||
assert report.metrics["coherence_components"] == 1
|
||||
assert report.metrics["consistency_cycles"] == 1
|
||||
assert report.metrics["granularity_entropy"] == 1
|
||||
|
||||
|
||||
def test_viability_reports_per_threshold_and_overall_result() -> None:
|
||||
report = evaluate_viability(
|
||||
{"coverage_ratio": 0.75, "consistency_cycles": 1},
|
||||
{
|
||||
"coverage_ratio": ViabilityThreshold(min=0.5),
|
||||
"consistency_cycles": ViabilityThreshold(max=0),
|
||||
},
|
||||
)
|
||||
|
||||
assert report.passed is False
|
||||
assert report.results["coverage_ratio"].passed is True
|
||||
assert report.results["consistency_cycles"].passed is False
|
||||
|
||||
|
||||
def test_relationship_summary_and_mermaid_export() -> None:
|
||||
summary = relationship_summary(artifacts())
|
||||
|
||||
assert summary.node_count == 2
|
||||
assert summary.edge_count == 2
|
||||
assert summary.relationship_types == {"refines": 1, "supports": 1}
|
||||
|
||||
mermaid = export_mermaid(summary)
|
||||
assert "source/a.md -->|supports| generated/b.md" in mermaid
|
||||
assert "generated/b.md -->|refines| source/a.md" in mermaid
|
||||
91
tests/test_lifecycle.py
Normal file
91
tests/test_lifecycle.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from infospace_bench import (
|
||||
InfospaceError,
|
||||
add_artifact,
|
||||
create_infospace,
|
||||
load_infospace,
|
||||
)
|
||||
|
||||
|
||||
def test_create_infospace_writes_layout_and_loadable_config(tmp_path: Path) -> None:
|
||||
infospace = create_infospace(
|
||||
tmp_path,
|
||||
"wealth-vsm",
|
||||
name="Wealth of Nations through VSM",
|
||||
topic_domain="Classical Economics",
|
||||
)
|
||||
|
||||
root = tmp_path / "infospaces" / "wealth-vsm"
|
||||
assert infospace.root == root
|
||||
assert (root / "infospace.yaml").is_file()
|
||||
assert (root / "artifacts" / "sources").is_dir()
|
||||
assert (root / "artifacts" / "generated").is_dir()
|
||||
assert (root / "output" / "evaluations").is_dir()
|
||||
assert (root / "output" / "metrics").is_dir()
|
||||
assert (root / "reports").is_dir()
|
||||
assert (root / "exports").is_dir()
|
||||
|
||||
loaded = load_infospace(root)
|
||||
|
||||
assert loaded.config.slug == "wealth-vsm"
|
||||
assert loaded.config.name == "Wealth of Nations through VSM"
|
||||
assert loaded.config.topic.domain == "Classical Economics"
|
||||
assert loaded.config.topic.sources == "artifacts/sources"
|
||||
assert loaded.artifacts == []
|
||||
|
||||
|
||||
def test_create_infospace_rejects_unsafe_slug_with_structured_error(tmp_path: Path) -> None:
|
||||
with pytest.raises(InfospaceError) as raised:
|
||||
create_infospace(tmp_path, "../outside", name="Nope")
|
||||
|
||||
assert raised.value.code == "invalid_slug"
|
||||
assert raised.value.detail["slug"] == "../outside"
|
||||
|
||||
|
||||
def test_load_infospace_reports_missing_config(tmp_path: Path) -> None:
|
||||
root = tmp_path / "infospaces" / "empty"
|
||||
root.mkdir(parents=True)
|
||||
|
||||
with pytest.raises(InfospaceError) as raised:
|
||||
load_infospace(root)
|
||||
|
||||
assert raised.value.code == "missing_config"
|
||||
assert "infospace.yaml" in raised.value.message
|
||||
|
||||
|
||||
def test_add_artifact_copies_file_and_updates_manifest(tmp_path: Path) -> None:
|
||||
create_infospace(tmp_path, "pilot", name="Pilot Infospace")
|
||||
source = tmp_path / "chapter.md"
|
||||
source.write_text("# Chapter\n\nSource text.\n", encoding="utf-8")
|
||||
|
||||
artifact = add_artifact(
|
||||
tmp_path / "infospaces" / "pilot",
|
||||
source,
|
||||
kind="source",
|
||||
title="Chapter 1",
|
||||
)
|
||||
|
||||
assert artifact.id == "source/chapter.md"
|
||||
assert artifact.path == "artifacts/sources/chapter.md"
|
||||
assert (tmp_path / "infospaces" / "pilot" / artifact.path).read_text(
|
||||
encoding="utf-8"
|
||||
).startswith("# Chapter")
|
||||
|
||||
loaded = load_infospace(tmp_path / "infospaces" / "pilot")
|
||||
assert [item.id for item in loaded.artifacts] == ["source/chapter.md"]
|
||||
assert loaded.artifacts[0].title == "Chapter 1"
|
||||
|
||||
|
||||
def test_add_artifact_rejects_duplicate_manifest_entry(tmp_path: Path) -> None:
|
||||
create_infospace(tmp_path, "pilot", name="Pilot Infospace")
|
||||
source = tmp_path / "chapter.md"
|
||||
source.write_text("# Chapter\n", encoding="utf-8")
|
||||
add_artifact(tmp_path / "infospaces" / "pilot", source, kind="source")
|
||||
|
||||
with pytest.raises(InfospaceError) as raised:
|
||||
add_artifact(tmp_path / "infospaces" / "pilot", source, kind="source")
|
||||
|
||||
assert raised.value.code == "duplicate_artifact"
|
||||
27
tests/test_reference_pilot.py
Normal file
27
tests/test_reference_pilot.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from pathlib import Path
|
||||
|
||||
from infospace_bench import load_infospace
|
||||
from infospace_bench.checks import run_collection_checks
|
||||
from infospace_bench.viability import evaluate_viability
|
||||
|
||||
|
||||
def test_reference_pilot_is_loadable_and_viable() -> None:
|
||||
root = Path("infospaces/bootstrap-pilot")
|
||||
|
||||
infospace = load_infospace(root)
|
||||
metrics = run_collection_checks(infospace.artifacts).metrics
|
||||
viability = evaluate_viability(metrics, infospace.config.viability)
|
||||
|
||||
assert infospace.config.slug == "bootstrap-pilot"
|
||||
assert len(infospace.artifacts) == 4
|
||||
assert metrics["coverage_ratio"] == 1
|
||||
assert metrics["coherence_components"] == 1
|
||||
assert viability.passed is True
|
||||
|
||||
|
||||
def test_reference_pilot_has_traceable_decision_and_report() -> None:
|
||||
decision = Path("docs/reference-pilot-decision.md")
|
||||
report = Path("infospaces/bootstrap-pilot/reports/baseline-inspection.md")
|
||||
|
||||
assert "small purpose-built corpus" in decision.read_text(encoding="utf-8")
|
||||
assert "Migration from markitect-main" in report.read_text(encoding="utf-8")
|
||||
Reference in New Issue
Block a user