Files
next-tjwater-server/tests/integration/test_synthetic_check_cleanup.py

327 lines
12 KiB
Python

"""Opt-in boundary checks for the synthetic-check cleanup migration."""
import json
import os
from datetime import datetime, timezone
from pathlib import Path
from uuid import UUID, uuid4
import psycopg
import pytest
from psycopg.rows import dict_row
from app.core.config import get_pgconn_string
from samples.peripheral.seed_identity import SEED_ACTOR_ID
pytestmark = pytest.mark.skipif(
os.environ.get("OPERATIONAL_TASKS_STORAGE_TEST") != "1",
reason="explicit PostgreSQL test opt-in required",
)
@pytest.fixture
def anyio_backend():
return "asyncio"
def _migration_sql(*names: str) -> str:
root = Path(__file__).parents[2]
sql = "\n".join((root / f"resources/sql/{name}").read_text() for name in names)
return sql.replace("\nBEGIN;", "").replace("\nCOMMIT;", "")
async def _insert_task(
conn: psycopg.AsyncConnection,
*,
project_id: UUID,
external_id: str,
event_kind: str,
title: str,
task_type: str = "external_work_order",
) -> UUID:
task_id = uuid4()
request_id = uuid4()
execution_request_id = uuid4()
now = datetime.now(timezone.utc)
document = {
"project_id": str(project_id),
"task_id": str(task_id),
"task_type": task_type,
"workflow": "external-work-order" if task_type == "external_work_order" else event_kind,
"status": "completed",
"lifecycle_status": "completed",
"priority": "normal",
"revision": 1,
"source": {
"kind": "external_system",
"external_id": external_id,
"event_kind": event_kind,
},
"title": title,
}
await conn.execute(
"""INSERT INTO workbench.operational_tasks (
project_id,task_id,create_request_id,task_type,workflow,status,priority,
revision,execution_generation,execution_request_id,created_at,updated_at,
document,lifecycle_status
) VALUES (%s,%s,%s,%s,%s,'completed','normal',1,1,%s,%s,%s,%s::jsonb,'completed')""",
(
project_id,
task_id,
request_id,
task_type,
document["workflow"],
execution_request_id,
now,
now,
json.dumps(document),
),
)
return task_id
async def _insert_source_order(
conn: psycopg.AsyncConnection,
*,
project_id: UUID,
work_order_id: UUID,
title: str,
created_by: UUID = SEED_ACTOR_ID,
deleted: bool = True,
) -> None:
now = datetime.now(timezone.utc)
document = {
"project_id": str(project_id),
"work_order_id": str(work_order_id),
"revision": 1,
"title": title,
"created_by": str(created_by),
"origin": "synthetic",
}
await conn.execute(
"""INSERT INTO peripheral_sample.work_orders (
project_id,work_order_id,revision,document,deleted_at
) VALUES (%s,%s,1,%s::jsonb,%s)""",
(project_id, work_order_id, json.dumps(document), now if deleted else None),
)
@pytest.mark.anyio
async def test_cleanup_deletes_only_unlinked_exact_namespace_and_is_idempotent():
base_migration = _migration_sql(
"014_operational_tasks.sql",
"015_workbench_events_and_task_runs.sql",
)
cleanup_migration = _migration_sql("018_remove_synthetic_check_task_projections.sql")
project_id = uuid4()
conn = await psycopg.AsyncConnection.connect(
get_pgconn_string(), row_factory=dict_row, connect_timeout=3
)
try:
async with conn.transaction(force_rollback=True):
await conn.execute(base_migration)
await conn.execute("CREATE SCHEMA IF NOT EXISTS peripheral_sample")
await conn.execute(
"""CREATE TABLE IF NOT EXISTS peripheral_sample.work_orders (
project_id uuid NOT NULL,
work_order_id uuid NOT NULL,
revision bigint NOT NULL,
document jsonb NOT NULL,
deleted_at timestamptz,
PRIMARY KEY (project_id,work_order_id)
)"""
)
removed_order_id = uuid4()
await _insert_source_order(
conn,
project_id=project_id,
work_order_id=removed_order_id,
title="合成通信核查",
deleted=False,
)
removed_id = await _insert_task(
conn,
project_id=project_id,
external_id=f"peripheral_sample:{removed_order_id}",
event_kind="general",
title=" 合成通信核查",
)
deleted_artifact_order_id = uuid4()
await _insert_source_order(
conn,
project_id=project_id,
work_order_id=deleted_artifact_order_id,
title="合成联调:预约工单编辑已对齐",
created_by=uuid4(),
)
deleted_user_order_id = await _insert_task(
conn,
project_id=project_id,
external_id=f"peripheral_sample:{deleted_artifact_order_id}",
event_kind="general",
title="合成联调:预约工单编辑已对齐",
)
suffix_alias_id = await _insert_task(
conn,
project_id=project_id,
external_id=f"peripheral_sample:{removed_order_id}:suffix",
event_kind="general",
title="合成但不是规范来源标识",
)
similar_namespace_id = await _insert_task(
conn,
project_id=project_id,
external_id=f"peripheralXsample:{uuid4()}",
event_kind="general",
title="合成但属于其他命名空间",
)
event_order_id = uuid4()
await _insert_source_order(
conn,
project_id=project_id,
work_order_id=event_order_id,
title="合成水质投诉",
)
event_id = await _insert_task(
conn,
project_id=project_id,
external_id=f"peripheral_sample:{event_order_id}",
event_kind="water_quality_complaint",
title="合成水质投诉",
)
linked_order_id = uuid4()
await _insert_source_order(
conn,
project_id=project_id,
work_order_id=linked_order_id,
title="合成现场采样",
)
linked_id = await _insert_task(
conn,
project_id=project_id,
external_id=f"peripheral_sample:{linked_order_id}",
event_kind="general",
title="合成现场采样",
)
normal_order_id = uuid4()
await _insert_source_order(
conn,
project_id=project_id,
work_order_id=normal_order_id,
title="常规预约",
created_by=uuid4(),
)
normal_id = await _insert_task(
conn,
project_id=project_id,
external_id=f"peripheral_sample:{normal_order_id}",
event_kind="general",
title="常规预约",
)
action_id = uuid4()
now = datetime.now(timezone.utc)
action_document = {
"action_id": str(action_id),
"version": 1,
"kind": "create_work_order",
"approval_status": "approved",
"execution_status": "succeeded",
"receipt": {
"adapter": "peripheral_sample",
"receipt_id": str(linked_order_id),
},
}
await conn.execute(
"""INSERT INTO workbench.operational_task_actions (
project_id,task_id,action_id,version,approval_status,execution_status,
document,created_at,updated_at
) VALUES (%s,%s,%s,1,'approved','succeeded',%s::jsonb,%s,%s)""",
(project_id, linked_id, action_id, json.dumps(action_document), now, now),
)
await conn.execute(
"""INSERT INTO workbench.operational_task_requests (
project_id,scope,request_id,request_digest,response
) VALUES (%s,%s,%s,'test',%s::jsonb)""",
(
project_id,
f"task:{removed_id}:create",
uuid4(),
json.dumps({"task_id": str(removed_id)}),
),
)
await conn.execute(
"""INSERT INTO workbench.operational_task_requests (
project_id,scope,request_id,request_digest,response
) VALUES (%s,%s,%s,'test',%s::jsonb)""",
(
project_id,
f"notification:{uuid4()}:ack",
uuid4(),
json.dumps({"notification": {"task_id": str(removed_id)}}),
),
)
await conn.execute(cleanup_migration)
rows = await (
await conn.execute(
"""SELECT task_id FROM workbench.operational_tasks
WHERE project_id=%s AND task_id=ANY(%s)""",
(
project_id,
[
removed_id,
deleted_user_order_id,
suffix_alias_id,
similar_namespace_id,
event_id,
linked_id,
normal_id,
],
),
)
).fetchall()
assert {row["task_id"] for row in rows} == {
similar_namespace_id,
deleted_user_order_id,
suffix_alias_id,
event_id,
linked_id,
normal_id,
}
request_count = await (
await conn.execute(
"""SELECT count(*) AS count FROM workbench.operational_task_requests
WHERE project_id=%s AND (
response->>'task_id'=%s
OR response#>>'{notification,task_id}'=%s
)""",
(project_id, str(removed_id), str(removed_id)),
)
).fetchone()
assert request_count["count"] == 0
cleanup_event_count = await (
await conn.execute(
"""SELECT count(*) AS count FROM workbench.domain_event_log
WHERE project_id=%s
AND metadata->>'reason'='synthetic_check_projection_cleanup'""",
(project_id,),
)
).fetchone()
assert cleanup_event_count["count"] == 1
await conn.execute("DROP TABLE synthetic_check_task_cleanup")
await conn.execute(cleanup_migration)
repeated_event_count = await (
await conn.execute(
"""SELECT count(*) AS count FROM workbench.domain_event_log
WHERE project_id=%s
AND metadata->>'reason'='synthetic_check_projection_cleanup'""",
(project_id,),
)
).fetchone()
assert repeated_event_count["count"] == 1
finally:
await conn.close()