"""合成参数接口验收:隔离 H2/Go Edge/Postgres/酒店,无文件上传、无现有服务操作。""" import argparse import json import os from pathlib import Path import secrets import subprocess import sys import time HERE = Path(__file__).resolve().parent PROJECT = HERE.parents[3] CLASSIFICATION = 'LOCAL SIMULATION ONLY' def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--run-dir', required=True, type=Path, help='必须是尚不存在的独立目录') parser.add_argument('--platform-dir', required=True, type=Path) parser.add_argument('--pg-bin-dir', required=True, type=Path) parser.add_argument('--java-home', required=True, type=Path) parser.add_argument('--go', default='go') parser.add_argument('--catalog', type=Path, default=HERE / 'catalog.example.json') parser.add_argument('--classpath', type=Path, help='已完成项目编译时,可复用测试依赖classpath文件') args = parser.parse_args() directory = args.run_dir.resolve() directory.mkdir(mode=0o700, parents=True, exist_ok=False) token = secrets.token_hex(24) marker = directory / '.local-fit-simulation.json' with os.fdopen(os.open(marker, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600), 'w') as stream: json.dump({'classification': CLASSIFICATION, 'token': token}, stream) env = {k: v for k, v in os.environ.items() if k in {'PATH', 'HOME', 'USER', 'LOGNAME', 'TMPDIR', 'LANG', 'LC_ALL'}} env['JAVA_HOME'] = str(args.java_home.resolve()) stage = 'compile' result = {'classification': CLASSIFICATION, 'status': 'running', 'inputs': 'synthetic confirmed parameters, no uploaded files'} edge = None log = None def run(command, name, cwd=PROJECT): with (directory / (name + '.log')).open('w') as output: subprocess.run(list(map(str, command)), cwd=cwd, env=env, stdout=output, stderr=subprocess.STDOUT, check=True) try: dependency = args.classpath.resolve() if args.classpath else directory / 'classpath.txt' if args.classpath is None: run([PROJECT / 'server/mvnw', '-q', '-DskipTests', 'test-compile', 'dependency:build-classpath', '-Dmdep.includeScope=test', '-Dmdep.outputFile=' + str(dependency)], 'maven', PROJECT / 'server') classes = directory / 'classes'; classes.mkdir() classpath = os.pathsep.join([str(classes), str(PROJECT / 'server/target/classes'), dependency.read_text().strip()]) run([args.java_home / 'bin/javac', '-cp', classpath, '-d', classes, *sorted((HERE / 'java').glob('*.java'))], 'fixture-compile') main_class = 'cn.nianxx.thhotel.integrations.ohip.edge.adapter.' run([args.java_home / 'bin/java', '-cp', classpath, main_class + 'LocalBookingCatalogCheck', args.catalog.resolve()], 'catalog-check') stage = 'edge-start' log = (directory / 'edge-supervisor.log').open('w') edge = subprocess.Popen([sys.executable, str(HERE / 'run_edge.py'), '--output-dir', str(directory / 'edge'), '--platform-dir', str(args.platform_dir.resolve()), '--pg-bin-dir', str(args.pg_bin_dir.resolve()), '--java-home', str(args.java_home.resolve()), '--go', args.go, '--token', token, '--catalog', str(args.catalog.resolve())], cwd=PROJECT, env=env, stdout=log, stderr=subprocess.STDOUT) connection = directory / 'edge/runtime-connection.json' deadline = time.monotonic() + 300 while not connection.is_file(): if edge.poll() is not None: raise RuntimeError('edge_start_failed') if time.monotonic() > deadline: raise TimeoutError('edge_start_timeout') time.sleep(.2) ta_requests = 0 for mode in ('parameters', 'faults'): stage = mode print(json.dumps({'classification': CLASSIFICATION, 'stage': mode}), flush=True) run([args.java_home / 'bin/java', '-cp', classpath, main_class + 'LocalBookingEvolutionCheck', connection, directory / mode, mode], mode) # 只有创建Reservation的原生externalReferences TA类型允许携带TA。 for trace in (directory / mode / 'interface-audit').glob('*/*.json'): audit = json.loads(trace.read_text()) request = audit.get('request_values', []) for row in request: if row.get('field') in {'taRecordLocatorList', '__localSimulationTaRecordLocator'}: raise RuntimeError('ta_search_or_legacy_field_forbidden') if row.get('field') == 'taRecordLocator': if audit.get('operation') != 'postReservation' or not row.get('path', '').endswith('/externalReferences/0/id'): raise RuntimeError('ta_only_allowed_on_fit_creation') if not any(r.get('field') == 'referenceType' and r.get('value') == 'TA_RECORD_LOCATOR' for r in request): raise RuntimeError('ta_reference_type_missing') ta_requests += 1 if ta_requests == 0: raise RuntimeError('ta_creation_not_exercised') result['ta_requests'] = ta_requests result['status'] = 'passed' except BaseException as error: result.update(status='failed', stage=stage) raise RuntimeError("Parameter acceptance failed at " + stage + "; inspect its local log") from None finally: if edge is not None and edge.poll() is None: edge.terminate() try: edge.wait(timeout=50) except subprocess.TimeoutExpired: result.update(status='cleanup_required', owned_supervisor_pid=edge.pid) if log is not None: log.close() (directory / 'acceptance-result.json').write_text(json.dumps(result, indent=2) + '\n') print(json.dumps(result), flush=True) if __name__ == '__main__': main()