346 lines
12 KiB
Python
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
|