teleo-infrastructure/tests/test_observatory_read_adapter.py
twentyOne2x 92da54d804
Some checks are pending
CI / lint-and-test (push) Waiting to run
Add authenticated GCP Observatory read adapter (#154)
2026-07-15 03:43:05 +02:00

346 lines
12 KiB
Python

from __future__ import annotations
import asyncio
import json
from pathlib import Path
from aiohttp.test_utils import TestClient, TestServer
from observatory_read_adapter.app import create_app
from observatory_read_adapter.config import EXPECTED_CONNECTION_NAME, Settings
from observatory_read_adapter.db import CloudSqlClaimReader
CLAIM_ID = "d0000000-0000-0000-0000-000000000005"
API_KEY = "observatory-test-api-key-000000000000000000000000"
ROOT = Path(__file__).resolve().parents[1]
def settings(**overrides: object) -> Settings:
values = {
"project_id": "teleo-501523",
"instance_connection_name": EXPECTED_CONNECTION_NAME,
"database": "teleo_canonical",
"iam_database_user": "sa-observatory-read-adapter@teleo-501523.iam",
"authorization_role": "kb_observatory_read",
"api_key": API_KEY,
"service_revision": "test-revision",
}
values.update(overrides)
return Settings(**values)
class FakeReader:
def __init__(self) -> None:
self.closed = False
def check_ready(self) -> dict[str, object]:
return {
"ready": True,
"database": "teleo_canonical",
"authorization_role": "kb_observatory_read",
"transaction_read_only": True,
}
def read_claim(self, claim_id: str | None = None) -> dict[str, object] | None:
if claim_id not in (None, CLAIM_ID):
return None
return {
"schema": "livingip.observatory-canonical-claim.v1",
"read_only": True,
"provenance": {
"database": "teleo_canonical",
"database_principal": "sa-observatory-read-adapter@teleo-501523.iam",
"authorization_role": "kb_observatory_read",
"transaction_read_only": True,
"write_privileges_denied": True,
},
"canonical": {
"claim": {"id": CLAIM_ID, "status": "open", "text": "Canonical claim"},
"evidence": [{"source_id": "e0000000-0000-0000-0000-000000000006"}],
},
"proposal_ledger": {
"distinct_from_canonical": True,
"status_counts": {"pending_review": 2, "approved": 1, "applied": 1},
"related": [{"status": "approved", "applied_at": None}],
},
}
def close(self) -> None:
self.closed = True
class FakeCursor:
def __init__(self) -> None:
self.description: list[tuple[str]] = []
self.result: list[tuple[object, ...]] = []
self.executed: list[tuple[str, tuple[object, ...]]] = []
def execute(self, sql: str, parameters: tuple[object, ...] = ()) -> None:
normalized = " ".join(sql.split()).lower()
self.executed.append((normalized, parameters))
self.description = []
self.result = []
if "current_database() as database" in normalized:
self.description = [
("database",),
("database_principal",),
("transaction_read_only",),
("has_authorization_role",),
("has_required_reads",),
("required_writes_denied",),
]
self.result = [
(
"teleo_canonical",
"sa-observatory-read-adapter@teleo-501523.iam",
"on",
True,
True,
True,
)
]
elif "from public.claims c" in normalized:
self.description = [
("id",),
("type",),
("text",),
("status",),
("confidence",),
("tags",),
("superseded_by",),
("created_at",),
("updated_at",),
]
self.result = [
(
CLAIM_ID,
"strategy",
"Canonical claim",
"open",
0.8,
["test"],
None,
"2026-07-15T00:00:00+00:00",
"2026-07-15T00:00:00+00:00",
)
]
elif "from public.claim_evidence ce" in normalized:
self.description = [
("evidence_id",),
("role",),
("weight",),
("linked_at",),
("source_id",),
("source_type",),
("url",),
("storage_path",),
("excerpt",),
("content_hash",),
("captured_at",),
("source_created_at",),
]
self.result = [
(
"e0000000-0000-0000-0000-000000000006",
"grounds",
1.0,
"2026-07-15T00:00:00+00:00",
"f0000000-0000-0000-0000-000000000007",
"document",
"https://example.test/source",
None,
"Source excerpt",
"sha256:test",
"2026-07-15T00:00:00+00:00",
"2026-07-15T00:00:00+00:00",
)
]
elif "from public.claim_edges e" in normalized:
self.description = [
("edge_id",),
("direction",),
("edge_type",),
("weight",),
("connected_claim_id",),
("created_at",),
]
self.result = []
elif "group by status" in normalized:
self.description = [("status",), ("count",)]
self.result = [("approved", 1), ("applied", 1), ("pending_review", 2)]
elif "where p.payload::text like" in normalized:
self.description = [
("id",),
("proposal_type",),
("status",),
("source_ref",),
("reviewed_at",),
("applied_at",),
("updated_at",),
]
self.result = [
(
"a0000000-0000-0000-0000-000000000001",
"revise_claim",
"approved",
"source:test",
"2026-07-15T00:00:00+00:00",
None,
"2026-07-15T00:00:00+00:00",
)
]
elif "from kb_stage.kb_proposals p" in normalized:
self.description = [
("id",),
("proposal_type",),
("status",),
("source_ref",),
("reviewed_at",),
("applied_at",),
("updated_at",),
]
self.result = []
def fetchone(self) -> tuple[object, ...] | None:
return self.result.pop(0) if self.result else None
def fetchall(self) -> list[tuple[object, ...]]:
result, self.result = self.result, []
return result
class FakeConnection:
def __init__(self) -> None:
self.cursor_value = FakeCursor()
self.rolled_back = False
self.closed = False
def cursor(self) -> FakeCursor:
return self.cursor_value
def rollback(self) -> None:
self.rolled_back = True
def close(self) -> None:
self.closed = True
def test_settings_fail_closed_on_wrong_database_role_or_short_key() -> None:
for overrides in (
{"database": "teleo"},
{"authorization_role": "leoclean_kb_runtime"},
{"api_key": "too-short"},
{"instance_connection_name": "other:region:instance"},
):
candidate = settings(**overrides)
try:
candidate.validate()
except ValueError:
pass
else:
raise AssertionError(f"unsafe settings accepted: {overrides}")
def test_authenticated_claim_response_and_negative_http_paths() -> None:
async def exercise() -> None:
reader = FakeReader()
client = TestClient(TestServer(create_app(settings(), reader=reader)))
await client.start_server()
try:
anonymous = await client.get("/v1/claims/sample")
assert anonymous.status == 401
assert await anonymous.json() == {"error": "unauthorized"}
response = await client.get(
f"/v1/claims/{CLAIM_ID}",
headers={"X-Api-Key": API_KEY},
)
assert response.status == 200
assert response.headers["Cache-Control"] == "private, no-store, max-age=0"
payload = await response.json()
assert payload["schema"] == "livingip.observatory-canonical-claim.v1"
assert payload["provenance"]["database"] == "teleo_canonical"
assert payload["provenance"]["write_privileges_denied"] is True
assert payload["canonical"]["evidence"]
assert payload["proposal_ledger"]["distinct_from_canonical"] is True
assert payload["proposal_ledger"]["related"][0]["applied_at"] is None
assert API_KEY not in json.dumps(payload)
write_attempt = await client.post(
f"/v1/claims/{CLAIM_ID}",
headers={"X-Api-Key": API_KEY},
json={"status": "applied"},
)
assert write_attempt.status == 405
invalid = await client.get(
"/v1/claims/not-a-uuid",
headers={"X-Api-Key": API_KEY},
)
assert invalid.status == 400
finally:
await client.close()
assert reader.closed is True
asyncio.run(exercise())
def test_readiness_checks_database_contract_without_exposing_claims() -> None:
async def exercise() -> None:
client = TestClient(TestServer(create_app(settings(), reader=FakeReader())))
await client.start_server()
try:
response = await client.get("/readyz")
payload = await response.json()
assert response.status == 200
assert payload == {"status": "ready"}
finally:
await client.close()
asyncio.run(exercise())
def test_database_reader_returns_provenance_and_keeps_proposals_distinct() -> None:
connection = FakeConnection()
reader = CloudSqlClaimReader(settings(), connection_factory=lambda: connection)
payload = reader.read_claim(CLAIM_ID)
assert payload is not None
assert payload["provenance"]["database"] == "teleo_canonical"
assert payload["provenance"]["database_principal"] == settings().iam_database_user
assert payload["provenance"]["write_privileges_denied"] is True
assert payload["canonical"]["claim"]["id"] == CLAIM_ID
assert payload["canonical"]["evidence"][0]["content_hash"] == "sha256:test"
assert payload["proposal_ledger"]["distinct_from_canonical"] is True
assert payload["proposal_ledger"]["related"][0]["status"] == "approved"
assert payload["proposal_ledger"]["related"][0]["applied_at"] is None
assert connection.rolled_back is True
assert connection.closed is True
assert any("set transaction read only" in sql for sql, _parameters in connection.cursor_value.executed)
def test_database_role_contract_is_select_only_and_iam_scoped() -> None:
sql = (ROOT / "ops" / "observatory_read_role.sql").read_text(encoding="utf-8")
lowered = sql.lower()
assert "kb_observatory_read nologin" in lowered
assert "default_transaction_read_only = on" in lowered
assert "public.claims" in lowered
assert "public.sources" in lowered
assert "public.claim_evidence" in lowered
assert "public.claim_edges" in lowered
assert "kb_stage.kb_proposals" in lowered
assert "grant select on" in lowered
assert "grant insert" not in lowered
assert "grant update" not in lowered
assert "grant delete" not in lowered
assert "effective_table_writes_denied" in lowered
def test_container_runs_as_non_root_and_contains_no_password_contract() -> None:
dockerfile = (ROOT / "Dockerfile.observatory-read-adapter").read_text(encoding="utf-8")
assert "USER 10001:10001" in dockerfile
assert "PGPASSWORD" not in dockerfile
assert "DB_PASS" not in dockerfile