This commit is contained in:
1 parent
4db3f45164
commit
d25c04cdd2
175 files changed
+56243
-15
No files matched your search
@@ -0,0 +1,231 @@
|
||||
import json
|
||||
from datetime import UTC, date, datetime
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from gewu.domain.contracts import (
|
||||
AutoAcceptPolicy,
|
||||
Confidence,
|
||||
Locator,
|
||||
NodeType,
|
||||
ProjectRecord,
|
||||
ProjectStatus,
|
||||
ProposalReceipt,
|
||||
ProposalState,
|
||||
ReliabilityGrade,
|
||||
Scope,
|
||||
SearchRequest,
|
||||
SourceRevisionRecord,
|
||||
)
|
||||
|
||||
|
||||
def test_project_contract_rejects_unknown_fields() -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
ProjectRecord.model_validate(
|
||||
{
|
||||
"project_id": "dome",
|
||||
"title": "Dome",
|
||||
"status": ProjectStatus.ACTIVE,
|
||||
"north_star": "Understand interactive environments",
|
||||
"key_questions": ("What works?",),
|
||||
"autonomy_policy_id": "pilot",
|
||||
"untrusted_extra": "ignored?",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_project_status_supports_pause_without_losing_registry_record() -> None:
|
||||
project = ProjectRecord(
|
||||
project_id="dome",
|
||||
title="Dome",
|
||||
status=ProjectStatus.PAUSED,
|
||||
north_star="Understand interactive environments",
|
||||
key_questions=("What works?",),
|
||||
autonomy_policy_id="pilot",
|
||||
)
|
||||
assert (
|
||||
ProjectRecord.model_validate_json(project.model_dump_json()).status == ProjectStatus.PAUSED
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("project_id", ["Dome", "two_words", "project.slug"])
|
||||
def test_project_ids_must_be_lowercase_slugs(project_id: str) -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
ProjectRecord(
|
||||
project_id=project_id,
|
||||
title="Invalid project ID",
|
||||
status=ProjectStatus.ACTIVE,
|
||||
north_star="A stable project lens.",
|
||||
key_questions=("What matters?",),
|
||||
autonomy_policy_id="policy",
|
||||
)
|
||||
|
||||
|
||||
def test_scope_requires_explicit_all_projects_flag() -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
Scope(projects=("all",))
|
||||
assert Scope(projects=("all",), all_projects=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("unsafe_id", ["../private", "bad/id", " leading-space"])
|
||||
def test_scope_rejects_unsafe_project_ids(unsafe_id: str) -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
Scope(projects=(unsafe_id,))
|
||||
|
||||
|
||||
def test_search_facets_are_typed_and_reject_duplicate_node_types() -> None:
|
||||
request = SearchRequest(
|
||||
query="audience",
|
||||
scope=Scope(projects=("dome-performance",)),
|
||||
node_types=(NodeType.PROBLEM, NodeType.TECHNIQUE),
|
||||
tags=("attendance", "real-time"),
|
||||
audience=("product",),
|
||||
use_for=("comparison",),
|
||||
domain=("production",),
|
||||
confidences=(Confidence.MEDIUM,),
|
||||
as_of_from=date(2024, 1, 1),
|
||||
as_of_to=date(2024, 12, 31),
|
||||
stale_only=True,
|
||||
budget_tokens=1000,
|
||||
)
|
||||
assert request.stale_only
|
||||
assert request.tags == ("attendance", "real-time")
|
||||
assert request.domain == ("production",)
|
||||
assert request.confidences == (Confidence.MEDIUM,)
|
||||
assert request.as_of_from == date(2024, 1, 1)
|
||||
with pytest.raises(ValidationError, match="must not be later"):
|
||||
SearchRequest(
|
||||
query="audience",
|
||||
scope=Scope(projects=("dome-performance",)),
|
||||
as_of_from=date(2025, 1, 1),
|
||||
as_of_to=date(2024, 12, 31),
|
||||
budget_tokens=1000,
|
||||
)
|
||||
with pytest.raises(ValidationError, match="confidences must be unique"):
|
||||
SearchRequest(
|
||||
query="audience",
|
||||
scope=Scope(projects=("dome-performance",)),
|
||||
confidences=(Confidence.MEDIUM, Confidence.MEDIUM),
|
||||
budget_tokens=1000,
|
||||
)
|
||||
with pytest.raises(ValidationError):
|
||||
SearchRequest(
|
||||
query="audience",
|
||||
scope=Scope(projects=("dome-performance",)),
|
||||
node_types=(NodeType.PROBLEM, NodeType.PROBLEM),
|
||||
budget_tokens=1000,
|
||||
)
|
||||
with pytest.raises(ValidationError):
|
||||
SearchRequest(
|
||||
query="audience",
|
||||
scope=Scope(projects=("dome-performance",)),
|
||||
tags=("attendance", "attendance"),
|
||||
budget_tokens=1000,
|
||||
)
|
||||
with pytest.raises(ValidationError):
|
||||
SearchRequest(
|
||||
query="audience",
|
||||
scope=Scope(projects=("dome-performance",)),
|
||||
domain=(" production",),
|
||||
budget_tokens=1000,
|
||||
)
|
||||
|
||||
|
||||
def test_search_allows_empty_query_only_for_a_typed_browse() -> None:
|
||||
request = SearchRequest(
|
||||
scope=Scope(projects=("dome-performance",)),
|
||||
node_types=(NodeType.PROBLEM,),
|
||||
budget_tokens=1000,
|
||||
)
|
||||
assert request.query == ""
|
||||
with pytest.raises(ValidationError, match="search requires text"):
|
||||
SearchRequest(
|
||||
scope=Scope(projects=("dome-performance",)),
|
||||
budget_tokens=1000,
|
||||
)
|
||||
with pytest.raises(ValidationError, match="search requires text"):
|
||||
SearchRequest(
|
||||
query=" ",
|
||||
scope=Scope(projects=("dome-performance",)),
|
||||
budget_tokens=1000,
|
||||
)
|
||||
|
||||
|
||||
def test_pdf_locator_uses_one_based_page_and_nonempty_span() -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
Locator(kind="pdf_page_span", block_id="p1-b1", start=8, end=8, page=0)
|
||||
|
||||
|
||||
def test_unarchived_source_requires_a_url_and_cannot_claim_artifact_bytes() -> None:
|
||||
base = {
|
||||
"source_id": "project:bookmark",
|
||||
"revision_id": "project:bookmark:r1",
|
||||
"media_type": "application/octet-stream",
|
||||
"byte_length": 0,
|
||||
"captured_at": datetime.now(UTC),
|
||||
"original_url": "https://example.org/paper",
|
||||
"title": "Referenced paper",
|
||||
"license": "unspecified",
|
||||
"source_type": "paper",
|
||||
"reliability_grade": ReliabilityGrade.MEDIUM,
|
||||
"ingested_for": ("dome-performance",),
|
||||
"archive_status": "reference-only",
|
||||
}
|
||||
record = SourceRevisionRecord.model_validate(base)
|
||||
assert record.artifact_sha256 is None
|
||||
with pytest.raises(ValidationError):
|
||||
SourceRevisionRecord.model_validate({**base, "byte_length": 10})
|
||||
with pytest.raises(ValidationError):
|
||||
SourceRevisionRecord.model_validate({**base, "original_url": None})
|
||||
|
||||
|
||||
def test_receipt_is_immutable_and_versioned() -> None:
|
||||
receipt = ProposalReceipt(
|
||||
proposal_id="proposal-1",
|
||||
revision=1,
|
||||
payload_digest="a" * 64,
|
||||
state=ProposalState.ACCEPTED,
|
||||
outcome="accepted",
|
||||
commit_id="b" * 64,
|
||||
decided_at=datetime.now(UTC),
|
||||
)
|
||||
assert receipt.schema_version == 1
|
||||
with pytest.raises(ValidationError):
|
||||
receipt.revision = 2 # type: ignore[misc]
|
||||
|
||||
|
||||
def test_pilot_registry_fixture_matches_project_and_policy_contracts() -> None:
|
||||
fixture_path = (
|
||||
Path(__file__).resolve().parents[2] / "schema" / "fixtures" / "pilot-projects.json"
|
||||
)
|
||||
|
||||
def reject_duplicate_keys(pairs: list[tuple[str, object]]) -> dict[str, object]:
|
||||
result: dict[str, object] = {}
|
||||
for key, value in pairs:
|
||||
if key in result:
|
||||
raise ValueError(f"duplicate JSON key: {key}")
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
fixture = json.loads(
|
||||
fixture_path.read_text(encoding="utf-8"), object_pairs_hook=reject_duplicate_keys
|
||||
)
|
||||
projects = tuple(
|
||||
ProjectRecord.model_validate_json(json.dumps(record), strict=True)
|
||||
for record in fixture["projects"]
|
||||
)
|
||||
policies = tuple(
|
||||
AutoAcceptPolicy.model_validate_json(json.dumps(record), strict=True)
|
||||
for record in fixture["test_policies"]
|
||||
)
|
||||
assert {project.project_id for project in projects} == {
|
||||
"dome-performance",
|
||||
"interactive-video",
|
||||
}
|
||||
assert {policy.project_id for policy in policies} == {
|
||||
"dome-performance",
|
||||
"interactive-video",
|
||||
"shared",
|
||||
}
|
||||
Reference in new issue
Block a user