Files
GEWU/tests/api/test_authenticated_read.py
T

273 lines
13 KiB
Python
Raw Normal View History

import asyncio
import hashlib
from pathlib import Path
import httpx
import yaml
from gewu.adapters.git_repository import GitCanonicalRepository
from gewu.adapters.sqlite_identity import SQLiteIdentityStore
from gewu.adapters.text_extractor import TextMarkdownExtractor
from gewu.application.reading import CanonicalNodeReader
from gewu.application.uploads import LocalArtifactStore
from gewu.bootstrap.app import create_app
from tests.domain.proposal_fixtures import NODE, SOURCE_REVISION
def test_authenticated_scoped_canonical_node_read(tmp_path: Path) -> None:
async def exercise() -> None:
repository = GitCanonicalRepository(tmp_path / "knowledge.git")
project_key = hashlib.sha256(NODE.project_id.encode()).hexdigest()
node_key = hashlib.sha256(NODE.node_id.encode()).hexdigest()
node_path = f"projects/{project_key}/nodes/{node_key}.yaml"
source_path = (
f"sources/{hashlib.sha256(SOURCE_REVISION.source_id.encode()).hexdigest()}/"
f"{hashlib.sha256(SOURCE_REVISION.revision_id.encode()).hexdigest()}.yaml"
)
artifact_store = LocalArtifactStore(tmp_path / "artifacts")
source_bytes = b"# Archived source\nA precise citation.\n"
source_digest = hashlib.sha256(source_bytes).hexdigest()
upload_id = artifact_store.begin(
filename="source.md",
media_type="text/markdown",
byte_length=len(source_bytes),
expected_sha256=source_digest,
owner_id="agent-pilot",
project_id=NODE.project_id,
).upload_id
artifact_store.put_chunk(
upload_id,
offset=0,
content=source_bytes,
chunk_sha256=source_digest,
owner_id="agent-pilot",
)
artifact_store.finalize(upload_id, owner_id="agent-pilot")
extraction = TextMarkdownExtractor().extract(
SOURCE_REVISION.revision_id, source_bytes, "text/markdown"
)
artifact_store.save_extraction(
extraction, owner_id="agent-pilot", project_id=NODE.project_id
)
archived_source = SOURCE_REVISION.model_copy(
update={"artifact_sha256": source_digest, "byte_length": len(source_bytes)}
)
repository.publish_files(
expected_head=repository.head(),
files={node_path: yaml.safe_dump(NODE.model_dump(mode="json")).encode()},
message="Publish test node",
index_directory=tmp_path / "publishing",
)
updated_node = NODE.model_copy(update={"summary": "A revised accepted summary."})
restricted_source = SOURCE_REVISION.model_copy(
update={
"source_id": "video:private",
"revision_id": "video:private:r1",
"ingested_for": ("interactive-video",),
}
)
restricted_source_path = (
f"sources/{hashlib.sha256(restricted_source.source_id.encode()).hexdigest()}/"
f"{hashlib.sha256(restricted_source.revision_id.encode()).hexdigest()}.yaml"
)
reference_source = SOURCE_REVISION.model_copy(
update={
"source_id": "dome:reference-only",
"revision_id": "dome:reference-only:r1",
"artifact_sha256": None,
"media_type": "application/octet-stream",
"byte_length": 0,
"original_url": "https://research.example/unavailable-paper",
"ingested_for": (NODE.project_id,),
"archive_status": "reference-only",
}
)
reference_source_path = (
f"sources/{hashlib.sha256(reference_source.source_id.encode()).hexdigest()}/"
f"{hashlib.sha256(reference_source.revision_id.encode()).hexdigest()}.yaml"
)
repository.publish_files(
expected_head=repository.head(),
files={
node_path: yaml.safe_dump(updated_node.model_dump(mode="json")).encode(),
source_path: yaml.safe_dump(archived_source.model_dump(mode="json")).encode(),
restricted_source_path: yaml.safe_dump(
restricted_source.model_dump(mode="json")
).encode(),
reference_source_path: yaml.safe_dump(
reference_source.model_dump(mode="json")
).encode(),
},
message="Revise node and register source",
index_directory=tmp_path / "publishing",
)
repository.publish_files(
expected_head=repository.head(),
files={"metadata/unrelated.yaml": b"accepted but unrelated\n"},
message="Advance repository without changing the node",
index_directory=tmp_path / "publishing",
)
identities = SQLiteIdentityStore(tmp_path / "identity.sqlite3")
token = identities.provision(
credential_id="agent-key",
principal_id="agent-pilot",
roles=("agent",),
project_ids=(NODE.project_id,),
)
app = create_app(
identity_provider=identities,
node_reader=CanonicalNodeReader(repository),
artifact_store=artifact_store,
)
async with httpx.AsyncClient(
transport=httpx.ASGITransport(app=app), base_url="http://gewu.test"
) as client:
path = f"/v1/projects/{NODE.project_id}/nodes/{NODE.node_id}"
assert (await client.get(path)).status_code == 401
malformed_id = await client.get(
f"/v1/projects/{NODE.project_id}/nodes/not%20a%20record-id",
headers={"Authorization": f"Bearer {token}"},
)
assert malformed_id.status_code == 422
denied = await client.get(
f"/v1/projects/interactive-video/nodes/{NODE.node_id}",
headers={"Authorization": f"Bearer {token}"},
)
assert denied.status_code == 403
accepted = await client.get(path, headers={"Authorization": f"Bearer {token}"})
assert accepted.status_code == 200
envelope = accepted.json()
assert envelope["retrieval_mode"] == "canonical"
assert envelope["canonical_revision"] == repository.head()
assert envelope["data"][0]["node_id"] == NODE.node_id
assert envelope["citations"][0]["quote"] == NODE.claims[0].evidence[0].quote
history = await client.get(
f"{path}/history", headers={"Authorization": f"Bearer {token}"}
)
assert history.status_code == 200
history_data = history.json()["data"]
assert len(history_data) == 2
assert history_data[0]["node"]["summary"] == updated_node.summary
latest_node_revision = history_data[0]["revision"]
assert latest_node_revision != repository.head()
latest_file_version = await client.get(
f"{path}?revision={latest_node_revision}",
headers={"Authorization": f"Bearer {token}"},
)
assert latest_file_version.status_code == 200
assert latest_file_version.json()["canonical_revision"] == latest_node_revision
assert latest_file_version.json()["data"][0]["summary"] == updated_node.summary
historical_revision = history_data[-1]["revision"]
historical = await client.get(
f"{path}?revision={historical_revision}",
headers={"Authorization": f"Bearer {token}"},
)
assert historical.status_code == 200, historical.text
assert historical.json()["canonical_revision"] == historical_revision
assert historical.json()["data"][0]["summary"] == NODE.summary
assert historical.json()["citations"][0]["quote"] == NODE.claims[0].evidence[0].quote
malformed_revision = await client.get(
f"{path}?revision=not-a-commit",
headers={"Authorization": f"Bearer {token}"},
)
assert malformed_revision.status_code == 422
unknown_revision = await client.get(
f"{path}?revision={'a' * 40}",
headers={"Authorization": f"Bearer {token}"},
)
assert unknown_revision.status_code == 404
source = await client.get(
f"/v1/projects/{NODE.project_id}/sources/{SOURCE_REVISION.revision_id}",
headers={"Authorization": f"Bearer {token}"},
)
assert source.status_code == 200
assert source.json()["data"][0]["revision_id"] == SOURCE_REVISION.revision_id
reference_path = (
f"/v1/projects/{NODE.project_id}/sources/{reference_source.revision_id}"
)
reference = await client.get(
reference_path, headers={"Authorization": f"Bearer {token}"}
)
assert reference.status_code == 200
assert reference.json()["data"][0]["artifact_sha256"] is None
diagnostics_path = "/v1/diagnostics?projects=dome-performance"
assert (await client.get("/v1/diagnostics")).status_code == 401
diagnostic_response = await client.get(
diagnostics_path, headers={"Authorization": f"Bearer {token}"}
)
assert diagnostic_response.status_code == 200
diagnostic_body = diagnostic_response.json()
catalog_path = Path(__file__).resolve().parents[2] / "schema/v1/catalog.json"
assert (
diagnostic_body["catalog_sha256"]
== hashlib.sha256(catalog_path.read_bytes()).hexdigest()
)
assert diagnostic_body["selected_scope"]["projects"] == [NODE.project_id]
assert diagnostic_body["index_state"] == "not_configured"
assert diagnostic_body["max_upload_bytes"] == 50 * 1024 * 1024
assert "get_source_artifact" in diagnostic_body["authorized_operations"]
assert token not in diagnostic_response.text
assert "principal_id" not in diagnostic_body
forbidden_diagnostic_scope = await client.get(
"/v1/diagnostics?projects=interactive-video",
headers={"Authorization": f"Bearer {token}"},
)
assert forbidden_diagnostic_scope.status_code == 403
forbidden_shared_scope = await client.get(
diagnostics_path + "&include_shared=true",
headers={"Authorization": f"Bearer {token}"},
)
assert forbidden_shared_scope.status_code == 403
artifact_path = (
f"/v1/projects/{NODE.project_id}/sources/{SOURCE_REVISION.revision_id}/artifact"
)
assert (await client.get(artifact_path)).status_code == 401
archived = await client.get(artifact_path, headers={"Authorization": f"Bearer {token}"})
assert archived.status_code == 200
assert archived.content == source_bytes
assert archived.headers["etag"] == f'"{source_digest}"'
assert archived.headers["x-content-type-options"] == "nosniff"
assert archived.headers["cache-control"] == "private, no-store"
assert archived.headers["content-security-policy"] == "default-src 'none'; sandbox"
missing_artifact = await client.get(
f"{reference_path}/artifact", headers={"Authorization": f"Bearer {token}"}
)
assert missing_artifact.status_code == 404
block = extraction.blocks[0]
offset = block.normalized_text.index("precise")
location_path = (
f"/v1/projects/{NODE.project_id}/sources/{SOURCE_REVISION.revision_id}"
f"/extractions/{extraction.extraction_id}/blocks/{block.block_id}"
)
location = await client.get(
location_path,
params={"offset": offset, "length": len("precise")},
headers={"Authorization": f"Bearer {token}"},
)
assert location.status_code == 200
assert location.json()["normalized_text"] == "precise"
assert location.json()["original_text"] == "precise"
denied_location = await client.get(
location_path,
params={"offset": offset, "length": len("precise")},
)
assert denied_location.status_code == 401
restricted = await client.get(
f"/v1/projects/{NODE.project_id}/sources/{restricted_source.revision_id}",
headers={"Authorization": f"Bearer {token}"},
)
assert restricted.status_code == 404
restricted_artifact = await client.get(
f"/v1/projects/{NODE.project_id}/sources/{restricted_source.revision_id}/artifact",
headers={"Authorization": f"Bearer {token}"},
)
assert restricted_artifact.status_code == 404
assert identities.revoke("agent-key")
assert (
await client.get(path, headers={"Authorization": f"Bearer {token}"})
).status_code == 401
assert (
await client.get(artifact_path, headers={"Authorization": f"Bearer {token}"})
).status_code == 401
asyncio.run(exercise())