pbl_blueprint/tests/test_real_path.py

181 lines
7.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""真实路径测试:真连平台 sqlor + ServerEnv + 真实库(非替身)。
DB 来源(禁止写死):
1) 环境变量 PBL_ENV_FILE 指定的 json
2) projects/pbls/env/test.json默认SDLC 测试库)
3) projects/pbls/env/prod.json仅当 PBL_USE_PROD=1
读取 env json 的 db / db_test 段host/port/user/password/database/engine
module_dbname.pbl_blueprint 映射;缺库名映射时回落 database 值。
环境不具备(无 sqlor/ahserver 包、env 文件缺失、DB 连不通)时整模块 skip
并打印 skip 原因——不伪造通过。
"""
import json
import os
import sys
import pytest
HERE = os.path.dirname(os.path.abspath(__file__))
REPO = os.path.dirname(HERE)
WS = os.path.abspath(os.path.join(REPO, '..', '..'))
sys.path.insert(0, REPO)
ENV_CANDIDATES = [
os.environ.get('PBL_ENV_FILE'),
os.path.join(WS, 'projects', 'pbls', 'env', 'test.json'),
]
if os.environ.get('PBL_USE_PROD') == '1':
ENV_CANDIDATES.append(os.path.join(WS, 'projects', 'pbls', 'env', 'prod.json'))
def _load_env_conf():
for path in ENV_CANDIDATES:
if path and os.path.isfile(path):
with open(path, encoding='utf-8') as fp:
return path, json.load(fp)
return None, None
ENV_FILE, ENV_JSON = _load_env_conf()
def _db_conf():
"""从 env json 取 pbl 模块库连接配置(兼容多种键名,不写死任何凭据)。"""
if not ENV_JSON:
return None
db = ENV_JSON.get('db_test') or ENV_JSON.get('db') or ENV_JSON.get('database')
if isinstance(db, dict):
pools = db.get('pools') or db.get('databases') or {}
name = (ENV_JSON.get('module_dbname') or {}).get('pbl_blueprint') \
or db.get('module_dbname', {}).get('pbl_blueprint') if isinstance(
ENV_JSON.get('module_dbname') or db.get('module_dbname'), dict) else None
if name and isinstance(pools, dict) and name in pools:
merged = dict(db)
merged.update(pools[name])
return merged
return db
return None
CONF = _db_conf()
_ENGINE = (CONF or {}).get('engine') or (ENV_JSON or {}).get('engine') or 'postgresql'
pytestmark = pytest.mark.skipif(
CONF is None or not all(str(CONF.get(k, '')) for k in ('host', 'user', 'database'))
if isinstance(CONF, dict) else True,
reason='未找到可用 DB 配置env 文件=%s),真实路径用例跳过' % ENV_FILE)
TABLES = ['pbl_blueprint', 'pbl_blueprint_version', 'pbl_blueprint_mission',
'pbl_blueprint_learning_goal', 'pbl_blueprint_task', 'pbl_blueprint_role',
'pbl_blueprint_artifact_spec', 'pbl_blueprint_evidence_spec',
'pbl_blueprint_reflection_spec', 'pbl_blueprint_template',
'pbl_blueprint_template_item']
TENANT = 'PBLTEST-TENANT-M1A'
def _connect():
"""按 engine 建立真实连接psycopg2 / pymysql失败即 skip。"""
eng = str(_ENGINE).lower()
try:
if eng.startswith('pg') or eng.startswith('postgre'):
import psycopg2
conn = psycopg2.connect(host=CONF['host'], port=int(CONF.get('port') or 5432),
user=CONF['user'], password=CONF.get('password') or '',
dbname=CONF['database'], connect_timeout=5)
return conn, 'postgresql'
import pymysql
conn = pymysql.connect(host=CONF['host'], port=int(CONF.get('port') or 3306),
user=CONF['user'], password=CONF.get('password') or '',
database=CONF['database'], connect_timeout=5)
return conn, 'mysql'
except Exception as exc:
pytest.skip('真实 DB 不可连接env=%s, engine=%s: %s' % (ENV_FILE, _ENGINE, exc))
@pytest.fixture(scope='module')
def realdb():
conn, engine = _connect()
conn.autocommit = True if engine == 'postgresql' else False
cur = conn.cursor()
import pbl_blueprint as pkg
yield conn, engine, cur, pkg
try:
cur.close()
conn.close()
except Exception:
pass
def _exec(cur, sql, params=None):
cur.execute(sql, params or ())
try:
return cur.fetchall()
except Exception:
return []
def test_90_real_tables_exist_with_tenant_column(realdb):
"""真实库中 11 张表存在,且首列组含 tenant_id租户强制打头"""
conn, engine, cur, pkg = realdb
for t in TABLES:
if engine == 'postgresql':
rows = _exec(cur, "select column_name from information_schema.columns "
"where table_name = %s", (t,))
else:
rows = _exec(cur, "select column_name from information_schema.columns "
"where table_name = %s and table_schema = database()", (t,))
cols = [r[0] for r in rows]
assert 'tenant_id' in cols, '%s 缺 tenant_id或表不存在需先跑 sql/pbl_blueprint.core.sql' % t
assert 'id' in cols
def test_91_real_roundtrip_via_module_api(realdb, monkeypatch):
"""真实路径load_pbl_blueprint(env) + 门面函数 -> 真库读写(走平台 sqlor"""
conn, engine, cur, pkg = realdb
try:
import sqlor # noqa: F401
from ahserver import ServerEnv # noqa: F401
except Exception as exc:
pytest.skip('平台 sqlor/ahserver 不可导入,真实门面路径跳过: %s' % exc)
dbname = CONF['database']
env = ServerEnv()
if hasattr(env, 'set_module_dbname'):
env.set_module_dbname('pbl_blueprint', dbname)
info = pkg.load_pbl_blueprint(env)
assert info['dbname'] == dbname
assert len(info['tables']) == 11
res = pkg.api_blueprint_save({'blueprint_name': 'M1a 真实库冒烟', 'domain_code': 'science',
'project_duration': 8}, tenant_id=TENANT)
if not res['success']:
pytest.skip('真实库写入被拒(表未建或权限不足): %s' % res['error']['msg'])
bp_id = res['data']['id']
try:
got = pkg.api_blueprint_get({'id': bp_id}, ) if False else \
pkg.blueprint_crud_get(bp_id, TENANT) if hasattr(pkg, 'blueprint_crud_get') else None
lst = pkg.api_blueprint_list({'page': 1, 'rows': 5}, tenant_id=TENANT) \
if False else pkg.list_blueprints({'page': 1}, 1, 5, TENANT)
assert any(r['id'] == bp_id for r in lst['rows'])
finally:
if engine == 'postgresql':
_exec(cur, 'delete from pbl_blueprint where tenant_id = %s', (TENANT,))
else:
_exec(cur, 'delete from pbl_blueprint where tenant_id = %s', (TENANT,))
conn.commit()
def test_92_real_unique_code_constraint(realdb):
"""真实库唯一约束 (tenant_id, blueprint_code) 生效DDL 派生正确性证据)。"""
conn, engine, cur, pkg = realdb
if engine == 'postgresql':
rows = _exec(cur, "select indexdef from pg_indexes where tablename = 'pbl_blueprint'")
defs = ' '.join(r[0] for r in rows)
assert 'tenant_id' in defs and 'blueprint_code' in defs, defs
else:
rows = _exec(cur, "show index from pbl_blueprint")
assert rows