| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- from __future__ import annotations
- import os
- import pytest
- from neo4j import GraphDatabase
- from sqlalchemy import create_engine, text
- from sqlalchemy.orm import Session
- from app.core.common.identifiers import new_governance_uid
- from app.core.data_flow.create_reconciliation import DataFlowCreateReconciler
- from app.core.data_flow.dataflows import DataFlowService
- from app.core.data_rules.repository import DataRuleRepository
- def test_real_postgres_neo4j_orphan_is_reconciled_from_stored_intent(
- monkeypatch,
- ):
- pg_url = os.environ.get("DATA_RULE_POSTGRES_ACCEPTANCE_URL")
- neo4j_uri = os.environ.get("DATA_RULE_NEO4J_ACCEPTANCE_URI")
- password = os.environ.get("DATA_RULE_NEO4J_ACCEPTANCE_PASSWORD")
- if not pg_url or not neo4j_uri or not password:
- pytest.skip("real PostgreSQL and Neo4j acceptance are not configured")
- user = os.environ.get("DATA_RULE_NEO4J_ACCEPTANCE_USER", "neo4j")
- monkeypatch.setenv("DATABASE_URL", pg_url)
- from app import create_app
- app = create_app()
- app.config.update(
- TESTING=True,
- NEO4J_URI=neo4j_uri,
- NEO4J_USER=user,
- NEO4J_PASSWORD=password,
- NEO4J_ENCRYPTED=False,
- )
- pg = create_engine(pg_url)
- graph = GraphDatabase.driver(
- neo4j_uri, auth=(user, password), encrypted=False
- )
- actor = new_governance_uid()
- reservation_id = None
- dataflow_uid = None
- try:
- with app.app_context(), Session(pg) as session:
- session.execute(
- text(
- "INSERT INTO public.users "
- "(id, username, display_name, password_hash, status) "
- "VALUES (CAST(:id AS uuid), :username, "
- "'Reconcile Acceptance', 'not-a-login-hash', 'active')"
- ),
- {"id": actor, "username": f"reconcile-{actor}"},
- )
- session.commit()
- repository = DataRuleRepository(session)
- receipt = repository.reserve_dataflow_draft(actor_uid=actor)
- reservation_id = receipt["reservation_id"]
- dataflow_uid = receipt["dataflow_uid"]
- session.commit()
- node = {
- "uid": dataflow_uid,
- "name_zh": f"孤儿生产线-{dataflow_uid}",
- "name_en": f"orphan-{dataflow_uid}",
- "category": "应用类",
- "organization": "acceptance",
- "leader": "system",
- "frequency": "月",
- "describe": "reconciliation acceptance",
- "status": "active",
- "update_mode": "append",
- "script_type": "governed",
- "script_requirement": "{}",
- "script_path": "",
- }
- intent = {
- "dataflow_uid": dataflow_uid,
- "node": node,
- "tags": [],
- }
- create_request = {
- "payload": {"name_zh": node["name_zh"]},
- "intent": intent,
- }
- closed = {
- key: receipt[key]
- for key in ("reservation_id", "dataflow_uid", "nonce")
- }
- claim = repository.begin_dataflow_create(
- closed,
- actor_uid=actor,
- create_request=create_request,
- create_intent=intent,
- lease_seconds=10,
- )
- session.commit()
- DataFlowService._merge_governed_dataflow(node)
- session.execute(
- text(
- "UPDATE public.dataflow_draft_reservations "
- "SET lease_expires_at = CURRENT_TIMESTAMP - INTERVAL '1 second' "
- "WHERE id = CAST(:id AS uuid)"
- ),
- {"id": reservation_id},
- )
- session.commit()
- monkeypatch.setattr(
- DataFlowService,
- "validate_governed_create_intent",
- lambda value, *, repository: value,
- )
- reconciler = DataFlowCreateReconciler(repository)
- before_preview = session.execute(
- text(
- "SELECT state, attempt_count "
- "FROM public.dataflow_draft_reservations "
- "WHERE id = CAST(:id AS uuid)"
- ),
- {"id": reservation_id},
- ).one()
- dry_run = reconciler.run(dry_run=True)
- assert dry_run["candidate_count"] == 1
- after_preview = session.execute(
- text(
- "SELECT state, attempt_count "
- "FROM public.dataflow_draft_reservations "
- "WHERE id = CAST(:id AS uuid)"
- ),
- {"id": reservation_id},
- ).one()
- assert after_preview == before_preview
- report = reconciler.run(dry_run=False)
- assert report["reconciled_count"] == 1
- assert report["items"][0]["attempt"] == claim["attempt"] + 1
- assert reconciler.run(dry_run=False)["candidate_count"] == 0
- state = session.execute(
- text(
- "SELECT state, request_digest, result_digest "
- "FROM public.dataflow_draft_reservations "
- "WHERE id = CAST(:id AS uuid)"
- ),
- {"id": reservation_id},
- ).mappings().one()
- assert state["state"] == "completed"
- assert state["request_digest"]
- assert state["result_digest"]
- with graph.session() as graph_session:
- count = graph_session.run(
- "MATCH (n:DataFlow {uid: $uid}) RETURN count(n) AS count",
- {"uid": dataflow_uid},
- ).single()["count"]
- assert count == 1
- finally:
- if dataflow_uid:
- with graph.session() as graph_session:
- graph_session.run(
- "MATCH (n:DataFlow {uid: $uid}) DETACH DELETE n",
- {"uid": dataflow_uid},
- )
- graph.close()
- with Session(pg) as session:
- if reservation_id:
- session.execute(
- text(
- "DELETE FROM public.dataflow_draft_reservations "
- "WHERE id = CAST(:id AS uuid)"
- ),
- {"id": reservation_id},
- )
- session.execute(
- text(
- "DELETE FROM public.users WHERE id = CAST(:id AS uuid)"
- ),
- {"id": actor},
- )
- session.commit()
- pg.dispose()
|