"""Opt-in SQL checks against a disposable PostgreSQL database; no business data.""" import asyncio import os from datetime import datetime, timedelta, timezone from types import SimpleNamespace from uuid import uuid4 import pytest from fastapi import HTTPException from psycopg import AsyncConnection from psycopg.rows import dict_row from psycopg.types.json import Jsonb from app.infra.db.postgresql import read_queries from app.mcp.models import PeripheralInput from app.mcp.peripheral import read_peripheral_query from app.services.content_digest import digest def test_history_and_evidence_pages_with_real_postgresql(): dsn = os.environ.get("MCP_QUERY_TEST_DSN") if not dsn: pytest.skip("MCP_QUERY_TEST_DSN must point to a disposable PostgreSQL database") async def check(): conn = await AsyncConnection.connect(dsn, row_factory=dict_row) async with conn, conn.transaction(force_rollback=True): await conn.execute("CREATE SCHEMA workbench") await conn.execute("CREATE TABLE workbench.operational_task_evidence (project_id uuid, task_id uuid, evidence_id text, document jsonb)") await conn.execute("CREATE SCHEMA scada") await conn.execute("CREATE TABLE scada.measurements (device_id text, time timestamptz, monitored_value double precision, cleaned_value double precision)") await conn.execute("CREATE SCHEMA analysis") await conn.execute("CREATE TABLE analysis.node_results (run_id uuid, node_id text, time timestamptz, pressure double precision)") await conn.execute("CREATE SCHEMA peripheral_sample") await conn.execute("CREATE TABLE peripheral_sample.demo_datasets (project_id uuid, dataset_id text, document jsonb, digest text)") project, task, run = uuid4(), uuid4(), uuid4() start = datetime(2026, 10, 1, tzinfo=timezone.utc) for i in range(2): await conn.execute("INSERT INTO scada.measurements VALUES (%s,%s,%s,%s)", ("D1", start+timedelta(minutes=i), i, i+10)) await conn.execute("INSERT INTO analysis.node_results VALUES (%s,%s,%s,%s)", (run, "J1", start+timedelta(minutes=i), i+20)) await conn.execute("INSERT INTO analysis.node_results VALUES (%s,%s,%s,%s)", (uuid4(), "J1", start+timedelta(minutes=i), 999)) descriptors = [ {"source": "scada", "element_type": "junction", "id": "D1", "field": field} for field in ("monitored_value", "cleaned_value") ] + [{"source": "analysis", "element_type": "junction", "id": "J1", "field": "pressure"}] seen = [] for offset in (0, 2, 4, 6): rows = await read_queries.history_rows(conn, descriptors, start=start, end=start+timedelta(hours=1), run_id=run, limit=2, offset=offset) seen.extend(row["value"] for row in rows) assert seen == [0, 1, 10, 11, 20, 21] snapshot = { "source": "query_scada_readings", "source_ref": "saved:1", "queried_at": start.isoformat(), "origin": "synthetic", "truncated": False, "data": {"semantics": "observed", "data": { "items": [{"value": 1}, {"value": 2, "points": [3, 4]}], "text": "η”²πŸ˜€δΉ™δΈ™", "none": None, }}, } await conn.execute("INSERT INTO workbench.operational_task_evidence VALUES (%s,%s,%s,%s)", (project, task, "E1", Jsonb({"data_snapshot": snapshot}))) async def evidence(path, **overrides): values = dict(project_id=project, task_id=task, evidence_id="E1", data_path=path, limit=1, offset=0, mcp_sources=["query_scada_readings"]) return await read_queries.evidence_page(conn, **(values | overrides)) page = await evidence(["items"], offset=1) assert page["total"] == 2 and page["nested"] assert page["snapshot_metadata"]["origin"] == "synthetic" assert page["query_metadata"] == {"semantics": "observed"} assert page["value"] == [{"value": 2, "points": {"collection_type": "array", "count": 2, "data_path": ["items", "1", "points"]}}] assert (await evidence(["items", "1", "points"], offset=1))["value"] == [4] assert (await evidence(["text"], offset=1, limit=2))["value"] == "πŸ˜€δΉ™" assert (await evidence(["none"]))["value"] is None assert (await evidence([], full_snapshot=True)) == snapshot assert (await evidence([]))["value"]["items"]["data_path"] == ["items"] for overrides, status in (({"project_id": uuid4()}, 404), ({"task_id": uuid4()}, 404), ({"data_path": ["absent"]}, 422)): with pytest.raises(HTTPException) as caught: await evidence([], **overrides) assert caught.value.status_code == status legacy = {**snapshot, "source": "scada_readings", "data": {"items": [5, 6]}} await conn.execute("INSERT INTO workbench.operational_task_evidence VALUES (%s,%s,%s,%s)", (project, task, "legacy", Jsonb({"data_snapshot": legacy}))) old_page = await evidence(["items"], evidence_id="legacy", offset=1) assert old_page["value"] == [6] and old_page["query_metadata"] is None # A large nested collection is represented by a path, not transferred in full. snapshot["data"]["data"]["huge"] = ["x" * (read_queries.MAX_QUERY_BYTES + 1)] await conn.execute("UPDATE workbench.operational_task_evidence SET document=%s", (Jsonb({"data_snapshot": snapshot}),)) assert (await evidence(["huge"], offset=1))["value"] == [] assert (await evidence([], limit=1))["value"]["huge"]["count"] == 1 descriptor = (await evidence(["huge"]))["value"][0] assert descriptor["collection_type"] == "string" and descriptor["data_path"] == ["huge", "0"] assert (await evidence(["huge", "0"], offset=7, limit=3))["value"] == "xxx" with pytest.raises(HTTPException) as caught: await evidence([], full_snapshot=True) assert caught.value.status_code == 413 payload = {"project_id": str(project), "points": [{"point_id": "DEMO-1"}], "observations": [ {"point_id": "DEMO-1", "observed_at": (start+timedelta(minutes=i)).isoformat(), "value": i} for i in range(3) ]} await conn.execute("INSERT INTO peripheral_sample.demo_datasets VALUES (%s,'water-demo-v1',%s,%s)", (project, Jsonb(payload), digest(payload))) args = PeripheralInput(context_handle="a"*43, source="peripheral_observations", point_id="DEMO-1", start=start, end=start+timedelta(hours=1), limit=1, offset=1) scope = SimpleNamespace(connection=conn, project=SimpleNamespace(project_id=project)) demo = await read_peripheral_query(scope, args) assert demo.origin == "synthetic" and demo.truncated and demo.data["items"][0]["value"] == 1 await conn.execute("UPDATE peripheral_sample.demo_datasets SET digest='corrupted'") with pytest.raises(HTTPException) as caught: await read_peripheral_query(scope, args) assert caught.value.status_code == 503 payload["padding"] = "x" * (read_queries.MAX_QUERY_BYTES + 1) await conn.execute("UPDATE peripheral_sample.demo_datasets SET document=%s", (Jsonb(payload),)) with pytest.raises(HTTPException) as caught: await read_peripheral_query(scope, args) assert caught.value.status_code == 413 asyncio.run(check()) def test_task_context_summary_and_sections_with_real_postgresql(): dsn = os.environ.get("MCP_QUERY_TEST_DSN") if not dsn: pytest.skip("MCP_QUERY_TEST_DSN must point to a disposable PostgreSQL database") async def check(): conn = await AsyncConnection.connect(dsn, row_factory=dict_row) async with conn, conn.transaction(force_rollback=True): await conn.execute("CREATE SCHEMA workbench") await conn.execute("""CREATE TABLE workbench.operational_tasks ( project_id uuid,task_id uuid,task_type text,status text,lifecycle_status text,priority text, revision bigint,execution_generation bigint,current_report_revision bigint,document jsonb)""") for name, key in (("reports", "revision bigint"), ("plans", "revision bigint"), ("evidence", "evidence_id text"), ("actions", "action_id uuid")): await conn.execute(f"CREATE TABLE workbench.operational_task_{name} (project_id uuid,task_id uuid,{key},document jsonb)") await conn.execute("CREATE TABLE workbench.operational_task_runs (project_id uuid,task_id uuid,run_id uuid,generation bigint,status text)") project, task, run = uuid4(), uuid4(), uuid4() long_text = "η”²πŸ˜€" * 10000 await conn.execute("INSERT INTO workbench.operational_tasks VALUES (%s,%s,'event_investigation','running','active','normal',1,2,4,%s)", (project, task, Jsonb({"title": long_text, "source": {"description": long_text}}))) for revision in (3, 4): await conn.execute("INSERT INTO workbench.operational_task_reports VALUES (%s,%s,%s,%s)", (project, task, revision, Jsonb({"revision": revision, "report": {"stage": "investigation", "outcome": "pending", "current_conclusion": long_text}}))) await conn.execute("INSERT INTO workbench.operational_task_plans VALUES (%s,%s,1,%s)", (project, task, Jsonb({"nodes": [{"id": "N1"}]}))) for evidence_id in ("E1", "E2", "E3"): await conn.execute("INSERT INTO workbench.operational_task_evidence VALUES (%s,%s,%s,%s)", (project, task, evidence_id, Jsonb({"data_snapshot": {"text": long_text}, "none": None}))) action_id = uuid4() await conn.execute("INSERT INTO workbench.operational_task_actions VALUES (%s,%s,%s,%s)", (project, task, action_id, Jsonb({"reason": long_text}))) await conn.execute("INSERT INTO workbench.operational_task_runs VALUES (%s,%s,%s,2,'running')", (project, task, run)) summary = await read_queries.task_context_summary(conn, project_id=project, task_id=task, report_revision=3) assert summary["selected_report"]["revision"] == 3 and summary["latest_report"]["revision"] == 4 assert summary["task"]["preview_only"] and len(summary["task"]["title_preview"]) == 160 assert len(summary["selected_report"]["current_conclusion_preview"]) == 200 assert "source" not in summary["task"] for overrides in ({"report_revision": 99}, {"project_id": uuid4()}, {"task_id": uuid4()}): with pytest.raises(HTTPException) as caught: await read_queries.task_context_summary(conn, **(dict(project_id=project, task_id=task, report_revision=3) | overrides)) assert caught.value.status_code == 404 async def page(section, path=None, **overrides): values = dict(project_id=project, task_id=task, section=section, report_revision=None, data_path=path or [], limit=1, offset=0) return await read_queries.task_context_page(conn, **(values | overrides)) second = await page("evidence", offset=1) assert second["snapshot_metadata"]["total"] == 3 assert list(second["value"]) == ["E2"] assert second["value"]["E2"]["data_path"] == ["E2"] nested = await page("evidence", ["E2"], limit=20) assert nested["value"]["data_snapshot"]["data_path"] == ["E2", "data_snapshot"] assert nested["value"]["none"] is None text = await page("evidence", ["E2", "data_snapshot", "text"], offset=1, limit=2) assert text["value"] == "πŸ˜€η”²" and text["total"] == len(long_text) assert (await page("evidence", offset=3))["value"] == {} assert (await page("evidence", project_id=uuid4()))["value"] == {} with pytest.raises(HTTPException) as caught: await page("evidence", ["E2"], task_id=uuid4()) assert caught.value.status_code == 404 report = await page("report", ["report", "current_conclusion"], report_revision=3, offset=2, limit=2) assert report["value"] == "η”²πŸ˜€" assert (await page("task"))["nested"] assert (await page("plan"))["value"]["nodes"]["collection_type"] == "array" assert (await page("actions"))["value"][str(action_id)]["data_path"] == [str(action_id)] assert "project_id" not in (await page("run", limit=20))["value"] for section in ("progress", "access_requests"): assert (await page(section))["snapshot_metadata"]["available"] is False assert await read_queries.task_data_scopes(conn, project_id=project, task_id=task, permissions={"scada.view"}) == ["scada_devices", "scada_readings"] await conn.execute("CREATE TABLE workbench.operational_task_progress (project_id uuid,task_id uuid,run_id uuid,document jsonb)") await conn.execute("INSERT INTO workbench.operational_task_progress VALUES (%s,%s,%s,%s)", (project, task, run, Jsonb({"content": long_text}))) assert (await page("progress"))["value"]["content"]["collection_type"] == "string" await conn.execute("CREATE TABLE workbench.operational_task_access_requests (project_id uuid,task_id uuid,access_request_id uuid,status text,document jsonb)") for grant_project, grant_task, status, scopes in ( (project, task, "approved", ["peripheral_points", "unknown_source"]), (project, task, "pending", ["scada_readings"]), (uuid4(), task, "approved", ["peripheral_observations"]), (project, uuid4(), "approved", ["peripheral_test_data"]), ): await conn.execute("INSERT INTO workbench.operational_task_access_requests VALUES (%s,%s,%s,%s,%s)", (grant_project, grant_task, uuid4(), status, Jsonb({"approved_scopes": scopes}))) assert await read_queries.task_data_scopes(conn, project_id=project, task_id=task, permissions=set()) == ["peripheral_points"] assert (await page("access_requests", limit=20))["snapshot_metadata"]["total"] == 2 # A very wide document must fail before it crosses the DB boundary; # a smaller page remains readable. wide = {str(i): "x" * 250 for i in range(200)} await conn.execute("UPDATE workbench.operational_tasks SET document=%s", (Jsonb(wide),)) with pytest.raises(HTTPException) as caught: await page("task", limit=200) assert caught.value.status_code == 413 assert len((await page("task", limit=1))["value"]) == 1 asyncio.run(check())