Accumulate investigation paths and preserve failed attempts across rounds. Keep graph annotations separate from execution state, synchronize the API contract, and consolidate documentation.
85 lines
4.1 KiB
Python
85 lines
4.1 KiB
Python
"""Opt-in read-only JSONB probes; no tables or business records are written."""
|
|
import asyncio
|
|
import os
|
|
from copy import deepcopy
|
|
|
|
import pytest
|
|
from psycopg import AsyncConnection
|
|
from psycopg.rows import dict_row
|
|
from psycopg.types.json import Jsonb
|
|
|
|
from app.infra.db.postgresql import read_queries
|
|
|
|
|
|
@pytest.mark.parametrize("section", ["progress", "report"])
|
|
def test_reference_annotations_are_normalized_before_nested_paging(section):
|
|
dsn = os.environ.get("MCP_QUERY_TEST_DSN")
|
|
if not dsn:
|
|
pytest.skip("MCP_QUERY_TEST_DSN is required for a read-only PostgreSQL JSONB probe")
|
|
|
|
async def check():
|
|
statuses = ["running", "completed", "waiting", "waiting_human", "skipped", "cancelled", "failed", None]
|
|
original = {"section": section, "data": {
|
|
"revision": 3, "execution_generation": 2,
|
|
section: {"diagram": {"mermaid": "flowchart TD\nA[原图]", "node_refs": [
|
|
{"node_id": f"n{index}", "status": status, "detail": "保留原说明", "evidence_refs": ["e1"]}
|
|
for index, status in enumerate(statuses)
|
|
] + [{"node_id": "unset"}]}},
|
|
"status": "running", "action": {"status": "completed"},
|
|
}}
|
|
untouched = deepcopy(original)
|
|
async with await AsyncConnection.connect(
|
|
dsn, row_factory=dict_row, options="-c default_transaction_read_only=on",
|
|
) as conn:
|
|
async def page(path=(), *, limit=50, offset=0, full=False):
|
|
statement, params = read_queries._reference_diagram_projection(
|
|
"SELECT %s::jsonb AS snapshot", (Jsonb(original),), section,
|
|
)
|
|
return await read_queries._snapshot_page(
|
|
conn, stored_sql=statement, stored_params=params, data_path=list(path),
|
|
limit=limit, offset=offset, mcp_sources=[], full_snapshot=full, max_bytes=8000,
|
|
)
|
|
|
|
scalar = await page([section, "diagram", "node_refs", "0", "status"])
|
|
assert scalar["value"] == "reference"
|
|
second = await page([section, "diagram", "node_refs"], limit=1, offset=1)
|
|
assert second["value"][0]["status"] == "recorded"
|
|
assert second["total"] == 9 and second["nested"]
|
|
assert second["value"][0]["evidence_refs"]["data_path"] == [section, "diagram", "node_refs", "1", "evidence_refs"]
|
|
snapshot = await page(full=True)
|
|
nodes = snapshot["data"][section]["diagram"]["node_refs"]
|
|
assert [node.get("status") for node in nodes] == [
|
|
"reference", "recorded", "needs_input", "needs_input", "not_taken", "not_taken", "failed", None, None,
|
|
]
|
|
assert "status" not in nodes[-1]
|
|
assert nodes[6]["detail"] == "保留原说明" and nodes[6]["evidence_refs"] == ["e1"]
|
|
assert snapshot["data"]["status"] == "running"
|
|
assert snapshot["data"]["action"]["status"] == "completed"
|
|
assert snapshot["data"][section]["diagram"]["mermaid"] == original["data"][section]["diagram"]["mermaid"]
|
|
assert original == untouched
|
|
|
|
asyncio.run(check())
|
|
|
|
|
|
@pytest.mark.parametrize("diagram", [None, {}, {"node_refs": []}])
|
|
def test_reference_projection_preserves_empty_and_missing_legacy_diagrams(diagram):
|
|
dsn = os.environ.get("MCP_QUERY_TEST_DSN")
|
|
if not dsn:
|
|
pytest.skip("MCP_QUERY_TEST_DSN is required for a read-only PostgreSQL JSONB probe")
|
|
|
|
async def check():
|
|
original = {"section": "progress", "data": {"progress": {"diagram": diagram}, "status": "running"}}
|
|
async with await AsyncConnection.connect(
|
|
dsn, row_factory=dict_row, options="-c default_transaction_read_only=on",
|
|
) as conn:
|
|
statement, params = read_queries._reference_diagram_projection(
|
|
"SELECT %s::jsonb AS snapshot", (Jsonb(original),), "progress",
|
|
)
|
|
result = await read_queries._snapshot_page(
|
|
conn, stored_sql=statement, stored_params=params, data_path=[],
|
|
limit=20, offset=0, mcp_sources=[], full_snapshot=True,
|
|
)
|
|
assert result == original
|
|
|
|
asyncio.run(check())
|