Files
ARR-2.0-0918/tests/direct_data_portal.py

98 lines
4.2 KiB
Python

"""Explicit synthetic-data portal for local acceptance. No Oracle transport or credentials."""
import argparse
import hashlib
from contextlib import contextmanager
from datetime import date
import os
from pathlib import Path
import secrets
import signal
import tempfile
from types import SimpleNamespace
from arr_processing.policy import load_processor_policy
from arr_web.arr_data_executor import DirectARRExecutor
from arr_web.local_replay import ReplayRuntime
from arr_web.local_replay_database import ReplayDatabase
from arr_web.local_xml_replay import LocalReplayPortal, document
from arr_web.server import serve
from integrations.ohip.arr_data import ARRDataSource
from integrations.ohip.capture_job import atomic_json, fingerprint, job_lock, private_directory
from tests.test_ohip_arr_data import SimulatedOHIP, DAY, HOTEL, FIXTURE
PROJECT = Path(__file__).resolve().parents[1]
class DirectDataPortal(LocalReplayPortal):
allow_xml_upload = True
cookie_name = "arr_direct_data_simulation_session"
environment = "local-direct-data-simulation"
source_kind = "synthetic_direct_data"
filename_prefix = "LOCAL-DATA-"
banner_title = "本机接口数据模拟 · ARR 全流程"
banner_action = "直接处理 6 笔模拟订单,使用独立测试数据库。"
def identity(root, policy):
return {"version": "arr-local-direct-data/v1", "root": str(root), "hotel_id": HOTEL,
"report_date": DAY, "source_records": 6, "oracle_connected": False,
"fixture_sha256": hashlib.sha256(FIXTURE.read_bytes()).hexdigest(), "processor_version": policy.processor_version,
"rule_set_sha256": policy.rule_set_sha256}
def create(parent):
if parent.resolve().is_relative_to(PROJECT):
raise ValueError("local_data_must_be_outside_repository")
root = Path(tempfile.mkdtemp(prefix="arr-direct-data-", dir=parent)).resolve()
atomic_json(root / "instance.json", identity(root, load_processor_policy(PROJECT)), replace=False)
atomic_json(root / "login.json", {"username": "arr-data", "password": secrets.token_urlsafe(24)}, replace=False)
return root
@contextmanager
def open_portal(root, port):
if root.is_symlink() or root.resolve().is_relative_to(PROJECT) or not 1024 <= port <= 65535:
raise ValueError("invalid_local_instance")
root = root.resolve(); private_directory(root)
policy = load_processor_policy(PROJECT)
if document(root / "instance.json") != identity(root, policy):
raise ValueError("local_instance_source_or_rules_changed")
snapshot = SimpleNamespace(day=date.fromisoformat(DAY), manifest={"source_records": 6})
with job_lock(root):
database = ReplayDatabase(root, schema_version=19)
try:
database.start()
def factory(*, root, snapshot, **dependencies):
source = ARRDataSource(root / "source", HOTEL, transport_factory=lambda: SimulatedOHIP(6))
return DirectARRExecutor(root=root / "processing", source=source, **dependencies)
runtime = ReplayRuntime(root, database, snapshot, policy, port,
executor_factory=factory, portal_type=DirectDataPortal, enable_upload=True)
try:
yield runtime
finally:
runtime.close()
finally:
database.close()
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("action", choices=["init", "serve"])
parser.add_argument("--root", type=Path)
parser.add_argument("--parent", type=Path, default=Path(tempfile.gettempdir()))
parser.add_argument("--port", type=int, default=8875)
args = parser.parse_args(); os.umask(0o077)
if args.action == "init":
print(create(args.parent)); return
if args.root is None: parser.error("serve requires --root")
def stop(*_): raise KeyboardInterrupt
signal.signal(signal.SIGTERM, stop)
try:
with open_portal(args.root, args.port) as runtime:
runtime.start_monthly_worker()
print(f"Local direct-data simulation: http://127.0.0.1:{args.port}/", flush=True)
serve(runtime.app, "127.0.0.1", args.port)
except KeyboardInterrupt:
pass
if __name__ == "__main__": main()