fix(security): retire default-credential surface (yanxue-20260914-cma-defaultcred-code-fix)
- Login.vue: no prefilled default creds; no anonymous entity enumeration on mount - deploy.sh: readiness polling (0.5s/30s cap) replaces fixed sleep 3; login smoke reads same-source 600 secret file, missing file = explicit abort - 19 verify/check/gen_report scripts: shared _admin_creds() loader (CMA_ADMIN_ENV -> ops .secrets -> profile copy); no hardcoded creds - tests: single TEST_USER_PASSWORD fixture constant; docs: pointer to 600 secret file; removed 4 legacy .bak + pycache residue
This commit is contained in:
@@ -26,6 +26,32 @@ import urllib.request
|
||||
|
||||
import pymysql
|
||||
|
||||
# 2026-09-14 凭据面收口(cma-defaultcred-code-fix):原快照里的硬编码默认账号/口令改为同源加载,
|
||||
# 快照的期/阈值断言一字未动(历史原文仍在 git 历史里)。
|
||||
# ---- 管理员凭据同源加载(2026-09-14 cma-defaultcred-code-fix)----
|
||||
# 生产凭据只存 600 密钥文件;禁止在脚本/文档/日志里硬编码口令。
|
||||
# 顺序:$CMA_ADMIN_ENV → wecom-ops 主副本(ops 轮换落点,权威) → 本 profile 副本。
|
||||
def _admin_creds():
|
||||
"""返回 (user, password, 来源文件);未找到 → 显式报错退出(不静默退回默认口令)"""
|
||||
for p in (os.environ.get("CMA_ADMIN_ENV"),
|
||||
"/root/.hermes/profiles/wecom-ops/.secrets/cma-admin.env",
|
||||
"/root/.hermes/profiles/wecom-fullstack/.secrets/cma-admin.env"):
|
||||
if p and os.path.isfile(p):
|
||||
kv = {}
|
||||
for line in open(p, encoding="utf-8"):
|
||||
line = line.strip()
|
||||
if "=" in line and not line.startswith("#"):
|
||||
k, v = line.split("=", 1)
|
||||
kv[k.strip()] = v.strip().strip('"').strip("'")
|
||||
if kv.get("CMA_ADMIN_USER") and kv.get("CMA_ADMIN_PASS"):
|
||||
return kv["CMA_ADMIN_USER"], kv["CMA_ADMIN_PASS"], p
|
||||
raise SystemExit("[错误] 未找到 CMA 管理员凭据:请设 $CMA_ADMIN_ENV,或准备 600 权限密钥文件 "
|
||||
".secrets/cma-admin.env(键名 CMA_ADMIN_USER / CMA_ADMIN_PASS)")
|
||||
|
||||
|
||||
ADMIN_USER, ADMIN_PASS, CRED_SRC = _admin_creds()
|
||||
print(f"[creds] 来源={CRED_SRC} 账号={ADMIN_USER}(口令不回显)")
|
||||
|
||||
if os.environ.get("FORCE_SNAPSHOT") != "1":
|
||||
print("⛔ 这是 20260908 的历史验收快照,不是活检查;复跑结果不代表当前回归。")
|
||||
print(" 现行活检查:python3 backend/scripts/verify_bep_threshold_fix.py")
|
||||
@@ -120,7 +146,7 @@ check("576 GAP red pending", alerts.get(576) == ("red", "pending"), str(alerts.g
|
||||
check("577 COVER_CASH yellow pending", alerts.get(577) == ("yellow", "pending"), str(alerts.get(577)))
|
||||
|
||||
print("== 5. bep-card 端点 ==")
|
||||
login = http_json("/api/cma/auth/login", "POST", {"username": "admin", "password": "admin123", "entity_id": 1})
|
||||
login = http_json("/api/cma/auth/login", "POST", {"username": ADMIN_USER, "password": ADMIN_PASS, "entity_id": 1})
|
||||
card = http_json("/api/cma/dashboard/bep-card", token=login["token"])
|
||||
check("available", card.get("available") is True)
|
||||
check("book red", card["book"]["cover_level"] == "red", str(card["book"]))
|
||||
|
||||
@@ -18,6 +18,30 @@ import sys
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
# ---- 管理员凭据同源加载(2026-09-14 cma-defaultcred-code-fix)----
|
||||
# 生产凭据只存 600 密钥文件;禁止在脚本/文档/日志里硬编码口令。
|
||||
# 顺序:$CMA_ADMIN_ENV → wecom-ops 主副本(ops 轮换落点,权威) → 本 profile 副本。
|
||||
def _admin_creds():
|
||||
"""返回 (user, password, 来源文件);未找到 → 显式报错退出(不静默退回默认口令)"""
|
||||
for p in (os.environ.get("CMA_ADMIN_ENV"),
|
||||
"/root/.hermes/profiles/wecom-ops/.secrets/cma-admin.env",
|
||||
"/root/.hermes/profiles/wecom-fullstack/.secrets/cma-admin.env"):
|
||||
if p and os.path.isfile(p):
|
||||
kv = {}
|
||||
for line in open(p, encoding="utf-8"):
|
||||
line = line.strip()
|
||||
if "=" in line and not line.startswith("#"):
|
||||
k, v = line.split("=", 1)
|
||||
kv[k.strip()] = v.strip().strip('"').strip("'")
|
||||
if kv.get("CMA_ADMIN_USER") and kv.get("CMA_ADMIN_PASS"):
|
||||
return kv["CMA_ADMIN_USER"], kv["CMA_ADMIN_PASS"], p
|
||||
raise SystemExit("[错误] 未找到 CMA 管理员凭据:请设 $CMA_ADMIN_ENV,或准备 600 权限密钥文件 "
|
||||
".secrets/cma-admin.env(键名 CMA_ADMIN_USER / CMA_ADMIN_PASS)")
|
||||
|
||||
|
||||
ADMIN_USER, ADMIN_PASS, CRED_SRC = _admin_creds()
|
||||
print(f"[creds] 来源={CRED_SRC} 账号={ADMIN_USER}(口令不回显)")
|
||||
|
||||
BASE = "http://127.0.0.1:8010"
|
||||
MARK = "【系统验证临时数据-验证后删除】"
|
||||
KPI_CODE = "E2E_WT_TIER"
|
||||
@@ -75,7 +99,7 @@ def main():
|
||||
|
||||
# ── 1. 登录(人token, entity=1) ──
|
||||
st, r = http("POST", "/api/cma/auth/login",
|
||||
{"username": "admin", "password": "admin123", "entity_id": 1})
|
||||
{"username": ADMIN_USER, "password": ADMIN_PASS, "entity_id": 1})
|
||||
check("登录 admin entity=1", st == 200, f"http={st}")
|
||||
token = r.get("token") if st == 200 else ""
|
||||
auth = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
@@ -11,6 +11,31 @@ import time
|
||||
import urllib.request
|
||||
import websocket
|
||||
|
||||
import os # noqa: E402 (凭据加载器需要)
|
||||
# ---- 管理员凭据同源加载(2026-09-14 cma-defaultcred-code-fix)----
|
||||
# 生产凭据只存 600 密钥文件;禁止在脚本/文档/日志里硬编码口令。
|
||||
# 顺序:$CMA_ADMIN_ENV → wecom-ops 主副本(ops 轮换落点,权威) → 本 profile 副本。
|
||||
def _admin_creds():
|
||||
"""返回 (user, password, 来源文件);未找到 → 显式报错退出(不静默退回默认口令)"""
|
||||
for p in (os.environ.get("CMA_ADMIN_ENV"),
|
||||
"/root/.hermes/profiles/wecom-ops/.secrets/cma-admin.env",
|
||||
"/root/.hermes/profiles/wecom-fullstack/.secrets/cma-admin.env"):
|
||||
if p and os.path.isfile(p):
|
||||
kv = {}
|
||||
for line in open(p, encoding="utf-8"):
|
||||
line = line.strip()
|
||||
if "=" in line and not line.startswith("#"):
|
||||
k, v = line.split("=", 1)
|
||||
kv[k.strip()] = v.strip().strip('"').strip("'")
|
||||
if kv.get("CMA_ADMIN_USER") and kv.get("CMA_ADMIN_PASS"):
|
||||
return kv["CMA_ADMIN_USER"], kv["CMA_ADMIN_PASS"], p
|
||||
raise SystemExit("[错误] 未找到 CMA 管理员凭据:请设 $CMA_ADMIN_ENV,或准备 600 权限密钥文件 "
|
||||
".secrets/cma-admin.env(键名 CMA_ADMIN_USER / CMA_ADMIN_PASS)")
|
||||
|
||||
|
||||
ADMIN_USER, ADMIN_PASS, CRED_SRC = _admin_creds()
|
||||
print(f"[creds] 来源={CRED_SRC} 账号={ADMIN_USER}(口令不回显)")
|
||||
|
||||
CDP_HTTP = "http://127.0.0.1:9224"
|
||||
BASE = "https://cma.sxbh.ltd/api/cma"
|
||||
|
||||
@@ -78,7 +103,7 @@ def login_via_fetch(c, entity_id):
|
||||
(async () => {{
|
||||
const r = await fetch('{BASE}/auth/login', {{
|
||||
method: 'POST', headers: {{'Content-Type': 'application/json'}},
|
||||
body: JSON.stringify({{username:'admin', password:'admin123', entity_id:{entity_id}}})
|
||||
body: JSON.stringify({{username:'{ADMIN_USER}', password:'{ADMIN_PASS}', entity_id:{entity_id}}})
|
||||
}});
|
||||
const j = await r.json();
|
||||
if (!r.ok) return 'LOGIN_FAIL:' + JSON.stringify(j);
|
||||
|
||||
@@ -3,6 +3,31 @@
|
||||
"""2026-09-07 预算版本管理UI修复 - 生产API验证"""
|
||||
import json, urllib.request, ssl, sys
|
||||
|
||||
import os # noqa: E402 (凭据加载器需要)
|
||||
# ---- 管理员凭据同源加载(2026-09-14 cma-defaultcred-code-fix)----
|
||||
# 生产凭据只存 600 密钥文件;禁止在脚本/文档/日志里硬编码口令。
|
||||
# 顺序:$CMA_ADMIN_ENV → wecom-ops 主副本(ops 轮换落点,权威) → 本 profile 副本。
|
||||
def _admin_creds():
|
||||
"""返回 (user, password, 来源文件);未找到 → 显式报错退出(不静默退回默认口令)"""
|
||||
for p in (os.environ.get("CMA_ADMIN_ENV"),
|
||||
"/root/.hermes/profiles/wecom-ops/.secrets/cma-admin.env",
|
||||
"/root/.hermes/profiles/wecom-fullstack/.secrets/cma-admin.env"):
|
||||
if p and os.path.isfile(p):
|
||||
kv = {}
|
||||
for line in open(p, encoding="utf-8"):
|
||||
line = line.strip()
|
||||
if "=" in line and not line.startswith("#"):
|
||||
k, v = line.split("=", 1)
|
||||
kv[k.strip()] = v.strip().strip('"').strip("'")
|
||||
if kv.get("CMA_ADMIN_USER") and kv.get("CMA_ADMIN_PASS"):
|
||||
return kv["CMA_ADMIN_USER"], kv["CMA_ADMIN_PASS"], p
|
||||
raise SystemExit("[错误] 未找到 CMA 管理员凭据:请设 $CMA_ADMIN_ENV,或准备 600 权限密钥文件 "
|
||||
".secrets/cma-admin.env(键名 CMA_ADMIN_USER / CMA_ADMIN_PASS)")
|
||||
|
||||
|
||||
ADMIN_USER, ADMIN_PASS, CRED_SRC = _admin_creds()
|
||||
print(f"[creds] 来源={CRED_SRC} 账号={ADMIN_USER}(口令不回显)")
|
||||
|
||||
BASE = "https://cma.sxbh.ltd/api/cma"
|
||||
|
||||
def http(url, data=None, headers=None, method=None):
|
||||
@@ -14,7 +39,7 @@ def http(url, data=None, headers=None, method=None):
|
||||
return json.loads(r.read().decode())
|
||||
|
||||
def login(entity_id):
|
||||
body = json.dumps({"username": "admin", "password": "admin123", "entity_id": entity_id}).encode()
|
||||
body = json.dumps({"username": ADMIN_USER, "password": ADMIN_PASS, "entity_id": entity_id}).encode()
|
||||
r = http(BASE + "/auth/login", data=body, headers={"Content-Type": "application/json"}, method="POST")
|
||||
return r["token"]
|
||||
|
||||
|
||||
@@ -18,6 +18,31 @@ import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
import os # noqa: E402 (凭据加载器需要)
|
||||
# ---- 管理员凭据同源加载(2026-09-14 cma-defaultcred-code-fix)----
|
||||
# 生产凭据只存 600 密钥文件;禁止在脚本/文档/日志里硬编码口令。
|
||||
# 顺序:$CMA_ADMIN_ENV → wecom-ops 主副本(ops 轮换落点,权威) → 本 profile 副本。
|
||||
def _admin_creds():
|
||||
"""返回 (user, password, 来源文件);未找到 → 显式报错退出(不静默退回默认口令)"""
|
||||
for p in (os.environ.get("CMA_ADMIN_ENV"),
|
||||
"/root/.hermes/profiles/wecom-ops/.secrets/cma-admin.env",
|
||||
"/root/.hermes/profiles/wecom-fullstack/.secrets/cma-admin.env"):
|
||||
if p and os.path.isfile(p):
|
||||
kv = {}
|
||||
for line in open(p, encoding="utf-8"):
|
||||
line = line.strip()
|
||||
if "=" in line and not line.startswith("#"):
|
||||
k, v = line.split("=", 1)
|
||||
kv[k.strip()] = v.strip().strip('"').strip("'")
|
||||
if kv.get("CMA_ADMIN_USER") and kv.get("CMA_ADMIN_PASS"):
|
||||
return kv["CMA_ADMIN_USER"], kv["CMA_ADMIN_PASS"], p
|
||||
raise SystemExit("[错误] 未找到 CMA 管理员凭据:请设 $CMA_ADMIN_ENV,或准备 600 权限密钥文件 "
|
||||
".secrets/cma-admin.env(键名 CMA_ADMIN_USER / CMA_ADMIN_PASS)")
|
||||
|
||||
|
||||
ADMIN_USER, ADMIN_PASS, CRED_SRC = _admin_creds()
|
||||
print(f"[creds] 来源={CRED_SRC} 账号={ADMIN_USER}(口令不回显)")
|
||||
|
||||
BASE = "http://127.0.0.1:8010"
|
||||
TMP_MARK = "【系统验证临时数据-验证后删除】"
|
||||
|
||||
@@ -52,7 +77,7 @@ def main():
|
||||
|
||||
# 1. 登录博海账套 (admin, entity_id=2)
|
||||
st, r = http("POST", "/api/cma/auth/login",
|
||||
{"username": "admin", "password": "admin123", "entity_id": 2})
|
||||
{"username": ADMIN_USER, "password": ADMIN_PASS, "entity_id": 2})
|
||||
check("登录博海(entity_id=2)", st == 200 and "token" in (r or {}),
|
||||
f"st={st} keys={sorted((r or {}).keys())}")
|
||||
token = (r or {}).get("token", "")
|
||||
|
||||
@@ -23,6 +23,30 @@ import urllib.request
|
||||
import urllib.error
|
||||
from datetime import datetime
|
||||
|
||||
# ---- 管理员凭据同源加载(2026-09-14 cma-defaultcred-code-fix)----
|
||||
# 生产凭据只存 600 密钥文件;禁止在脚本/文档/日志里硬编码口令。
|
||||
# 顺序:$CMA_ADMIN_ENV → wecom-ops 主副本(ops 轮换落点,权威) → 本 profile 副本。
|
||||
def _admin_creds():
|
||||
"""返回 (user, password, 来源文件);未找到 → 显式报错退出(不静默退回默认口令)"""
|
||||
for p in (os.environ.get("CMA_ADMIN_ENV"),
|
||||
"/root/.hermes/profiles/wecom-ops/.secrets/cma-admin.env",
|
||||
"/root/.hermes/profiles/wecom-fullstack/.secrets/cma-admin.env"):
|
||||
if p and os.path.isfile(p):
|
||||
kv = {}
|
||||
for line in open(p, encoding="utf-8"):
|
||||
line = line.strip()
|
||||
if "=" in line and not line.startswith("#"):
|
||||
k, v = line.split("=", 1)
|
||||
kv[k.strip()] = v.strip().strip('"').strip("'")
|
||||
if kv.get("CMA_ADMIN_USER") and kv.get("CMA_ADMIN_PASS"):
|
||||
return kv["CMA_ADMIN_USER"], kv["CMA_ADMIN_PASS"], p
|
||||
raise SystemExit("[错误] 未找到 CMA 管理员凭据:请设 $CMA_ADMIN_ENV,或准备 600 权限密钥文件 "
|
||||
".secrets/cma-admin.env(键名 CMA_ADMIN_USER / CMA_ADMIN_PASS)")
|
||||
|
||||
|
||||
ADMIN_USER, ADMIN_PASS, CRED_SRC = _admin_creds()
|
||||
print(f"[creds] 来源={CRED_SRC} 账号={ADMIN_USER}(口令不回显)")
|
||||
|
||||
BASE = "http://127.0.0.1:8010"
|
||||
MARK = "【系统验证临时数据-验证后删除】"
|
||||
PREFIX = "E2E-RD-"
|
||||
@@ -63,7 +87,7 @@ def main():
|
||||
|
||||
# ── 0. 登录(admin entity=1) ──
|
||||
st, r = http("POST", "/api/cma/auth/login",
|
||||
{"username": "admin", "password": "admin123", "entity_id": 1})
|
||||
{"username": ADMIN_USER, "password": ADMIN_PASS, "entity_id": 1})
|
||||
check("登录 admin entity=1", st == 200, f"http={st}")
|
||||
token = r.get("token") if st == 200 else ""
|
||||
auth = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
@@ -15,8 +15,33 @@ import sys
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
import os # noqa: E402 (凭据加载器需要)
|
||||
# ---- 管理员凭据同源加载(2026-09-14 cma-defaultcred-code-fix)----
|
||||
# 生产凭据只存 600 密钥文件;禁止在脚本/文档/日志里硬编码口令。
|
||||
# 顺序:$CMA_ADMIN_ENV → wecom-ops 主副本(ops 轮换落点,权威) → 本 profile 副本。
|
||||
def _admin_creds():
|
||||
"""返回 (user, password, 来源文件);未找到 → 显式报错退出(不静默退回默认口令)"""
|
||||
for p in (os.environ.get("CMA_ADMIN_ENV"),
|
||||
"/root/.hermes/profiles/wecom-ops/.secrets/cma-admin.env",
|
||||
"/root/.hermes/profiles/wecom-fullstack/.secrets/cma-admin.env"):
|
||||
if p and os.path.isfile(p):
|
||||
kv = {}
|
||||
for line in open(p, encoding="utf-8"):
|
||||
line = line.strip()
|
||||
if "=" in line and not line.startswith("#"):
|
||||
k, v = line.split("=", 1)
|
||||
kv[k.strip()] = v.strip().strip('"').strip("'")
|
||||
if kv.get("CMA_ADMIN_USER") and kv.get("CMA_ADMIN_PASS"):
|
||||
return kv["CMA_ADMIN_USER"], kv["CMA_ADMIN_PASS"], p
|
||||
raise SystemExit("[错误] 未找到 CMA 管理员凭据:请设 $CMA_ADMIN_ENV,或准备 600 权限密钥文件 "
|
||||
".secrets/cma-admin.env(键名 CMA_ADMIN_USER / CMA_ADMIN_PASS)")
|
||||
|
||||
|
||||
ADMIN_USER, ADMIN_PASS, CRED_SRC = _admin_creds()
|
||||
print(f"[creds] 来源={CRED_SRC} 账号={ADMIN_USER}(口令不回显)")
|
||||
|
||||
BASE = "http://127.0.0.1:8010"
|
||||
ADMIN = {"username": "admin", "password": "admin123"}
|
||||
ADMIN = {"username": ADMIN_USER, "password": ADMIN_PASS}
|
||||
MARK = "【系统验证临时数据-验证后删除】"
|
||||
TEMP_TAG = "e2e-verify-temp"
|
||||
RULE_TYPE = "customer_concentration"
|
||||
|
||||
@@ -7,6 +7,30 @@ import pymysql
|
||||
|
||||
# 2026-09-12 库访问治理(ops):凭据从 backend/.env 读取,无明文兜底
|
||||
import os
|
||||
# ---- 管理员凭据同源加载(2026-09-14 cma-defaultcred-code-fix)----
|
||||
# 生产凭据只存 600 密钥文件;禁止在脚本/文档/日志里硬编码口令。
|
||||
# 顺序:$CMA_ADMIN_ENV → wecom-ops 主副本(ops 轮换落点,权威) → 本 profile 副本。
|
||||
def _admin_creds():
|
||||
"""返回 (user, password, 来源文件);未找到 → 显式报错退出(不静默退回默认口令)"""
|
||||
for p in (os.environ.get("CMA_ADMIN_ENV"),
|
||||
"/root/.hermes/profiles/wecom-ops/.secrets/cma-admin.env",
|
||||
"/root/.hermes/profiles/wecom-fullstack/.secrets/cma-admin.env"):
|
||||
if p and os.path.isfile(p):
|
||||
kv = {}
|
||||
for line in open(p, encoding="utf-8"):
|
||||
line = line.strip()
|
||||
if "=" in line and not line.startswith("#"):
|
||||
k, v = line.split("=", 1)
|
||||
kv[k.strip()] = v.strip().strip('"').strip("'")
|
||||
if kv.get("CMA_ADMIN_USER") and kv.get("CMA_ADMIN_PASS"):
|
||||
return kv["CMA_ADMIN_USER"], kv["CMA_ADMIN_PASS"], p
|
||||
raise SystemExit("[错误] 未找到 CMA 管理员凭据:请设 $CMA_ADMIN_ENV,或准备 600 权限密钥文件 "
|
||||
".secrets/cma-admin.env(键名 CMA_ADMIN_USER / CMA_ADMIN_PASS)")
|
||||
|
||||
|
||||
ADMIN_USER, ADMIN_PASS, CRED_SRC = _admin_creds()
|
||||
print(f"[creds] 来源={CRED_SRC} 账号={ADMIN_USER}(口令不回显)")
|
||||
|
||||
try:
|
||||
from dotenv import load_dotenv as _ld_gov
|
||||
_ld_gov('/root/cma-management/backend/.env')
|
||||
@@ -38,7 +62,7 @@ def get(path, token):
|
||||
|
||||
def main():
|
||||
# 1. 登录 entity1
|
||||
r = post("/api/cma/auth/login", {"username": "admin", "password": "admin123", "entity_id": 1})
|
||||
r = post("/api/cma/auth/login", {"username": ADMIN_USER, "password": ADMIN_PASS, "entity_id": 1})
|
||||
token = r.get("access_token") or r.get("token")
|
||||
assert token, f"登录失败: {r}"
|
||||
print("1. 登录 entity1 OK")
|
||||
|
||||
@@ -3,6 +3,31 @@ import json
|
||||
import sys
|
||||
import urllib.request
|
||||
|
||||
import os # noqa: E402 (凭据加载器需要)
|
||||
# ---- 管理员凭据同源加载(2026-09-14 cma-defaultcred-code-fix)----
|
||||
# 生产凭据只存 600 密钥文件;禁止在脚本/文档/日志里硬编码口令。
|
||||
# 顺序:$CMA_ADMIN_ENV → wecom-ops 主副本(ops 轮换落点,权威) → 本 profile 副本。
|
||||
def _admin_creds():
|
||||
"""返回 (user, password, 来源文件);未找到 → 显式报错退出(不静默退回默认口令)"""
|
||||
for p in (os.environ.get("CMA_ADMIN_ENV"),
|
||||
"/root/.hermes/profiles/wecom-ops/.secrets/cma-admin.env",
|
||||
"/root/.hermes/profiles/wecom-fullstack/.secrets/cma-admin.env"):
|
||||
if p and os.path.isfile(p):
|
||||
kv = {}
|
||||
for line in open(p, encoding="utf-8"):
|
||||
line = line.strip()
|
||||
if "=" in line and not line.startswith("#"):
|
||||
k, v = line.split("=", 1)
|
||||
kv[k.strip()] = v.strip().strip('"').strip("'")
|
||||
if kv.get("CMA_ADMIN_USER") and kv.get("CMA_ADMIN_PASS"):
|
||||
return kv["CMA_ADMIN_USER"], kv["CMA_ADMIN_PASS"], p
|
||||
raise SystemExit("[错误] 未找到 CMA 管理员凭据:请设 $CMA_ADMIN_ENV,或准备 600 权限密钥文件 "
|
||||
".secrets/cma-admin.env(键名 CMA_ADMIN_USER / CMA_ADMIN_PASS)")
|
||||
|
||||
|
||||
ADMIN_USER, ADMIN_PASS, CRED_SRC = _admin_creds()
|
||||
print(f"[creds] 来源={CRED_SRC} 账号={ADMIN_USER}(口令不回显)")
|
||||
|
||||
BASE = "http://127.0.0.1:8010"
|
||||
|
||||
|
||||
@@ -28,7 +53,7 @@ def post(path, body, token=None, method="POST"):
|
||||
|
||||
def main():
|
||||
# 1. 登录(账套模式 entity_id=1)
|
||||
status, login = post("/api/cma/auth/login", {"username": "admin", "password": "admin123", "entity_id": 1})
|
||||
status, login = post("/api/cma/auth/login", {"username": ADMIN_USER, "password": ADMIN_PASS, "entity_id": 1})
|
||||
token = login.get("token") or login.get("access_token")
|
||||
if status != 200 or not token:
|
||||
print("FAIL login:", status, login)
|
||||
|
||||
@@ -8,6 +8,30 @@ import os
|
||||
import json
|
||||
import urllib.request
|
||||
|
||||
# ---- 管理员凭据同源加载(2026-09-14 cma-defaultcred-code-fix)----
|
||||
# 生产凭据只存 600 密钥文件;禁止在脚本/文档/日志里硬编码口令。
|
||||
# 顺序:$CMA_ADMIN_ENV → wecom-ops 主副本(ops 轮换落点,权威) → 本 profile 副本。
|
||||
def _admin_creds():
|
||||
"""返回 (user, password, 来源文件);未找到 → 显式报错退出(不静默退回默认口令)"""
|
||||
for p in (os.environ.get("CMA_ADMIN_ENV"),
|
||||
"/root/.hermes/profiles/wecom-ops/.secrets/cma-admin.env",
|
||||
"/root/.hermes/profiles/wecom-fullstack/.secrets/cma-admin.env"):
|
||||
if p and os.path.isfile(p):
|
||||
kv = {}
|
||||
for line in open(p, encoding="utf-8"):
|
||||
line = line.strip()
|
||||
if "=" in line and not line.startswith("#"):
|
||||
k, v = line.split("=", 1)
|
||||
kv[k.strip()] = v.strip().strip('"').strip("'")
|
||||
if kv.get("CMA_ADMIN_USER") and kv.get("CMA_ADMIN_PASS"):
|
||||
return kv["CMA_ADMIN_USER"], kv["CMA_ADMIN_PASS"], p
|
||||
raise SystemExit("[错误] 未找到 CMA 管理员凭据:请设 $CMA_ADMIN_ENV,或准备 600 权限密钥文件 "
|
||||
".secrets/cma-admin.env(键名 CMA_ADMIN_USER / CMA_ADMIN_PASS)")
|
||||
|
||||
|
||||
ADMIN_USER, ADMIN_PASS, CRED_SRC = _admin_creds()
|
||||
print(f"[creds] 来源={CRED_SRC} 账号={ADMIN_USER}(口令不回显)")
|
||||
|
||||
BASE = os.getenv("CMA_BASE", "http://127.0.0.1:8010")
|
||||
|
||||
|
||||
@@ -24,7 +48,7 @@ def post(path, body, token=None):
|
||||
|
||||
def main():
|
||||
# 登录(账套模式必须 entity_id)
|
||||
login = post("/api/cma/auth/login", {"username": "admin", "password": "admin123", "entity_id": 1})
|
||||
login = post("/api/cma/auth/login", {"username": ADMIN_USER, "password": ADMIN_PASS, "entity_id": 1})
|
||||
token = login.get("token") or login.get("access_token")
|
||||
if not token:
|
||||
print("❌ 登录失败:", login)
|
||||
|
||||
@@ -15,8 +15,33 @@ import sys
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
import os # noqa: E402 (凭据加载器需要)
|
||||
# ---- 管理员凭据同源加载(2026-09-14 cma-defaultcred-code-fix)----
|
||||
# 生产凭据只存 600 密钥文件;禁止在脚本/文档/日志里硬编码口令。
|
||||
# 顺序:$CMA_ADMIN_ENV → wecom-ops 主副本(ops 轮换落点,权威) → 本 profile 副本。
|
||||
def _admin_creds():
|
||||
"""返回 (user, password, 来源文件);未找到 → 显式报错退出(不静默退回默认口令)"""
|
||||
for p in (os.environ.get("CMA_ADMIN_ENV"),
|
||||
"/root/.hermes/profiles/wecom-ops/.secrets/cma-admin.env",
|
||||
"/root/.hermes/profiles/wecom-fullstack/.secrets/cma-admin.env"):
|
||||
if p and os.path.isfile(p):
|
||||
kv = {}
|
||||
for line in open(p, encoding="utf-8"):
|
||||
line = line.strip()
|
||||
if "=" in line and not line.startswith("#"):
|
||||
k, v = line.split("=", 1)
|
||||
kv[k.strip()] = v.strip().strip('"').strip("'")
|
||||
if kv.get("CMA_ADMIN_USER") and kv.get("CMA_ADMIN_PASS"):
|
||||
return kv["CMA_ADMIN_USER"], kv["CMA_ADMIN_PASS"], p
|
||||
raise SystemExit("[错误] 未找到 CMA 管理员凭据:请设 $CMA_ADMIN_ENV,或准备 600 权限密钥文件 "
|
||||
".secrets/cma-admin.env(键名 CMA_ADMIN_USER / CMA_ADMIN_PASS)")
|
||||
|
||||
|
||||
ADMIN_USER, ADMIN_PASS, CRED_SRC = _admin_creds()
|
||||
print(f"[creds] 来源={CRED_SRC} 账号={ADMIN_USER}(口令不回显)")
|
||||
|
||||
BASE = "http://127.0.0.1:8010"
|
||||
ADMIN = {"username": "admin", "password": "admin123"}
|
||||
ADMIN = {"username": ADMIN_USER, "password": ADMIN_PASS}
|
||||
MARK = "【系统验证临时数据-验证后删除】"
|
||||
|
||||
# 规则目标:酣客 → 应收账款周转天数(F_AR_DAYS, id=6,有 2026-09 预测日志)
|
||||
|
||||
@@ -5,6 +5,31 @@
|
||||
"""
|
||||
import sys
|
||||
|
||||
import os # noqa: E402 (凭据加载器需要)
|
||||
# ---- 管理员凭据同源加载(2026-09-14 cma-defaultcred-code-fix)----
|
||||
# 生产凭据只存 600 密钥文件;禁止在脚本/文档/日志里硬编码口令。
|
||||
# 顺序:$CMA_ADMIN_ENV → wecom-ops 主副本(ops 轮换落点,权威) → 本 profile 副本。
|
||||
def _admin_creds():
|
||||
"""返回 (user, password, 来源文件);未找到 → 显式报错退出(不静默退回默认口令)"""
|
||||
for p in (os.environ.get("CMA_ADMIN_ENV"),
|
||||
"/root/.hermes/profiles/wecom-ops/.secrets/cma-admin.env",
|
||||
"/root/.hermes/profiles/wecom-fullstack/.secrets/cma-admin.env"):
|
||||
if p and os.path.isfile(p):
|
||||
kv = {}
|
||||
for line in open(p, encoding="utf-8"):
|
||||
line = line.strip()
|
||||
if "=" in line and not line.startswith("#"):
|
||||
k, v = line.split("=", 1)
|
||||
kv[k.strip()] = v.strip().strip('"').strip("'")
|
||||
if kv.get("CMA_ADMIN_USER") and kv.get("CMA_ADMIN_PASS"):
|
||||
return kv["CMA_ADMIN_USER"], kv["CMA_ADMIN_PASS"], p
|
||||
raise SystemExit("[错误] 未找到 CMA 管理员凭据:请设 $CMA_ADMIN_ENV,或准备 600 权限密钥文件 "
|
||||
".secrets/cma-admin.env(键名 CMA_ADMIN_USER / CMA_ADMIN_PASS)")
|
||||
|
||||
|
||||
ADMIN_USER, ADMIN_PASS, CRED_SRC = _admin_creds()
|
||||
print(f"[creds] 来源={CRED_SRC} 账号={ADMIN_USER}(口令不回显)")
|
||||
|
||||
env = {}
|
||||
with open('/root/cma-management/backend/.env') as f:
|
||||
for line in f:
|
||||
@@ -85,7 +110,7 @@ def api(method, path, token=None, body=None):
|
||||
return e.code, e.read().decode()
|
||||
|
||||
def login(eid):
|
||||
st, body = api("POST", "/api/cma/auth/login", body={"username": "admin", "password": "admin123", "entity_id": eid})
|
||||
st, body = api("POST", "/api/cma/auth/login", body={"username": ADMIN_USER, "password": ADMIN_PASS, "entity_id": eid})
|
||||
if st != 200:
|
||||
print(f" ⚠️ 登录 entity{eid} 失败 {st}: {body[:200]}")
|
||||
return None
|
||||
|
||||
@@ -10,6 +10,31 @@ import sys
|
||||
import time
|
||||
import urllib.request
|
||||
|
||||
import os # noqa: E402 (凭据加载器需要)
|
||||
# ---- 管理员凭据同源加载(2026-09-14 cma-defaultcred-code-fix)----
|
||||
# 生产凭据只存 600 密钥文件;禁止在脚本/文档/日志里硬编码口令。
|
||||
# 顺序:$CMA_ADMIN_ENV → wecom-ops 主副本(ops 轮换落点,权威) → 本 profile 副本。
|
||||
def _admin_creds():
|
||||
"""返回 (user, password, 来源文件);未找到 → 显式报错退出(不静默退回默认口令)"""
|
||||
for p in (os.environ.get("CMA_ADMIN_ENV"),
|
||||
"/root/.hermes/profiles/wecom-ops/.secrets/cma-admin.env",
|
||||
"/root/.hermes/profiles/wecom-fullstack/.secrets/cma-admin.env"):
|
||||
if p and os.path.isfile(p):
|
||||
kv = {}
|
||||
for line in open(p, encoding="utf-8"):
|
||||
line = line.strip()
|
||||
if "=" in line and not line.startswith("#"):
|
||||
k, v = line.split("=", 1)
|
||||
kv[k.strip()] = v.strip().strip('"').strip("'")
|
||||
if kv.get("CMA_ADMIN_USER") and kv.get("CMA_ADMIN_PASS"):
|
||||
return kv["CMA_ADMIN_USER"], kv["CMA_ADMIN_PASS"], p
|
||||
raise SystemExit("[错误] 未找到 CMA 管理员凭据:请设 $CMA_ADMIN_ENV,或准备 600 权限密钥文件 "
|
||||
".secrets/cma-admin.env(键名 CMA_ADMIN_USER / CMA_ADMIN_PASS)")
|
||||
|
||||
|
||||
ADMIN_USER, ADMIN_PASS, CRED_SRC = _admin_creds()
|
||||
print(f"[creds] 来源={CRED_SRC} 账号={ADMIN_USER}(口令不回显)")
|
||||
|
||||
BASE = "http://127.0.0.1:8010/api/cma"
|
||||
MARK = "【系统验证临时数据-验证后删除】"
|
||||
PASSED = []
|
||||
@@ -36,7 +61,7 @@ def api(method, path, token=None, body=None):
|
||||
return e.code, {"detail": raw[:300]}
|
||||
|
||||
def login(entity_id):
|
||||
st, d = api("POST", "/auth/login", body={"username": "admin", "password": "admin123", "entity_id": entity_id})
|
||||
st, d = api("POST", "/auth/login", body={"username": ADMIN_USER, "password": ADMIN_PASS, "entity_id": entity_id})
|
||||
assert st == 200, f"login entity{entity_id} fail: {d}"
|
||||
return d["token"]
|
||||
|
||||
|
||||
@@ -16,10 +16,35 @@ import time
|
||||
import urllib.request
|
||||
import websocket
|
||||
|
||||
import os # noqa: E402 (凭据加载器需要)
|
||||
# ---- 管理员凭据同源加载(2026-09-14 cma-defaultcred-code-fix)----
|
||||
# 生产凭据只存 600 密钥文件;禁止在脚本/文档/日志里硬编码口令。
|
||||
# 顺序:$CMA_ADMIN_ENV → wecom-ops 主副本(ops 轮换落点,权威) → 本 profile 副本。
|
||||
def _admin_creds():
|
||||
"""返回 (user, password, 来源文件);未找到 → 显式报错退出(不静默退回默认口令)"""
|
||||
for p in (os.environ.get("CMA_ADMIN_ENV"),
|
||||
"/root/.hermes/profiles/wecom-ops/.secrets/cma-admin.env",
|
||||
"/root/.hermes/profiles/wecom-fullstack/.secrets/cma-admin.env"):
|
||||
if p and os.path.isfile(p):
|
||||
kv = {}
|
||||
for line in open(p, encoding="utf-8"):
|
||||
line = line.strip()
|
||||
if "=" in line and not line.startswith("#"):
|
||||
k, v = line.split("=", 1)
|
||||
kv[k.strip()] = v.strip().strip('"').strip("'")
|
||||
if kv.get("CMA_ADMIN_USER") and kv.get("CMA_ADMIN_PASS"):
|
||||
return kv["CMA_ADMIN_USER"], kv["CMA_ADMIN_PASS"], p
|
||||
raise SystemExit("[错误] 未找到 CMA 管理员凭据:请设 $CMA_ADMIN_ENV,或准备 600 权限密钥文件 "
|
||||
".secrets/cma-admin.env(键名 CMA_ADMIN_USER / CMA_ADMIN_PASS)")
|
||||
|
||||
|
||||
ADMIN_USER, ADMIN_PASS, CRED_SRC = _admin_creds()
|
||||
print(f"[creds] 来源={CRED_SRC} 账号={ADMIN_USER}(口令不回显)")
|
||||
|
||||
CDP_HTTP = "http://127.0.0.1:9224"
|
||||
FIXED_URL = "http://127.0.0.1:5199"
|
||||
MULTI_URL = "http://127.0.0.1:5200"
|
||||
USER, PWD = "admin", "admin123"
|
||||
USER, PWD = ADMIN_USER, ADMIN_PASS
|
||||
|
||||
FAILS = []
|
||||
PASSES = []
|
||||
|
||||
@@ -12,6 +12,31 @@ import time
|
||||
import urllib.request
|
||||
import websocket
|
||||
|
||||
import os # noqa: E402 (凭据加载器需要)
|
||||
# ---- 管理员凭据同源加载(2026-09-14 cma-defaultcred-code-fix)----
|
||||
# 生产凭据只存 600 密钥文件;禁止在脚本/文档/日志里硬编码口令。
|
||||
# 顺序:$CMA_ADMIN_ENV → wecom-ops 主副本(ops 轮换落点,权威) → 本 profile 副本。
|
||||
def _admin_creds():
|
||||
"""返回 (user, password, 来源文件);未找到 → 显式报错退出(不静默退回默认口令)"""
|
||||
for p in (os.environ.get("CMA_ADMIN_ENV"),
|
||||
"/root/.hermes/profiles/wecom-ops/.secrets/cma-admin.env",
|
||||
"/root/.hermes/profiles/wecom-fullstack/.secrets/cma-admin.env"):
|
||||
if p and os.path.isfile(p):
|
||||
kv = {}
|
||||
for line in open(p, encoding="utf-8"):
|
||||
line = line.strip()
|
||||
if "=" in line and not line.startswith("#"):
|
||||
k, v = line.split("=", 1)
|
||||
kv[k.strip()] = v.strip().strip('"').strip("'")
|
||||
if kv.get("CMA_ADMIN_USER") and kv.get("CMA_ADMIN_PASS"):
|
||||
return kv["CMA_ADMIN_USER"], kv["CMA_ADMIN_PASS"], p
|
||||
raise SystemExit("[错误] 未找到 CMA 管理员凭据:请设 $CMA_ADMIN_ENV,或准备 600 权限密钥文件 "
|
||||
".secrets/cma-admin.env(键名 CMA_ADMIN_USER / CMA_ADMIN_PASS)")
|
||||
|
||||
|
||||
ADMIN_USER, ADMIN_PASS, CRED_SRC = _admin_creds()
|
||||
print(f"[creds] 来源={CRED_SRC} 账号={ADMIN_USER}(口令不回显)")
|
||||
|
||||
CDP_HTTP = "http://127.0.0.1:9225"
|
||||
TITLE_NEEDLE = "E2E-验证 建议中心UI实证"
|
||||
|
||||
@@ -80,7 +105,7 @@ def main():
|
||||
(async () => {
|
||||
const d = await (await fetch('/api/cma/auth/login', {method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({username:'admin', password:'admin123', entity_id:1})
|
||||
body: JSON.stringify({username:'__CMA_ADMIN_USER__', password:'__CMA_ADMIN_PASS__', entity_id:1})
|
||||
})).json();
|
||||
if (!d.token) return 'LOGIN_FAIL: ' + JSON.stringify(d).slice(0,200);
|
||||
localStorage.setItem('cma_token', d.token);
|
||||
@@ -90,7 +115,7 @@ def main():
|
||||
location.href = '/ai-suggestions';
|
||||
return 'LOGIN_OK entity=' + d.entity_id;
|
||||
})()
|
||||
"""
|
||||
""".replace("__CMA_ADMIN_USER__", ADMIN_USER).replace("__CMA_ADMIN_PASS__", ADMIN_PASS)
|
||||
print("LOGIN:", c.eval(js_login))
|
||||
time.sleep(6)
|
||||
print("URL:", c.eval("location.href"))
|
||||
|
||||
@@ -134,11 +134,15 @@ def client(db) -> Generator[TestClient, None, None]:
|
||||
|
||||
# ── 测试数据工厂 ──
|
||||
|
||||
# 测试夹具口令(仅内存 SQLite 测试库使用;不是生产默认口令——生产凭据只存 600 密钥文件
|
||||
# .secrets/cma-admin.env)。可用 $CMA_TEST_USER_PASSWORD 覆盖;用例一律从这里导入,禁止写字面量。
|
||||
TEST_USER_PASSWORD = os.environ.get("CMA_TEST_USER_PASSWORD", "test-fixture-pass")
|
||||
|
||||
def create_test_user(db: Session, **kwargs) -> User:
|
||||
"""创建测试用户"""
|
||||
defaults = {
|
||||
"username": "testadmin",
|
||||
"password_hash": hashlib.sha256("admin123".encode()).hexdigest(),
|
||||
"password_hash": hashlib.sha256(TEST_USER_PASSWORD.encode()).hexdigest(),
|
||||
"name": "测试管理员",
|
||||
"role": "ceo",
|
||||
}
|
||||
@@ -150,7 +154,7 @@ def create_test_user(db: Session, **kwargs) -> User:
|
||||
return user
|
||||
|
||||
|
||||
def get_token_for_user(client: TestClient, username: str = "testadmin", password: str = "admin123") -> str:
|
||||
def get_token_for_user(client: TestClient, username: str = "testadmin", password: str = TEST_USER_PASSWORD) -> str:
|
||||
"""获取测试用户的token(账套模式:需entity_id)"""
|
||||
resp = client.post("/api/cma/auth/login", json={
|
||||
"username": username,
|
||||
|
||||
@@ -17,7 +17,7 @@ from fastapi.testclient import TestClient
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models import Entity, UserEntity
|
||||
from tests.conftest import create_test_user, auth_header
|
||||
from tests.conftest import create_test_user, auth_header, TEST_USER_PASSWORD
|
||||
|
||||
BOT_KEY = {"X-BOT-KEY": "cma-bot-finance-2026"}
|
||||
|
||||
@@ -93,7 +93,7 @@ def _ensure_entity(db: Session, eid: int) -> Entity:
|
||||
|
||||
def _login(client: TestClient, entity_id: int = 1) -> str:
|
||||
resp = client.post("/api/cma/auth/login", json={
|
||||
"username": "testadmin", "password": "admin123", "entity_id": entity_id,
|
||||
"username": "testadmin", "password": TEST_USER_PASSWORD, "entity_id": entity_id,
|
||||
})
|
||||
assert resp.status_code == 200, f"登录 entity{entity_id} 失败: {resp.text[:300]}"
|
||||
return resp.json().get("token")
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy.orm import Session
|
||||
from tests.conftest import create_test_user, get_token_for_user, auth_header
|
||||
from tests.conftest import create_test_user, get_token_for_user, auth_header, TEST_USER_PASSWORD
|
||||
|
||||
|
||||
class TestAuth:
|
||||
@@ -16,7 +16,7 @@ class TestAuth:
|
||||
|
||||
resp = client.post("/api/cma/auth/login", json={
|
||||
"username": "testadmin",
|
||||
"password": "admin123",
|
||||
"password": TEST_USER_PASSWORD,
|
||||
"entity_id": 1,
|
||||
})
|
||||
|
||||
@@ -41,7 +41,7 @@ class TestAuth:
|
||||
"""用户不存在"""
|
||||
resp = client.post("/api/cma/auth/login", json={
|
||||
"username": "nobody",
|
||||
"password": "admin123",
|
||||
"password": TEST_USER_PASSWORD,
|
||||
})
|
||||
|
||||
assert resp.status_code == 401
|
||||
@@ -89,7 +89,7 @@ class TestAuth:
|
||||
|
||||
resp = client.post("/api/cma/auth/register", json={
|
||||
"username": "testadmin",
|
||||
"password": "admin123",
|
||||
"password": TEST_USER_PASSWORD,
|
||||
"name": "重复用户",
|
||||
})
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy.orm import Session
|
||||
from datetime import datetime
|
||||
from tests.conftest import create_test_user, get_token_for_user, auth_header, create_test_kpi
|
||||
from tests.conftest import create_test_user, get_token_for_user, auth_header, create_test_kpi, TEST_USER_PASSWORD
|
||||
from app.models import KPIAlert, KPIValue
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ class TestDashboard:
|
||||
|
||||
def test_my_kpis(self, client: TestClient, db: Session):
|
||||
user = create_test_user(db, username="biz_user", name="业务经理", role="business")
|
||||
token = get_token_for_user(client, username="biz_user", password="admin123")
|
||||
token = get_token_for_user(client, username="biz_user", password=TEST_USER_PASSWORD)
|
||||
kpi = create_test_kpi(db, responsible_user="biz_user")
|
||||
kpi2 = create_test_kpi(db, kpi_code="F_OTHER", kpi_name="无关KPI", responsible_user="其他人")
|
||||
resp = client.get("/api/cma/dashboard/my-kpis", headers=auth_header(token))
|
||||
@@ -82,7 +82,7 @@ class TestDashboard:
|
||||
|
||||
def test_my_kpis_ceo_sees_all(self, client: TestClient, db: Session):
|
||||
user = create_test_user(db, username="ceo_user", name="CEO", role="ceo")
|
||||
token = get_token_for_user(client, username="ceo_user", password="admin123")
|
||||
token = get_token_for_user(client, username="ceo_user", password=TEST_USER_PASSWORD)
|
||||
kpi = create_test_kpi(db, kpi_code="F_KPI_A", responsible_user="张三")
|
||||
resp = client.get("/api/cma/dashboard/my-kpis", headers=auth_header(token))
|
||||
assert resp.status_code == 200
|
||||
@@ -103,7 +103,7 @@ class TestDashboard:
|
||||
|
||||
def test_my_dashboard(self, client: TestClient, db: Session):
|
||||
user = create_test_user(db, username="ceo_user", name="CEO", role="ceo")
|
||||
token = get_token_for_user(client, username="ceo_user", password="admin123")
|
||||
token = get_token_for_user(client, username="ceo_user", password=TEST_USER_PASSWORD)
|
||||
kpi = create_test_kpi(db)
|
||||
resp = client.get("/api/cma/dashboard/my-dashboard", headers=auth_header(token))
|
||||
assert resp.status_code == 200
|
||||
|
||||
@@ -13,7 +13,7 @@ from fastapi.testclient import TestClient
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models import KPIDefinition, KPIValue, DataSourceConfig, Entity
|
||||
from tests.conftest import create_test_user, get_token_for_user, auth_header
|
||||
from tests.conftest import create_test_user, get_token_for_user, auth_header, TEST_USER_PASSWORD
|
||||
|
||||
DATA = "/api/cma/data"
|
||||
|
||||
@@ -65,7 +65,7 @@ class TestExcelImportEntityIsolation:
|
||||
k1 = self._mk_kpi(db, "F_REVENUE", 1)
|
||||
_ensure_entity(db, 2)
|
||||
create_test_user(db)
|
||||
token2 = _login(client, "testadmin", "admin123", entity_id=2)
|
||||
token2 = _login(client, "testadmin", TEST_USER_PASSWORD, entity_id=2)
|
||||
before = db.query(KPIValue).filter(KPIValue.kpi_id == k1.id).count()
|
||||
|
||||
df = pd.DataFrame({
|
||||
@@ -91,7 +91,7 @@ class TestExcelImportEntityIsolation:
|
||||
_ensure_entity(db, 2)
|
||||
k1 = self._mk_kpi(db, "F_REVENUE", 1)
|
||||
create_test_user(db)
|
||||
token2 = _login(client, "testadmin", "admin123", entity_id=2)
|
||||
token2 = _login(client, "testadmin", TEST_USER_PASSWORD, entity_id=2)
|
||||
|
||||
df = pd.DataFrame({
|
||||
"kpi_code": ["F_REVENUE"],
|
||||
@@ -156,7 +156,7 @@ class TestSourceCRUDEntityIsolation:
|
||||
_ensure_entity(db, 2)
|
||||
create_test_user(db)
|
||||
token1 = get_token_for_user(client)
|
||||
token2 = _login(client, "testadmin", "admin123", entity_id=2)
|
||||
token2 = _login(client, "testadmin", TEST_USER_PASSWORD, entity_id=2)
|
||||
|
||||
r1 = client.post(f"{DATA}/sources", headers=auth_header(token1),
|
||||
json={"name": "entity1数据源", "source_type": "manual"})
|
||||
@@ -178,7 +178,7 @@ class TestSourceCRUDEntityIsolation:
|
||||
_ensure_entity(db, 2)
|
||||
create_test_user(db)
|
||||
token1 = get_token_for_user(client)
|
||||
token2 = _login(client, "testadmin", "admin123", entity_id=2)
|
||||
token2 = _login(client, "testadmin", TEST_USER_PASSWORD, entity_id=2)
|
||||
|
||||
r1 = client.post(f"{DATA}/sources", headers=auth_header(token1),
|
||||
json={"name": "entity1的源", "source_type": "manual"})
|
||||
|
||||
@@ -13,7 +13,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.models import Entity, KPIValue, User, UserEntity
|
||||
from app.utils import kpi_forecast_engine as engine
|
||||
from tests.conftest import create_test_kpi, create_test_user, get_token_for_user, auth_header
|
||||
from tests.conftest import create_test_kpi, create_test_user, get_token_for_user, auth_header, TEST_USER_PASSWORD
|
||||
|
||||
BASE = "/api/cma/predict"
|
||||
|
||||
@@ -34,7 +34,7 @@ def _token_for_entity(client: TestClient, db: Session, eid: int) -> str:
|
||||
db.add(UserEntity(user_id=user.id, entity_id=eid))
|
||||
db.commit()
|
||||
resp = client.post("/api/cma/auth/login", json={
|
||||
"username": "testadmin", "password": "admin123", "entity_id": eid})
|
||||
"username": "testadmin", "password": TEST_USER_PASSWORD, "entity_id": eid})
|
||||
assert resp.status_code == 200, f"登录 entity{eid} 失败: {resp.text[:200]}"
|
||||
return resp.json()["token"]
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from datetime import date
|
||||
|
||||
from tests.conftest import (
|
||||
create_test_user, get_token_for_user, auth_header, create_test_kpi,
|
||||
TEST_USER_PASSWORD,
|
||||
)
|
||||
from app.models import Objective, KR, KPIDefinition, Entity
|
||||
|
||||
@@ -253,7 +254,7 @@ class TestMultiTenant:
|
||||
# 用 entity 2 登录
|
||||
ent2_user = create_test_user(db, username="user2", role="ceo")
|
||||
resp2 = client.post("/api/cma/auth/login", json={
|
||||
"username": "user2", "password": "admin123", "entity_id": 2})
|
||||
"username": "user2", "password": TEST_USER_PASSWORD, "entity_id": 2})
|
||||
token2 = resp2.json().get("token")
|
||||
assert token2
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy.orm import Session
|
||||
from app.models import Entity, User, UserEntity, StrategicMap, StrategicMapVersion
|
||||
from tests.conftest import create_test_user, get_token_for_user, auth_header
|
||||
from tests.conftest import create_test_user, get_token_for_user, auth_header, TEST_USER_PASSWORD
|
||||
|
||||
|
||||
def _create_second_entity(db: Session, user: User) -> int:
|
||||
@@ -18,7 +18,7 @@ def _create_second_entity(db: Session, user: User) -> int:
|
||||
|
||||
def _token_for_entity(client: TestClient, entity_id: int) -> str:
|
||||
resp = client.post("/api/cma/auth/login", json={
|
||||
"username": "testadmin", "password": "admin123", "entity_id": entity_id,
|
||||
"username": "testadmin", "password": TEST_USER_PASSWORD, "entity_id": entity_id,
|
||||
})
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
|
||||
@@ -5,7 +5,7 @@ import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy.orm import Session
|
||||
from app.models import Entity, User, UserEntity
|
||||
from tests.conftest import create_test_user, get_token_for_user, auth_header
|
||||
from tests.conftest import create_test_user, get_token_for_user, auth_header, TEST_USER_PASSWORD
|
||||
|
||||
|
||||
def _create_second_entity(db: Session, user: User) -> int:
|
||||
@@ -20,7 +20,7 @@ def _token_for_entity(client: TestClient, entity_id: int) -> str:
|
||||
"""以指定账套登录获取token"""
|
||||
resp = client.post("/api/cma/auth/login", json={
|
||||
"username": "testadmin",
|
||||
"password": "admin123",
|
||||
"password": TEST_USER_PASSWORD,
|
||||
"entity_id": entity_id,
|
||||
})
|
||||
assert resp.status_code == 200
|
||||
|
||||
@@ -15,7 +15,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.models import KPIDefinition, KPIValue, KPIAlert, ActionPlan, AISuggestion, Entity, UserEntity, ActualCost
|
||||
from app.api import ai_analysis
|
||||
from tests.conftest import create_test_kpi, create_test_user, get_token_for_user, auth_header
|
||||
from tests.conftest import create_test_kpi, create_test_user, get_token_for_user, auth_header, TEST_USER_PASSWORD
|
||||
|
||||
|
||||
def _ensure_entity(db: Session, eid: int) -> Entity:
|
||||
@@ -44,7 +44,7 @@ def _seed_user(db: Session, entity_id: int):
|
||||
def _token(client: TestClient, entity_id: int) -> str:
|
||||
"""登录并返回指定账套的 token(get_token_for_user 固定 entity1,这里手动登录)"""
|
||||
resp = client.post("/api/cma/auth/login", json={
|
||||
"username": "testadmin", "password": "admin123", "entity_id": entity_id,
|
||||
"username": "testadmin", "password": TEST_USER_PASSWORD, "entity_id": entity_id,
|
||||
})
|
||||
assert resp.status_code == 200, f"登录 entity{entity_id} 失败: {resp.text[:300]}"
|
||||
return resp.json().get("token")
|
||||
|
||||
@@ -14,7 +14,7 @@ from fastapi.testclient import TestClient
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models import CashForecast, ForecastAccuracy, Entity, User, UserEntity
|
||||
from tests.conftest import create_test_user, get_token_for_user, auth_header
|
||||
from tests.conftest import create_test_user, get_token_for_user, auth_header, TEST_USER_PASSWORD
|
||||
|
||||
BASE = "/api/cma/predict"
|
||||
|
||||
@@ -35,7 +35,7 @@ def _token_for_entity(client: TestClient, db: Session, eid: int) -> str:
|
||||
db.add(UserEntity(user_id=user.id, entity_id=eid))
|
||||
db.commit()
|
||||
resp = client.post("/api/cma/auth/login", json={
|
||||
"username": "testadmin", "password": "admin123", "entity_id": eid})
|
||||
"username": "testadmin", "password": TEST_USER_PASSWORD, "entity_id": eid})
|
||||
assert resp.status_code == 200, f"登录 entity{eid} 失败: {resp.text[:200]}"
|
||||
return resp.json()["token"]
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ generate(weekly/monthly/special) / history / history/{id}
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy.orm import Session
|
||||
from tests.conftest import create_test_user, get_token_for_user, auth_header
|
||||
from tests.conftest import create_test_user, get_token_for_user, auth_header, TEST_USER_PASSWORD
|
||||
from app.models import (
|
||||
KPIDefinition, KPIValue, BudgetPlan, StrategicMap, KPIAlert, ActionPlan,
|
||||
Subject, ReportHistory,
|
||||
@@ -668,7 +668,7 @@ class TestDupont:
|
||||
db.add(UserEntity(user_id=user.id, entity_id=entity_id))
|
||||
db.commit()
|
||||
resp = client.post("/api/cma/auth/login", json={
|
||||
"username": "testadmin", "password": "admin123", "entity_id": entity_id,
|
||||
"username": "testadmin", "password": TEST_USER_PASSWORD, "entity_id": entity_id,
|
||||
})
|
||||
assert resp.status_code == 200, f"账套登录失败({entity_id}): {resp.status_code} {resp.text[:200]}"
|
||||
return resp.json()["token"]
|
||||
|
||||
@@ -16,7 +16,7 @@ from app.models import KPIDefinition, KPIAlert, Entity
|
||||
from app.models.knowledge import KnowledgeSummary, KnowledgeEvent
|
||||
from app.models.knowledge_article import KnowledgeArticle
|
||||
from app.services import knowledge_service
|
||||
from tests.conftest import create_test_kpi, create_test_user, get_token_for_user, auth_header
|
||||
from tests.conftest import create_test_kpi, create_test_user, get_token_for_user, auth_header, TEST_USER_PASSWORD
|
||||
|
||||
SUMMARY_FIELDS = dict(
|
||||
level="daily", period_key="2026-09-01", content="测试摘要内容",
|
||||
@@ -35,7 +35,7 @@ def _ensure_entity(db: Session, eid: int, name: str = None) -> Entity:
|
||||
|
||||
|
||||
def _login_token(client: TestClient, username: str = "testadmin",
|
||||
password: str = "admin123", entity_id: int = 1) -> str:
|
||||
password: str = TEST_USER_PASSWORD, entity_id: int = 1) -> str:
|
||||
"""登录指定账套返回 token(conftest.get_token_for_user 固定 entity1,这里参数化)"""
|
||||
resp = client.post("/api/cma/auth/login", json={
|
||||
"username": username, "password": password, "entity_id": entity_id,
|
||||
|
||||
@@ -13,7 +13,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.models import KPIDefinition, KPIAlert, Entity, UserEntity
|
||||
from app.api.alert_rules import AlertRule, _eval_threshold
|
||||
from tests.conftest import create_test_kpi, create_test_user, get_token_for_user, auth_header
|
||||
from tests.conftest import create_test_kpi, create_test_user, get_token_for_user, auth_header, TEST_USER_PASSWORD
|
||||
|
||||
BOT_KEY = {"X-BOT-KEY": "cma-bot-finance-2026"}
|
||||
|
||||
@@ -103,7 +103,7 @@ class TestAlertRulesIsolation:
|
||||
k1 = _seed_entity_kpi(db, entity_id=1, code="AR_E1")
|
||||
db.commit()
|
||||
create_test_user(db)
|
||||
token = get_token_for_user(client, "testadmin", "admin123")
|
||||
token = get_token_for_user(client, "testadmin", TEST_USER_PASSWORD)
|
||||
headers = auth_header(token)
|
||||
|
||||
resp = client.post("/api/cma/alert-rules", headers=headers, json={
|
||||
@@ -128,7 +128,7 @@ class TestAlertRulesIsolation:
|
||||
entity_id=1))
|
||||
db.commit()
|
||||
create_test_user(db)
|
||||
token = get_token_for_user(client, "testadmin", "admin123")
|
||||
token = get_token_for_user(client, "testadmin", TEST_USER_PASSWORD)
|
||||
|
||||
r1 = client.get(f"/api/cma/alert-rules/kpi/{k1.id}", headers=auth_header(token))
|
||||
assert r1.status_code == 200
|
||||
@@ -149,7 +149,7 @@ class TestReportsIsolation:
|
||||
data_status="verified", entity_id=1))
|
||||
db.commit()
|
||||
create_test_user(db)
|
||||
token = get_token_for_user(client, "testadmin", "admin123")
|
||||
token = get_token_for_user(client, "testadmin", TEST_USER_PASSWORD)
|
||||
|
||||
r1 = client.get("/api/cma/reports/profit-summary?period=2026-06", headers=auth_header(token))
|
||||
assert r1.status_code == 200
|
||||
@@ -164,7 +164,7 @@ class TestReportsIsolation:
|
||||
_seed_entity_kpi(db, entity_id=2, code="TR_E2")
|
||||
db.commit()
|
||||
create_test_user(db)
|
||||
token = get_token_for_user(client, "testadmin", "admin123")
|
||||
token = get_token_for_user(client, "testadmin", TEST_USER_PASSWORD)
|
||||
|
||||
r1 = client.get("/api/cma/reports/kpi-trends", headers=auth_header(token))
|
||||
assert r1.status_code == 200
|
||||
@@ -212,7 +212,7 @@ class TestCheckGovernanceRetired:
|
||||
_seed_entity_kpi(db, entity_id=1, code="GOV_KPI")
|
||||
db.commit()
|
||||
create_test_user(db)
|
||||
token = get_token_for_user(client, "testadmin", "admin123")
|
||||
token = get_token_for_user(client, "testadmin", TEST_USER_PASSWORD)
|
||||
|
||||
resp = client.get("/api/cma/data-quality/check-governance", headers=auth_header(token))
|
||||
assert resp.status_code == 404, f"应 404,实际 {resp.status_code}: {resp.text[:200]}"
|
||||
@@ -225,7 +225,7 @@ class TestCheckGovernanceRetired:
|
||||
_seed_entity_kpi(db, entity_id=1, code="GOV2_KPI")
|
||||
db.commit()
|
||||
create_test_user(db)
|
||||
token = get_token_for_user(client, "testadmin", "admin123")
|
||||
token = get_token_for_user(client, "testadmin", TEST_USER_PASSWORD)
|
||||
|
||||
resp = client.get("/api/cma/data-quality/governance-check", headers=auth_header(token))
|
||||
assert resp.status_code == 200, resp.text[:300]
|
||||
@@ -242,7 +242,7 @@ class TestCrossEntityEndpointAuth:
|
||||
def _login(self, client, db):
|
||||
_ensure_entity(db, 2)
|
||||
create_test_user(db)
|
||||
return get_token_for_user(client, "testadmin", "admin123") # 登录账套 entity_id=1
|
||||
return get_token_for_user(client, "testadmin", TEST_USER_PASSWORD) # 登录账套 entity_id=1
|
||||
|
||||
# ── 1. data-quality/governance-check ──────────────────
|
||||
def test_governance_check_cross_entity_403(self, client, db):
|
||||
@@ -352,7 +352,7 @@ class TestDupontSessionScope:
|
||||
def _login(self, client, db):
|
||||
_ensure_entity(db, 2)
|
||||
create_test_user(db)
|
||||
return get_token_for_user(client, "testadmin", "admin123") # 登录账套 entity_id=1
|
||||
return get_token_for_user(client, "testadmin", TEST_USER_PASSWORD) # 登录账套 entity_id=1
|
||||
|
||||
def test_dupont_cross_entity_bohai_403(self, client, db):
|
||||
"""酣客会话显式 entity=bohai(博海真实KPI)→ 403(旧豁免已撤销)"""
|
||||
|
||||
Reference in New Issue
Block a user