Files
infospace-bench/tests/test_semantics.py
2026-05-14 15:06:17 +02:00

222 lines
6.0 KiB
Python

import json
import os
import subprocess
import sys
from pathlib import Path
import pytest
from infospace_bench import InfospaceError, add_artifact, create_infospace
from infospace_bench.semantics import (
list_entities,
list_relations,
parse_entity_artifact,
parse_relation_artifact,
)
ENTITY = """# Division of Labour
## Definition
Increasing productivity by splitting work into specialized tasks.
## Source Chapter
Book I, Chapter 1
## Context
Smith introduces the pin factory as an example of this mechanism.
## Economic Domain
Production
## Original Wording
The greatest improvement in the productive powers of labour.
## Modern Interpretation
Specialization improves throughput by reducing switching costs.
"""
INVALID_ENTITY = """# Thin Entity
## Context
This artifact is missing its definition.
"""
RELATION = """# Division of Labour enables Market Extent
## Subject
Division of Labour
## Predicate
is limited by
## Object
Market Extent
## Relation Type
constrains
## VSM Channel
S1 -> S4
## Evidence
Book I, Chapter 3 connects specialization to market size.
## Feedback Role
Part of the market expansion loop.
"""
def cli_env() -> dict[str, str]:
env = os.environ.copy()
env["PYTHONPATH"] = "src:/home/worsch/markitect-tool/src"
return env
def test_parse_entity_artifact_extracts_legacy_sections(tmp_path: Path) -> None:
path = tmp_path / "division.md"
path.write_text(ENTITY, encoding="utf-8")
entity = parse_entity_artifact("entity/division.md", path)
assert entity.artifact_id == "entity/division.md"
assert entity.slug == "division-of-labour"
assert entity.title == "Division of Labour"
assert entity.definition_word_count == 8
assert entity.domain == "Production"
assert entity.has_original_wording is True
assert entity.section_slugs == [
"definition",
"source-chapter",
"context",
"economic-domain",
"original-wording",
"modern-interpretation",
]
def test_parse_entity_artifact_reports_missing_required_sections(tmp_path: Path) -> None:
path = tmp_path / "thin.md"
path.write_text(INVALID_ENTITY, encoding="utf-8")
with pytest.raises(InfospaceError) as raised:
parse_entity_artifact("entity/thin.md", path)
assert raised.value.code == "invalid_entity_artifact"
assert raised.value.detail["missing_sections"] == ["definition"]
def test_parse_relation_artifact_links_entity_endpoints(tmp_path: Path) -> None:
path = tmp_path / "relation.md"
path.write_text(RELATION, encoding="utf-8")
entity_ids = {
"division-of-labour": "entity/division.md",
"market-extent": "entity/market.md",
}
relation = parse_relation_artifact("relation/division-market.md", path, entity_ids)
assert relation.slug == "division-of-labour-enables-market-extent"
assert relation.subject_slug == "division-of-labour"
assert relation.object_slug == "market-extent"
assert relation.subject_entity_id == "entity/division.md"
assert relation.object_entity_id == "entity/market.md"
assert relation.is_feedback_member is True
def test_parse_relation_artifact_reports_unresolved_endpoint(tmp_path: Path) -> None:
path = tmp_path / "relation.md"
path.write_text(RELATION, encoding="utf-8")
with pytest.raises(InfospaceError) as raised:
parse_relation_artifact("relation/division-market.md", path, {})
assert raised.value.code == "unresolved_relation_endpoint"
assert raised.value.detail["missing_slugs"] == [
"division-of-labour",
"market-extent",
]
def test_list_entities_and_relations_from_manifest(tmp_path: Path) -> None:
infospace = create_infospace(tmp_path, "pilot", name="Pilot")
division = tmp_path / "division.md"
division.write_text(ENTITY, encoding="utf-8")
market = tmp_path / "market.md"
market.write_text(
ENTITY.replace("Division of Labour", "Market Extent").replace(
"Production", "Exchange"
),
encoding="utf-8",
)
relation = tmp_path / "relation.md"
relation.write_text(RELATION, encoding="utf-8")
add_artifact(infospace.root, division, kind="entity", title="Division")
add_artifact(infospace.root, market, kind="entity", title="Market")
add_artifact(infospace.root, relation, kind="relation", title="Relation")
assert [entity.slug for entity in list_entities(infospace.root)] == [
"division-of-labour",
"market-extent",
]
assert [item.relation_type for item in list_relations(infospace.root)] == [
"constrains"
]
def test_cli_entities_and_relations_output_json(tmp_path: Path) -> None:
infospace = create_infospace(tmp_path, "pilot", name="Pilot")
division = tmp_path / "division.md"
division.write_text(ENTITY, encoding="utf-8")
market = tmp_path / "market.md"
market.write_text(
ENTITY.replace("Division of Labour", "Market Extent").replace(
"Production", "Exchange"
),
encoding="utf-8",
)
relation = tmp_path / "relation.md"
relation.write_text(RELATION, encoding="utf-8")
add_artifact(infospace.root, division, kind="entity", title="Division")
add_artifact(infospace.root, market, kind="entity", title="Market")
add_artifact(infospace.root, relation, kind="relation", title="Relation")
entities = subprocess.run(
[sys.executable, "-m", "infospace_bench", "entities", str(infospace.root)],
check=False,
env=cli_env(),
text=True,
capture_output=True,
)
relations = subprocess.run(
[sys.executable, "-m", "infospace_bench", "relations", str(infospace.root)],
check=False,
env=cli_env(),
text=True,
capture_output=True,
)
assert entities.returncode == 0, entities.stderr
assert relations.returncode == 0, relations.stderr
assert json.loads(entities.stdout)["entities"][0]["slug"] == "division-of-labour"
assert json.loads(relations.stdout)["relations"][0]["subject_entity_id"] == (
"entity/division.md"
)